summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.mailmap1
-rw-r--r--.travis.yml3
-rw-r--r--CHANGELOG18
-rw-r--r--configure.ac2
-rw-r--r--include/urweb/urweb_cpp.h15
-rw-r--r--lib/js/urweb.js68
-rw-r--r--lib/ur/basis.urs15
-rw-r--r--lib/ur/top.ur20
-rw-r--r--src/c/memmem.c16
-rw-r--r--src/c/openssl.c32
-rw-r--r--src/c/request.c4
-rw-r--r--src/c/urweb.c59
-rw-r--r--src/compiler.sml2
-rw-r--r--src/elisp/urweb-mode.el1
-rw-r--r--src/main.mlton.sml10
-rw-r--r--src/mysql.sml4
-rw-r--r--src/postgres.sml12
-rw-r--r--src/settings.sml13
-rw-r--r--tests/math.ur26
-rw-r--r--tests/normalizeTable.ur50
-rw-r--r--tests/normalizeTable.urp1
-rw-r--r--tests/normalizeTable.urs1
-rw-r--r--tests/timeout.ur22
-rw-r--r--tests/timeout.urp7
-rw-r--r--tests/timeout.urs1
25 files changed, 355 insertions, 48 deletions
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..abfaa565
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+<bbaren@mit.edu> <bbaren at mit.edu>
diff --git a/.travis.yml b/.travis.yml
index 0b2b8b90..df4e4abc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,8 @@ before_install:
- if command -v apt-get &>/dev/null; then sudo apt-get update -qq; fi
- if command -v apt-get &>/dev/null; then sudo apt-get install -y mlton; fi
- if command -v brew &>/dev/null; then brew update; fi
- - if command -v brew &>/dev/null; then brew tap MLton/mlton; fi
+ - if command -v brew &>/dev/null; then brew uninstall libtool; fi
+ - if command -v brew &>/dev/null; then brew install libtool; fi
- if command -v brew &>/dev/null; then brew install openssl mlton; fi
- if command -v brew &>/dev/null; then export CONFIGURE_ARGS="--with-openssl=/usr/local/opt/openssl"; fi
diff --git a/CHANGELOG b/CHANGELOG
index bec77354..f5c92708 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,22 @@
========
+20160805
+========
+
+- Compatibility fixes for C compilers and OpenSSL
+- Starting to change SQL functions to return results in most natural order
+ - Step 1: queryL
+- Bug fixes
+
+========
+20160621
+========
+
+- Client-side: detect session timeout and ask the user to reload
+- New Basis math functions: abs, acos, asin, atan, atan2, cos, exp, floor, log, pow, sqrt, sin
+- Compatibility fixes for newer C and SML compilers
+- Bug fixes
+
+========
20160515
========
diff --git a/configure.ac b/configure.ac
index 2a09d3b5..196d597c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT([urweb], [20160515])
+AC_INIT([urweb], [20160805])
WORKING_VERSION=0
AC_USE_SYSTEM_EXTENSIONS
diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h
index 5b6c6221..0d5f5e0e 100644
--- a/include/urweb/urweb_cpp.h
+++ b/include/urweb/urweb_cpp.h
@@ -387,6 +387,19 @@ uw_Basis_float uw_Basis_floatFromInt(struct uw_context *, uw_Basis_int);
uw_Basis_int uw_Basis_ceil(struct uw_context *, uw_Basis_float);
uw_Basis_int uw_Basis_trunc(struct uw_context *, uw_Basis_float);
uw_Basis_int uw_Basis_round(struct uw_context *, uw_Basis_float);
+uw_Basis_int uw_Basis_floor(struct uw_context *, uw_Basis_float);
+
+uw_Basis_float uw_Basis_pow(struct uw_context *, uw_Basis_float, uw_Basis_float);
+uw_Basis_float uw_Basis_sqrt(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_sin(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_cos(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_log(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_exp(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_asin(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_acos(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_atan(struct uw_context *, uw_Basis_float);
+uw_Basis_float uw_Basis_atan2(struct uw_context *, uw_Basis_float, uw_Basis_float);
+uw_Basis_float uw_Basis_abs(struct uw_context *, uw_Basis_float);
uw_Basis_string uw_Basis_atom(struct uw_context *, uw_Basis_string);
uw_Basis_string uw_Basis_css_url(struct uw_context *, uw_Basis_string);
@@ -417,4 +430,6 @@ uw_Sqlcache_Value *uw_Sqlcache_check(struct uw_context *, uw_Sqlcache_Cache *, c
void *uw_Sqlcache_store(struct uw_context *, uw_Sqlcache_Cache *, char **, uw_Sqlcache_Value *);
void *uw_Sqlcache_flush(struct uw_context *, uw_Sqlcache_Cache *, char **);
+int strcmp_nullsafe(const char *, const char *);
+
#endif
diff --git a/lib/js/urweb.js b/lib/js/urweb.js
index 410a0e23..68e7979d 100644
--- a/lib/js/urweb.js
+++ b/lib/js/urweb.js
@@ -116,6 +116,48 @@ function pow(n, m) {
return Math.pow(n, m);
}
+function sqrt(n){
+ return Math.sqrt(n);
+}
+
+function sin(n){
+ return Math.sin(n);
+}
+
+function cos(n){
+ return Math.cos(n);
+}
+
+function log(n){
+ return Math.log(n);
+}
+
+function exp(n){
+ return Math.exp(n);
+}
+
+function asin(n){
+ return Math.asin(n);
+}
+function acos(n){
+ return Math.acos(n);
+}
+
+function atan(n){
+ return Math.atan(n);
+}
+
+function atan2(n, m){
+ return Math.atan2(n, m);
+}
+
+function floor(n){
+ return Math.floor(n);
+}
+
+function abs(n){
+ return Math.abs(n);
+}
// Time, represented as counts of microseconds since the epoch
@@ -837,10 +879,12 @@ function normalizeTable(table) {
for (script = table.firstChild; script && script != tbody; script = next) {
next = script.nextSibling;
- if (firstChild)
- tbody.insertBefore(script, firstChild);
- else
- tbody.appendChild(script);
+ if (script.tagName === "SCRIPT") {
+ if (firstChild)
+ tbody.insertBefore(script, firstChild);
+ else
+ tbody.appendChild(script);
+ }
}
return;
@@ -1662,10 +1706,11 @@ function newChannel() {
function listener() {
var uri = path_join(url_prefix, ".msgs");
var xhr = getXHR();
- var tid, orsc, onTimeout;
+ var tid, orsc, onTimeout, lastTick;
var connect = function () {
xhr.onreadystatechange = orsc;
+ lastTick = new Date().getTime();
tid = window.setTimeout(onTimeout, timeout * 500);
requestUri(xhr, uri, false, false);
}
@@ -1755,8 +1800,19 @@ function listener() {
};
onTimeout = function() {
+ var thisTick = new Date().getTime();
xhrFinished(xhr);
- connect();
+
+ if (thisTick - lastTick > timeout * 1000) {
+ if (confirm("The session for this page has expired. Please choose \"OK\" to reload.")) {
+ if (isPost)
+ history.back();
+ else
+ location.reload();
+ }
+ } else {
+ connect();
+ }
};
connect();
diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs
index 883cc5b1..1163daed 100644
--- a/lib/ur/basis.urs
+++ b/lib/ur/basis.urs
@@ -152,7 +152,20 @@ val float : int -> float
val ceil : float -> int
val trunc : float -> int
val round : float -> int
-
+val floor : float -> int
+
+(** * Basic Math *)
+
+val sqrt : float -> float
+val sin : float -> float
+val cos : float -> float
+val log : float -> float
+val exp : float -> float
+val asin : float -> float
+val acos : float -> float
+val atan : float -> float
+val atan2 : float -> float -> float
+val abs: float -> float
(** * Time *)
diff --git a/lib/ur/top.ur b/lib/ur/top.ur
index e831b4f7..6c6c896c 100644
--- a/lib/ur/top.ur
+++ b/lib/ur/top.ur
@@ -225,15 +225,23 @@ fun query1' [t ::: Name] [fs ::: {Type}] [state ::: Type] (q : sql_query [] [] [
(f : $fs -> state -> state) (i : state) =
query q (fn r s => return (f r.t s)) i
+val rev = fn [a] =>
+ let
+ fun rev' acc (ls : list a) =
+ case ls of
+ [] => acc
+ | x :: ls => rev' (x :: acc) ls
+ in
+ rev' []
+ end
+
fun queryL [tables] [exps] [tables ~ exps] (q : sql_query [] [] tables exps) =
- query q
- (fn r ls => return (r :: ls))
- []
+ ls <- query q (fn r ls => return (r :: ls)) [];
+ return (rev ls)
fun queryL1 [t ::: Name] [fs ::: {Type}] (q : sql_query [] [] [t = fs] []) =
- query q
- (fn r ls => return (r.t :: ls))
- []
+ ls <- query q (fn r ls => return (r.t :: ls)) [];
+ return (rev ls)
fun queryI [tables ::: {{Type}}] [exps ::: {Type}]
[tables ~ exps] (q : sql_query [] [] tables exps)
diff --git a/src/c/memmem.c b/src/c/memmem.c
index 68526714..f31f4e31 100644
--- a/src/c/memmem.c
+++ b/src/c/memmem.c
@@ -38,6 +38,8 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
+// Function renamed by Adam Chlipala in 2016.
+
#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
__RCSID("$NetBSD$");
@@ -53,13 +55,17 @@ __RCSID("$NetBSD$");
#endif
/*
- * memmem() returns the location of the first occurence of data
+ * urweb_memmem() returns the location of the first occurence of data
* pattern b2 of size len2 in memory block b1 of size len1 or
* NULL if none is found.
*/
void *
-memmem(const void *b1, size_t len1, const void *b2, size_t len2)
+urweb_memmem(const void *b1, size_t len1, const void *b2, size_t len2)
{
+ /* Sanity check */
+ if(!(b1 != NULL && b2 != NULL && len1 != 0 && len2 != 0))
+ return NULL;
+
/* Initialize search pointer */
char *sp = (char *) b1;
@@ -69,16 +75,12 @@ memmem(const void *b1, size_t len1, const void *b2, size_t len2)
/* Intialize end of search address space pointer */
char *eos = sp + len1 - len2;
- /* Sanity check */
- if(!(b1 && b2 && len1 && len2))
- return NULL;
-
while (sp <= eos) {
if (*sp == *pp)
if (memcmp(sp, pp, len2) == 0)
return sp;
- sp++;
+ sp++;
}
return NULL;
diff --git a/src/c/openssl.c b/src/c/openssl.c
index 15c4de5e..5982b831 100644
--- a/src/c/openssl.c
+++ b/src/c/openssl.c
@@ -1,6 +1,5 @@
#include "config.h"
-#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
@@ -8,17 +7,13 @@
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
-#include <pthread.h>
-#include <openssl/crypto.h>
+#include <openssl/opensslv.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#define PASSSIZE 4
-// OpenSSL locks array. See threads(3SSL).
-static pthread_mutex_t *openssl_locks;
-
int uw_hash_blocksize = 32;
static int password[PASSSIZE];
@@ -33,6 +28,17 @@ static void random_password() {
}
}
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+// We're using OpenSSL <1.1, so we need to specify threading callbacks. See
+// threads(3SSL).
+
+#include <assert.h>
+#include <pthread.h>
+
+#include <openssl/crypto.h>
+
+static pthread_mutex_t *openssl_locks;
+
// OpenSSL callbacks
#ifdef PTHREAD_T_IS_POINTER
static void thread_id(CRYPTO_THREADID *const result) {
@@ -60,7 +66,7 @@ static void lock_or_unlock(const int mode, const int type, const char *file,
}
}
-void uw_init_crypto() {
+static void init_openssl() {
int i;
// Set up OpenSSL.
assert(openssl_locks == NULL);
@@ -74,6 +80,18 @@ void uw_init_crypto() {
}
CRYPTO_THREADID_set_callback(thread_id);
CRYPTO_set_locking_callback(lock_or_unlock);
+}
+
+#else
+// We're using OpenSSL >=1.1, which is thread-safe by default. We don't need to
+// do anything here.
+
+static void init_openssl() {}
+
+#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
+
+void uw_init_crypto() {
+ init_openssl();
// Prepare signatures.
if (uw_sig_file) {
int fd;
diff --git a/src/c/request.c b/src/c/request.c
index cad84cb2..a7f23851 100644
--- a/src/c/request.c
+++ b/src/c/request.c
@@ -16,7 +16,7 @@
#define MAX_RETRIES 5
-void *memmem(const void *b1, size_t len1, const void *b2, size_t len2);
+void *urweb_memmem(const void *b1, size_t len1, const void *b2, size_t len2);
static int try_rollback(uw_context ctx, int will_retry, void *logger_data, uw_logger log_error) {
int r = uw_rollback(ctx, will_retry);
@@ -418,7 +418,7 @@ request_result uw_request(uw_request_context rc, uw_context ctx,
}
}
- part = memmem(after_sub_headers, body + body_len - after_sub_headers, boundary, boundary_len);
+ part = urweb_memmem(after_sub_headers, body + body_len - after_sub_headers, boundary, boundary_len);
if (!part) {
log_error(logger_data, "Missing boundary after multipart payload\n");
return FAILED;
diff --git a/src/c/urweb.c b/src/c/urweb.c
index c23366fb..afe8457b 100644
--- a/src/c/urweb.c
+++ b/src/c/urweb.c
@@ -4517,6 +4517,54 @@ uw_Basis_int uw_Basis_round(uw_context ctx, uw_Basis_float n) {
return round(n);
}
+uw_Basis_int uw_Basis_floor(uw_context ctx, uw_Basis_float n) {
+ return floor(n);
+}
+
+uw_Basis_float uw_Basis_pow(uw_context ctx, uw_Basis_float n, uw_Basis_float m) {
+ return pow(n,m);
+}
+
+uw_Basis_float uw_Basis_sqrt(uw_context ctx, uw_Basis_float n) {
+ return sqrt(n);
+}
+
+uw_Basis_float uw_Basis_sin(uw_context ctx, uw_Basis_float n) {
+ return sin(n);
+}
+
+uw_Basis_float uw_Basis_cos(uw_context ctx, uw_Basis_float n) {
+ return cos(n);
+}
+
+uw_Basis_float uw_Basis_log(uw_context ctx, uw_Basis_float n) {
+ return log(n);
+}
+
+uw_Basis_float uw_Basis_exp(uw_context ctx, uw_Basis_float n) {
+ return exp(n);
+}
+
+uw_Basis_float uw_Basis_asin(uw_context ctx, uw_Basis_float n) {
+ return asin(n);
+}
+
+uw_Basis_float uw_Basis_acos(uw_context ctx, uw_Basis_float n) {
+ return acos(n);
+}
+
+uw_Basis_float uw_Basis_atan(uw_context ctx, uw_Basis_float n) {
+ return atan(n);
+}
+
+uw_Basis_float uw_Basis_atan2(uw_context ctx, uw_Basis_float n, uw_Basis_float m) {
+ return atan2(n, m);
+}
+
+uw_Basis_float uw_Basis_abs(uw_context ctx, uw_Basis_float n) {
+ return fabs(n);
+}
+
uw_Basis_string uw_Basis_atom(uw_context ctx, uw_Basis_string s) {
char *p;
@@ -4713,7 +4761,7 @@ uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, c
char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
char *buf = key;
time_t timeInvalid = cache->timeInvalid;
- uw_Sqlcache_Entry *entry;
+ uw_Sqlcache_Entry *entry = NULL;
if (numKeys == 0) {
entry = cache->table;
if (!entry) {
@@ -4748,7 +4796,7 @@ static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw
pthread_rwlock_wrlock(&cache->lockIn);
size_t numKeys = cache->numKeys;
time_t timeNow = uw_Sqlcache_getTimeNow(cache);
- uw_Sqlcache_Entry *entry;
+ uw_Sqlcache_Entry *entry = NULL;
if (numKeys == 0) {
entry = cache->table;
if (!entry) {
@@ -4920,3 +4968,10 @@ void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) {
}
pthread_rwlock_unlock(&cache->lockIn);
}
+
+int strcmp_nullsafe(const char *str1, const char *str2) {
+ if (str1)
+ return strcmp(str1, str2);
+ else
+ return 1;
+}
diff --git a/src/compiler.sml b/src/compiler.sml
index 76743fad..dccda06d 100644
--- a/src/compiler.sml
+++ b/src/compiler.sml
@@ -434,7 +434,7 @@ fun parseUrp' accLibs fname =
sql = NONE,
debug = Settings.getDebug (),
profile = false,
- timeout = 60,
+ timeout = 120,
ffi = [],
link = [],
linker = NONE,
diff --git a/src/elisp/urweb-mode.el b/src/elisp/urweb-mode.el
index bc71a052..d1eec2a1 100644
--- a/src/elisp/urweb-mode.el
+++ b/src/elisp/urweb-mode.el
@@ -395,7 +395,6 @@ This mode runs `urweb-mode-hook' just before exiting.
;; Treat paragraph-separators in comments as paragraph-separators.
(set (make-local-variable 'paragraph-separate)
(concat "\\([ \t]*\\*)?\\)?\\(" paragraph-separate "\\)"))
- (set (make-local-variable 'require-final-newline) t)
;; forward-sexp-function is an experimental variable in my hacked Emacs.
(set (make-local-variable 'forward-sexp-function) 'urweb-user-forward-sexp)
;; For XEmacs
diff --git a/src/main.mlton.sml b/src/main.mlton.sml
index f595134f..6d368106 100644
--- a/src/main.mlton.sml
+++ b/src/main.mlton.sml
@@ -246,7 +246,7 @@ fun oneRun args =
fun send (sock, s) =
let
- val n = Socket.sendVec (sock, Word8VectorSlice.full (Vector.map (Word8.fromInt o ord) s))
+ val n = Socket.sendVec (sock, Word8VectorSlice.full (MLton.Word8Vector.fromPoly (Vector.map (Word8.fromInt o ord) (MLton.CharVector.toPoly s))))
in
if n >= size s then
()
@@ -272,7 +272,7 @@ val () = case CommandLine.arguments () of
val s = if CharVector.exists (fn ch => ch = #"\n") buf then
""
else
- Vector.map (chr o Word8.toInt) (Socket.recvVec (sock, 1024))
+ MLton.CharVector.fromPoly (Vector.map (chr o Word8.toInt) (MLton.Word8Vector.toPoly (Socket.recvVec (sock, 1024))))
val s = buf ^ s
val (befor, after) = Substring.splitl (fn ch => ch <> #"\n") (Substring.full s)
in
@@ -345,12 +345,12 @@ val () = case CommandLine.arguments () of
let
val v = Socket.recvVec (sock, 1024)
in
- if Vector.length v = 0 then
+ if Word8Vector.length v = 0 then
OS.Process.failure
else
let
- val s = Vector.map (chr o Word8.toInt) v
- val last = Vector.sub (v, Vector.length v - 1)
+ val s = MLton.CharVector.fromPoly (Vector.map (chr o Word8.toInt) (MLton.Word8Vector.toPoly v))
+ val last = Word8Vector.sub (v, Word8Vector.length v - 1)
val (rc, s) = if last = Word8.fromInt 1 then
(SOME OS.Process.success, String.substring (s, 0, size s - 1))
else if last = Word8.fromInt 2 then
diff --git a/src/mysql.sml b/src/mysql.sml
index 539428f6..52e4921e 100644
--- a/src/mysql.sml
+++ b/src/mysql.sml
@@ -867,7 +867,7 @@ fun queryCommon {loc, query, cols, doCols} =
newline,
string "uw_error(ctx, FATAL, \"",
string (ErrorMsg.spanToString loc),
- string ": Error reseting statement: %s\\n%s\", ",
+ string ": Error resetting statement: %s\\n%s\", ",
query,
string ", mysql_error(conn->conn));",
newline],
@@ -931,7 +931,7 @@ fun queryCommon {loc, query, cols, doCols} =
string "if (mysql_stmt_reset(stmt)) uw_error(ctx, FATAL, \"",
string (ErrorMsg.spanToString loc),
- string ": Error reseting statement: %s\\n%s\", ",
+ string ": Error resetting statement: %s\\n%s\", ",
query,
string ", mysql_error(conn->conn));",
newline,
diff --git a/src/postgres.sml b/src/postgres.sml
index ddfe0ad6..404384d2 100644
--- a/src/postgres.sml
+++ b/src/postgres.sml
@@ -443,7 +443,7 @@ fun init {dbstring, prepared = ss, tables, views, sequences} =
newline,
newline,
string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
- box [string "if (!strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
+ box [string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
box [newline,
string "PQclear(res);",
newline,
@@ -451,7 +451,7 @@ fun init {dbstring, prepared = ss, tables, views, sequences} =
newline],
string "}",
newline,
- string "if (!strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
+ string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
box [newline,
string "PQclear(res);",
newline,
@@ -629,7 +629,7 @@ fun queryCommon {loc, query, cols, doCols} =
string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
newline,
- box [string "if (!strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
+ box [string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
box [newline,
string "PQclear(res);",
newline,
@@ -637,7 +637,7 @@ fun queryCommon {loc, query, cols, doCols} =
newline],
string "}",
newline,
- string "if (!strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
+ string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
box [newline,
string "PQclear(res);",
newline,
@@ -800,7 +800,7 @@ fun dmlCommon {loc, dml, mode} =
string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
newline,
- box [string "if (!strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
+ box [string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
box [newline,
string "PQclear(res);",
newline,
@@ -808,7 +808,7 @@ fun dmlCommon {loc, dml, mode} =
newline],
string "}",
newline,
- string "if (!strcmp(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
+ string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
box [newline,
string "PQclear(res);",
newline,
diff --git a/src/settings.sml b/src/settings.sml
index 85cab207..b72789df 100644
--- a/src/settings.sml
+++ b/src/settings.sml
@@ -335,6 +335,19 @@ val jsFuncsBase = basisM [("alert", "alert"),
("ceil", "ceil"),
("trunc", "trunc"),
("round", "round"),
+ ("floor", "floor"),
+
+ ("pow", "pow"),
+ ("sqrt", "sqrt"),
+ ("sin", "sin"),
+ ("cos", "cos"),
+ ("log", "log"),
+ ("exp", "exp"),
+ ("asin", "asin"),
+ ("acos", "acos"),
+ ("atan", "atan"),
+ ("atan2", "atan2"),
+ ("abs", "abs"),
("now", "now"),
("timeToString", "showTime"),
diff --git a/tests/math.ur b/tests/math.ur
new file mode 100644
index 00000000..964b73e6
--- /dev/null
+++ b/tests/math.ur
@@ -0,0 +1,26 @@
+fun main () = return <xml><body>
+ <button value="Power 2.0 of 2.0!" onclick={fn _ => alert (show (pow 2.0 2.0))}/>
+ {[(pow 2.0 2.0)]}
+ <button value="Square root of 25!" onclick={fn _ => alert (show (sqrt 25.0))}/>
+ {[(sqrt 25.0)]}
+ <button value="Sin of 0.1!" onclick={fn _ => alert (show (sin 0.1))}/>
+ {[(sin 0.1)]}
+ <button value="Cos of 0.1!" onclick={fn _ => alert (show (cos 0.1))}/>
+ {[(cos 0.1)]}
+ <button value="log of 0.1!" onclick={fn _ => alert (show (log 0.1))}/>
+ {[(log 0.1)]}
+ <button value="Exp of 0.1!" onclick={fn _ => alert (show (exp 0.1))}/>
+ {[(exp 0.1)]}
+ <button value="asin of 0.1!" onclick={fn _ => alert (show (asin 0.1))}/>
+ {[(asin 0.1)]}
+ <button value="acos of 0.1!" onclick={fn _ => alert (show (acos 0.1))}/>
+ {[(acos 0.1)]}
+ <button value="atan of 0.1!" onclick={fn _ => alert (show (atan 0.1))}/>
+ {[(atan 0.1)]}
+ <button value="atan2 of 0.1 and -0.2!" onclick={fn _ => alert (show (atan2 0.1 (-0.2)))}/>
+ {[(atan2 0.1 (-0.2))]}
+ <button value="floor of 34.5!" onclick={fn _ => alert (show (floor 34.5))}/>
+ {[(floor 34.5)]}
+ <button value="abs of -10.0!" onclick={fn _ => alert (show (abs (-10.0)))}/>
+ {[(abs (-10.0))]}
+ </body></xml>
diff --git a/tests/normalizeTable.ur b/tests/normalizeTable.ur
new file mode 100644
index 00000000..be16d935
--- /dev/null
+++ b/tests/normalizeTable.ur
@@ -0,0 +1,50 @@
+fun main () =
+visible <- source True;
+return
+ <xml>
+ <body>
+ <section>
+ <h1>Static table</h1>
+ <table border=1>
+ <thead>
+ <tr>
+ <th>Column 0</th>
+ <th>Column 1</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>A</td>
+ <td>B</td>
+ </tr>
+ </tbody>
+ </table>
+ </section>
+
+ <section>
+ <h1>Dynamic table</h1>
+ <table border=1>
+ <thead>
+ <tr>
+ <th>Column 0</th>
+ <th>Column 1</th>
+ </tr>
+ </thead>
+ <tbody>
+ <dyn signal={
+ visible <- signal visible;
+ return (if visible then
+ <xml>
+ <tr>
+ <td>A</td>
+ <td>B</td>
+ </tr>
+ </xml>
+ else
+ <xml></xml>)
+ }/>
+ </tbody>
+ </table>
+ </section>
+ </body>
+ </xml>
diff --git a/tests/normalizeTable.urp b/tests/normalizeTable.urp
new file mode 100644
index 00000000..e22cda3b
--- /dev/null
+++ b/tests/normalizeTable.urp
@@ -0,0 +1 @@
+normalizeTable
diff --git a/tests/normalizeTable.urs b/tests/normalizeTable.urs
new file mode 100644
index 00000000..9e80cf40
--- /dev/null
+++ b/tests/normalizeTable.urs
@@ -0,0 +1 @@
+val main: unit -> transaction page
diff --git a/tests/timeout.ur b/tests/timeout.ur
new file mode 100644
index 00000000..d96b42bd
--- /dev/null
+++ b/tests/timeout.ur
@@ -0,0 +1,22 @@
+table listeners : { Ch : channel unit }
+
+fun ping () =
+ queryI1 (SELECT * FROM listeners)
+ (fn r => send r.Ch ())
+
+fun main () =
+ ch <- channel;
+ dml (INSERT INTO listeners(Ch) VALUES ({[ch]}));
+ count <- source 0;
+ return <xml><body onload={let
+ fun loop () =
+ _ <- recv ch;
+ c <- get count;
+ set count (c + 1);
+ loop ()
+ in
+ loop ()
+ end}>
+ <dyn signal={n <- signal count; return (txt n)}/>
+ <button onclick={fn _ => rpc (ping ())}>Ping</button>
+ </body></xml>
diff --git a/tests/timeout.urp b/tests/timeout.urp
new file mode 100644
index 00000000..6d3ca878
--- /dev/null
+++ b/tests/timeout.urp
@@ -0,0 +1,7 @@
+timeout 2
+rewrite url Timeout/*
+database dbname=test
+sql timeout.sql
+safeGet main
+
+timeout
diff --git a/tests/timeout.urs b/tests/timeout.urs
new file mode 100644
index 00000000..6ac44e0b
--- /dev/null
+++ b/tests/timeout.urs
@@ -0,0 +1 @@
+val main : unit -> transaction page