summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/c/Makefile.am21
-rw-r--r--src/c/cgi.c149
-rw-r--r--src/c/fastcgi.c693
-rw-r--r--src/c/fastcgi.h113
-rw-r--r--src/c/http.c561
-rw-r--r--src/c/memmem.c87
-rw-r--r--src/c/openssl.c139
-rw-r--r--src/c/queue.c63
-rw-r--r--src/c/request.c614
-rw-r--r--src/c/static.c70
-rw-r--r--src/c/urweb.c4980
-rw-r--r--src/cache.sml17
-rw-r--r--src/cgi.sig30
-rw-r--r--src/cgi.sml52
-rw-r--r--src/checknest.sig32
-rw-r--r--src/checknest.sml187
-rw-r--r--src/cjr.sml138
-rw-r--r--src/cjr_env.sig59
-rw-r--r--src/cjr_env.sml177
-rw-r--r--src/cjr_print.sig39
-rw-r--r--src/cjr_print.sml3749
-rw-r--r--src/cjrize.sig32
-rw-r--r--src/cjrize.sml745
-rw-r--r--src/compiler.mlb6
-rw-r--r--src/compiler.sig213
-rw-r--r--src/compiler.sml1716
-rw-r--r--src/config.sig23
-rw-r--r--src/config.sml.in37
-rw-r--r--src/coq/Axioms.v47
-rw-r--r--src/coq/Makefile14
-rw-r--r--src/coq/Name.v31
-rw-r--r--src/coq/README3
-rw-r--r--src/coq/Semantics.v232
-rw-r--r--src/coq/Syntax.v186
-rw-r--r--src/core.sml146
-rw-r--r--src/core_env.sig72
-rw-r--r--src/core_env.sml379
-rw-r--r--src/core_print.sig41
-rw-r--r--src/core_print.sml643
-rw-r--r--src/core_untangle.sig32
-rw-r--r--src/core_untangle.sml237
-rw-r--r--src/core_util.sig232
-rw-r--r--src/core_util.sml1240
-rw-r--r--src/corify.sig32
-rw-r--r--src/corify.sml1330
-rw-r--r--src/css.sig43
-rw-r--r--src/css.sml320
-rw-r--r--src/datatype_kind.sml35
-rw-r--r--src/dbmodecheck.sig32
-rw-r--r--src/dbmodecheck.sml86
-rw-r--r--src/demo.sig35
-rw-r--r--src/demo.sml477
-rw-r--r--src/disjoint.sig46
-rw-r--r--src/disjoint.sml285
-rw-r--r--src/effectize.sig32
-rw-r--r--src/effectize.sml208
-rw-r--r--src/elab.sml204
-rw-r--r--src/elab_env.sig127
-rw-r--r--src/elab_env.sml1709
-rw-r--r--src/elab_err.sig125
-rw-r--r--src/elab_err.sml440
-rw-r--r--src/elab_ops.sig50
-rw-r--r--src/elab_ops.sml517
-rw-r--r--src/elab_print.sig44
-rw-r--r--src/elab_print.sml906
-rw-r--r--src/elab_util.sig257
-rw-r--r--src/elab_util.sml1310
-rw-r--r--src/elaborate.sig50
-rw-r--r--src/elaborate.sml5100
-rw-r--r--src/elisp/urweb-compat.el111
-rw-r--r--src/elisp/urweb-defs.el206
-rw-r--r--src/elisp/urweb-mode-startup.el20
-rw-r--r--src/elisp/urweb-mode.el930
-rw-r--r--src/elisp/urweb-move.el373
-rw-r--r--src/elisp/urweb-util.el123
-rw-r--r--src/errormsg.sig56
-rw-r--r--src/errormsg.sml107
-rw-r--r--src/especialize.sig34
-rw-r--r--src/especialize.sml717
-rw-r--r--src/expl.sml166
-rw-r--r--src/expl_env.sig71
-rw-r--r--src/expl_env.sml413
-rw-r--r--src/expl_print.sig39
-rw-r--r--src/expl_print.sml794
-rw-r--r--src/expl_rename.sig41
-rw-r--r--src/expl_rename.sml454
-rw-r--r--src/expl_util.sig119
-rw-r--r--src/expl_util.sml557
-rw-r--r--src/explify.sig32
-rw-r--r--src/explify.sml213
-rw-r--r--src/export.sig44
-rw-r--r--src/export.sml57
-rw-r--r--src/fastcgi.sig30
-rw-r--r--src/fastcgi.sml53
-rw-r--r--src/fileio.sig9
-rw-r--r--src/fileio.sml39
-rw-r--r--src/fuse.sig32
-rw-r--r--src/fuse.sml152
-rw-r--r--src/globals.sig7
-rw-r--r--src/globals.sml7
-rw-r--r--src/http.sig30
-rw-r--r--src/http.sml55
-rw-r--r--src/iflow.sig34
-rw-r--r--src/iflow.sml2184
-rw-r--r--src/jscomp.sig36
-rw-r--r--src/jscomp.sml1369
-rw-r--r--src/list_key_fn.sml14
-rw-r--r--src/list_util.sig59
-rw-r--r--src/list_util.sml260
-rw-r--r--src/lru_cache.sml207
-rw-r--r--src/main.mlton.sml383
-rw-r--r--src/marshalcheck.sig32
-rw-r--r--src/marshalcheck.sml132
-rw-r--r--src/mod_db.sig42
-rw-r--r--src/mod_db.sml153
-rw-r--r--src/mono.sml171
-rw-r--r--src/mono_env.sig55
-rw-r--r--src/mono_env.sml169
-rw-r--r--src/mono_fooify.sig39
-rw-r--r--src/mono_fooify.sml346
-rw-r--r--src/mono_inline.sml28
-rw-r--r--src/mono_opt.sig33
-rw-r--r--src/mono_opt.sml655
-rw-r--r--src/mono_print.sig38
-rw-r--r--src/mono_print.sml554
-rw-r--r--src/mono_reduce.sig40
-rw-r--r--src/mono_reduce.sml924
-rw-r--r--src/mono_shake.sig34
-rw-r--r--src/mono_shake.sml164
-rw-r--r--src/mono_util.sig161
-rw-r--r--src/mono_util.sml825
-rw-r--r--src/monoize.sig34
-rw-r--r--src/monoize.sml4549
-rw-r--r--src/multimap_fn.sml16
-rw-r--r--src/mysql.sig30
-rw-r--r--src/mysql.sml1614
-rw-r--r--src/name_js.sig35
-rw-r--r--src/name_js.sml173
-rw-r--r--src/option_key_fn.sml12
-rw-r--r--src/order.sig36
-rw-r--r--src/order.sml53
-rw-r--r--src/pair_key_fn.sml12
-rw-r--r--src/pathcheck.sig32
-rw-r--r--src/pathcheck.sml115
-rw-r--r--src/postgres.sig30
-rw-r--r--src/postgres.sml1153
-rw-r--r--src/prefix.cm7
-rw-r--r--src/prefix.mlb7
-rw-r--r--src/prepare.sig32
-rw-r--r--src/prepare.sml356
-rw-r--r--src/prim.sig49
-rw-r--r--src/prim.sml119
-rw-r--r--src/print.sig64
-rw-r--r--src/print.sml127
-rw-r--r--src/reduce.sig34
-rw-r--r--src/reduce.sml953
-rw-r--r--src/reduce_local.sig36
-rw-r--r--src/reduce_local.sml386
-rw-r--r--src/rpcify.sig32
-rw-r--r--src/rpcify.sml168
-rw-r--r--src/scriptcheck.sig32
-rw-r--r--src/scriptcheck.sml182
-rw-r--r--src/search.sig62
-rw-r--r--src/search.sml73
-rw-r--r--src/settings.sig309
-rw-r--r--src/settings.sml1012
-rw-r--r--src/sha1.sig31
-rw-r--r--src/sha1.sml264
-rw-r--r--src/shake.sig37
-rw-r--r--src/shake.sml229
-rw-r--r--src/sidecheck.sig37
-rw-r--r--src/sidecheck.sml84
-rw-r--r--src/sigcheck.sig36
-rw-r--r--src/sigcheck.sml97
-rw-r--r--src/source.sml192
-rw-r--r--src/source_print.sig40
-rw-r--r--src/source_print.sml728
-rw-r--r--src/sources272
-rw-r--r--src/specialize.sig34
-rw-r--r--src/specialize.sml298
-rw-r--r--src/sql.sig104
-rw-r--r--src/sql.sml509
-rw-r--r--src/sqlcache.sig11
-rw-r--r--src/sqlcache.sml1732
-rw-r--r--src/sqlite.sig30
-rw-r--r--src/sqlite.sml855
-rw-r--r--src/static.sig30
-rw-r--r--src/static.sml41
-rw-r--r--src/suffix.mlb2
-rw-r--r--src/tag.sig32
-rw-r--r--src/tag.sml356
-rw-r--r--src/termination.sig32
-rw-r--r--src/termination.sml396
-rw-r--r--src/toy_cache.sml207
-rw-r--r--src/triple_key_fn.sml15
-rw-r--r--src/tutorial.sig32
-rw-r--r--src/tutorial.sml322
-rw-r--r--src/union_find_fn.sml58
-rw-r--r--src/unnest.sig34
-rw-r--r--src/unnest.sml567
-rw-r--r--src/unpoly.sig34
-rw-r--r--src/unpoly.sml336
-rw-r--r--src/untangle.sig32
-rw-r--r--src/untangle.sml214
-rw-r--r--src/urweb.grm2394
-rw-r--r--src/urweb.lex579
-rw-r--r--src/utf8.sig32
-rw-r--r--src/utf8.sml59
208 files changed, 70667 insertions, 0 deletions
diff --git a/src/c/Makefile.am b/src/c/Makefile.am
new file mode 100644
index 0000000..f4d9bef
--- /dev/null
+++ b/src/c/Makefile.am
@@ -0,0 +1,21 @@
+lib_LTLIBRARIES = liburweb.la liburweb_http.la liburweb_cgi.la liburweb_fastcgi.la liburweb_static.la
+
+liburweb_la_SOURCES = memmem.c openssl.c urweb.c request.c queue.c
+liburweb_http_la_SOURCES = http.c
+liburweb_cgi_la_SOURCES = cgi.c
+liburweb_fastcgi_la_SOURCES = fastcgi.c fastcgi.h
+liburweb_static_la_SOURCES = static.c
+
+AM_CPPFLAGS = -I$(srcdir)/../../include/urweb $(OPENSSL_INCLUDES)
+AM_CFLAGS = -Wimplicit -Wall -Werror -Wno-format-security -Wno-deprecated-declarations -U_FORTIFY_SOURCE $(PTHREAD_CFLAGS)
+liburweb_la_LDFLAGS = $(AM_LDFLAGS) $(OPENSSL_LDFLAGS) \
+ -export-symbols-regex '^(client_pruner|pthread_create_big|strcmp_nullsafe|uw_.*)'
+liburweb_la_LIBADD = $(PTHREAD_LIBS) -lm $(OPENSSL_LIBS)
+liburweb_http_la_LIBADD = liburweb.la
+liburweb_http_la_LDFLAGS = -export-symbols-regex '^(main|uw_.*)'
+liburweb_cgi_la_LIBADD = liburweb.la
+liburweb_cgi_la_LDFLAGS = -export-symbols-regex '^(main|uw_.*)'
+liburweb_fastcgi_la_LIBADD = liburweb.la
+liburweb_fastcgi_la_LDFLAGS = -export-symbols-regex '^(main|uw_.*)'
+liburweb_static_la_LIBADD = liburweb.la
+liburweb_static_la_LDFLAGS = -export-symbols-regex '^(main|uw_.*)'
diff --git a/src/c/cgi.c b/src/c/cgi.c
new file mode 100644
index 0000000..d060532
--- /dev/null
+++ b/src/c/cgi.c
@@ -0,0 +1,149 @@
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "urweb.h"
+#include "request.h"
+
+extern uw_app uw_application;
+
+static char *uppercased;
+static size_t uppercased_len;
+
+static char *get_header(void *data, const char *h) {
+ size_t len = strlen(h);
+ char *s, *r;
+ const char *saved_h = h;
+
+ if (len > uppercased_len) {
+ uppercased_len = len;
+ uppercased = realloc(uppercased, len + 6);
+ }
+
+ strcpy(uppercased, "HTTP_");
+ for (s = uppercased+5; *h; ++h)
+ *s++ = *h == '-' ? '_' : toupper((int)*h);
+ *s = 0;
+
+ if ((r = getenv(uppercased)))
+ return r;
+ else if (!strcasecmp(saved_h, "Content-length")
+ || !strcasecmp(saved_h, "Content-type"))
+ return getenv(uppercased + 5);
+ else
+ return NULL;
+}
+
+static char *get_env(void *data, const char *name) {
+ return getenv(name);
+}
+
+static void on_success(uw_context ctx) { }
+
+static void on_failure(uw_context ctx) {
+ uw_write_header(ctx, "Status: 500 Internal Server Error\r\n");
+}
+
+static void log_error(void *data, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ vfprintf(stderr, fmt, ap);
+}
+
+static void log_debug(void *data, const char *fmt, ...) {
+}
+
+static uw_loggers ls = {NULL, log_error, log_debug};
+
+int main(int argc, char *argv[]) {
+ uw_context ctx = uw_request_new_context(0, &uw_application, &ls);
+ uw_request_context rc = uw_new_request_context();
+ request_result rr;
+ char *method = getenv("REQUEST_METHOD"),
+ *path = getenv("SCRIPT_NAME"), *path_info = getenv("PATH_INFO"),
+ *query_string = getenv("QUERY_STRING");
+ char *body = malloc(1);
+ ssize_t body_len = 1, body_pos = 0, res;
+
+ uppercased = malloc(6);
+
+ if (!method) {
+ log_error(NULL, "REQUEST_METHOD not set\n");
+ exit(1);
+ }
+
+ if (!path) {
+ log_error(NULL, "SCRIPT_NAME not set\n");
+ exit(1);
+ }
+
+ if (path_info) {
+ char *new_path = malloc(strlen(path) + strlen(path_info) + 1);
+ sprintf(new_path, "%s%s", path, path_info);
+ path = new_path;
+ }
+
+ if (!query_string)
+ query_string = "";
+
+ while ((res = read(0, body + body_pos, body_len - body_pos)) > 0) {
+ body_pos += res;
+
+ if (body_pos == body_len) {
+ body_len *= 2;
+ body = realloc(body, body_len);
+ }
+ }
+
+ if (res < 0) {
+ log_error(NULL, "Error reading stdin\n");
+ exit(1);
+ }
+
+ uw_set_on_success("");
+ uw_set_headers(ctx, get_header, NULL);
+ uw_set_env(ctx, get_env, NULL);
+ uw_request_init(&uw_application, &ls);
+
+ body[body_pos] = 0;
+ rr = uw_request(rc, ctx, method, path, query_string, body, body_pos,
+ on_success, on_failure,
+ NULL, log_error, log_debug,
+ -1, NULL, NULL);
+ uw_print(ctx, 1);
+
+ if (rr == SERVED)
+ return 0;
+ else
+ return 1;
+}
+
+void *uw_init_client_data() {
+ return NULL;
+}
+
+void uw_free_client_data(void *data) {
+}
+
+void uw_copy_client_data(void *dst, void *src) {
+}
+
+void uw_do_expunge(uw_context ctx, uw_Basis_client cli, void *data) {
+ uw_ensure_transaction(ctx);
+ uw_get_app(ctx)->expunger(ctx, cli);
+
+ if (uw_commit(ctx))
+ uw_error(ctx, UNLIMITED_RETRY, "Rerunning expunge transaction");
+}
+
+void uw_post_expunge(uw_context ctx, void *data) {
+}
+
+int uw_supports_direct_status = 0;
diff --git a/src/c/fastcgi.c b/src/c/fastcgi.c
new file mode 100644
index 0000000..c37debf
--- /dev/null
+++ b/src/c/fastcgi.c
@@ -0,0 +1,693 @@
+#include "config.h"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#include <pthread.h>
+
+#include "urweb.h"
+#include "request.h"
+#include "queue.h"
+
+#include "fastcgi.h"
+
+#define THREAD_LOCAL __thread
+
+extern uw_app uw_application;
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+ unsigned char contentData[65535];
+} FCGI_Record;
+
+typedef struct {
+ FCGI_Record r;
+ int sock;
+} FCGI_Output;
+
+typedef struct {
+ FCGI_Record r;
+ int available, used, sock;
+} FCGI_Input;
+
+// The FastCGI request ID corresponding to the request being handled by the
+// current worker thread. (Each worker thread can only handle one request at a
+// time.)
+static THREAD_LOCAL int current_request_id;
+
+// Reads the FastCGI request ID from a FastCGI record. The result is guaranteed
+// to be in the range [0, 2^16); this function returns an int to avoid C type
+// promotion insanity.
+static int fastcgi_request_id(const FCGI_Record* const r) {
+ const int requestid = r->requestIdB1 << 8 | r->requestIdB0;
+ assert(requestid >= 0);
+ assert(requestid <= UINT16_MAX);
+ return requestid;
+}
+
+static FCGI_Output *fastcgi_output() {
+ FCGI_Output *o = malloc(sizeof(FCGI_Output));
+
+ o->r.version = FCGI_VERSION_1;
+ o->r.paddingLength = 0;
+ o->r.reserved = 0;
+
+ return o;
+}
+
+static FCGI_Input *fastcgi_input() {
+ FCGI_Input *i = malloc(sizeof(FCGI_Input));
+
+ i->available = i->used = 0;
+
+ return i;
+}
+
+static void fastcgi_input_reset(FCGI_Input *i) {
+ i->available = i->used = 0;
+}
+
+static int fastcgi_send(FCGI_Output *o,
+ unsigned char type,
+ unsigned short contentLength) {
+ o->r.type = type;
+ assert(current_request_id <= UINT16_MAX);
+ o->r.requestIdB1 = current_request_id >> 8;
+ o->r.requestIdB0 = current_request_id & 0x000000ff;
+ o->r.contentLengthB1 = contentLength >> 8;
+ o->r.contentLengthB0 = contentLength & 255;
+ return uw_really_send(o->sock, &o->r, sizeof(o->r) - 65535 + contentLength);
+}
+
+static FCGI_Record *fastcgi_recv(FCGI_Input *i) {
+ if (i->used > 0) {
+ memmove((void*)&i->r, (void*)&i->r + i->used, i->available - i->used);
+ i->available -= i->used;
+ i->used = 0;
+ }
+
+ while (1) {
+ ssize_t n;
+
+ if (i->available >= sizeof(FCGI_Record) - 65535
+ && i->available >= sizeof(FCGI_Record) - 65535
+ + ((i->r.contentLengthB1 << 8) | i->r.contentLengthB0)
+ + i->r.paddingLength) {
+ i->used = sizeof(FCGI_Record) - 65535
+ + ((i->r.contentLengthB1 << 8) | i->r.contentLengthB0)
+ + i->r.paddingLength;
+
+ return &i->r;
+ }
+
+ n = recv(i->sock, (void*)&i->r + i->available, sizeof(i->r) - i->available, 0);
+
+ if (n <= 0)
+ return NULL;
+
+ i->available += n;
+ }
+}
+
+static void on_success(uw_context ctx) { }
+
+static void on_failure(uw_context ctx) {
+ uw_write_header(ctx, "Status: 500 Internal Server Error\r\n");
+}
+
+static int write_stdout(void *data, const char *buf, size_t len) {
+ FCGI_Output *o = (FCGI_Output *)data;
+ while (len > 0) {
+ size_t len2 = len;
+ if (len2 > 65535)
+ len2 = 65535;
+ memcpy(o->r.contentData, buf, len2);
+ if (fastcgi_send(o, FCGI_STDOUT, len2)) {
+ fprintf(stderr, "fastcgi_send() failed in write_stdout().\n");
+ return -1;
+ }
+ buf += len2;
+ len -= len2;
+ }
+
+ return 0;
+}
+
+#include <errno.h>
+
+static void write_stderr(FCGI_Output *o, const char *fmt, ...) {
+ int len;
+ va_list ap;
+ va_start(ap, fmt);
+
+ len = vsnprintf((char *)o->r.contentData, 65535, fmt, ap);
+ if (len < 0)
+ fprintf(stderr, "vsnprintf() failed in write_stderr().\n");
+ else if (fastcgi_send(o, FCGI_STDERR, len))
+ fprintf(stderr, "fastcgi_send() failed in write_stderr().\n");
+}
+
+static void close_stream(FCGI_Output *o, unsigned char type) {
+ if (fastcgi_send(o, type, 0))
+ fprintf(stderr, "fastcgi_send() failed in close_stream().\n");
+}
+
+static void log_error(void *data, const char *fmt, ...) {
+ FCGI_Output *o = (FCGI_Output *)data;
+ va_list ap;
+ va_start(ap, fmt);
+
+ if (o) {
+ int len = vsnprintf((char *)o->r.contentData, 65535, fmt, ap);
+ if (len < 0)
+ fprintf(stderr, "vsnprintf() failed in log_error().\n");
+ else if (fastcgi_send(o, FCGI_STDERR, len))
+ fprintf(stderr, "fastcgi_send() failed in log_error().\n");
+ } else
+ vfprintf(stderr, fmt, ap);
+}
+
+static void log_debug(void *data, const char *fmt, ...) {
+ FCGI_Output *o = (FCGI_Output *)data;
+ va_list ap;
+ va_start(ap, fmt);
+
+ if (o) {
+ strcpy((char *)o->r.contentData, "DEBUG: ");
+ int len = vsnprintf((char *)o->r.contentData + 7, 65535 - 7, fmt, ap);
+ if (len < 0)
+ fprintf(stderr, "vsnprintf() failed in log_debug().\n");
+ else if (fastcgi_send(o, FCGI_STDERR, len + 7)) {
+ len += 7;
+ if (len >= 65535) len = 65534;
+ o->r.contentData[len] = 0;
+ fputs((char *)o->r.contentData, stderr);
+ fflush(stderr);
+ }
+ } else
+ vfprintf(stderr, fmt, ap);
+}
+
+typedef struct {
+ char *name, *value;
+ unsigned name_len, value_len;
+} nvp;
+
+static char *search_nvps(nvp *nvps, const char *h) {
+ for (; nvps->name[0]; ++nvps)
+ if (!strcmp(h, nvps->name))
+ return nvps->value;
+
+ return NULL;
+}
+
+typedef struct {
+ nvp *nvps;
+ char *uppercased;
+ int n_nvps, uppercased_len;
+} headers;
+
+static char *get_header(void *data, const char *h) {
+ headers *hs = (headers *)data;
+ size_t len = strlen(h);
+ char *s;
+ const char *saved_h = h;
+
+ if (len > hs->uppercased_len) {
+ hs->uppercased_len = len;
+ hs->uppercased = realloc(hs->uppercased, len + 6);
+ }
+
+ strcpy(hs->uppercased, "HTTP_");
+ for (s = hs->uppercased+5; *h; ++h)
+ *s++ = *h == '-' ? '_' : toupper((int)*h);
+ *s = 0;
+
+ if (!strcasecmp(saved_h, "Content-length")
+ || !strcasecmp(saved_h, "Content-type")) {
+ if ((s = search_nvps(hs->nvps, hs->uppercased + 5)))
+ return s;
+ }
+
+ return search_nvps(hs->nvps, hs->uppercased);
+}
+
+static char *get_env(void *data, const char *h) {
+ headers *hs = (headers *)data;
+
+ return search_nvps(hs->nvps, h);
+}
+
+static int read_funny_len(unsigned char **buf, int *len) {
+ if (*len <= 0)
+ return -1;
+
+ if ((*buf)[0] >> 7 == 0) {
+ int r = (*buf)[0];
+ ++*buf;
+ --*len;
+ return r;
+ }
+ else if (*len < 4)
+ return -1;
+ else {
+ int r = (((*buf)[0] & 0x7f) << 24) + ((*buf)[1] << 16) + ((*buf)[2] << 8) + (*buf)[3];
+ *buf += 4;
+ *len -= 4;
+ return r;
+ }
+}
+
+static int read_nvp(unsigned char **buf, int len, nvp *nv) {
+ int nameLength, valueLength;
+
+ if ((nameLength = read_funny_len(buf, &len)) < 0)
+ return -1;
+ if ((valueLength = read_funny_len(buf, &len)) < 0)
+ return -2;
+ if (len < nameLength + valueLength)
+ return -3;
+
+ if (nameLength+1 > nv->name_len) {
+ nv->name_len = nameLength+1;
+ nv->name = realloc(nv->name, nv->name_len);
+ }
+ if (valueLength+1 > nv->value_len) {
+ nv->value_len = valueLength+1;
+ nv->value = realloc(nv->value, nv->value_len);
+ }
+
+ memcpy(nv->name, *buf, nameLength);
+ nv->name[nameLength] = 0;
+
+ memcpy(nv->value, *buf + nameLength, valueLength);
+ nv->value[valueLength] = 0;
+
+ *buf += nameLength + valueLength;
+
+ return 0;
+}
+
+static int fastcgi_close_with(FCGI_Output *out, request_result rr) {
+ FCGI_EndRequestBody *erb = (FCGI_EndRequestBody *)out->r.contentData;
+
+ close_stream(out, FCGI_STDOUT);
+ close_stream(out, FCGI_STDERR);
+
+ if (rr == SERVED)
+ erb->appStatusB3 = erb->appStatusB2 = erb->appStatusB1 = erb->appStatusB0 = 0;
+ else
+ erb->appStatusB3 = erb->appStatusB2 = erb->appStatusB1 = erb->appStatusB0 = 0xFF;
+
+ erb->protocolStatus = FCGI_REQUEST_COMPLETE;
+ fastcgi_send(out, FCGI_END_REQUEST, sizeof(FCGI_EndRequestBody));
+ return close(out->sock);
+}
+
+static int fastcgi_close(int sock) {
+ FCGI_Output out;
+ out.sock = sock;
+ out.r.version = FCGI_VERSION_1;
+ out.r.paddingLength = 0;
+ out.r.reserved = 0;
+
+ return fastcgi_close_with(&out, SERVED);
+}
+
+int fastcgi_send_normal(int sock, const void *buf, ssize_t len) {
+ FCGI_Output out;
+ out.sock = sock;
+ out.r.version = FCGI_VERSION_1;
+ out.r.paddingLength = 0;
+ out.r.reserved = 0;
+
+ return write_stdout(&out, buf, len);
+}
+
+static void *worker(void *data) {
+ FCGI_Input *in = fastcgi_input();
+ FCGI_Output *out = fastcgi_output();
+ uw_loggers ls = {out, log_error, log_debug};
+ uw_context ctx = uw_request_new_context(*(int *)data, &uw_application, &ls);
+ uw_request_context rc = uw_new_request_context();
+ headers hs;
+ size_t body_size = 0;
+ char *body = malloc(0);
+ size_t path_size = 0;
+ char *path_buf = malloc(0);
+
+ hs.uppercased = malloc(6);
+ hs.uppercased_len = 0;
+ hs.nvps = malloc(sizeof(nvp));
+ hs.n_nvps = 1;
+ hs.nvps[0].name = malloc(1);
+ hs.nvps[0].name_len = 1;
+ hs.nvps[0].value = malloc(0);
+ hs.nvps[0].value_len = 0;
+
+ while (1) {
+ FCGI_Record *r;
+ size_t used_nvps = 0;
+ int body_len, body_read;
+ char *s;
+ char *method, *path, *path_info, *query_string;
+
+ in->sock = out->sock = uw_dequeue();
+
+ if (!(r = fastcgi_recv(in))) {
+ fprintf(stderr, "Error receiving initial message\n");
+ goto done;
+ }
+
+ // Save the FastCGI request ID this worker is handling so that fastcgi_send
+ // can include it in its response.
+ current_request_id = fastcgi_request_id(r);
+
+ if (r->type != FCGI_BEGIN_REQUEST) {
+ write_stderr(out, "First message is not BEGIN_REQUEST\n");
+ goto done;
+ } else if (r->contentData[1] != FCGI_RESPONDER) {
+ write_stderr(out, "Request is for a role besides RESPONDER\n");
+ goto done;
+ }
+
+ while (1) {
+ unsigned char *buf;
+ int len;
+
+ if (!(r = fastcgi_recv(in))) {
+ write_stderr(out, "Error receiving environment variables\n");
+ goto done;
+ }
+
+ if (fastcgi_request_id(r) != current_request_id) {
+ write_stderr(out,
+ "Ignoring environment variables for request %d (current"
+ " request has id %d)\n",
+ fastcgi_request_id(r),
+ current_request_id);
+ continue;
+ }
+
+ if (r->type != FCGI_PARAMS) {
+ write_stderr(out, "Expected FCGI_PARAMS but got %d\n", r->type);
+ goto done;
+ }
+
+ if (r->contentLengthB1 == 0 && r->contentLengthB0 == 0)
+ break;
+
+ len = (r->contentLengthB1 << 8) | r->contentLengthB0;
+
+ for (buf = r->contentData; buf < r->contentData + len; ) {
+ if (used_nvps == hs.n_nvps-1) {
+ ++hs.n_nvps;
+ hs.nvps = realloc(hs.nvps, hs.n_nvps * sizeof(nvp));
+ hs.nvps[hs.n_nvps-1].name = malloc(1);
+ hs.nvps[hs.n_nvps-1].value = malloc(0);
+ hs.nvps[hs.n_nvps-1].name_len = 1;
+ hs.nvps[hs.n_nvps-1].value_len = 0;
+ }
+
+ if (read_nvp(&buf, len - (buf - r->contentData), &hs.nvps[used_nvps]) < 0) {
+ write_stderr(out, "Error reading FCGI_PARAMS name-value pair\n");
+ goto done;
+ }
+
+ //write_stderr(out, "PARAM: %s -> %s\n", hs.nvps[used_nvps].name, hs.nvps[used_nvps].value);
+
+ ++used_nvps;
+ }
+ }
+
+ hs.nvps[used_nvps].name[0] = 0;
+
+ if ((s = get_header(&hs, "Content-Length"))) {
+ body_len = atoi(s);
+ if (body_len < 0) {
+ write_stderr(out, "Invalid Content-Length\n");
+ goto done;
+ }
+ } else
+ body_len = 0;
+
+ if (body_len+1 > body_size) {
+ body_size = body_len+1;
+ body = realloc(body, body_size);
+ }
+
+ for (body_read = 0; body_read < body_len; ) {
+ int this_len;
+
+ if (!(r = fastcgi_recv(in))) {
+ write_stderr(out, "Error receiving STDIN\n");
+ goto done;
+ }
+
+ if (fastcgi_request_id(r) != current_request_id) {
+ write_stderr(out,
+ "Ignoring STDIN for request %d (current request has id"
+ " %d)\n",
+ fastcgi_request_id(r),
+ current_request_id);
+ continue;
+ }
+
+ if (r->type != FCGI_STDIN) {
+ write_stderr(out, "Expected FCGI_STDIN but got %d\n", r->type);
+ goto done;
+ }
+
+ if (r->contentLengthB1 == 0 && r->contentLengthB0 == 0) {
+ write_stderr(out, "End of STDIN\n");
+ break;
+ }
+
+ this_len = (r->contentLengthB1 << 8) | r->contentLengthB0;
+
+ if (body_read + this_len > body_len) {
+ write_stderr(out, "Too much STDIN\n");
+ goto done;
+ }
+
+ memcpy(&body[body_read], r->contentData, this_len);
+ body_read += this_len;
+ }
+
+ body[body_read] = 0;
+
+ if (!(method = search_nvps(hs.nvps, "REQUEST_METHOD"))) {
+ write_stderr(out, "REQUEST_METHOD not set\n");
+ goto done;
+ }
+
+ if (!(path = search_nvps(hs.nvps, "SCRIPT_NAME"))) {
+ write_stderr(out, "SCRIPT_NAME not set\n");
+ goto done;
+ }
+
+ if ((path_info = search_nvps(hs.nvps, "PATH_INFO"))) {
+ int len1 = strlen(path), len2 = strlen(path_info);
+ int len = len1 + len2 + 1;
+
+ if (len > path_size) {
+ path_size = len;
+ path_buf = realloc(path_buf, path_size);
+ }
+
+ sprintf(path_buf, "%s%s", path, path_info);
+ path = path_buf;
+ }
+
+ if (!(query_string = search_nvps(hs.nvps, "QUERY_STRING")))
+ query_string = "";
+
+ uw_set_headers(ctx, get_header, &hs);
+ uw_set_env(ctx, get_env, &hs);
+
+ {
+ request_result rr;
+
+ rr = uw_request(rc, ctx, method, path, query_string, body, body_read,
+ on_success, on_failure,
+ out, log_error, log_debug,
+ in->sock, fastcgi_send_normal, fastcgi_close);
+
+ if (rr == KEEP_OPEN)
+ goto done2;
+
+ uw_output(ctx, write_stdout, out);
+ fastcgi_close_with(out, rr);
+ goto done2;
+ }
+
+ done:
+ close(in->sock);
+ done2:
+ fastcgi_input_reset(in);
+ uw_reset(ctx);
+ }
+
+ return NULL;
+}
+
+static void help(char *cmd) {
+ printf("Usage: %s [-t <thread-count>]\n", cmd);
+}
+
+static void sigint(int signum) {
+ printf("Exiting....\n");
+ exit(0);
+}
+
+static uw_loggers ls = {NULL, log_error, log_debug};
+
+int main(int argc, char *argv[]) {
+ // The skeleton for this function comes from Beej's sockets tutorial.
+ struct sockaddr_in their_addr; // connector's address information
+ socklen_t sin_size;
+ int nthreads = 1, i, *names, opt;
+ char *fwsa = getenv("FCGI_WEB_SERVER_ADDRS"), *nthreads_s = getenv("URWEB_NUM_THREADS");
+
+ if (nthreads_s) {
+ nthreads = atoi(nthreads_s);
+ if (nthreads <= 0) {
+ fprintf(stderr, "Bad URWEB_NUM_THREADS value\n");
+ return 1;
+ }
+ }
+
+ signal(SIGINT, sigint);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGUSR1, sigint);
+ signal(SIGTERM, sigint);
+
+ while ((opt = getopt(argc, argv, "ht:")) != -1) {
+ switch (opt) {
+ case '?':
+ fprintf(stderr, "Unknown command-line option");
+ help(argv[0]);
+ return 1;
+
+ case 'h':
+ help(argv[0]);
+ return 0;
+
+ case 't':
+ nthreads = atoi(optarg);
+ if (nthreads <= 0) {
+ fprintf(stderr, "Invalid thread count\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected getopt() behavior\n");
+ return 1;
+ }
+ }
+
+ uw_set_on_success("");
+ uw_request_init(&uw_application, &ls);
+
+ names = calloc(nthreads, sizeof(int));
+
+ sin_size = sizeof their_addr;
+
+ {
+ pthread_t thread;
+
+ pruner_data *pd = (pruner_data *)malloc(sizeof(pruner_data));
+ pd->app = &uw_application;
+ pd->loggers = &ls;
+
+ if (pthread_create_big(&thread, NULL, client_pruner, pd)) {
+ fprintf(stderr, "Error creating pruner thread\n");
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nthreads; ++i) {
+ pthread_t thread;
+ names[i] = i;
+ if (pthread_create_big(&thread, NULL, worker, &names[i])) {
+ fprintf(stderr, "Error creating worker thread #%d\n", i);
+ return 1;
+ }
+ }
+
+ while (1) {
+ int new_fd = accept(FCGI_LISTENSOCK_FILENO, (struct sockaddr *)&their_addr, &sin_size);
+
+ if (new_fd < 0) {
+ fprintf(stderr, "Socket accept failed\n");
+ return 1;
+ }
+
+ if (fwsa) {
+ char host[100], matched = 0;
+ char *ips, *sep;
+
+ if (getnameinfo((struct sockaddr *)&their_addr, sin_size, host, sizeof host, NULL, 0, NI_NUMERICHOST)) {
+ fprintf(stderr, "Remote IP determination failed\n");
+ return 1;
+ }
+
+ for (ips = fwsa; (sep = strchr(ips, ',')); ips = sep+1) {
+ if (!strncmp(ips, host, sep - ips)) {
+ matched = 1;
+ break;
+ }
+ }
+
+ if (!matched && strcmp(ips, host)) {
+ fprintf(stderr, "Remote address is not in FCGI_WEB_SERVER_ADDRS");
+ return 1;
+ }
+ }
+
+ uw_enqueue(new_fd);
+ }
+}
+
+void *uw_init_client_data() {
+ return NULL;
+}
+
+void uw_free_client_data(void *data) {
+}
+
+void uw_copy_client_data(void *dst, void *src) {
+}
+
+void uw_do_expunge(uw_context ctx, uw_Basis_client cli, void *data) {
+ uw_ensure_transaction(ctx);
+ uw_get_app(ctx)->expunger(ctx, cli);
+
+ if (uw_commit(ctx))
+ uw_error(ctx, UNLIMITED_RETRY, "Rerunning expunge transaction");
+}
+
+void uw_post_expunge(uw_context ctx, void *data) {
+}
+
+int uw_supports_direct_status = 0;
diff --git a/src/c/fastcgi.h b/src/c/fastcgi.h
new file mode 100644
index 0000000..826d808
--- /dev/null
+++ b/src/c/fastcgi.h
@@ -0,0 +1,113 @@
+// This code comes from the FastCGI 1.0 spec at:
+// http://www.fastcgi.com/drupal/node/6?q=node/22
+
+/*
+ * Listening socket file number
+ */
+#define FCGI_LISTENSOCK_FILENO 0
+
+typedef struct {
+ unsigned char version;
+ unsigned char type;
+ unsigned char requestIdB1;
+ unsigned char requestIdB0;
+ unsigned char contentLengthB1;
+ unsigned char contentLengthB0;
+ unsigned char paddingLength;
+ unsigned char reserved;
+} FCGI_Header;
+
+/*
+ * Number of bytes in a FCGI_Header. Future versions of the protocol
+ * will not reduce this number.
+ */
+#define FCGI_HEADER_LEN 8
+
+/*
+ * Value for version component of FCGI_Header
+ */
+#define FCGI_VERSION_1 1
+
+/*
+ * Values for type component of FCGI_Header
+ */
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+/*
+ * Value for requestId component of FCGI_Header
+ */
+#define FCGI_NULL_REQUEST_ID 0
+
+typedef struct {
+ unsigned char roleB1;
+ unsigned char roleB0;
+ unsigned char flags;
+ unsigned char reserved[5];
+} FCGI_BeginRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_BeginRequestBody body;
+} FCGI_BeginRequestRecord;
+
+/*
+ * Mask for flags component of FCGI_BeginRequestBody
+ */
+#define FCGI_KEEP_CONN 1
+
+/*
+ * Values for role component of FCGI_BeginRequestBody
+ */
+#define FCGI_RESPONDER 1
+#define FCGI_AUTHORIZER 2
+#define FCGI_FILTER 3
+
+typedef struct {
+ unsigned char appStatusB3;
+ unsigned char appStatusB2;
+ unsigned char appStatusB1;
+ unsigned char appStatusB0;
+ unsigned char protocolStatus;
+ unsigned char reserved[3];
+} FCGI_EndRequestBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_EndRequestBody body;
+} FCGI_EndRequestRecord;
+
+/*
+ * Values for protocolStatus component of FCGI_EndRequestBody
+ */
+#define FCGI_REQUEST_COMPLETE 0
+#define FCGI_CANT_MPX_CONN 1
+#define FCGI_OVERLOADED 2
+#define FCGI_UNKNOWN_ROLE 3
+
+/*
+ * Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
+ */
+#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
+#define FCGI_MAX_REQS "FCGI_MAX_REQS"
+#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
+
+typedef struct {
+ unsigned char type;
+ unsigned char reserved[7];
+} FCGI_UnknownTypeBody;
+
+typedef struct {
+ FCGI_Header header;
+ FCGI_UnknownTypeBody body;
+} FCGI_UnknownTypeRecord;
diff --git a/src/c/http.c b/src/c/http.c
new file mode 100644
index 0000000..21ad809
--- /dev/null
+++ b/src/c/http.c
@@ -0,0 +1,561 @@
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdarg.h>
+
+#include <pthread.h>
+
+#include "urweb.h"
+#include "request.h"
+#include "queue.h"
+
+extern uw_app uw_application;
+
+int uw_backlog = SOMAXCONN;
+static int keepalive = 0, quiet = 0;
+
+#define qfprintf(f, fmt, args...) do { if(!quiet) fprintf(f, fmt, ##args); } while(0)
+#define qprintf(fmt, args...) do { if(!quiet) printf(fmt, ##args); } while(0)
+
+static char *get_header(void *data, const char *h) {
+ char *s = data;
+ int len = strlen(h);
+ char *p;
+
+ while ((p = strchr(s, ':'))) {
+ if (p - s == len && !strncasecmp(s, h, len)) {
+ return p + 2;
+ } else {
+ if ((s = strchr(p, 0)) && s[1] != 0)
+ s += 2;
+ else
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static char *get_env(void *data, const char *name) {
+ return getenv(name);
+}
+
+static void on_success(uw_context ctx) {
+ uw_write_header(ctx, "HTTP/1.1 200 OK\r\n");
+}
+
+static void on_failure(uw_context ctx) {
+ uw_write_header(ctx, "HTTP/1.1 500 Internal Server Error\r\n");
+}
+
+static void log_error(void *data, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ vfprintf(stderr, fmt, ap);
+}
+
+static void log_debug(void *data, const char *fmt, ...) {
+ if (!quiet) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ vprintf(fmt, ap);
+ }
+}
+
+static uw_loggers ls = {NULL, log_error, log_debug};
+
+static unsigned max_buf_size = 1024 * 1024; // That's 1MB.
+
+static void *worker(void *data) {
+ int me = *(int *)data;
+ uw_context ctx = uw_request_new_context(me, &uw_application, &ls);
+ size_t buf_size = 1024;
+ char *buf = malloc(buf_size), *back = buf;
+ uw_request_context rc = uw_new_request_context();
+ int sock = 0;
+
+ while (1) {
+ if (sock == 0) {
+ back = buf;
+ sock = uw_dequeue();
+ }
+
+ uw_set_remoteSock(ctx, sock);
+
+ qprintf("Handling connection with thread #%d.\n", me);
+
+ while (1) {
+ int r;
+ char *method, *path, *query_string, *headers, *body, *after, *s, *s2;
+
+ if (back - buf == buf_size - 1) {
+ char *new_buf;
+ size_t new_buf_size = buf_size*2;
+ if (new_buf_size > max_buf_size) {
+ qfprintf(stderr, "HTTP input exceeds buffer-size limit of %u bytes.\n", max_buf_size);
+ close(sock);
+ sock = 0;
+ break;
+ }
+ new_buf = realloc(buf, new_buf_size);
+ if(!new_buf) {
+ qfprintf(stderr, "Realloc failed while receiving header\n");
+ close(sock);
+ sock = 0;
+ break;
+ }
+ buf_size = new_buf_size;
+ back = new_buf + (back - buf);
+ buf = new_buf;
+ }
+
+ *back = 0;
+ body = strstr(buf, "\r\n\r\n");
+ if (body == NULL) {
+ r = recv(sock, back, buf_size - 1 - (back - buf), 0);
+
+ if (r < 0) {
+ qfprintf(stderr, "Recv failed while receiving header, retcode %d errno %m\n", r);
+ close(sock);
+ sock = 0;
+ break;
+ }
+
+ if (r == 0) {
+ qprintf("Connection closed.\n");
+ close(sock);
+ sock = 0;
+ break;
+ }
+
+ back += r;
+ *back = 0;
+ }
+
+ if (body != NULL || (body = strstr(buf, "\r\n\r\n"))) {
+ request_result rr;
+ int should_keepalive = 0;
+
+ body[0] = body[1] = 0;
+ body += 4;
+
+ if ((s = strcasestr(buf, "\r\nContent-Length: ")) && s < body) {
+ int clen;
+
+ if (sscanf(s + 18, "%d\r\n", &clen) != 1) {
+ fprintf(stderr, "Malformed Content-Length header\n");
+ close(sock);
+ sock = 0;
+ break;
+ }
+
+ while (back - body < clen) {
+ if (back - buf == buf_size - 1) {
+ char *new_buf;
+ size_t new_buf_size = buf_size * 2;
+ if (new_buf_size > max_buf_size) {
+ qfprintf(stderr, "HTTP input exceeds buffer-size limit of %u bytes.\n", max_buf_size);
+ close(sock);
+ sock = 0;
+ break;
+ }
+ new_buf = realloc(buf, new_buf_size);
+ if(!new_buf) {
+ qfprintf(stderr, "Realloc failed while receiving content\n");
+ close(sock);
+ sock = 0;
+ goto done;
+ }
+
+ buf_size = new_buf_size;
+ back = new_buf + (back - buf);
+ body = new_buf + (body - buf);
+ s = new_buf + (s - buf);
+
+ buf = new_buf;
+ }
+
+ r = recv(sock, back, buf_size - 1 - (back - buf), 0);
+
+ if (r < 0) {
+ qfprintf(stderr, "Recv failed while receiving content, retcode %d errno %m\n", r);
+ close(sock);
+ sock = 0;
+ goto done;
+ }
+
+ if (r == 0) {
+ qfprintf(stderr, "Connection closed.\n");
+ close(sock);
+ sock = 0;
+ goto done;
+ }
+
+ back += r;
+ *back = 0;
+ }
+
+ after = body + clen;
+ } else
+ after = body;
+
+ body[-4] = '\r';
+ body[-3] = '\n';
+
+ if (!(s = strstr(buf, "\r\n"))) {
+ fprintf(stderr, "No newline in request\n");
+ close(sock);
+ sock = 0;
+ goto done;
+ }
+
+ body[-4] = body[-3] = 0;
+
+ *s = 0;
+ headers = s + 2;
+ method = s = buf;
+
+ strsep(&s, " ");
+ if (!s) {
+ fprintf(stderr, "No first space in HTTP command\n");
+ close(sock);
+ sock = 0;
+ goto done;
+ }
+ path = s;
+
+ if ((s = strchr(path, ' ')))
+ *s = 0;
+
+ if ((s = strchr(path, '?'))) {
+ *s = 0;
+ query_string = s+1;
+ }
+ else
+ query_string = NULL;
+
+ s = headers;
+ while ((s2 = strchr(s, '\r'))) {
+ if (s2 == s) {
+ *s = 0;
+ break;
+ }
+
+ s = s2;
+
+ if (s[1] == 0)
+ break;
+
+ *s = 0;
+ s += 2;
+ }
+
+ uw_set_headers(ctx, get_header, headers);
+ uw_set_env(ctx, get_env, NULL);
+
+ qprintf("Serving URI %s....\n", path);
+ rr = uw_request(rc, ctx, method, path, query_string, body, back - body,
+ on_success, on_failure,
+ NULL, log_error, log_debug,
+ sock, uw_really_send, close);
+
+ if (rr != KEEP_OPEN) {
+ if (keepalive) {
+ char *connection = uw_Basis_requestHeader(ctx, "Connection");
+
+ should_keepalive = !(connection && !strcmp(connection, "close"));
+ }
+
+ if (!should_keepalive)
+ uw_write_header(ctx, "Connection: close\r\n");
+
+ if (!uw_has_contentLength(ctx)) {
+ char clen[100];
+
+ sprintf(clen, "Content-length: %d\r\n", uw_pagelen(ctx));
+ uw_write_header(ctx, clen);
+ }
+
+ uw_send(ctx, sock);
+ }
+
+ if (rr == SERVED || rr == FAILED) {
+ if (should_keepalive) {
+ // In case any other requests are queued up, shift
+ // unprocessed part of buffer to front.
+ int kept = back - after;
+
+ if (kept == 0) {
+ // No pipelining going on here.
+ // We'd might as well try to switch to a different connection,
+ // while we wait for more input on this one.
+ uw_enqueue(sock);
+ sock = 0;
+ } else {
+ // More input! Move it to the front and continue in this loop.
+ memmove(buf, after, kept);
+ back = buf + kept;
+ }
+ } else {
+ close(sock);
+ sock = 0;
+ }
+ } else if (rr == KEEP_OPEN)
+ sock = 0;
+ else
+ fprintf(stderr, "Illegal uw_request return code: %d\n", rr);
+
+ break;
+ }
+ }
+
+ done:
+ uw_reset(ctx);
+ }
+
+ return NULL;
+}
+
+static void help(char *cmd) {
+ printf("Usage: %s [-p <port>] [-a <IPv4 address>] [-A <IPv6 address>] [-t <thread count>] [-m <bytes>] [-k] [-q] [-T SEC]\nThe '-k' option turns on HTTP keepalive.\nThe '-q' option turns off some chatter on stdout.\nThe '-T' option sets socket recv timeout (0 disables timeout, default is 5 sec).\nThe '-m' sets the maximum size (in bytes) for any buffer used to hold HTTP data sent by clients. (The default is 1 MB.)\n", cmd);
+}
+
+static void sigint(int signum) {
+ printf("Exiting....\n");
+ exit(0);
+}
+
+union uw_sockaddr {
+ struct sockaddr sa;
+ struct sockaddr_in ipv4;
+ struct sockaddr_in6 ipv6;
+};
+
+int main(int argc, char *argv[]) {
+ // The skeleton for this function comes from Beej's sockets tutorial.
+ int sockfd; // listen on sock_fd
+ union uw_sockaddr my_addr;
+ union uw_sockaddr their_addr; // connector's address information
+ socklen_t my_size = 0, sin_size;
+ int yes = 1, uw_port = 8080, nthreads = 1, i, *names, opt;
+ int recv_timeout_sec = 5;
+
+ signal(SIGINT, sigint);
+ signal(SIGPIPE, SIG_IGN);
+
+ // default if not specified: IPv4 with my IP
+ memset(&my_addr, 0, sizeof my_addr);
+ my_addr.sa.sa_family = AF_INET;
+ my_addr.ipv4.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
+
+ while ((opt = getopt(argc, argv, "hp:a:A:t:kqT:m:")) != -1) {
+ switch (opt) {
+ case '?':
+ fprintf(stderr, "Unknown command-line option\n");
+ help(argv[0]);
+ return 1;
+
+ case 'h':
+ help(argv[0]);
+ return 0;
+
+ case 'p':
+ uw_port = atoi(optarg);
+ if (uw_port <= 0) {
+ fprintf(stderr, "Invalid port number\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ case 'a':
+ my_addr.sa.sa_family = AF_INET;
+ if (!inet_pton(AF_INET, optarg, &my_addr.ipv4.sin_addr)) {
+ fprintf(stderr, "Invalid IPv4 address\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ case 'A':
+ my_addr.sa.sa_family = AF_INET6;
+ if (!inet_pton(AF_INET6, optarg, &my_addr.ipv6.sin6_addr)) {
+ fprintf(stderr, "Invalid IPv6 address\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ case 't':
+ nthreads = atoi(optarg);
+ if (nthreads <= 0) {
+ fprintf(stderr, "Invalid thread count\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ case 'k':
+ keepalive = 1;
+ break;
+
+ case 'T':
+ recv_timeout_sec = atoi(optarg);
+ if (recv_timeout_sec < 0) {
+ fprintf(stderr, "Invalid recv timeout\n");
+ help(argv[0]);
+ return 1;
+ }
+ break;
+
+ case 'q':
+ quiet = 1;
+ break;
+
+ case 'm':
+ opt = atoi(optarg);
+ if (opt <= 0) {
+ fprintf(stderr, "Invalid maximum buffer size\n");
+ help(argv[0]);
+ return 1;
+ }
+ max_buf_size = opt;
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected getopt() behavior\n");
+ return 1;
+ }
+ }
+
+ uw_request_init(&uw_application, &ls);
+
+ names = calloc(nthreads, sizeof(int));
+
+ sockfd = socket(my_addr.sa.sa_family, SOCK_STREAM, 0); // do some error checking!
+
+ if (sockfd < 0) {
+ fprintf(stderr, "Listener socket creation failed\n");
+ return 1;
+ }
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) {
+ fprintf(stderr, "Listener socket option setting failed\n");
+ return 1;
+ }
+
+ switch (my_addr.sa.sa_family)
+ {
+ case AF_INET:
+ my_size = sizeof(my_addr.ipv4);
+ my_addr.ipv4.sin_port = htons(uw_port);
+ break;
+
+ case AF_INET6:
+ my_size = sizeof(my_addr.ipv6);
+ my_addr.ipv6.sin6_port = htons(uw_port);
+ break;
+ }
+
+ if (bind(sockfd, &my_addr.sa, my_size) < 0) {
+ fprintf(stderr, "Listener socket bind failed\n");
+ return 1;
+ }
+
+ if (listen(sockfd, uw_backlog) < 0) {
+ fprintf(stderr, "Socket listen failed\n");
+ return 1;
+ }
+
+ sin_size = sizeof their_addr;
+
+ qprintf("Starting the Ur/Web native HTTP server, which is intended for use\n"
+ "ONLY DURING DEVELOPMENT. You probably want to use one of the other backends,\n"
+ "behind a production-quality HTTP server, for a real deployment.\n\n");
+
+ qprintf("Listening on port %d....\n", uw_port);
+
+ {
+ pthread_t thread;
+
+ pruner_data *pd = (pruner_data *)malloc(sizeof(pruner_data));
+ pd->app = &uw_application;
+ pd->loggers = &ls;
+
+ if (pthread_create_big(&thread, NULL, client_pruner, pd)) {
+ fprintf(stderr, "Error creating pruner thread\n");
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nthreads; ++i) {
+ pthread_t thread;
+ names[i] = i;
+ if (pthread_create_big(&thread, NULL, worker, &names[i])) {
+ fprintf(stderr, "Error creating worker thread #%d\n", i);
+ return 1;
+ }
+ }
+
+ while (1) {
+ int new_fd = accept(sockfd, &their_addr.sa, &sin_size);
+
+ if (new_fd < 0) {
+ qfprintf(stderr, "Socket accept failed\n");
+ } else {
+ qprintf("Accepted connection.\n");
+
+ if (keepalive) {
+ int flag = 1;
+ setsockopt(new_fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
+ }
+
+ if(recv_timeout_sec>0) {
+ int ret;
+ struct timeval tv;
+ memset(&tv, 0, sizeof(struct timeval));
+ tv.tv_sec = recv_timeout_sec;
+ ret = setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));
+ if(ret != 0) {
+ qfprintf(stderr, "Timeout setting failed, errcode %d errno '%m'\n", ret);
+ }
+ }
+
+ uw_enqueue(new_fd);
+ }
+ }
+}
+
+void *uw_init_client_data() {
+ return NULL;
+}
+
+void uw_free_client_data(void *data) {
+}
+
+void uw_copy_client_data(void *dst, void *src) {
+}
+
+void uw_do_expunge(uw_context ctx, uw_Basis_client cli, void *data) {
+ uw_ensure_transaction(ctx);
+ uw_get_app(ctx)->expunger(ctx, cli);
+
+ if (uw_commit(ctx))
+ uw_error(ctx, UNLIMITED_RETRY, "Rerunning expunge transaction");
+}
+
+void uw_post_expunge(uw_context ctx, void *data) {
+}
+
+int uw_supports_direct_status = 1;
diff --git a/src/c/memmem.c b/src/c/memmem.c
new file mode 100644
index 0000000..f31f4e3
--- /dev/null
+++ b/src/c/memmem.c
@@ -0,0 +1,87 @@
+#include "config.h"
+
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2003 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+// Function renamed by Adam Chlipala in 2016.
+
+#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD$");
+#endif /* LIBC_SCCS and not lint */
+
+#if !defined(_KERNEL) && !defined(_STANDALONE)
+#include <assert.h>
+#include <string.h>
+#else
+#include <lib/libkern/libkern.h>
+#define _DIAGASSERT(x) (void)0
+#define NULL ((char *)0)
+#endif
+
+/*
+ * 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 *
+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;
+
+ /* Initialize pattern pointer */
+ char *pp = (char *) b2;
+
+ /* Intialize end of search address space pointer */
+ char *eos = sp + len1 - len2;
+
+ while (sp <= eos) {
+ if (*sp == *pp)
+ if (memcmp(sp, pp, len2) == 0)
+ return sp;
+
+ sp++;
+ }
+
+ return NULL;
+}
diff --git a/src/c/openssl.c b/src/c/openssl.c
new file mode 100644
index 0000000..5982b83
--- /dev/null
+++ b/src/c/openssl.c
@@ -0,0 +1,139 @@
+#include "config.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <openssl/opensslv.h>
+#include <openssl/sha.h>
+#include <openssl/rand.h>
+
+#define PASSSIZE 4
+
+int uw_hash_blocksize = 32;
+
+static int password[PASSSIZE];
+
+char *uw_sig_file = NULL;
+
+static void random_password() {
+ if (!RAND_bytes((unsigned char *)password, sizeof password)) {
+ fprintf(stderr, "Error generating random password\n");
+ perror("RAND_bytes");
+ exit(1);
+ }
+}
+
+#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) {
+ CRYPTO_THREADID_set_pointer(result, pthread_self());
+}
+#else
+static void thread_id(CRYPTO_THREADID *const result) {
+ CRYPTO_THREADID_set_numeric(result, (unsigned long)pthread_self());
+}
+#endif
+
+static void lock_or_unlock(const int mode, const int type, const char *file,
+ const int line) {
+ pthread_mutex_t *const lock = &openssl_locks[type];
+ if (mode & CRYPTO_LOCK) {
+ if (pthread_mutex_lock(lock)) {
+ fprintf(stderr, "Can't take lock at %s:%d\n", file, line);
+ exit(1);
+ }
+ } else {
+ if (pthread_mutex_unlock(lock)) {
+ fprintf(stderr, "Can't release lock at %s:%d\n", file, line);
+ exit(1);
+ }
+ }
+}
+
+static void init_openssl() {
+ int i;
+ // Set up OpenSSL.
+ assert(openssl_locks == NULL);
+ openssl_locks = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
+ if (!openssl_locks) {
+ perror("malloc");
+ exit(1);
+ }
+ for (i = 0; i < CRYPTO_num_locks(); ++i) {
+ pthread_mutex_init(&(openssl_locks[i]), NULL);
+ }
+ 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;
+
+ if (access(uw_sig_file, F_OK)) {
+ random_password();
+
+ if ((fd = open(uw_sig_file, O_WRONLY | O_CREAT, 0700)) < 0) {
+ fprintf(stderr, "Can't open signature file %s\n", uw_sig_file);
+ perror("open");
+ exit(1);
+ }
+
+ if (write(fd, &password, sizeof password) != sizeof password) {
+ fprintf(stderr, "Error writing signature file\n");
+ exit(1);
+ }
+
+ close(fd);
+ } else {
+ if ((fd = open(uw_sig_file, O_RDONLY)) < 0) {
+ fprintf(stderr, "Can't open signature file %s\n", uw_sig_file);
+ perror("open");
+ exit(1);
+ }
+
+ if (read(fd, &password, sizeof password) != sizeof password) {
+ fprintf(stderr, "Error reading signature file\n");
+ exit(1);
+ }
+
+ close(fd);
+ }
+ } else
+ random_password();
+}
+
+void uw_sign(const char *in, unsigned char *out) {
+ SHA256_CTX c;
+
+ SHA256_Init(&c);
+ SHA256_Update(&c, password, sizeof password);
+ SHA256_Update(&c, in, strlen(in));
+ SHA256_Final(out, &c);
+}
diff --git a/src/c/queue.c b/src/c/queue.c
new file mode 100644
index 0000000..645f69e
--- /dev/null
+++ b/src/c/queue.c
@@ -0,0 +1,63 @@
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <pthread.h>
+
+typedef struct node {
+ int fd;
+ struct node *next;
+} *node;
+
+static node front = NULL, back = NULL;
+
+static int empty() {
+ return front == NULL;
+}
+
+static void enqueue(int fd) {
+ node n = malloc(sizeof(struct node));
+
+ n->fd = fd;
+ n->next = NULL;
+ if (back)
+ back->next = n;
+ else
+ front = n;
+ back = n;
+}
+
+static int dequeue() {
+ int ret = front->fd;
+ node n = front->next;
+ free(front);
+
+ front = n;
+
+ if (!front)
+ back = NULL;
+
+ return ret;
+}
+
+static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
+
+int uw_dequeue() {
+ int sock;
+
+ pthread_mutex_lock(&queue_mutex);
+ while (empty())
+ pthread_cond_wait(&queue_cond, &queue_mutex);
+ sock = dequeue();
+ pthread_mutex_unlock(&queue_mutex);
+
+ return sock;
+}
+
+void uw_enqueue(int new_fd) {
+ pthread_mutex_lock(&queue_mutex);
+ enqueue(new_fd);
+ pthread_cond_broadcast(&queue_cond);
+ pthread_mutex_unlock(&queue_mutex);
+}
diff --git a/src/c/request.c b/src/c/request.c
new file mode 100644
index 0000000..a7f2385
--- /dev/null
+++ b/src/c/request.c
@@ -0,0 +1,614 @@
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <pthread.h>
+
+#include "urweb.h"
+#include "request.h"
+
+#define MAX_RETRIES 5
+
+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);
+
+ if (r) {
+ log_error(logger_data, "Error running SQL ROLLBACK\n");
+ uw_reset(ctx);
+ uw_write(ctx, "HTTP/1.1 500 Internal Server Error\r\n");
+ uw_write(ctx, "Content-type: text/plain\r\n\r\n");
+ uw_write(ctx, "Error running SQL ROLLBACK\n");
+ uw_set_error_message(ctx, "Database error; you are probably out of storage space.");
+ }
+
+ return r;
+}
+
+uw_context uw_request_new_context(int id, uw_app *app, uw_loggers *ls) {
+ void *logger_data = ls->logger_data;
+ uw_logger log_debug = ls->log_debug;
+ uw_logger log_error = ls->log_error;
+ uw_context ctx = uw_init(id, ls);
+ int retries_left = MAX_RETRIES;
+
+ if (uw_set_app(ctx, app)) {
+ log_error(logger_data, "Unable to initialize request context. Most likely the limit on number of form inputs has been exceeded.\n");
+ uw_free(ctx);
+ return NULL;
+ }
+
+ while (1) {
+ failure_kind fk = uw_begin_init(ctx);
+
+ if (fk == SUCCESS) {
+ log_debug(logger_data, "Database connection initialized.\n");
+ break;
+ } else if (fk == BOUNDED_RETRY) {
+ if (retries_left) {
+ log_debug(logger_data, "Initialization error triggers bounded retry: %s\n", uw_error_message(ctx));
+ --retries_left;
+ } else {
+ log_error(logger_data, "Fatal initialization error (out of retries): %s\n", uw_error_message(ctx));
+ uw_free(ctx);
+ return NULL;
+ }
+ } else if (fk == UNLIMITED_RETRY)
+ log_debug(logger_data, "Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx));
+ else if (fk == FATAL) {
+ log_error(logger_data, "Fatal initialization error: %s\n", uw_error_message(ctx));
+ uw_free(ctx);
+ return NULL;
+ } else {
+ log_error(logger_data, "Unknown uw_begin_init return code!\n");
+ uw_free(ctx);
+ return NULL;
+ }
+ }
+
+ return ctx;
+}
+
+static void *ticker(void *data) {
+ while (1) {
+ usleep(100000);
+ ++uw_time;
+ }
+
+ return NULL;
+}
+
+typedef struct {
+ int id;
+ uw_loggers *ls;
+ uw_periodic pdic;
+ uw_app *app;
+} periodic;
+
+static void *periodic_loop(void *data) {
+ periodic *p = (periodic *)data;
+ uw_context ctx = uw_request_new_context(p->id, p->app, p->ls);
+
+ if (!ctx)
+ exit(1);
+
+ while (1) {
+ int retries_left = MAX_RETRIES;
+
+ failure_kind r;
+ do {
+ uw_reset(ctx);
+ r = uw_runCallback(ctx, p->pdic.callback);
+ if (r == BOUNDED_RETRY)
+ --retries_left;
+ else if (r == UNLIMITED_RETRY)
+ p->ls->log_debug(p->ls->logger_data, "Error triggers unlimited retry in periodic: %s\n", uw_error_message(ctx));
+ else if (r == BOUNDED_RETRY)
+ p->ls->log_debug(p->ls->logger_data, "Error triggers bounded retry in periodic: %s\n", uw_error_message(ctx));
+ else if (r == FATAL)
+ p->ls->log_error(p->ls->logger_data, "Fatal error: %s\n", uw_error_message(ctx));
+ if (r == FATAL || r == BOUNDED_RETRY || r == UNLIMITED_RETRY)
+ if (try_rollback(ctx, 0, p->ls->logger_data, p->ls->log_error))
+ return NULL;
+ } while (r == UNLIMITED_RETRY || (r == BOUNDED_RETRY && retries_left > 0));
+
+ if (r != FATAL && r != BOUNDED_RETRY) {
+ if (uw_commit(ctx))
+ r = UNLIMITED_RETRY;
+ }
+
+ sleep(p->pdic.period);
+ };
+}
+
+static unsigned long long stackSize;
+
+int pthread_create_big(pthread_t *outThread, void *foo, void *threadFunc, void *arg)
+{
+ if (stackSize > 0) {
+ int err;
+ pthread_attr_t stackSizeAttribute;
+
+ err = pthread_attr_init(&stackSizeAttribute);
+ if (err) return err;
+
+ err = pthread_attr_setstacksize(&stackSizeAttribute, stackSize);
+ if (err) return err;
+
+ return pthread_create(outThread, &stackSizeAttribute, threadFunc, arg);
+ } else {
+ return pthread_create(outThread, NULL, threadFunc, arg);
+ }
+}
+
+void uw_request_init(uw_app *app, uw_loggers* ls) {
+ uw_context ctx;
+ failure_kind fk;
+ uw_periodic *ps;
+ int id;
+ char *stackSize_s;
+
+ uw_logger log_debug = ls->log_debug;
+ uw_logger log_error = ls->log_error;
+ void* logger_data = ls->logger_data;
+
+ if ((stackSize_s = getenv("URWEB_STACK_SIZE")) != NULL && stackSize_s[0] != 0) {
+ stackSize = atoll(stackSize_s);
+
+ if (stackSize <= 0) {
+ fprintf(stderr, "Invalid stack size \"%s\"\n", stackSize_s);
+ exit(1);
+ }
+ }
+
+ uw_global_init();
+ uw_app_init(app);
+
+ {
+ pthread_t thread;
+
+ if (uw_time_max && pthread_create_big(&thread, NULL, ticker, NULL)) {
+ fprintf(stderr, "Error creating ticker thread\n");
+ exit(1);
+ }
+ }
+
+ ctx = uw_request_new_context(0, app, ls);
+
+ if (!ctx)
+ exit(1);
+
+ for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) {
+ log_debug(logger_data, "Unlimited retry during init: %s\n", uw_error_message(ctx));
+ uw_rollback(ctx, 1);
+ uw_reset(ctx);
+ }
+
+ if (fk != SUCCESS) {
+ log_error(logger_data, "Failed to initialize database! %s\n", uw_error_message(ctx));
+ uw_rollback(ctx, 0);
+ exit(1);
+ }
+
+ uw_free(ctx);
+
+ id = 1;
+ for (ps = app->periodics; ps->callback; ++ps) {
+ pthread_t thread;
+ periodic *arg = malloc(sizeof(periodic));
+ arg->id = id++;
+ arg->ls = ls;
+ arg->pdic = *ps;
+ arg->app = app;
+
+ if (pthread_create_big(&thread, NULL, periodic_loop, arg)) {
+ fprintf(stderr, "Error creating periodic thread\n");
+ exit(1);
+ }
+ }
+}
+
+
+struct uw_rc {
+ size_t path_copy_size, queryString_size;
+ char *path_copy, *queryString;
+};
+
+uw_request_context uw_new_request_context(void) {
+ uw_request_context r = malloc(sizeof(struct uw_rc));
+ r->path_copy_size = 0;
+ r->queryString_size = 1;
+ r->path_copy = malloc(0);
+ r->queryString = malloc(1);
+ return r;
+}
+
+void uw_free_request_context(uw_request_context r) {
+ free(r->path_copy);
+ free(r->queryString);
+ free(r);
+}
+
+request_result uw_request(uw_request_context rc, uw_context ctx,
+ char *method, char *path, char *query_string,
+ char *body, size_t body_len,
+ void (*on_success)(uw_context), void (*on_failure)(uw_context),
+ void *logger_data, uw_logger log_error, uw_logger log_debug,
+ int sock,
+ int (*send)(int sockfd, const void *buf, ssize_t len),
+ int (*close)(int fd)) {
+ int retries_left = MAX_RETRIES;
+ failure_kind fk;
+ int is_post = 0;
+ char *boundary = NULL;
+ size_t boundary_len = 0;
+ char *inputs;
+ const char *prefix = uw_get_url_prefix(ctx);
+ char *s;
+ int had_error = 0, is_fancy = 0;
+ char errmsg[ERROR_BUF_LEN];
+
+ uw_reset(ctx);
+
+ rc->queryString[0] = 0;
+
+ for (s = path; *s; ++s) {
+ if (s[0] == '%' && s[1] == '2' && s[2] == '7') {
+ s[0] = '\'';
+ memmove(s+1, s+3, strlen(s+3)+1);
+ }
+ }
+
+ uw_set_currentUrl(ctx, path);
+
+ if (!strcmp(method, "POST")) {
+ char *clen_s = uw_Basis_requestHeader(ctx, "Content-length");
+ if (!clen_s) {
+ clen_s = "0";
+ /*log_error(logger_data, "No Content-length with POST\n");
+ return FAILED;*/
+ }
+ int clen = atoi(clen_s);
+ if (clen < 0) {
+ log_error(logger_data, "Negative Content-length with POST\n");
+ return FAILED;
+ }
+
+ if (body_len < clen) {
+ log_error(logger_data, "Request doesn't contain all POST data (according to Content-Length)\n");
+ return FAILED;
+ }
+
+ is_post = 1;
+ uw_isPost(ctx);
+
+ clen_s = uw_Basis_requestHeader(ctx, "Content-type");
+
+ if (!clen_s || strcasecmp(clen_s, "application/x-www-form-urlencoded"))
+ is_fancy = 1;
+
+ if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) {
+ if (strncasecmp(clen_s + 19, "; boundary=", 11)) {
+ log_error(logger_data, "Bad multipart boundary spec");
+ return FAILED;
+ }
+
+ boundary = clen_s + 28;
+ boundary[0] = '-';
+ boundary[1] = '-';
+ boundary_len = strlen(boundary);
+ } else if (clen_s) {
+ uw_Basis_postBody pb = {clen_s, body, body_len};
+ uw_postBody(ctx, pb);
+ }
+ } else if (strcmp(method, "GET")) {
+ log_error(logger_data, "Not ready for non-GET/POST command: %s\n", method);
+ return FAILED;
+ }
+
+ if (!strncmp(path, prefix, strlen(prefix))
+ && !strcmp(path + strlen(prefix), ".msgs")) {
+ char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client");
+ char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass");
+
+ if (sock < 0) {
+ log_error(logger_data, ".msgs requested, but not socket supplied\n");
+ return FAILED;
+ }
+
+ if (id && pass) {
+ unsigned idn = atoi(id);
+ uw_client_connect(idn, atoi(pass), sock, send, close, logger_data, log_error);
+ log_debug(logger_data, "Processed request for messages by client %u\n\n", idn);
+ return KEEP_OPEN;
+ }
+ else {
+ log_error(logger_data, "Missing fields in .msgs request: %s, %s\n\n", id, pass);
+ return FAILED;
+ }
+ }
+
+ if (boundary) {
+ char *part = body, *after_sub_headers, *header, *after_header;
+ size_t part_len;
+
+ part = strstr(part, boundary);
+ if (!part) {
+ log_error(logger_data, "Missing first multipart boundary\n");
+ return FAILED;
+ }
+ part += boundary_len;
+
+ while (1) {
+ char *name = NULL, *filename = NULL, *type = NULL;
+
+ if (part[0] == '-' && part[1] == '-')
+ break;
+
+ if (*part != '\r') {
+ log_error(logger_data, "No \\r after multipart boundary\n");
+ return FAILED;
+ }
+ ++part;
+ if (*part != '\n') {
+ log_error(logger_data, "No \\n after multipart boundary\n");
+ return FAILED;
+ }
+ ++part;
+
+ if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) {
+ log_error(logger_data, "Missing end of headers after multipart boundary\n");
+ return FAILED;
+ }
+ after_sub_headers[2] = 0;
+ after_sub_headers += 4;
+
+ for (header = part; (after_header = strstr(header, "\r\n")); header = after_header + 2) {
+ char *colon, *after_colon;
+
+ *after_header = 0;
+ if (!(colon = strchr(header, ':'))) {
+ log_error(logger_data, "Missing colon in multipart sub-header\n");
+ return FAILED;
+ }
+ *colon++ = 0;
+ if (*colon++ != ' ') {
+ log_error(logger_data, "No space after colon in multipart sub-header\n");
+ return FAILED;
+ }
+
+ if (!strcasecmp(header, "Content-Disposition")) {
+ if (strncmp(colon, "form-data; ", 11)) {
+ log_error(logger_data, "Multipart data is not \"form-data\"\n");
+ return FAILED;
+ }
+
+ for (colon += 11; (after_colon = strchr(colon, '=')); colon = after_colon) {
+ char *data;
+ after_colon[0] = 0;
+ if (after_colon[1] != '"') {
+ log_error(logger_data, "Disposition setting is missing initial quote\n");
+ return FAILED;
+ }
+ data = after_colon+2;
+ if (!(after_colon = strchr(data, '"'))) {
+ log_error(logger_data, "Disposition setting is missing final quote\n");
+ return FAILED;
+ }
+ after_colon[0] = 0;
+ ++after_colon;
+ if (after_colon[0] == ';' && after_colon[1] == ' ')
+ after_colon += 2;
+
+ if (!strcasecmp(colon, "name"))
+ name = data;
+ else if (!strcasecmp(colon, "filename"))
+ filename = data;
+ }
+ } else if (!strcasecmp(header, "Content-Type")) {
+ type = colon;
+ }
+ }
+
+ 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;
+ }
+ part[-2] = 0;
+ part_len = part - after_sub_headers - 2;
+ part[0] = 0;
+ part += boundary_len;
+
+ if (filename) {
+ uw_Basis_file f = {filename, type, {part_len, after_sub_headers}};
+
+ if (uw_set_file_input(ctx, name, f)) {
+ log_error(logger_data, "%s\n", uw_error_message(ctx));
+ return FAILED;
+ }
+ } else if (uw_set_input(ctx, name, after_sub_headers)) {
+ log_error(logger_data, "%s\n", uw_error_message(ctx));
+ return FAILED;
+ }
+ }
+ }
+ else if (!is_fancy) {
+ inputs = is_post ? body : query_string;
+
+ if (inputs) {
+ char *name, *value;
+ int len = strlen(inputs);
+
+ if (len+1 > rc->queryString_size) {
+ char *qs = realloc(rc->queryString, len+1);
+ if(qs == NULL) {
+ log_error(logger_data, "queryString is too long (not enough memory)\n");
+ return FAILED;
+ }
+ rc->queryString = qs;
+ rc->queryString_size = len+1;
+ }
+ strcpy(rc->queryString, inputs);
+
+ while (*inputs) {
+ name = inputs;
+ if ((inputs = strchr(inputs, '&')))
+ *inputs++ = 0;
+ else
+ inputs = strchr(name, 0);
+
+ if ((value = strchr(name, '='))) {
+ *value++ = 0;
+ if (uw_set_input(ctx, name, value)) {
+ log_error(logger_data, "%s\n", uw_error_message(ctx));
+ return FAILED;
+ }
+ }
+ else if (uw_set_input(ctx, name, "")) {
+ log_error(logger_data, "%s\n", uw_error_message(ctx));
+ return FAILED;
+ }
+ }
+ }
+ }
+
+ while (1) {
+ uw_setQueryString(ctx, rc->queryString);
+
+ if (!had_error) {
+ size_t path_len = strlen(path);
+
+ on_success(ctx);
+
+ if (path_len + 1 > rc->path_copy_size) {
+ char *pc = realloc(rc->path_copy, path_len + 1);
+ if(pc == NULL) {
+ log_error(logger_data, "Path is too long (not enough memory)\n");
+ return FAILED;
+ }
+ rc->path_copy = pc;
+ rc->path_copy_size = path_len + 1;
+ }
+ strcpy(rc->path_copy, path);
+
+ uw_set_deadline(ctx, uw_time + uw_time_max);
+ fk = uw_begin(ctx, rc->path_copy);
+ } else {
+ uw_set_deadline(ctx, uw_time + uw_time_max);
+ fk = uw_begin_onError(ctx, errmsg);
+ }
+
+ if (fk == SUCCESS || fk == RETURN_INDIRECTLY) {
+ uw_commit(ctx);
+ if (uw_has_error(ctx) && !had_error) {
+ log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx));
+ uw_reset_keep_error_message(ctx);
+ on_failure(ctx);
+
+ if (uw_get_app(ctx)->on_error) {
+ had_error = 1;
+ strcpy(errmsg, uw_error_message(ctx));
+ } else {
+ try_rollback(ctx, 0, logger_data, log_error);
+
+ uw_write_header(ctx, "Content-type: text/html\r\n");
+ uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>");
+ uw_write(ctx, "Fatal error: ");
+ uw_write(ctx, uw_error_message(ctx));
+ uw_write(ctx, "\n</body></html>");
+
+ return FAILED;
+ }
+ } else
+ return had_error ? FAILED : SERVED;
+ } else if (fk == BOUNDED_RETRY) {
+ if (retries_left) {
+ log_debug(logger_data, "Error triggers bounded retry: %s\n", uw_error_message(ctx));
+ --retries_left;
+ }
+ else {
+ log_error(logger_data, "Fatal error (out of retries): %s\n", uw_error_message(ctx));
+
+ if (!had_error && uw_get_app(ctx)->on_error) {
+ had_error = 1;
+ strcpy(errmsg, uw_error_message(ctx));
+ } else {
+ try_rollback(ctx, 0, logger_data, log_error);
+
+ uw_reset_keep_error_message(ctx);
+ on_failure(ctx);
+ uw_write_header(ctx, "Content-type: text/plain\r\n");
+ uw_write(ctx, "Fatal error (out of retries): ");
+ uw_write(ctx, uw_error_message(ctx));
+ uw_write(ctx, "\n");
+
+ return FAILED;
+ }
+ }
+ } else if (fk == UNLIMITED_RETRY)
+ log_debug(logger_data, "Error triggers unlimited retry: %s\n", uw_error_message(ctx));
+ else if (fk == FATAL) {
+ log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx));
+
+ if (uw_get_app(ctx)->on_error && !had_error) {
+ had_error = 1;
+ strcpy(errmsg, uw_error_message(ctx));
+ } else {
+ try_rollback(ctx, 0, logger_data, log_error);
+
+ uw_reset_keep_error_message(ctx);
+ on_failure(ctx);
+ uw_write_header(ctx, "Content-type: text/html\r\n");
+ uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>");
+ uw_write(ctx, "Fatal error: ");
+ uw_write(ctx, uw_error_message(ctx));
+ uw_write(ctx, "\n</body></html>");
+
+ return FAILED;
+ }
+ } else {
+ log_error(logger_data, "Unknown uw_handle return code!\n");
+
+ if (uw_get_app(ctx)->on_error && !had_error) {
+ had_error = 1;
+ strcpy(errmsg, "Unknown uw_handle return code");
+ } else {
+ try_rollback(ctx, 0, logger_data, log_error);
+
+ uw_reset_keep_request(ctx);
+ on_failure(ctx);
+ uw_write_header(ctx, "Content-type: text/plain\r\n");
+ uw_write(ctx, "Unknown uw_handle return code!\n");
+
+ return FAILED;
+ }
+ }
+
+ if (try_rollback(ctx, 1, logger_data, log_error))
+ return FAILED;
+
+ uw_reset_keep_request(ctx);
+ }
+}
+
+void *client_pruner(void *data) {
+ pruner_data *pd = (pruner_data *)data;
+ uw_context ctx = uw_request_new_context(0, pd->app, pd->loggers);
+
+ if (!ctx)
+ exit(1);
+
+ while (1) {
+ uw_prune_clients(ctx);
+ sleep(5);
+ }
+}
diff --git a/src/c/static.c b/src/c/static.c
new file mode 100644
index 0000000..d70881e
--- /dev/null
+++ b/src/c/static.c
@@ -0,0 +1,70 @@
+#include "config.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "urweb.h"
+
+extern uw_app uw_application;
+
+static void log_(void *data, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ vprintf(fmt, ap);
+}
+
+static uw_loggers loggers = {NULL, log_, log_};
+
+static char *get_header(void *data, const char *h) {
+ return NULL;
+}
+
+int main(int argc, char *argv[]) {
+ uw_context ctx;
+ failure_kind fk;
+
+ if (argc != 2) {
+ fprintf(stderr, "Pass exactly one argument: the URI to run\n");
+ return 1;
+ }
+
+ ctx = uw_init(0, &loggers);
+ uw_set_app(ctx, &uw_application);
+ uw_set_headers(ctx, get_header, NULL);
+ uw_initialize(ctx);
+
+ while (1) {
+ fk = uw_begin(ctx, argv[1]);
+
+ if (fk == SUCCESS || fk == RETURN_INDIRECTLY) {
+ uw_commit(ctx);
+ uw_print(ctx, 1);
+ puts("");
+ return 0;
+ } else if (fk != UNLIMITED_RETRY) {
+ fprintf(stderr, "Error: %s\n", uw_error_message(ctx));
+ return 1;
+ }
+
+ uw_reset(ctx);
+ }
+}
+
+void *uw_init_client_data() {
+ return NULL;
+}
+
+void uw_free_client_data(void *data) {
+}
+
+void uw_copy_client_data(void *dst, void *src) {
+}
+
+void uw_do_expunge(uw_context ctx, uw_Basis_client cli, void *data) {
+}
+
+void uw_post_expunge(uw_context ctx, void *data) {
+}
+
+int uw_supports_direct_status = 0;
diff --git a/src/c/urweb.c b/src/c/urweb.c
new file mode 100644
index 0000000..6f2dde3
--- /dev/null
+++ b/src/c/urweb.c
@@ -0,0 +1,4980 @@
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/des.h>
+#include <openssl/rand.h>
+#include <time.h>
+#include <math.h>
+
+#include <pthread.h>
+
+#include "types.h"
+
+#include "uthash.h"
+
+uw_unit uw_unit_v = 0;
+
+
+// Socket extras
+
+int uw_really_send(int sock, const void *buf, ssize_t len) {
+ while (len > 0) {
+ ssize_t n = send(sock, buf, len, 0);
+
+ if (n < 0)
+ return n;
+
+ buf += n;
+ len -= n;
+ }
+
+ return 0;
+}
+
+int uw_really_write(int fd, const void *buf, size_t len) {
+ while (len > 0) {
+ ssize_t n = write(fd, buf, len);
+
+ if (n < 0)
+ return n;
+
+ buf += n;
+ len -= n;
+ }
+
+ return 0;
+}
+
+
+// Buffers
+
+void uw_buffer_init(size_t max, uw_buffer *b, size_t s) {
+ b->max = max;
+ b->front = b->start = malloc(s);
+ b->back = b->front + s;
+}
+
+void uw_buffer_free(uw_buffer *b) {
+ free(b->start);
+}
+
+void uw_buffer_reset(uw_buffer *b) {
+ b->front = b->start;
+ if (b->front != b->back) {
+ *b->front = 0;
+ }
+}
+
+int uw_buffer_check(uw_buffer *b, size_t extra) {
+ if (b->back - b->front < extra) {
+ size_t desired = b->front - b->start + extra, next;
+ char *new_heap;
+
+ next = b->back - b->start;
+ if (next == 0)
+ next = 1;
+ for (; next < desired; next *= 2);
+
+ if (next > b->max) {
+ if (desired <= b->max)
+ next = desired;
+ else
+ return 1;
+ }
+
+ new_heap = realloc(b->start, next);
+ b->front = new_heap + (b->front - b->start);
+ b->back = new_heap + next;
+ b->start = new_heap;
+ }
+
+ return 0;
+}
+
+__attribute__((noreturn)) void uw_error(uw_context, failure_kind, const char *, ...);
+
+static void ctx_uw_buffer_check(uw_context ctx, const char *kind, uw_buffer *b, size_t extra) {
+ if (uw_buffer_check(b, extra))
+ uw_error(ctx, FATAL, "Memory limit exceeded (%s)", kind);
+}
+
+size_t uw_buffer_used(uw_buffer *b) {
+ return b->front - b->start;
+}
+
+size_t uw_buffer_avail(uw_buffer *b) {
+ return b->back - b->start;
+}
+
+int uw_buffer_append(uw_buffer *b, const char *s, size_t len) {
+ if (uw_buffer_check(b, len+1))
+ return 1;
+
+ memcpy(b->front, s, len);
+ b->front += len;
+ *b->front = 0;
+
+ return 0;
+}
+
+static void ctx_uw_buffer_append(uw_context ctx, const char *kind, uw_buffer *b, const char *s, size_t len) {
+ ctx_uw_buffer_check(ctx, kind, b, len+1);
+
+ memcpy(b->front, s, len);
+ b->front += len;
+ *b->front = 0;
+}
+
+
+// Persistent state types
+
+typedef enum { UNUSED, USED } usage;
+
+typedef struct client {
+ unsigned id;
+ usage mode;
+ int pass;
+ struct client *next;
+ pthread_mutex_t lock, pull_lock;
+ uw_buffer msgs;
+ int sock;
+ int (*send)(int sockfd, const void *buf, ssize_t len);
+ int (*close)(int fd);
+ time_t last_contact;
+ unsigned n_channels;
+ unsigned refcount;
+ void *data;
+} client;
+
+
+// Persistent client state
+
+static client **clients, *clients_free, *clients_used;
+static unsigned n_clients;
+
+static pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;
+size_t uw_messages_max = SIZE_MAX;
+size_t uw_clients_max = SIZE_MAX;
+
+void *uw_init_client_data();
+void uw_free_client_data(void *);
+void uw_copy_client_data(void *dst, void *src);
+
+static uw_Basis_int my_rand() {
+ int ret, r = RAND_bytes((unsigned char *)&ret, sizeof ret);
+ if (r)
+ return abs(ret);
+ else
+ return -1;
+}
+
+static client *new_client(uw_context ctx) {
+ client *c;
+ int pass = my_rand();
+
+ if (pass < 0) uw_error(ctx, FATAL, "Random number generation failed during client initialization");
+
+ pthread_mutex_lock(&clients_mutex);
+
+ if (clients_free) {
+ c = clients_free;
+ clients_free = clients_free->next;
+ }
+ else if (n_clients >= uw_clients_max) {
+ pthread_mutex_unlock(&clients_mutex);
+ return NULL;
+ } else {
+ ++n_clients;
+ clients = realloc(clients, sizeof(client) * n_clients);
+ c = malloc(sizeof(client));
+ c->id = n_clients-1;
+ pthread_mutex_init(&c->lock, NULL);
+ pthread_mutex_init(&c->pull_lock, NULL);
+ uw_buffer_init(uw_messages_max, &c->msgs, 0);
+ clients[n_clients-1] = c;
+ }
+
+ pthread_mutex_lock(&c->lock);
+ c->mode = USED;
+ c->pass = pass;
+ c->sock = -1;
+ c->last_contact = time(NULL);
+ uw_buffer_reset(&c->msgs);
+ c->n_channels = 0;
+ c->refcount = 0;
+ c->data = uw_init_client_data();
+ pthread_mutex_unlock(&c->lock);
+
+ c->next = clients_used;
+ clients_used = c;
+
+ pthread_mutex_unlock(&clients_mutex);
+
+ return c;
+}
+
+static void use_client(client *c) {
+ pthread_mutex_lock(&c->lock);
+ ++c->refcount;
+ pthread_mutex_unlock(&c->lock);
+ pthread_mutex_lock(&c->pull_lock);
+}
+
+static void release_client(client *c) {
+ pthread_mutex_unlock(&c->pull_lock);
+ pthread_mutex_lock(&c->lock);
+ --c->refcount;
+ pthread_mutex_unlock(&c->lock);
+}
+
+static const char begin_msgs[] = "Content-type: text/plain\r\n\r\n";
+static pthread_t pruning_thread;
+static int pruning_thread_initialized = 0;
+
+static client *find_client(unsigned id) {
+ client *c;
+ int i_am_pruner = pruning_thread_initialized && pthread_equal(pruning_thread, pthread_self());
+
+ if (!i_am_pruner) pthread_mutex_lock(&clients_mutex);
+
+ if (id >= n_clients) {
+ if (!i_am_pruner) pthread_mutex_unlock(&clients_mutex);
+ return NULL;
+ }
+
+ c = clients[id];
+
+ if (!i_am_pruner) pthread_mutex_unlock(&clients_mutex);
+ return c;
+}
+
+static char *on_success = "HTTP/1.1 200 OK\r\n";
+static char *on_redirect = "HTTP/1.1 303 See Other\r\n";
+
+void uw_set_on_success(char *s) {
+ on_success = s;
+}
+
+static void chastise(int (*send)(int sockfd, const void *buf, ssize_t len), int sock) {
+ send(sock, on_success, strlen(on_success));
+ send(sock, begin_msgs, sizeof(begin_msgs) - 1);
+ send(sock, "R", 1);
+ close(sock);
+}
+
+void uw_client_connect(unsigned id, int pass, int sock,
+ int (*send)(int sockfd, const void *buf, ssize_t len),
+ int (*close)(int fd),
+ void *logger_data, uw_logger log_error) {
+ client *c = find_client(id);
+
+ if (c == NULL) {
+ chastise(send, sock);
+ log_error(logger_data, "Out-of-bounds client request (%u)\n", id);
+ return;
+ }
+
+ pthread_mutex_lock(&c->lock);
+
+ if (c->mode != USED) {
+ pthread_mutex_unlock(&c->lock);
+ chastise(send, sock);
+ log_error(logger_data, "Client request for unused slot (%u)\n", id);
+ return;
+ }
+
+ if (pass != c->pass) {
+ pthread_mutex_unlock(&c->lock);
+ chastise(send, sock);
+ log_error(logger_data, "Wrong client password (%u, %d)\n", id, pass);
+ return;
+ }
+
+ if (c->sock != -1) {
+ c->close(c->sock);
+ c->sock = -1;
+ }
+
+ c->last_contact = time(NULL);
+
+ if (uw_buffer_used(&c->msgs) > 0) {
+ send(sock, on_success, strlen(on_success));
+ send(sock, begin_msgs, sizeof(begin_msgs) - 1);
+ send(sock, c->msgs.start, uw_buffer_used(&c->msgs));
+ uw_buffer_reset(&c->msgs);
+ close(sock);
+ }
+ else {
+ c->sock = sock;
+ c->send = send;
+ c->close = close;
+ }
+
+ pthread_mutex_unlock(&c->lock);
+}
+
+static void free_client(client *c) {
+ c->mode = UNUSED;
+ c->pass = -1;
+
+ c->next = clients_free;
+ clients_free = c;
+}
+
+static uw_Basis_channel new_channel(client *c) {
+ uw_Basis_channel ch = {c->id, c->n_channels++};
+ return ch;
+}
+
+static void client_send(client *c, uw_buffer *msg, const char *script, int script_len) {
+ pthread_mutex_lock(&c->lock);
+
+ if (c->sock != -1) {
+ c->send(c->sock, on_success, strlen(on_success));
+ c->send(c->sock, begin_msgs, sizeof(begin_msgs) - 1);
+ if (script_len > 0) {
+ c->send(c->sock, "E\n", 2);
+ c->send(c->sock, script, script_len);
+ c->send(c->sock, "\n", 1);
+ }
+ c->send(c->sock, msg->start, uw_buffer_used(msg));
+ c->close(c->sock);
+ c->sock = -1;
+ } else if (uw_buffer_append(&c->msgs, msg->start, uw_buffer_used(msg)))
+ fprintf(stderr, "Client message buffer size exceeded");
+
+ pthread_mutex_unlock(&c->lock);
+}
+
+
+// Global entry points
+
+extern void uw_global_custom();
+extern void uw_init_crypto();
+
+void uw_global_init() {
+ clients = malloc(0);
+
+ uw_global_custom();
+ uw_init_crypto();
+
+ // Fast non-cryptographic strength randomness for Sqlcache.
+ srandom(clock());
+}
+
+void uw_app_init(uw_app *app) {
+ app->client_init();
+}
+
+int uw_time = 0, uw_time_max = 0, uw_min_heap = 0;
+
+
+// Single-request state
+
+typedef struct regions {
+ struct regions *next;
+} regions;
+
+typedef struct {
+ void (*func)(void*);
+ void *arg;
+} cleanup;
+
+typedef struct {
+ unsigned client;
+ uw_buffer msgs;
+} delta;
+
+typedef enum {
+ UNSET, NORMAL, FIL, SUBFORM, SUBFORMS, ENTRY
+} input_kind;
+
+typedef struct input {
+ input_kind kind;
+ union {
+ char *normal;
+ uw_Basis_file file;
+ struct {
+ struct input *fields, *parent;
+ } subform;
+ struct {
+ struct input *entries, *parent;
+ } subforms;
+ struct {
+ struct input *fields, *next, *parent;
+ } entry;
+ } data;
+} input;
+
+typedef struct {
+ void *data;
+ uw_callback commit, rollback;
+ uw_callback_with_retry free;
+} transactional;
+
+typedef struct {
+ char *name;
+ void *data;
+ void (*free)(void*);
+} global;
+
+typedef struct uw_Sqlcache_Update {
+ uw_Sqlcache_Cache *cache;
+ char **keys;
+ uw_Sqlcache_Value *value;
+ struct uw_Sqlcache_Update *next;
+} uw_Sqlcache_Update;
+
+typedef struct uw_Sqlcache_Unlock {
+ pthread_rwlock_t *lock;
+ struct uw_Sqlcache_Unlock *next;
+} uw_Sqlcache_Unlock;
+
+struct uw_context {
+ uw_app *app;
+ int id;
+
+ char *(*get_header)(void *, const char *);
+ void *get_header_data;
+
+ char *(*get_env)(void *, const char *);
+ void *get_env_data;
+
+ uw_buffer outHeaders, page, heap, script;
+ int allowed_to_return_indirectly, returning_indirectly;
+ input *inputs, *subinputs, *cur_container;
+ size_t sz_inputs, n_subinputs, used_subinputs;
+
+ unsigned long long source_count;
+
+ void *db;
+ int transaction_started;
+
+ jmp_buf jmp_buf;
+
+ regions *regions;
+
+ cleanup *cleanup, *cleanup_front, *cleanup_back;
+
+ const char *script_header;
+
+ int needs_push, needs_sig, could_write_db, at_most_one_query;
+
+ size_t n_deltas, used_deltas;
+ delta *deltas;
+
+ client *client;
+
+ transactional *transactionals;
+ size_t n_transactionals, used_transactionals;
+
+ global *globals;
+ size_t n_globals;
+
+ char *current_url;
+
+ int deadline;
+
+ void *client_data;
+
+ uw_loggers *loggers;
+
+ int isPost, hasPostBody;
+ uw_Basis_postBody postBody;
+ uw_Basis_string queryString;
+
+ unsigned nextId;
+
+ int amInitializing;
+
+ char error_message[ERROR_BUF_LEN];
+
+ int usedSig, needsResig;
+
+ char *output_buffer;
+ size_t output_buffer_size;
+
+ // Sqlcache.
+ int numRecording, recordingCapacity;
+ int *recordingOffsets, *scriptRecordingOffsets;
+ uw_Sqlcache_Update *cacheUpdate;
+ uw_Sqlcache_Update *cacheUpdateTail;
+ uw_Sqlcache_Unlock *cacheUnlock;
+
+ int remoteSock;
+};
+
+size_t uw_headers_max = SIZE_MAX;
+size_t uw_page_max = SIZE_MAX;
+size_t uw_heap_max = SIZE_MAX;
+size_t uw_script_max = SIZE_MAX;
+
+uw_context uw_init(int id, uw_loggers *lg) {
+ uw_context ctx = malloc(sizeof(struct uw_context));
+
+ ctx->app = NULL;
+ ctx->id = id;
+
+ ctx->get_header = NULL;
+ ctx->get_header_data = NULL;
+
+ ctx->get_env = NULL;
+ ctx->get_env_data = NULL;
+
+ uw_buffer_init(uw_headers_max, &ctx->outHeaders, 1);
+ ctx->outHeaders.start[0] = 0;
+ uw_buffer_init(uw_page_max, &ctx->page, 1);
+ ctx->page.start[0] = 0;
+ ctx->allowed_to_return_indirectly = ctx->returning_indirectly = 0;
+ uw_buffer_init(uw_heap_max, &ctx->heap, uw_min_heap);
+ uw_buffer_init(uw_script_max, &ctx->script, 1);
+ ctx->script.start[0] = 0;
+
+ ctx->inputs = malloc(0);
+ ctx->cur_container = NULL;
+ ctx->subinputs = malloc(0);
+ ctx->sz_inputs = ctx->n_subinputs = ctx->used_subinputs = 0;
+
+ ctx->db = NULL;
+ ctx->transaction_started = 0;
+
+ ctx->regions = NULL;
+
+ ctx->cleanup_front = ctx->cleanup_back = ctx->cleanup = malloc(0);
+
+ ctx->script_header = "";
+ ctx->needs_push = 0;
+ ctx->needs_sig = 0;
+ ctx->could_write_db = 1;
+ ctx->at_most_one_query = 0;
+
+ ctx->source_count = 0;
+
+ ctx->n_deltas = ctx->used_deltas = 0;
+ ctx->deltas = malloc(0);
+
+ ctx->client = NULL;
+
+ ctx->error_message[0] = 0;
+
+ ctx->transactionals = malloc(0);
+ ctx->n_transactionals = ctx->used_transactionals = 0;
+
+ ctx->globals = malloc(0);
+ ctx->n_globals = 0;
+
+ ctx->current_url = "";
+
+ ctx->deadline = INT_MAX;
+
+ ctx->client_data = uw_init_client_data();
+
+ ctx->loggers = lg;
+
+ ctx->isPost = ctx->hasPostBody = 0;
+
+ ctx->queryString = NULL;
+
+ ctx->nextId = 0;
+
+ ctx->amInitializing = 0;
+
+ ctx->usedSig = 0;
+ ctx->needsResig = 0;
+
+ ctx->output_buffer = malloc(1);
+ ctx->output_buffer_size = 1;
+
+ ctx->numRecording = 0;
+ ctx->recordingCapacity = 0;
+ ctx->recordingOffsets = malloc(0);
+ ctx->scriptRecordingOffsets = malloc(0);
+ ctx->cacheUpdate = NULL;
+ ctx->cacheUpdateTail = NULL;
+
+ ctx->remoteSock = -1;
+
+ ctx->cacheUnlock = NULL;
+
+ return ctx;
+}
+
+size_t uw_inputs_max = SIZE_MAX;
+
+uw_app *uw_get_app(uw_context ctx) {
+ return ctx->app;
+}
+
+int uw_set_app(uw_context ctx, uw_app *app) {
+ ctx->app = app;
+
+ if (app && app->inputs_len > ctx->sz_inputs) {
+ if (app->inputs_len > uw_inputs_max)
+ return 1;
+
+ ctx->sz_inputs = app->inputs_len;
+ ctx->inputs = realloc(ctx->inputs, ctx->sz_inputs * sizeof(input));
+ memset(ctx->inputs, 0, ctx->sz_inputs * sizeof(input));
+ }
+
+ return 0;
+}
+
+void uw_set_client_data(uw_context ctx, void *data) {
+ uw_copy_client_data(ctx->client_data, data);
+}
+
+void uw_set_db(uw_context ctx, void *db) {
+ ctx->db = db;
+}
+
+void *uw_get_db(uw_context ctx) {
+ return ctx->db;
+}
+
+
+uw_loggers* uw_get_loggers(struct uw_context *ctx) {
+ return ctx->loggers;
+}
+
+void uw_free(uw_context ctx) {
+ size_t i;
+
+ uw_buffer_free(&ctx->outHeaders);
+ uw_buffer_free(&ctx->script);
+ uw_buffer_free(&ctx->page);
+ uw_buffer_free(&ctx->heap);
+ free(ctx->inputs);
+ free(ctx->subinputs);
+ free(ctx->cleanup);
+ free(ctx->transactionals);
+ uw_free_client_data(ctx->client_data);
+
+ for (i = 0; i < ctx->n_deltas; ++i)
+ uw_buffer_free(&ctx->deltas[i].msgs);
+ free(ctx->deltas);
+
+ for (i = 0; i < ctx->n_globals; ++i)
+ if (ctx->globals[i].free)
+ ctx->globals[i].free(ctx->globals[i].data);
+ free(ctx->globals);
+
+ free(ctx->output_buffer);
+
+ free(ctx->recordingOffsets);
+ free(ctx->scriptRecordingOffsets);
+
+ free(ctx);
+}
+
+void uw_reset_keep_error_message(uw_context ctx) {
+ uw_buffer_reset(&ctx->outHeaders);
+ uw_buffer_reset(&ctx->script);
+ ctx->script.start[0] = 0;
+ uw_buffer_reset(&ctx->page);
+ ctx->allowed_to_return_indirectly = ctx->returning_indirectly = 0;
+ uw_buffer_reset(&ctx->heap);
+ ctx->regions = NULL;
+ ctx->cleanup_front = ctx->cleanup;
+ ctx->used_deltas = 0;
+ ctx->client = NULL;
+ ctx->cur_container = NULL;
+ ctx->used_transactionals = 0;
+ ctx->script_header = "";
+ ctx->queryString = NULL;
+ ctx->nextId = 0;
+ ctx->amInitializing = 0;
+ ctx->usedSig = 0;
+ ctx->needsResig = 0;
+ ctx->remoteSock = -1;
+ ctx->numRecording = 0;
+}
+
+void uw_reset_keep_request(uw_context ctx) {
+ uw_reset_keep_error_message(ctx);
+ ctx->error_message[0] = 0;
+}
+
+void uw_reset(uw_context ctx) {
+ uw_reset_keep_request(ctx);
+ if (ctx->app)
+ memset(ctx->inputs, 0, ctx->app->inputs_len * sizeof(input));
+ memset(ctx->subinputs, 0, ctx->n_subinputs * sizeof(input));
+ ctx->used_subinputs = ctx->hasPostBody = ctx->isPost = 0;
+ ctx->transaction_started = 0;
+}
+
+failure_kind uw_begin_init(uw_context ctx) {
+ int r = setjmp(ctx->jmp_buf);
+
+ if (r == 0 && ctx->app)
+ ctx->app->db_init(ctx);
+
+ return r;
+}
+
+void uw_close(uw_context ctx) {
+ ctx->app->db_close(ctx);
+}
+
+uw_Basis_string uw_Basis_requestHeader(uw_context ctx, uw_Basis_string h) {
+ return ctx->get_header(ctx->get_header_data, h);
+}
+
+void uw_set_headers(uw_context ctx, char *(*get_header)(void *, const char *), void *get_header_data) {
+ ctx->get_header = get_header;
+ ctx->get_header_data = get_header_data;
+}
+
+void uw_set_env(uw_context ctx, char *(*get_env)(void *, const char *), void *get_env_data) {
+ ctx->get_env = get_env;
+ ctx->get_env_data = get_env_data;
+}
+
+static void uw_set_error(uw_context ctx, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ vsnprintf(ctx->error_message, ERROR_BUF_LEN, fmt, ap);
+}
+
+int uw_has_error(uw_context ctx) {
+ return ctx->error_message[0] != 0;
+}
+
+__attribute__((noreturn)) void uw_error(uw_context ctx, failure_kind fk, const char *fmt, ...) {
+ cleanup *cl;
+
+ va_list ap;
+ va_start(ap, fmt);
+
+ vsnprintf(ctx->error_message, ERROR_BUF_LEN, fmt, ap);
+
+ for (cl = ctx->cleanup; cl < ctx->cleanup_front; ++cl)
+ cl->func(cl->arg);
+
+ ctx->cleanup_front = ctx->cleanup;
+
+ longjmp(ctx->jmp_buf, fk);
+}
+
+size_t uw_cleanup_max = SIZE_MAX;
+
+void uw_push_cleanup(uw_context ctx, void (*func)(void *), void *arg) {
+ if (ctx->cleanup_front >= ctx->cleanup_back) {
+ int len = ctx->cleanup_back - ctx->cleanup, newLen;
+ if (len == 0)
+ newLen = 1;
+ else
+ newLen = len * 2;
+
+ if (newLen > uw_cleanup_max) {
+ if (len+1 <= uw_cleanup_max)
+ newLen = uw_cleanup_max;
+ else
+ uw_error(ctx, FATAL, "Exceeded limit on number of cleanup handlers");
+ }
+
+ ctx->cleanup = realloc(ctx->cleanup, newLen * sizeof(cleanup));
+ ctx->cleanup_front = ctx->cleanup + len;
+ ctx->cleanup_back = ctx->cleanup + newLen;
+ }
+
+ ctx->cleanup_front->func = func;
+ ctx->cleanup_front->arg = arg;
+ ++ctx->cleanup_front;
+}
+
+char *uw_Basis_htmlifyString(uw_context, const char *);
+
+void uw_login(uw_context ctx) {
+ char *id_s, *pass_s;
+
+ if ((id_s = uw_Basis_requestHeader(ctx, "UrWeb-Client"))
+ && (pass_s = uw_Basis_requestHeader(ctx, "UrWeb-Pass"))) {
+ unsigned id = atoi(id_s);
+ int pass = atoi(pass_s);
+ client *c = find_client(id);
+
+ if (c == NULL)
+ uw_error(ctx, FATAL, "Unknown client ID in HTTP headers (%s, %s)", uw_Basis_htmlifyString(ctx, id_s), uw_Basis_htmlifyString(ctx, pass_s));
+ else {
+ use_client(c);
+ ctx->client = c;
+
+ if (c->mode != USED)
+ uw_error(ctx, FATAL, "Stale client ID (%u) in subscription request", id);
+ if (c->pass != pass)
+ uw_error(ctx, FATAL, "Wrong client password (%u, %d) in subscription request", id, pass);
+ }
+ } else if (ctx->needs_push) {
+ client *c = new_client(ctx);
+
+ if (c == NULL)
+ uw_error(ctx, FATAL, "Limit exceeded on number of message-passing clients");
+
+ use_client(c);
+ uw_copy_client_data(c->data, ctx->client_data);
+ ctx->client = c;
+ }
+}
+
+failure_kind uw_begin(uw_context ctx, char *path) {
+ int r = setjmp(ctx->jmp_buf);
+
+ if (r == 0)
+ ctx->app->handle(ctx, path);
+
+ return r;
+}
+
+static void uw_try_reconnecting(uw_context ctx) {
+ // Hm, error starting transaction.
+ // Maybe the database server died but has since come back up.
+ // Let's try starting from scratch.
+ if (ctx->db) {
+ ctx->app->db_close(ctx);
+ ctx->db = NULL;
+ }
+ ctx->app->db_init(ctx);
+}
+
+void uw_try_reconnecting_and_restarting(uw_context ctx) {
+ uw_try_reconnecting(ctx);
+ uw_error(ctx, BOUNDED_RETRY, "Restarting transaction after fixing database connection");
+}
+
+void uw_ensure_transaction(uw_context ctx) {
+ if (!ctx->transaction_started && !ctx->at_most_one_query) {
+ if (!ctx->db || ctx->app->db_begin(ctx, ctx->could_write_db)) {
+ uw_try_reconnecting(ctx);
+
+ if (ctx->app->db_begin(ctx, ctx->could_write_db))
+ uw_error(ctx, FATAL, "Error running SQL BEGIN");
+ }
+
+ ctx->transaction_started = 1;
+ } else if (ctx->at_most_one_query && !ctx->db)
+ uw_try_reconnecting(ctx);
+}
+
+uw_Basis_client uw_Basis_self(uw_context ctx) {
+ if (ctx->client == NULL)
+ uw_error(ctx, FATAL, "Call to Basis.self() from page that has only server-side code");
+
+ return ctx->client->id;
+}
+
+void uw_pop_cleanup(uw_context ctx) {
+ if (ctx->cleanup_front == ctx->cleanup)
+ uw_error(ctx, FATAL, "Attempt to pop from empty cleanup action stack");
+
+ --ctx->cleanup_front;
+ ctx->cleanup_front->func(ctx->cleanup_front->arg);
+}
+
+char *uw_error_message(uw_context ctx) {
+ return ctx->error_message;
+}
+
+void uw_set_error_message(uw_context ctx, const char *msg) {
+ strncpy(ctx->error_message, msg, sizeof(ctx->error_message));
+ ctx->error_message[sizeof(ctx->error_message)-1] = 0;
+}
+
+static input *INP(uw_context ctx) {
+ if (ctx->cur_container == NULL)
+ return ctx->inputs;
+ else if (ctx->cur_container->kind == SUBFORM)
+ return ctx->cur_container->data.subform.fields;
+ else if (ctx->cur_container->kind == ENTRY)
+ return ctx->cur_container->data.entry.fields;
+ else
+ uw_error(ctx, FATAL, "INP: Wrong kind (%d, %p)", ctx->cur_container->kind, ctx->cur_container);
+}
+
+static void adjust_pointer(input **ptr, input *old_start, input *new_start, size_t len) {
+ if (*ptr != NULL && *ptr >= old_start && *ptr < old_start + len)
+ *ptr += new_start - old_start;
+}
+
+static void adjust_input(input *x, input *old_start, input *new_start, size_t len) {
+ switch (x->kind) {
+ case SUBFORM:
+ adjust_pointer(&x->data.subform.fields, old_start, new_start, len);
+ adjust_pointer(&x->data.subform.parent, old_start, new_start, len);
+ break;
+ case SUBFORMS:
+ adjust_pointer(&x->data.subforms.entries, old_start, new_start, len);
+ adjust_pointer(&x->data.subforms.parent, old_start, new_start, len);
+ break;
+ case ENTRY:
+ adjust_pointer(&x->data.entry.fields, old_start, new_start, len);
+ adjust_pointer(&x->data.entry.next, old_start, new_start, len);
+ adjust_pointer(&x->data.entry.parent, old_start, new_start, len);
+ break;
+ default:
+ break;
+ }
+}
+
+size_t uw_subinputs_max = SIZE_MAX;
+
+static input *check_input_space(uw_context ctx, size_t len) {
+ size_t i;
+ input *r;
+
+ if (ctx->used_subinputs + len >= ctx->n_subinputs) {
+ if (ctx->used_subinputs + len > uw_subinputs_max)
+ uw_error(ctx, FATAL, "Exceeded limit on number of subinputs");
+
+ input *new_subinputs = realloc(ctx->subinputs, sizeof(input) * (ctx->used_subinputs + len));
+
+ if (ctx->subinputs != new_subinputs) {
+ for (i = 0; i < ctx->used_subinputs; ++i)
+ adjust_input(&new_subinputs[i], ctx->subinputs, new_subinputs, ctx->used_subinputs);
+ for (i = 0; i < ctx->app->inputs_len; ++i)
+ adjust_input(&ctx->inputs[i], ctx->subinputs, new_subinputs, ctx->used_subinputs);
+
+ adjust_pointer(&ctx->cur_container, ctx->subinputs, new_subinputs, ctx->used_subinputs);
+
+ ctx->n_subinputs = ctx->used_subinputs + len;
+ ctx->subinputs = new_subinputs;
+ }
+ }
+
+ r = &ctx->subinputs[ctx->used_subinputs];
+
+ for (i = 0; i < len; ++i)
+ ctx->subinputs[ctx->used_subinputs++].kind = UNSET;
+
+ return r;
+}
+
+int uw_set_input(uw_context ctx, const char *name, char *value) {
+ //printf("Input name %s\n", name);
+
+ if (!strcasecmp(name, ".b")) {
+ int n = ctx->app->input_num(value);
+ input *inps;
+
+ if (n < 0) {
+ uw_set_error(ctx, "Bad subform name %s", uw_Basis_htmlifyString(ctx, value));
+ return -1;
+ }
+
+ if (n >= ctx->app->inputs_len) {
+ uw_set_error(ctx, "For subform name %s, index %d is out of range", uw_Basis_htmlifyString(ctx, value), n);
+ return -1;
+ }
+
+ inps = check_input_space(ctx, ctx->app->inputs_len);
+
+ INP(ctx)[n].kind = SUBFORM;
+ INP(ctx)[n].data.subform.parent = ctx->cur_container;
+ INP(ctx)[n].data.subform.fields = inps;
+ ctx->cur_container = &INP(ctx)[n];
+ } else if (!strcasecmp(name, ".e")) {
+ input *tmp;
+
+ if (ctx->cur_container == NULL) {
+ uw_set_error(ctx, "Unmatched subform closer");
+ return -1;
+ }
+
+ tmp = ctx->cur_container;
+ switch (tmp->kind) {
+ case SUBFORM:
+ ctx->cur_container = tmp->data.subform.parent;
+ tmp->data.subform.parent = NULL;
+ break;
+ case SUBFORMS:
+ ctx->cur_container = tmp->data.subforms.parent;
+ tmp->data.subforms.parent = NULL;
+ break;
+ case ENTRY:
+ ctx->cur_container = tmp->data.entry.parent;
+ break;
+ default:
+ uw_set_error(ctx, "uw_set_input: Wrong kind");
+ return -1;
+ }
+ } else if (!strcasecmp(name, ".s")) {
+ int n = ctx->app->input_num(value);
+
+ if (n < 0) {
+ uw_set_error(ctx, "Bad subforms name %s", uw_Basis_htmlifyString(ctx, value));
+ return -1;
+ }
+
+ if (n >= ctx->app->inputs_len) {
+ uw_set_error(ctx, "For subforms name %s, index %d is out of range", uw_Basis_htmlifyString(ctx, value), n);
+ return -1;
+ }
+
+ INP(ctx)[n].kind = SUBFORMS;
+ INP(ctx)[n].data.subforms.parent = ctx->cur_container;
+ INP(ctx)[n].data.subforms.entries = NULL;
+ ctx->cur_container = &INP(ctx)[n];
+ } else if (!strcasecmp(name, ".i")) {
+ input *inps;
+
+ if (!ctx->cur_container) {
+ uw_set_error(ctx, "New entry without container");
+ return -1;
+ }
+
+ if (ctx->cur_container->kind != SUBFORMS) {
+ uw_set_error(ctx, "Bad kind for entry parent");
+ return -1;
+ }
+
+ inps = check_input_space(ctx, ctx->app->inputs_len + 1);
+
+ inps->kind = ENTRY;
+ inps->data.entry.parent = ctx->cur_container;
+ inps->data.entry.next = ctx->cur_container->data.subforms.entries;
+ ctx->cur_container->data.subforms.entries = inps;
+
+ inps->data.entry.fields = inps+1;
+ ctx->cur_container = inps;
+ } else {
+ int n = ctx->app->input_num(name);
+
+ if (n < 0)
+ return 0;
+
+ if (n >= ctx->app->inputs_len) {
+ uw_set_error(ctx, "For input name %s, index %d is out of range", uw_Basis_htmlifyString(ctx, name), n);
+ return -1;
+ }
+
+ INP(ctx)[n].kind = NORMAL;
+ INP(ctx)[n].data.normal = value;
+ }
+
+ return 0;
+}
+
+char *uw_get_input(uw_context ctx, int n) {
+ if (n < 0)
+ uw_error(ctx, FATAL, "Negative input index %d", n);
+ if (n >= ctx->app->inputs_len)
+ uw_error(ctx, FATAL, "Out-of-bounds input index %d", n);
+
+ switch (INP(ctx)[n].kind) {
+ case UNSET:
+ return NULL;
+ case FIL:
+ uw_error(ctx, FATAL, "Tried to read a file form input as normal");
+ case SUBFORM:
+ uw_error(ctx, FATAL, "Tried to read a subform form input as normal");
+ case SUBFORMS:
+ uw_error(ctx, FATAL, "Tried to read a subforms form input as normal");
+ case ENTRY:
+ uw_error(ctx, FATAL, "Tried to read an entry form input as normal");
+ case NORMAL:
+ return INP(ctx)[n].data.normal;
+ default:
+ uw_error(ctx, FATAL, "Impossible input kind");
+ }
+}
+
+char *uw_get_optional_input(uw_context ctx, int n) {
+ if (n < 0)
+ uw_error(ctx, FATAL, "Negative input index %d", n);
+ if (n >= ctx->app->inputs_len)
+ uw_error(ctx, FATAL, "Out-of-bounds input index %d", n);
+
+ switch (INP(ctx)[n].kind) {
+ case UNSET:
+ return "";
+ case FIL:
+ uw_error(ctx, FATAL, "Tried to read a file form input as normal");
+ case SUBFORM:
+ uw_error(ctx, FATAL, "Tried to read a subform form input as normal");
+ case SUBFORMS:
+ uw_error(ctx, FATAL, "Tried to read a subforms form input as normal");
+ case ENTRY:
+ uw_error(ctx, FATAL, "Tried to read an entry form input as normal");
+ case NORMAL:
+ return INP(ctx)[n].data.normal;
+ default:
+ uw_error(ctx, FATAL, "Impossible input kind");
+ }
+}
+
+int uw_set_file_input(uw_context ctx, const char *name, uw_Basis_file f) {
+ int n = ctx->app->input_num(name);
+
+ if (n < 0) {
+ uw_set_error(ctx, "Bad file input name");
+ return -1;
+ }
+
+ if (n >= ctx->app->inputs_len) {
+ uw_set_error(ctx, "For file input name, index %d is out of range", n);
+ return -1;
+ }
+
+ ctx->inputs[n].kind = FIL;
+ ctx->inputs[n].data.file = f;
+
+ return 0;
+}
+
+void *uw_malloc(uw_context ctx, size_t len);
+
+uw_Basis_file uw_get_file_input(uw_context ctx, int n) {
+ if (n < 0)
+ uw_error(ctx, FATAL, "Negative file input index %d", n);
+ if (n >= ctx->app->inputs_len)
+ uw_error(ctx, FATAL, "Out-of-bounds file input index %d", n);
+
+ switch (INP(ctx)[n].kind) {
+ case UNSET:
+ {
+ char *data = uw_malloc(ctx, 0);
+ uw_Basis_file f = {NULL, "", {0, data}};
+ return f;
+ }
+ case FIL:
+ return INP(ctx)[n].data.file;
+ case NORMAL:
+ uw_error(ctx, FATAL, "Tried to read a normal form input as files");
+ case SUBFORM:
+ uw_error(ctx, FATAL, "Tried to read a subform form input as files");
+ case SUBFORMS:
+ uw_error(ctx, FATAL, "Tried to read a subforms form input as files");
+ case ENTRY:
+ uw_error(ctx, FATAL, "Tried to read an entry form input as files");
+ default:
+ uw_error(ctx, FATAL, "Impossible input kind");
+ }
+}
+
+void uw_enter_subform(uw_context ctx, int n) {
+ if (n < 0)
+ uw_error(ctx, FATAL, "Negative subform index %d", n);
+ if (n >= ctx->app->inputs_len)
+ uw_error(ctx, FATAL, "Out-of-bounds subform index %d", n);
+
+ switch (INP(ctx)[n].kind) {
+ case UNSET:
+ uw_error(ctx, FATAL, "Missing subform");
+ case FIL:
+ uw_error(ctx, FATAL, "Tried to read a file form input as subform");
+ case NORMAL:
+ uw_error(ctx, FATAL, "Tried to read a normal form input as subform");
+ case SUBFORMS:
+ uw_error(ctx, FATAL, "Tried to read a subforms form input as subform");
+ case ENTRY:
+ uw_error(ctx, FATAL, "Tried to read an entry form input as subform");
+ case SUBFORM:
+ INP(ctx)[n].data.subform.parent = ctx->cur_container;
+ ctx->cur_container = &INP(ctx)[n];
+ return;
+ default:
+ uw_error(ctx, FATAL, "Impossible input kind");
+ }
+}
+
+void uw_leave_subform(uw_context ctx) {
+ input *tmp;
+
+ if (ctx->cur_container == NULL)
+ uw_error(ctx, FATAL, "Unmatched uw_leave_subform");
+
+ tmp = ctx->cur_container;
+ ctx->cur_container = tmp->data.subform.parent;
+ tmp->data.subform.parent = NULL;
+}
+
+int uw_enter_subforms(uw_context ctx, int n) {
+ input *inps;
+
+ if (n < 0)
+ uw_error(ctx, FATAL, "Negative subforms index %d", n);
+ if (n >= ctx->app->inputs_len)
+ uw_error(ctx, FATAL, "Out-of-bounds subforms index %d", n);
+
+ switch (INP(ctx)[n].kind) {
+ case UNSET:
+ uw_error(ctx, FATAL, "Missing subforms");
+ case FIL:
+ uw_error(ctx, FATAL, "Tried to read a file form input as subforms");
+ case NORMAL:
+ uw_error(ctx, FATAL, "Tried to read a normal form input %p as subforms", &INP(ctx)[n]);
+ case SUBFORM:
+ uw_error(ctx, FATAL, "Tried to read a subform form input as subforms");
+ case ENTRY:
+ uw_error(ctx, FATAL, "Tried to read an entry form input as subforms");
+ case SUBFORMS:
+ inps = INP(ctx)[n].data.subforms.entries;
+ if (inps) {
+ INP(ctx)[n].data.subforms.parent = ctx->cur_container;
+ ctx->cur_container = INP(ctx)[n].data.subforms.entries;
+ return 1;
+ } else
+ return 0;
+ default:
+ uw_error(ctx, FATAL, "Impossible input kind");
+ }
+}
+
+int uw_next_entry(uw_context ctx) {
+ if (ctx->cur_container == NULL)
+ uw_error(ctx, FATAL, "uw_next_entry(NULL)");
+
+ switch (ctx->cur_container->kind) {
+ case UNSET:
+ uw_error(ctx, FATAL, "Missing entry");
+ case FIL:
+ uw_error(ctx, FATAL, "Tried to read a file form input as entry");
+ case NORMAL:
+ uw_error(ctx, FATAL, "Tried to read a normal form input as entry");
+ case SUBFORM:
+ uw_error(ctx, FATAL, "Tried to read a subform form input as entry");
+ case SUBFORMS:
+ uw_error(ctx, FATAL, "Tried to read a subforms form input as entry");
+ case ENTRY:
+ if (ctx->cur_container->data.entry.next) {
+ ctx->cur_container = ctx->cur_container->data.entry.next;
+ return 1;
+ } else {
+ ctx->cur_container = ctx->cur_container->data.entry.parent->data.subforms.parent;
+ return 0;
+ }
+ default:
+ uw_error(ctx, FATAL, "Impossible input kind");
+ }
+}
+
+void uw_set_script_header(uw_context ctx, const char *s) {
+ ctx->script_header = s;
+}
+
+const char *uw_get_url_prefix(uw_context ctx) {
+ return ctx->app->url_prefix;
+}
+
+void uw_set_needs_push(uw_context ctx, int n) {
+ ctx->needs_push = n;
+}
+
+void uw_set_needs_sig(uw_context ctx, int n) {
+ ctx->needs_sig = n;
+}
+
+void uw_set_could_write_db(uw_context ctx, int n) {
+ ctx->could_write_db = n;
+}
+
+void uw_set_at_most_one_query(uw_context ctx, int n) {
+ ctx->at_most_one_query = n;
+}
+
+
+static void uw_buffer_check_ctx(uw_context ctx, const char *kind, uw_buffer *b, size_t extra, const char *desc) {
+ if (b->back - b->front < extra) {
+ size_t desired = b->front - b->start + extra, next;
+ char *new_heap;
+
+ next = b->back - b->start;
+ if (next == 0)
+ next = 1;
+ for (; next < desired; next *= 2);
+
+ if (next > b->max) {
+ if (desired <= b->max)
+ next = desired;
+ else
+ uw_error(ctx, FATAL, "Memory limit exceeded (%s)", kind);
+ }
+
+ new_heap = realloc(b->start, next);
+ b->front = new_heap + (b->front - b->start);
+ b->back = new_heap + next;
+
+ if (new_heap != b->start) {
+ b->start = new_heap;
+ uw_error(ctx, UNLIMITED_RETRY, "Couldn't allocate new %s contiguously; increasing size to %llu", desc, (unsigned long long)next);
+ }
+
+ b->start = new_heap;
+ }
+}
+
+void uw_check_heap(uw_context ctx, size_t extra) {
+ uw_buffer_check_ctx(ctx, "heap", &ctx->heap, extra, "heap chunk");
+}
+
+char *uw_heap_front(uw_context ctx) {
+ return ctx->heap.front;
+}
+
+void uw_set_heap_front(uw_context ctx, char *fr) {
+ ctx->heap.front = fr;
+}
+
+void uw_begin_initializing(uw_context ctx) {
+ ctx->amInitializing = 1;
+}
+
+void uw_end_initializing(uw_context ctx) {
+ ctx->amInitializing = 0;
+}
+
+static void align_heap(uw_context ctx) {
+ size_t posn = ctx->heap.front - ctx->heap.start;
+
+ if (posn % sizeof(void *) != 0) {
+ size_t bump = sizeof(void *) - posn % sizeof(void *);
+ uw_check_heap(ctx, bump);
+ ctx->heap.front += bump;
+ }
+}
+
+void *uw_malloc(uw_context ctx, size_t len) {
+ // On some architectures, it's important that all word-sized memory accesses
+ // be to word-aligned addresses, so we'll do a little bit of extra work here
+ // in anticipation of a possible word-aligned access to the address we'll
+ // return.
+
+ void *result;
+
+ if (ctx->amInitializing) {
+ int error = posix_memalign(&result, sizeof(void *), len);
+
+ if (!error)
+ return result;
+ else
+ uw_error(ctx, FATAL, "uw_malloc: posix_memalign() returns %d", error);
+ } else {
+ align_heap(ctx);
+
+ uw_check_heap(ctx, len);
+
+ result = ctx->heap.front;
+ ctx->heap.front += len;
+ return result;
+ }
+}
+
+void uw_begin_region(uw_context ctx) {
+ align_heap(ctx);
+
+ regions *r = (regions *) ctx->heap.front;
+
+ uw_check_heap(ctx, sizeof(regions));
+
+ ctx->heap.front += sizeof(regions);
+
+ r->next = ctx->regions;
+ ctx->regions = r;
+}
+
+void uw_end_region(uw_context ctx) {
+ regions *r = ctx->regions;
+
+ if (r == NULL)
+ uw_error(ctx, FATAL, "Region stack underflow");
+
+ ctx->heap.front = (char *) r;
+ ctx->regions = r->next;
+}
+
+void uw_memstats(uw_context ctx) {
+ printf("Headers: %lu/%lu\n", (unsigned long)uw_buffer_used(&ctx->outHeaders), (unsigned long)uw_buffer_avail(&ctx->outHeaders));
+ printf("Script: %lu/%lu\n", (unsigned long)uw_buffer_used(&ctx->script), (unsigned long)uw_buffer_avail(&ctx->script));
+ printf("Page: %lu/%lu\n", (unsigned long)uw_buffer_used(&ctx->page), (unsigned long)uw_buffer_avail(&ctx->page));
+ printf("Heap: %lu/%lu\n", (unsigned long)uw_buffer_used(&ctx->heap), (unsigned long)uw_buffer_avail(&ctx->heap));
+}
+
+int uw_pagelen(uw_context ctx) {
+ return ctx->page.front - ctx->page.start;
+}
+
+int uw_send(uw_context ctx, int sock) {
+ size_t target_length = (ctx->outHeaders.front - ctx->outHeaders.start) + 2 + (ctx->page.front - ctx->page.start);
+
+ if (ctx->output_buffer_size < target_length) {
+ do {
+ ctx->output_buffer_size *= 2;
+ } while (ctx->output_buffer_size < target_length);
+ ctx->output_buffer = realloc(ctx->output_buffer, ctx->output_buffer_size);
+ }
+
+ memcpy(ctx->output_buffer, ctx->outHeaders.start, ctx->outHeaders.front - ctx->outHeaders.start);
+ memcpy(ctx->output_buffer + (ctx->outHeaders.front - ctx->outHeaders.start), "\r\n", 2);
+ memcpy(ctx->output_buffer + (ctx->outHeaders.front - ctx->outHeaders.start) + 2, ctx->page.start, ctx->page.front - ctx->page.start);
+
+ return uw_really_send(sock, ctx->output_buffer, target_length);
+}
+
+int uw_print(uw_context ctx, int fd) {
+ int n = uw_really_write(fd, ctx->outHeaders.start, ctx->outHeaders.front - ctx->outHeaders.start);
+
+ if (n < 0)
+ return n;
+
+ n = uw_really_write(fd, "\r\n", 2);
+
+ if (n < 0)
+ return n;
+
+ return uw_really_write(fd, ctx->page.start, ctx->page.front - ctx->page.start);
+}
+
+int uw_output(uw_context ctx, int (*output)(void *data, char *buf, size_t len), void *data) {
+ int n = output(data, ctx->outHeaders.start, ctx->outHeaders.front - ctx->outHeaders.start);
+
+ if (n < 0)
+ return n;
+
+ n = output(data, "\r\n", 2);
+
+ if (n < 0)
+ return n;
+
+ return output(data, ctx->page.start, ctx->page.front - ctx->page.start);
+}
+
+static void uw_check_headers(uw_context ctx, size_t extra) {
+ ctx_uw_buffer_check(ctx, "headers", &ctx->outHeaders, extra);
+}
+
+void uw_write_header(uw_context ctx, uw_Basis_string s) {
+ int len = strlen(s);
+
+ uw_check_headers(ctx, len + 1);
+ strcpy(ctx->outHeaders.front, s);
+ ctx->outHeaders.front += len;
+}
+
+int uw_has_contentLength(uw_context ctx) {
+ return strstr(ctx->outHeaders.start, "Content-length: ") != NULL;
+}
+
+void uw_clear_headers(uw_context ctx) {
+ uw_buffer_reset(&ctx->outHeaders);
+}
+
+void uw_Basis_clear_page(uw_context ctx) {
+ uw_buffer_reset(&ctx->page);
+}
+
+static void uw_check_script(uw_context ctx, size_t extra) {
+ ctx_uw_buffer_check(ctx, "script", &ctx->script, extra);
+}
+
+void uw_write_script(uw_context ctx, uw_Basis_string s) {
+ int len = strlen(s);
+
+ uw_check_script(ctx, len + 1);
+ strcpy(ctx->script.front, s);
+ ctx->script.front += len;
+}
+
+const char *uw_get_real_script(uw_context ctx) {
+ if (strstr(ctx->outHeaders.start, "Set-Cookie: ")) {
+ uw_write_script(ctx, "sig=\"");
+ uw_write_script(ctx, ctx->app->cookie_sig(ctx));
+ uw_write_script(ctx, "\";");
+ }
+
+ return ctx->script.start;
+}
+
+uw_Basis_string uw_Basis_maybe_onload(uw_context ctx, uw_Basis_string s) {
+ if (s[0] == 0)
+ return "";
+ else {
+ char *r = uw_malloc(ctx, 11 + strlen(s));
+ sprintf(r, " onload='%s'", s);
+ return r;
+ }
+}
+
+uw_Basis_string uw_Basis_maybe_onunload(uw_context ctx, uw_Basis_string s) {
+ if (ctx->script_header[0] == 0)
+ return "";
+ else {
+ char *r = uw_malloc(ctx, 37 + strlen(s));
+ sprintf(r, " onunload='unloading=true;%s;unload()'", s);
+ return r;
+ }
+}
+
+const char *uw_Basis_get_settings(uw_context ctx, uw_unit u) {
+ if (ctx->client == NULL) {
+ if (ctx->needs_sig) {
+ char *sig = ctx->app->cookie_sig(ctx);
+ char *r = uw_malloc(ctx, strlen(sig) + 8);
+ sprintf(r, "sig=\"%s\";", sig);
+ return r;
+ }
+ else
+ return "";
+ } else {
+ char *sig = ctx->needs_sig ? ctx->app->cookie_sig(ctx) : "";
+ char *r = uw_malloc(ctx, 59 + 3 * INTS_MAX + strlen(ctx->app->url_prefix)
+ + (ctx->needs_sig ? strlen(sig) + 7 : 0));
+ sprintf(r, "isPost=%s;client_id=%u;client_pass=%d;url_prefix=\"%s\";timeout=%d;%s%s%slistener();",
+ (ctx->isPost ? "true" : "false"),
+ ctx->client->id,
+ ctx->client->pass,
+ ctx->app->url_prefix,
+ ctx->app->timeout,
+ ctx->needs_sig ? "sig=\"" : "",
+ sig,
+ ctx->needs_sig ? "\";" : "");
+ return r;
+ }
+}
+
+uw_Basis_string uw_Basis_jsifyString(uw_context ctx, uw_Basis_string s) {
+ char *r, *s2;
+
+ uw_check_heap(ctx, strlen(s) * 4 + 3);
+
+ r = s2 = ctx->heap.front;
+ *s2++ = '"';
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ switch (c) {
+ case '"':
+ strcpy(s2, "\\\"");
+ s2 += 2;
+ break;
+ case '\'':
+ strcpy(s2, "\\047");
+ s2 += 4;
+ break;
+ case '\\':
+ strcpy(s2, "\\\\");
+ s2 += 2;
+ break;
+ case '<':
+ strcpy(s2, "\\074");
+ s2 += 4;
+ break;
+ case '&':
+ strcpy(s2, "\\046");
+ s2 += 4;
+ break;
+ default:
+ if (isprint((int)c) || c >= 128)
+ *s2++ = c;
+ else {
+ sprintf(s2, "\\%03o", c);
+ s2 += 4;
+ }
+ }
+ }
+
+ strcpy(s2, "\"");
+ ctx->heap.front = s2 + 2;
+ return r;
+}
+
+uw_Basis_string uw_Basis_jsifyChar(uw_context ctx, uw_Basis_char c1) {
+ unsigned char c = c1;
+ char *r, *s2;
+
+ uw_check_heap(ctx, 7);
+
+ r = s2 = ctx->heap.front;
+ *s2++ = '"';
+
+ switch (c) {
+ case '"':
+ strcpy(s2, "\\\"");
+ s2 += 2;
+ break;
+ case '\'':
+ strcpy(s2, "\\047");
+ s2 += 4;
+ break;
+ case '\\':
+ strcpy(s2, "\\\\");
+ s2 += 2;
+ break;
+ case '<':
+ strcpy(s2, "\\074");
+ s2 += 4;
+ break;
+ case '&':
+ strcpy(s2, "\\046");
+ s2 += 4;
+ break;
+ default:
+ if (isprint((int)c) || c >= 128)
+ *s2++ = c;
+ else {
+ sprintf(s2, "\\%03o", (unsigned char)c);
+ s2 += 4;
+ }
+ }
+
+ strcpy(s2, "\"");
+ ctx->heap.front = s2 + 2;
+ return r;
+}
+
+uw_Basis_string uw_Basis_jsifyString_ws(uw_context ctx, uw_Basis_string s) {
+ char *r, *s2;
+
+ uw_check_script(ctx, strlen(s) * 4 + 3);
+
+ r = s2 = ctx->script.front;
+ *s2++ = '"';
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ switch (c) {
+ case '\'':
+ strcpy(s2, "\\");
+ s2 += 2;
+ break;
+ case '\\':
+ strcpy(s2, "\\\\");
+ s2 += 2;
+ break;
+ case '<':
+ strcpy(s2, "\\074");
+ s2 += 4;
+ break;
+ case '&':
+ strcpy(s2, "\\046");
+ s2 += 4;
+ break;
+ default:
+ if (isprint((int)c) || c >= 128)
+ *s2++ = c;
+ else {
+ sprintf(s2, "\\%03o", c);
+ s2 += 4;
+ }
+ }
+ }
+
+ strcpy(s2, "\"");
+ ctx->script.front = s2 + 1;
+ return r;
+}
+
+char *uw_Basis_jsifyChannel(uw_context ctx, uw_Basis_channel chn) {
+ if (ctx->client == NULL || chn.cli != ctx->client->id)
+ return "null";
+ else {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX + 1);
+ r = ctx->heap.front;
+ sprintf(r, "%u%n", chn.chn, &len);
+ ctx->heap.front += len+1;
+ return r;
+ }
+}
+
+uw_Basis_source uw_Basis_new_client_source(uw_context ctx, uw_Basis_string s) {
+ int len;
+ size_t s_len = strlen(s);
+
+ if(ctx->id < 0)
+ uw_error(ctx, FATAL, "Attempt to create client source using inappropriate context");
+
+ uw_check_script(ctx, 15 + 2 * INTS_MAX + s_len);
+ sprintf(ctx->script.front, "s%d_%llu=sc(exec(%n", ctx->id, ctx->source_count, &len);
+ ctx->script.front += len;
+ strcpy(ctx->script.front, s);
+ ctx->script.front += s_len;
+ strcpy(ctx->script.front, "));");
+ ctx->script.front += 3;
+
+ uw_Basis_source r = {ctx->id, ctx->source_count++};
+ return r;
+}
+
+uw_unit uw_Basis_set_client_source(uw_context ctx, uw_Basis_source src, uw_Basis_string s) {
+ int len;
+ size_t s_len = strlen(s);
+
+ uw_check_script(ctx, 15 + 2 * INTS_MAX + s_len);
+ sprintf(ctx->script.front, "sv(s%d_%llu,exec(%n", src.context, src.source, &len);
+ ctx->script.front += len;
+ strcpy(ctx->script.front, s);
+ ctx->script.front += s_len;
+ strcpy(ctx->script.front, "));");
+ ctx->script.front += 3;
+
+ return uw_unit_v;
+}
+
+static void uw_check(uw_context ctx, size_t extra) {
+ ctx_uw_buffer_check(ctx, "page", &ctx->page, extra);
+}
+
+static void uw_writec_unsafe(uw_context ctx, char c) {
+ *(ctx->page.front)++ = c;
+ *ctx->page.front = 0;
+}
+
+void uw_writec(uw_context ctx, char c) {
+ uw_check(ctx, 2);
+ uw_writec_unsafe(ctx, c);
+}
+
+void uw_Basis_writec(uw_context ctx, char c) {
+ uw_writec(ctx, c);
+}
+
+static void uw_write_unsafe(uw_context ctx, const char* s) {
+ int len = strlen(s);
+ memcpy(ctx->page.front, s, len);
+ ctx->page.front += len;
+}
+
+void uw_write(uw_context ctx, const char* s) {
+ uw_check(ctx, strlen(s) + 1);
+ uw_write_unsafe(ctx, s);
+ *ctx->page.front = 0;
+}
+
+void uw_recordingStart(uw_context ctx) {
+ if (ctx->numRecording == ctx->recordingCapacity) {
+ ++ctx->recordingCapacity;
+ ctx->recordingOffsets = realloc(ctx->recordingOffsets, sizeof(int) * ctx->recordingCapacity);
+ ctx->scriptRecordingOffsets = realloc(ctx->scriptRecordingOffsets, sizeof(int) * ctx->recordingCapacity);
+ }
+ ctx->recordingOffsets[ctx->numRecording] = ctx->page.front - ctx->page.start;
+ ctx->scriptRecordingOffsets[ctx->numRecording] = ctx->script.front - ctx->script.start;
+ ++ctx->numRecording;
+}
+
+char *uw_recordingRead(uw_context ctx) {
+ char *recording = ctx->page.start + ctx->recordingOffsets[ctx->numRecording-1];
+ return strdup(recording);
+}
+
+char *uw_recordingReadScript(uw_context ctx) {
+ char *recording = ctx->script.start + ctx->scriptRecordingOffsets[--ctx->numRecording];
+ return strdup(recording);
+}
+
+char *uw_Basis_attrifyInt(uw_context ctx, uw_Basis_int n) {
+ char *result;
+ int len;
+ uw_check_heap(ctx, INTS_MAX);
+ result = ctx->heap.front;
+ sprintf(result, "%lld%n", n, &len);
+ ctx->heap.front += len+1;
+ return result;
+}
+
+char *uw_Basis_attrifyFloat(uw_context ctx, uw_Basis_float n) {
+ char *result;
+ int len;
+ uw_check_heap(ctx, FLOATS_MAX);
+ result = ctx->heap.front;
+ sprintf(result, "%.16g%n", n, &len);
+ ctx->heap.front += len+1;
+ return result;
+}
+
+char *uw_Basis_attrifyString(uw_context ctx, uw_Basis_string s) {
+ int len = strlen(s);
+ char *result, *p;
+ uw_check_heap(ctx, len * 6 + 1);
+
+ result = p = ctx->heap.front;
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ if (c == '"') {
+ strcpy(p, "&quot;");
+ p += 6;
+ } else if (c == '&') {
+ strcpy(p, "&amp;");
+ p += 5;
+ }
+ else
+ *p++ = c;
+ }
+
+ *p++ = 0;
+ ctx->heap.front = p;
+ return result;
+}
+
+char *uw_Basis_attrifyChar(uw_context ctx, uw_Basis_char c) {
+ char *result, *p;
+ uw_check_heap(ctx, 7);
+
+ result = p = ctx->heap.front;
+
+ if (c == '"') {
+ strcpy(p, "&quot;");
+ p += 6;
+ } else if (c == '&') {
+ strcpy(p, "&amp;");
+ p += 5;
+ }
+ else
+ *p++ = c;
+
+ *p++ = 0;
+ ctx->heap.front = p;
+ return result;
+}
+
+char *uw_Basis_attrifyCss_class(uw_context ctx, uw_Basis_css_class s) {
+ return s;
+}
+
+static void uw_Basis_attrifyInt_w_unsafe(uw_context ctx, uw_Basis_int n) {
+ int len;
+
+ sprintf(ctx->page.front, "%lld%n", n, &len);
+ ctx->page.front += len;
+}
+
+uw_unit uw_Basis_attrifyInt_w(uw_context ctx, uw_Basis_int n) {
+ uw_check(ctx, INTS_MAX);
+ uw_Basis_attrifyInt_w_unsafe(ctx, n);
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_attrifyFloat_w(uw_context ctx, uw_Basis_float n) {
+ int len;
+
+ uw_check(ctx, FLOATS_MAX);
+ sprintf(ctx->page.front, "%g%n", n, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_attrifyString_w(uw_context ctx, uw_Basis_string s) {
+ uw_check(ctx, strlen(s) * 6);
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ if (c == '"')
+ uw_write_unsafe(ctx, "&quot;");
+ else if (c == '&')
+ uw_write_unsafe(ctx, "&amp;");
+ else
+ uw_writec_unsafe(ctx, c);
+ }
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_attrifyChar_w(uw_context ctx, uw_Basis_char c) {
+ uw_check(ctx, 6);
+
+ if (c == '"')
+ uw_write_unsafe(ctx, "&quot;");
+ else if (c == '&')
+ uw_write_unsafe(ctx, "&amp;");
+ else
+ uw_writec_unsafe(ctx, c);
+
+ return uw_unit_v;
+}
+
+
+char *uw_Basis_urlifyInt(uw_context ctx, uw_Basis_int n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%lld%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_Basis_urlifyChannel(uw_context ctx, uw_Basis_channel chn) {
+ if (ctx->client == NULL || chn.cli != ctx->client->id)
+ return "";
+ else {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX + 1);
+ r = ctx->heap.front;
+ sprintf(r, "%u%n", chn.chn, &len);
+ ctx->heap.front += len+1;
+ return r;
+ }
+}
+
+char *uw_Basis_urlifyFloat(uw_context ctx, uw_Basis_float n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, FLOATS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%g%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_Basis_urlifyString(uw_context ctx, uw_Basis_string s) {
+ char *r, *p;
+
+ if (s[0] == '\0')
+ return "_";
+
+ uw_check_heap(ctx, strlen(s) * 3 + 1 + !!(s[0] == '_'));
+
+ r = p = ctx->heap.front;
+ if (s[0] == '_')
+ *p++ = '_';
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ if (c == ' ')
+ *p++ = '+';
+ else if (isalnum(c))
+ *p++ = c;
+ else {
+ sprintf(p, ".%02X", c);
+ p += 3;
+ }
+ }
+
+ *p++ = 0;
+ ctx->heap.front = p;
+ return r;
+}
+
+char *uw_Basis_urlifyBool(uw_context ctx, uw_Basis_bool b) {
+ if (b == uw_Basis_False)
+ return "0";
+ else
+ return "1";
+}
+
+char *uw_Basis_urlifySource(uw_context ctx, uw_Basis_source src) {
+ char *r;
+ int len;
+ uw_check_heap(ctx, 2 * INTS_MAX + 2);
+ r = ctx->heap.front;
+ sprintf(r, "%d/%llu%n", src.context, src.source, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+static void uw_Basis_urlifyInt_w_unsafe(uw_context ctx, uw_Basis_int n) {
+ int len;
+
+ sprintf(ctx->page.front, "%lld%n", n, &len);
+ ctx->page.front += len;
+}
+
+uw_unit uw_Basis_urlifyInt_w(uw_context ctx, uw_Basis_int n) {
+ uw_check(ctx, INTS_MAX);
+ uw_Basis_urlifyInt_w_unsafe(ctx, n);
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_urlifyChannel_w(uw_context ctx, uw_Basis_channel chn) {
+ if (ctx->client != NULL && chn.cli == ctx->client->id) {
+ int len;
+
+ uw_check(ctx, INTS_MAX + 1);
+ sprintf(ctx->page.front, "%u%n", chn.chn, &len);
+ ctx->page.front += len;
+ }
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_urlifyFloat_w(uw_context ctx, uw_Basis_float n) {
+ int len;
+
+ uw_check(ctx, FLOATS_MAX);
+ sprintf(ctx->page.front, "%g%n", n, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+uw_Basis_string uw_Basis_urlifyTime(uw_context ctx, uw_Basis_time t) {
+ return uw_Basis_urlifyInt(ctx, (uw_Basis_int)t.seconds * 1000000 + t.microseconds);
+}
+
+uw_unit uw_Basis_urlifyTime_w(uw_context ctx, uw_Basis_time t) {
+ return uw_Basis_urlifyInt_w(ctx, (uw_Basis_int)t.seconds * 1000000 + t.microseconds);
+}
+
+uw_unit uw_Basis_urlifyString_w(uw_context ctx, uw_Basis_string s) {
+ if (s[0] == '\0') {
+ uw_check(ctx, 1);
+ uw_writec_unsafe(ctx, '_');
+ return uw_unit_v;
+ }
+
+ uw_check(ctx, strlen(s) * 3 + !!(s[0] == '_'));
+
+ if (s[0] == '_')
+ uw_writec_unsafe(ctx, '_');
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ if (c == ' ')
+ uw_writec_unsafe(ctx, '+');
+ else if (isalnum(c))
+ uw_writec_unsafe(ctx, c);
+ else {
+ sprintf(ctx->page.front, ".%02X", c);
+ ctx->page.front += 3;
+ }
+ }
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_urlifyBool_w(uw_context ctx, uw_Basis_bool b) {
+ if (b == uw_Basis_False)
+ uw_writec(ctx, '0');
+ else
+ uw_writec(ctx, '1');
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_urlifySource_w(uw_context ctx, uw_Basis_source src) {
+ int len;
+
+ uw_check(ctx, 2 * INTS_MAX + 2);
+ sprintf(ctx->page.front, "%d/%llu%n", src.context, src.source, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+
+static char *uw_unurlify_advance(char *s) {
+ char *new_s = strchr(s, '/');
+
+ if (new_s)
+ *new_s++ = 0;
+ else
+ new_s = strchr(s, 0);
+
+ return new_s;
+}
+
+uw_Basis_int uw_Basis_unurlifyInt(uw_context ctx, char **s) {
+ char *new_s = uw_unurlify_advance(*s);
+ uw_Basis_int r;
+
+ r = atoll(*s);
+ *s = new_s;
+ return r;
+}
+
+uw_Basis_float uw_Basis_unurlifyFloat(uw_context ctx, char **s) {
+ char *new_s = uw_unurlify_advance(*s);
+ uw_Basis_float r;
+
+ r = atof(*s);
+ *s = new_s;
+ return r;
+}
+
+uw_Basis_time uw_Basis_unurlifyTime(uw_context ctx, char **s) {
+ uw_Basis_int n = uw_Basis_unurlifyInt(ctx, s);
+ uw_Basis_time r = {n / 1000000, n % 1000000};
+ return r;
+}
+
+static uw_Basis_string uw_unurlifyString_to(int fromClient, uw_context ctx, char *r, char *s) {
+ char *s1, *s2 = s;
+ int n;
+
+ if (!fromClient) {
+ if (*s2 == '_')
+ ++s2;
+ else if ((s2[0] == '%' || s2[0] == '.') && s2[1] == '5' && (s2[2] == 'f' || s2[2] == 'F'))
+ s2 += 3;
+ }
+
+ for (s1 = r; *s2; ++s1, ++s2) {
+ unsigned char c = *s2;
+
+ switch (c) {
+ case '+':
+ *s1 = ' ';
+ break;
+ case '%':
+ if (s2[1] == 0)
+ uw_error(ctx, FATAL, "Missing first character of escaped URL byte");
+ if (s2[2] == 0)
+ uw_error(ctx, FATAL, "Missing second character of escaped URL byte");
+ if (sscanf(s2+1, "%02X", &n) != 1)
+ uw_error(ctx, FATAL, "Invalid escaped URL byte starting at: %s", uw_Basis_htmlifyString(ctx, s2));
+ *s1 = n;
+ s2 += 2;
+ break;
+ case '.':
+ if (!fromClient) {
+ if (s2[1] == 0)
+ uw_error(ctx, FATAL, "Missing first character of escaped URL byte");
+ if (s2[2] == 0)
+ uw_error(ctx, FATAL, "Missing second character of escaped URL byte");
+ if (sscanf(s2+1, "%02X", &n) != 1)
+ uw_error(ctx, FATAL, "Invalid escaped URL byte starting at: %s", uw_Basis_htmlifyString(ctx, s2));
+ *s1 = n;
+ s2 += 2;
+ break;
+ }
+ default:
+ *s1 = c;
+ }
+ }
+ *s1++ = 0;
+ return s1;
+}
+
+uw_Basis_bool uw_Basis_unurlifyBool(uw_context ctx, char **s) {
+ char *new_s = uw_unurlify_advance(*s);
+ uw_Basis_bool r;
+
+ if (*s[0] == 0 || !strcmp(*s, "0") || !strcmp(*s, "off"))
+ r = uw_Basis_False;
+ else
+ r = uw_Basis_True;
+
+ *s = new_s;
+ return r;
+}
+
+uw_Basis_string uw_Basis_unurlifyString(uw_context ctx, char **s) {
+ char *new_s = uw_unurlify_advance(*s);
+ char *r;
+ int len;
+
+ len = strlen(*s);
+ uw_check_heap(ctx, len + 1);
+
+ r = ctx->heap.front;
+ ctx->heap.front = uw_unurlifyString_to(0, ctx, ctx->heap.front, *s);
+ *s = new_s;
+ return r;
+}
+
+uw_Basis_unit uw_Basis_unurlifyUnit(uw_context ctx, char **s) {
+ *s = uw_unurlify_advance(*s);
+ return uw_unit_v;
+}
+
+uw_Basis_string uw_Basis_unurlifyString_fromClient(uw_context ctx, char **s) {
+ char *new_s = uw_unurlify_advance(*s);
+ char *r;
+ int len;
+
+ len = strlen(*s);
+ uw_check_heap(ctx, len + 1);
+
+ r = ctx->heap.front;
+ ctx->heap.front = uw_unurlifyString_to(1, ctx, ctx->heap.front, *s);
+ *s = new_s;
+ return r;
+}
+
+
+char *uw_Basis_htmlifyInt(uw_context ctx, uw_Basis_int n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%lld%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_unit uw_Basis_htmlifyInt_w(uw_context ctx, uw_Basis_int n) {
+ int len;
+
+ uw_check(ctx, INTS_MAX);
+ sprintf(ctx->page.front, "%lld%n", n, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+char *uw_Basis_htmlifySpecialChar(uw_context ctx, unsigned char ch) {
+ unsigned int n = ch;
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX+3);
+ r = ctx->heap.front;
+ sprintf(r, "&#%u;%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_unit uw_Basis_htmlifySpecialChar_w(uw_context ctx, unsigned char ch) {
+ unsigned int n = ch;
+ int len;
+
+ uw_check(ctx, INTS_MAX+3);
+ sprintf(ctx->page.front, "&#%u;%n", n, &len);
+ ctx->page.front += len;
+ return uw_unit_v;
+}
+
+char *uw_Basis_htmlifyFloat(uw_context ctx, uw_Basis_float n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, FLOATS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%g%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_unit uw_Basis_htmlifyFloat_w(uw_context ctx, uw_Basis_float n) {
+ int len;
+
+ uw_check(ctx, FLOATS_MAX);
+ sprintf(ctx->page.front, "%g%n", n, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+char *uw_Basis_jsifyTime(uw_context ctx, uw_Basis_time t) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%lld%n", (uw_Basis_int)t.seconds * 1000000 + t.microseconds, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_unit uw_Basis_jsifyInt_w(uw_context ctx, uw_Basis_int n) {
+ int len;
+
+ uw_check(ctx, INTS_MAX);
+ sprintf(ctx->page.front, "%lld%n", (uw_Basis_int)n, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+char *uw_Basis_htmlifyString(uw_context ctx, const char *s) {
+ char *r, *s2;
+
+ uw_check_heap(ctx, strlen(s) * 5 + 1);
+
+ for (r = s2 = ctx->heap.front; *s; s++) {
+ unsigned char c = *s;
+
+ switch (c) {
+ case '<':
+ strcpy(s2, "&lt;");
+ s2 += 4;
+ break;
+ case '&':
+ strcpy(s2, "&amp;");
+ s2 += 5;
+ break;
+ default:
+ *s2++ = c;
+ }
+ }
+
+ *s2++ = 0;
+ ctx->heap.front = s2;
+ return r;
+}
+
+uw_unit uw_Basis_htmlifyString_w(uw_context ctx, uw_Basis_string s) {
+ uw_check(ctx, strlen(s) * 6);
+
+ for (; *s; s++) {
+ unsigned char c = *s;
+
+ switch (c) {
+ case '<':
+ uw_write_unsafe(ctx, "&lt;");
+ break;
+ case '&':
+ uw_write_unsafe(ctx, "&amp;");
+ break;
+ default:
+ uw_writec_unsafe(ctx, c);
+ }
+ }
+
+ return uw_unit_v;
+}
+
+uw_Basis_string uw_Basis_htmlifyBool(uw_context ctx, uw_Basis_bool b) {
+ if (b == uw_Basis_False)
+ return "False";
+ else
+ return "True";
+}
+
+uw_unit uw_Basis_htmlifyBool_w(uw_context ctx, uw_Basis_bool b) {
+ if (b == uw_Basis_False) {
+ uw_check(ctx, 6);
+ strcpy(ctx->page.front, "False");
+ ctx->page.front += 5;
+ } else {
+ uw_check(ctx, 5);
+ strcpy(ctx->page.front, "True");
+ ctx->page.front += 4;
+ }
+
+ return uw_unit_v;
+}
+
+#define TIME_FMT "%x %X"
+#define TIME_FMT_PG "%Y-%m-%d %T"
+#define TIME_FMT_JS "%Y/%m/%d %T"
+
+uw_Basis_string uw_Basis_timeToString(uw_context, uw_Basis_time);
+
+uw_Basis_string uw_Basis_htmlifyTime(uw_context ctx, uw_Basis_time t) {
+ return uw_Basis_htmlifyString(ctx, uw_Basis_timeToString(ctx, t));
+}
+
+uw_unit uw_Basis_htmlifyTime_w(uw_context ctx, uw_Basis_time t) {
+ return uw_Basis_htmlifyString_w(ctx, uw_Basis_timeToString(ctx, t));
+}
+
+char *uw_Basis_htmlifySource(uw_context ctx, uw_Basis_source src) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, 2 * INTS_MAX + 2);
+ r = ctx->heap.front;
+ sprintf(r, "s%d_%llu%n", src.context, src.source, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_unit uw_Basis_htmlifySource_w(uw_context ctx, uw_Basis_source src) {
+ int len;
+
+ uw_check(ctx, 2 * INTS_MAX + 1);
+ sprintf(ctx->page.front, "s%d_%llu%n", src.context, src.source, &len);
+ ctx->page.front += len;
+
+ return uw_unit_v;
+}
+
+uw_Basis_char uw_Basis_strsub(uw_context ctx, uw_Basis_string s, uw_Basis_int n) {
+ while (n >= 0) {
+ if (*s == 0)
+ uw_error(ctx, FATAL, "Out-of-bounds strsub");
+
+ if (n == 0)
+ return *s;
+
+ --n;
+ ++s;
+ }
+
+ uw_error(ctx, FATAL, "Negative strsub bound");
+}
+
+uw_Basis_string uw_Basis_strsuffix(uw_context ctx, uw_Basis_string s, uw_Basis_int n) {
+ while (n >= 0) {
+ if (*s == 0 || n == 0)
+ return s;
+
+ --n;
+ ++s;
+ }
+
+ uw_error(ctx, FATAL, "Negative strsuffix bound");
+}
+
+uw_Basis_int uw_Basis_strlen(uw_context ctx, uw_Basis_string s) {
+ return strlen(s);
+}
+
+uw_Basis_bool uw_Basis_strlenGe(uw_context ctx, uw_Basis_string s, uw_Basis_int n) {
+ while (n > 0) {
+ if (*s == 0)
+ return uw_Basis_False;
+
+ --n;
+ ++s;
+ }
+
+ return uw_Basis_True;
+}
+
+uw_Basis_string uw_Basis_strchr(uw_context ctx, uw_Basis_string s, uw_Basis_char ch) {
+ return strchr(s, ch);
+}
+
+uw_Basis_int uw_Basis_strcspn(uw_context ctx, uw_Basis_string s, uw_Basis_string chs) {
+ return strcspn(s, chs);
+}
+
+uw_Basis_int *uw_Basis_strindex(uw_context ctx, uw_Basis_string s, uw_Basis_char ch) {
+ uw_Basis_string r = strchr(s, ch);
+ if (r == NULL)
+ return NULL;
+ else {
+ uw_Basis_int *nr = uw_malloc(ctx, sizeof(uw_Basis_int));
+ *nr = r - s;
+ return nr;
+ }
+}
+
+uw_Basis_int *uw_Basis_strsindex(uw_context ctx, const char *haystack, const char *needle) {
+ uw_Basis_string r = strstr(haystack, needle);
+ if (r == NULL)
+ return NULL;
+ else {
+ uw_Basis_int *nr = uw_malloc(ctx, sizeof(uw_Basis_int));
+ *nr = r - haystack;
+ return nr;
+ }
+}
+
+uw_Basis_string uw_Basis_strcat(uw_context ctx, uw_Basis_string s1, uw_Basis_string s2) {
+ int len = uw_Basis_strlen(ctx, s1) + uw_Basis_strlen(ctx, s2) + 1;
+ char *s;
+
+ uw_check_heap(ctx, len);
+
+ s = ctx->heap.front;
+
+ strcpy(s, s1);
+ strcat(s, s2);
+ ctx->heap.front += len;
+
+ return s;
+}
+
+uw_Basis_string uw_Basis_substring(uw_context ctx, uw_Basis_string s, uw_Basis_int start, uw_Basis_int len) {
+ size_t full_len = uw_Basis_strlen(ctx, s);
+
+ if (start < 0)
+ uw_error(ctx, FATAL, "substring: Negative start index");
+ if (len < 0)
+ uw_error(ctx, FATAL, "substring: Negative length");
+ if (start + len > full_len)
+ uw_error(ctx, FATAL, "substring: Start index plus length is too large");
+
+ if (start + len == full_len)
+ return &s[start];
+ else {
+ uw_Basis_string r = uw_malloc(ctx, len+1);
+ memcpy(r, s+start, len);
+ r[len] = 0;
+ return r;
+ }
+
+}
+
+uw_Basis_string uw_Basis_str1(uw_context ctx, uw_Basis_char ch) {
+ char *r;
+
+ uw_check_heap(ctx, 2);
+ r = ctx->heap.front;
+ r[0] = ch;
+ r[1] = 0;
+
+ ctx->heap.front += 2;
+
+ return r;
+}
+
+uw_Basis_string uw_strdup(uw_context ctx, uw_Basis_string s1) {
+ int len = uw_Basis_strlen(ctx, s1) + 1;
+ char *s;
+
+ uw_check_heap(ctx, len);
+
+ s = ctx->heap.front;
+
+ strcpy(s, s1);
+ ctx->heap.front += len;
+
+ return s;
+}
+
+uw_Basis_string uw_dup_and_clear_error_message(uw_context ctx) {
+ if (ctx->error_message[0]) {
+ char *s = uw_strdup(ctx, ctx->error_message);
+ ctx->error_message[0] = 0;
+ return s;
+ } else
+ return NULL;
+}
+
+uw_Basis_string uw_maybe_strdup(uw_context ctx, uw_Basis_string s1) {
+ if (s1)
+ return uw_strdup(ctx, s1);
+ else
+ return NULL;
+}
+
+char *uw_memdup(uw_context ctx, const char *p, size_t len) {
+ char *r = uw_malloc(ctx, len);
+ memcpy(r, p, len);
+ return r;
+}
+
+char *uw_sqlfmtInt = "%lld::int8%n";
+
+char *uw_Basis_sqlifyInt(uw_context ctx, uw_Basis_int n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX + 6);
+ r = ctx->heap.front;
+ sprintf(r, uw_sqlfmtInt, n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_Basis_sqlifyIntN(uw_context ctx, uw_Basis_int *n) {
+ if (n == NULL)
+ return "NULL";
+ else
+ return uw_Basis_sqlifyInt(ctx, *n);
+}
+
+char *uw_sqlfmtFloat = "%.16g::float8%n";
+
+char *uw_Basis_sqlifyFloat(uw_context ctx, uw_Basis_float n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, FLOATS_MAX + 8);
+ r = ctx->heap.front;
+ sprintf(r, uw_sqlfmtFloat, n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_Basis_sqlifyFloatN(uw_context ctx, uw_Basis_float *n) {
+ if (n == NULL)
+ return "NULL";
+ else
+ return uw_Basis_sqlifyFloat(ctx, *n);
+}
+
+int uw_Estrings = 1, uw_sql_type_annotations = 1;
+char *uw_sqlsuffixString = "::text";
+char *uw_sqlsuffixChar = "::char";
+
+uw_Basis_string uw_Basis_sqlifyString(uw_context ctx, uw_Basis_string s) {
+ char *r, *s2;
+
+ uw_check_heap(ctx, strlen(s) * 2 + 3 + uw_Estrings + strlen(uw_sqlsuffixString));
+
+ r = s2 = ctx->heap.front;
+ if (uw_Estrings)
+ *s2++ = 'E';
+ *s2++ = '\'';
+
+ for (; *s; s++) {
+ char c = *s;
+
+ switch (c) {
+ case '\'':
+ if (uw_Estrings)
+ strcpy(s2, "\\'");
+ else
+ strcpy(s2, "''");
+ s2 += 2;
+ break;
+ case '\\':
+ if (uw_Estrings) {
+ strcpy(s2, "\\\\");
+ s2 += 2;
+ } else
+ *s2++ = '\\';
+ break;
+ default:
+ if (isprint((int)c))
+ *s2++ = c;
+ else if (uw_Estrings) {
+ sprintf(s2, "\\%03o", (unsigned char)c);
+ s2 += 4;
+ }
+ else
+ *s2++ = c; // I hope this is safe to do... don't know how to support UTF-8 outside Postgres otherwise!
+ }
+ }
+
+ *s2++ = '\'';
+ strcpy(s2, uw_sqlsuffixString);
+ ctx->heap.front = s2 + 1 + strlen(uw_sqlsuffixString);
+ return r;
+}
+
+uw_Basis_string uw_Basis_sqlifyChar(uw_context ctx, uw_Basis_char c) {
+ char *r, *s2;
+
+ uw_check_heap(ctx, 5 + uw_Estrings + strlen(uw_sqlsuffixChar));
+
+ r = s2 = ctx->heap.front;
+ if (uw_Estrings)
+ *s2++ = 'E';
+ *s2++ = '\'';
+
+ switch (c) {
+ case '\'':
+ if (uw_Estrings)
+ strcpy(s2, "\\'");
+ else
+ strcpy(s2, "''");
+ s2 += 2;
+ break;
+ case '\\':
+ if (uw_Estrings) {
+ strcpy(s2, "\\\\");
+ s2 += 2;
+ } else
+ *s2++ = '\\';
+ break;
+ default:
+ if (isprint((int)c))
+ *s2++ = c;
+ else if (uw_Estrings) {
+ sprintf(s2, "\\%03o", (unsigned char)c);
+ s2 += 4;
+ }
+ else
+ uw_error(ctx, FATAL, "Non-printable character %u in char to SQLify", c);
+ }
+
+ *s2++ = '\'';
+ strcpy(s2, uw_sqlsuffixChar);
+ ctx->heap.front = s2 + 1 + strlen(uw_sqlsuffixChar);
+ return r;
+}
+
+char *uw_sqlsuffixBlob = "::bytea";
+
+uw_Basis_string uw_Basis_sqlifyBlob(uw_context ctx, uw_Basis_blob b) {
+ char *r, *s2;
+ size_t i;
+
+ uw_check_heap(ctx, b.size * 5 + 4 + strlen(uw_sqlsuffixBlob));
+
+ r = s2 = ctx->heap.front;
+ if (uw_Estrings)
+ *s2++ = 'E';
+ else
+ *s2++ = 'X';
+ *s2++ = '\'';
+
+ for (i = 0; i < b.size; ++i) {
+ unsigned char c = b.data[i];
+
+ if (uw_Estrings) {
+ switch (c) {
+ case '\'':
+ strcpy(s2, "\\'");
+ s2 += 2;
+ break;
+ case '\\':
+ strcpy(s2, "\\\\\\\\");
+ s2 += 4;
+ break;
+ default:
+ if (isprint((int)c))
+ *s2++ = c;
+ else {
+ sprintf(s2, "\\\\%03o", c);
+ s2 += 5;
+ }
+ }
+ } else {
+ sprintf(s2, "%02X", c);
+ s2 += 2;
+ }
+ }
+
+ *s2++ = '\'';
+ strcpy(s2, uw_sqlsuffixBlob);
+ ctx->heap.front = s2 + 1 + strlen(uw_sqlsuffixBlob);
+ return r;
+}
+
+char *uw_Basis_sqlifyChannel(uw_context ctx, uw_Basis_channel chn) {
+ int len;
+ char *r;
+ unsigned long long combo = ((unsigned long long)chn.cli << 32) | chn.chn;
+
+ uw_check_heap(ctx, INTS_MAX + 7);
+ r = ctx->heap.front;
+ sprintf(r, uw_sqlfmtInt, combo, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_Basis_attrifyChannel(uw_context ctx, uw_Basis_channel chn) {
+ int len;
+ char *r;
+ unsigned long long combo = ((unsigned long long)chn.cli << 32) | chn.chn;
+
+ uw_check_heap(ctx, INTS_MAX + 1);
+ r = ctx->heap.front;
+ sprintf(r, "%lld%n", combo, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_sqlfmtUint4 = "%u::int4%n";
+
+char *uw_Basis_sqlifyClient(uw_context ctx, uw_Basis_client cli) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX + 7);
+ r = ctx->heap.front;
+ sprintf(r, uw_sqlfmtUint4, cli, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+char *uw_Basis_attrifyClient(uw_context ctx, uw_Basis_client cli) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX + 1);
+ r = ctx->heap.front;
+ sprintf(r, "%u%n", cli, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_Basis_string uw_Basis_sqlifyStringN(uw_context ctx, uw_Basis_string s) {
+ if (s == NULL)
+ return "NULL";
+ else
+ return uw_Basis_sqlifyString(ctx, s);
+}
+
+char *uw_Basis_sqlifyBool(uw_context ctx, uw_Basis_bool b) {
+ if (b == uw_Basis_False)
+ return "FALSE";
+ else
+ return "TRUE";
+}
+
+char *uw_Basis_sqlifyBoolN(uw_context ctx, uw_Basis_bool *b) {
+ if (b == NULL)
+ return "NULL";
+ else
+ return uw_Basis_sqlifyBool(ctx, *b);
+}
+
+char *uw_Basis_sqlifyTime(uw_context ctx, uw_Basis_time t) {
+ size_t len;
+ char *r, *s;
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (localtime_r(&t.seconds, &stm)) {
+ s = uw_malloc(ctx, TIMES_MAX);
+ len = strftime(s, TIMES_MAX, TIME_FMT_PG, &stm);
+ if (uw_sql_type_annotations) {
+ if (t.microseconds) {
+ r = uw_malloc(ctx, len + 21);
+ sprintf(r, "'%s.%06u'::timestamp", s, t.microseconds);
+ } else {
+ r = uw_malloc(ctx, len + 14);
+ sprintf(r, "'%s'::timestamp", s);
+ }
+ } else {
+ r = uw_malloc(ctx, len + 3);
+ sprintf(r, "'%s'", s);
+ }
+ return r;
+ } else
+ return "<Invalid time>";
+}
+
+char *uw_Basis_attrifyTime(uw_context ctx, uw_Basis_time t) {
+ size_t len;
+ char *r;
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (localtime_r(&t.seconds, &stm)) {
+ uw_check_heap(ctx, TIMES_MAX);
+ r = ctx->heap.front;
+ len = strftime(r, TIMES_MAX, TIME_FMT, &stm);
+ ctx->heap.front += len+1;
+ return r;
+ } else
+ return "<Invalid time>";
+}
+
+char *uw_Basis_ensqlTime(uw_context ctx, uw_Basis_time t) {
+ size_t len;
+ char *r;
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (localtime_r(&t.seconds, &stm)) {
+ uw_check_heap(ctx, TIMES_MAX);
+ r = ctx->heap.front;
+ len = strftime(r, TIMES_MAX-7, TIME_FMT_PG, &stm);
+ ctx->heap.front += len;
+ sprintf(ctx->heap.front, ".%06u", t.microseconds);
+ ctx->heap.front += 8;
+ return r;
+ } else
+ return "<Invalid time>";
+}
+
+char *uw_Basis_sqlifyTimeN(uw_context ctx, uw_Basis_time *t) {
+ if (t == NULL)
+ return "NULL";
+ else
+ return uw_Basis_sqlifyTime(ctx, *t);
+}
+
+char *uw_Basis_ensqlBool(uw_Basis_bool b) {
+ static uw_Basis_int true = 1;
+ static uw_Basis_int false = 0;
+
+ if (b == uw_Basis_False)
+ return (char *)&false;
+ else
+ return (char *)&true;
+}
+
+uw_Basis_string uw_Basis_intToString(uw_context ctx, uw_Basis_int n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, INTS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%lld%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_Basis_string uw_Basis_floatToString(uw_context ctx, uw_Basis_float n) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, FLOATS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "%g%n", n, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_Basis_string uw_Basis_charToString(uw_context ctx, uw_Basis_char ch) {
+ char *r = uw_malloc(ctx, 2);
+ r[0] = ch;
+ r[1] = 0;
+ return r;
+}
+
+uw_Basis_string uw_Basis_boolToString(uw_context ctx, uw_Basis_bool b) {
+ if (b == uw_Basis_False)
+ return "False";
+ else
+ return "True";
+}
+
+uw_Basis_string uw_Basis_timef(uw_context ctx, const char *fmt, uw_Basis_time t) {
+ size_t len;
+ char *r;
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (localtime_r(&t.seconds, &stm)) {
+ uw_check_heap(ctx, TIMES_MAX);
+ r = ctx->heap.front;
+ len = strftime(r, TIMES_MAX, fmt, &stm);
+ ctx->heap.front += len+1;
+ return r;
+ } else
+ return "<Invalid time>";
+}
+
+uw_Basis_string uw_Basis_timeToString(uw_context ctx, uw_Basis_time t) {
+ return uw_Basis_timef(ctx, ctx->app->time_format, t);
+}
+
+uw_Basis_int *uw_Basis_stringToInt(uw_context ctx, uw_Basis_string s) {
+ char *endptr;
+ uw_Basis_int n = strtoll(s, &endptr, 10);
+
+ if (*s != '\0' && *endptr == '\0') {
+ uw_Basis_int *r = uw_malloc(ctx, sizeof(uw_Basis_int));
+ *r = n;
+ return r;
+ } else
+ return NULL;
+}
+
+uw_Basis_float *uw_Basis_stringToFloat(uw_context ctx, uw_Basis_string s) {
+ char *endptr;
+ uw_Basis_float n = strtod(s, &endptr);
+
+ if (*s != '\0' && *endptr == '\0') {
+ uw_Basis_float *r = uw_malloc(ctx, sizeof(uw_Basis_float));
+ *r = n;
+ return r;
+ } else
+ return NULL;
+}
+
+uw_Basis_char *uw_Basis_stringToChar(uw_context ctx, uw_Basis_string s) {
+ if (s[0] == 0) {
+ uw_Basis_char *r = uw_malloc(ctx, 1);
+ r[0] = 0;
+ return r;
+ } else if (s[1] != 0)
+ return NULL;
+ else {
+ uw_Basis_char *r = uw_malloc(ctx, 1);
+ r[0] = s[0];
+ return r;
+ }
+}
+
+uw_Basis_bool *uw_Basis_stringToBool(uw_context ctx, uw_Basis_string s) {
+ static uw_Basis_bool true = uw_Basis_True;
+ static uw_Basis_bool false = uw_Basis_False;
+
+ if (!strcasecmp (s, "True"))
+ return &true;
+ else if (!strcasecmp (s, "False"))
+ return &false;
+ else
+ return NULL;
+}
+
+uw_Basis_time *uw_Basis_stringToTime(uw_context ctx, uw_Basis_string s) {
+ char *dot = strchr(s, '.'), *end = strchr(s, 0);
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (dot) {
+ *dot = 0;
+ if (strptime(s, TIME_FMT_PG, &stm) == end) {
+ *dot = '.';
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+ r->seconds = mktime(&stm);
+ r->microseconds = 0;
+ return r;
+ }
+ else {
+ *dot = '.';
+ return NULL;
+ }
+ }
+ else {
+ if (strptime(s, ctx->app->time_format, &stm) == end) {
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+ r->seconds = mktime(&stm);
+ r->microseconds = 0;
+ return r;
+ } else if (strptime(s, TIME_FMT_PG, &stm) == end) {
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+ r->seconds = mktime(&stm);
+ r->microseconds = 0;
+ return r;
+ } else if (strptime(s, TIME_FMT, &stm) == end) {
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+ r->seconds = mktime(&stm);
+ r->microseconds = 0;
+ return r;
+ } else if (strptime(s, TIME_FMT_JS, &stm) == end) {
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+ r->seconds = mktime(&stm);
+ r->microseconds = 0;
+ return r;
+ }
+ else
+ return NULL;
+ }
+}
+
+uw_Basis_time *uw_Basis_stringToTimef(uw_context ctx, const char *fmt, uw_Basis_string s) {
+ char *end = strchr(s, 0);
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (strptime(s, fmt, &stm) == end) {
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+ r->seconds = mktime(&stm);
+ r->microseconds = 0;
+ return r;
+ }
+ else
+ return NULL;
+}
+
+uw_Basis_int uw_Basis_stringToInt_error(uw_context ctx, uw_Basis_string s) {
+ char *endptr;
+ uw_Basis_int n = strtoll(s, &endptr, 10);
+
+ if (*s != '\0' && *endptr == '\0')
+ return n;
+ else
+ uw_error(ctx, FATAL, "Can't parse int: %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+#include <errno.h>
+
+uw_Basis_channel uw_Basis_stringToChannel_error(uw_context ctx, uw_Basis_string s) {
+ unsigned long long n;
+
+ if (sscanf(s, "%llu", &n) < 1)
+ uw_error(ctx, FATAL, "Can't parse channel: %s", uw_Basis_htmlifyString(ctx, s));
+ else {
+ uw_Basis_channel ch = {n >> 32, n & ((1ull << 32) - 1)};
+ return ch;
+ }
+}
+
+uw_Basis_client uw_Basis_stringToClient_error(uw_context ctx, uw_Basis_string s) {
+ char *endptr;
+ unsigned long n = strtoul(s, &endptr, 10);
+
+ if (*s != '\0' && *endptr == '\0')
+ return n;
+ else
+ uw_error(ctx, FATAL, "Can't parse client: %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_float uw_Basis_stringToFloat_error(uw_context ctx, uw_Basis_string s) {
+ char *endptr;
+ uw_Basis_float n = strtod(s, &endptr);
+
+ if (*s != '\0' && *endptr == '\0')
+ return n;
+ else
+ uw_error(ctx, FATAL, "Can't parse float: %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_char uw_Basis_stringToChar_error(uw_context ctx, uw_Basis_string s) {
+ if (s[0] == 0)
+ return 0;
+ else if (s[1] != 0)
+ uw_error(ctx, FATAL, "Can't parse char: %s", uw_Basis_htmlifyString(ctx, s));
+ else
+ return s[0];
+}
+
+uw_Basis_bool uw_Basis_stringToBool_error(uw_context ctx, uw_Basis_string s) {
+ if (!strcasecmp(s, "T") || !strcasecmp (s, "True"))
+ return uw_Basis_True;
+ else if (!strcasecmp(s, "F") || !strcasecmp (s, "False"))
+ return uw_Basis_False;
+ else
+ uw_error(ctx, FATAL, "Can't parse bool: %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_time uw_Basis_unsqlTime(uw_context ctx, uw_Basis_string s) {
+ char *dot = strchr(s, '.'), *end = strchr(s, 0);
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (dot) {
+ *dot = 0;
+ if (strptime(s, TIME_FMT_PG, &stm)) {
+ *dot = '.';
+ char usec[] = "000000";
+ int len = strlen(dot+1);
+ memcpy(usec, dot+1, len < 6 ? len : 6);
+ uw_Basis_time r = { mktime(&stm), atoi(usec) };
+ return r;
+ }
+ else {
+ *dot = '.';
+ uw_error(ctx, FATAL, "Can't parse time: %s", uw_Basis_htmlifyString(ctx, s));
+ }
+ }
+ else {
+ if (strptime(s, TIME_FMT_PG, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else if (strptime(s, TIME_FMT, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else
+ uw_error(ctx, FATAL, "Can't parse time: %s", uw_Basis_htmlifyString(ctx, s));
+ }
+}
+
+uw_Basis_time uw_Basis_stringToTime_error(uw_context ctx, uw_Basis_string s) {
+ char *dot = strchr(s, '.'), *end = strchr(s, 0);
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (dot) {
+ *dot = 0;
+ if (strptime(s, TIME_FMT_PG, &stm)) {
+ *dot = '.';
+ {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ }
+ }
+ else {
+ *dot = '.';
+ uw_error(ctx, FATAL, "Can't parse time: %s", uw_Basis_htmlifyString(ctx, s));
+ }
+ }
+ else {
+ if (strptime(s, ctx->app->time_format, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else if (strptime(s, TIME_FMT_PG, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else if (strptime(s, TIME_FMT, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else if (strptime(s, TIME_FMT_JS, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else
+ uw_error(ctx, FATAL, "Can't parse time: %s", uw_Basis_htmlifyString(ctx, s));
+ }
+}
+
+uw_Basis_time uw_Basis_stringToTimef_error(uw_context ctx, const char *fmt, uw_Basis_string s) {
+ char *end = strchr(s, 0);
+ struct tm stm = {};
+ stm.tm_isdst = -1;
+
+ if (strptime(s, fmt, &stm) == end) {
+ uw_Basis_time r = { mktime(&stm) };
+ return r;
+ } else
+ uw_error(ctx, FATAL, "Can't parse time: %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_blob uw_Basis_stringToBlob_error(uw_context ctx, uw_Basis_string s, size_t len) {
+ char *r = ctx->heap.front;
+ uw_Basis_blob b = {len, r};
+
+ uw_check_heap(ctx, len);
+
+ if (s[0] == '\\' && s[1] == 'x') {
+ s += 2;
+
+ while (*s) {
+ int n;
+ sscanf(s, "%02x", &n);
+ *r++ = n;
+ s += 2;
+ }
+ } else {
+ while (*s) {
+ if (s[0] == '\\') {
+ if (s[1] == '\\') {
+ *r++ = '\\';
+ s += 2;
+ } else if (isdigit((int)s[1]) && isdigit((int)s[2]) && isdigit((int)s[3])) {
+ *r++ = (s[1] - '0') * 8 * 8 + ((s[2] - '0') * 8) + (s[3] - '0');
+ s += 4;
+ }
+ else {
+ *r++ = '\\';
+ ++s;
+ }
+ } else {
+ *r++ = s[0];
+ ++s;
+ }
+ }
+ }
+
+ b.size = r - ctx->heap.front;
+ ctx->heap.front = r;
+
+ return b;
+}
+
+#define THE_PAST "expires=Sat, 01-Jan-2011 00:00:00 GMT"
+
+uw_Basis_string uw_Basis_get_cookie(uw_context ctx, uw_Basis_string c) {
+ int len = strlen(c);
+ char *p = ctx->outHeaders.start;
+
+ while ((p = strstr(p, "\nSet-Cookie: "))) {
+ char *p2;
+ p += 13;
+ p2 = strchr(p, '=');
+
+ if (p2) {
+ size_t sz = strcspn(p2+1, ";\r\n");
+
+ if (!strncasecmp(p, c, p2 - p)) {
+ if (sz == 0 && strstr(p2+2, THE_PAST))
+ return NULL;
+ else {
+ char *ret = uw_malloc(ctx, sz + 1);
+ memcpy(ret, p2+1, sz);
+ ret[sz] = 0;
+ return ret;
+ }
+ }
+ }
+ }
+
+ if ((p = uw_Basis_requestHeader(ctx, "Cookie"))) {
+ char *p2;
+
+ while (1) {
+ if (!strncmp(p, c, len) && p[len] == '=') {
+ if ((p2 = strchr(p, ';'))) {
+ size_t n = p2 - (p + len);
+ char *r = uw_malloc(ctx, n);
+ memcpy(r, p + 1 + len, n-1);
+ r[n-1] = 0;
+ return r;
+ } else
+ return p + 1 + len;
+ } else if ((p = strchr(p, ';')))
+ p += 2;
+ else
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static void set_cookie(uw_context ctx) {
+ if (ctx->usedSig)
+ ctx->needsResig = 1;
+}
+
+uw_unit uw_Basis_set_cookie(uw_context ctx, uw_Basis_string prefix, uw_Basis_string c, uw_Basis_string v, uw_Basis_time *expires, uw_Basis_bool secure) {
+ uw_write_header(ctx, "Set-Cookie: ");
+ uw_write_header(ctx, c);
+ uw_write_header(ctx, "=");
+ uw_write_header(ctx, v);
+ uw_write_header(ctx, "; path=");
+ uw_write_header(ctx, prefix);
+ if (expires) {
+ char formatted[30];
+ struct tm tm = {};
+ tm.tm_isdst = -1;
+
+ gmtime_r(&expires->seconds, &tm);
+
+ strftime(formatted, sizeof formatted, "%a, %d-%b-%Y %T GMT", &tm);
+
+ uw_write_header(ctx, "; expires=");
+ uw_write_header(ctx, formatted);
+ }
+ if (secure)
+ uw_write_header(ctx, "; secure");
+ uw_write_header(ctx, "\r\n");
+ set_cookie(ctx);
+
+ return uw_unit_v;
+}
+
+uw_unit uw_Basis_clear_cookie(uw_context ctx, uw_Basis_string prefix, uw_Basis_string c) {
+ uw_write_header(ctx, "Set-Cookie: ");
+ uw_write_header(ctx, c);
+ uw_write_header(ctx, "=; path=");
+ uw_write_header(ctx, prefix);
+ uw_write_header(ctx, "; " THE_PAST "\r\n");
+ set_cookie(ctx);
+
+ return uw_unit_v;
+}
+
+size_t uw_deltas_max = SIZE_MAX;
+
+static delta *allocate_delta(uw_context ctx, unsigned client) {
+ unsigned i;
+ delta *d;
+
+ for (i = 0; i < ctx->used_deltas; ++i)
+ if (ctx->deltas[i].client == client)
+ return &ctx->deltas[i];
+
+ if (ctx->used_deltas >= ctx->n_deltas) {
+ if (ctx->n_deltas + 1 > uw_deltas_max)
+ uw_error(ctx, FATAL, "Exceeded limit on number of deltas");
+
+ ctx->deltas = realloc(ctx->deltas, sizeof(delta) * ++ctx->n_deltas);
+ uw_buffer_init(uw_messages_max, &ctx->deltas[ctx->n_deltas-1].msgs, 0);
+ }
+
+ d = &ctx->deltas[ctx->used_deltas++];
+ d->client = client;
+ uw_buffer_reset(&d->msgs);
+ return d;
+}
+
+uw_Basis_channel uw_Basis_new_channel(uw_context ctx, uw_unit u) {
+ if (ctx->client == NULL)
+ uw_error(ctx, FATAL, "Attempt to create channel on request not associated with a persistent connection");
+
+ return new_channel(ctx->client);
+}
+
+uw_unit uw_Basis_send(uw_context ctx, uw_Basis_channel chn, uw_Basis_string msg) {
+ delta *d = allocate_delta(ctx, chn.cli);
+ size_t len;
+ int preLen;
+ char pre[INTS_MAX + 2];
+
+ len = strlen(msg);
+
+ sprintf(pre, "%u\n%n", chn.chn, &preLen);
+
+ ctx_uw_buffer_append(ctx, "messages", &d->msgs, pre, preLen);
+ ctx_uw_buffer_append(ctx, "messages", &d->msgs, msg, len);
+ ctx_uw_buffer_append(ctx, "messages", &d->msgs, "\n", 1);
+
+ return uw_unit_v;
+}
+
+int uw_rollback(uw_context ctx, int will_retry) {
+ int i;
+ cleanup *cl;
+
+ if (ctx->client)
+ release_client(ctx->client);
+
+ for (cl = ctx->cleanup; cl < ctx->cleanup_front; ++cl)
+ cl->func(cl->arg);
+
+ ctx->cleanup_front = ctx->cleanup;
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].rollback != NULL)
+ ctx->transactionals[i].rollback(ctx->transactionals[i].data);
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].free)
+ ctx->transactionals[i].free(ctx->transactionals[i].data, will_retry);
+
+ if (ctx->app && ctx->transaction_started) {
+ ctx->transaction_started = 0;
+ return ctx->app->db_rollback(ctx);
+ } else
+ return 0;
+}
+
+const char uw_begin_xhtml[] = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
+const char uw_begin_html5[] = "<!DOCTYPE html><html>";
+
+extern int uw_hash_blocksize;
+
+static const char sig_intro[] = "<input type=\"hidden\" name=\"Sig\" value=\"";
+
+static char *find_sig(char *haystack) {
+ int i;
+ char *s = strstr(haystack, sig_intro);
+
+ if (!s || strlen(haystack) - (s - haystack) - (sizeof sig_intro - 1) < uw_hash_blocksize*2+1)
+ return NULL;
+
+ s += sizeof sig_intro - 1;
+
+ for (i = 0; i < uw_hash_blocksize*2; ++i)
+ if (!isxdigit((int)s[i]))
+ return NULL;
+
+ if (s[i] != '"')
+ return NULL;
+
+ return s;
+}
+
+static pthread_mutex_t message_send_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+int uw_commit(uw_context ctx) {
+ int i;
+ char *sig;
+
+ if (uw_has_error(ctx)) {
+ uw_rollback(ctx, 0);
+ return 0;
+ }
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].rollback != NULL)
+ if (ctx->transactionals[i].commit) {
+ ctx->transactionals[i].commit(ctx->transactionals[i].data);
+ if (uw_has_error(ctx)) {
+ uw_rollback(ctx, 0);
+ return 0;
+ }
+ }
+
+ // Here's an important lock to provide the abstraction that all messages from one transaction are sent as an atomic unit.
+ if (ctx->used_deltas > 0)
+ pthread_mutex_lock(&message_send_mutex);
+
+ if (ctx->transaction_started) {
+ int code = ctx->app->db_commit(ctx);
+
+ if (code) {
+ if (ctx->used_deltas > 0)
+ pthread_mutex_unlock(&message_send_mutex);
+
+ if (ctx->client)
+ release_client(ctx->client);
+
+ if (code == -1) {
+ // This case is for a serialization failure, which is not really an "error."
+ // The transaction will restart, so we should rollback any transactionals
+ // that triggered above.
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].rollback != NULL)
+ ctx->transactionals[i].rollback(ctx->transactionals[i].data);
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].free)
+ ctx->transactionals[i].free(ctx->transactionals[i].data, 1);
+
+ return 1;
+ }
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].free)
+ ctx->transactionals[i].free(ctx->transactionals[i].data, 0);
+
+ uw_set_error_message(ctx, "Error running SQL COMMIT");
+ return 0;
+ }
+ }
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].rollback == NULL)
+ if (ctx->transactionals[i].commit) {
+ ctx->transactionals[i].commit(ctx->transactionals[i].data);
+ if (uw_has_error(ctx)) {
+ if (ctx->used_deltas > 0)
+ pthread_mutex_unlock(&message_send_mutex);
+
+ if (ctx->client)
+ release_client(ctx->client);
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].rollback != NULL)
+ ctx->transactionals[i].rollback(ctx->transactionals[i].data);
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].free)
+ ctx->transactionals[i].free(ctx->transactionals[i].data, 0);
+
+ return 0;
+ }
+ }
+
+ for (i = 0; i < ctx->used_deltas; ++i) {
+ delta *d = &ctx->deltas[i];
+ client *c = find_client(d->client);
+
+ assert (c != NULL);
+ assert(c->mode == USED);
+
+ client_send(c, &d->msgs, ctx->script.start, uw_buffer_used(&ctx->script));
+ }
+
+ if (ctx->used_deltas > 0)
+ pthread_mutex_unlock(&message_send_mutex);
+
+ if (ctx->client)
+ release_client(ctx->client);
+
+ for (i = ctx->used_transactionals-1; i >= 0; --i)
+ if (ctx->transactionals[i].free)
+ ctx->transactionals[i].free(ctx->transactionals[i].data, 0);
+
+ uw_check(ctx, 1);
+ *ctx->page.front = 0;
+
+ if (!ctx->returning_indirectly
+ && (ctx->app->is_html5
+ ? !strncmp(ctx->page.start, uw_begin_html5, sizeof uw_begin_html5 - 1)
+ : !strncmp(ctx->page.start, uw_begin_xhtml, sizeof uw_begin_xhtml - 1))) {
+ char *s;
+
+ // Splice script data into appropriate part of page, also adding <head> if needed.
+ s = ctx->page.start + (ctx->app->is_html5 ? sizeof uw_begin_html5 - 1 : sizeof uw_begin_xhtml - 1);
+ s = strchr(s, '<');
+ if (s == NULL) {
+ // Weird. Document has no tags!
+
+ uw_write(ctx, "<head></head><body></body>");
+ uw_check(ctx, 1);
+ *ctx->page.front = 0;
+ } else if (!strncmp(s, "<head>", 6)) {
+ // <head> is present. Let's add the <script> tags immediately after it.
+
+ // Any freeform JavaScript to include?
+ if (uw_buffer_used(&ctx->script) > 0) {
+ size_t lenH = strlen(ctx->script_header), len = uw_buffer_used(&ctx->script);
+ size_t lenP = lenH + 40 + len;
+ char *start = s + 6, *oldPage = ctx->page.start;
+
+ ctx_uw_buffer_check(ctx, "page", &ctx->page, uw_buffer_used(&ctx->page) + lenP);
+ start += ctx->page.start - oldPage;
+ memmove(start + lenP, start, uw_buffer_used(&ctx->page) - (start - ctx->page.start) + 1);
+ ctx->page.front += lenP;
+ memcpy(start, ctx->script_header, lenH);
+ memcpy(start + lenH, "<script type=\"text/javascript\">", 31);
+ memcpy(start + lenH + 31, ctx->script.start, len);
+ memcpy(start + lenH + 31 + len, "</script>", 9);
+ } else {
+ size_t lenH = strlen(ctx->script_header);
+ char *start = s + 6, *oldPage = ctx->page.start;
+
+ ctx_uw_buffer_check(ctx, "page", &ctx->page, uw_buffer_used(&ctx->page) + lenH);
+ start += ctx->page.start - oldPage;
+ memmove(start + lenH, start, uw_buffer_used(&ctx->page) - (start - ctx->page.start) + 1);
+ ctx->page.front += lenH;
+ memcpy(start, ctx->script_header, lenH);
+ }
+ } else {
+ // No <head>. At this point, add it, with <script> tags inside.
+
+ if (uw_buffer_used(&ctx->script) > 0) {
+ size_t lenH = strlen(ctx->script_header), len = uw_buffer_used(&ctx->script);
+ size_t lenP = lenH + 53 + len;
+ char *start = s, *oldPage = ctx->page.start;
+
+ ctx_uw_buffer_check(ctx, "page", &ctx->page, uw_buffer_used(&ctx->page) + lenP);
+ start += ctx->page.start - oldPage;
+ memmove(start + lenP, start, uw_buffer_used(&ctx->page) - (start - ctx->page.start) + 1);
+ ctx->page.front += lenP;
+ memcpy(start, "<head>", 6);
+ memcpy(start + 6, ctx->script_header, lenH);
+ memcpy(start + 6 + lenH, "<script type=\"text/javascript\">", 31);
+ memcpy(start + 6 + lenH + 31, ctx->script.start, len);
+ memcpy(start + 6 + lenH + 31 + len, "</script></head>", 16);
+ } else {
+ size_t lenH = strlen(ctx->script_header);
+ size_t lenP = lenH + 13;
+ char *start = s, *oldPage = ctx->page.start;
+
+ ctx_uw_buffer_check(ctx, "page", &ctx->page, uw_buffer_used(&ctx->page) + lenP);
+ start += ctx->page.start - oldPage;
+ memmove(start + lenP, start, uw_buffer_used(&ctx->page) - (start - ctx->page.start) + 1);
+ ctx->page.front += lenP;
+ memcpy(start, "<head>", 6);
+ memcpy(start + 6, ctx->script_header, lenH);
+ memcpy(start + 6 + lenH, "</head>", 7);
+ }
+ }
+ }
+
+ if (ctx->needsResig) {
+ sig = find_sig(ctx->page.start);
+ if (sig) {
+ char *realsig = ctx->app->cookie_sig(ctx);
+
+ do {
+ memcpy(sig, realsig, 2*uw_hash_blocksize);
+ sig = find_sig(sig);
+ } while (sig);
+ }
+ }
+
+ return 0;
+}
+
+
+size_t uw_transactionals_max = SIZE_MAX;
+
+int uw_register_transactional(uw_context ctx, void *data, uw_callback commit, uw_callback rollback,
+ uw_callback_with_retry free) {
+ if (ctx->used_transactionals >= ctx->n_transactionals) {
+ if (ctx->used_transactionals+1 > uw_transactionals_max)
+ // Exceeded limit on number of transactionals.
+ return -1;
+ ctx->transactionals = realloc(ctx->transactionals, sizeof(transactional) * (ctx->used_transactionals+1));
+ ++ctx->n_transactionals;
+ }
+
+ ctx->transactionals[ctx->used_transactionals].data = data;
+ ctx->transactionals[ctx->used_transactionals].commit = commit;
+ ctx->transactionals[ctx->used_transactionals].rollback = rollback;
+ ctx->transactionals[ctx->used_transactionals++].free = free;
+
+ return 0;
+}
+
+
+// "Garbage collection"
+
+void uw_do_expunge(uw_context ctx, uw_Basis_client cli, void *data);
+void uw_post_expunge(uw_context ctx, void *data);
+
+static failure_kind uw_expunge(uw_context ctx, uw_Basis_client cli, void *data) {
+ int r = setjmp(ctx->jmp_buf);
+
+ if (r == 0)
+ uw_do_expunge(ctx, cli, data);
+ else
+ ctx->app->db_rollback(ctx);
+
+ uw_post_expunge(ctx, data);
+
+ return r;
+}
+
+void uw_prune_clients(uw_context ctx) {
+ client *c, *next, *prev = NULL;
+ time_t cutoff;
+
+ cutoff = time(NULL) - ctx->app->timeout;
+
+ pthread_mutex_lock(&clients_mutex);
+ pruning_thread = pthread_self();
+ pruning_thread_initialized = 1;
+
+ for (c = clients_used; c; c = next) {
+ next = c->next;
+ pthread_mutex_lock(&c->lock);
+ if (c->last_contact < cutoff && c->refcount == 0) {
+ failure_kind fk = UNLIMITED_RETRY;
+ if (prev)
+ prev->next = next;
+ else
+ clients_used = next;
+ while (fk == UNLIMITED_RETRY) {
+ uw_reset(ctx);
+ fk = uw_expunge(ctx, c->id, c->data);
+ if (fk == UNLIMITED_RETRY)
+ printf("Unlimited retry during expunge: %s\n", uw_error_message(ctx));
+ }
+ if (fk == SUCCESS)
+ free_client(c);
+ else
+ fprintf(stderr, "Expunge blocked by error: %s\n", uw_error_message(ctx));
+ }
+ else
+ prev = c;
+ pthread_mutex_unlock(&c->lock);
+ }
+
+ pthread_mutex_unlock(&clients_mutex);
+}
+
+failure_kind uw_initialize(uw_context ctx) {
+ int r = setjmp(ctx->jmp_buf);
+
+ if (r == 0) {
+ uw_ensure_transaction(ctx);
+ ctx->app->initializer(ctx);
+ if (uw_commit(ctx))
+ uw_error(ctx, FATAL, "Error running SQL COMMIT");
+ }
+
+ return r;
+}
+
+static int url_bad(uw_Basis_string s) {
+ for (; *s; ++s)
+ if (!isgraph((int)*s))
+ return 1;
+
+ return 0;
+}
+
+uw_Basis_string uw_Basis_bless(uw_context ctx, uw_Basis_string s) {
+ if (url_bad(s))
+ uw_error(ctx, FATAL, "Invalid URL %s", uw_Basis_htmlifyString(ctx, s));
+ if (ctx->app->check_url(s))
+ return s;
+ else
+ uw_error(ctx, FATAL, "Disallowed URL %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkUrl(uw_context ctx, uw_Basis_string s) {
+ if (url_bad(s))
+ return NULL;
+ if (ctx->app->check_url(s))
+ return s;
+ else
+ return NULL;
+}
+
+static int mime_format(const char *s) {
+ for (; *s; ++s)
+ if (!isalnum((int)*s) && *s != '/' && *s != '-' && *s != '.' && *s != '+')
+ return 0;
+
+ return 1;
+}
+
+uw_Basis_string uw_Basis_blessMime(uw_context ctx, uw_Basis_string s) {
+ if (!mime_format(s))
+ uw_error(ctx, FATAL, "MIME type \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+ if (ctx->app->check_mime(s))
+ return s;
+ else
+ uw_error(ctx, FATAL, "Disallowed MIME type %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkMime(uw_context ctx, uw_Basis_string s) {
+ if (!mime_format(s))
+ return NULL;
+
+ if (ctx->app->check_mime(s))
+ return s;
+ else
+ return NULL;
+}
+
+uw_Basis_string uw_Basis_blessRequestHeader(uw_context ctx, uw_Basis_string s) {
+ if (!mime_format(s))
+ uw_error(ctx, FATAL, "Request header \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+ if (ctx->app->check_requestHeader(s))
+ return s;
+ else
+ uw_error(ctx, FATAL, "Disallowed request header %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkRequestHeader(uw_context ctx, uw_Basis_string s) {
+ if (!mime_format(s))
+ return NULL;
+
+ if (ctx->app->check_requestHeader(s))
+ return s;
+ else
+ return NULL;
+}
+
+uw_Basis_string uw_Basis_blessResponseHeader(uw_context ctx, uw_Basis_string s) {
+ if (!mime_format(s))
+ uw_error(ctx, FATAL, "Response header \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+ if (ctx->app->check_responseHeader(s))
+ return s;
+ else
+ uw_error(ctx, FATAL, "Disallowed response header %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+static int envVar_format(const char *s) {
+ for (; *s; ++s)
+ if (!isalnum((int)*s) && *s != '_' && *s != '.')
+ return 0;
+
+ return 1;
+}
+
+uw_Basis_string uw_Basis_checkResponseHeader(uw_context ctx, uw_Basis_string s) {
+ if (!envVar_format(s))
+ return NULL;
+
+ if (ctx->app->check_responseHeader(s))
+ return s;
+ else
+ return NULL;
+}
+
+uw_Basis_string uw_Basis_blessEnvVar(uw_context ctx, uw_Basis_string s) {
+ if (!envVar_format(s))
+ uw_error(ctx, FATAL, "Environment variable \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+ if (ctx->app->check_envVar(s))
+ return s;
+ else
+ uw_error(ctx, FATAL, "Disallowed environment variable %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkEnvVar(uw_context ctx, uw_Basis_string s) {
+ if (!mime_format(s))
+ return NULL;
+
+ if (ctx->app->check_envVar(s))
+ return s;
+ else
+ return NULL;
+}
+
+static int meta_format(const char *s) {
+ for (; *s; ++s)
+ if (!isalpha((int)*s) && *s != '-')
+ return 0;
+
+ return 1;
+}
+
+uw_Basis_string uw_Basis_blessMeta(uw_context ctx, uw_Basis_string s) {
+ if (!meta_format(s))
+ uw_error(ctx, FATAL, "Meta name \"%s\" contains invalid character", uw_Basis_htmlifyString(ctx, s));
+
+ if (ctx->app->check_meta(s))
+ return s;
+ else
+ uw_error(ctx, FATAL, "Disallowed meta name %s", uw_Basis_htmlifyString(ctx, s));
+}
+
+uw_Basis_string uw_Basis_checkMeta(uw_context ctx, uw_Basis_string s) {
+ if (!meta_format(s))
+ return NULL;
+
+ if (ctx->app->check_meta(s))
+ return s;
+ else
+ return NULL;
+}
+
+uw_Basis_string uw_Basis_getHeader(uw_context ctx, uw_Basis_string name) {
+ return uw_Basis_requestHeader(ctx, name);
+}
+
+static int mime_value_format(const char *s) {
+ for (; *s; ++s)
+ if (*s == '\r' || *s == '\n')
+ return 0;
+
+ return 1;
+}
+
+uw_unit uw_Basis_setHeader(uw_context ctx, uw_Basis_string name, uw_Basis_string value) {
+ if (!mime_value_format(value))
+ uw_error(ctx, FATAL, "Invalid value for HTTP response header");
+
+ uw_write_header(ctx, name);
+ uw_write_header(ctx, ": ");
+ uw_write_header(ctx, value);
+ uw_write_header(ctx, "\r\n");
+
+ return uw_unit_v;
+}
+
+uw_Basis_string uw_Basis_getenv(uw_context ctx, uw_Basis_string name) {
+ if (ctx->get_env)
+ return ctx->get_env(ctx->get_env_data, name);
+ else
+ return getenv(name);
+}
+
+uw_Basis_string uw_unnull(uw_Basis_string s) {
+ return s ? s : "";
+}
+
+uw_Basis_string uw_Basis_makeSigString(uw_context ctx, uw_Basis_string sig) {
+ uw_Basis_string r = uw_malloc(ctx, 2 * uw_hash_blocksize + 1);
+ int i;
+
+ for (i = 0; i < uw_hash_blocksize; ++i)
+ sprintf(&r[2*i], "%.02X", ((unsigned char *)sig)[i]);
+
+ return r;
+}
+
+/* This bit of crafty code is intended to prevent GCC from performing
+ * optimizations that would enable timing attacks. See:
+ * http://www.impredicative.com/pipermail/ur/2011-July/000659.html
+ */
+int uw_streq(uw_Basis_string s1, uw_Basis_string s2) {
+ int i, x = 0, len1 = strlen(s1);
+
+ if (len1 != strlen(s2)) return 0;
+
+ for (i = 0; i < len1; ++i) {
+ __asm__ __volatile__ ("");
+ x |= s1[i] ^ s2[i];
+ }
+
+ return x == 0;
+}
+
+uw_Basis_string uw_Basis_sigString(uw_context ctx, uw_unit u) {
+ ctx->usedSig = 1;
+ return ctx->app->cookie_sig(ctx);
+}
+
+uw_Basis_string uw_Basis_fileName(uw_context ctx, uw_Basis_file f) {
+ return f.name;
+}
+
+uw_Basis_string uw_Basis_fileMimeType(uw_context ctx, uw_Basis_file f) {
+ return f.type;
+}
+
+uw_Basis_int uw_Basis_blobSize(uw_context ctx, uw_Basis_blob b) {
+ return b.size;
+}
+
+uw_Basis_blob uw_Basis_textBlob(uw_context ctx, uw_Basis_string s) {
+ uw_Basis_blob b = {strlen(s), s};
+
+ return b;
+}
+
+uw_Basis_blob uw_Basis_fileData(uw_context ctx, uw_Basis_file f) {
+ return f.data;
+}
+
+uw_Basis_string uw_Basis_postType(uw_context ctx, uw_Basis_postBody pb) {
+ return pb.type;
+}
+
+uw_Basis_string uw_Basis_postData(uw_context ctx, uw_Basis_postBody pb) {
+ return pb.data;
+}
+
+static char *old_headers(uw_context ctx) {
+ if (uw_buffer_used(&ctx->outHeaders) == 0)
+ return NULL;
+ else {
+ char *s;
+ int is_good;
+
+ if (strncasecmp(ctx->outHeaders.start, "Content-type: ", 14)) {
+ s = strchr(ctx->outHeaders.start, '\n');
+ is_good = !strncasecmp(s+1, "Content-type: ", 14);
+ } else {
+ s = ctx->outHeaders.start;
+ is_good = 1;
+ }
+
+ if (!is_good)
+ return NULL;
+ else {
+ s = strchr(s+15, '\n');
+ if (s == NULL)
+ return NULL;
+ else
+ return uw_strdup(ctx, s+1);
+ }
+ }
+}
+
+__attribute__((noreturn)) void uw_return_blob(uw_context ctx, uw_Basis_blob b, uw_Basis_string mimeType) {
+ cleanup *cl;
+ int len;
+ char *oldh;
+
+ if (!ctx->allowed_to_return_indirectly)
+ uw_error(ctx, FATAL, "Tried to return a blob from an RPC");
+
+ ctx->returning_indirectly = 1;
+ oldh = old_headers(ctx);
+ uw_buffer_reset(&ctx->outHeaders);
+ uw_buffer_reset(&ctx->page);
+
+ uw_write_header(ctx, on_success);
+ uw_write_header(ctx, "Content-Type: ");
+ uw_write_header(ctx, mimeType);
+ uw_write_header(ctx, "\r\nContent-length: ");
+ ctx_uw_buffer_check(ctx, "headers", &ctx->outHeaders, INTS_MAX);
+ sprintf(ctx->outHeaders.front, "%lu%n", (unsigned long)b.size, &len);
+ ctx->outHeaders.front += len;
+ uw_write_header(ctx, "\r\n");
+ if (oldh) uw_write_header(ctx, oldh);
+
+ ctx_uw_buffer_append(ctx, "page", &ctx->page, b.data, b.size);
+
+ for (cl = ctx->cleanup; cl < ctx->cleanup_front; ++cl)
+ cl->func(cl->arg);
+
+ ctx->cleanup_front = ctx->cleanup;
+
+ longjmp(ctx->jmp_buf, RETURN_INDIRECTLY);
+}
+
+void uw_replace_page(uw_context ctx, const char *data, size_t size) {
+ uw_buffer_reset(&ctx->page);
+ ctx_uw_buffer_append(ctx, "page", &ctx->page, data, size);
+}
+
+__attribute__((noreturn)) void uw_return_blob_from_page(uw_context ctx, uw_Basis_string mimeType) {
+ cleanup *cl;
+ int len;
+ char *oldh;
+
+ if (!ctx->allowed_to_return_indirectly)
+ uw_error(ctx, FATAL, "Tried to return a blob from an RPC");
+
+ ctx->returning_indirectly = 1;
+ oldh = old_headers(ctx);
+ uw_buffer_reset(&ctx->outHeaders);
+
+ uw_write_header(ctx, on_success);
+ uw_write_header(ctx, "Content-Type: ");
+ uw_write_header(ctx, mimeType);
+ uw_write_header(ctx, "\r\nContent-length: ");
+ ctx_uw_buffer_check(ctx, "headers", &ctx->outHeaders, INTS_MAX);
+ sprintf(ctx->outHeaders.front, "%lu%n", (unsigned long)uw_buffer_used(&ctx->page), &len);
+ ctx->outHeaders.front += len;
+ uw_write_header(ctx, "\r\n");
+ if (oldh) uw_write_header(ctx, oldh);
+
+ for (cl = ctx->cleanup; cl < ctx->cleanup_front; ++cl)
+ cl->func(cl->arg);
+
+ ctx->cleanup_front = ctx->cleanup;
+
+ longjmp(ctx->jmp_buf, RETURN_INDIRECTLY);
+}
+
+__attribute__((noreturn)) void uw_redirect(uw_context ctx, uw_Basis_string url) {
+ cleanup *cl;
+ char *s;
+ char *oldh;
+
+ if (!ctx->allowed_to_return_indirectly)
+ uw_error(ctx, FATAL, "Tried to redirect from an RPC");
+
+ ctx->returning_indirectly = 1;
+ oldh = old_headers(ctx);
+ uw_buffer_reset(&ctx->page);
+ ctx_uw_buffer_check(ctx, "page", &ctx->page, uw_buffer_used(&ctx->outHeaders)+1);
+ memcpy(ctx->page.start, ctx->outHeaders.start, uw_buffer_used(&ctx->outHeaders));
+ ctx->page.start[uw_buffer_used(&ctx->outHeaders)] = 0;
+ uw_buffer_reset(&ctx->outHeaders);
+
+ if (on_success[0])
+ uw_write_header(ctx, on_redirect);
+ else
+ uw_write_header(ctx, "Status: 303 See Other\r\n");
+ s = on_success[0] ? strchr(ctx->page.start, '\n') : ctx->page.start;
+ if (s) {
+ char *s2;
+ if (s[0] == '\n') ++s;
+ for (; (s2 = strchr(s, '\n')); s = s2+1) {
+ *s2 = 0;
+ if (!strncmp(s, "Set-Cookie: ", 12)) {
+ uw_write_header(ctx, s);
+ uw_write_header(ctx, "\n");
+ }
+ }
+ }
+
+ uw_write_header(ctx, "Location: ");
+ uw_write_header(ctx, url);
+ uw_write_header(ctx, "\r\n\r\n");
+ if (oldh) uw_write_header(ctx, oldh);
+
+ for (cl = ctx->cleanup; cl < ctx->cleanup_front; ++cl)
+ cl->func(cl->arg);
+
+ ctx->cleanup_front = ctx->cleanup;
+
+ longjmp(ctx->jmp_buf, RETURN_INDIRECTLY);
+}
+
+uw_Basis_string uw_Basis_unAs(uw_context ctx, uw_Basis_string s) {
+ uw_Basis_string ret = uw_malloc(ctx, strlen(s) + 1), r = ret;
+
+ for (; *s; ++s) {
+ if (s[0] == '\'') {
+ *r++ = '\'';
+ for (++s; *s; ++s) {
+ if (s[0] == '\'') {
+ *r++ = '\'';
+ break;
+ } else if (s[0] == '\\') {
+ *r++ = '\\';
+ *r++ = s[1];
+ ++s;
+ } else
+ *r++ = s[0];
+ }
+ if (*s == 0) break;
+ } else if (s[0] == 'T' && s[1] == '_' && s[2] == 'T' && s[3] == '.')
+ s += 3;
+ else
+ *r++ = s[0];
+ }
+
+ *r = 0;
+ return ret;
+}
+
+uw_Basis_string uw_Basis_mstrcat(uw_context ctx, ...) {
+ va_list ap;
+ size_t len = 1;
+ char *s, *r, *s2;
+
+ va_start(ap, ctx);
+ for (s = va_arg(ap, char*); s; s = va_arg(ap, char*))
+ len += strlen(s);
+ va_end(ap);
+
+ r = uw_malloc(ctx, len);
+ va_start(ap, ctx);
+ for (s2 = r, s = va_arg(ap, char*); s; s = va_arg(ap, char*))
+ while (*s)
+ *s2++ = *s++;
+ va_end(ap);
+ *s2 = 0;
+
+ return r;
+}
+
+const uw_Basis_time uw_Basis_minTime = {};
+
+uw_Basis_time uw_Basis_now(uw_context ctx) {
+ uw_Basis_time r = { time(NULL) };
+ return r;
+}
+
+uw_Basis_time uw_Basis_addSeconds(uw_context ctx, uw_Basis_time tm, uw_Basis_int n) {
+ tm.seconds += n;
+ return tm;
+}
+
+uw_Basis_int uw_Basis_diffInSeconds(uw_context ctx, uw_Basis_time tm1, uw_Basis_time tm2) {
+ return difftime(tm2.seconds, tm1.seconds);
+}
+
+uw_Basis_int uw_Basis_toMilliseconds(uw_context ctx, uw_Basis_time tm) {
+ return tm.seconds * 1000 + tm.microseconds / 1000;
+}
+
+uw_Basis_time uw_Basis_fromMilliseconds(uw_context ctx, uw_Basis_int n) {
+ uw_Basis_time tm = {n / 1000, n % 1000 * 1000};
+ return tm;
+}
+
+uw_Basis_int uw_Basis_diffInMilliseconds(uw_context ctx, uw_Basis_time tm1, uw_Basis_time tm2) {
+ return uw_Basis_toMilliseconds(ctx, tm2) - uw_Basis_toMilliseconds(ctx, tm1);
+}
+
+uw_Basis_int uw_Basis_toSeconds(uw_context ctx, uw_Basis_time tm) {
+ return tm.seconds;
+}
+
+uw_Basis_time uw_Basis_fromDatetime(uw_context ctx, uw_Basis_int year, uw_Basis_int month, uw_Basis_int day, uw_Basis_int hour, uw_Basis_int minute, uw_Basis_int second) {
+ struct tm tm = { .tm_year = year - 1900, .tm_mon = month, .tm_mday = day,
+ .tm_hour = hour, .tm_min = minute, .tm_sec = second,
+ .tm_isdst = -1 };
+ uw_Basis_time r = { timelocal(&tm) };
+ return r;
+}
+
+uw_Basis_int uw_Basis_datetimeYear(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_year + 1900;
+}
+
+uw_Basis_int uw_Basis_datetimeMonth(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_mon;
+}
+
+uw_Basis_int uw_Basis_datetimeDay(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_mday;
+}
+
+uw_Basis_int uw_Basis_datetimeHour(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_hour;
+}
+
+uw_Basis_int uw_Basis_datetimeMinute(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_min;
+}
+
+uw_Basis_int uw_Basis_datetimeSecond(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_sec;
+}
+
+uw_Basis_int uw_Basis_datetimeDayOfWeek(uw_context ctx, uw_Basis_time time) {
+ struct tm tm;
+ localtime_r(&time.seconds, &tm);
+ return tm.tm_wday;
+}
+
+
+void *uw_get_global(uw_context ctx, char *name) {
+ int i;
+
+ for (i = 0; i < ctx->n_globals; ++i)
+ if (!strcmp(name, ctx->globals[i].name))
+ return ctx->globals[i].data;
+
+ return NULL;
+}
+
+size_t uw_globals_max = SIZE_MAX;
+
+void uw_set_global(uw_context ctx, char *name, void *data, void (*free)(void*)) {
+ int i;
+
+ for (i = 0; i < ctx->n_globals; ++i)
+ if (!strcmp(name, ctx->globals[i].name)) {
+ if (ctx->globals[i].free)
+ ctx->globals[i].free(ctx->globals[i].data);
+ ctx->globals[i].data = data;
+ ctx->globals[i].free = free;
+ return;
+ }
+
+ if (ctx->n_globals+1 > uw_globals_max)
+ uw_error(ctx, FATAL, "Exceeded limit on number of globals");
+
+ ++ctx->n_globals;
+ ctx->globals = realloc(ctx->globals, ctx->n_globals * sizeof(global));
+ ctx->globals[ctx->n_globals-1].name = name;
+ ctx->globals[ctx->n_globals-1].data = data;
+ ctx->globals[ctx->n_globals-1].free = free;
+}
+
+uw_Basis_bool uw_Basis_isalnum(uw_context ctx, uw_Basis_char c) {
+ return !!isalnum((int)c);
+}
+
+uw_Basis_bool uw_Basis_isalpha(uw_context ctx, uw_Basis_char c) {
+ return !!isalpha((int)c);
+}
+
+uw_Basis_bool uw_Basis_isblank(uw_context ctx, uw_Basis_char c) {
+ return !!isblank((int)c);
+}
+
+uw_Basis_bool uw_Basis_iscntrl(uw_context ctx, uw_Basis_char c) {
+ return !!iscntrl((int)c);
+}
+
+uw_Basis_bool uw_Basis_isdigit(uw_context ctx, uw_Basis_char c) {
+ return !!isdigit((int)c);
+}
+
+uw_Basis_bool uw_Basis_isgraph(uw_context ctx, uw_Basis_char c) {
+ return !!isgraph((int)c);
+}
+
+uw_Basis_bool uw_Basis_islower(uw_context ctx, uw_Basis_char c) {
+ return !!islower((int)c);
+}
+
+uw_Basis_bool uw_Basis_isprint(uw_context ctx, uw_Basis_char c) {
+ return !!isprint((int)c);
+}
+
+uw_Basis_bool uw_Basis_ispunct(uw_context ctx, uw_Basis_char c) {
+ return !!ispunct((int)c);
+}
+
+uw_Basis_bool uw_Basis_isspace(uw_context ctx, uw_Basis_char c) {
+ return !!isspace((int)c);
+}
+
+uw_Basis_bool uw_Basis_isupper(uw_context ctx, uw_Basis_char c) {
+ return !!isupper((int)c);
+}
+
+uw_Basis_bool uw_Basis_isxdigit(uw_context ctx, uw_Basis_char c) {
+ return !!isxdigit((int)c);
+}
+
+uw_Basis_char uw_Basis_tolower(uw_context ctx, uw_Basis_char c) {
+ return tolower((int)c);
+}
+
+uw_Basis_char uw_Basis_toupper(uw_context ctx, uw_Basis_char c) {
+ return toupper((int)c);
+}
+
+uw_Basis_int uw_Basis_ord(uw_context ctx, uw_Basis_char c) {
+ return (unsigned char)c;
+}
+
+uw_Basis_char uw_Basis_chr(uw_context ctx, uw_Basis_int n) {
+ return n;
+}
+
+uw_Basis_string uw_Basis_currentUrl(uw_context ctx) {
+ return ctx->current_url;
+}
+
+void uw_set_currentUrl(uw_context ctx, char *s) {
+ ctx->current_url = s;
+}
+
+void uw_set_deadline(uw_context ctx, int n) {
+ ctx->deadline = n;
+}
+
+void uw_check_deadline(uw_context ctx) {
+ if (uw_time > ctx->deadline)
+ uw_error(ctx, FATAL, "Maximum running time exceeded");
+}
+
+size_t uw_database_max = SIZE_MAX;
+
+uw_Basis_int uw_Basis_naughtyDebug(uw_context ctx, uw_Basis_string s) {
+ if (ctx->loggers->log_debug)
+ ctx->loggers->log_debug(ctx->loggers->logger_data, "%s\n", s);
+ else
+ fprintf(stderr, "%s\n", s);
+ return 0;
+}
+
+uw_Basis_unit uw_Basis_debug(uw_context ctx, uw_Basis_string s) {
+ if (ctx->loggers->log_debug)
+ ctx->loggers->log_debug(ctx->loggers->logger_data, "%s\n", s);
+ else
+ fprintf(stderr, "%s\n", s);
+ return uw_unit_v;
+}
+
+uw_Basis_int uw_Basis_rand(uw_context ctx) {
+ int r = my_rand();
+
+ if (r >= 0)
+ return r;
+ else
+ uw_error(ctx, FATAL, "Random number generation failed");
+}
+
+void uw_noPostBody(uw_context ctx) {
+ ctx->hasPostBody = 0;
+}
+
+void uw_postBody(uw_context ctx, uw_Basis_postBody pb) {
+ ctx->hasPostBody = 1;
+ ctx->postBody = pb;
+}
+
+int uw_hasPostBody(uw_context ctx) {
+ return ctx->hasPostBody;
+}
+
+void uw_isPost(uw_context ctx) {
+ ctx->isPost = 1;
+}
+
+uw_Basis_bool uw_Basis_currentUrlHasPost(uw_context ctx) {
+ return ctx->isPost;
+}
+
+uw_Basis_bool uw_Basis_currentUrlHasQueryString(uw_context ctx) {
+ return ctx->queryString != NULL && ctx->queryString[0] != 0;
+}
+
+void uw_setQueryString(uw_context ctx, uw_Basis_string s) {
+ ctx->queryString = s;
+}
+
+uw_Basis_string uw_queryString(uw_context ctx) {
+ return ctx->queryString;
+}
+
+uw_Basis_postBody uw_getPostBody(uw_context ctx) {
+ if (ctx->hasPostBody)
+ return ctx->postBody;
+ else
+ uw_error(ctx, FATAL, "Asked for POST body when none exists");
+}
+
+failure_kind uw_runCallback(uw_context ctx, void (*callback)(uw_context)) {
+ int r = setjmp(ctx->jmp_buf);
+
+ if (r == 0) {
+ uw_ensure_transaction(ctx);
+
+ callback(ctx);
+ }
+
+ return r;
+}
+
+uw_Basis_string uw_Basis_crypt(uw_context ctx, uw_Basis_string key, uw_Basis_string salt) {
+ char buf[14];
+ return uw_strdup(ctx, DES_fcrypt(key, salt, buf));
+}
+
+uw_Basis_bool uw_Basis_eq_time(uw_context ctx, uw_Basis_time t1, uw_Basis_time t2) {
+ return !!(t1.seconds == t2.seconds && t1.microseconds == t2.microseconds);
+}
+
+uw_Basis_bool uw_Basis_lt_time(uw_context ctx, uw_Basis_time t1, uw_Basis_time t2) {
+ return !!(t1.seconds < t2.seconds || (t1.seconds == t2.seconds && t1.microseconds < t2.microseconds));
+}
+
+uw_Basis_bool uw_Basis_le_time(uw_context ctx, uw_Basis_time t1, uw_Basis_time t2) {
+ return !!(uw_Basis_eq_time(ctx, t1, t2) || uw_Basis_lt_time(ctx, t1, t2));
+}
+
+uw_Basis_time *uw_Basis_readUtc(uw_context ctx, uw_Basis_string s) {
+ struct tm stm = {};
+ char *end = strchr(s, 0);
+ stm.tm_isdst = -1;
+
+ if (strptime(s, TIME_FMT_PG, &stm) == end || strptime(s, TIME_FMT, &stm) == end || strptime(s, TIME_FMT_JS, &stm) == end) {
+ uw_Basis_time *r = uw_malloc(ctx, sizeof(uw_Basis_time));
+
+ r->seconds = timegm(&stm);
+ r->microseconds = 0;
+
+ return r;
+ }
+ else
+ return NULL;
+}
+
+failure_kind uw_begin_onError(uw_context ctx, char *msg) {
+ int r = setjmp(ctx->jmp_buf);
+
+ if (ctx->app->on_error) {
+ if (r == 0) {
+ uw_ensure_transaction(ctx);
+
+ uw_buffer_reset(&ctx->outHeaders);
+ if (on_success[0])
+ uw_write_header(ctx, "HTTP/1.1 ");
+ else
+ uw_write_header(ctx, "Status: ");
+ uw_write_header(ctx, "500 Internal Server Error\r\n");
+ uw_write_header(ctx, "Content-type: text/html\r\n");
+ uw_write(ctx, ctx->app->is_html5 ? uw_begin_html5 : uw_begin_xhtml);
+ ctx->app->on_error(ctx, msg);
+ uw_write(ctx, "</html>");
+ }
+
+ return r;
+ } else
+ uw_error(ctx, FATAL, "Tried to run nonexistent onError handler");
+}
+
+void uw_mayReturnIndirectly(uw_context ctx) {
+ ctx->allowed_to_return_indirectly = 1;
+}
+
+uw_Basis_string uw_Basis_fresh(uw_context ctx) {
+ int len;
+ char *r;
+
+ uw_check_heap(ctx, 2+INTS_MAX);
+ r = ctx->heap.front;
+ sprintf(r, "uw%u%n", ctx->nextId++, &len);
+ ctx->heap.front += len+1;
+ return r;
+}
+
+uw_Basis_float uw_Basis_floatFromInt(uw_context ctx, uw_Basis_int n) {
+ return n;
+}
+
+uw_Basis_int uw_Basis_ceil(uw_context ctx, uw_Basis_float n) {
+ return ceil(n);
+}
+
+uw_Basis_int uw_Basis_trunc(uw_context ctx, uw_Basis_float n) {
+ return trunc(n);
+}
+
+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;
+
+ for (p = s; *p; ++p) {
+ char c = *p;
+ if (!isalnum((int)c) && c != '+' && c != '-' && c != '.' && c != '%' && c != '#')
+ uw_error(ctx, FATAL, "Disallowed character in CSS atom");
+ }
+
+ return s;
+}
+
+uw_Basis_string uw_Basis_css_url(uw_context ctx, uw_Basis_string s) {
+ char *p;
+
+ for (p = s; *p; ++p) {
+ char c = *p;
+ if (!isalnum((int)c) && c != ':' && c != '/' && c != '.' && c != '_' && c != '+'
+ && c != '-' && c != '%' && c != '?' && c != '&' && c != '=' && c != '#')
+ uw_error(ctx, FATAL, "Disallowed character in CSS URL");
+ }
+
+ return s;
+}
+
+uw_Basis_string uw_Basis_property(uw_context ctx, uw_Basis_string s) {
+ char *p;
+
+ if (!*s)
+ uw_error(ctx, FATAL, "Empty CSS property");
+
+ if (!islower((int)s[0]) && s[0] != '_')
+ uw_error(ctx, FATAL, "Bad initial character in CSS property");
+
+ for (p = s; *p; ++p) {
+ char c = *p;
+ if (!islower((int)c) && !isdigit((int)c) && c != '_' && c != '-')
+ uw_error(ctx, FATAL, "Disallowed character in CSS property");
+ }
+
+ return s;
+}
+
+uw_Basis_string uw_Basis_fieldName(uw_context ctx, uw_Basis_postField f) {
+ return f.name;
+}
+
+uw_Basis_string uw_Basis_fieldValue(uw_context ctx, uw_Basis_postField f) {
+ return f.value;
+}
+
+uw_Basis_string uw_Basis_remainingFields(uw_context ctx, uw_Basis_postField f) {
+ return f.remaining;
+}
+
+uw_Basis_postField *uw_Basis_firstFormField(uw_context ctx, uw_Basis_string s) {
+ char *unurl;
+ uw_Basis_postField *f;
+
+ if (!ctx->hasPostBody)
+ uw_error(ctx, FATAL, "firstFormField called when there is no POST body");
+
+ if (s < ctx->postBody.data || s >= ctx->postBody.data + ctx->postBody.len)
+ return NULL;
+
+ f = uw_malloc(ctx, sizeof(uw_Basis_postField));
+ unurl = s;
+ f->name = uw_Basis_unurlifyString(ctx, &unurl);
+ s = strchr(s, 0);
+ if (!s)
+ uw_error(ctx, FATAL, "firstFormField: Missing null terminator");
+ ++s;
+ unurl = s;
+ f->value = uw_Basis_unurlifyString(ctx, &unurl);
+ s = strchr(s, 0);
+ if (!s)
+ uw_error(ctx, FATAL, "firstFormField: Missing null terminator");
+ f->remaining = s+1;
+
+ return f;
+}
+
+uw_Basis_string uw_Basis_blessData(uw_context ctx, uw_Basis_string s) {
+ char *p = s;
+
+ for (; *p; ++p)
+ if (!isalnum(*p) && *p != '-' && *p != '_')
+ uw_error(ctx, FATAL, "Illegal HTML5 data-* attribute: %s", s);
+
+ return s;
+}
+
+int uw_remoteSock(uw_context ctx) {
+ return ctx->remoteSock;
+}
+
+void uw_set_remoteSock(uw_context ctx, int sock) {
+ ctx->remoteSock = sock;
+}
+
+
+// Sqlcache
+
+static void uw_Sqlcache_freeValue(uw_Sqlcache_Value *value) {
+ if (value) {
+ free(value->result);
+ free(value->output);
+ free(value->scriptOutput);
+ free(value);
+ }
+}
+
+static void uw_Sqlcache_freeEntry(uw_Sqlcache_Entry* entry) {
+ if (entry) {
+ free(entry->key);
+ uw_Sqlcache_freeValue(entry->value);
+ free(entry);
+ }
+}
+
+// TODO: pick a number.
+static unsigned int uw_Sqlcache_maxSize = 1234567890;
+
+static void uw_Sqlcache_delete(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry) {
+ if (entry) {
+ HASH_DEL(cache->table, entry);
+ uw_Sqlcache_freeEntry(entry);
+ }
+}
+
+static uw_Sqlcache_Entry *uw_Sqlcache_find(uw_Sqlcache_Cache *cache, char *key, size_t len, int bump) {
+ uw_Sqlcache_Entry *entry = NULL;
+ HASH_FIND(hh, cache->table, key, len, entry);
+ if (entry && bump) {
+ // Bump for LRU purposes.
+ HASH_DEL(cache->table, entry);
+ // Important that we use [entry->key], because [key] might be ephemeral.
+ HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry);
+ }
+ return entry;
+}
+
+static void uw_Sqlcache_add(uw_Sqlcache_Cache *cache, uw_Sqlcache_Entry *entry, size_t len) {
+ HASH_ADD_KEYPTR(hh, cache->table, entry->key, len, entry);
+ if (HASH_COUNT(cache->table) > uw_Sqlcache_maxSize) {
+ // Deletes the first element of the cache.
+ uw_Sqlcache_delete(cache, cache->table);
+ }
+}
+
+static unsigned long uw_Sqlcache_getTimeNow(uw_Sqlcache_Cache *cache) {
+ // TODO: verify that this makes time comparisons do the Right Thing.
+ return cache->timeNow++;
+}
+
+static unsigned long uw_Sqlcache_timeMax(unsigned long x, unsigned long y) {
+ return x > y ? x : y;
+}
+
+static char uw_Sqlcache_keySep = '_';
+
+static char *uw_Sqlcache_allocKeyBuffer(char **keys, size_t numKeys) {
+ size_t len = 0;
+ while (numKeys-- > 0) {
+ char* k = keys[numKeys];
+ if (!k) {
+ // Can only happen when flushing, in which case we don't need anything past the null key.
+ break;
+ }
+ // Leave room for separator.
+ len += 1 + strlen(k);
+ }
+ char *buf = malloc(len+1);
+ // If nothing is copied into the buffer, it should look like it has length 0.
+ buf[0] = 0;
+ return buf;
+}
+
+static char *uw_Sqlcache_keyCopy(char *buf, char *key) {
+ *buf++ = uw_Sqlcache_keySep;
+ return stpcpy(buf, key);
+}
+
+// The NUL-terminated prefix of [key] below always looks something like "_k1_k2_k3..._kn".
+
+uw_Sqlcache_Value *uw_Sqlcache_check(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) {
+ int doBump = random() % 1024 == 0;
+ if (doBump) {
+ pthread_rwlock_wrlock(&cache->lockIn);
+ } else {
+ pthread_rwlock_rdlock(&cache->lockIn);
+ }
+ size_t numKeys = cache->numKeys;
+ char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
+ char *buf = key;
+ time_t timeInvalid = cache->timeInvalid;
+ uw_Sqlcache_Entry *entry = NULL;
+ if (numKeys == 0) {
+ entry = cache->table;
+ if (!entry) {
+ free(key);
+ pthread_rwlock_unlock(&cache->lockIn);
+ return NULL;
+ }
+ } else {
+ while (numKeys-- > 0) {
+ buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]);
+ size_t len = buf - key;
+ entry = uw_Sqlcache_find(cache, key, len, doBump);
+ if (!entry) {
+ free(key);
+ pthread_rwlock_unlock(&cache->lockIn);
+ return NULL;
+ }
+ timeInvalid = uw_Sqlcache_timeMax(timeInvalid, entry->timeInvalid);
+ }
+ free(key);
+ }
+ uw_Sqlcache_Value *value = entry->value;
+ pthread_rwlock_unlock(&cache->lockIn);
+ // ASK: though the argument isn't trivial, this is safe, right?
+ // Returning outside the lock is safe because updates happen at commit time.
+ // Those are the only times the returned value or its strings can get freed.
+ // Handler output is a new string, so it's safe to free this at commit time.
+ return value && timeInvalid < value->timeValid ? value : NULL;
+}
+
+static void uw_Sqlcache_storeCommitOne(uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) {
+ pthread_rwlock_wrlock(&cache->lockIn);
+ size_t numKeys = cache->numKeys;
+ time_t timeNow = uw_Sqlcache_getTimeNow(cache);
+ uw_Sqlcache_Entry *entry = NULL;
+ if (numKeys == 0) {
+ entry = cache->table;
+ if (!entry) {
+ entry = calloc(1, sizeof(uw_Sqlcache_Entry));
+ entry->key = NULL;
+ entry->value = NULL;
+ entry->timeInvalid = 0;
+ cache->table = entry;
+ }
+ } else {
+ char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
+ char *buf = key;
+ while (numKeys-- > 0) {
+ buf = uw_Sqlcache_keyCopy(buf, keys[numKeys]);
+ size_t len = buf - key;
+
+ entry = uw_Sqlcache_find(cache, key, len, 1);
+ if (!entry) {
+ entry = calloc(1, sizeof(uw_Sqlcache_Entry));
+ entry->key = strdup(key);
+ entry->value = NULL;
+ entry->timeInvalid = 0;
+ uw_Sqlcache_add(cache, entry, len);
+ }
+ }
+ free(key);
+ }
+ if (!entry->value || entry->value->timeValid < value->timeValid) {
+ uw_Sqlcache_freeValue(entry->value);
+ entry->value = value;
+ entry->value->timeValid = timeNow;
+ }
+ pthread_rwlock_unlock(&cache->lockIn);
+}
+
+static void uw_Sqlcache_flushCommitOne(uw_Sqlcache_Cache *cache, char **keys) {
+}
+
+static void uw_Sqlcache_commit(void *data) {
+ uw_context ctx = (uw_context)data;
+ uw_Sqlcache_Update *update = ctx->cacheUpdate;
+ while (update) {
+ uw_Sqlcache_Cache *cache = update->cache;
+ char **keys = update->keys;
+ if (update->value) {
+ uw_Sqlcache_storeCommitOne(cache, keys, update->value);
+ } else {
+ uw_Sqlcache_flushCommitOne(cache, keys);
+ }
+ update = update->next;
+ }
+}
+
+static void uw_Sqlcache_free(void *data, int dontCare) {
+ uw_context ctx = (uw_context)data;
+ uw_Sqlcache_Update *update = ctx->cacheUpdate;
+ while (update) {
+ char** keys = update->keys;
+ size_t numKeys = update->cache->numKeys;
+ while (numKeys-- > 0) {
+ free(keys[numKeys]);
+ }
+ free(keys);
+ // Don't free [update->value]: it's in the cache now!
+ uw_Sqlcache_Update *nextUpdate = update->next;
+ free(update);
+ update = nextUpdate;
+ }
+ ctx->cacheUpdate = NULL;
+ ctx->cacheUpdateTail = NULL;
+ uw_Sqlcache_Unlock *unlock = ctx->cacheUnlock;
+ while (unlock) {
+ pthread_rwlock_unlock(unlock->lock);
+ uw_Sqlcache_Unlock *nextUnlock = unlock->next;
+ free(unlock);
+ unlock = nextUnlock;
+ }
+ ctx->cacheUnlock = NULL;
+}
+
+static void uw_Sqlcache_pushUnlock(uw_context ctx, pthread_rwlock_t *lock) {
+ if (!ctx->cacheUnlock) {
+ // Just need one registered commit for both updating and unlocking.
+ uw_register_transactional(ctx, ctx, uw_Sqlcache_commit, NULL, uw_Sqlcache_free);
+ }
+ uw_Sqlcache_Unlock *unlock = malloc(sizeof(uw_Sqlcache_Unlock));
+ unlock->lock = lock;
+ unlock->next = ctx->cacheUnlock;
+ ctx->cacheUnlock = unlock;
+}
+
+void uw_Sqlcache_rlock(uw_context ctx, uw_Sqlcache_Cache *cache) {
+ pthread_rwlock_rdlock(&cache->lockOut);
+ uw_Sqlcache_pushUnlock(ctx, &cache->lockOut);
+}
+
+void uw_Sqlcache_wlock(uw_context ctx, uw_Sqlcache_Cache *cache) {
+ pthread_rwlock_wrlock(&cache->lockOut);
+ uw_Sqlcache_pushUnlock(ctx, &cache->lockOut);
+}
+
+static char **uw_Sqlcache_copyKeys(char **keys, size_t numKeys) {
+ char **copy = malloc(sizeof(char *) * numKeys);
+ while (numKeys-- > 0) {
+ char *k = keys[numKeys];
+ copy[numKeys] = k ? strdup(k) : NULL;
+ }
+ return copy;
+}
+
+void uw_Sqlcache_store(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys, uw_Sqlcache_Value *value) {
+ uw_Sqlcache_Update *update = malloc(sizeof(uw_Sqlcache_Update));
+ update->cache = cache;
+ update->keys = uw_Sqlcache_copyKeys(keys, cache->numKeys);
+ update->value = value;
+ update->next = NULL;
+ // Can't use [uw_Sqlcache_getTimeNow] because it modifies state and we don't have the lock.
+ pthread_rwlock_rdlock(&cache->lockIn);
+ value->timeValid = cache->timeNow;
+ pthread_rwlock_unlock(&cache->lockIn);
+ if (ctx->cacheUpdateTail) {
+ ctx->cacheUpdateTail->next = update;
+ } else {
+ ctx->cacheUpdate = update;
+ }
+ ctx->cacheUpdateTail = update;
+}
+
+void uw_Sqlcache_flush(uw_context ctx, uw_Sqlcache_Cache *cache, char **keys) {
+ // A flush has to happen immediately so that subsequent stores in the same transaction fail.
+ // This is safe to do because we will always call [uw_Sqlcache_wlock] earlier.
+ // If the transaction fails, the only harm done is a few extra cache misses.
+ pthread_rwlock_wrlock(&cache->lockIn);
+ size_t numKeys = cache->numKeys;
+ if (numKeys == 0) {
+ uw_Sqlcache_Entry *entry = cache->table;
+ if (entry) {
+ uw_Sqlcache_freeValue(entry->value);
+ entry->value = NULL;
+ }
+ } else {
+ char *key = uw_Sqlcache_allocKeyBuffer(keys, numKeys);
+ char *buf = key;
+ time_t timeNow = uw_Sqlcache_getTimeNow(cache);
+ while (numKeys-- > 0) {
+ char *k = keys[numKeys];
+ if (!k) {
+ size_t len = buf - key;
+ if (len == 0) {
+ // The first key was null.
+ cache->timeInvalid = timeNow;
+ } else {
+ uw_Sqlcache_Entry *entry = uw_Sqlcache_find(cache, key, len, 0);
+ if (entry) {
+ entry->timeInvalid = timeNow;
+ }
+ }
+ free(key);
+ pthread_rwlock_unlock(&cache->lockIn);
+ return;
+ }
+ buf = uw_Sqlcache_keyCopy(buf, k);
+ }
+ // All the keys were non-null, so we delete the pointed-to entry.
+ size_t len = buf - key;
+ uw_Sqlcache_Entry *entry = uw_Sqlcache_find(cache, key, len, 0);
+ free(key);
+ uw_Sqlcache_delete(cache, entry);
+ }
+ 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/cache.sml b/src/cache.sml
new file mode 100644
index 0000000..015c3ff
--- /dev/null
+++ b/src/cache.sml
@@ -0,0 +1,17 @@
+structure Cache = struct
+
+type cache =
+ {(* Takes a query ID and parameters (and, for store, the value to
+ store) and gives an FFI call that checks, stores, or flushes the
+ relevant entry. The parameters are strings for check and store and
+ optional strings for flush because some parameters might not be
+ fixed. *)
+ check : int * Mono.exp list -> Mono.exp',
+ store : int * Mono.exp list * Mono.exp -> Mono.exp',
+ flush : int * Mono.exp list -> Mono.exp',
+ lock : int * bool (* true = write, false = read *) -> Mono.exp',
+ (* Generates C needed for FFI calls in check, store, and flush. *)
+ setupGlobal : Print.PD.pp_desc,
+ setupQuery : {index : int, params : int} -> Print.PD.pp_desc}
+
+end
diff --git a/src/cgi.sig b/src/cgi.sig
new file mode 100644
index 0000000..ae6549a
--- /dev/null
+++ b/src/cgi.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+signature CGI = sig
+
+end
diff --git a/src/cgi.sml b/src/cgi.sml
new file mode 100644
index 0000000..7ee8142
--- /dev/null
+++ b/src/cgi.sml
@@ -0,0 +1,52 @@
+(* Copyright (c) 2008-2010, 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 Cgi :> CGI = struct
+
+open Settings
+open Print.PD Print
+
+val () = addProtocol {name = "cgi",
+ compile = "",
+ linkStatic = "liburweb_cgi.a",
+ linkDynamic = "-lurweb_cgi",
+ persistent = false,
+ code = fn () => box [string "void uw_global_custom() {",
+ case getSigFile () of
+ NONE => box []
+ | SOME sf => box [string "extern char *uw_sig_file;",
+ newline,
+ string "uw_sig_file = \"",
+ string sf,
+ string "\";",
+ newline],
+ string "uw_setup_limits();",
+ newline,
+ string "}",
+ newline]}
+
+end
diff --git a/src/checknest.sig b/src/checknest.sig
new file mode 100644
index 0000000..f8273b4
--- /dev/null
+++ b/src/checknest.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+signature CHECKNEST = sig
+
+ val annotate : Cjr.file -> Cjr.file
+
+end
diff --git a/src/checknest.sml b/src/checknest.sml
new file mode 100644
index 0000000..fa418d8
--- /dev/null
+++ b/src/checknest.sml
@@ -0,0 +1,187 @@
+(* Copyright (c) 2009, 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 Checknest :> CHECKNEST = struct
+
+open Cjr
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+fun expUses globals =
+ let
+ fun eu (e, _) =
+ case e of
+ EPrim _ => IS.empty
+ | ERel _ => IS.empty
+ | ENamed n => Option.getOpt (IM.find (globals, n), IS.empty)
+ | ECon (_, _, NONE) => IS.empty
+ | ECon (_, _, SOME e) => eu e
+ | ENone _ => IS.empty
+ | ESome (_, e) => eu e
+ | EFfi _ => IS.empty
+ | EFfiApp (_, _, es) => foldl IS.union IS.empty (map (eu o #1) es)
+ | EApp (e, es) => foldl IS.union (eu e) (map eu es)
+
+ | EUnop (_, e) => eu e
+ | EBinop (_, e1, e2) => IS.union (eu e1, eu e2)
+
+ | ERecord (_, xes) => foldl (fn ((_, e), s) => IS.union (eu e, s)) IS.empty xes
+ | EField (e, _) => eu e
+
+ | ECase (e, pes, _) => foldl (fn ((_, e), s) => IS.union (eu e, s)) (eu e) pes
+
+ | EError (e, _) => eu e
+ | EReturnBlob {blob = NONE, mimeType, ...} => eu mimeType
+ | EReturnBlob {blob = SOME blob, mimeType, ...} => IS.union (eu blob, eu mimeType)
+ | ERedirect (e, _) => eu e
+
+ | EWrite e => eu e
+ | ESeq (e1, e2) => IS.union (eu e1, eu e2)
+ | ELet (_, _, e1, e2) => IS.union (eu e1, eu e2)
+
+ | EQuery {query, body, initial, prepared, ...} =>
+ let
+ val s = IS.union (eu query, IS.union (eu body, eu initial))
+ in
+ case prepared of
+ SOME {id, ...} => IS.add (s, id)
+ | _ => s
+ end
+ | EDml {dml, prepared, ...} =>
+ let
+ val s = eu dml
+ in
+ case prepared of
+ SOME {id, ...} => IS.add (s, id)
+ | _ => s
+ end
+ | ENextval {seq, prepared, ...} =>
+ let
+ val s = eu seq
+ in
+ case prepared of
+ SOME {id, ...} => IS.add (s, id)
+ | _ => s
+ end
+ | ESetval {seq, count} => IS.union (eu seq, eu count)
+
+ | EUnurlify (e, _, _) => eu e
+ in
+ eu
+ end
+
+fun annotateExp globals =
+ let
+ fun ae (e as (_, loc)) =
+ case #1 e of
+ EPrim _ => e
+ | ERel _ => e
+ | ENamed n => e
+ | ECon (_, _, NONE) => e
+ | ECon (dk, pc, SOME e) => (ECon (dk, pc, SOME (ae e)), loc)
+ | ENone _ => e
+ | ESome (t, e) => (ESome (t, ae e), loc)
+ | EFfi _ => e
+ | EFfiApp (m, f, es) => (EFfiApp (m, f, map (fn (e, t) => (ae e, t)) es), loc)
+ | EApp (e, es) => (EApp (ae e, map ae es), loc)
+
+ | EUnop (uo, e) => (EUnop (uo, ae e), loc)
+ | EBinop (bo, e1, e2) => (EBinop (bo, ae e1, ae e2), loc)
+
+ | ERecord (n, xes) => (ERecord (n, map (fn (x, e) => (x, ae e)) xes), loc)
+ | EField (e, f) => (EField (ae e, f), loc)
+
+ | ECase (e, pes, ts) => (ECase (ae e, map (fn (p, e) => (p, ae e)) pes, ts), loc)
+
+ | EError (e, t) => (EError (ae e, t), loc)
+ | EReturnBlob {blob = NONE, mimeType, t} => (EReturnBlob {blob = NONE, mimeType = ae mimeType, t = t}, loc)
+ | EReturnBlob {blob = SOME blob, mimeType, t} => (EReturnBlob {blob = SOME (ae blob), mimeType = ae mimeType, t = t}, loc)
+ | ERedirect (e, t) => (ERedirect (ae e, t), loc)
+
+ | EWrite e => (EWrite (ae e), loc)
+ | ESeq (e1, e2) => (ESeq (ae e1, ae e2), loc)
+ | ELet (x, t, e1, e2) => (ELet (x, t, ae e1, ae e2), loc)
+
+ | EQuery {exps, tables, rnum, state, query, body, initial, prepared} =>
+ (EQuery {exps = exps,
+ tables = tables,
+ rnum = rnum,
+ state = state,
+ query = ae query,
+ body = ae body,
+ initial = ae initial,
+ prepared = case prepared of
+ NONE => NONE
+ | SOME {id, query, ...} => SOME {id = id, query = query,
+ nested = IS.member (expUses globals body, id)}},
+ loc)
+ | EDml {dml, prepared, mode} =>
+ (EDml {dml = ae dml,
+ prepared = prepared,
+ mode = mode}, loc)
+
+ | ENextval {seq, prepared} =>
+ (ENextval {seq = ae seq,
+ prepared = prepared}, loc)
+ | ESetval {seq, count} =>
+ (ESetval {seq = ae seq,
+ count = ae count}, loc)
+
+ | EUnurlify (e, t, b) => (EUnurlify (ae e, t, b), loc)
+ in
+ ae
+ end
+
+fun annotate (ds, syms) =
+ let
+ val globals =
+ foldl (fn ((d, _), globals) =>
+ case d of
+ DVal (_, n, _, e) => IM.insert (globals, n, expUses globals e)
+ | DFun (_, n, _, _, e) => IM.insert (globals, n, expUses globals e)
+ | DFunRec fs =>
+ let
+ val s = foldl (fn ((_, _, _, _, e), s) => IS.union (expUses globals e, s)) IS.empty fs
+ in
+ foldl (fn ((_, n, _, _, _), globals) => IM.insert (globals, n, s)) globals fs
+ end
+ | _ => globals) IM.empty ds
+
+ val ds =
+ map (fn d as (_, loc) =>
+ case #1 d of
+ DVal (x, n, t, e) => (DVal (x, n, t, annotateExp globals e), loc)
+ | DFun (x, n, ts, t, e) => (DFun (x, n, ts, t, annotateExp globals e), loc)
+ | DFunRec fs => (DFunRec
+ (map (fn (x, n, ts, t, e) => (x, n, ts, t, annotateExp globals e)) fs), loc)
+ | _ => d) ds
+ in
+ (ds, syms)
+ end
+
+end
diff --git a/src/cjr.sml b/src/cjr.sml
new file mode 100644
index 0000000..e582e6a
--- /dev/null
+++ b/src/cjr.sml
@@ -0,0 +1,138 @@
+(* Copyright (c) 2008, 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 Cjr = struct
+
+type 'a located = 'a ErrorMsg.located
+
+datatype datatype_kind = datatype DatatypeKind.datatype_kind
+
+datatype typ' =
+ TFun of typ * typ
+ | TRecord of int
+ | TDatatype of datatype_kind * int * (string * int * typ option) list ref
+ | TFfi of string * string
+ | TOption of typ
+ | TList of typ * int
+
+withtype typ = typ' located
+
+datatype patCon =
+ PConVar of int
+ | PConFfi of {mod : string, datatyp : string, con : string, arg : typ option}
+
+datatype pat' =
+ PVar of string * typ
+ | PPrim of Prim.t
+ | PCon of datatype_kind * patCon * pat option
+ | PRecord of (string * pat * typ) list
+ | PNone of typ
+ | PSome of typ * pat
+
+withtype pat = pat' located
+
+datatype failure_mode = datatype Settings.failure_mode
+
+datatype exp' =
+ EPrim of Prim.t
+ | ERel of int
+ | ENamed of int
+ | ECon of datatype_kind * patCon * exp option
+ | ENone of typ
+ | ESome of typ * exp
+ | EFfi of string * string
+ | EFfiApp of string * string * (exp * typ) list
+ | EApp of exp * exp list
+
+ | EUnop of string * exp
+ | EBinop of string * exp * exp
+
+ | ERecord of int * (string * exp) list
+ | EField of exp * string
+
+ | ECase of exp * (pat * exp) list * { disc : typ, result : typ }
+
+ | EError of exp * typ
+ | EReturnBlob of {blob : exp option, mimeType : exp, t : typ}
+ | ERedirect of exp * typ
+
+ | EWrite of exp
+ | ESeq of exp * exp
+ | ELet of string * typ * exp * exp
+
+ | EQuery of { exps : (string * typ) list,
+ tables : (string * (string * typ) list) list,
+ rnum : int,
+ state : typ,
+ query : exp,
+ body : exp,
+ initial : exp,
+ prepared : {id : int, query : string, nested : bool} option }
+ | EDml of { dml : exp,
+ prepared : {id : int, dml : string} option,
+ mode : failure_mode }
+ | ENextval of { seq : exp,
+ prepared : {id : int, query : string} option }
+ | ESetval of { seq : exp, count : exp }
+ | EUnurlify of exp * typ * bool
+
+withtype exp = exp' located
+
+datatype task = Initialize | ClientLeaves | Periodic of Int64.int
+
+datatype decl' =
+ DStruct of int * (string * typ) list
+ | DDatatype of (datatype_kind * string * int * (string * int * typ option) list) list
+ | DDatatypeForward of datatype_kind * string * int
+ | DVal of string * int * typ * exp
+ | DFun of string * int * (string * typ) list * typ * exp
+ | DFunRec of (string * int * (string * typ) list * typ * exp) list
+
+ | DTable of string * (string * typ) list * string * (string * string) list
+ | DSequence of string
+ | DView of string * (string * typ) list * string
+ | DDatabase of {name : string, expunge : int, initialize : int}
+ | DPreparedStatements of (string * int) list
+
+ | DJavaScript of string
+ | DCookie of string
+ | DStyle of string
+
+ | DTask of task * string (* first arg name *) * string * exp
+ | DOnError of int
+
+withtype decl = decl' located
+
+datatype sidedness = datatype Mono.sidedness
+datatype dbmode = datatype Mono.dbmode
+
+datatype effect = datatype Export.effect
+datatype export_kind = datatype Export.export_kind
+
+type file = decl list * (export_kind * string * int * typ list * typ * sidedness * dbmode * bool) list
+
+end
diff --git a/src/cjr_env.sig b/src/cjr_env.sig
new file mode 100644
index 0000000..0254f15
--- /dev/null
+++ b/src/cjr_env.sig
@@ -0,0 +1,59 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature CJR_ENV = sig
+
+ type env
+
+ val empty : env
+
+ exception UnboundRel of int
+ exception UnboundNamed of int
+ exception UnboundF of int
+ exception UnboundStruct of int
+
+ val pushDatatype : env -> string -> int -> (string * int * Cjr.typ option) list -> env
+ val lookupDatatype : env -> int -> string * (string * int * Cjr.typ option) list
+
+ val lookupConstructor : env -> int -> string * Cjr.typ option * int
+
+ val pushERel : env -> string -> Cjr.typ -> env
+ val lookupERel : env -> int -> string * Cjr.typ
+ val listERels : env -> (string * Cjr.typ) list
+ val countERels : env -> int
+
+ val pushENamed : env -> string -> int -> Cjr.typ -> env
+ val lookupENamed : env -> int -> string * Cjr.typ
+
+ val pushStruct : env -> int -> (string * Cjr.typ) list -> env
+ val lookupStruct : env -> int -> (string * Cjr.typ) list
+
+ val declBinds : env -> Cjr.decl -> env
+
+ val classifyDatatype : (string * int * Cjr.typ option) list -> Cjr.datatype_kind
+
+end
diff --git a/src/cjr_env.sml b/src/cjr_env.sml
new file mode 100644
index 0000000..21188b5
--- /dev/null
+++ b/src/cjr_env.sml
@@ -0,0 +1,177 @@
+(* Copyright (c) 2008, 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 CjrEnv :> CJR_ENV = struct
+
+open Cjr
+
+structure IM = IntBinaryMap
+
+
+exception UnboundRel of int
+exception UnboundNamed of int
+exception UnboundF of int
+exception UnboundStruct of int
+
+type env = {
+ datatypes : (string * (string * int * typ option) list) IM.map,
+ constructors : (string * typ option * int) IM.map,
+
+ numRelE : int,
+ relE : (string * typ) list,
+ namedE : (string * typ) IM.map,
+
+ structs : (string * typ) list IM.map
+}
+
+val empty : env = {
+ datatypes = IM.empty,
+ constructors = IM.empty,
+
+ numRelE = 0,
+ relE = [],
+ namedE = IM.empty,
+
+ structs = IM.insert (IM.empty, 0, [])
+}
+
+fun pushDatatype (env : env) x n xncs =
+ {datatypes = IM.insert (#datatypes env, n, (x, xncs)),
+ constructors = foldl (fn ((x, n', to), constructors) =>
+ IM.insert (constructors, n', (x, to, n)))
+ (#constructors env) xncs,
+
+ numRelE = #numRelE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ structs = #structs env}
+
+fun lookupDatatype (env : env) n =
+ case IM.find (#datatypes env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupConstructor (env : env) n =
+ case IM.find (#constructors env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushERel (env : env) x t =
+ {datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ numRelE = #numRelE env + 1,
+ relE = (x, t) :: #relE env,
+ namedE = #namedE env,
+
+ structs = #structs env}
+
+fun lookupERel (env : env) n =
+ (List.nth (#relE env, n))
+ handle Subscript => raise UnboundRel n
+
+fun countERels (env : env) = #numRelE env
+
+fun listERels (env : env) = #relE env
+
+fun pushENamed (env : env) x n t =
+ {datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ numRelE = #numRelE env,
+ relE = #relE env,
+ namedE = IM.insert (#namedE env, n, (x, t)),
+
+ structs = #structs env}
+
+fun lookupENamed (env : env) n =
+ case IM.find (#namedE env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushStruct (env : env) n xts =
+ {datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ numRelE = #numRelE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ structs = IM.insert (#structs env, n, xts)}
+
+fun lookupStruct (env : env) n =
+ case IM.find (#structs env, n) of
+ NONE => raise UnboundStruct n
+ | SOME x => x
+
+fun classifyDatatype xncs =
+ if List.all (fn (_, _, NONE) => true | _ => false) xncs then
+ Enum
+ else
+ Default
+
+fun declBinds env (d, loc) =
+ case d of
+ DDatatype dts =>
+ foldl (fn ((_, x, n, xncs), env) =>
+ let
+ val env = pushDatatype env x n xncs
+ val dt = (TDatatype (classifyDatatype xncs, n, ref xncs), loc)
+ in
+ foldl (fn ((x', n', NONE), env) => pushENamed env x' n' dt
+ | ((x', n', SOME t), env) => pushENamed env x' n' (TFun (t, dt), loc))
+ env xncs
+ end) env dts
+ | DDatatypeForward (_, x, n) => pushDatatype env x n []
+ | DStruct (n, xts) => pushStruct env n xts
+ | DVal (x, n, t, _) => pushENamed env x n t
+ | DFun (fx, n, args, ran, _) =>
+ let
+ val t = foldl (fn ((_, arg), t) => (TFun (arg, t), loc)) ran args
+ in
+ pushENamed env fx n t
+ end
+ | DFunRec vis =>
+ foldl (fn ((fx, n, args, ran, _), env) =>
+ let
+ val t = foldl (fn ((_, arg), t) => (TFun (arg, t), loc)) ran args
+ in
+ pushENamed env fx n t
+ end) env vis
+ | DTable _ => env
+ | DSequence _ => env
+ | DView _ => env
+ | DDatabase _ => env
+ | DPreparedStatements _ => env
+ | DJavaScript _ => env
+ | DCookie _ => env
+ | DStyle _ => env
+ | DTask _ => env
+ | DOnError _ => env
+
+end
diff --git a/src/cjr_print.sig b/src/cjr_print.sig
new file mode 100644
index 0000000..baef005
--- /dev/null
+++ b/src/cjr_print.sig
@@ -0,0 +1,39 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing Ur/Web C jr. language *)
+
+signature CJR_PRINT = sig
+ val p_typ : CjrEnv.env -> Cjr.typ Print.printer
+ val p_exp : CjrEnv.env -> Cjr.exp Print.printer
+ val p_decl : CjrEnv.env -> Cjr.decl Print.printer
+ val p_file : CjrEnv.env -> Cjr.file Print.printer
+
+ val p_sql : CjrEnv.env -> Cjr.file Print.printer
+
+ val debug : bool ref
+end
diff --git a/src/cjr_print.sml b/src/cjr_print.sml
new file mode 100644
index 0000000..53587ff
--- /dev/null
+++ b/src/cjr_print.sml
@@ -0,0 +1,3749 @@
+(* Copyright (c) 2008-2014, 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.
+ *)
+
+(* Pretty-printing C jr. *)
+
+structure CjrPrint :> CJR_PRINT = struct
+
+open Print.PD
+open Print
+
+open Cjr
+
+val dummyt = (TRecord 0, ErrorMsg.dummySpan)
+
+structure E = CjrEnv
+structure EM = ErrorMsg
+
+structure SK = struct
+type ord_key = string
+val compare = String.compare
+end
+
+structure SS = BinarySetFn(SK)
+structure SM = BinaryMapFn(SK)
+structure IS = IntBinarySet
+
+structure CM = BinaryMapFn(struct
+ type ord_key = char
+ val compare = Char.compare
+ end)
+
+val debug = ref false
+
+val app_js = ref ""
+
+val dummyTyp = (TDatatype (Enum, 0, ref []), ErrorMsg.dummySpan)
+
+val ident = String.translate (fn #"'" => "PRIME"
+ | ch => str ch)
+
+val p_ident = string o ident
+
+fun isUnboxable (t : typ) =
+ case #1 t of
+ TDatatype (Default, _, _) => true
+ | TFfi ("Basis", "string") => true
+ | TFfi ("Basis", "queryString") => true
+ | _ => false
+
+fun p_typ' par env (t, loc) =
+ case t of
+ TFun (t1, t2) => (EM.errorAt loc "Function type remains";
+ string "<FUNCTION>")
+ | TRecord 0 => string "uw_unit"
+ | TRecord i => box [string "struct",
+ space,
+ string "__uws_",
+ string (Int.toString i)]
+ | TDatatype (Enum, n, _) =>
+ (box [string "enum",
+ space,
+ string ("__uwe_" ^ ident (#1 (E.lookupDatatype env n)) ^ "_" ^ Int.toString n)]
+ handle CjrEnv.UnboundNamed _ => string ("__uwd_UNBOUND__" ^ Int.toString n))
+ | TDatatype (Option, n, xncs) =>
+ (case ListUtil.search #3 (!xncs) of
+ NONE => raise Fail "CjrPrint: TDatatype marked Option has no constructor with an argument"
+ | SOME t =>
+ if isUnboxable t then
+ p_typ' par env t
+ else
+ box [p_typ' par env t,
+ string "*"])
+ | TDatatype (Default, n, _) =>
+ (box [string "struct",
+ space,
+ string ("__uwd_" ^ ident (#1 (E.lookupDatatype env n)) ^ "_" ^ Int.toString n ^ "*")]
+ handle CjrEnv.UnboundNamed _ => string ("__uwd_UNBOUND__" ^ Int.toString n))
+ | TFfi (m, x) => box [string "uw_", p_ident m, string "_", p_ident x]
+ | TOption t =>
+ if isUnboxable t then
+ p_typ' par env t
+ else
+ box [p_typ' par env t,
+ string "*"]
+ | TList (_, i) => box [string "struct",
+ space,
+ string "__uws_",
+ string (Int.toString i),
+ string "*"]
+
+and p_typ env = p_typ' false env
+
+fun p_htyp' par env (t, loc) =
+ case t of
+ TFun (t1, t2) => parenIf par (box [p_htyp' true env t1,
+ space,
+ string "->",
+ space,
+ p_htyp' true env t2])
+ | TRecord i =>
+ let
+ val xts = E.lookupStruct env i
+ in
+ box [string "{",
+ p_list (fn (x, t) =>
+ box [string x,
+ space,
+ string ":",
+ space,
+ p_htyp env t]) xts,
+ string "}"]
+ end
+ | TDatatype (_, n, _) =>
+ let
+ val (name, _) = E.lookupDatatype env n
+ in
+ string name
+ end
+ | TFfi (m, x) => string (m ^ "." ^ x)
+ | TOption t => parenIf par (box [string "option",
+ space,
+ p_htyp' true env t])
+ | TList (t, _) => parenIf par (box [string "list",
+ space,
+ p_htyp' true env t])
+
+and p_htyp env = p_htyp' false env
+
+fun p_rel env n = string ("__uwr_" ^ ident (#1 (E.lookupERel env n)) ^ "_" ^ Int.toString (E.countERels env - n - 1))
+ handle CjrEnv.UnboundRel _ => string ("__uwr_UNBOUND_" ^ Int.toString (E.countERels env - n - 1))
+
+fun p_enamed' env n =
+ "__uwn_" ^ ident (#1 (E.lookupENamed env n)) ^ "_" ^ Int.toString n
+ handle CjrEnv.UnboundNamed _ => "__uwn_UNBOUND_" ^ Int.toString n
+
+fun p_enamed env n = string (p_enamed' env n)
+
+fun p_con_named env n =
+ string ("__uwc_" ^ ident (#1 (E.lookupConstructor env n)) ^ "_" ^ Int.toString n)
+ handle CjrEnv.UnboundNamed _ => string ("__uwc_UNBOUND_" ^ Int.toString n)
+
+fun p_pat_preamble env (p, _) =
+ case p of
+ PVar (x, t) => (box [p_typ env t,
+ space,
+ string "__uwr_",
+ p_ident x,
+ string "_",
+ string (Int.toString (E.countERels env)),
+ string ";",
+ newline],
+ E.pushERel env x t)
+ | PPrim _ => (box [], env)
+ | PCon (_, _, NONE) => (box [], env)
+ | PCon (_, _, SOME p) => p_pat_preamble env p
+ | PRecord xps =>
+ foldl (fn ((_, p, _), (pp, env)) =>
+ let
+ val (pp', env) = p_pat_preamble env p
+ in
+ (box [pp', pp], env)
+ end) (box [], env) xps
+ | PNone _ => (box [], env)
+ | PSome (_, p) => p_pat_preamble env p
+
+fun p_patCon env pc =
+ case pc of
+ PConVar n => p_con_named env n
+ | PConFfi {mod = m, con, ...} => string ("uw_" ^ ident m ^ "_" ^ ident con)
+
+fun p_patMatch (env, disc) (p, loc) =
+ case p of
+ PVar _ => string "1"
+ | PPrim (Prim.Int n) => box [string ("(" ^ disc),
+ space,
+ string "==",
+ space,
+ Prim.p_t_GCC (Prim.Int n),
+ string ")"]
+ | PPrim (Prim.String s) => box [string ("!strcmp(" ^ disc),
+ string ",",
+ space,
+ Prim.p_t_GCC (Prim.String s),
+ string ")"]
+ | PPrim (Prim.Char ch) => box [string ("(" ^ disc),
+ space,
+ string "==",
+ space,
+ Prim.p_t_GCC (Prim.Char ch),
+ string ")"]
+ | PPrim _ => raise Fail "CjrPrint: Disallowed PPrim primitive"
+
+ | PCon (dk, pc, po) =>
+ let
+ val p =
+ case po of
+ NONE => box []
+ | SOME p =>
+ let
+ val (x, to) = case pc of
+ PConVar n =>
+ let
+ val (x, to, _) = E.lookupConstructor env n
+ in
+ ("uw_" ^ ident x, to)
+ end
+ | PConFfi {mod = m, con, arg, ...} =>
+ ("uw_" ^ ident m ^ "_" ^ ident con, arg)
+
+ val t = case to of
+ NONE => raise Fail "CjrPrint: Constructor mismatch"
+ | SOME t => t
+
+ val x = case pc of
+ PConVar n =>
+ let
+ val (x, _, _) = E.lookupConstructor env n
+ in
+ "uw_" ^ ident x
+ end
+ | PConFfi {mod = m, con, ...} =>
+ "uw_" ^ ident m ^ "_" ^ ident con
+
+ val disc' = case dk of
+ Enum => raise Fail "CjrPrint: Looking at argument of no-argument constructor"
+ | Default => disc ^ "->data." ^ x
+ | Option =>
+ if isUnboxable t then
+ disc
+ else
+ "(*" ^ disc ^ ")"
+
+ val p = p_patMatch (env, disc') p
+ in
+ box [space,
+ string "&&",
+ space,
+ p]
+ end
+ in
+ box [string disc,
+ case (dk, po) of
+ (Enum, _) => box [space,
+ string "==",
+ space,
+ p_patCon env pc]
+ | (Default, _) => box [string "->tag",
+ space,
+ string "==",
+ space,
+ p_patCon env pc]
+ | (Option, NONE) => box [space,
+ string "==",
+ space,
+ string "NULL"]
+ | (Option, SOME _) => box [space,
+ string "!=",
+ space,
+ string "NULL"],
+ p]
+ end
+
+ | PRecord [] => string "1"
+ | PRecord xps =>
+ p_list_sep (box [space, string "&&", space]) (fn (x, p, _) => p_patMatch (env, disc ^ ".__uwf_" ^ ident x) p) xps
+
+ | PNone _ =>
+ box [string disc,
+ space,
+ string "==",
+ space,
+ string "NULL"]
+
+ | PSome (t, p) =>
+ let
+ val disc' = if isUnboxable t then
+ disc
+ else
+ "(*" ^ disc ^ ")"
+
+ val p = p_patMatch (env, disc') p
+ in
+ box [string disc,
+ space,
+ string "!=",
+ space,
+ string "NULL",
+ space,
+ string "&&",
+ space,
+ p]
+ end
+
+fun p_patBind (env, disc) (p, loc) =
+ case p of
+ PVar (x, t) =>
+ (box [p_typ env t,
+ space,
+ string "__uwr_",
+ p_ident x,
+ string "_",
+ string (Int.toString (E.countERels env)),
+ space,
+ string "=",
+ space,
+ string disc,
+ string ";",
+ newline],
+ E.pushERel env x t)
+ | PPrim _ => (box [], env)
+
+ | PCon (_, _, NONE) => (box [], env)
+
+ | PCon (dk, pc, SOME p) =>
+ let
+ val (x, to) = case pc of
+ PConVar n =>
+ let
+ val (x, to, _) = E.lookupConstructor env n
+ in
+ ("uw_" ^ ident x, to)
+ end
+ | PConFfi {mod = m, con, arg, ...} =>
+ ("uw_" ^ ident m ^ "_" ^ ident con, arg)
+
+ val t = case to of
+ NONE => raise Fail "CjrPrint: Constructor mismatch"
+ | SOME t => t
+
+ val disc' = case dk of
+ Enum => raise Fail "CjrPrint: Looking at argument of no-argument constructor"
+ | Default => disc ^ "->data." ^ x
+ | Option =>
+ if isUnboxable t then
+ disc
+ else
+ "(*" ^ disc ^ ")"
+ in
+ p_patBind (env, disc') p
+ end
+
+ | PRecord xps =>
+ let
+ val (xps, env) =
+ ListUtil.foldlMap (fn ((x, p, t), env) => p_patBind (env, disc ^ ".__uwf_" ^ ident x) p)
+ env xps
+ in
+ (p_list_sep (box []) (fn x => x) xps,
+ env)
+ end
+
+ | PNone _ => (box [], env)
+
+ | PSome (t, p) =>
+ let
+ val disc' = if isUnboxable t then
+ disc
+ else
+ "(*" ^ disc ^ ")"
+ in
+ p_patBind (env, disc') p
+ end
+
+fun patConInfo env pc =
+ case pc of
+ PConVar n =>
+ let
+ val (x, _, dn) = E.lookupConstructor env n
+ val (dx, _) = E.lookupDatatype env dn
+ in
+ ("__uwd_" ^ ident dx ^ "_" ^ Int.toString dn,
+ "__uwc_" ^ ident x ^ "_" ^ Int.toString n,
+ "uw_" ^ ident x)
+ end
+ | PConFfi {mod = m, datatyp, con, ...} =>
+ ("uw_" ^ ident m ^ "_" ^ ident datatyp,
+ "uw_" ^ ident m ^ "_" ^ ident con,
+ "uw_" ^ ident con)
+
+fun p_unsql wontLeakStrings env (tAll as (t, loc)) e eLen =
+ case t of
+ TFfi ("Basis", "int") => box [string "uw_Basis_stringToInt_error(ctx, ", e, string ")"]
+ | TFfi ("Basis", "float") => box [string "uw_Basis_stringToFloat_error(ctx, ", e, string ")"]
+ | TFfi ("Basis", "string") =>
+ if wontLeakStrings then
+ e
+ else
+ box [string "uw_strdup(ctx, ", e, string ")"]
+ | TFfi ("Basis", "bool") => box [string "uw_Basis_stringToBool_error(ctx, ", e, string ")"]
+ | TFfi ("Basis", "time") => box [string "uw_Basis_stringToTime_error(ctx, ", e, string ")"]
+ | TFfi ("Basis", "blob") => box [string "uw_Basis_stringToBlob_error(ctx, ",
+ e,
+ string ", ",
+ eLen,
+ string ")"]
+ | TFfi ("Basis", "channel") => box [string "uw_Basis_stringToChannel_error(ctx, ", e, string ")"]
+ | TFfi ("Basis", "client") => box [string "uw_Basis_stringToClient_error(ctx, ", e, string ")"]
+
+ | _ => (ErrorMsg.errorAt loc "Don't know how to unmarshal type from SQL";
+ Print.eprefaces' [("Type", p_htyp env tAll)];
+ string "ERROR")
+
+fun p_getcol wontLeakStrings env (tAll as (t, loc)) i =
+ case t of
+ TOption t =>
+ box [string "(PQgetisnull(res, i, ",
+ string (Int.toString i),
+ string ") ? NULL : ",
+ case t of
+ (TFfi ("Basis", "string"), _) => p_getcol wontLeakStrings env t i
+ | _ => box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "*tmp = uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp = ",
+ p_getcol wontLeakStrings env t i,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string ")"]
+ | _ =>
+ box [string "(PQgetisnull(res, i, ",
+ string (Int.toString i),
+ string ") ? ",
+ box [string "({",
+ p_typ env tAll,
+ space,
+ string "tmp;",
+ newline,
+ string "uw_error(ctx, FATAL, \"Unexpectedly NULL field #",
+ string (Int.toString i),
+ string "\");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string " : ",
+ p_unsql wontLeakStrings env tAll
+ (box [string "PQgetvalue(res, i, ",
+ string (Int.toString i),
+ string ")"])
+ (box [string "PQgetlength(res, i, ",
+ string (Int.toString i),
+ string ")"]),
+ string ")"]
+
+datatype sql_type = datatype Settings.sql_type
+val isBlob = Settings.isBlob
+
+fun isFile (t : typ) =
+ case #1 t of
+ TFfi ("Basis", "file") => true
+ | _ => false
+
+fun p_sql_type t = string (Settings.p_sql_ctype t)
+
+fun getPargs (e, _) =
+ case e of
+ EPrim (Prim.String _) => []
+ | EFfiApp ("Basis", "strcat", [(e1, _), (e2, _)]) => getPargs e1 @ getPargs e2
+
+ | EFfiApp ("Basis", "sqlifyInt", [(e, _)]) => [(e, Int)]
+ | EFfiApp ("Basis", "sqlifyFloat", [(e, _)]) => [(e, Float)]
+ | EFfiApp ("Basis", "sqlifyString", [(e, _)]) => [(e, String)]
+ | EFfiApp ("Basis", "sqlifyBool", [(e, _)]) => [(e, Bool)]
+ | EFfiApp ("Basis", "sqlifyTime", [(e, _)]) => [(e, Time)]
+ | EFfiApp ("Basis", "sqlifyBlob", [(e, _)]) => [(e, Blob)]
+ | EFfiApp ("Basis", "sqlifyChannel", [(e, _)]) => [(e, Channel)]
+ | EFfiApp ("Basis", "sqlifyClient", [(e, _)]) => [(e, Client)]
+
+ | ECase (e,
+ [((PNone _, _),
+ (EPrim (Prim.String (_, "NULL")), _)),
+ ((PSome (_, (PVar _, _)), _),
+ (EFfiApp (m, x, [((ERel 0, _), _)]), _))],
+ {disc = t, ...}) => map (fn (x, y) => (x, Nullable y)) (getPargs (EFfiApp (m, x, [(e, t)]), #2 e))
+
+ | ECase (e,
+ [((PCon (_, PConFfi {mod = "Basis", con = "True", ...}, _), _),
+ (EPrim (Prim.String (_, "TRUE")), _)),
+ ((PCon (_, PConFfi {mod = "Basis", con = "False", ...}, _), _),
+ (EPrim (Prim.String (_, "FALSE")), _))],
+ _) => [(e, Bool)]
+
+ | _ => raise Fail "CjrPrint: getPargs"
+
+val notLeakies = SS.fromList ["int", "float", "char", "time", "bool", "unit", "client", "channel",
+ "xhtml", "page", "xbody", "css_class"]
+val notLeakies' = SS.fromList ["blob"]
+
+fun notLeaky env allowHeapAllocated =
+ let
+ fun nl ok (t, _) =
+ case t of
+ TFun _ => false
+ | TRecord n =>
+ let
+ val xts = E.lookupStruct env n
+ in
+ List.all (fn (_, t) => nl ok t) xts
+ end
+ | TDatatype (dk, n, ref cons) =>
+ IS.member (ok, n)
+ orelse
+ ((allowHeapAllocated orelse dk = Enum)
+ andalso
+ let
+ val ok' = IS.add (ok, n)
+ in
+ List.all (fn (_, _, to) => case to of
+ NONE => true
+ | SOME t => nl ok' t) cons
+ end)
+ | TFfi ("Basis", t) => SS.member (notLeakies, t)
+ orelse (allowHeapAllocated andalso SS.member (notLeakies', t))
+ | TFfi _ => false
+ | TOption t => allowHeapAllocated andalso nl ok t
+ | TList (t, _) => allowHeapAllocated andalso nl ok t
+ in
+ nl IS.empty
+ end
+
+fun capitalize s =
+ if s = "" then
+ ""
+ else
+ str (Char.toUpper (String.sub (s, 0))) ^ String.extract (s, 1, NONE)
+
+local
+ val urlHandlers = ref ([] : (pp_desc * pp_desc) list)
+in
+
+fun addUrlHandler v = urlHandlers := v :: !urlHandlers
+
+fun latestUrlHandlers () =
+ !urlHandlers
+ before urlHandlers := []
+
+fun clearUrlHandlers () = urlHandlers := []
+
+end
+
+val unurlifies = ref IS.empty
+
+fun unurlify fromClient env (t, loc) =
+ let
+ fun deStar request =
+ case request of
+ "(*request)" => "request"
+ | _ => "&" ^ request
+
+ fun unurlify' request t =
+ case t of
+ TFfi ("Basis", "unit") => string ("uw_Basis_unurlifyUnit(ctx, " ^ deStar request ^ ")")
+ | TFfi ("Basis", "string") => string (if fromClient then
+ "uw_Basis_unurlifyString_fromClient(ctx, " ^ deStar request ^ ")"
+ else
+ "uw_Basis_unurlifyString(ctx, " ^ deStar request ^ ")")
+ | TFfi (m, t) => string ("uw_" ^ ident m ^ "_unurlify" ^ capitalize t ^ "(ctx, " ^ deStar request ^ ")")
+
+ | TRecord 0 => string ("uw_Basis_unurlifyUnit(ctx, " ^ deStar request ^ ")")
+ | TRecord i =>
+ let
+ val xts = E.lookupStruct env i
+ in
+ box [string "({",
+ newline,
+ box (map (fn (x, t) =>
+ box [p_typ env t,
+ space,
+ string "uwr_",
+ string x,
+ space,
+ string "=",
+ space,
+ unurlify' request (#1 t),
+ string ";",
+ newline]) xts),
+ string "struct",
+ space,
+ string "__uws_",
+ string (Int.toString i),
+ space,
+ string "tmp",
+ space,
+ string "=",
+ space,
+ string "{",
+ space,
+ p_list_sep (box [string ",", space]) (fn (x, _) => box [string "uwr_",
+ string x]) xts,
+ space,
+ string "};",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ end
+
+ | TDatatype (Enum, i, _) =>
+ let
+ val (x, xncs) = E.lookupDatatype env i
+
+ fun doEm xncs =
+ case xncs of
+ [] => string ("(uw_error(ctx, FATAL, \"Error unurlifying datatype "
+ ^ x ^ "\"), (enum __uwe_"
+ ^ x ^ "_" ^ Int.toString i ^ ")0)")
+ | (x', n, to) :: rest =>
+ box [string ("((!strncmp(" ^ request ^ ", \""),
+ string x',
+ string "\", ",
+ string (Int.toString (size x')),
+ string (") && (" ^ request ^ "["),
+ string (Int.toString (size x')),
+ string ("] == 0 || " ^ request ^ "["),
+ string (Int.toString (size x')),
+ string ("] == '/')) ? (" ^ request ^ " += "),
+ string (Int.toString (size x')),
+ string (", (" ^ request ^ "[0] == '/' ? ++" ^ request ^ " : NULL), __uwc_" ^ ident x' ^ "_" ^ Int.toString n ^ ")"),
+ space,
+ string ":",
+ space,
+ doEm rest,
+ string ")"]
+ in
+ doEm xncs
+ end
+
+ | TDatatype (Option, i, xncs) =>
+ if IS.member (!unurlifies, i) then
+ box [string "unurlify_",
+ string (Int.toString i),
+ string ("(ctx, " ^ deStar request ^ ")")]
+ else
+ let
+ val (x, _) = E.lookupDatatype env i
+
+ val (no_arg, has_arg, t) =
+ case !xncs of
+ [(no_arg, _, NONE), (has_arg, _, SOME t)] =>
+ (no_arg, has_arg, t)
+ | [(has_arg, _, SOME t), (no_arg, _, NONE)] =>
+ (no_arg, has_arg, t)
+ | _ => raise Fail "CjrPrint: unfooify misclassified Option datatype"
+
+ val unboxable = isUnboxable t
+ in
+ unurlifies := IS.add (!unurlifies, i);
+ addUrlHandler (box [string "static",
+ space,
+ p_typ env t,
+ space,
+ if unboxable then
+ box []
+ else
+ string "*",
+ string "unurlify_",
+ string (Int.toString i),
+ string "(uw_context, char **);",
+ newline],
+ box [string "static",
+ space,
+ p_typ env t,
+ space,
+ if unboxable then
+ box []
+ else
+ string "*",
+ string "unurlify_",
+ string (Int.toString i),
+ string "(uw_context ctx, char **request) {",
+ newline,
+ box [string "return ((*request)[0] == '/' ? ++*request : *request,",
+ newline,
+ string "((!strncmp(*request, \"",
+ string no_arg,
+ string "\", ",
+ string (Int.toString (size no_arg)),
+ string ") && ((*request)[",
+ string (Int.toString (size no_arg)),
+ string "] == 0 || (*request)[",
+ string (Int.toString (size no_arg)),
+ string "] == '/')) ? (*request",
+ space,
+ string "+=",
+ space,
+ string (Int.toString (size no_arg)),
+ string ", NULL) : ((!strncmp(*request, \"",
+ string has_arg,
+ string "\", ",
+ string (Int.toString (size has_arg)),
+ string ") && ((*request)[",
+ string (Int.toString (size has_arg)),
+ string "] == 0 || (*request)[",
+ string (Int.toString (size has_arg)),
+ string "] == '/')) ? (*request",
+ space,
+ string "+=",
+ space,
+ string (Int.toString (size has_arg)),
+ string ", ((*request)[0] == '/' ? ++*request : NULL), ",
+ newline,
+
+ if unboxable then
+ unurlify' "(*request)" (#1 t)
+ else
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ unurlify' "(*request)" (#1 t),
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string ")",
+ newline,
+ string ":",
+ space,
+ string ("(uw_error(ctx, FATAL, \"Error unurlifying datatype " ^ x
+ ^ "\"), NULL))));"),
+ newline],
+ string "}",
+ newline,
+ newline]);
+
+ box [string "unurlify_",
+ string (Int.toString i),
+ string ("(ctx, &" ^ request ^ ")")]
+ end
+
+ | TDatatype (Default, i, _) =>
+ if IS.member (!unurlifies, i) then
+ box [string "unurlify_",
+ string (Int.toString i),
+ string ("(ctx, " ^ deStar request ^ ")")]
+ else
+ let
+ val (x, xncs) = E.lookupDatatype env i
+
+ val () = unurlifies := IS.add (!unurlifies, i)
+
+ fun doEm xncs =
+ case xncs of
+ [] => string ("(uw_error(ctx, FATAL, \"Error unurlifying datatype "
+ ^ x ^ "\"), NULL)")
+ | (x', n, to) :: rest =>
+ box [string "((!strncmp(*request, \"",
+ string x',
+ string "\", ",
+ string (Int.toString (size x')),
+ string ") && ((*request)[",
+ string (Int.toString (size x')),
+ string "] == 0 || (*request)[",
+ string (Int.toString (size x')),
+ string "] == '/')) ? ({",
+ newline,
+ string "struct",
+ space,
+ string ("__uwd_" ^ ident x ^ "_" ^ Int.toString i),
+ space,
+ string "*tmp = uw_malloc(ctx, sizeof(struct __uwd_",
+ string x,
+ string "_",
+ string (Int.toString i),
+ string "));",
+ newline,
+ string "tmp->tag",
+ space,
+ string "=",
+ space,
+ string ("__uwc_" ^ ident x' ^ "_" ^ Int.toString n),
+ string ";",
+ newline,
+ string "*request",
+ space,
+ string "+=",
+ space,
+ string (Int.toString (size x')),
+ string ";",
+ newline,
+ string "if ((*request)[0] == '/') ++*request;",
+ newline,
+ case to of
+ NONE => box []
+ | SOME (t, _) => box [string "tmp->data.uw_",
+ p_ident x',
+ space,
+ string "=",
+ space,
+ unurlify' "(*request)" t,
+ string ";",
+ newline],
+ string "tmp;",
+ newline,
+ string "})",
+ space,
+ string ":",
+ space,
+ doEm rest,
+ string ")"]
+ in
+ addUrlHandler (box [string "static",
+ space,
+ p_typ env (t, ErrorMsg.dummySpan),
+ space,
+ string "unurlify_",
+ string (Int.toString i),
+ string "(uw_context, char **);",
+ newline],
+ box [string "static",
+ space,
+ p_typ env (t, ErrorMsg.dummySpan),
+ space,
+ string "unurlify_",
+ string (Int.toString i),
+ string "(uw_context ctx, char **request) {",
+ newline,
+ box [string "return",
+ space,
+ doEm xncs,
+ string ";",
+ newline],
+ string "}",
+ newline,
+ newline]);
+
+ box [string "unurlify_",
+ string (Int.toString i),
+ string ("(ctx, " ^ deStar request ^ ")")]
+ end
+
+ | TList (t', i) =>
+ if IS.member (!unurlifies, i) then
+ box [string "unurlify_list_",
+ string (Int.toString i),
+ string ("(ctx, " ^ deStar request ^ ")")]
+ else
+ (unurlifies := IS.add (!unurlifies, i);
+ addUrlHandler (box [string "static",
+ space,
+ p_typ env (t, loc),
+ space,
+ string "unurlify_list_",
+ string (Int.toString i),
+ string "(uw_context, char **);",
+ newline],
+ box [string "static",
+ space,
+ p_typ env (t, loc),
+ space,
+ string "unurlify_list_",
+ string (Int.toString i),
+ string "(uw_context ctx, char **request) {",
+ newline,
+ box [string "return ((*request)[0] == '/' ? ++*request : *request,",
+ newline,
+ string "((!strncmp(*request, \"Nil\", 3) && ((*request)[3] == 0 ",
+ string "|| (*request)[3] == '/')) ? (*request",
+ space,
+ string "+=",
+ space,
+ string "3, ((*request)[0] == '/' ? ((*request)[0] = 0, ++*request) : NULL), NULL) : ((!strncmp(*request, \"Cons\", 4) && ((*request)[4] == 0 ",
+ string "|| (*request)[4] == '/')) ? (*request",
+ space,
+ string "+=",
+ space,
+ string "4, ((*request)[0] == '/' ? ++*request : NULL), ",
+ newline,
+
+ string "({",
+ newline,
+ p_typ env (t, loc),
+ space,
+ string "tmp",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(struct __uws_",
+ string (Int.toString i),
+ string "));",
+ newline,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ unurlify' "(*request)" (TRecord i),
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})",
+ string ")",
+ newline,
+ string ":",
+ space,
+ string ("(uw_error(ctx, FATAL, \"Error unurlifying list: %s\", request), NULL))));"),
+ newline],
+ string "}",
+ newline,
+ newline]);
+
+ box [string "unurlify_list_",
+ string (Int.toString i),
+ string ("(ctx, " ^ deStar request ^ ")")])
+
+ | TOption t =>
+ box [string ("(" ^ request ^ "[0] == '/' ? ++" ^ request ^ " : " ^ request ^ ", "),
+ string ("((!strncmp(" ^ request ^ ", \"None\", 4) "),
+ string ("&& (" ^ request ^ "[4] == 0 || " ^ request ^ "[4] == '/')) "),
+ string ("? (" ^ request ^ " += (" ^ request ^ "[4] == 0 ? 4 : 5), NULL) "),
+ string (": ((!strncmp(" ^ request ^ ", \"Some\", 4) "),
+ string ("&& " ^ request ^ "[4] == '/') "),
+ string ("? (" ^ request ^ " += 5, "),
+ if isUnboxable t then
+ unurlify' request (#1 t)
+ else
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ unurlify' request (#1 t),
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string ") :",
+ space,
+ string "(uw_error(ctx, FATAL, \"Error unurlifying option type\"), NULL))))"]
+
+ | _ => (ErrorMsg.errorAt loc "Unable to choose a URL decoding function";
+ space)
+ in
+ unurlify' "request" t
+ end
+
+val urlify1 = ref 0
+
+val urlifies = ref IS.empty
+val urlifiesL = ref IS.empty
+
+fun urlify env t =
+ let
+ fun urlify' level (t as (_, loc)) =
+ case #1 t of
+ TFfi ("Basis", "unit") => box []
+ | TFfi (m, t) => box [string ("uw_" ^ ident m ^ "_urlify" ^ capitalize t
+ ^ "_w(ctx, it" ^ Int.toString level ^ ");"),
+ newline]
+
+ | TRecord 0 => box []
+ | TRecord i =>
+ let
+ fun empty (t, _) =
+ case t of
+ TFfi ("Basis", "unit") => true
+ | TRecord 0 => true
+ | TRecord j =>
+ List.all (fn (_, t) => empty t) (E.lookupStruct env j)
+ | _ => false
+
+ val xts = E.lookupStruct env i
+
+ val (blocks, _) = foldl
+ (fn ((x, t), (blocks, printingSinceLastSlash)) =>
+ let
+ val thisEmpty = empty t
+ in
+ if thisEmpty then
+ (blocks, printingSinceLastSlash)
+ else
+ (box [string "{",
+ newline,
+ p_typ env t,
+ space,
+ string ("it" ^ Int.toString (level + 1)),
+ space,
+ string "=",
+ space,
+ string ("it" ^ Int.toString level ^ ".__uwf_" ^ x ^ ";"),
+ newline,
+ box (if printingSinceLastSlash then
+ [string "uw_write(ctx, \"/\");",
+ newline]
+ else
+ []),
+ urlify' (level + 1) t,
+ string "}",
+ newline] :: blocks,
+ true)
+ end)
+ ([], false) xts
+ in
+ box (rev blocks)
+ end
+
+ | TDatatype (Enum, i, _) =>
+ let
+ val (x, xncs) = E.lookupDatatype env i
+
+ fun doEm xncs =
+ case xncs of
+ [] => box [string ("uw_error(ctx, FATAL, \"Error urlifying datatype "
+ ^ x ^ "\");"),
+ newline]
+ | (x', n, to) :: rest =>
+ box [string ("if (it" ^ Int.toString level
+ ^ "==__uwc_" ^ ident x' ^ "_" ^ Int.toString n ^ ") {"),
+ newline,
+ box [string ("uw_write(ctx, \"" ^ x' ^ "\");"),
+ newline],
+ string "} else {",
+ newline,
+ box [doEm rest,
+ newline],
+ string "}"]
+ in
+ doEm xncs
+ end
+
+ | TDatatype (Option, i, xncs) =>
+ if IS.member (!urlifies, i) then
+ box [string "urlify_",
+ string (Int.toString i),
+ string "(ctx,",
+ space,
+ string "it",
+ string (Int.toString level),
+ string ");",
+ newline]
+ else
+ let
+ val (x, _) = E.lookupDatatype env i
+
+ val (no_arg, has_arg, t) =
+ case !xncs of
+ [(no_arg, _, NONE), (has_arg, _, SOME t)] =>
+ (no_arg, has_arg, t)
+ | [(has_arg, _, SOME t), (no_arg, _, NONE)] =>
+ (no_arg, has_arg, t)
+ | _ => raise Fail "CjrPrint: urlify misclassified Option datatype"
+ in
+ urlifies := IS.add (!urlifies, i);
+ addUrlHandler (box [string "static",
+ space,
+ string "void",
+ space,
+ string "urlify_",
+ string (Int.toString i),
+ string "(uw_context,",
+ space,
+ p_typ env t,
+ space,
+ if isUnboxable t then
+ box []
+ else
+ string "*",
+ string ");",
+ newline],
+ box [string "static",
+ space,
+ string "void",
+ space,
+ string "urlify_",
+ string (Int.toString i),
+ string "(uw_context ctx,",
+ space,
+ p_typ env t,
+ space,
+ if isUnboxable t then
+ box []
+ else
+ string "*",
+ string "it0) {",
+ newline,
+ box [string "if (it0) {",
+ newline,
+ if isUnboxable t then
+ box [string "uw_write(ctx, \"",
+ string has_arg,
+ string "/\");",
+ newline,
+ urlify' 0 t,
+ string ";",
+ newline]
+ else
+ box [p_typ env t,
+ space,
+ string "it1",
+ space,
+ string "=",
+ space,
+ string "*it0;",
+ newline,
+ string "uw_write(ctx, \"",
+ string has_arg,
+ string "/\");",
+ newline,
+ urlify' 1 t,
+ string ";",
+ newline],
+ string "} else {",
+ box [newline,
+ string "uw_write(ctx, \"",
+ string no_arg,
+ string "\");",
+ newline],
+ string "}",
+ newline],
+ string "}",
+ newline,
+ newline]);
+
+ box [string "urlify_",
+ string (Int.toString i),
+ string "(ctx,",
+ space,
+ string "it",
+ string (Int.toString level),
+ string ");",
+ newline]
+ end
+
+ | TDatatype (Default, i, _) =>
+ if IS.member (!urlifies, i) then
+ box [string "urlify_",
+ string (Int.toString i),
+ string "(ctx,",
+ space,
+ string "it",
+ string (Int.toString level),
+ string ");",
+ newline]
+ else
+ let
+ val (x, xncs) = E.lookupDatatype env i
+
+ val () = urlifies := IS.add (!urlifies, i)
+
+ fun doEm xncs =
+ case xncs of
+ [] => box [string ("uw_error(ctx, FATAL, \"Error urlifying datatype "
+ ^ x ^ " (%d)\", it0->data);"),
+ newline]
+ | (x', n, to) :: rest =>
+ box [string "if",
+ space,
+ string "(it0->tag==__uwc_",
+ string (ident x'),
+ string "_",
+ string (Int.toString n),
+ string ") {",
+ newline,
+ case to of
+ NONE => box [string "uw_write(ctx, \"",
+ string x',
+ string "\");",
+ newline]
+ | SOME t => box [string "uw_write(ctx, \"",
+ string x',
+ string "/\");",
+ newline,
+ p_typ env t,
+ space,
+ string "it1",
+ space,
+ string "=",
+ space,
+ string "it0->data.uw_",
+ string x',
+ string ";",
+ newline,
+ urlify' 1 t,
+ newline],
+ string "} else {",
+ newline,
+ box [doEm rest,
+ newline],
+ string "}",
+ newline]
+ in
+ addUrlHandler (box [string "static",
+ space,
+ string "void",
+ space,
+ string "urlify_",
+ string (Int.toString i),
+ string "(uw_context,",
+ space,
+ p_typ env t,
+ string ");",
+ newline],
+ box [string "static",
+ space,
+ string "void",
+ space,
+ string "urlify_",
+ string (Int.toString i),
+ string "(uw_context ctx,",
+ space,
+ p_typ env t,
+ space,
+ string "it0) {",
+ newline,
+ box [doEm xncs,
+ newline],
+ newline,
+ string "}",
+ newline,
+ newline]);
+
+ box [string "urlify_",
+ string (Int.toString i),
+ string "(ctx,",
+ space,
+ string "it",
+ string (Int.toString level),
+ string ");",
+ newline]
+ end
+
+ | TOption t =>
+ box [string "if (it",
+ string (Int.toString level),
+ string ") {",
+ if isUnboxable t then
+ box [string "uw_write(ctx, \"Some/\");",
+ newline,
+ urlify' level t]
+ else
+ box [p_typ env t,
+ space,
+ string "it",
+ string (Int.toString (level + 1)),
+ space,
+ string "=",
+ space,
+ string "*it",
+ string (Int.toString level),
+ string ";",
+ newline,
+ string "uw_write(ctx, \"Some/\");",
+ newline,
+ urlify' (level + 1) t,
+ string ";",
+ newline],
+ string "} else {",
+ box [newline,
+ string "uw_write(ctx, \"None\");",
+ newline],
+ string "}",
+ newline]
+
+ | TList (t, i) =>
+ if IS.member (!urlifiesL, i) then
+ box [string "urlifyl_",
+ string (Int.toString i),
+ string "(ctx,",
+ space,
+ string "it",
+ string (Int.toString level),
+ string ");",
+ newline]
+ else
+ (urlifiesL := IS.add (!urlifiesL, i);
+ addUrlHandler (box [string "static",
+ space,
+ string "void",
+ space,
+ string "urlifyl_",
+ string (Int.toString i),
+ string "(uw_context,",
+ space,
+ string "struct __uws_",
+ string (Int.toString i),
+ space,
+ string "*);",
+ newline],
+ box [string "static",
+ space,
+ string "void",
+ space,
+ string "urlifyl_",
+ string (Int.toString i),
+ string "(uw_context ctx,",
+ space,
+ string "struct __uws_",
+ string (Int.toString i),
+ space,
+ string "*it0) {",
+ newline,
+ box [string "if (it0) {",
+ newline,
+ p_typ env t,
+ space,
+ string "it1",
+ space,
+ string "=",
+ space,
+ string "it0->__uwf_1;",
+ newline,
+ string "uw_write(ctx, \"Cons/\");",
+ newline,
+ urlify' 1 t,
+ string ";",
+ newline,
+ string "uw_write(ctx, \"/\");",
+ newline,
+ string "urlifyl_",
+ string (Int.toString i),
+ string "(ctx, it0->__uwf_2);",
+ newline,
+ string "} else {",
+ newline,
+ box [string "uw_write(ctx, \"Nil\");",
+ newline],
+ string "}",
+ newline],
+ string "}",
+ newline,
+ newline]);
+
+ box [string "urlifyl_",
+ string (Int.toString i),
+ string "(ctx,",
+ space,
+ string "it",
+ string (Int.toString level),
+ string ");",
+ newline])
+
+ | _ => (ErrorMsg.errorAt loc "Unable to choose a URL encoding function";
+ space)
+ in
+ urlify' 0 t
+ end
+
+fun sql_type_in env (tAll as (t, loc)) =
+ case t of
+ TFfi ("Basis", "int") => Int
+ | TFfi ("Basis", "float") => Float
+ | TFfi ("Basis", "string") => String
+ | TFfi ("Basis", "char") => Char
+ | TFfi ("Basis", "bool") => Bool
+ | TFfi ("Basis", "time") => Time
+ | TFfi ("Basis", "blob") => Blob
+ | TFfi ("Basis", "channel") => Channel
+ | TFfi ("Basis", "client") => Client
+ | TOption t' => Nullable (sql_type_in env t')
+ | _ => (ErrorMsg.errorAt loc "Don't know SQL equivalent of type";
+ Print.eprefaces' [("Type", p_htyp env tAll)];
+ Int)
+
+fun potentiallyFancy (e, _) =
+ case e of
+ EPrim _ => false
+ | ERel _ => false
+ | ENamed _ => false
+ | ECon (_, _, NONE) => false
+ | ECon (_, _, SOME e) => potentiallyFancy e
+ | ENone _ => false
+ | ESome (_, e) => potentiallyFancy e
+ | EFfi _ => false
+ | EFfiApp _ => true
+ | EApp _ => true
+ | EUnop (_, e) => potentiallyFancy e
+ | EBinop (_, e1, e2) => potentiallyFancy e1 orelse potentiallyFancy e2
+ | ERecord (_, xes) => List.exists (potentiallyFancy o #2) xes
+ | EField (e, _) => potentiallyFancy e
+ | ECase (e, pes, _) => potentiallyFancy e orelse List.exists (potentiallyFancy o #2) pes
+ | EError _ => false
+ | EReturnBlob _ => false
+ | ERedirect _ => false
+ | EWrite e => potentiallyFancy e
+ | ESeq (e1, e2) => potentiallyFancy e1 orelse potentiallyFancy e2
+ | ELet (_, _, e1, e2) => potentiallyFancy e1 orelse potentiallyFancy e2
+ | EQuery _ => true
+ | EDml {dml = e, ...} => potentiallyFancy e
+ | ENextval {seq = e, ...} => potentiallyFancy e
+ | ESetval {seq = e1, count = e2} => potentiallyFancy e1 orelse potentiallyFancy e2
+ | EUnurlify _ => true
+
+val self = ref (NONE : int option)
+
+(* The crucial thing to do here is assign arguments to local variables, to enforce order of evaluation.
+ * Otherwise, we are at the mercy of C's undefined order of function argument evaluation. *)
+fun pFuncall env (m, x, es, extra) =
+ case es of
+ [] => box [string "uw_",
+ p_ident m,
+ string "_",
+ p_ident x,
+ string "(ctx",
+ case extra of
+ NONE => box []
+ | SOME extra => box [string ",",
+ space,
+ string extra],
+ string ")"]
+ | [(e, _)] => box [string "uw_",
+ p_ident m,
+ string "_",
+ p_ident x,
+ string "(ctx,",
+ space,
+ p_exp' false false env e,
+ case extra of
+ NONE => box []
+ | SOME extra => box [string ",",
+ space,
+ string extra],
+ string ")"]
+ | _ => box [string "({",
+ newline,
+ p_list_sepi (box []) (fn i => fn (e, t) =>
+ box [p_typ env t,
+ space,
+ string "arg",
+ string (Int.toString i),
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";",
+ newline]) es,
+ string "uw_",
+ p_ident m,
+ string "_",
+ p_ident x,
+ string "(ctx, ",
+ p_list_sepi (box [string ",", space]) (fn i => fn _ => box [string "arg", string (Int.toString i)]) es,
+ case extra of
+ NONE => box []
+ | SOME extra => box [string ",",
+ space,
+ string extra],
+ string ");",
+ newline,
+ string "})"]
+
+and p_exp' par tail env (e, loc) =
+ case e of
+ EPrim p => Prim.p_t_GCC p
+ | ERel n => p_rel env n
+ | ENamed n => p_enamed env n
+ | ECon (Enum, pc, _) => p_patCon env pc
+ | ECon (Option, pc, NONE) => string "NULL"
+ | ECon (Option, pc, SOME e) =>
+ let
+ val to = case pc of
+ PConVar n => #2 (E.lookupConstructor env n)
+ | PConFfi {arg, ...} => arg
+
+ val t = case to of
+ NONE => raise Fail "CjrPrint: ECon argument status mismatch"
+ | SOME t => t
+ in
+ if isUnboxable t then
+ p_exp' par tail env e
+ else
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ p_exp' par false env e,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ end
+ | ECon (Default, pc, eo) =>
+ let
+ val (xd, xc, xn) = patConInfo env pc
+ in
+ box [string "({",
+ newline,
+ string "struct",
+ space,
+ string xd,
+ space,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(struct ",
+ string xd,
+ string "));",
+ newline,
+ string "tmp->tag",
+ space,
+ string "=",
+ space,
+ string xc,
+ string ";",
+ newline,
+ case eo of
+ NONE => box []
+ | SOME e => box [string "tmp->data.",
+ string xn,
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";",
+ newline],
+ string "tmp;",
+ newline,
+ string "})"]
+ end
+ | ENone _ => string "NULL"
+ | ESome (t, e) =>
+ if isUnboxable t then
+ p_exp' par tail env e
+ else
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp",
+ space,
+ string "=",
+ space,
+ p_exp' par false env e,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+
+ | EFfi (m, x) => box [string "uw_", p_ident m, string "_", p_ident x]
+ | EError (e, t) =>
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "tmp;",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (if Settings.getDebug () then
+ ErrorMsg.spanToString loc ^ ": "
+ else
+ ""),
+ string "%s\", ",
+ p_exp' false false env e,
+ string ");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ | EReturnBlob {blob = SOME blob, mimeType, t} =>
+ box [string "({",
+ newline,
+ string "uw_Basis_blob",
+ space,
+ string "blob",
+ space,
+ string "=",
+ space,
+ p_exp' false false env blob,
+ string ";",
+ newline,
+ string "uw_Basis_string",
+ space,
+ string "mimeType",
+ space,
+ string "=",
+ space,
+ p_exp' false false env mimeType,
+ string ";",
+ newline,
+ p_typ env t,
+ space,
+ string "tmp;",
+ newline,
+ string "uw_return_blob(ctx, blob, mimeType);",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ | EReturnBlob {blob = NONE, mimeType, t} =>
+ box [string "({",
+ newline,
+ string "uw_Basis_string",
+ space,
+ string "mimeType",
+ space,
+ string "=",
+ space,
+ p_exp' false false env mimeType,
+ string ";",
+ newline,
+ p_typ env t,
+ space,
+ string "tmp;",
+ newline,
+ string "uw_return_blob_from_page(ctx, mimeType);",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ | ERedirect (e, t) =>
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "tmp;",
+ newline,
+ string "uw_redirect(ctx, ",
+ p_exp' false false env e,
+ string ");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ | EApp ((EError (e, (TFun (_, ran), _)), loc), _) =>
+ p_exp' false false env (EError (e, ran), loc)
+ | EApp ((EReturnBlob {blob, mimeType, t = (TFun (_, ran), _)}, loc), _) =>
+ p_exp' false false env (EReturnBlob {blob = blob, mimeType = mimeType, t = ran}, loc)
+
+ | EFfiApp ("Basis", "strcat", [(e1, _), (e2, _)]) =>
+ let
+ fun flatten e =
+ case #1 e of
+ EFfiApp ("Basis", "strcat", [(e1, _), (e2, _)]) => flatten e1 @ flatten e2
+ | _ => [e]
+
+ val es = flatten e1 @ flatten e2
+ val t = (TFfi ("Basis", "string"), loc)
+ val es = map (fn e => (e, t)) es
+ in
+ case es of
+ [_, _] => pFuncall env ("Basis", "strcat", es, NONE)
+ | _ => pFuncall env ("Basis", "mstrcat", es, SOME "NULL")
+ end
+
+ | EFfiApp (m, x, es) => pFuncall env (m, x, es, NONE)
+ | EApp (f, args) =>
+ let
+ fun getSig n =
+ let
+ val (_, t) = E.lookupENamed env n
+
+ fun getSig (t, args) =
+ case #1 t of
+ TFun (dom, t) => getSig (t, dom :: args)
+ | _ => (args, t)
+ in
+ getSig (t, [])
+ end
+
+ fun default () =
+ case (#1 f, args) of
+ (ENamed n, _ :: _ :: _) =>
+ let
+ val (args', ret) = getSig n
+ val args = ListPair.zip (args, args')
+ in
+ parenIf par (box [string "({",
+ newline,
+ p_list_sepi newline
+ (fn i => fn (e, t) =>
+ box [p_typ env t,
+ space,
+ string ("arg" ^ Int.toString i),
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";"])
+ args,
+ newline,
+ p_exp' false false env f,
+ string "(ctx,",
+ space,
+ p_list_sepi (box [string ",", space])
+ (fn i => fn _ =>
+ string ("arg" ^ Int.toString i)) args,
+ string ");",
+ newline,
+ string "})"])
+ end
+ | _ =>
+ parenIf par (box [p_exp' true false env f,
+ string "(ctx,",
+ space,
+ p_list_sep (box [string ",", space]) (p_exp' false false env) args,
+ string ")"])
+
+ fun isSelf n =
+ let
+ val (argts, ret) = getSig n
+ in
+ parenIf par (box [string "({",
+ newline,
+ p_list_sepi newline
+ (fn i => fn (e, t) =>
+ box [p_typ env t,
+ space,
+ string ("rearg" ^ Int.toString i),
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";"])
+ (ListPair.zip (args, argts)),
+ newline,
+ p_typ env ret,
+ space,
+ string "tmp;",
+ newline,
+ p_list_sepi newline
+ (fn i => fn _ =>
+ box [p_rel env (E.countERels env - 1 - i),
+ space,
+ string "=",
+ space,
+ string ("rearg" ^ Int.toString i ^ ";")]) args,
+ newline,
+ string "goto restart;",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"])
+ end
+ in
+ case #1 f of
+ ENamed n => if SOME n = !self andalso tail then
+ isSelf n
+ else
+ default ()
+ | _ => default ()
+ end
+
+ | EUnop (s, e1) =>
+ parenIf par (box [string s,
+ space,
+ p_exp' true false env e1])
+
+ | EBinop (s, e1, e2) =>
+ if s <> "fdiv" andalso Char.isAlpha (String.sub (s, size s - 1)) then
+ box [string s,
+ string "(",
+ p_exp' false false env e1,
+ string ",",
+ space,
+ p_exp' false false env e2,
+ string ")"]
+ else if s = "/" orelse s = "%" then
+ box [string "({",
+ newline,
+ string "uw_Basis_int",
+ space,
+ string "dividend",
+ space,
+ string "=",
+ space,
+ p_exp env e1,
+ string ",",
+ space,
+ string "divisor",
+ space,
+ string "=",
+ space,
+ p_exp env e2,
+ string ";",
+ newline,
+ string "if",
+ space,
+ string "(divisor",
+ space,
+ string "==",
+ space,
+ string "0)",
+ newline,
+ box [string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": division by zero\");",
+ newline],
+ string "dividend",
+ space,
+ string s,
+ space,
+ string "divisor;",
+ newline,
+ string "})"]
+ else
+ parenIf par (box [p_exp' true false env e1,
+ space,
+ string (if s = "fdiv" then "/" else s),
+ space,
+ p_exp' true false env e2])
+
+ | ERecord (0, _) => string "0"
+
+ | ERecord (i, xes) => box [string "({",
+ space,
+ string "struct",
+ space,
+ string ("__uws_" ^ Int.toString i),
+ space,
+ string "tmp",
+ space,
+ string "=",
+ space,
+ string "{",
+ p_list (fn (_, e) =>
+ p_exp' false false env e) xes,
+ string "};",
+ space,
+ string "tmp;",
+ space,
+ string "})" ]
+ | EField (e, x) =>
+ box [p_exp' true false env e,
+ string ".__uwf_",
+ p_ident x]
+
+ | ECase (e, pes, {disc, result}) =>
+ box [string "({",
+ newline,
+ p_typ env disc,
+ space,
+ string "disc",
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";",
+ newline,
+ newline,
+ foldr (fn ((p, e), body) =>
+ let
+ val pm = p_patMatch (env, "disc") p
+ val (pb, env') = p_patBind (env, "disc") p
+ in
+ box [pm,
+ space,
+ string "?",
+ space,
+ if E.countERels env' = E.countERels env then
+ p_exp' false tail env e
+ else
+ box [string "({",
+ pb,
+ p_exp' false tail env' e,
+ string ";",
+ newline,
+ string "})"],
+ newline,
+ space,
+ string ":",
+ space,
+ body]
+ end) (box [string "({",
+ newline,
+ p_typ env result,
+ space,
+ string "tmp;",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": pattern match failure\");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]) pes,
+ string ";",
+ newline,
+ string "})"]
+
+ | EWrite e => box [string "(uw_write(ctx, ",
+ p_exp' false false env e,
+ string "), 0)"]
+
+ | ESeq (e1, e2) =>
+ let
+ val useRegion = potentiallyFancy e1
+ in
+ box [string "(",
+ if useRegion then
+ box [string "uw_begin_region(ctx),",
+ space]
+ else
+ box [],
+ p_exp' false false env e1,
+ string ",",
+ space,
+ if useRegion then
+ box [string "uw_end_region(ctx),",
+ space]
+ else
+ box [],
+ p_exp' false tail env e2,
+ string ")"]
+ end
+ | ELet (x, t, e1, e2) =>
+ let
+ val useRegion = notLeaky env false t andalso potentiallyFancy e1
+ in
+ box [string "({",
+ newline,
+ p_typ env t,
+ space,
+ string "__uwr_",
+ p_ident x,
+ string "_",
+ string (Int.toString (E.countERels env)),
+ space,
+ string "=",
+ space,
+ if useRegion then
+ box [string "(uw_begin_region(ctx),",
+ space]
+ else
+ box [],
+ p_exp' false false env e1,
+ if useRegion then
+ string ")"
+ else
+ box [],
+ string ";",
+ newline,
+ if useRegion then
+ box [string "uw_end_region(ctx);",
+ newline]
+ else
+ box [],
+ p_exp' false tail (E.pushERel env x t) e2,
+ string ";",
+ newline,
+ string "})"]
+ end
+
+ | EQuery {exps, tables, rnum, state, query, body, initial, prepared} =>
+ let
+ val exps = map (fn (x, t) => ("__uwf_" ^ ident x, t)) exps
+ val tables = ListUtil.mapConcat (fn (x, xts) =>
+ map (fn (x', t) => ("__uwf_" ^ ident x ^ ".__uwf_" ^ ident x', t)) xts)
+ tables
+
+ val sort = ListMergeSort.sort (fn ((s1, _), (s2, _)) => String.compare (s1, s2) = GREATER)
+ val outputs = sort exps @ sort tables
+
+ val wontLeakStrings = notLeaky env true state
+ val wontLeakAnything = notLeaky env false state
+
+ val inputs =
+ case prepared of
+ NONE => []
+ | SOME _ => getPargs query
+
+ fun doCols p_getcol =
+ box [string "struct __uws_",
+ string (Int.toString rnum),
+ string " __uwr_r_",
+ string (Int.toString (E.countERels env)),
+ string ";",
+ newline,
+ p_typ env state,
+ space,
+ string "__uwr_acc_",
+ string (Int.toString (E.countERels env + 1)),
+ space,
+ string "=",
+ space,
+ string "acc;",
+ newline,
+ newline,
+
+ if Settings.getDeadlines () then
+ box [string "uw_check_deadline(ctx);",
+ newline]
+ else
+ box [],
+
+ p_list_sepi (box []) (fn i =>
+ fn (proj, t) =>
+ box [string "__uwr_r_",
+ string (Int.toString (E.countERels env)),
+ string ".",
+ string proj,
+ space,
+ string "=",
+ space,
+ p_getcol {loc = loc,
+ wontLeakStrings = wontLeakStrings,
+ col = i,
+ typ = sql_type_in env t},
+ string ";",
+ newline]) outputs,
+ newline,
+ newline,
+
+ string "acc",
+ space,
+ string "=",
+ space,
+ p_exp' false false (E.pushERel
+ (E.pushERel env "r" (TRecord rnum, loc))
+ "acc" state)
+ body,
+ string ";",
+ newline]
+ in
+ box [if wontLeakAnything then
+ string "(uw_begin_region(ctx), "
+ else
+ box [],
+ string "({",
+ newline,
+ p_typ env state,
+ space,
+ string "acc",
+ space,
+ string "=",
+ space,
+ p_exp' false false env initial,
+ string ";",
+ newline,
+ string "int dummy = (uw_begin_region(ctx), 0);",
+ newline,
+ string "uw_ensure_transaction(ctx);",
+ newline,
+
+ case prepared of
+ NONE =>
+ box [string "char *query = ",
+ p_exp' false false env query,
+ string ";",
+ newline,
+ newline,
+
+ #query (Settings.currentDbms ())
+ {loc = loc,
+ cols = map (fn (_, t) => sql_type_in env t) outputs,
+ doCols = doCols}]
+ | SOME {id, query, nested} =>
+ box [p_list_sepi newline
+ (fn i => fn (e, t) =>
+ box [p_sql_type t,
+ space,
+ string "arg",
+ string (Int.toString (i + 1)),
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";"])
+ inputs,
+ newline,
+ newline,
+
+ #queryPrepared (Settings.currentDbms ())
+ {loc = loc,
+ id = id,
+ query = query,
+ inputs = map #2 inputs,
+ cols = map (fn (_, t) => sql_type_in env t) outputs,
+ doCols = doCols,
+ nested = nested}],
+ newline,
+
+ if wontLeakAnything then
+ box [string "uw_end_region(ctx);",
+ newline]
+ else
+ box [],
+ string "acc;",
+ newline,
+ string "})",
+ if wontLeakAnything then
+ string ")"
+ else
+ box []]
+ end
+
+ | EDml {dml, prepared, mode} =>
+ box [string "(uw_begin_region(ctx), ({",
+ newline,
+ case prepared of
+ NONE => box [string "char *dml = ",
+ p_exp' false false env dml,
+ string ";",
+ newline,
+ string "uw_ensure_transaction(ctx);",
+ newline,
+ newline,
+ #dml (Settings.currentDbms ()) (loc, mode)]
+ | SOME {id, dml = dml'} =>
+ let
+ val inputs = getPargs dml
+ in
+ box [p_list_sepi newline
+ (fn i => fn (e, t) =>
+ box [p_sql_type t,
+ space,
+ string "arg",
+ string (Int.toString (i + 1)),
+ space,
+ string "=",
+ space,
+ p_exp' false false env e,
+ string ";"])
+ inputs,
+ newline,
+ string "uw_ensure_transaction(ctx);",
+ newline,
+ newline,
+
+ #dmlPrepared (Settings.currentDbms ()) {loc = loc,
+ id = id,
+ dml = dml',
+ inputs = map #2 inputs,
+ mode = mode}]
+ end,
+ newline,
+ newline,
+ string "uw_end_region(ctx);",
+ newline,
+
+ case mode of
+ Settings.Error => string "0;"
+ | Settings.None => string "uw_dup_and_clear_error_message(ctx);",
+
+ newline,
+ string "}))"]
+
+ | ENextval {seq, prepared} =>
+ box [string "({",
+ newline,
+ string "uw_Basis_int n;",
+ newline,
+ string "uw_ensure_transaction(ctx);",
+ newline,
+
+ case prepared of
+ NONE => #nextval (Settings.currentDbms ()) {loc = loc,
+ seqE = p_exp' false false env seq,
+ seqName = case #1 seq of
+ EPrim (Prim.String (_, s)) => SOME s
+ | _ => NONE}
+ | SOME {id, query} => #nextvalPrepared (Settings.currentDbms ()) {loc = loc,
+ id = id,
+ query = query},
+ newline,
+ newline,
+
+ string "n;",
+ newline,
+ string "})"]
+
+ | ESetval {seq, count} =>
+ box [string "({",
+ newline,
+ string "uw_ensure_transaction(ctx);",
+ newline,
+
+ #setval (Settings.currentDbms ()) {loc = loc,
+ seqE = p_exp' false false env seq,
+ count = p_exp' false false env count},
+ newline,
+ newline,
+
+ string "0;",
+ newline,
+ string "})"]
+
+ | EUnurlify (e, t, true) =>
+ let
+ fun getIt () =
+ if isUnboxable t then
+ unurlify false env t
+ else
+ box [string "({",
+ newline,
+ p_typ env t,
+ string " *tmp = uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp = ",
+ unurlify false env t,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ in
+ box [string "({",
+ newline,
+ string "uw_Basis_string request = uw_maybe_strdup(ctx, ",
+ p_exp' false false env e,
+ string ");",
+ newline,
+ newline,
+ string "(request ? ",
+ getIt (),
+ string " : NULL);",
+ newline,
+ string "})"]
+ end
+
+ | EUnurlify (e, t, false) =>
+ let
+ fun getIt () =
+ if isUnboxable t then
+ unurlify false env t
+ else
+ box [string "({",
+ newline,
+ p_typ env t,
+ string " *tmp = uw_malloc(ctx, sizeof(",
+ p_typ env t,
+ string "));",
+ newline,
+ string "*tmp = ",
+ unurlify false env t,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"]
+ in
+ box [string "({",
+ newline,
+ string "uw_Basis_string request = uw_maybe_strdup(ctx, ",
+ p_exp' false false env e,
+ string ");",
+ newline,
+ newline,
+ unurlify false env t,
+ string ";",
+ newline,
+ string "})"]
+ end
+
+and p_exp env = p_exp' false true env
+
+fun p_fun isRec env (fx, n, args, ran, e) =
+ let
+ val nargs = length args
+ val env' = foldl (fn ((x, dom), env) => E.pushERel env x dom) env args
+ in
+ box [string "static",
+ space,
+ p_typ env ran,
+ space,
+ string ("__uwn_" ^ ident fx ^ "_" ^ Int.toString n),
+ string "(",
+ p_list_sep (box [string ",", space]) (fn x => x)
+ (string "uw_context ctx" :: ListUtil.mapi (fn (i, (_, dom)) =>
+ box [p_typ env dom,
+ space,
+ p_rel env' (nargs - i - 1)]) args),
+ string ")",
+ space,
+ string "{",
+ if isRec then
+ box [string "restart:",
+ newline]
+ else
+ box [],
+ newline,
+ if isRec andalso Settings.getDeadlines () then
+ box [string "uw_check_deadline(ctx);",
+ newline]
+ else
+ box [],
+ box [string "return(",
+ p_exp env' e,
+ string ");"],
+ newline,
+ string "}"]
+ end
+
+val global_initializers : Print.PD.pp_desc list ref = ref []
+
+fun p_decl env (dAll as (d, loc) : decl) =
+ case d of
+ DStruct (n, xts) =>
+ let
+ val env = E.declBinds env dAll
+ in
+ box [string "struct",
+ space,
+ string ("__uws_" ^ Int.toString n),
+ space,
+ string "{",
+ newline,
+ p_list_sep (box []) (fn (x, t) => box [p_typ env t,
+ space,
+ string "__uwf_",
+ p_ident x,
+ string ";",
+ newline]) xts,
+ string "};"]
+ end
+ | DDatatype dts =>
+ let
+ fun p_one (Enum, x, n, xncs) =
+ box [string "enum",
+ space,
+ string ("__uwe_" ^ ident x ^ "_" ^ Int.toString n),
+ space,
+ string "{",
+ space,
+ case xncs of
+ [] => string ("__uwec_" ^ ident x ^ "_" ^ Int.toString n)
+ | _ =>
+ p_list_sep (box [string ",", space]) (fn (x, n, _) =>
+ string ("__uwc_" ^ ident x ^ "_" ^ Int.toString n)) xncs,
+ space,
+ string "};"]
+ | p_one (Option, _, _, _) = box []
+ | p_one (Default, x, n, xncs) =
+ let
+ val xncsArgs = List.mapPartial (fn (x, n, NONE) => NONE
+ | (x, n, SOME t) => SOME (x, n, t)) xncs
+ in
+ box [string "enum",
+ space,
+ string ("__uwe_" ^ ident x ^ "_" ^ Int.toString n),
+ space,
+ string "{",
+ space,
+ p_list_sep (box [string ",", space]) (fn (x, n, _) =>
+ string ("__uwc_" ^ ident x ^ "_" ^ Int.toString n))
+ xncs,
+ space,
+ string "};",
+ newline,
+ newline,
+ string "struct",
+ space,
+ string ("__uwd_" ^ ident x ^ "_" ^ Int.toString n),
+ space,
+ string "{",
+ newline,
+ string "enum",
+ space,
+ string ("__uwe_" ^ ident x ^ "_" ^ Int.toString n),
+ space,
+ string "tag;",
+ newline,
+ box (case xncsArgs of
+ [] => []
+ | _ => [string "union",
+ space,
+ string "{",
+ newline,
+ p_list_sep newline (fn (x, n, t) => box [p_typ env t,
+ space,
+ string ("uw_" ^ ident x),
+ string ";"]) xncsArgs,
+ newline,
+ string "}",
+ space,
+ string "data;",
+ newline]),
+ string "};"]
+ end
+ in
+ p_list_sep (box []) p_one dts
+ end
+
+ | DDatatypeForward _ => box []
+
+ | DVal (x, n, t, e) =>
+ (global_initializers := box [string ("__uwn_" ^ ident x ^ "_" ^ Int.toString n),
+ space,
+ string "=",
+ space,
+ p_exp env e,
+ string ";"] :: !global_initializers;
+ box [p_typ env t,
+ space,
+ string ("__uwn_" ^ ident x ^ "_" ^ Int.toString n ^ ";")])
+ | DFun vi => p_fun false env vi
+ | DFunRec vis =>
+ let
+ val env = E.declBinds env dAll
+ in
+ box [p_list_sep newline (fn (fx, n, args, ran, _) =>
+ box [string "static",
+ space,
+ p_typ env ran,
+ space,
+ string ("__uwn_" ^ ident fx ^ "_" ^ Int.toString n),
+ string "(uw_context,",
+ space,
+ p_list_sep (box [string ",", space])
+ (fn (_, dom) => p_typ env dom) args,
+ string ");"]) vis,
+ newline,
+ p_list_sep newline (fn vi as (_, n, _, _, _) =>
+ (self := SOME n;
+ p_fun true env vi
+ before self := NONE)) vis,
+ newline]
+ end
+ | DTable (x, _, pk, csts) => box [string "/* SQL table ",
+ string x,
+ space,
+ case pk of
+ "" => box []
+ | _ => box [string "keys",
+ space,
+ string pk,
+ space],
+ string "constraints",
+ space,
+ p_list (fn (x, v) => box [string x,
+ space,
+ string ":",
+ space,
+ string v]) csts,
+ space,
+ string " */",
+ newline]
+ | DSequence x => box [string "/* SQL sequence ",
+ string x,
+ string " */",
+ newline]
+ | DView (x, _, s) => box [string "/* SQL view ",
+ string x,
+ space,
+ string "AS",
+ space,
+ string s,
+ space,
+ string " */",
+ newline]
+ | DDatabase _ => box []
+ | DPreparedStatements _ => box []
+
+ | DJavaScript s =>
+ let
+ val name =
+ (case Settings.getOutputJsFile () of
+ NONE => "app." ^ SHA1.bintohex (SHA1.hash s) ^ ".js"
+ | SOME s => s)
+ val () = app_js := OS.Path.joinDirFile {dir = Settings.getUrlPrefix (),
+ file = name}
+ in
+ box [string "static char jslib[] = \"",
+ string (Prim.toCString s),
+ string "\";"]
+ end
+ | DCookie s => box [string "/*",
+ space,
+ string "cookie",
+ space,
+ string s,
+ space,
+ string "*/"]
+ | DStyle s => box [string "/*",
+ space,
+ string "style",
+ space,
+ string s,
+ space,
+ string "*/"]
+
+ | DTask _ => box []
+ | DOnError _ => box []
+
+datatype 'a search =
+ Found of 'a
+ | NotFound
+ | Error
+
+fun p_sqltype'' env (tAll as (t, loc)) =
+ case t of
+ TFfi ("Basis", "int") => "int8"
+ | TFfi ("Basis", "float") => "float8"
+ | TFfi ("Basis", "string") => "text"
+ | TFfi ("Basis", "bool") => "bool"
+ | TFfi ("Basis", "time") => "timestamp"
+ | TFfi ("Basis", "blob") => "bytea"
+ | TFfi ("Basis", "channel") => "int8"
+ | TFfi ("Basis", "client") => "int4"
+ | _ => (ErrorMsg.errorAt loc "Don't know SQL equivalent of type";
+ Print.eprefaces' [("Type", p_htyp env tAll)];
+ "ERROR")
+
+fun p_sqltype' env (tAll as (t, loc)) =
+ case t of
+ (TOption t, _) => p_sqltype'' env t
+ | _ => p_sqltype'' env t ^ " NOT NULL"
+
+fun p_sqltype env t = string (p_sqltype' env t)
+
+fun p_sqltype_base' env t =
+ case t of
+ (TOption t, _) => p_sqltype'' env t
+ | _ => p_sqltype'' env t
+
+fun p_sqltype_base env t = string (p_sqltype_base' env t)
+
+fun is_not_null t =
+ case t of
+ (TOption _, _) => false
+ | _ => true
+
+fun sigName fields =
+ let
+ fun inFields s = List.exists (fn (s', _) => s' = s) fields
+
+ fun getSigName n =
+ let
+ val s = "Sig" ^ Int.toString n
+ in
+ if inFields s then
+ getSigName (n + 1)
+ else
+ s
+ end
+ in
+ if inFields "Sig" then
+ getSigName 0
+ else
+ "Sig"
+ end
+
+fun p_file env (ds, ps) =
+ let
+ val () = (clearUrlHandlers ();
+ unurlifies := IS.empty;
+ urlifies := IS.empty;
+ urlifiesL := IS.empty;
+ self := NONE;
+ global_initializers := [])
+
+ (* First, pull out all of the enumerated types, to be declared first. *)
+ val (ds, enums) = ListUtil.foldlMapPartial (fn (d, enums) =>
+ case #1 d of
+ DDatatype dts =>
+ let
+ val (enum, other) = List.partition (fn (Enum, _, _, _) => true
+ | _ => false) dts
+ in
+ (SOME (DDatatype other, #2 d),
+ List.revAppend (enum, enums))
+ end
+ | DDatatypeForward (Enum, _, _) => (NONE, enums)
+ | _ => (SOME d, enums))
+ [] ds
+
+ val ds = (DDatatype enums, ErrorMsg.dummySpan) :: ds
+
+ val (pds, env) = ListUtil.foldlMap (fn (d, env) =>
+ let
+ val d' = p_decl env d
+ val hs = latestUrlHandlers ()
+ val (protos, defs) = ListPair.unzip hs
+ in
+ (box (List.revAppend (protos, (List.revAppend (defs, [d'])))),
+ E.declBinds env d)
+ end)
+ env ds
+
+ fun flatFields always (t : typ) =
+ case #1 t of
+ TRecord i =>
+ let
+ val xts = E.lookupStruct env i
+ in
+ SOME ((always @ map #1 xts) :: List.concat (List.mapPartial (flatFields [] o #2) xts))
+ end
+ | TList (_, i) =>
+ let
+ val ts = E.lookupStruct env i
+ in
+ case ts of
+ [("1", t'), ("2", _)] => flatFields [] t'
+ | _ => raise Fail "CjrPrint: Bad struct for TList"
+ end
+ | _ => NONE
+
+ val fields = foldl (fn ((ek, _, _, ts, _, _, _, _), fields) =>
+ case ek of
+ Action eff =>
+ (case List.nth (ts, length ts - 2) of
+ (TRecord i, loc) =>
+ let
+ val xts = E.lookupStruct env i
+ val extra = case eff of
+ ReadCookieWrite => [sigName xts]
+ | _ => []
+ in
+ case flatFields extra (TRecord i, loc) of
+ NONE => raise Fail "CjrPrint: flatFields impossible"
+ | SOME fields' => List.revAppend (fields', fields)
+ end
+ | _ => raise Fail "CjrPrint: Last argument of action isn't record")
+ | _ => fields)
+ [] ps
+
+ val fields = foldl (fn (xts, fields) =>
+ let
+ val xtsSet = SS.addList (SS.empty, xts)
+ in
+ foldl (fn (x, fields) =>
+ let
+ val xtsSet' = Option.getOpt (SM.find (fields, x), SS.empty)
+ in
+ SM.insert (fields, x, SS.union (SS.delete (xtsSet, x),
+ xtsSet'))
+ end) fields xts
+ end)
+ SM.empty fields
+
+ val fnums = SM.foldli (fn (x, xs, fnums) =>
+ let
+ val unusable = SS.foldl (fn (x', unusable) =>
+ case SM.find (fnums, x') of
+ NONE => unusable
+ | SOME n => IS.add (unusable, n))
+ IS.empty xs
+
+ fun findAvailable n =
+ if IS.member (unusable, n) then
+ findAvailable (n + 1)
+ else
+ n
+ in
+ SM.insert (fnums, x, findAvailable 0)
+ end)
+ SM.empty fields
+
+ val cookies = List.mapPartial (fn (DCookie s, _) => SOME s | _ => NONE) ds
+
+ fun makeSwitch (fnums, i) =
+ case SM.foldl (fn (n, NotFound) => Found n
+ | (n, Error) => Error
+ | (n, Found n') => if n = n' then
+ Found n'
+ else
+ Error) NotFound fnums of
+ NotFound => box [string "return",
+ space,
+ string "-1;"]
+ | Found n => box [string "return",
+ space,
+ string (Int.toString n),
+ string ";"]
+ | Error =>
+ let
+ val cmap = SM.foldli (fn (x, n, cmap) =>
+ let
+ val ch = if i < size x then
+ String.sub (x, i)
+ else
+ chr 0
+
+ val fnums = case CM.find (cmap, ch) of
+ NONE => SM.empty
+ | SOME fnums => fnums
+ val fnums = SM.insert (fnums, x, n)
+ in
+ CM.insert (cmap, ch, fnums)
+ end)
+ CM.empty fnums
+
+ val cmap = CM.listItemsi cmap
+ in
+ case cmap of
+ [(_, fnums)] =>
+ box [string "if",
+ space,
+ string "(name[",
+ string (Int.toString i),
+ string "]",
+ space,
+ string "==",
+ space,
+ string "0)",
+ space,
+ string "return",
+ space,
+ string "-1;",
+ newline,
+ makeSwitch (fnums, i+1)]
+ | _ =>
+ box [string "switch",
+ space,
+ string "(name[",
+ string (Int.toString i),
+ string "])",
+ space,
+ string "{",
+ newline,
+ box (map (fn (ch, fnums) =>
+ box [string "case",
+ space,
+ if ch = chr 0 then
+ string "0:"
+ else
+ box [string "'",
+ string (Char.toString ch),
+ string "':"],
+ newline,
+ makeSwitch (fnums, i+1),
+ newline]) cmap),
+ string "default:",
+ newline,
+ string "return",
+ space,
+ string "-1;",
+ newline,
+ string "}"]
+ end
+
+ fun getInput (x, t) =
+ let
+ val n = case SM.find (fnums, x) of
+ NONE => raise Fail ("CjrPrint: Can't find " ^ x ^ " in fnums")
+ | SOME n => n
+
+ val f = case t of
+ (TFfi ("Basis", "bool"), _) => "optional_"
+ | _ => ""
+ in
+ if isFile t then
+ box [string "uw_input_",
+ p_ident x,
+ space,
+ string "=",
+ space,
+ string "uw_get_file_input(ctx, ",
+ string (Int.toString n),
+ string ");",
+ newline]
+ else case #1 t of
+ TRecord i =>
+ let
+ val xts = E.lookupStruct env i
+ in
+ box [string "uw_enter_subform(ctx, ",
+ string (Int.toString n),
+ string ");",
+ newline,
+ string "uw_input_",
+ p_ident x,
+ space,
+ string "=",
+ space,
+ string "({",
+ box [p_typ env t,
+ space,
+ string "result;",
+ newline,
+ p_list_sep (box [])
+ (fn (x, t) =>
+ box [p_typ env t,
+ space,
+ string "uw_input_",
+ string x,
+ string ";",
+ newline])
+ xts,
+ newline,
+ p_list_sep (box []) (fn (x, t) =>
+ box [getInput (x, t),
+ string "result.__uwf_",
+ string x,
+ space,
+ string "=",
+ space,
+ string "uw_input_",
+ string x,
+ string ";",
+ newline])
+ xts,
+ newline,
+ string "result;",
+ newline],
+ string "});",
+ newline,
+ string "uw_leave_subform(ctx);"]
+ end
+ | TList (t', i) =>
+ let
+ val xts = E.lookupStruct env i
+ val i' = case xts of
+ [("1", (TRecord i', loc)), ("2", _)] => i'
+ | _ => raise Fail "CjrPrint: Bad TList record [2]"
+ val xts = E.lookupStruct env i'
+ in
+ box [string "{",
+ newline,
+ string "int status;",
+ newline,
+ string "uw_input_",
+ p_ident x,
+ space,
+ string "=",
+ space,
+ string "NULL;",
+ newline,
+ string "for (status = uw_enter_subforms(ctx, ",
+ string (Int.toString n),
+ string "); status; status = uw_next_entry(ctx)) {",
+ newline,
+ box [p_typ env t,
+ space,
+ string "result",
+ space,
+ string "=",
+ space,
+ string "uw_malloc(ctx, sizeof(struct __uws_",
+ string (Int.toString i),
+ string "));",
+ newline,
+ box [string "{",
+ p_list_sep (box [])
+ (fn (x, t) =>
+ box [p_typ env t,
+ space,
+ string "uw_input_",
+ string x,
+ string ";",
+ newline])
+ xts,
+ newline,
+ p_list_sep (box []) (fn (x, t) =>
+ box [getInput (x, t),
+ string "result->__uwf_1.__uwf_",
+ string x,
+ space,
+ string "=",
+ space,
+ string "uw_input_",
+ string x,
+ string ";",
+ newline])
+ xts,
+ string "}",
+ newline],
+ newline,
+ string "result->__uwf_2 = uw_input_",
+ p_ident x,
+ string ";",
+ newline,
+ string "uw_input_",
+ p_ident x,
+ string " = result;",
+ newline],
+ string "}}",
+ newline]
+ end
+ | TOption _ =>
+ box [string "uw_input_",
+ p_ident x,
+ space,
+ string "=",
+ space,
+ string "uw_get_input(ctx, ",
+ string (Int.toString n),
+ string ");",
+ newline]
+ | _ =>
+ box [string "request = uw_get_",
+ string f,
+ string "input(ctx, ",
+ string (Int.toString n),
+ string ");",
+ newline,
+ string "if (request == NULL)",
+ newline,
+ box [string "uw_error(ctx, FATAL, \"Missing input ",
+ string x,
+ string "\");"],
+ newline,
+ string "uw_input_",
+ p_ident x,
+ space,
+ string "=",
+ space,
+ unurlify true env t,
+ string ";",
+ newline]
+ end
+
+ fun allScripts () =
+ foldl (fn (x, scripts) =>
+ scripts
+ ^ "<script type=\\\"text/javascript\\\" src=\\\"" ^ x ^ "\\\"></script>\\n")
+ "" (Settings.getScripts () @ [!app_js])
+
+ fun p_page (ek, s, n, ts, ran, side, dbmode, tellSig) =
+ let
+ val (ts, defInputs, inputsVar, fields) =
+ case ek of
+ Core.Action _ =>
+ (case List.nth (ts, length ts - 2) of
+ (TRecord i, _) =>
+ let
+ val xts = E.lookupStruct env i
+ in
+ (List.take (ts, length ts - 2),
+ box [box (map (fn (x, t) => box [p_typ env t,
+ space,
+ string "uw_input_",
+ p_ident x,
+ string ";",
+ newline]) xts),
+ newline,
+ box (map getInput xts),
+ case i of
+ 0 => string "uw_unit uw_inputs;"
+ | _ => box [string "struct __uws_",
+ string (Int.toString i),
+ space,
+ string "uw_inputs",
+ space,
+ string "= {",
+ newline,
+ box (map (fn (x, _) => box [string "uw_input_",
+ p_ident x,
+ string ",",
+ newline]) xts),
+ string "};"],
+ newline],
+ box [string ",",
+ space,
+ string "uw_inputs"],
+ SOME xts)
+ end
+
+ | _ => raise Fail "CjrPrint: Last argument to an action isn't a record")
+ | _ => (List.take (ts, length ts - 1), string "", string "", NONE)
+
+ fun couldWrite ek =
+ case ek of
+ Link _ => false
+ | Action ef => ef = ReadCookieWrite
+ | Rpc ef => ef = ReadCookieWrite
+ | Extern _ => false
+
+ fun couldWriteDb ek =
+ case ek of
+ Link ef => ef <> ReadOnly
+ | Action ef => ef <> ReadOnly
+ | Rpc ef => ef <> ReadOnly
+ | Extern ef => ef <> ReadOnly
+
+ val s =
+ case Settings.getUrlPrefix () of
+ "" => s
+ | "/" => s
+ | prefix =>
+ if size s > 0 andalso String.sub (s, 0) = #"/" then
+ prefix ^ String.extract (s, 1, NONE)
+ else
+ prefix ^ s
+ in
+ box [string "if (!strncmp(request, \"",
+ string (Prim.toCString s),
+ string "\", ",
+ string (Int.toString (size s)),
+ string ") && (request[",
+ string (Int.toString (size s)),
+ string "] == 0 || request[",
+ string (Int.toString (size s)),
+ string "] == '/')) {",
+ newline,
+ string "request += ",
+ string (Int.toString (size s)),
+ string ";",
+ newline,
+ string "if (*request == '/') ++request;",
+ newline,
+ case ek of
+ Rpc _ => box [string "if (uw_hasPostBody(ctx)) {",
+ newline,
+ box [string "uw_Basis_postBody pb = uw_getPostBody(ctx);",
+ newline,
+ string "if (pb.data[0])",
+ newline,
+ box [string "request = uw_Basis_strcat(ctx, request, pb.data);"],
+ newline],
+ string "}",
+ newline]
+ | _ => box [],
+ if couldWrite ek andalso not (Settings.checkNoXsrfProtection s) then
+ box [string "{",
+ newline,
+ string "uw_Basis_string sig = ",
+ case fields of
+ NONE => string "uw_Basis_requestHeader(ctx, \"UrWeb-Sig\")"
+ | SOME fields =>
+ case SM.find (fnums, sigName fields) of
+ NONE => raise Fail "CjrPrint: sig name wasn't assigned a number"
+ | SOME inum =>
+ string ("uw_get_input(ctx, " ^ Int.toString inum ^ ")"),
+ string ";",
+ newline,
+ string "if (sig == NULL) uw_error(ctx, FATAL, \"Missing cookie signature\");",
+ newline,
+ string "if (!uw_streq(sig, uw_cookie_sig(ctx)))",
+ newline,
+ box [string "uw_error(ctx, FATAL, \"Wrong cookie signature\");",
+ newline],
+ string "}",
+ newline]
+ else
+ box [],
+ box (case ek of
+ Core.Rpc _ => [string "uw_write_header(ctx, \"Content-type: text/plain\\r\\n\");",
+ newline]
+ | _ => [string "uw_write_header(ctx, \"Content-type: text/html; charset=utf-8\\r\\n\");",
+ newline,
+ case side of
+ ServerOnly => box []
+ | _ => box [string "uw_write_header(ctx, \"Content-script-type: text/javascript\\r\\n\");",
+ newline],
+ string ("uw_write(ctx, uw_begin_" ^
+ (if Settings.getIsHtml5 () then
+ "html5"
+ else
+ "xhtml") ^ ");"),
+ newline,
+ string "uw_mayReturnIndirectly(ctx);",
+ newline,
+ string "uw_set_script_header(ctx, \"",
+ let
+ val scripts =
+ case side of
+ ServerOnly => ""
+ | _ => allScripts ()
+ in
+ string scripts
+ end,
+ string "\");",
+ newline]),
+ string "uw_set_could_write_db(ctx, ",
+ string (if couldWriteDb ek then "1" else "0"),
+ string ");",
+ newline,
+ string "uw_set_at_most_one_query(ctx, ",
+ string (case dbmode of OneQuery => "1" | _ => "0"),
+ string ");",
+ newline,
+ string "uw_set_needs_push(ctx, ",
+ string (case side of
+ ServerAndPullAndPush => "1"
+ | _ => "0"),
+ string ");",
+ newline,
+ string "uw_set_needs_sig(ctx, ",
+ string (if tellSig then
+ "1"
+ else
+ "0"),
+ string ");",
+ newline,
+ string "uw_login(ctx);",
+ newline,
+ box [string "{",
+ newline,
+ box (ListUtil.mapi (fn (i, t) => box [p_typ env t,
+ space,
+ string "arg",
+ string (Int.toString i),
+ space,
+ string "=",
+ space,
+ case #1 t of
+ TFfi ("Basis", "postBody") => string "uw_getPostBody(ctx)"
+ | TOption (TFfi ("Basis", "queryString"), _) => string "uw_queryString(ctx)"
+ | _ => unurlify false env t,
+ string ";",
+ newline]) ts),
+ defInputs,
+ box (case ek of
+ Core.Rpc _ => [p_typ env ran,
+ space,
+ string "it0",
+ space,
+ string "=",
+ space]
+ | _ => []),
+ p_enamed env n,
+ string "(",
+ p_list_sep (box [string ",", space])
+ (fn x => x)
+ (string "ctx"
+ :: ListUtil.mapi (fn (i, _) => string ("arg" ^ Int.toString i)) ts),
+ inputsVar,
+ string ", 0);",
+ newline,
+ box (case ek of
+ Core.Rpc _ => [string "uw_write(ctx, uw_get_real_script(ctx));",
+ newline,
+ string "uw_write(ctx, \"\\n\");",
+ newline,
+ urlify env ran]
+ | _ => [string "uw_write(ctx, \"</html>\");",
+ newline]),
+ string "return;",
+ newline,
+ string "}",
+ newline,
+ string "}"]
+ ]
+ end
+
+ val (pds', handlers) = ListUtil.foldlMap (fn (p, handlers) =>
+ let
+ val p' = p_page p
+ in
+ (p', latestUrlHandlers () @ handlers)
+ end) [] ps
+ val (protos, defs) = ListPair.unzip handlers
+
+ val hasDb = ref false
+ val tables = ref []
+ val views = ref []
+ val sequences = ref []
+ val dbstring = ref ""
+ val expunge = ref 0
+ val initialize = ref 0
+ val prepped = ref []
+ val hasJs = ref false
+
+ val _ = foldl (fn (d, env) =>
+ ((case #1 d of
+ DDatabase {name = x, expunge = y, initialize = z} => (hasDb := true;
+ dbstring := x;
+ expunge := y;
+ initialize := z)
+ | DJavaScript _ => hasJs := true
+ | DTable (s, xts, _, _) => tables := (s, map (fn (x, t) =>
+ (x, sql_type_in env t)) xts) :: !tables
+ | DView (s, xts, _) => views := (s, map (fn (x, t) =>
+ (x, sql_type_in env t)) xts) :: !views
+ | DSequence s => sequences := s :: !sequences
+ | DPreparedStatements ss => prepped := ss
+ | _ => ());
+ E.declBinds env d)) E.empty ds
+
+ val hasDb = !hasDb
+
+ fun expDb (e, _) =
+ case e of
+ ECon (_, _, SOME e) => expDb e
+ | ESome (_, e) => expDb e
+ | EFfiApp (_, _, es) => List.exists (expDb o #1) es
+ | EApp (e, es) => expDb e orelse List.exists expDb es
+ | EUnop (_, e) => expDb e
+ | EBinop (_, e1, e2) => expDb e1 orelse expDb e2
+ | ERecord (_, xes) => List.exists (expDb o #2) xes
+ | EField (e, _) => expDb e
+ | ECase (e, pes, _) => expDb e orelse List.exists (expDb o #2) pes
+ | EError (e, _) => expDb e
+ | EReturnBlob {blob = NONE, mimeType = e2, ...} => expDb e2
+ | EReturnBlob {blob = SOME e1, mimeType = e2, ...} => expDb e1 orelse expDb e2
+ | ERedirect (e, _) => expDb e
+ | EWrite e => expDb e
+ | ESeq (e1, e2) => expDb e1 orelse expDb e2
+ | ELet (_, _, e1, e2) => expDb e1 orelse expDb e2
+ | EQuery _ => true
+ | EDml _ => true
+ | ENextval _ => true
+ | ESetval _ => true
+ | EUnurlify (e, _, _) => expDb e
+ | _ => false
+
+ fun declDb (d, _) =
+ case d of
+ DVal (_, _, _, e) => expDb e
+ | DFun (_, _, _, _, e) => expDb e
+ | DFunRec vis => List.exists (expDb o #5) vis
+ | DTask (_, _, _, e) => expDb e
+ | _ => false
+
+ val () = if not hasDb andalso List.exists declDb ds then
+ ErrorMsg.error "Application uses a database but has none configured with 'database' in .urp file."
+ else
+ ()
+
+ val cookies = List.mapPartial (fn (DCookie s, _) => SOME s | _ => NONE) ds
+
+ val cookieCode = foldl (fn (cookie, acc) =>
+ SOME (case acc of
+ NONE => string ("uw_unnull(uw_Basis_get_cookie(ctx, \""
+ ^ cookie ^ "\"))")
+ | SOME acc => box [string ("uw_Basis_strcat(ctx, uw_unnull(uw_Basis_get_cookie(ctx, \""
+ ^ cookie ^ "\")), uw_Basis_strcat(ctx, \"/\", "),
+ acc,
+ 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,
+ string "(const char *s) {",
+ newline,
+ box [p_list_sep (box [])
+ (fn rule =>
+ box [string "if (!str",
+ case #kind rule of
+ Settings.Exact => box [string "cmp(s, \"",
+ string (Prim.toCString (#pattern rule)),
+ string "\"))"]
+ | Settings.Prefix => box [string "ncmp(s, \"",
+ string (Prim.toCString (#pattern rule)),
+ string "\", ",
+ string (Int.toString (size (#pattern rule))),
+ string "))"],
+ string " return ",
+ string (case #action rule of
+ Settings.Allow => "1"
+ | Settings.Deny => "0"),
+ string ";",
+ newline]) rules,
+ string "return 0;",
+ newline],
+ string "}",
+ newline]
+
+ val initializers = List.mapPartial (fn (DTask (Initialize, x1, x2, e), _) => SOME (x1, x2, e) | _ => NONE) ds
+ val expungers = List.mapPartial (fn (DTask (ClientLeaves, x1, x2, e), _) => SOME (x1, x2, e) | _ => NONE) ds
+ val periodics = List.mapPartial (fn (DTask (Periodic n, x1, x2, e), _) => SOME (n, x1, x2, e) | _ => NONE) ds
+
+ val onError = ListUtil.search (fn (DOnError n, _) => SOME n | _ => NONE) ds
+
+ val lastMod = Date.fromTimeUniv (FileIO.mostRecentModTime ())
+ val rfcFmt = "%a, %d %b %Y %H:%M:%S GMT"
+
+ fun hexifyByte (b : Word8.word) : string =
+ let
+ val s = Int.fmt StringCvt.HEX (Word8.toInt b)
+ in
+ "\\x" ^ (if size s < 2 then "0" else "") ^ s
+ end
+
+ fun hexify (v : Word8Vector.vector) : string =
+ String.concat (Word8Vector.foldr (fn (b, ls) =>
+ hexifyByte b :: ls) [] v)
+ in
+ box [string "#include \"",
+ string (OS.Path.joinDirFile {dir = !Settings.configInclude,
+ file = "config.h"}),
+ string "\"",
+ newline,
+ string "#include <stdio.h>",
+ newline,
+ string "#include <stdlib.h>",
+ newline,
+ string "#include <string.h>",
+ newline,
+ string "#include <math.h>",
+ newline,
+ string "#include <time.h>",
+ newline,
+ if hasDb then
+ box [string ("#include <" ^ #header (Settings.currentDbms ()) ^ ">"),
+ newline]
+ else
+ box [],
+ p_list_sep (box []) (fn s => box [string "#include \"",
+ string s,
+ string "\"",
+ newline]) (Settings.getHeaders ()),
+ string "#include \"",
+ string (OS.Path.joinDirFile {dir = !Settings.configInclude,
+ file = "urweb.h"}),
+ string "\"",
+ newline,
+ newline,
+
+ box [string "static void uw_setup_limits() {",
+ newline,
+ case Settings.getMinHeap () of
+ 0 => box []
+ | n => box [string "uw_min_heap",
+ space,
+ string "=",
+ space,
+ string (Int.toString n),
+ string ";",
+ newline,
+ newline],
+ box [p_list_sep (box []) (fn (class, num) =>
+ let
+ val num = case class of
+ "page" => Int.max (2048, num)
+ | _ => num
+ in
+ box [string ("uw_" ^ class ^ "_max"),
+ space,
+ string "=",
+ space,
+ string (Int.toString num),
+ string ";",
+ newline]
+ end) (Settings.limits ())],
+ string "}",
+ newline,
+ newline],
+
+ #code (Settings.currentProtocol ()) (),
+
+ if hasDb then
+ #init (Settings.currentDbms ()) {dbstring = !dbstring,
+ prepared = !prepped,
+ tables = !tables,
+ views = !views,
+ sequences = !sequences}
+ else
+ box [string "static void uw_client_init(void) { };",
+ newline,
+ string "static void uw_db_init(uw_context ctx) { };",
+ newline,
+ string "static int uw_db_begin(uw_context ctx, int could_write) { return 0; };",
+ newline,
+ string "static void uw_db_close(uw_context ctx) { };",
+ newline,
+ string "static int uw_db_commit(uw_context ctx) { return 0; };",
+ newline,
+ string "static int uw_db_rollback(uw_context ctx) { return 0; };"],
+ newline,
+ newline,
+
+ (* For sqlcache. *)
+ let
+ val {setupGlobal, setupQuery, ...} = Sqlcache.getCache ()
+ in
+ box (setupGlobal :: newline :: List.map setupQuery (Sqlcache.getFfiInfo ()))
+ end,
+ newline,
+
+ p_list_sep newline (fn x => x) pds,
+ newline,
+ newline,
+ string "static int uw_input_num(const char *name) {",
+ newline,
+ makeSwitch (fnums, 0),
+ string "}",
+ newline,
+ newline,
+
+ box (ListUtil.mapi (fn (i, (_, x1, x2, e)) =>
+ box [string "static void uw_periodic",
+ string (Int.toString i),
+ string "(uw_context ctx) {",
+ newline,
+ box [string "uw_unit __uwr_",
+ string x1,
+ string "_0 = 0, __uwr_",
+ string x2,
+ string "_1 = 0;",
+ newline,
+ p_exp (E.pushERel (E.pushERel env x1 dummyt) x2 dummyt) e,
+ string ";",
+ newline],
+ string "}",
+ newline,
+ newline]) periodics),
+
+ string "static uw_periodic my_periodics[] = {",
+ box (ListUtil.mapi (fn (i, (n, _, _, _)) =>
+ box [string "{uw_periodic",
+ string (Int.toString i),
+ string ",",
+ space,
+ string (Int64.toString n),
+ string "},"]) periodics),
+ string "{NULL}};",
+ newline,
+ newline,
+
+ makeChecker ("uw_check_url", Settings.getUrlRules ()),
+ newline,
+
+ makeChecker ("uw_check_mime", Settings.getMimeRules ()),
+ newline,
+
+ makeChecker ("uw_check_requestHeader", Settings.getRequestHeaderRules ()),
+ newline,
+
+ makeChecker ("uw_check_responseHeader", Settings.getResponseHeaderRules ()),
+ newline,
+
+ makeChecker ("uw_check_envVar", Settings.getEnvVarRules ()),
+ newline,
+
+ makeChecker ("uw_check_meta", Settings.getMetaRules ()),
+ newline,
+
+ string "extern void uw_sign(const char *in, char *out);",
+ newline,
+ string "extern int uw_hash_blocksize;",
+ newline,
+ string "static uw_Basis_string uw_cookie_sig(uw_context ctx) {",
+ newline,
+ box [string "uw_Basis_string r = uw_malloc(ctx, uw_hash_blocksize);",
+ newline,
+ string "uw_sign(",
+ case cookieCode of
+ NONE => string "\"\""
+ | SOME code => code,
+ string ", r);",
+ newline,
+ string "return uw_Basis_makeSigString(ctx, r);",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ box (rev protos),
+ box (rev defs),
+
+ string "static void uw_handle(uw_context ctx, char *request) {",
+ newline,
+ string "uw_Basis_string ims = uw_Basis_requestHeader(ctx, \"If-modified-since\");",
+ newline,
+ string ("if (ims && !strcmp(ims, \"" ^ Date.fmt rfcFmt lastMod ^ "\")) {"),
+ newline,
+ box [string "uw_clear_headers(ctx);",
+ newline,
+ string "uw_write_header(ctx, uw_supports_direct_status ? \"HTTP/1.1 304 Not Modified\\r\\n\" : \"Status: 304 Not Modified\\r\\n\");",
+ newline,
+ string "return;",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (!strcmp(request, \"",
+ string (!app_js),
+ string "\")) {",
+ newline,
+ box [string "uw_write_header(ctx, \"Content-Type: text/javascript\\r\\n\");",
+ newline,
+ string ("uw_write_header(ctx, \"Last-Modified: " ^ Date.fmt rfcFmt lastMod ^ "\\r\\n\");"),
+ newline,
+ string ("uw_write_header(ctx, \"Cache-Control: max-age=31536000, public\\r\\n\");"),
+ newline,
+ string "uw_write(ctx, jslib);",
+ newline,
+ string "return;",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ p_list_sep newline (fn r =>
+ box [string "if (!strcmp(request, \"",
+ string (String.toCString (#Uri r)),
+ string "\")) {",
+ newline,
+ box [(case #ContentType r of
+ NONE => box []
+ | SOME ct => box [string "uw_write_header(ctx, \"Content-Type: ",
+ string (String.toCString ct),
+ string "\\r\\n\");",
+ newline]),
+ string ("uw_write_header(ctx, \"Last-Modified: " ^ Date.fmt rfcFmt lastMod ^ "\\r\\n\");"),
+ newline,
+ string ("uw_write_header(ctx, \"Content-Length: " ^ Int.toString (Word8Vector.length (#Bytes r)) ^ "\\r\\n\");"),
+ newline,
+ string ("uw_write_header(ctx, \"Cache-Control: max-age=31536000, public\\r\\n\");"),
+ newline,
+ string "uw_replace_page(ctx, \"",
+ string (hexify (#Bytes r)),
+ string "\", ",
+ string (Int.toString (Word8Vector.length (#Bytes r))),
+ string ");",
+ newline,
+ string "return;",
+ newline],
+ string "};",
+ newline]) (Settings.listFiles ()),
+
+ newline,
+ p_list_sep newline (fn x => x) pds',
+ newline,
+ string "uw_clear_headers(ctx);",
+ newline,
+ string "uw_write_header(ctx, uw_supports_direct_status ? \"HTTP/1.1 404 Not Found\\r\\n\" : \"Status: 404 Not Found\\r\\n\");",
+ newline,
+ string "uw_write_header(ctx, \"Content-type: text/plain\\r\\n\");",
+ newline,
+ string "uw_write(ctx, \"Not Found\");",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ box [string "static void uw_expunger(uw_context ctx, uw_Basis_client cli) {",
+ newline,
+
+ p_list_sep (box []) (fn (x1, x2, e) => box [string "({",
+ newline,
+ string "uw_Basis_client __uwr_",
+ string x1,
+ string "_0 = cli;",
+ newline,
+ string "uw_unit __uwr_",
+ string x2,
+ string "_1 = 0;",
+ newline,
+ p_exp (E.pushERel (E.pushERel env x1 (TFfi ("Basis", "client"), ErrorMsg.dummySpan))
+ x2 dummyt) e,
+ string ";",
+ newline,
+ string "});",
+ newline]) expungers,
+
+ if hasDb then
+ box [p_enamed env (!expunge),
+ string "(ctx, cli);",
+ newline]
+ else
+ box [],
+ string "}"],
+
+ newline,
+ string "static void uw_initializer(uw_context ctx) {",
+ newline,
+ box [string "uw_begin_initializing(ctx);",
+ newline,
+ p_list_sep newline (fn x => x) (rev (!global_initializers)),
+ string "uw_end_initializing(ctx);",
+ newline,
+ p_list_sep (box []) (fn (x1, x2, e) => box [string "({",
+ newline,
+ string "uw_unit __uwr_",
+ string x1,
+ string "_0 = 0, __uwr_",
+ string x2,
+ string "_1 = 0;",
+ newline,
+ p_exp (E.pushERel (E.pushERel env x1 dummyt) x2 dummyt) e,
+ string ";",
+ newline,
+ string "});",
+ newline]) initializers,
+ if hasDb then
+ box [p_enamed env (!initialize),
+ string "(ctx, 0);",
+ newline]
+ else
+ box []],
+ string "}",
+ newline,
+
+ case onError of
+ NONE => box []
+ | SOME n => box [string "static void uw_onError(uw_context ctx, char *msg) {",
+ newline,
+ if !hasJs then
+ box [string "uw_set_script_header(ctx, \"",
+ string (allScripts ()),
+ string "\");",
+ newline]
+ else
+ box [],
+ box [string "uw_write(ctx, ",
+ p_enamed env n,
+ string "(ctx, msg, 0));",
+ newline],
+ string "}",
+ newline,
+ newline],
+
+ string "uw_app uw_application = {",
+ p_list_sep (box [string ",", newline]) string
+ [Int.toString (SM.foldl Int.max 0 fnums + 1),
+ Int.toString (Settings.getTimeout ()),
+ "\"" ^ Settings.getUrlPrefix () ^ "\"",
+ "uw_client_init", "uw_initializer", "uw_expunger",
+ "uw_db_init", "uw_db_begin", "uw_db_commit", "uw_db_rollback", "uw_db_close",
+ "uw_handle",
+ "uw_input_num", "uw_cookie_sig", "uw_check_url", "uw_check_mime", "uw_check_requestHeader", "uw_check_responseHeader", "uw_check_envVar", "uw_check_meta",
+ case onError of NONE => "NULL" | SOME _ => "uw_onError", "my_periodics",
+ "\"" ^ Prim.toCString (Settings.getTimeFormat ()) ^ "\"",
+ if Settings.getIsHtml5 () then "1" else "0"],
+ string "};",
+ newline]
+ end
+
+fun p_sql env (ds, _) =
+ let
+ val (pps, _) = ListUtil.foldlMap
+ (fn (dAll as (d, _), env) =>
+ let
+ val pp = case d of
+ DTable (s, xts, pk, csts) =>
+ box [string "CREATE TABLE ",
+ string s,
+ string "(",
+ p_list (fn (x, t) =>
+ let
+ val t = sql_type_in env t
+ in
+ box [string (Settings.mangleSql (CharVector.map Char.toLower x)),
+ space,
+ string (#p_sql_type (Settings.currentDbms ()) t),
+ case t of
+ Nullable _ => box []
+ | _ => string " NOT NULL"]
+ end) xts,
+ case (pk, csts) of
+ ("", []) => box []
+ | _ => string ",",
+ cut,
+ case pk of
+ "" => box []
+ | _ => box [string "PRIMARY",
+ space,
+ string "KEY",
+ space,
+ string "(",
+ string pk,
+ string ")",
+ case csts of
+ [] => box []
+ | _ => string ",",
+ newline],
+ p_list_sep (box [string ",", newline])
+ (fn (x, c) =>
+ box [string "CONSTRAINT",
+ space,
+ string s,
+ string "_",
+ string x,
+ space,
+ string c]) csts,
+ newline,
+ string ");",
+ newline,
+ newline]
+ | DSequence s =>
+ box [string (#createSequence (Settings.currentDbms ()) s),
+ string ";",
+ newline,
+ newline]
+ | DView (s, xts, q) =>
+ box [string "CREATE VIEW",
+ space,
+ string s,
+ space,
+ string "AS",
+ space,
+ string q,
+ string ";",
+ newline,
+ newline]
+ | _ => box []
+ in
+ (pp, E.declBinds env dAll)
+ end)
+ env ds
+ in
+ box (string (#sqlPrefix (Settings.currentDbms ())) :: pps)
+ end
+
+end
diff --git a/src/cjrize.sig b/src/cjrize.sig
new file mode 100644
index 0000000..fb8d37f
--- /dev/null
+++ b/src/cjrize.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature CJRIZE = sig
+
+ val cjrize : Mono.file -> Cjr.file
+
+end
diff --git a/src/cjrize.sml b/src/cjrize.sml
new file mode 100644
index 0000000..fbc7eba
--- /dev/null
+++ b/src/cjrize.sml
@@ -0,0 +1,745 @@
+(* Copyright (c) 2008-2010, 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 Cjrize :> CJRIZE = struct
+
+structure L = Mono
+structure L' = Cjr
+
+structure IM = IntBinaryMap
+
+structure Sm :> sig
+ type t
+
+ val empty : t
+ val find : t * (string * L.typ) list * (string * L'.typ) list -> t * int
+ val findList : t * L.typ * L'.typ -> t * int
+
+ val declares : t -> (int * (string * L'.typ) list) list
+ val clearDeclares : t -> t
+end = struct
+
+structure FM = BinaryMapFn(struct
+ type ord_key = L.typ
+ val compare = MonoUtil.Typ.compare
+ end)
+
+type t = {
+ count : int,
+ normal : int FM.map,
+ lists : int FM.map,
+ decls : (int * (string * L'.typ) list) list
+}
+
+val empty : t = {
+ count = 1,
+ normal = FM.insert (FM.empty, (L.TRecord [], ErrorMsg.dummySpan), 0),
+ lists = FM.empty,
+ decls = []
+}
+
+fun find (v as {count, normal, decls, lists}, xts, xts') =
+ let
+ val t = (L.TRecord xts, ErrorMsg.dummySpan)
+ in
+ case FM.find (normal, t) of
+ SOME i => (v, i)
+ | NONE => ({count = count+1,
+ normal = FM.insert (normal, t, count),
+ lists = lists,
+ decls = (count, xts') :: decls},
+ count)
+ end
+
+fun findList (v as {count, normal, decls, lists}, t, t') =
+ case FM.find (lists, t) of
+ SOME i => (v, i)
+ | NONE =>
+ let
+ val xts = [("1", t), ("2", (L.TList t, #2 t))]
+ val xts' = [("1", t'), ("2", (L'.TList (t', count), #2 t'))]
+ in
+ ({count = count+1,
+ normal = FM.insert (normal, (L.TRecord xts, ErrorMsg.dummySpan), count),
+ lists = FM.insert (lists, t, count),
+ decls = (count, xts') :: decls},
+ count)
+ end
+
+fun declares (v : t) = #decls v
+
+fun clearDeclares (v : t) = {count = #count v,
+ normal = #normal v,
+ lists = #lists v,
+ decls = []}
+
+end
+
+fun cifyTyp x =
+ let
+ fun cify dtmap ((t, loc), sm) =
+ case t of
+ L.TFun (t1, t2) =>
+ let
+ val (t1, sm) = cify dtmap (t1, sm)
+ val (t2, sm) = cify dtmap (t2, sm)
+ in
+ ((L'.TFun (t1, t2), loc), sm)
+ end
+ | L.TRecord xts =>
+ let
+ val xts = MonoUtil.Typ.sortFields xts
+ val old_xts = xts
+ val (xts, sm) = ListUtil.foldlMap (fn ((x, t), sm) =>
+ let
+ val (t, sm) = cify dtmap (t, sm)
+ in
+ ((x, t), sm)
+ end)
+ sm xts
+ val (sm, si) = Sm.find (sm, old_xts, xts)
+ in
+ ((L'.TRecord si, loc), sm)
+ end
+ | L.TDatatype (n, ref (dk, xncs)) =>
+ (case IM.find (dtmap, n) of
+ SOME r => ((L'.TDatatype (dk, n, r), loc), sm)
+ | NONE =>
+ let
+ val r = ref []
+ val dtmap = IM.insert (dtmap, n, r)
+
+ val (xncs, sm) = ListUtil.foldlMap (fn ((x, n, to), sm) =>
+ case to of
+ NONE => ((x, n, NONE), sm)
+ | SOME t =>
+ let
+ val (t, sm) = cify dtmap (t, sm)
+ in
+ ((x, n, SOME t), sm)
+ end)
+ sm xncs
+ in
+ r := xncs;
+ ((L'.TDatatype (dk, n, r), loc), sm)
+ end)
+ | L.TFfi mx => ((L'.TFfi mx, loc), sm)
+ | L.TOption t =>
+ let
+ val (t, sm) = cify dtmap (t, sm)
+ in
+ ((L'.TOption t, loc), sm)
+ end
+ | L.TList t =>
+ let
+ val (t', sm) = cify dtmap (t, sm)
+ val (sm, si) = Sm.findList (sm, t, t')
+ in
+ ((L'.TList (t', si), loc), sm)
+ end
+ | L.TSource => ((L'.TFfi ("Basis", "source"), loc), sm)
+ | L.TSignal _ => (ErrorMsg.errorAt loc "TSignal remains";
+ Print.epreface ("Full type", MonoPrint.p_typ MonoEnv.empty (#1 x));
+ ((L'.TFfi ("Basis", "bogus"), loc), sm))
+ in
+ cify IM.empty x
+ end
+
+val dummye = (L'.EPrim (Prim.Int 0), ErrorMsg.dummySpan)
+
+fun cifyPatCon (pc, sm) =
+ case pc of
+ L.PConVar n => (L'.PConVar n, sm)
+ | L.PConFfi {mod = m, datatyp, con, arg} =>
+ let
+ val (arg, sm) =
+ case arg of
+ NONE => (NONE, sm)
+ | SOME t =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ (SOME t, sm)
+ end
+ in
+ (L'.PConFfi {mod = m, datatyp = datatyp, con = con, arg = arg}, sm)
+ end
+
+fun cifyPat ((p, loc), sm) =
+ case p of
+ L.PVar (x, t) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.PVar (x, t), loc), sm)
+ end
+ | L.PPrim p => ((L'.PPrim p, loc), sm)
+ | L.PCon (dk, pc, NONE) =>
+ let
+ val (pc, sm) = cifyPatCon (pc, sm)
+ in
+ ((L'.PCon (dk, pc, NONE), loc), sm)
+ end
+ | L.PCon (dk, pc, SOME p) =>
+ let
+ val (pc, sm) = cifyPatCon (pc, sm)
+ val (p, sm) = cifyPat (p, sm)
+ in
+ ((L'.PCon (dk, pc, SOME p), loc), sm)
+ end
+ | L.PRecord xps =>
+ let
+ val (xps, sm) = ListUtil.foldlMap (fn ((x, p, t), sm) =>
+ let
+ val (p, sm) = cifyPat (p, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, p, t), sm)
+ end) sm xps
+ in
+ ((L'.PRecord xps, loc), sm)
+ end
+ | L.PNone t =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.PNone t, loc), sm)
+ end
+ | L.PSome (t, p) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ val (p, sm) = cifyPat (p, sm)
+ in
+ ((L'.PSome (t, p), loc), sm)
+ end
+
+fun cifyExp (eAll as (e, loc), sm) =
+ let
+ fun fail msg =
+ (ErrorMsg.errorAt loc msg;
+ ((L'.EPrim (Prim.String (Prim.Normal, "")), loc), sm))
+ in
+ case e of
+ L.EPrim p => ((L'.EPrim p, loc), sm)
+ | L.ERel n => ((L'.ERel n, loc), sm)
+ | L.ENamed n => ((L'.ENamed n, loc), sm)
+ | L.ECon (dk, pc, eo) =>
+ let
+ val (eo, sm) =
+ case eo of
+ NONE => (NONE, sm)
+ | SOME e =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ in
+ (SOME e, sm)
+ end
+ val (pc, sm) = cifyPatCon (pc, sm)
+ in
+ ((L'.ECon (dk, pc, eo), loc), sm)
+ end
+ | L.ENone t =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.ENone t, loc), sm)
+ end
+ | L.ESome (t, e) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((L'.ESome (t, e), loc), sm)
+ end
+ | L.EFfi mx => ((L'.EFfi mx, loc), sm)
+ | L.EFfiApp (m, x, es) =>
+ let
+ val (es, sm) = ListUtil.foldlMap (fn ((e, t), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((e, t), sm)
+ end) sm es
+ in
+ ((L'.EFfiApp (m, x, es), loc), sm)
+ end
+ | L.EApp (e1, e2) =>
+ let
+ fun unravel (e, args) =
+ case e of
+ (L.EApp (e1, e2), _) => unravel (e1, e2 :: args)
+ | _ => (e, args)
+
+ val (f, es) = unravel (e1, [e2])
+
+ val (f, sm) = cifyExp (f, sm)
+ val (es, sm) = ListUtil.foldlMap cifyExp sm es
+ in
+ ((L'.EApp (f, es), loc), sm)
+ end
+ | L.EAbs _ => (ErrorMsg.errorAt loc "Anonymous function remains at code generation";
+ Print.prefaces' [("Function", MonoPrint.p_exp MonoEnv.empty eAll)];
+ (dummye, sm))
+
+ | L.EUnop (s, e1) =>
+ let
+ val (e1, sm) = cifyExp (e1, sm)
+ in
+ ((L'.EUnop (s, e1), loc), sm)
+ end
+ | L.EBinop (_, s, e1, e2) =>
+ let
+ val (e1, sm) = cifyExp (e1, sm)
+ val (e2, sm) = cifyExp (e2, sm)
+ in
+ ((L'.EBinop (s, e1, e2), loc), sm)
+ end
+
+ | L.ERecord xes =>
+ let
+ val old_xts = map (fn (x, _, t) => (x, t)) xes
+
+ val (xets, sm) = ListUtil.foldlMap (fn ((x, e, t), sm) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, e, t), sm)
+ end)
+ sm xes
+
+ val (sm, si) = Sm.find (sm, old_xts, map (fn (x, _, t) => (x, t)) xets)
+
+ val xes = map (fn (x, e, _) => (x, e)) xets
+ val xes = ListMergeSort.sort (fn ((x1, _), (x2, _)) => String.compare (x1, x2) = GREATER) xes
+ in
+ ((L'.ERecord (si, xes), loc), sm)
+ end
+ | L.EField (e, x) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((L'.EField (e, x), loc), sm)
+ end
+
+ | L.ECase (e, pes, {disc, result}) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ val (pes, sm) = ListUtil.foldlMap
+ (fn ((p, e), sm) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ val (p, sm) = cifyPat (p, sm)
+ in
+ ((p, e), sm)
+ end) sm pes
+ val (disc, sm) = cifyTyp (disc, sm)
+ val (result, sm) = cifyTyp (result, sm)
+ in
+ ((L'.ECase (e, pes, {disc = disc, result = result}), loc), sm)
+ end
+
+ | L.EError (e, t) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.EError (e, t), loc), sm)
+ end
+ | L.EReturnBlob {blob = NONE, mimeType, t} =>
+ let
+ val (mimeType, sm) = cifyExp (mimeType, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.EReturnBlob {blob = NONE, mimeType = mimeType, t = t}, loc), sm)
+ end
+ | L.EReturnBlob {blob = SOME blob, mimeType, t} =>
+ let
+ val (blob, sm) = cifyExp (blob, sm)
+ val (mimeType, sm) = cifyExp (mimeType, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.EReturnBlob {blob = SOME blob, mimeType = mimeType, t = t}, loc), sm)
+ end
+ | L.ERedirect (e, t) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.ERedirect (e, t), loc), sm)
+ end
+
+ | L.EStrcat (e1, e2) =>
+ let
+ val (e1, sm) = cifyExp (e1, sm)
+ val (e2, sm) = cifyExp (e2, sm)
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EFfiApp ("Basis", "strcat", [(e1, s), (e2, s)]), loc), sm)
+ end
+
+ | L.EWrite e =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((L'.EWrite e, loc), sm)
+ end
+
+ | L.ESeq (e1, e2) =>
+ let
+ val (e1, sm) = cifyExp (e1, sm)
+ val (e2, sm) = cifyExp (e2, sm)
+ in
+ ((L'.ESeq (e1, e2), loc), sm)
+ end
+
+ | L.ELet (x, t, e1, e2) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ val (e1, sm) = cifyExp (e1, sm)
+ val (e2, sm) = cifyExp (e2, sm)
+ in
+ ((L'.ELet (x, t, e1, e2), loc), sm)
+ end
+
+ | L.EClosure _ => (ErrorMsg.errorAt loc "Nested closure remains in code generation";
+ (dummye, sm))
+
+ | L.EQuery {exps, tables, state, query, body, initial} =>
+ let
+ val (exps', sm) = ListUtil.foldlMap (fn ((x, t), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, t), sm)
+ end) sm exps
+ val (tables', sm) = ListUtil.foldlMap (fn ((x, xts), sm) =>
+ let
+ val (xts, sm) = ListUtil.foldlMap
+ (fn ((x, t), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, t), sm)
+ end) sm xts
+ in
+ ((x, xts), sm)
+ end) sm tables
+
+ val row = exps @ map (fn (x, xts) => (x, (L.TRecord xts, loc))) tables
+ val row = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) row
+
+ val (tableRows, sm) = ListUtil.foldlMap (fn (((x, xts), (_, xts')), sm) =>
+ let
+ val (sm, rnum) = Sm.find (sm, xts, xts')
+ in
+ ((x, rnum), sm)
+ end)
+ sm (ListPair.zip (tables, tables'))
+ val row' = exps' @ map (fn (x, n) => (x, (L'.TRecord n, loc))) tableRows
+ val row' = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) row'
+
+ val (sm, rnum) = Sm.find (sm, row, row')
+
+ val (state, sm) = cifyTyp (state, sm)
+ val (query, sm) = cifyExp (query, sm)
+ val (body, sm) = cifyExp (body, sm)
+ val (initial, sm) = cifyExp (initial, sm)
+ in
+ ((L'.EQuery {exps = exps', tables = tables', rnum = rnum, state = state,
+ query = query, body = body, initial = initial, prepared = NONE}, loc), sm)
+ end
+
+ | L.EDml (e, mode) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((L'.EDml {dml = e, prepared = NONE, mode = mode}, loc), sm)
+ end
+
+ | L.ENextval e =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((L'.ENextval {seq = e, prepared = NONE}, loc), sm)
+ end
+ | L.ESetval (e1, e2) =>
+ let
+ val (e1, sm) = cifyExp (e1, sm)
+ val (e2, sm) = cifyExp (e2, sm)
+ in
+ ((L'.ESetval {seq = e1, count = e2}, loc), sm)
+ end
+
+ | L.EUnurlify (e, t, b) =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((L'.EUnurlify (e, t, b), loc), sm)
+ end
+
+ | L.EJavaScript _ => fail "Uncompilable JavaScript remains"
+
+ | L.ESignalReturn _ => fail "Signal monad 'return' remains in server-side code"
+ | L.ESignalBind _ => fail "Signal monad 'bind' remains in server-side code"
+ | L.ESignalSource _ => fail "Signal monad 'source' remains in server-side code"
+
+ | L.EServerCall _ => fail "RPC in server-side code"
+ | L.ERecv _ => fail "Message receive in server-side code"
+ | L.ESleep _ => fail "Sleep in server-side code"
+ | L.ESpawn _ => fail "Thread spawn in server-side code"
+ end
+
+fun cifyDecl ((d, loc), sm) =
+ case d of
+ L.DDatatype dts =>
+ let
+ val (dts, sm) = ListUtil.foldlMap
+ (fn ((x, n, xncs), sm) =>
+ let
+ val dk = ElabUtil.classifyDatatype xncs
+ val (xncs, sm) = ListUtil.foldlMap (fn ((x, n, to), sm) =>
+ case to of
+ NONE => ((x, n, NONE), sm)
+ | SOME t =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, n, SOME t), sm)
+ end) sm xncs
+ in
+ ((dk, x, n, xncs), sm)
+ end)
+ sm dts
+ in
+ (SOME (L'.DDatatype dts, loc), NONE, sm)
+ end
+
+ | L.DVal (x, n, t, e, _) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+
+ val (d, sm) = case #1 t of
+ L'.TFun _ =>
+ let
+ fun unravel (tAll as (t, _), eAll as (e, _)) =
+ case (t, e) of
+ (L'.TFun (dom, ran), L.EAbs (ax, _, _, e)) =>
+ let
+ val (args, t, e) = unravel (ran, e)
+ in
+ ((ax, dom) :: args, t, e)
+ end
+ | (L'.TFun (dom, ran), _) =>
+ let
+ val e = MonoEnv.liftExpInExp 0 eAll
+ val e = (L.EApp (e, (L.ERel 0, loc)), loc)
+ val (args, t, e) = unravel (ran, e)
+ in
+ (("x", dom) :: args, t, e)
+ end
+ | _ => ([], tAll, eAll)
+
+ val (args, ran, e) = unravel (t, e)
+ val (e, sm) = cifyExp (e, sm)
+ in
+ (L'.DFun (x, n, args, ran, e), sm)
+ end
+
+ | _ =>
+ let
+ val (e, sm) = cifyExp (e, sm)
+ in
+ (L'.DVal (x, n, t, e), sm)
+ end
+ in
+ (SOME (d, loc), NONE, sm)
+ end
+ | L.DValRec vis =>
+ let
+ val (vis, sm) = ListUtil.foldlMap
+ (fn ((x, n, t, e, _), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+
+ fun unravel (tAll as (t, _), eAll as (e, _)) =
+ case (t, e) of
+ (L'.TFun (dom, ran), L.EAbs (ax, _, _, e)) =>
+ let
+ val (args, t, e) = unravel (ran, e)
+ in
+ ((ax, dom) :: args, t, e)
+ end
+ | (L'.TFun _, _) =>
+ (ErrorMsg.errorAt loc "Function isn't explicit at code generation";
+ ([], tAll, eAll))
+ | _ => ([], tAll, eAll)
+
+ val (args, ran, e) = unravel (t, e)
+ val (e, sm) = cifyExp (e, sm)
+ in
+ ((x, n, args, ran, e), sm)
+ end)
+ sm vis
+ in
+ (SOME (L'.DFunRec vis, loc), NONE, sm)
+ end
+
+ | L.DExport (ek, s, n, ts, t, b) =>
+ let
+ val (ts, sm) = ListUtil.foldlMap cifyTyp sm ts
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ (NONE, SOME (ek, "/" ^ s, n, ts, t, L'.ServerAndPullAndPush, b), sm)
+ end
+
+ | L.DTable (s, xts, pe, ce) =>
+ let
+ val (xts, sm) = ListUtil.foldlMap (fn ((x, t), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, t), sm)
+ end) sm xts
+
+ fun flatten e =
+ case #1 e of
+ L.ERecord [] => []
+ | L.ERecord [(x, (L.EPrim (Prim.String (_, v)), _), _)] => [(x, v)]
+ | L.EStrcat (e1, e2) => flatten e1 @ flatten e2
+ | _ => (ErrorMsg.errorAt loc "Constraint has not been fully determined";
+ Print.prefaces "Undetermined constraint"
+ [("e", MonoPrint.p_exp MonoEnv.empty e)];
+ [])
+
+ val pe = case #1 pe of
+ L.EPrim (Prim.String (_, s)) => s
+ | _ => (ErrorMsg.errorAt loc "Primary key has not been fully determined";
+ Print.prefaces "Undetermined constraint"
+ [("e", MonoPrint.p_exp MonoEnv.empty pe)];
+ "")
+ in
+ (SOME (L'.DTable (s, xts, pe, flatten ce), loc), NONE, sm)
+ end
+ | L.DSequence s =>
+ (SOME (L'.DSequence s, loc), NONE, sm)
+ | L.DView (s, xts, e) =>
+ let
+ val (xts, sm) = ListUtil.foldlMap (fn ((x, t), sm) =>
+ let
+ val (t, sm) = cifyTyp (t, sm)
+ in
+ ((x, t), sm)
+ end) sm xts
+
+ fun flatten e =
+ case #1 e of
+ L.ERecord [] => []
+ | L.ERecord [(x, (L.EPrim (Prim.String (_, v)), _), _)] => [(x, v)]
+ | L.EStrcat (e1, e2) => flatten e1 @ flatten e2
+ | _ => (ErrorMsg.errorAt loc "Constraint has not been fully determined";
+ Print.prefaces "Undetermined constraint"
+ [("e", MonoPrint.p_exp MonoEnv.empty e)];
+ [])
+
+ val e = case #1 e of
+ L.EPrim (Prim.String (_, s)) => s
+ | _ => (ErrorMsg.errorAt loc "VIEW query has not been fully determined";
+ Print.prefaces "Undetermined VIEW query"
+ [("e", MonoPrint.p_exp MonoEnv.empty e)];
+ "")
+ in
+ (SOME (L'.DView (s, xts, e), loc), NONE, sm)
+ end
+ | L.DDatabase s => (SOME (L'.DDatabase s, loc), NONE, sm)
+ | L.DJavaScript s => (SOME (L'.DJavaScript s, loc), NONE, sm)
+ | L.DCookie args => (SOME (L'.DCookie args, loc), NONE, sm)
+ | L.DStyle args => (SOME (L'.DStyle args, loc), NONE, sm)
+ | L.DTask (e1, e2) =>
+ (case #1 e2 of
+ L.EAbs (x1, _, _, (L.EAbs (x2, _, _, e), _)) =>
+ let
+ val tk = case #1 e1 of
+ L.EFfi ("Basis", "initialize") => L'.Initialize
+ | L.EFfi ("Basis", "clientLeaves") => L'.ClientLeaves
+ | L.EFfiApp ("Basis", "periodic", [((L.EPrim (Prim.Int n), _), _)]) => L'.Periodic n
+ | _ => (ErrorMsg.errorAt loc "Task kind not fully determined";
+ L'.Initialize)
+ val (e, sm) = cifyExp (e, sm)
+ in
+ (SOME (L'.DTask (tk, x1, x2, e), loc), NONE, sm)
+ end
+ | _ => (ErrorMsg.errorAt loc "Initializer has not been fully determined";
+ (NONE, NONE, sm)))
+ | L.DPolicy _ => (NONE, NONE, sm)
+ | L.DOnError n => (SOME (L'.DOnError n, loc), NONE, sm)
+
+fun cjrize (ds, sideInfo) =
+ let
+ val (dsF, ds, ps, sm) = foldl (fn (d, (dsF, ds, ps, sm)) =>
+ let
+ val (dop, pop, sm) = cifyDecl (d, sm)
+
+ val dsF = case dop of
+ SOME (L'.DDatatype dts, loc) =>
+ map (fn (dk, x, n, _) =>
+ (L'.DDatatypeForward (dk, x, n), loc)) dts @ dsF
+ | _ => dsF
+
+ val dsF = map (fn v => (L'.DStruct v, ErrorMsg.dummySpan)) (Sm.declares sm)
+ @ dsF
+
+ val (dsF, ds) = case dop of
+ NONE => (dsF, ds)
+ | SOME (d as (L'.DDatatype _, loc)) =>
+ (d :: dsF, ds)
+ | SOME d => (dsF, d :: ds)
+
+ val ps = case pop of
+ NONE => ps
+ | SOME p => p :: ps
+ in
+ (dsF, ds, ps, Sm.clearDeclares sm)
+ end)
+ ([], [], [], Sm.empty) ds
+
+ val sideInfo = foldl (fn ((n, mode, dbmode), mp) => IM.insert (mp, n, (mode, dbmode))) IM.empty sideInfo
+
+ val ps = map (fn (ek, s, n, ts, t, _, b) =>
+ let
+ val (side, db) = getOpt (IM.find (sideInfo, n), (L'.ServerOnly, L'.AnyDb))
+ in
+ (ek, s, n, ts, t, side, db, b)
+ end) ps
+ in
+ (List.revAppend (dsF, rev ds),
+ ps)
+ end
+
+end
diff --git a/src/compiler.mlb b/src/compiler.mlb
new file mode 100644
index 0000000..04a5871
--- /dev/null
+++ b/src/compiler.mlb
@@ -0,0 +1,6 @@
+$(SML_LIB)/basis/basis.mlb
+$(SML_LIB)/basis/mlton.mlb
+
+$(BUILD)/urweb.mlb
+
+main.mlton.sml
diff --git a/src/compiler.sig b/src/compiler.sig
new file mode 100644
index 0000000..952c707
--- /dev/null
+++ b/src/compiler.sig
@@ -0,0 +1,213 @@
+(* Copyright (c) 2008-2012, 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.
+ *)
+
+(* Ur/Web main compiler interface *)
+
+signature COMPILER = sig
+
+ type job = {
+ prefix : string,
+ database : string option,
+ sources : string list,
+ exe : string,
+ sql : string option,
+ debug : bool,
+ profile : bool,
+ timeout : int,
+ ffi : string list,
+ link : string list,
+ linker : string option,
+ headers : string list,
+ scripts : string list,
+ clientToServer : Settings.ffi list,
+ effectful : Settings.ffi list,
+ benignEffectful : Settings.ffi list,
+ clientOnly : Settings.ffi list,
+ serverOnly : Settings.ffi list,
+ jsModule : string option,
+ jsFuncs : (Settings.ffi * string) list,
+ rewrites : Settings.rewrite list,
+ filterUrl : Settings.rule list,
+ filterMime : Settings.rule list,
+ filterRequest : Settings.rule list,
+ filterResponse : Settings.rule list,
+ filterEnv : Settings.rule list,
+ filterMeta : Settings.rule list,
+ protocol : string option,
+ dbms : string option,
+ sigFile : string option,
+ safeGets : string list,
+ onError : (string * string list * string) option,
+ minHeap : int
+ }
+ val compile : string -> bool
+ val compiler : string -> unit
+ val compileC : {cname : string, oname : string, ename : string, libs : string,
+ profile : bool, debug : bool, linker : string option, link : string list} -> bool
+
+ val beforeC : (unit -> unit) ref
+ (* This function is called before beginning C compilation.
+ * The current use is for MLton to compact its heap here, to avoid hogging
+ * space after all the interesting ML code is done. *)
+
+ type ('src, 'dst) phase
+ type ('src, 'dst) transform
+
+ val transform : ('src, 'dst) phase -> string -> ('src, 'dst) transform
+ val o : ('b, 'c) transform * ('a, 'b) transform -> ('a, 'c) transform
+
+ val check : ('src, 'dst) transform -> 'src -> unit
+ val run : ('src, 'dst) transform -> 'src -> 'dst option
+ val runPrint : ('src, 'dst) transform -> 'src -> unit
+ val runPrintToFile : ('src, 'dst) transform -> 'src -> string -> unit
+ val time : ('src, 'dst) transform -> 'src -> unit
+ val timePrint : ('src, 'dst) transform -> 'src -> unit
+
+ val runPrintCoreFuncs : ('src, Core.file) transform -> 'src -> unit
+
+ val parseUr : (string, Source.file) phase
+ val parseUrs : (string, Source.sgn_item list) phase
+ val parseUrp : (string, job) phase
+ val parseUrp' : (string, {Job : job, Libs : string list}) phase
+
+ val parse : (job, Source.file) phase
+ val elaborate : (Source.file, Elab.file) phase
+ val unnest : (Elab.file, Elab.file) phase
+ val termination : (Elab.file, Elab.file) phase
+ val explify : (Elab.file, Expl.file) phase
+ val corify : (Expl.file, Core.file) phase
+ val core_untangle : (Core.file, Core.file) phase
+ val shake : (Core.file, Core.file) phase
+ val rpcify : (Core.file, Core.file) phase
+ val tag : (Core.file, Core.file) phase
+ val reduce : (Core.file, Core.file) phase
+ val unpoly : (Core.file, Core.file) phase
+ val especialize : (Core.file, Core.file) phase
+ val specialize : (Core.file, Core.file) phase
+ val marshalcheck : (Core.file, Core.file) phase
+ val effectize : (Core.file, Core.file) phase
+ val css : (Core.file, Css.report) phase
+ val monoize : (Core.file, Mono.file) phase
+ val mono_opt : (Mono.file, Mono.file) phase
+ val untangle : (Mono.file, Mono.file) phase
+ val mono_reduce : (Mono.file, Mono.file) phase
+ val mono_shake : (Mono.file, Mono.file) phase
+ val iflow : (Mono.file, Mono.file) phase
+ val namejs : (Mono.file, Mono.file) phase
+ val scriptcheck : (Mono.file, Mono.file) phase
+ val jscomp : (Mono.file, Mono.file) phase
+ val fuse : (Mono.file, Mono.file) phase
+ val pathcheck : (Mono.file, Mono.file) phase
+ val sidecheck : (Mono.file, Mono.file) phase
+ val sigcheck : (Mono.file, Mono.file) phase
+ val sqlcache : (Mono.file, Mono.file) phase
+ val cjrize : (Mono.file, Cjr.file) phase
+ val prepare : (Cjr.file, Cjr.file) phase
+ val checknest : (Cjr.file, Cjr.file) phase
+ val sqlify : (Mono.file, Cjr.file) phase
+
+ val toParseJob : (string, job) transform
+ val toParseJob' : (string, {Job : job, Libs : string list}) transform
+ val toParse : (string, Source.file) transform
+ val toElaborate : (string, Elab.file) transform
+ val toUnnest : (string, Elab.file) transform
+ val toTermination : (string, Elab.file) transform
+ val toExplify : (string, Expl.file) transform
+ val toCorify : (string, Core.file) transform
+ val toCore_untangle : (string, Core.file) transform
+ val toShake1 : (string, Core.file) transform
+ val toEspecialize1' : (string, Core.file) transform
+ val toShake1' : (string, Core.file) transform
+ val toRpcify : (string, Core.file) transform
+ val toCore_untangle2 : (string, Core.file) transform
+ val toShake2 : (string, Core.file) transform
+ val toEspecialize1 : (string, Core.file) transform
+ val toCore_untangle3 : (string, Core.file) transform
+ val toShake3 : (string, Core.file) transform
+ val toTag : (string, Core.file) transform
+ val toReduce : (string, Core.file) transform
+ val toShakey : (string, Core.file) transform
+ val toUnpoly : (string, Core.file) transform
+ val toSpecialize : (string, Core.file) transform
+ val toShake4 : (string, Core.file) transform
+ val toEspecialize2 : (string, Core.file) transform
+ val toShake4' : (string, Core.file) transform
+ val toSpecialize2 : (string, Core.file) transform
+ val toUnpoly2 : (string, Core.file) transform
+ val toShake4'' : (string, Core.file) transform
+ val toEspecialize3 : (string, Core.file) transform
+ val toReduce2 : (string, Core.file) transform
+ val toShake5 : (string, Core.file) transform
+ val toMarshalcheck : (string, Core.file) transform
+ val toEffectize : (string, Core.file) transform
+ val toCss : (string, Css.report) transform
+ val toMonoize : (string, Mono.file) transform
+ val toMono_opt1 : (string, Mono.file) transform
+ val toUntangle : (string, Mono.file) transform
+ val toMono_reduce : (string, Mono.file) transform
+ val toMono_shake : (string, Mono.file) transform
+ val toMono_opt2 : (string, Mono.file) transform
+ val toIflow : (string, Mono.file) transform
+ val toNamejs : (string, Mono.file) transform
+ val toNamejs_untangle : (string, Mono.file) transform
+ val toScriptcheck : (string, Mono.file) transform
+ val toDbmodecheck : (string, Mono.file) transform
+ val toJscomp : (string, Mono.file) transform
+ val toMono_opt3 : (string, Mono.file) transform
+ val toFuse : (string, Mono.file) transform
+ val toUntangle2 : (string, Mono.file) transform
+ val toMono_reduce2 : (string, Mono.file) transform
+ val toMono_shake2 : (string, Mono.file) transform
+ val toMono_opt4 : (string, Mono.file) transform
+ val toMono_reduce3 : (string, Mono.file) transform
+ val toFuse2 : (string, Mono.file) transform
+ val toUntangle3 : (string, Mono.file) transform
+ val toMono_shake3 : (string, Mono.file) transform
+ val toPathcheck : (string, Mono.file) transform
+ val toSidecheck : (string, Mono.file) transform
+ val toSigcheck : (string, Mono.file) transform
+ val toSqlcache : (string, Mono.file) transform
+ val toCjrize : (string, Cjr.file) transform
+ val toPrepare : (string, Cjr.file) transform
+ val toChecknest : (string, Cjr.file) transform
+ val toSqlify : (string, Cjr.file) transform
+
+ val debug : bool ref
+ val dumpSource : bool ref
+ val enableBoot : unit -> unit
+
+ val doIflow : bool ref
+
+ val addPath : string * string -> unit
+ val addModuleRoot : string * string -> unit
+
+ val moduleOf : string -> string
+
+ val setStop : string -> unit
+ (* Stop compilation after this phase. *)
+
+end
diff --git a/src/compiler.sml b/src/compiler.sml
new file mode 100644
index 0000000..c13de30
--- /dev/null
+++ b/src/compiler.sml
@@ -0,0 +1,1716 @@
+(* Copyright (c) 2008-2012, 2014, 2016, 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 Compiler :> COMPILER = struct
+
+structure UrwebLrVals = UrwebLrValsFn(structure Token = LrParser.Token)
+structure Lex = UrwebLexFn(structure Tokens = UrwebLrVals.Tokens)
+structure UrwebP = Join(structure ParserData = UrwebLrVals.ParserData
+ structure Lex = Lex
+ structure LrParser = LrParser)
+
+type job = {
+ prefix : string,
+ database : string option,
+ sources : string list,
+ exe : string,
+ sql : string option,
+ debug : bool,
+ profile : bool,
+ timeout : int,
+ ffi : string list,
+ link : string list,
+ linker : string option,
+ headers : string list,
+ scripts : string list,
+ clientToServer : Settings.ffi list,
+ effectful : Settings.ffi list,
+ benignEffectful : Settings.ffi list,
+ clientOnly : Settings.ffi list,
+ serverOnly : Settings.ffi list,
+ jsModule : string option,
+ jsFuncs : (Settings.ffi * string) list,
+ rewrites : Settings.rewrite list,
+ filterUrl : Settings.rule list,
+ filterMime : Settings.rule list,
+ filterRequest : Settings.rule list,
+ filterResponse : Settings.rule list,
+ filterEnv : Settings.rule list,
+ filterMeta : Settings.rule list,
+ protocol : string option,
+ dbms : string option,
+ sigFile : string option,
+ safeGets : string list,
+ onError : (string * string list * string) option,
+ minHeap : int
+}
+
+type ('src, 'dst) phase = {
+ func : 'src -> 'dst,
+ print : 'dst -> Print.PD.pp_desc
+}
+
+type pmap = (string * Time.time) list
+
+type ('src, 'dst) transform = {
+ func : 'src -> 'dst option,
+ print : 'dst -> Print.PD.pp_desc,
+ time : 'src * pmap -> 'dst option * pmap
+}
+
+val debug = ref false
+val dumpSource = ref false
+val doIflow = ref false
+
+val doDumpSource = ref (fn () => ())
+
+val stop = ref (NONE : string option)
+fun setStop s = stop := SOME s
+
+fun transform (ph : ('src, 'dst) phase) name = {
+ func = fn input => let
+ val () = if !debug then
+ print ("Starting " ^ name ^ "....\n")
+ else
+ ()
+ val v = #func ph input
+ in
+ if !debug then
+ print ("Finished " ^ name ^ ".\n")
+ else
+ ();
+ if ErrorMsg.anyErrors () then
+ (!doDumpSource ();
+ doDumpSource := (fn () => ());
+ NONE)
+ else if !stop = SOME name then
+ (Print.eprint (#print ph v);
+ ErrorMsg.error ("Stopped compilation after phase " ^ name);
+ NONE)
+ else
+ (if !dumpSource then
+ doDumpSource := (fn () => Print.eprint (#print ph v))
+ else
+ ();
+ SOME v)
+ end,
+ print = #print ph,
+ time = fn (input, pmap) => let
+ val () = if !debug then
+ print ("Starting " ^ name ^ "....\n")
+ else
+ ()
+ val befor = Time.now ()
+ val v = #func ph input
+ val elapsed = Time.- (Time.now (), befor)
+ in
+ if !debug then
+ print ("Finished " ^ name ^ ".\n")
+ else
+ ();
+ (if ErrorMsg.anyErrors () then
+ NONE
+ else
+ SOME v,
+ (name, elapsed) :: pmap)
+ end
+}
+
+fun check (tr : ('src, 'dst) transform) x = (ErrorMsg.resetErrors ();
+ ignore (#func tr x))
+
+fun run (tr : ('src, 'dst) transform) x = (ErrorMsg.resetErrors ();
+ #func tr x)
+
+fun runPrint (tr : ('src, 'dst) transform) input =
+ (ErrorMsg.resetErrors ();
+ case #func tr input of
+ NONE => print "Failure\n"
+ | SOME v =>
+ (print "Success\n";
+ Print.print (#print tr v);
+ print "\n"))
+
+fun runPrintToFile (tr : ('src, 'dst) transform) input fname =
+ (ErrorMsg.resetErrors ();
+ case #func tr input of
+ NONE => print "Failure\n"
+ | SOME v =>
+ let
+ val outf = TextIO.openOut fname
+ val str = Print.openOut {dst = outf, wid = 80}
+ in
+ print "Success\n";
+ Print.fprint str (#print tr v);
+ Print.PD.PPS.closeStream str;
+ TextIO.closeOut outf
+ end)
+
+fun time (tr : ('src, 'dst) transform) input =
+ let
+ val (_, pmap) = #time tr (input, [])
+ in
+ app (fn (name, time) =>
+ print (name ^ ": " ^ LargeReal.toString (Time.toReal time) ^ "\n")) (rev pmap);
+ print ("TOTAL: " ^ LargeReal.toString (Time.toReal (foldl Time.+ Time.zeroTime (map #2 pmap))) ^ "\n");
+ print "\n"
+ end
+
+fun timePrint (tr : ('src, 'dst) transform) input =
+ let
+ val (ro, pmap) = #time tr (input, [])
+ in
+ app (fn (name, time) =>
+ print (name ^ ": " ^ LargeReal.toString (Time.toReal time) ^ "\n")) (rev pmap);
+ print ("TOTAL: " ^ LargeReal.toString (Time.toReal (foldl Time.+ Time.zeroTime (map #2 pmap))) ^ "\n");
+ print "\n";
+ case ro of
+ NONE => print "Failure\n"
+ | SOME v =>
+ (print "Success\n";
+ Print.print (#print tr v);
+ print "\n")
+ end
+
+fun runPrintCoreFuncs (tr : ('src, Core.file) transform) input =
+ (ErrorMsg.resetErrors ();
+ case #func tr input of
+ NONE => print "Failure\n"
+ | SOME file =>
+ (print "Success\n";
+ app (fn (d, _) =>
+ case d of
+ Core.DVal (x, _, t, _, _) => Print.preface(x, CorePrint.p_con CoreEnv.empty t)
+ | Core.DValRec xts => app (fn (x, _, t, _, _) => Print.preface(x, CorePrint.p_con CoreEnv.empty t)) xts
+ | _ => ()) file))
+
+val parseUrs =
+ {func = fn filename => let
+ val fname = OS.FileSys.tmpName ()
+ val outf = TextIO.openOut fname
+ val () = TextIO.output (outf, "sig\n")
+ val inf = FileIO.txtOpenIn filename
+ fun loop () =
+ case TextIO.inputLine inf of
+ NONE => ()
+ | SOME line => (TextIO.output (outf, line);
+ loop ())
+ val () = loop ()
+ val () = TextIO.closeIn inf
+ val () = TextIO.closeOut outf
+
+ val () = (ErrorMsg.resetErrors ();
+ ErrorMsg.resetPositioning filename;
+ Lex.UserDeclarations.initialize ())
+ val file = FileIO.txtOpenIn fname
+ fun get _ = TextIO.input file
+ fun parseerror (s, p1, p2) = ErrorMsg.errorAt' (p1, p2) s
+ val lexer = LrParser.Stream.streamify (Lex.makeLexer get)
+ val (absyn, _) = UrwebP.parse (30, lexer, parseerror, ())
+ in
+ TextIO.closeIn file;
+ case absyn of
+ [(Source.DSgn ("?", (Source.SgnConst sgis, _)), _)] => sgis
+ | _ => (ErrorMsg.errorAt {file = filename,
+ first = {line = 0,
+ char = 0},
+ last = {line = 0,
+ char = 0}} "Not a signature";
+ [])
+ end
+ handle LrParser.ParseError => [],
+ print = Print.p_list_sep Print.PD.newline SourcePrint.p_sgn_item}
+
+(* The main parsing routine *)
+val parseUr = {
+ func = fn filename =>
+ let
+ val () = (ErrorMsg.resetErrors ();
+ ErrorMsg.resetPositioning filename;
+ Lex.UserDeclarations.initialize ())
+ val file = FileIO.txtOpenIn filename
+ fun get _ = TextIO.input file
+ fun parseerror (s, p1, p2) = ErrorMsg.errorAt' (p1, p2) s
+ val lexer = LrParser.Stream.streamify (Lex.makeLexer get)
+ val (absyn, _) = UrwebP.parse (30, lexer, parseerror, ())
+ in
+ TextIO.closeIn file;
+ case absyn of
+ [(Source.DSgn ("?", _), _)] =>
+ (ErrorMsg.errorAt {file = filename,
+ first = {line = 0,
+ char = 0},
+ last = {line = 0,
+ char = 0}} "File starts with 'sig'";
+ [])
+ | _ => absyn
+ end
+ handle LrParser.ParseError => [],
+ print = SourcePrint.p_file}
+
+fun p_job ({prefix, database, exe, sql, sources, debug, profile,
+ timeout, ffi, link, headers, scripts,
+ clientToServer, effectful, benignEffectful, clientOnly, serverOnly, jsModule, jsFuncs, ...} : job) =
+ let
+ open Print.PD
+ open Print
+
+ fun p_ffi name = p_list_sep (box []) (fn (m, s) =>
+ box [string name, space, string m, string ".", string s, newline])
+ in
+ box [if debug then
+ box [string "DEBUG", newline]
+ else
+ box [],
+ if profile then
+ box [string "PROFILE", newline]
+ else
+ box [],
+ case database of
+ NONE => string "No database."
+ | SOME db => string ("Database: " ^ db),
+ newline,
+ string "Exe: ",
+ string exe,
+ newline,
+ case sql of
+ NONE => string "No SQL file."
+ | SOME sql => string ("SQL fle: " ^ sql),
+ newline,
+ string "Timeout: ",
+ string (Int.toString timeout),
+ newline,
+ p_list_sep (box []) (fn s => box [string "Ffi", space, string s, newline]) ffi,
+ p_list_sep (box []) (fn s => box [string "Header", space, string s, newline]) headers,
+ p_list_sep (box []) (fn s => box [string "Script", space, string s, newline]) scripts,
+ p_list_sep (box []) (fn s => box [string "Link", space, string s, newline]) link,
+ p_ffi "ClientToServer" clientToServer,
+ p_ffi "Effectful" effectful,
+ p_ffi "BenignEffectful" benignEffectful,
+ p_ffi "ClientOnly" clientOnly,
+ p_ffi "ServerOnly" serverOnly,
+ case jsModule of
+ NONE => string "No JavaScript FFI module"
+ | SOME m => string ("JavaScript FFI module: " ^ m),
+ p_list_sep (box []) (fn ((m, s), s') =>
+ box [string "JsFunc", space, string m, string ".", string s,
+ space, string "=", space, string s', newline]) jsFuncs,
+ string "Sources:",
+ p_list string sources,
+ newline]
+ end
+
+fun trim s =
+ let
+ val (_, s) = Substring.splitl Char.isSpace s
+ val (s, _) = Substring.splitr Char.isSpace s
+ in
+ s
+ end
+
+val trimS = Substring.string o trim o Substring.full
+
+structure M = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+(* XXX ezyang: pathmap gets initialized /really early/, before
+ * we do any options parsing. So libUr will always point to the
+ * default. We override it explicitly in enableBoot *)
+val pathmap = ref (M.insert (M.empty, "", Settings.libUr ()))
+
+fun addPath (k, v) = pathmap := M.insert (!pathmap, k, v)
+
+(* XXX ezyang: this is not right; it probably doesn't work in
+ * the case of separate build and src trees *)
+fun enableBoot () =
+ (Settings.configBin := OS.Path.joinDirFile {dir = Config.builddir, file = "bin"};
+ Settings.configSrcLib := OS.Path.joinDirFile {dir = Config.builddir, file = "lib"};
+ (* joinDirFile is annoying... (ArcError; it doesn't like
+ * slashes in file) *)
+ Settings.configLib := Config.builddir ^ "/src/c/.libs";
+ Settings.configInclude := OS.Path.joinDirFile {dir = Config.builddir ^ "/include", file = "urweb"};
+ Settings.configSitelisp := Config.builddir ^ "/src/elisp";
+ addPath ("", Settings.libUr ()))
+
+fun capitalize "" = ""
+ | capitalize s = str (Char.toUpper (String.sub (s, 0))) ^ String.extract (s, 1, NONE)
+
+fun institutionalizeJob (job : job) =
+ (Settings.setDebug (#debug job);
+ Settings.setUrlPrefix (#prefix job);
+ Settings.setTimeout (#timeout job);
+ Settings.setHeaders (#headers job);
+ Settings.setScripts (#scripts job);
+ Settings.setClientToServer (#clientToServer job);
+ Settings.setEffectful (#effectful job);
+ Settings.setBenignEffectful (#benignEffectful job);
+ Settings.setClientOnly (#clientOnly job);
+ Settings.setServerOnly (#serverOnly job);
+ Settings.setJsModule (#jsModule job);
+ Settings.setJsFuncs (#jsFuncs job);
+ Settings.setRewriteRules (#rewrites job);
+ Settings.setUrlRules (#filterUrl job);
+ Settings.setMimeRules (#filterMime job);
+ Settings.setRequestHeaderRules (#filterRequest job);
+ Settings.setResponseHeaderRules (#filterResponse job);
+ Settings.setEnvVarRules (#filterEnv job);
+ Settings.setMetaRules (#filterMeta job);
+ Option.app Settings.setProtocol (#protocol job);
+ Option.app Settings.setDbms (#dbms job);
+ Settings.setSafeGets (#safeGets job);
+ Settings.setOnError (#onError job);
+ Settings.setMinHeap (#minHeap job);
+ Settings.setSigFile (#sigFile job))
+
+datatype commentableLine =
+ EndOfFile
+ | OnlyComment
+ | Content of string
+
+fun inputCommentableLine inf =
+ case TextIO.inputLine inf of
+ NONE => EndOfFile
+ | SOME s =>
+ let
+ val (befor, after) = Substring.splitl (fn ch => ch <> #"#") (Substring.full s)
+ in
+ if not (Substring.isEmpty after)
+ andalso Substring.foldl (fn (ch, b) => b andalso Char.isSpace ch) true befor then
+ OnlyComment
+ else
+ let
+ val s = #1 (Substring.splitr (not o Char.isSpace) befor)
+ in
+ Content (Substring.string (if Substring.size s > 0 andalso Char.isSpace (Substring.sub (s, Substring.size s - 1)) then
+ if Substring.size s > 1 andalso Char.isSpace (Substring.sub (s, Substring.size s - 2)) then
+ Substring.trimr 2 s
+ else
+ Substring.trimr 1 s
+ else
+ s))
+ end
+ end
+
+val lastUrp = ref ""
+
+structure SK = struct
+type ord_key = string
+val compare = String.compare
+end
+
+structure SS = BinarySetFn(SK)
+structure SM = BinaryMapFn(SK)
+
+fun parseUrp' accLibs fname =
+ (lastUrp := fname;
+ if not (Posix.FileSys.access (fname ^ ".urp", []) orelse Posix.FileSys.access (fname ^ "/lib.urp", []))
+ andalso Posix.FileSys.access (fname ^ ".ur", []) then
+ let
+ val job = {prefix = "/",
+ database = NONE,
+ sources = [fname],
+ exe = fname ^ ".exe",
+ sql = NONE,
+ debug = Settings.getDebug (),
+ profile = false,
+ timeout = 120,
+ ffi = [],
+ link = [],
+ linker = NONE,
+ headers = [],
+ scripts = [],
+ clientToServer = [],
+ effectful = [],
+ benignEffectful = [],
+ clientOnly = [],
+ serverOnly = [],
+ jsModule = NONE,
+ jsFuncs = [],
+ rewrites = [{pkind = Settings.Any,
+ kind = Settings.Prefix,
+ from = capitalize (OS.Path.file fname) ^ "/", to = "",
+ hyphenate = false}],
+ filterUrl = [],
+ filterMime = [],
+ filterRequest = [],
+ filterResponse = [],
+ filterEnv = [],
+ filterMeta = [],
+ protocol = NONE,
+ dbms = NONE,
+ sigFile = NONE,
+ safeGets = [],
+ onError = NONE,
+ minHeap = 0}
+ in
+ institutionalizeJob job;
+ {Job = job, Libs = []}
+ end
+ else
+ let
+ val pathmap = ref (!pathmap)
+ val bigLibs = ref []
+ val libSet = ref SS.empty
+
+ 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 () = FileIO.txtOpenIn (OS.Path.joinBaseExt {base = filename, ext = SOME "urp"})
+
+ val inf = opener ()
+
+ fun hasSpaceLine () =
+ case inputCommentableLine inf of
+ Content s => s = "debug" orelse s = "profile"
+ orelse s = "html5" orelse s = "xhtml"
+ orelse s = "noMangleSql" orelse s = "lessSafeFfi"
+ orelse CharVector.exists (fn ch => ch = #" " orelse ch = #"\t") s orelse hasSpaceLine ()
+ | EndOfFile => false
+ | OnlyComment => hasSpaceLine ()
+
+ val hasBlankLine = hasSpaceLine ()
+
+ val inf = (TextIO.closeIn inf; opener ())
+
+ fun pathify fname =
+ if size fname > 0 andalso String.sub (fname, 0) = #"$" then
+ let
+ val fname' = Substring.extract (fname, 1, NONE)
+ val (befor, after) = Substring.splitl (fn ch => ch <> #"/") fname'
+ in
+ case M.find (!pathmap, Substring.string befor) of
+ NONE => fname
+ | SOME rep => rep ^ Substring.string after
+ end
+ else
+ fname
+
+ fun relify fname =
+ let
+ val fname = pathify fname
+ in
+ OS.Path.concat (dir, fname)
+ handle OS.Path.Path => fname
+ end
+
+ fun libify path =
+ (if Posix.FileSys.access (path ^ ".urp", []) then
+ path
+ else
+ path ^ "/lib")
+ handle SysErr => path
+
+ fun libify' path =
+ (if Posix.FileSys.access (relify path ^ ".urp", []) then
+ path
+ else
+ path ^ "/lib")
+ handle SysErr => path
+
+ val absDir = OS.Path.mkAbsolute {path = dir, relativeTo = OS.FileSys.getDir ()}
+
+ fun relifyA fname =
+ OS.Path.mkAbsolute {path = pathify fname, relativeTo = absDir}
+
+ fun readSources acc =
+ case inputCommentableLine inf of
+ Content line =>
+ let
+ val acc = if CharVector.all Char.isSpace line then
+ acc
+ else
+ let
+ fun trim s =
+ let
+ val s = Substring.full s
+ val (_, s) = Substring.splitl Char.isSpace s
+ val (s, _) = Substring.splitr Char.isSpace s
+ in
+ Substring.string s
+ end
+
+ val fname = relifyA (trim line)
+ in
+ fname :: acc
+ end
+ in
+ readSources acc
+ end
+ | OnlyComment => readSources acc
+ | EndOfFile => rev acc
+
+ val prefix = ref (case Settings.getUrlPrefixFull () of "/" => NONE | s => SOME s)
+ val database = ref (Settings.getDbstring ())
+ val exe = ref (Settings.getExe ())
+ val sql = ref (Settings.getSql ())
+ val debug = ref (Settings.getDebug ())
+ val profile = ref false
+ val timeout = ref NONE
+ val ffi = ref []
+ val link = ref []
+ val linker = ref NONE
+ val headers = ref []
+ val scripts = ref []
+ val clientToServer = ref []
+ val effectful = ref []
+ val benignEffectful = ref []
+ val clientOnly = ref []
+ val serverOnly = ref []
+ val jsModule = ref NONE
+ val jsFuncs = ref []
+ val rewrites = ref []
+ val url = ref []
+ val mime = ref []
+ val request = ref []
+ val response = ref []
+ val env = ref []
+ val meta = ref []
+ val libs = ref []
+ val protocol = ref NONE
+ val dbms = ref NONE
+ val sigFile = ref (Settings.getSigFile ())
+ val safeGets = ref []
+ val onError = ref NONE
+ val minHeap = ref 0
+
+ fun finish sources =
+ let
+ val job = {
+ prefix = Option.getOpt (!prefix, "/"),
+ database = !database,
+ exe = Option.getOpt (!exe, OS.Path.joinBaseExt {base = OS.Path.base filename,
+ ext = SOME "exe"}),
+ sql = !sql,
+ debug = !debug,
+ profile = !profile,
+ timeout = Option.getOpt (!timeout, 60),
+ ffi = rev (!ffi),
+ link = rev (!link),
+ linker = !linker,
+ headers = rev (!headers),
+ scripts = rev (!scripts),
+ clientToServer = rev (!clientToServer),
+ effectful = rev (!effectful),
+ benignEffectful = rev (!benignEffectful),
+ clientOnly = rev (!clientOnly),
+ serverOnly = rev (!serverOnly),
+ jsModule = !jsModule,
+ jsFuncs = rev (!jsFuncs),
+ rewrites = rev (!rewrites),
+ filterUrl = rev (!url),
+ filterMime = rev (!mime),
+ filterRequest = rev (!request),
+ filterResponse = rev (!response),
+ filterEnv = rev (!env),
+ filterMeta = rev (!meta),
+ sources = sources,
+ protocol = !protocol,
+ dbms = !dbms,
+ sigFile = !sigFile,
+ safeGets = rev (!safeGets),
+ onError = !onError,
+ minHeap = !minHeap
+ }
+
+ fun mergeO f (old, new) =
+ case (old, new) of
+ (NONE, _) => new
+ | (_, NONE) => old
+ | (SOME v1, SOME v2) => SOME (f (v1, v2))
+
+ fun same desc = mergeO (fn (x : string, y) =>
+ (if x = y then
+ ()
+ else
+ ErrorMsg.error ("Multiple "
+ ^ desc ^ " values that don't agree");
+ x))
+
+ fun merge (old : job, new : job) = {
+ prefix = case #prefix old of
+ "/" => #prefix new
+ | pold => case #prefix new of
+ "/" => pold
+ | pnew => (if pold = pnew then
+ ()
+ else
+ ErrorMsg.error ("Multiple prefix values that don't agree: "
+ ^ pold ^ ", " ^ pnew);
+ pold),
+ database = mergeO (fn (old, _) => old) (#database old, #database new),
+ exe = #exe old,
+ sql = #sql old,
+ debug = #debug old orelse #debug new,
+ profile = #profile old orelse #profile new,
+ timeout = #timeout old,
+ ffi = #ffi old @ #ffi new,
+ link = #link old @ #link new,
+ linker = mergeO (fn (_, new) => new) (#linker old, #linker new),
+ headers = #headers old @ #headers new,
+ scripts = #scripts old @ #scripts new,
+ clientToServer = #clientToServer old @ #clientToServer new,
+ effectful = #effectful old @ #effectful new,
+ benignEffectful = #benignEffectful old @ #benignEffectful new,
+ clientOnly = #clientOnly old @ #clientOnly new,
+ serverOnly = #serverOnly old @ #serverOnly new,
+ jsModule = #jsModule old,
+ jsFuncs = #jsFuncs old @ #jsFuncs new,
+ rewrites = #rewrites old @ #rewrites new,
+ filterUrl = #filterUrl old @ #filterUrl new,
+ filterMime = #filterMime old @ #filterMime new,
+ filterRequest = #filterRequest old @ #filterRequest new,
+ filterResponse = #filterResponse old @ #filterResponse new,
+ filterEnv = #filterEnv old @ #filterEnv new,
+ filterMeta = #filterMeta old @ #filterMeta new,
+ sources = #sources new
+ @ List.filter (fn s => List.all (fn s' => s' <> s) (#sources new))
+ (#sources old),
+ protocol = mergeO #2 (#protocol old, #protocol new),
+ dbms = mergeO #2 (#dbms old, #dbms new),
+ sigFile = mergeO #2 (#sigFile old, #sigFile new),
+ safeGets = #safeGets old @ #safeGets new,
+ onError = mergeO #2 (#onError old, #onError new),
+ minHeap = Int.max (#minHeap old, #minHeap new)
+ }
+ in
+ if accLibs then
+ foldl (fn (job', job) => merge (job, job')) job (!libs)
+ else
+ job
+ end
+
+ fun parsePkind s =
+ case s of
+ "all" => Settings.Any
+ | "url" => Settings.Url
+ | "table" => Settings.Table
+ | "sequence" => Settings.Sequence
+ | "view" => Settings.View
+ | "relation" => Settings.Relation
+ | "cookie" => Settings.Cookie
+ | "style" => Settings.Style
+ | _ => (ErrorMsg.error "Bad path kind spec";
+ Settings.Any)
+
+ 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 parseFkind s =
+ case s of
+ "url" => url
+ | "mime" => mime
+ | "requestHeader" => request
+ | "responseHeader" => response
+ | "env" => env
+ | "meta" => meta
+ | _ => (ErrorMsg.error "Bad filter kind";
+ url)
+
+ fun read () =
+ case inputCommentableLine inf of
+ EndOfFile => finish []
+ | OnlyComment => read ()
+ | Content "" => finish (readSources [])
+ | Content line =>
+ let
+ val (cmd, arg) = Substring.splitl (fn x => not (Char.isSpace x)) (Substring.full line)
+ val cmd = Substring.string (trim cmd)
+ val arg = Substring.string (trim arg)
+
+ fun ffiS () =
+ case String.fields (fn ch => ch = #".") arg of
+ [m, x] => (m, x)
+ | _ => (ErrorMsg.error (cmd ^ " argument not of the form Module.func");
+ ("", ""))
+
+ fun ffiM () =
+ case String.fields (fn ch => ch = #"=") arg of
+ [f, s] =>
+ let
+ val f = trimS f
+ val s = trimS s
+ in
+ case String.fields (fn ch => ch = #".") f of
+ [m, x] => ((m, x), s)
+ | _ => (ErrorMsg.error (cmd ^ " argument not of the form Module.func=func'");
+ (("", ""), ""))
+ end
+ | _ => (ErrorMsg.error (cmd ^ " argument not of the form Module.func=func'");
+ (("", ""), ""))
+ in
+ case cmd of
+ "prefix" => prefix := SOME arg
+ | "database" =>
+ (case !database of
+ NONE => database := SOME arg
+ | SOME _ => ())
+ | "dbms" =>
+ (case !dbms of
+ NONE => dbms := SOME arg
+ | SOME _ => ())
+ | "sigfile" =>
+ (case !sigFile of
+ NONE => sigFile := SOME arg
+ | SOME _ => ())
+ | "exe" =>
+ (case !exe of
+ NONE => exe := SOME (relify arg)
+ | SOME _ => ())
+ | "sql" =>
+ (case !sql of
+ NONE => sql := SOME (relify arg)
+ | SOME _ => ())
+ | "debug" => debug := true
+ | "profile" => profile := true
+ | "timeout" =>
+ (case !timeout of
+ NONE => ()
+ | SOME _ => ErrorMsg.error "Duplicate 'timeout' directive";
+ timeout := SOME (valOf (Int.fromString arg)))
+ | "ffi" => ffi := relify arg :: !ffi
+ | "link" => let
+ val arg = if size arg >= 1
+ andalso String.sub (arg, 0) = #"-" then
+ arg
+ else
+ relifyA arg
+ in
+ link := arg :: !link
+ end
+ | "linker" => linker := SOME arg
+ | "include" => headers := relifyA arg :: !headers
+ | "script" => scripts := arg :: !scripts
+ | "clientToServer" => clientToServer := ffiS () :: !clientToServer
+ | "safeGet" => safeGets := arg :: !safeGets
+ | "effectful" => effectful := ffiS () :: !effectful
+ | "benignEffectful" => benignEffectful := ffiS () :: !benignEffectful
+ | "clientOnly" => clientOnly := ffiS () :: !clientOnly
+ | "serverOnly" => serverOnly := ffiS () :: !serverOnly
+ | "jsModule" =>
+ (case !jsModule of
+ NONE => jsModule := SOME arg
+ | SOME _ => ())
+ | "jsFunc" => jsFuncs := ffiM () :: !jsFuncs
+ | "rewrite" =>
+ let
+ fun doit (pkind, from, to, hyph) =
+ let
+ val pkind = parsePkind pkind
+ val (kind, from) = parsePattern from
+ in
+ rewrites := {pkind = pkind, kind = kind, from = from, to = to, hyphenate = hyph} :: !rewrites
+ end
+ in
+ case String.tokens Char.isSpace arg of
+ [pkind, from, to, "[-]"] => doit (pkind, from, to, true)
+ | [pkind, from, "[-]"] => doit (pkind, from, "", true)
+ | [pkind, from, to] => doit (pkind, from, to, false)
+ | [pkind, from] => doit (pkind, from, "", false)
+ | _ => ErrorMsg.error "Bad 'rewrite' syntax"
+ end
+ | "allow" =>
+ (case String.tokens Char.isSpace arg of
+ [fkind, pattern] =>
+ let
+ val fkind = parseFkind fkind
+ val (kind, pattern) = parsePattern pattern
+ in
+ fkind := {action = Settings.Allow, kind = kind, pattern = pattern} :: !fkind
+ end
+ | _ => ErrorMsg.error "Bad 'allow' syntax")
+ | "deny" =>
+ (case String.tokens Char.isSpace arg of
+ [fkind, pattern] =>
+ let
+ val fkind = parseFkind fkind
+ val (kind, pattern) = parsePattern pattern
+ in
+ fkind := {action = Settings.Deny, kind = kind, pattern = pattern} :: !fkind
+ end
+ | _ => ErrorMsg.error "Bad 'deny' syntax")
+ | "library" =>
+ if accLibs then
+ let
+ val arg = libify (relify arg)
+ in
+ if SS.member (!libSet, arg) then
+ ()
+ else
+ (libs := pu arg :: !libs;
+ libSet := SS.add (!libSet, arg))
+ end
+ else
+ bigLibs := libify' arg :: !bigLibs
+ | "path" =>
+ (case String.fields (fn ch => ch = #"=") arg of
+ [n, v] => ((pathmap := M.insert (!pathmap, n, OS.Path.mkAbsolute {path = v, relativeTo = dir}))
+ handle OS.Path.Path => ErrorMsg.error "Invalid 'path' directory argument")
+ | _ => ErrorMsg.error "path argument not of the form name=value'")
+ | "onError" =>
+ (case String.fields (fn ch => ch = #".") arg of
+ m1 :: (fs as _ :: _) =>
+ onError := SOME (m1, List.take (fs, length fs - 1), List.last fs)
+ | _ => ErrorMsg.error "invalid 'onError' argument")
+ | "limit" =>
+ (case String.fields Char.isSpace arg of
+ [class, num] =>
+ (case Int.fromString num of
+ NONE => ErrorMsg.error ("invalid limit number '" ^ num ^ "'")
+ | SOME n =>
+ if n < 0 then
+ ErrorMsg.error ("invalid limit number '" ^ num ^ "'")
+ else
+ Settings.addLimit (class, n))
+ | _ => ErrorMsg.error "invalid 'limit' arguments")
+ | "minHeap" =>
+ (case Int.fromString arg of
+ NONE => ErrorMsg.error ("invalid min heap '" ^ arg ^ "'")
+ | SOME n => minHeap := n)
+ | "coreInline" =>
+ (case Int.fromString arg of
+ NONE => ErrorMsg.error ("invalid core inline level '" ^ arg ^ "'")
+ | SOME n => Settings.setCoreInline n)
+ | "monoInline" =>
+ (case Int.fromString arg of
+ NONE => ErrorMsg.error ("invalid mono inline level '" ^ arg ^ "'")
+ | SOME n => Settings.setMonoInline n)
+ | "alwaysInline" => Settings.addAlwaysInline arg
+ | "neverInline" => Settings.addNeverInline arg
+ | "noXsrfProtection" => Settings.addNoXsrfProtection arg
+ | "timeFormat" => Settings.setTimeFormat arg
+ | "noMangleSql" => Settings.setMangleSql false
+ | "html5" => Settings.setIsHtml5 true
+ | "xhtml" => Settings.setIsHtml5 false
+ | "lessSafeFfi" => Settings.setLessSafeFfi true
+
+ | "file" =>
+ (case String.fields Char.isSpace arg of
+ [uri, fname] => (Settings.setFilePath thisPath;
+ Settings.addFile {Uri = uri,
+ LoadFromFilename = fname};
+ url := {action = Settings.Allow, kind = Settings.Exact, pattern = uri} :: !url)
+ | _ => ErrorMsg.error "Bad 'file' arguments")
+
+ | "jsFile" =>
+ (Settings.setFilePath thisPath;
+ Settings.addJsFile arg)
+
+ | _ => ErrorMsg.error ("Unrecognized command '" ^ cmd ^ "'");
+ read ()
+ end
+
+ val job = if hasBlankLine then
+ read ()
+ else
+ finish (readSources [])
+ in
+ TextIO.closeIn inf;
+ institutionalizeJob job;
+ job
+ end
+ in
+ {Job = pu fname, Libs = !bigLibs}
+ end)
+
+fun p_job' {Job = j, Libs = _ : string list} = p_job j
+
+val parseUrp = {
+ func = #Job o parseUrp' true,
+ print = p_job
+}
+
+val parseUrp' = {
+ func = parseUrp' false,
+ print = p_job'
+}
+
+val toParseJob = transform parseUrp "parseJob"
+val toParseJob' = transform parseUrp' "parseJob'"
+
+fun op o (tr2 : ('b, 'c) transform, tr1 : ('a, 'b) transform) = {
+ func = fn input => case #func tr1 input of
+ NONE => NONE
+ | SOME v => #func tr2 v,
+ print = #print tr2,
+ time = fn (input, pmap) => let
+ val (ro, pmap) = #time tr1 (input, pmap)
+ in
+ case ro of
+ NONE => (NONE, pmap)
+ | SOME v => #time tr2 (v, pmap)
+ end
+}
+
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+val moduleRoots = ref ([] : (string * string) list)
+fun addModuleRoot (k, v) = moduleRoots :=
+ (OS.Path.mkAbsolute {path = k,
+ relativeTo = OS.FileSys.getDir ()},
+ v) :: !moduleRoots
+
+exception MissingFile of string
+
+val parse = {
+ func = fn {database, sources = fnames, ffi, onError, ...} : job =>
+ let
+ val mrs = !moduleRoots
+
+ val anyErrors = ref false
+ fun checkErrors () = anyErrors := (!anyErrors orelse ErrorMsg.anyErrors ())
+ fun nameOf fname =
+ let
+ val fname = OS.Path.file fname
+ val fst = if size fname = 0 then #"!" else String.sub (fname, 0)
+ in
+ if not (Char.isAlpha fst) then
+ ErrorMsg.error ("Filename doesn't start with letter: " ^ fname)
+ else if CharVector.exists (fn ch => not (Char.isAlphaNum ch) andalso ch <> #"_") fname then
+ ErrorMsg.error ("Filename contains a character that isn't alphanumeric or underscore: " ^ fname)
+ else
+ ();
+ capitalize fname
+ end
+
+ fun parseFfi fname =
+ let
+ val mname = nameOf fname
+ val urs = OS.Path.joinBaseExt {base = fname, ext = SOME "urs"}
+
+ val loc = {file = urs,
+ first = ErrorMsg.dummyPos,
+ last = ErrorMsg.dummyPos}
+
+ val sgn = (Source.SgnConst (#func parseUrs urs), loc)
+ in
+ checkErrors ();
+ (Source.DFfiStr (mname, sgn, if !Elaborate.incremental then SOME (OS.FileSys.modTime urs) else NONE), loc)
+ end
+
+ val defed = ref SS.empty
+ val fulls = ref SS.empty
+
+ val caughtOneThatIsn'tAFile = ref false
+
+ fun parseOne fname =
+ let
+ val mname = nameOf fname
+ val ur = OS.Path.joinBaseExt {base = fname, ext = SOME "ur"}
+ val urs = OS.Path.joinBaseExt {base = fname, ext = SOME "urs"}
+
+ val () = if Posix.FileSys.access (ur, []) then
+ ()
+ else
+ raise MissingFile ur
+
+ val sgnO =
+ if Posix.FileSys.access (urs, []) then
+ SOME (Source.SgnConst (#func parseUrs urs),
+ {file = urs,
+ first = ErrorMsg.dummyPos,
+ last = ErrorMsg.dummyPos})
+ before checkErrors ()
+ else
+ NONE
+
+ val loc = {file = ur,
+ first = ErrorMsg.dummyPos,
+ last = ErrorMsg.dummyPos}
+
+ val urt = OS.FileSys.modTime ur
+ val urst = (OS.FileSys.modTime urs) handle _ => urt
+
+ val ds = #func parseUr ur
+ val d = (Source.DStr (mname, sgnO, if !Elaborate.incremental then SOME (if Time.> (urt, urst) then urt else urst) else NONE,
+ (Source.StrConst ds, loc), false), loc)
+
+ val fname = OS.Path.mkCanonical fname
+ val d = case List.find (fn (root, name) =>
+ String.isPrefix (root ^ "/") fname) mrs of
+ NONE => d
+ | SOME (root, name) =>
+ let
+ val fname = String.extract (fname, size root + 1, NONE)
+ val pieces = name :: String.tokens (fn ch => ch = #"/") fname
+ val pieces = List.filter (fn s => size s > 0
+ andalso Char.isAlpha (String.sub (s, 0)))
+ pieces
+ val pieces = map capitalize pieces
+ val full = String.concatWith "." pieces
+
+ fun makeD first prefix pieces =
+ case pieces of
+ [] => (ErrorMsg.error "Empty module path";
+ (Source.DStyle "Boo", loc))
+ | [_] => d
+ | piece :: pieces =>
+ let
+ val this = case prefix of
+ "" => piece
+ | _ => prefix ^ "." ^ piece
+ val old = SS.member (!defed, this)
+
+ fun notThere (ch, s) =
+ Substring.isEmpty (#2 (Substring.splitl
+ (fn ch' => ch' <> ch) s))
+
+ fun simOpen () =
+ SS.foldl (fn (full, ds) =>
+ if String.isPrefix (this ^ ".") full
+ andalso notThere (#".",
+ Substring.extract (full,
+ size
+ this + 1,
+ NONE)) then
+ let
+ val parts = String.tokens
+ (fn ch => ch = #".") full
+
+ val part = List.last parts
+
+ val imp = if length parts >= 2 then
+ (Source.StrProj
+ ((Source.StrVar
+ (List.nth (parts,
+ length
+ parts
+ - 2)),
+ loc),
+ part), loc)
+ else
+ (Source.StrVar part, loc)
+ in
+ (Source.DStr (part, NONE, NONE, imp, false),
+ loc) :: ds
+ end
+ else
+ ds) [] (!fulls)
+ in
+ defed := SS.add (!defed, this);
+ (Source.DStr (piece, NONE, NONE,
+ (Source.StrConst (if old then
+ simOpen ()
+ @ [makeD false this pieces]
+ else
+ [makeD false this pieces]),
+ loc), first andalso old),
+ loc)
+ end
+ in
+ if SS.member (!fulls, full) then
+ ErrorMsg.error ("Rooted module " ^ full ^ " has multiple versions.")
+ else
+ ();
+
+ makeD true "" pieces
+ before ignore (foldl (fn (new, path) =>
+ let
+ val new' = case path of
+ "" => new
+ | _ => path ^ "." ^ new
+ in
+ fulls := SS.add (!fulls, new');
+ new'
+ end) "" pieces)
+ end
+ in
+ checkErrors ();
+ d
+ end handle MissingFile fname => (if not (!caughtOneThatIsn'tAFile)
+ andalso CharVector.exists Char.isSpace fname then
+ (caughtOneThatIsn'tAFile := true;
+ ErrorMsg.error ("In .urp files, all configuration directives must come before any blank lines.\n"
+ ^ "However, this .urp file contains at least one suspicious line in a position\n"
+ ^ "where filenames belong (after the first blank line) but containing a space\n"
+ ^ "character."))
+ else
+ ();
+ ErrorMsg.error ("Missing source file: " ^ fname);
+ (Source.DSequence "", ErrorMsg.dummySpan))
+
+ val dsFfi = map parseFfi ffi
+ val ds = map parseOne fnames
+ val loc = ErrorMsg.dummySpan
+ in
+ if !anyErrors then
+ ErrorMsg.error "Parse failure"
+ else
+ ();
+
+ let
+ val final = List.last fnames
+ val final = case List.find (fn (root, name) =>
+ String.isPrefix (root ^ "/") final) mrs of
+ NONE => (Source.StrVar (nameOf final), loc)
+ | SOME (root, name) =>
+ let
+ val m = (Source.StrVar name, loc)
+ val final = String.extract (final, size root + 1, NONE)
+ val fields = String.fields (fn ch => ch = #"/") final
+ val fields = List.filter (fn s => size s = 0
+ orelse not (Char.isDigit (String.sub (s, 0))))
+ fields
+ in
+ foldl (fn (x, m) => (Source.StrProj (m, capitalize x), loc))
+ m fields
+ end
+
+ val ds = dsFfi @ ds
+ @ [(Source.DExport final, loc)]
+
+ val ds = case database of
+ NONE => ds
+ | SOME s => (Source.DDatabase s, loc) :: ds
+
+ val ds = case onError of
+ NONE => ds
+ | SOME v => ds @ [(Source.DOnError v, loc)]
+
+ fun dummy fname = {file = Settings.libFile fname,
+ first = ErrorMsg.dummyPos,
+ last = ErrorMsg.dummyPos}
+
+ val used = SM.insert (SM.empty, "Basis", dummy "basis.urs")
+ val used = SM.insert (used, "Top", dummy "top.urs")
+ in
+ ignore (List.foldl (fn (d, used) =>
+ case #1 d of
+ Source.DStr (x, _, _, _, false) =>
+ (case SM.find (used, x) of
+ SOME loc =>
+ (ErrorMsg.error ("Duplicate top-level module name " ^ x);
+ Print.prefaces "Files" [("Previous", Print.PD.string (ErrorMsg.spanToString loc)),
+ ("Current", Print.PD.string (ErrorMsg.spanToString (#2 d)))];
+ used)
+ | NONE =>
+ SM.insert (used, x, #2 d))
+ | _ => used) used ds);
+ ds
+ end handle Empty => ds
+ end,
+ print = SourcePrint.p_file
+}
+
+val toParse = transform parse "parse" o toParseJob
+
+val elaborate = {
+ func = fn file => let
+ val basisF = Settings.libFile "basis.urs"
+ val topF = Settings.libFile "top.urs"
+ val topF' = Settings.libFile "top.ur"
+
+ val basis = #func parseUrs basisF
+ val topSgn = #func parseUrs topF
+ val topStr = #func parseUr topF'
+
+ val tm1 = OS.FileSys.modTime topF
+ val tm2 = OS.FileSys.modTime topF'
+ in
+ Elaborate.elabFile basis (OS.FileSys.modTime basisF)
+ topStr topSgn (if Time.< (tm1, tm2) then tm2 else tm1)
+ ElabEnv.empty file
+ end,
+ print = ElabPrint.p_file ElabEnv.empty
+}
+
+val toElaborate = transform elaborate "elaborate" o toParse
+
+val unnest = {
+ func = Unnest.unnest,
+ print = ElabPrint.p_file ElabEnv.empty
+}
+
+val toUnnest = transform unnest "unnest" o toElaborate
+
+val termination = {
+ func = (fn file => (Termination.check file; file)),
+ print = ElabPrint.p_file ElabEnv.empty
+}
+
+val toTermination = transform termination "termination" o toUnnest
+
+val explify = {
+ func = Explify.explify,
+ print = ExplPrint.p_file ExplEnv.empty
+}
+
+val toExplify = transform explify "explify" o toUnnest
+
+val corify = {
+ func = Corify.corify,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toCorify = transform corify "corify" o toExplify
+
+(*val reduce_local = {
+ func = ReduceLocal.reduce,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toReduce_local = transform reduce_local "reduce_local" o toCorify*)
+
+val especialize = {
+ func = ESpecialize.specialize,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val core_untangle = {
+ func = CoreUntangle.untangle,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toCore_untangle = transform core_untangle "core_untangle" o toCorify
+
+val shake = {
+ func = Shake.shake,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toShake1 = transform shake "shake1" o toCore_untangle
+
+val toEspecialize1' = transform especialize "especialize1'" o toShake1
+val toShake1' = transform shake "shake1'" o toEspecialize1'
+
+val rpcify = {
+ func = Rpcify.frob,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toRpcify = transform rpcify "rpcify" o toShake1'
+
+val toCore_untangle2 = transform core_untangle "core_untangle2" o toRpcify
+val toShake2 = transform shake "shake2" o toCore_untangle2
+
+val toEspecialize1 = transform especialize "especialize1" o toShake2
+
+val toCore_untangle3 = transform core_untangle "core_untangle3" o toEspecialize1
+val toShake3 = transform shake "shake3" o toCore_untangle3
+
+val tag = {
+ func = Tag.tag,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toTag = transform tag "tag" o toShake3
+
+val reduce = {
+ func = Reduce.reduce,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toReduce = transform reduce "reduce" o toTag
+
+val toShakey = transform shake "shakey" o toReduce
+
+val unpoly = {
+ func = Unpoly.unpoly,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toUnpoly = transform unpoly "unpoly" o toShakey
+
+val specialize = {
+ func = Specialize.specialize,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toSpecialize = transform specialize "specialize" o toUnpoly
+
+val toShake4 = transform shake "shake4" o toSpecialize
+
+val toEspecialize2 = transform especialize "especialize2" o toShake4
+val toShake4' = transform shake "shake4'" o toEspecialize2
+val toUnpoly2 = transform unpoly "unpoly2" o toShake4'
+val toSpecialize2 = transform specialize "specialize2" o toUnpoly2
+val toShake4'' = transform shake "shake4'" o toSpecialize2
+val toEspecialize3 = transform especialize "especialize3" o toShake4''
+
+val toReduce2 = transform reduce "reduce2" o toEspecialize3
+
+val toShake5 = transform shake "shake5" o toReduce2
+
+val marshalcheck = {
+ func = (fn file => (MarshalCheck.check file; file)),
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toMarshalcheck = transform marshalcheck "marshalcheck" o toShake5
+
+val effectize = {
+ func = Effective.effectize,
+ print = CorePrint.p_file CoreEnv.empty
+}
+
+val toEffectize = transform effectize "effectize" o toMarshalcheck
+
+val css = {
+ func = Css.summarize,
+ print = fn _ => Print.box []
+}
+
+val toCss = transform css "css" o toShake5
+
+val monoize = {
+ func = Monoize.monoize CoreEnv.empty,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toMonoize = transform monoize "monoize" o toEffectize
+
+val mono_opt = {
+ func = MonoOpt.optimize,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toMono_opt1 = transform mono_opt "mono_opt1" o toMonoize
+
+val untangle = {
+ func = Untangle.untangle,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toUntangle = transform untangle "untangle" o toMono_opt1
+
+val mono_reduce = {
+ func = MonoReduce.reduce,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toMono_reduce = transform mono_reduce "mono_reduce" o toUntangle
+
+val mono_shake = {
+ func = MonoShake.shake,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toMono_shake = transform mono_shake "mono_shake1" o toMono_reduce
+
+val toMono_opt2 = transform mono_opt "mono_opt2" o toMono_shake
+
+val iflow = {
+ func = (fn file => (if !doIflow then Iflow.check file else (); file)),
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toIflow = transform iflow "iflow" o toMono_opt2
+
+val namejs = {
+ func = NameJS.rewrite,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toNamejs = transform namejs "namejs" o toIflow
+
+val toNamejs_untangle = transform untangle "namejs_untangle" o toNamejs
+
+val scriptcheck = {
+ func = ScriptCheck.classify,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toScriptcheck = transform scriptcheck "scriptcheck" o toNamejs_untangle
+
+val dbmodecheck = {
+ func = DbModeCheck.classify,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toDbmodecheck = transform dbmodecheck "dbmodecheck" o toScriptcheck
+
+val jscomp = {
+ func = JsComp.process,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toJscomp = transform jscomp "jscomp" o toDbmodecheck
+
+val toMono_opt3 = transform mono_opt "mono_opt3" o toJscomp
+
+val fuse = {
+ func = Fuse.fuse,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toFuse = transform fuse "fuse" o toMono_opt3
+
+val toUntangle2 = transform untangle "untangle2" o toFuse
+
+val toMono_reduce2 = transform mono_reduce "mono_reduce2" o toUntangle2
+val toMono_shake2 = transform mono_shake "mono_shake2" o toMono_reduce2
+val toMono_opt4 = transform mono_opt "mono_opt4" o toMono_shake2
+val toMono_reduce3 = transform mono_reduce "mono_reduce3" o toMono_opt4
+val toFuse2 = transform fuse "fuse2" o toMono_reduce3
+val toUntangle3 = transform untangle "untangle3" o toFuse2
+val toMono_shake3 = transform mono_shake "mono_shake3" o toUntangle3
+
+val pathcheck = {
+ func = (fn file => (PathCheck.check file; file)),
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toPathcheck = transform pathcheck "pathcheck" o toMono_shake3
+
+val sidecheck = {
+ func = SideCheck.check,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toSidecheck = transform sidecheck "sidecheck" o toPathcheck
+
+val sigcheck = {
+ func = SigCheck.check,
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toSigcheck = transform sigcheck "sigcheck" o toSidecheck
+
+val sqlcache = {
+ func = (fn file =>
+ if Settings.getSqlcache ()
+ then let val file = MonoInline.inlineFull file in Sqlcache.go file end
+ else file),
+ print = MonoPrint.p_file MonoEnv.empty
+}
+
+val toSqlcache = transform sqlcache "sqlcache" o toSigcheck
+
+val cjrize = {
+ func = Cjrize.cjrize,
+ print = CjrPrint.p_file CjrEnv.empty
+}
+
+val toCjrize = transform cjrize "cjrize" o toSqlcache
+
+val prepare = {
+ func = Prepare.prepare,
+ print = CjrPrint.p_file CjrEnv.empty
+}
+
+val toPrepare = transform prepare "prepare" o toCjrize
+
+val checknest = {
+ func = fn f => if #supportsNestedPrepared (Settings.currentDbms ()) then f else Checknest.annotate f,
+ print = CjrPrint.p_file CjrEnv.empty
+}
+
+val toChecknest = transform checknest "checknest" o toPrepare
+
+val sqlify = {
+ func = Cjrize.cjrize,
+ print = CjrPrint.p_sql CjrEnv.empty
+}
+
+val toSqlify = transform sqlify "sqlify" o toMono_opt2
+
+fun escapeFilename s =
+ "\""
+ ^ String.translate (fn #"\"" => "\\\"" | #"\\" => "\\\\" | ch => str ch) s
+ ^ "\""
+
+val beforeC = ref (fn () => ())
+
+structure StringSet = BinarySetFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+fun compileC {cname, oname, ename, libs, profile, debug, linker, link = link'} =
+ let
+ val proto = Settings.currentProtocol ()
+
+ val lib = if Settings.getBootLinking () then
+ !Settings.configLib ^ "/" ^ #linkStatic proto ^ " " ^ !Settings.configLib ^ "/liburweb.a"
+ else if Settings.getStaticLinking () then
+ " -static " ^ !Settings.configLib ^ "/" ^ #linkStatic proto ^ " " ^ !Settings.configLib ^ "/liburweb.a"
+ else
+ "-L" ^ !Settings.configLib ^ " " ^ #linkDynamic proto ^ " -lurweb"
+
+ val opt = if debug then
+ ""
+ else
+ " -O3"
+
+ val compile = (Settings.getCCompiler ()) ^ " " ^ Config.ccArgs ^ " " ^ Config.pthreadCflags ^ " -Wimplicit -Werror -Wno-unused-value"
+ ^ opt ^ " -I " ^ !Settings.configInclude
+ ^ " " ^ #compile proto
+ ^ " -c " ^ escapeFilename cname ^ " -o " ^ escapeFilename oname
+
+ fun concatArgs (a1, a2) =
+ if CharVector.all Char.isSpace a1 then
+ a2
+ else
+ a1 ^ " " ^ a2
+
+ val args = concatArgs (Config.ccArgs, Config.pthreadCflags)
+ val args = concatArgs (args, Config.pthreadLibs)
+
+ val linker = Option.getOpt (linker, (Settings.getCCompiler ()) ^ " -Werror" ^ opt ^ " " ^ args)
+
+ val ssl = if Settings.getStaticLinking () then
+ Config.openssl ^ " -ldl -lz"
+ else
+ Config.openssl
+
+ val link = linker
+ ^ " " ^ escapeFilename oname ^ " " ^ lib ^ " -lm " ^ ssl ^ " " ^ libs ^ " -o " ^ escapeFilename ename
+
+ val (compile, link) =
+ if profile then
+ (compile ^ " -pg", link ^ " -pg")
+ else
+ (compile, link)
+
+ val (compile, link) =
+ if debug then
+ (compile ^ " -g", link ^ " -g")
+ else
+ (compile, link)
+
+ val link = #1 (foldl
+ (fn (s, (link, set)) =>
+ if StringSet.member (set, s) then
+ (link, set)
+ else
+ ((link ^ " " ^ s), StringSet.add (set, s)))
+ (link, StringSet.empty)
+ link')
+
+ fun system s =
+ (if debug then
+ print (s ^ "\n")
+ else
+ ();
+ OS.Process.isSuccess (OS.Process.system s))
+ in
+ !beforeC ();
+ system compile andalso system link
+ end
+
+fun compile job =
+ case run toChecknest job of
+ NONE => false
+ | SOME file =>
+ let
+ val job = valOf (run (transform parseUrp "parseUrp") job)
+
+ val (cname, oname, cleanup) =
+ if #debug job then
+ ("/tmp/webapp.c", "/tmp/webapp.o", fn () => ())
+ else
+ let
+ val dir = OS.FileSys.tmpName ()
+ val () = if OS.FileSys.access (dir, []) then
+ OS.FileSys.remove dir
+ else
+ ()
+ val cname = OS.Path.joinDirFile {dir = dir, file = "webapp.c"}
+ val oname = OS.Path.joinDirFile {dir = dir, file = "webapp.o"}
+ in
+ OS.FileSys.mkDir dir;
+ (cname, oname,
+ fn () => (if OS.Process.isSuccess (OS.Process.system ("rm -rf " ^ dir)) then
+ ()
+ else
+ raise Fail ("Unable to delete temporary directory " ^ dir)))
+ end
+ val ename = #exe job
+ in
+ let
+ val outf = TextIO.openOut cname
+ val s = TextIOPP.openOut {dst = outf, wid = 80}
+
+ val hasDb = List.exists (fn (Cjr.DDatabase _, _) => true | _ => false) (#1 file)
+ val libs =
+ if hasDb then
+ #link (Settings.currentDbms ())
+ else
+ ""
+ in
+ Print.fprint s (CjrPrint.p_file CjrEnv.empty file);
+ TextIO.output1 (outf, #"\n");
+ TextIO.closeOut outf;
+
+ if ErrorMsg.anyErrors () then
+ false
+ else
+ (case #sql job of
+ NONE => ()
+ | SOME sql =>
+ let
+ val outf = TextIO.openOut sql
+ val s = TextIOPP.openOut {dst = outf, wid = 80}
+ in
+ Print.fprint s (CjrPrint.p_sql CjrEnv.empty file);
+ TextIO.closeOut outf
+ end;
+
+ compileC {cname = cname, oname = oname, ename = ename, libs = libs,
+ profile = #profile job, debug = #debug job, linker = #linker job, link = #link job}
+
+ before cleanup ())
+ end
+ handle ex => (((cleanup ()) handle _ => ()); raise ex)
+ end
+
+fun compiler job =
+ if compile job then
+ ()
+ else
+ OS.Process.exit OS.Process.failure
+
+fun moduleOf fname =
+ let
+ val mrs = !moduleRoots
+ val fname = OS.Path.mkCanonical fname
+ in
+ case List.find (fn (root, _) => String.isPrefix (root ^ "/") fname) mrs of
+ NONE => capitalize (OS.Path.base (OS.Path.file fname))
+ | SOME (root, name) =>
+ let
+ val fname = OS.Path.base fname
+ val fname = String.extract (fname, size root + 1, NONE)
+ val fs = String.fields (fn ch => ch = #"/") fname
+ val fs = List.filter (CharVector.exists (fn ch => not (Char.isDigit ch))) fs
+ val fs = map capitalize fs
+ in
+ String.concatWith "." (name :: fs)
+ end
+ end
+
+end
diff --git a/src/config.sig b/src/config.sig
new file mode 100644
index 0000000..a3ad7d7
--- /dev/null
+++ b/src/config.sig
@@ -0,0 +1,23 @@
+signature CONFIG = sig
+ val builddir : string
+
+ val bin : string
+ val srclib : string
+ val lib : string
+ val includ : string
+ val sitelisp : string
+
+ val ccompiler : string
+ val ccArgs : string
+ val openssl : string
+
+ val pgheader : string
+ val msheader : string
+ val sqheader : string
+
+ val versionNumber : string
+ val versionString : string
+
+ val pthreadCflags : string
+ val pthreadLibs : string
+end
diff --git a/src/config.sml.in b/src/config.sml.in
new file mode 100644
index 0000000..ebcdb7b
--- /dev/null
+++ b/src/config.sml.in
@@ -0,0 +1,37 @@
+structure Config :> CONFIG = struct
+
+val builddir = "@abs_top_builddir@"
+
+val bin = "@BIN@"
+val srclib = "@SRCLIB@"
+val lib = "@LIB@"
+val includ = "@INCLUDE@"
+val sitelisp = "@SITELISP@"
+
+val ccompiler = "@CC@"
+val ccArgs = "@CCARGS@"
+
+val openssl = "@OPENSSL_LDFLAGS@ @OPENSSL_LIBS@"
+
+(* Something is rotten in the state of Ubuntu 11.10, so here's a manual fix that I hope doesn't break other platforms. *)
+val openssl =
+ let
+ val tokens = String.tokens Char.isSpace openssl
+ in
+ if List.exists (fn s => s = "-lssl") tokens then
+ String.concatWith " " (List.filter (fn s => s <> "-lssl") tokens @ ["-lssl"])
+ else
+ openssl
+ end
+
+val pgheader = "@PGHEADER@"
+val msheader = "@MSHEADER@"
+val sqheader = "@SQHEADER@"
+
+val versionNumber = "@VERSION@"
+val versionString = "The Ur/Web compiler, version " ^ versionNumber
+
+val pthreadCflags = "@PTHREAD_CFLAGS@"
+val pthreadLibs = "@PTHREAD_LIBS@"
+
+end
diff --git a/src/coq/Axioms.v b/src/coq/Axioms.v
new file mode 100644
index 0000000..0a0a84d
--- /dev/null
+++ b/src/coq/Axioms.v
@@ -0,0 +1,47 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+Set Implicit Arguments.
+
+
+Axiom ext_eq : forall dom ran (f g : forall x : dom, ran x),
+ (forall x, f x = g x)
+ -> f = g.
+
+Theorem ext_eq_forall : forall dom (f g : forall x : dom, Type),
+ (forall x, f x = g x)
+ -> (forall x, f x) = (forall x, g x).
+ intros.
+ rewrite (ext_eq _ f g H); reflexivity.
+Qed.
+
+Theorem ext_eq_forallS : forall dom (f g : forall x : dom, Set),
+ (forall x, f x = g x)
+ -> (forall x, f x) = (forall x, g x).
+ intros.
+ rewrite (ext_eq _ f g H); reflexivity.
+Qed.
diff --git a/src/coq/Makefile b/src/coq/Makefile
new file mode 100644
index 0000000..fc488d6
--- /dev/null
+++ b/src/coq/Makefile
@@ -0,0 +1,14 @@
+MODULES := Axioms Name Syntax Semantics
+VS := $(MODULES:%=%.v)
+
+.PHONY: coq clean
+
+coq: Makefile.coq
+ make -f Makefile.coq
+
+Makefile.coq: Makefile $(VS)
+ coq_makefile -impredicative-set $(VS) -o Makefile.coq
+
+clean:: Makefile.coq
+ make -f Makefile.coq clean
+ rm -f Makefile.coq
diff --git a/src/coq/Name.v b/src/coq/Name.v
new file mode 100644
index 0000000..6dedae6
--- /dev/null
+++ b/src/coq/Name.v
@@ -0,0 +1,31 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+Require Import String.
+
+Definition name := string.
+Definition name_eq_dec : forall x y : name, {x = y} + {x <> y} := string_dec.
diff --git a/src/coq/README b/src/coq/README
new file mode 100644
index 0000000..10cb01e
--- /dev/null
+++ b/src/coq/README
@@ -0,0 +1,3 @@
+This is a Coq formalization of a simplified version of the Ur programming language.
+
+It has only been tested with Coq version 8.3pl2.
diff --git a/src/coq/Semantics.v b/src/coq/Semantics.v
new file mode 100644
index 0000000..c334a89
--- /dev/null
+++ b/src/coq/Semantics.v
@@ -0,0 +1,232 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+Require Import Eqdep_dec.
+
+Require Import Axioms.
+Require Import Syntax.
+
+Set Implicit Arguments.
+
+
+Definition row (A : Type) : Type := name -> option A.
+
+Definition record (r : row Set) := forall n, match r n with
+ | None => unit
+ | Some T => T
+ end.
+
+Fixpoint kDen (k : kind) : Type :=
+ match k with
+ | KType => Set
+ | KName => name
+ | KArrow k1 k2 => kDen k1 -> kDen k2
+ | KRecord k1 => row (kDen k1)
+ end.
+
+Definition disjoint T (r1 r2 : row T) :=
+ forall n, match r1 n, r2 n with
+ | Some _, Some _ => False
+ | _, _ => True
+ end.
+
+Fixpoint cDen k (c : con kDen k) : kDen k :=
+ match c with
+ | CVar _ x => x
+ | Arrow c1 c2 => cDen c1 -> cDen c2
+ | Poly _ c1 => forall x, cDen (c1 x)
+ | CAbs _ _ c1 => fun x => cDen (c1 x)
+ | CApp _ _ c1 c2 => (cDen c1) (cDen c2)
+ | Name n => n
+ | TRecord c1 => record (cDen c1)
+ | CEmpty _ => fun _ => None
+ | CSingle _ c1 c2 => fun n => if name_eq_dec n (cDen c1) then Some (cDen c2) else None
+ | CConcat _ c1 c2 => fun n => match (cDen c1) n with
+ | None => (cDen c2) n
+ | v => v
+ end
+ | CMap k1 k2 => fun f r n => match r n with
+ | None => None
+ | Some T => Some (f T)
+ end
+ | TGuarded _ c1 c2 t => disjoint (cDen c1) (cDen c2) -> cDen t
+ end.
+
+Theorem subs_correct : forall k1 (c1 : con kDen k1) k2 (c2 : _ -> con kDen k2) c2',
+ subs c1 c2 c2'
+ -> cDen (c2 (cDen c1)) = cDen c2'.
+ induction 1; simpl; intuition; try (apply ext_eq_forallS || apply ext_eq);
+ repeat match goal with
+ | [ H : _ |- _ ] => rewrite H
+ end; intuition.
+Qed.
+
+Definition dvar k (c1 c2 : con kDen (KRecord k)) :=
+ disjoint (cDen c1) (cDen c2).
+
+Scheme deq_mut := Minimality for deq Sort Prop
+with disj_mut := Minimality for disj Sort Prop.
+
+Ltac deq_disj_correct scm :=
+ let t := repeat progress (simpl; intuition; subst) in
+
+ let rec use_disjoint' notDone E :=
+ match goal with
+ | [ H : disjoint _ _ |- _ ] =>
+ notDone H; generalize (H E); use_disjoint'
+ ltac:(fun H' =>
+ match H' with
+ | H => fail 1
+ | _ => notDone H'
+ end) E
+ | _ => idtac
+ end in
+ let use_disjoint := use_disjoint' ltac:(fun _ => idtac) in
+
+ apply (scm _ dvar
+ (fun k (c1 c2 : con kDen k) =>
+ cDen c1 = cDen c2)
+ (fun k (c1 c2 : con kDen (KRecord k)) =>
+ disjoint (cDen c1) (cDen c2))); t;
+ repeat ((unfold row; apply ext_eq)
+ || (match goal with
+ | [ H : _ |- _ ] => rewrite H; []
+ | [ H : subs _ _ _ |- _ ] => rewrite <- (subs_correct H)
+ end); t);
+ unfold disjoint; t;
+ repeat (match goal with
+ | [ |- context[match cDen ?C ?E with Some _ => _ | None => _ end] ] =>
+ use_disjoint E; destruct (cDen C E)
+ | [ |- context[if name_eq_dec ?N1 ?N2 then _ else _] ] =>
+ use_disjoint N1; use_disjoint N2; destruct (name_eq_dec N1 N2)
+ | [ _ : context[match cDen ?C ?E with Some _ => _ | None => _ end] |- _ ] =>
+ use_disjoint E; destruct (cDen C E)
+ | [ |- context[if ?E then _ else _] ] => destruct E
+ end; t).
+
+Hint Unfold dvar.
+
+Theorem deq_correct : forall k (c1 c2 : con kDen k),
+ deq dvar c1 c2
+ -> cDen c1 = cDen c2.
+ deq_disj_correct deq_mut.
+Qed.
+
+Theorem disj_correct : forall k (c1 c2 : con kDen (KRecord k)),
+ disj dvar c1 c2
+ -> disjoint (cDen c1) (cDen c2).
+ deq_disj_correct disj_mut.
+Qed.
+
+Definition tDen (t : con kDen KType) : Set := cDen t.
+
+Theorem name_eq_dec_refl : forall n, name_eq_dec n n = left _ (refl_equal n).
+ intros; destruct (name_eq_dec n n); intuition; [
+ match goal with
+ | [ e : _ = _ |- _ ] => rewrite (UIP_dec name_eq_dec e (refl_equal _)); reflexivity
+ end
+ | elimtype False; tauto
+ ].
+Qed.
+
+Theorem cut_disjoint : forall n1 v r,
+ disjoint (fun n => if name_eq_dec n n1 then Some v else None) r
+ -> unit = match r n1 with
+ | Some T => T
+ | None => unit
+ end.
+ intros;
+ match goal with
+ | [ H : disjoint _ _ |- _ ] => generalize (H n1)
+ end; rewrite name_eq_dec_refl;
+ destruct (r n1); intuition.
+Qed.
+
+Implicit Arguments cut_disjoint [v r].
+
+Fixpoint eDen t (e : exp dvar tDen t) : tDen t :=
+ match e in exp _ _ t return tDen t with
+ | Var _ x => x
+ | App _ _ e1 e2 => (eDen e1) (eDen e2)
+ | Abs _ _ e1 => fun x => eDen (e1 x)
+ | ECApp _ c _ _ e1 Hsub => match subs_correct Hsub in _ = T return T with
+ | refl_equal => (eDen e1) (cDen c)
+ end
+ | ECAbs _ _ e1 => fun X => eDen (e1 X)
+ | Cast _ _ Heq e1 => match deq_correct Heq in _ = T return T with
+ | refl_equal => eDen e1
+ end
+ | Empty => fun _ => tt
+ | Single c c' e1 => fun n => if name_eq_dec n (cDen c) as B
+ return (match (match (if B then _ else _) with Some _ => _ | None => _ end)
+ with Some _ => _ | None => unit end)
+ then eDen e1 else tt
+ | Proj c _ _ e1 =>
+ match name_eq_dec_refl (cDen c) in _ = B
+ return (match (match (if B then _ else _) with
+ | Some _ => _
+ | None => _ end)
+ return Set
+ with Some _ => _ | None => _ end) with
+ | refl_equal => (eDen e1) (cDen c)
+ end
+ | Cut c _ c' Hdisj e1 => fun n =>
+ match name_eq_dec n (cDen c) as B return (match (match (if B then Some _ else None) with Some _ => _ | None => (cDen c') n end)
+ with Some T => T | None => unit end
+ -> match (cDen c') n with
+ | None => unit
+ | Some T => T
+ end) with
+ | left Heq => fun _ =>
+ match sym_eq Heq in _ = n' return match cDen c' n' return Set with Some _ => _ | None => _ end with
+ | refl_equal =>
+ match cut_disjoint _ (disj_correct Hdisj) in _ = T return T with
+ | refl_equal => tt
+ end
+ end
+ | right _ => fun x => x
+ end ((eDen e1) n)
+
+ | Concat c1 c2 e1 e2 => fun n =>
+ match (cDen c1) n as D return match D with
+ | None => unit
+ | Some T => T
+ end
+ -> match (match D with
+ | None => (cDen c2) n
+ | Some v => Some v
+ end) with
+ | None => unit
+ | Some T => T
+ end with
+ | None => fun _ => (eDen e2) n
+ | _ => fun x => x
+ end ((eDen e1) n)
+
+ | Guarded _ _ _ _ e1 => fun pf => eDen (e1 pf)
+ | GuardedApp _ _ _ _ e1 Hdisj => (eDen e1) (disj_correct Hdisj)
+ end.
diff --git a/src/coq/Syntax.v b/src/coq/Syntax.v
new file mode 100644
index 0000000..03f8d82
--- /dev/null
+++ b/src/coq/Syntax.v
@@ -0,0 +1,186 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+Require Import Name.
+Export Name.
+
+Set Implicit Arguments.
+
+
+(** Syntax of Featherweight Ur *)
+
+Inductive kind : Type :=
+| KType : kind
+| KName : kind
+| KArrow : kind -> kind -> kind
+| KRecord : kind -> kind.
+
+Section vars.
+ Variable cvar : kind -> Type.
+
+ Inductive con : kind -> Type :=
+ | CVar : forall k, cvar k -> con k
+ | Arrow : con KType -> con KType -> con KType
+ | Poly : forall k, (cvar k -> con KType) -> con KType
+ | CAbs : forall k1 k2, (cvar k1 -> con k2) -> con (KArrow k1 k2)
+ | CApp : forall k1 k2, con (KArrow k1 k2) -> con k1 -> con k2
+ | Name : name -> con KName
+ | TRecord : con (KRecord KType) -> con KType
+ | CEmpty : forall k, con (KRecord k)
+ | CSingle : forall k, con KName -> con k -> con (KRecord k)
+ | CConcat : forall k, con (KRecord k) -> con (KRecord k) -> con (KRecord k)
+ | CMap : forall k1 k2, con (KArrow (KArrow k1 k2) (KArrow (KRecord k1) (KRecord k2)))
+ | TGuarded : forall k, con (KRecord k) -> con (KRecord k) -> con KType -> con KType.
+
+ Variable dvar : forall k, con (KRecord k) -> con (KRecord k) -> Type.
+
+ Section subs.
+ Variable k1 : kind.
+ Variable c1 : con k1.
+
+ Inductive subs : forall k2, (cvar k1 -> con k2) -> con k2 -> Type :=
+ | S_Unchanged : forall k2 (c2 : con k2),
+ subs (fun _ => c2) c2
+ | S_CVar : subs (fun x => CVar x) c1
+ | S_Arrow : forall c2 c3 c2' c3',
+ subs c2 c2'
+ -> subs c3 c3'
+ -> subs (fun x => Arrow (c2 x) (c3 x)) (Arrow c2' c3')
+ | S_Poly : forall k (c2 : cvar k1 -> cvar k -> _) (c2' : cvar k -> _),
+ (forall x', subs (fun x => c2 x x') (c2' x'))
+ -> subs (fun x => Poly (c2 x)) (Poly c2')
+ | S_CAbs : forall k2 k3 (c2 : cvar k1 -> cvar k2 -> con k3) (c2' : cvar k2 -> _),
+ (forall x', subs (fun x => c2 x x') (c2' x'))
+ -> subs (fun x => CAbs (c2 x)) (CAbs c2')
+ | S_CApp : forall k1 k2 (c2 : _ -> con (KArrow k1 k2)) c3 c2' c3',
+ subs c2 c2'
+ -> subs c3 c3'
+ -> subs (fun x => CApp (c2 x) (c3 x)) (CApp c2' c3')
+ | S_TRecord : forall c2 c2',
+ subs c2 c2'
+ -> subs (fun x => TRecord (c2 x)) (TRecord c2')
+ | S_CSingle : forall k2 c2 (c3 : _ -> con k2) c2' c3',
+ subs c2 c2'
+ -> subs c3 c3'
+ -> subs (fun x => CSingle (c2 x) (c3 x)) (CSingle c2' c3')
+ | S_CConcat : forall k2 (c2 c3 : _ -> con (KRecord k2)) c2' c3',
+ subs c2 c2'
+ -> subs c3 c3'
+ -> subs (fun x => CConcat (c2 x) (c3 x)) (CConcat c2' c3')
+ | S_TGuarded : forall k2 (c2 c3 : _ -> con (KRecord k2)) c4 c2' c3' c4',
+ subs c2 c2'
+ -> subs c3 c3'
+ -> subs c4 c4'
+ -> subs (fun x => TGuarded (c2 x) (c3 x) (c4 x)) (TGuarded c2' c3' c4').
+ End subs.
+
+ Inductive disj : forall k, con (KRecord k) -> con (KRecord k) -> Prop :=
+ | DVar : forall k (c1 c2 : con (KRecord k)),
+ dvar c1 c2 -> disj c1 c2
+ | DComm : forall k (c1 c2 : con (KRecord k)),
+ disj c1 c2 -> disj c2 c1
+
+ | DEmpty : forall k c2,
+ disj (CEmpty k) c2
+ | DSingleKeys : forall k X1 X2 (c1 c2 : con k),
+ X1 <> X2
+ -> disj (CSingle (Name X1) c1) (CSingle (Name X2) c2)
+ | DSingleValues : forall k n1 n2 (c1 c2 : con k) k' (c1' c2' : con k'),
+ disj (CSingle n1 c1') (CSingle n2 c2')
+ -> disj (CSingle n1 c1) (CSingle n2 c2)
+
+ | DConcat : forall k (c1 c2 c : con (KRecord k)),
+ disj c1 c
+ -> disj c2 c
+ -> disj (CConcat c1 c2) c
+
+ | DEq : forall k (c1 c2 c1' : con (KRecord k)),
+ disj c1 c2
+ -> deq c1' c1
+ -> disj c1' c2
+
+ with deq : forall k, con k -> con k -> Prop :=
+ | Eq_Beta : forall k1 k2 (c1 : cvar k1 -> con k2) c2 c1',
+ subs c2 c1 c1'
+ -> deq (CApp (CAbs c1) c2) c1'
+ | Eq_Refl : forall k (c : con k),
+ deq c c
+ | Eq_Comm : forall k (c1 c2 : con k),
+ deq c2 c1
+ -> deq c1 c2
+ | Eq_Trans : forall k (c1 c2 c3 : con k),
+ deq c1 c2
+ -> deq c2 c3
+ -> deq c1 c3
+ | Eq_Cong : forall k1 k2 c1 c1' (c2 : cvar k1 -> con k2) c2' c2'',
+ deq c1 c1'
+ -> subs c1 c2 c2'
+ -> subs c1' c2 c2''
+ -> deq c2' c2''
+
+ | Eq_Concat_Empty : forall k c,
+ deq (CConcat (CEmpty k) c) c
+ | Eq_Concat_Comm : forall k (c1 c2 c3 : con (KRecord k)),
+ disj c1 c2
+ -> deq (CConcat c1 c2) (CConcat c2 c1)
+ | Eq_Concat_Assoc : forall k (c1 c2 c3 : con (KRecord k)),
+ deq (CConcat c1 (CConcat c2 c3)) (CConcat (CConcat c1 c2) c3)
+
+ | Eq_Map_Empty : forall k1 k2 f,
+ deq (CApp (CApp (CMap k1 k2) f) (CEmpty _)) (CEmpty _)
+ | Eq_Map_Cons : forall k1 k2 f c1 c2 c3,
+ disj (CSingle c1 c2) c3
+ -> deq (CApp (CApp (CMap k1 k2) f) (CConcat (CSingle c1 c2) c3))
+ (CConcat (CSingle c1 (CApp f c2)) (CApp (CApp (CMap k1 k2) f) c3))
+
+ | Eq_Map_Ident : forall k c,
+ deq (CApp (CApp (CMap k k) (CAbs (fun x => CVar x))) c) c
+ | Eq_Map_Dist : forall k1 k2 f c1 c2,
+ deq (CApp (CApp (CMap k1 k2) f) (CConcat c1 c2))
+ (CConcat (CApp (CApp (CMap k1 k2) f) c1) (CApp (CApp (CMap k1 k2) f) c2))
+ | Eq_Map_Fuse : forall k1 k2 k3 f f' c,
+ deq (CApp (CApp (CMap k2 k3) f')
+ (CApp (CApp (CMap k1 k2) f) c))
+ (CApp (CApp (CMap k1 k3) (CAbs (fun x => CApp f' (CApp f (CVar x))))) c).
+
+ Variable evar : con KType -> Type.
+
+ Inductive exp : con KType -> Type :=
+ | Var : forall t, evar t -> exp t
+ | App : forall dom ran, exp (Arrow dom ran) -> exp dom -> exp ran
+ | Abs : forall dom ran, (evar dom -> exp ran) -> exp (Arrow dom ran)
+ | ECApp : forall k (dom : con k) ran ran', exp (Poly ran) -> subs dom ran ran' -> exp ran'
+ | ECAbs : forall k (ran : cvar k -> _), (forall X, exp (ran X)) -> exp (Poly ran)
+ | Cast : forall t1 t2, deq t1 t2 -> exp t1 -> exp t2
+ | Empty : exp (TRecord (CEmpty _))
+ | Single : forall c t, exp t -> exp (TRecord (CConcat (CSingle c t) (CEmpty _)))
+ | Proj : forall c t c', exp (TRecord (CConcat (CSingle c t) c')) -> exp t
+ | Cut : forall c t c', disj (CSingle c t) c' -> exp (TRecord (CConcat (CSingle c t) c')) -> exp (TRecord c')
+ | Concat : forall c1 c2, exp (TRecord c1) -> exp (TRecord c2) -> exp (TRecord (CConcat c1 c2))
+ | Guarded : forall k (c1 c2 : con (KRecord k)) c, (dvar c1 c2 -> exp c) -> exp (TGuarded c1 c2 c)
+ | GuardedApp : forall k (c1 c2 : con (KRecord k)) t, exp (TGuarded c1 c2 t) -> disj c1 c2 -> exp t.
+End vars.
diff --git a/src/core.sml b/src/core.sml
new file mode 100644
index 0000000..8f57c31
--- /dev/null
+++ b/src/core.sml
@@ -0,0 +1,146 @@
+(* Copyright (c) 2008, 2013, 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 Core = struct
+
+type 'a located = 'a ErrorMsg.located
+
+datatype kind' =
+ KType
+ | KArrow of kind * kind
+ | KName
+ | KRecord of kind
+ | KUnit
+ | KTuple of kind list
+
+ | KRel of int
+ | KFun of string * kind
+
+withtype kind = kind' located
+
+datatype con' =
+ TFun of con * con
+ | TCFun of string * kind * con
+ | TRecord of con
+
+ | CRel of int
+ | CNamed of int
+ | CFfi of string * string
+ | CApp of con * con
+ | CAbs of string * kind * con
+
+ | CKAbs of string * con
+ | CKApp of con * kind
+ | TKFun of string * con
+
+ | CName of string
+
+ | CRecord of kind * (con * con) list
+ | CConcat of con * con
+ | CMap of kind * kind
+
+ | CUnit
+
+ | CTuple of con list
+ | CProj of con * int
+
+withtype con = con' located
+
+datatype datatype_kind = datatype DatatypeKind.datatype_kind
+
+datatype patCon =
+ PConVar of int
+ | PConFfi of {mod : string, datatyp : string, params : string list,
+ con : string, arg : con option, kind : datatype_kind}
+
+datatype pat' =
+ PVar of string * con
+ | PPrim of Prim.t
+ | PCon of datatype_kind * patCon * con list * pat option
+ | PRecord of (string * pat * con) list
+
+withtype pat = pat' located
+
+datatype failure_mode = datatype Settings.failure_mode
+
+datatype exp' =
+ EPrim of Prim.t
+ | ERel of int
+ | ENamed of int
+ | ECon of datatype_kind * patCon * con list * exp option
+ | EFfi of string * string
+ | EFfiApp of string * string * (exp * con) list
+ | EApp of exp * exp
+ | EAbs of string * con * con * exp
+ | ECApp of exp * con
+ | ECAbs of string * kind * exp
+
+ | EKAbs of string * exp
+ | EKApp of exp * kind
+
+ | ERecord of (con * exp * con) list
+ | EField of exp * con * { field : con, rest : con }
+ | EConcat of exp * con * exp * con
+ | ECut of exp * con * { field : con, rest : con }
+ | ECutMulti of exp * con * { rest : con }
+
+ | ECase of exp * (pat * exp) list * { disc : con, result : con }
+
+ | EWrite of exp
+
+ | EClosure of int * exp list
+
+ | ELet of string * con * exp * exp
+
+ | EServerCall of int * exp list * con * failure_mode
+
+withtype exp = exp' located
+
+datatype effect = datatype Export.effect
+datatype export_kind = datatype Export.export_kind
+
+datatype decl' =
+ DCon of string * int * kind * con
+ | DDatatype of (string * int * string list * (string * int * con option) list) list
+ | DVal of string * int * con * exp * string
+ | DValRec of (string * int * con * exp * string) list
+ | DExport of export_kind * int * bool
+ | DTable of string * int * con * string * exp * con * exp * con
+ | DSequence of string * int * string
+ | DView of string * int * string * exp * con
+ | DDatabase of string
+ | DCookie of string * int * con * string
+ | DStyle of string * int * string
+ | DTask of exp * exp
+ | DPolicy of exp
+ | DOnError of int
+
+withtype decl = decl' located
+
+type file = decl list
+
+end
diff --git a/src/core_env.sig b/src/core_env.sig
new file mode 100644
index 0000000..9377373
--- /dev/null
+++ b/src/core_env.sig
@@ -0,0 +1,72 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature CORE_ENV = sig
+
+ val liftConInCon : int -> Core.con -> Core.con
+ val subConInCon : (int * Core.con) -> Core.con -> Core.con
+
+ val liftConInExp : int -> Core.exp -> Core.exp
+ val subConInExp : (int * Core.con) -> Core.exp -> Core.exp
+
+ val liftExpInExp : int -> Core.exp -> Core.exp
+ val subExpInExp : (int * Core.exp) -> Core.exp -> Core.exp
+
+ type env
+
+ val empty : env
+
+ exception UnboundRel of int
+ exception UnboundNamed of int
+
+ val pushKRel : env -> string -> env
+ val lookupKRel : env -> int -> string
+
+ val pushCRel : env -> string -> Core.kind -> env
+ val lookupCRel : env -> int -> string * Core.kind
+
+ val pushCNamed : env -> string -> int -> Core.kind -> Core.con option -> env
+ val lookupCNamed : env -> int -> string * Core.kind * Core.con option
+
+ val pushDatatype : env -> string -> int -> string list -> (string * int * Core.con option) list -> env
+ val lookupDatatype : env -> int -> string * string list * (string * int * Core.con option) list
+
+ val lookupConstructor : env -> int -> string * string list * Core.con option * int
+
+ val pushERel : env -> string -> Core.con -> env
+ val lookupERel : env -> int -> string * Core.con
+
+ val pushENamed : env -> string -> int -> Core.con -> Core.exp option -> string -> env
+ val lookupENamed : env -> int -> string * Core.con * Core.exp option * string
+
+ val declBinds : env -> Core.decl -> env
+ val patBinds : env -> Core.pat -> env
+
+ val patBindsN : Core.pat -> int
+ val patBindsL : Core.pat -> (string * Core.con) list
+
+end
diff --git a/src/core_env.sml b/src/core_env.sml
new file mode 100644
index 0000000..7d78bde
--- /dev/null
+++ b/src/core_env.sml
@@ -0,0 +1,379 @@
+(* Copyright (c) 2008, 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 CoreEnv :> CORE_ENV = struct
+
+open Core
+
+structure U = CoreUtil
+
+structure IM = IntBinaryMap
+
+
+(* AST utility functions *)
+
+val liftKindInKind =
+ U.Kind.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ bind = fn (bound, _) => bound + 1}
+
+val liftKindInCon =
+ U.Con.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ con = fn _ => fn c => c,
+ bind = fn (bound, U.Con.RelK _) => bound + 1
+ | (bound, _) => bound}
+
+val liftKindInExp =
+ U.Exp.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ con = fn _ => fn c => c,
+ exp = fn _ => fn e => e,
+ bind = fn (bound, U.Exp.RelK _) => bound + 1
+ | (bound, _) => bound}
+
+val liftConInCon =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + 1)
+ | _ => c,
+ bind = fn (bound, U.Con.RelC _) => bound + 1
+ | (bound, _) => bound}
+
+val lift = liftConInCon 0
+
+val subConInCon =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn (xn, rep) => fn c =>
+ case c of
+ CRel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 rep
+ | GREATER => CRel (xn' - 1)
+ | LESS => c)
+ | _ => c,
+ bind = fn ((xn, rep), U.Con.RelC _) => (xn+1, liftConInCon 0 rep)
+ | (ctx, _) => ctx}
+
+
+val liftConInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + 1)
+ | _ => c,
+ exp = fn _ => fn e => e,
+ bind = fn (bound, U.Exp.RelC _) => bound + 1
+ | (bound, _) => bound}
+
+val subConInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn (xn, rep) => fn c =>
+ case c of
+ CRel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 rep
+ | GREATER => CRel (xn' - 1)
+ | LESS => c)
+ | _ => c,
+ exp = fn _ => fn e => e,
+ bind = fn ((xn, rep), U.Exp.RelC _) => (xn+1, liftConInCon 0 rep)
+ | (ctx, _) => ctx}
+
+val liftExpInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn bound => fn e =>
+ case e of
+ ERel xn =>
+ if xn < bound then
+ e
+ else
+ ERel (xn + 1)
+ | _ => e,
+ bind = fn (bound, U.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+
+val subExpInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn (xn, rep) => fn e =>
+ case e of
+ ERel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 rep
+ | GREATER=> ERel (xn' - 1)
+ | LESS => e)
+ | _ => e,
+ bind = fn ((xn, rep), U.Exp.RelE _) => (xn+1, liftExpInExp 0 rep)
+ | ((xn, rep), U.Exp.RelC _) => (xn, liftConInExp 0 rep)
+ | (ctx, _) => ctx}
+
+(* Back to environments *)
+
+exception UnboundRel of int
+exception UnboundNamed of int
+
+type env = {
+ relK : string list,
+
+ relC : (string * kind) list,
+ namedC : (string * kind * con option) IM.map,
+
+ datatypes : (string * string list * (string * int * con option) list) IM.map,
+ constructors : (string * string list * con option * int) IM.map,
+
+ relE : (string * con) list,
+ namedE : (string * con * exp option * string) IM.map
+}
+
+val empty = {
+ relK = [],
+
+ relC = [],
+ namedC = IM.empty,
+
+ datatypes = IM.empty,
+ constructors = IM.empty,
+
+ relE = [],
+ namedE = IM.empty
+}
+
+fun pushKRel (env : env) x =
+ {relK = x :: #relK env,
+
+ relC = map (fn (x, k) => (x, liftKindInKind 0 k)) (#relC env),
+ namedC = #namedC env,
+
+ relE = map (fn (x, c) => (x, liftKindInCon 0 c)) (#relE env),
+ namedE = #namedE env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env
+ }
+
+fun lookupKRel (env : env) n =
+ (List.nth (#relK env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushCRel (env : env) x k =
+ {relK = #relK env,
+
+ relC = (x, k) :: #relC env,
+ namedC = IM.map (fn (x, k, co) => (x, k, Option.map lift co)) (#namedC env),
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ relE = map (fn (x, c) => (x, lift c)) (#relE env),
+ namedE = IM.map (fn (x, c, eo, s) => (x, lift c, eo, s)) (#namedE env)}
+
+fun lookupCRel (env : env) n =
+ (List.nth (#relC env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushCNamed (env : env) x n k co =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = IM.insert (#namedC env, n, (x, k, co)),
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ relE = #relE env,
+ namedE = #namedE env}
+
+fun lookupCNamed (env : env) n =
+ case IM.find (#namedC env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushDatatype (env : env) x n xs xncs =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = IM.insert (#datatypes env, n, (x, xs, xncs)),
+ constructors = foldl (fn ((x, n', to), constructors) =>
+ IM.insert (constructors, n', (x, xs, to, n)))
+ (#constructors env) xncs,
+
+ relE = #relE env,
+ namedE = #namedE env}
+
+fun lookupDatatype (env : env) n =
+ case IM.find (#datatypes env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupConstructor (env : env) n =
+ case IM.find (#constructors env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushERel (env : env) x t =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ relE = (x, t) :: #relE env,
+ namedE = #namedE env}
+
+fun lookupERel (env : env) n =
+ (List.nth (#relE env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushENamed (env : env) x n t eo s =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ relE = #relE env,
+ namedE = IM.insert (#namedE env, n, (x, t, eo, s))}
+
+fun lookupENamed (env : env) n =
+ case IM.find (#namedE env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun declBinds env (d, loc) =
+ case d of
+ DCon (x, n, k, c) => pushCNamed env x n k (SOME c)
+ | DDatatype dts =>
+ foldl (fn ((x, n, xs, xncs), env) =>
+ let
+ val env = pushDatatype env x n xs xncs
+ val env = pushCNamed env x n (KType, loc) NONE
+ in
+ foldl (fn ((x', n', NONE), env) => pushENamed env x' n' (CNamed n, loc) NONE ""
+ | ((x', n', SOME t), env) => pushENamed env x' n' (TFun (t, (CNamed n, loc)), loc) NONE "")
+ env xncs
+ end) env dts
+ | DVal (x, n, t, e, s) => pushENamed env x n t (SOME e) s
+ | DValRec vis => foldl (fn ((x, n, t, e, s), env) => pushENamed env x n t NONE s) env vis
+ | DExport _ => env
+ | DTable (x, n, c, s, _, pc, _, cc) =>
+ let
+ val ct = (CFfi ("Basis", "sql_table"), loc)
+ val ct = (CApp (ct, c), loc)
+ val ct = (CApp (ct, (CConcat (pc, cc), loc)), loc)
+ in
+ pushENamed env x n ct NONE s
+ end
+ | DSequence (x, n, s) =>
+ let
+ val t = (CFfi ("Basis", "sql_sequence"), loc)
+ in
+ pushENamed env x n t NONE s
+ end
+ | DView (x, n, s, _, c) =>
+ let
+ val ct = (CFfi ("Basis", "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ pushENamed env x n ct NONE s
+ end
+ | DDatabase _ => env
+ | DCookie (x, n, c, s) =>
+ let
+ val t = (CApp ((CFfi ("Basis", "http_cookie"), loc), c), loc)
+ in
+ pushENamed env x n t NONE s
+ end
+ | DStyle (x, n, s) =>
+ let
+ val t = (CFfi ("Basis", "css_class"), loc)
+ in
+ pushENamed env x n t NONE s
+ end
+ | DTask _ => env
+ | DPolicy _ => env
+ | DOnError _ => env
+
+fun patBinds env (p, loc) =
+ case p of
+ PVar (x, t) => pushERel env x t
+ | PPrim _ => env
+ | PCon (_, _, _, NONE) => env
+ | PCon (_, _, _, SOME p) => patBinds env p
+ | PRecord xps => foldl (fn ((_, p, _), env) => patBinds env p) env xps
+
+fun patBindsN (p, loc) =
+ case p of
+ PVar _ => 1
+ | PPrim _ => 0
+ | PCon (_, _, _, NONE) => 0
+ | PCon (_, _, _, SOME p) => patBindsN p
+ | PRecord xps => foldl (fn ((_, p, _), count) => count + patBindsN p) 0 xps
+
+fun patBindsL (p, loc) =
+ case p of
+ PVar (x, t) => [(x, t)]
+ | PPrim _ => []
+ | PCon (_, _, _, NONE) => []
+ | PCon (_, _, _, SOME p) => patBindsL p
+ | PRecord xps => rev (ListUtil.mapConcat (rev o patBindsL o #2) xps)
+
+end
diff --git a/src/core_print.sig b/src/core_print.sig
new file mode 100644
index 0000000..aee3717
--- /dev/null
+++ b/src/core_print.sig
@@ -0,0 +1,41 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing Ur/Web internal language *)
+
+signature CORE_PRINT = sig
+ val p_kind : CoreEnv.env -> Core.kind Print.printer
+ val p_con : CoreEnv.env -> Core.con Print.printer
+ val p_patCon : CoreEnv.env -> Core.patCon Print.printer
+ val p_pat : CoreEnv.env -> Core.pat Print.printer
+ val p_exp : CoreEnv.env -> Core.exp Print.printer
+ val p_decl : CoreEnv.env -> Core.decl Print.printer
+ val p_file : CoreEnv.env -> Core.file Print.printer
+
+ val debug : bool ref
+end
+
diff --git a/src/core_print.sml b/src/core_print.sml
new file mode 100644
index 0000000..5c71e97
--- /dev/null
+++ b/src/core_print.sml
@@ -0,0 +1,643 @@
+(* Copyright (c) 2008-2011, 2013, 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.
+ *)
+
+(* Pretty-printing core Ur/Web *)
+
+structure CorePrint :> CORE_PRINT = struct
+
+open Print.PD
+open Print
+
+open Core
+
+structure E = CoreEnv
+
+val debug = ref false
+
+fun p_kind' par env (k, _) =
+ case k of
+ KType => string "Type"
+ | KArrow (k1, k2) => parenIf par (box [p_kind' true env k1,
+ space,
+ string "->",
+ space,
+ p_kind env k2])
+ | KName => string "Name"
+ | KRecord k => box [string "{", p_kind env k, string "}"]
+ | KUnit => string "Unit"
+ | KTuple ks => box [string "(",
+ p_list_sep (box [space, string "*", space]) (p_kind env) ks,
+ string ")"]
+
+ | KRel n => ((if !debug then
+ string (E.lookupKRel env n ^ "_" ^ Int.toString n)
+ else
+ string (E.lookupKRel env n))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | KFun (x, k) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_kind (E.pushKRel env x) k]
+
+and p_kind env = p_kind' false env
+
+fun p_con' par env (c, _) =
+ case c of
+ TFun (t1, t2) => parenIf par (box [p_con' true env t1,
+ space,
+ string "->",
+ space,
+ p_con env t2])
+ | TCFun (x, k, c) => parenIf par (box [string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "->",
+ space,
+ p_con (E.pushCRel env x k) c])
+ | TRecord (CRecord (_, xcs), _) => box [string "{",
+ p_list (fn (x, c) =>
+ box [p_name env x,
+ space,
+ string ":",
+ space,
+ p_con env c]) xcs,
+ string "}"]
+ | TRecord c => box [string "$",
+ p_con' true env c]
+
+ | CRel n =>
+ ((if !debug then
+ string (#1 (E.lookupCRel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupCRel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_" ^ Int.toString n))
+ | CNamed n =>
+ ((if !debug then
+ string (#1 (E.lookupCNamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupCNamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUNDN_" ^ Int.toString n))
+ | CFfi (m, x) => box [string "FFI(", string m, string ".", string x, string ")"]
+
+ | CApp (c1, c2) => parenIf par (box [p_con env c1,
+ space,
+ p_con' true env c2])
+ | CAbs (x, k, c) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=>",
+ space,
+ p_con (E.pushCRel env x k) c])
+
+ | CName s => box [string "#", string s]
+
+ | CRecord (k, xcs) =>
+ if !debug then
+ parenIf par (box [string "[",
+ p_list (fn (x, c) =>
+ box [p_con env x,
+ space,
+ string "=",
+ space,
+ p_con env c]) xcs,
+ string "]::",
+ p_kind env k])
+ else
+ parenIf par (box [string "[",
+ p_list (fn (x, c) =>
+ box [p_con env x,
+ space,
+ string "=",
+ space,
+ p_con env c]) xcs,
+ string "]"])
+ | CConcat (c1, c2) => parenIf par (box [p_con' true env c1,
+ space,
+ string "++",
+ space,
+ p_con env c2])
+ | CMap _ => string "map"
+ | CUnit => string "()"
+
+ | CTuple cs => box [string "(",
+ p_list (p_con env) cs,
+ string ")"]
+ | CProj (c, n) => box [p_con env c,
+ string ".",
+ string (Int.toString n)]
+
+ | CKAbs (x, c) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_con (E.pushKRel env x) c]
+ | CKApp (c, k) => box [p_con env c,
+ string "[[",
+ p_kind env k,
+ string "]]"]
+ | TKFun (x, c) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_con (E.pushKRel env x) c]
+
+and p_con env = p_con' false env
+
+and p_name env (all as (c, _)) =
+ case c of
+ CName s => string s
+ | _ => p_con env all
+
+fun p_enamed env n =
+ (if !debug then
+ string (#1 (E.lookupENamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupENamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUNDN_" ^ Int.toString n)
+
+fun p_con_named env n =
+ (if !debug then
+ string (#1 (E.lookupConstructor env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupConstructor env n)))
+ handle E.UnboundNamed _ => string ("CONSTRUCTOR_" ^ Int.toString n)
+
+fun p_patCon env pc =
+ case pc of
+ PConVar n => p_con_named env n
+ | PConFfi {mod = m, con, arg, params, ...} =>
+ if !debug then
+ box [string "FFIC[",
+ case arg of
+ NONE => box []
+ | SOME t =>
+ let
+ val k = (KType, ErrorMsg.dummySpan)
+ val env' = foldl (fn (x, env) => E.pushCRel env x k) env params
+ in
+ p_con env' t
+ end,
+ string "](",
+ string m,
+ string ".",
+ string con,
+ string ")"]
+ else
+ box [string "FFIC(",
+ string m,
+ string ".",
+ string con,
+ string ")"]
+
+fun p_pat' par env (p, _) =
+ case p of
+ PVar (s, _) => string s
+ | PPrim p => Prim.p_t p
+ | PCon (_, n, _, NONE) => p_patCon env n
+ | PCon (_, n, _, SOME p) => parenIf par (box [p_patCon env n,
+ space,
+ p_pat' true env p])
+ | PRecord xps =>
+ box [string "{",
+ p_list_sep (box [string ",", space]) (fn (x, p, t) =>
+ box [string x,
+ space,
+ string "=",
+ space,
+ p_pat env p,
+ if !debug then
+ box [space,
+ string ":",
+ space,
+ p_con env t]
+ else
+ box []]) xps,
+ string "}"]
+
+and p_pat x = p_pat' false x
+
+fun p_exp' par env (e, _) =
+ case e of
+ EPrim p => Prim.p_t p
+ | ERel n =>
+ ((if !debug then
+ string (#1 (E.lookupERel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupERel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_" ^ Int.toString n))
+ | ENamed n => p_enamed env n
+ | ECon (_, pc, ts, NONE) => box [string "[",
+ p_patCon env pc,
+ p_list_sep (box []) (fn t => box [space, string "[", p_con env t, string "]"]) ts,
+ string "]"]
+ | ECon (_, pc, ts, SOME e) => box [string "[",
+ p_patCon env pc,
+ space,
+ p_exp' true env e,
+ p_list_sep (box []) (fn t => box [space, string "[", p_con env t, string "]"]) ts,
+ string "]"]
+ | EFfi (m, x) => box [string "FFI(", string m, string ".", string x, string ")"]
+ | EFfiApp (m, x, es) => box [string "FFI(",
+ string m,
+ string ".",
+ string x,
+ string "(",
+ p_list (p_exp env o #1) es,
+ string "))"]
+ | EApp (e1, e2) => parenIf par (box [p_exp' true env e1,
+ space,
+ p_exp' true env e2])
+ | EAbs (x, t, _, e) => parenIf par (box [string "(fn",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushERel env x t) e,
+ string ")"])
+ | ECApp (e, c) => parenIf par (box [p_exp env e,
+ space,
+ string "[",
+ p_con env c,
+ string "]"])
+ | ECAbs (x, k, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushCRel env x k) e])
+
+ | ERecord xes => box [string "{",
+ p_list (fn (x, e, _) =>
+ box [p_name env x,
+ space,
+ string "=",
+ space,
+ p_exp env e]) xes,
+ string "}"]
+ | EField (e, c, {field, rest}) =>
+ if !debug then
+ box [p_exp' true env e,
+ string ".",
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env field,
+ space,
+ string " in ",
+ space,
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ string ".",
+ p_con' true env c]
+ | EConcat (e1, c1, e2, c2) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e1,
+ space,
+ string ":",
+ space,
+ p_con env c1,
+ space,
+ string "++",
+ space,
+ p_exp' true env e2,
+ space,
+ string ":",
+ space,
+ p_con env c2]
+ else
+ box [p_exp' true env e1,
+ space,
+ string "with",
+ space,
+ p_exp' true env e2])
+ | ECut (e, c, {field, rest}) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e,
+ space,
+ string "--",
+ space,
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env field,
+ space,
+ string " in ",
+ space,
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ space,
+ string "--",
+ space,
+ p_con' true env c])
+ | ECutMulti (e, c, {rest}) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e,
+ space,
+ string "---",
+ space,
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ space,
+ string "---",
+ space,
+ p_con' true env c])
+
+ | ECase (e, pes, {disc, result}) =>
+ parenIf par (box [string "case",
+ space,
+ p_exp env e,
+ space,
+ if !debug then
+ box [string "in",
+ space,
+ p_con env disc,
+ space,
+ string "return",
+ space,
+ p_con env result,
+ space]
+ else
+ box [],
+ string "of",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (p, e) => box [p_pat env p,
+ space,
+ string "=>",
+ space,
+ p_exp (E.patBinds env p) e]) pes])
+
+ | EWrite e => box [string "write(",
+ p_exp env e,
+ string ")"]
+
+ | EClosure (n, es) => box [string "CLOSURE(",
+ p_enamed env n,
+ p_list_sep (string "") (fn e => box [string ", ",
+ p_exp env e]) es,
+ string ")"]
+
+ | ELet (x, t, e1, e2) => box [string "let",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e1,
+ space,
+ string "in",
+ newline,
+ p_exp (E.pushERel env x t) e2]
+
+ | EServerCall (n, es, _, _) => box [string "Server(",
+ p_enamed env n,
+ string ",",
+ space,
+ p_list (p_exp env) es,
+ string ")"]
+
+ | EKAbs (x, e) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_exp (E.pushKRel env x) e]
+ | EKApp (e, k) => box [p_exp env e,
+ string "[[",
+ p_kind env k,
+ string "]]"]
+
+and p_exp env = p_exp' false env
+
+fun p_named x n =
+ if !debug then
+ box [string x,
+ string "__",
+ string (Int.toString n)]
+ else
+ string x
+
+fun p_vali env (x, n, t, e, s) =
+ let
+ val xp = p_named x n
+ in
+ box [xp,
+ space,
+ string "as",
+ space,
+ string s,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e]
+ end
+
+fun p_datatype env (x, n, xs, cons) =
+ let
+ val k = (KType, ErrorMsg.dummySpan)
+ val env = E.pushCNamed env x n (KType, ErrorMsg.dummySpan) NONE
+ val env = foldl (fn (x, env) => E.pushCRel env x k) env xs
+
+ val xp = if !debug then
+ string (x ^ "__" ^ Int.toString n)
+ else
+ string x
+ in
+ box [xp,
+ p_list_sep (box []) (fn x => box [space, string x]) xs,
+ space,
+ string "=",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (x, n, NONE) => if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x
+ | (x, n, SOME t) => box [if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x, space, string "of", space, p_con env t])
+ cons]
+ end
+
+fun p_decl env (dAll as (d, _) : decl) =
+ case d of
+ DCon (x, n, k, c) =>
+ let
+ val xp = if !debug then
+ box [string x,
+ string "__",
+ string (Int.toString n)]
+ else
+ string x
+ in
+ box [string "con",
+ space,
+ xp,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=",
+ space,
+ p_con env c]
+ end
+ | DDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) (p_datatype (E.declBinds env dAll)) x]
+ | DVal vi => box [string "val",
+ space,
+ p_vali env vi]
+ | DValRec vis =>
+ let
+ val env = E.declBinds env dAll
+ in
+ box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) (p_vali env) vis]
+ end
+ | DExport (ek, n, _) => box [string "export",
+ space,
+ Export.p_export_kind ek,
+ space,
+ p_enamed env n,
+ space,
+ string "as",
+ space,
+ (p_con env (#2 (E.lookupENamed env n))
+ handle E.UnboundNamed _ => string "UNBOUND")]
+ | DTable (x, n, c, s, pe, _, ce, _) => box [string "table",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ string s,
+ space,
+ string ":",
+ space,
+ p_con env c,
+ space,
+ string "keys",
+ space,
+ p_exp env pe,
+ space,
+ string "constraints",
+ space,
+ p_exp (E.declBinds env dAll) ce]
+ | DSequence (x, n, s) => box [string "sequence",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ string s]
+ | DView (x, n, s, e, _) => box [string "view",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ p_exp env e]
+ | DDatabase s => box [string "database",
+ space,
+ string s]
+ | DCookie (x, n, c, s) => box [string "cookie",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ string s,
+ space,
+ string ":",
+ space,
+ p_con env c]
+ | DStyle (x, n, s) => box [string "style",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ string s]
+ | DTask (e1, e2) => box [string "task",
+ space,
+ p_exp env e1,
+ space,
+ string "=",
+ space,
+ p_exp env e2]
+ | DPolicy e1 => box [string "policy",
+ space,
+ p_exp env e1]
+ | DOnError _ => string "ONERROR"
+
+fun p_file env file =
+ let
+ val (pds, _) = ListUtil.foldlMap (fn (d, env) =>
+ (p_decl env d,
+ E.declBinds env d))
+ env file
+ in
+ p_list_sep newline (fn x => x) pds
+ end
+
+end
diff --git a/src/core_untangle.sig b/src/core_untangle.sig
new file mode 100644
index 0000000..86e039e
--- /dev/null
+++ b/src/core_untangle.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature CORE_UNTANGLE = sig
+
+ val untangle : Core.file -> Core.file
+
+end
diff --git a/src/core_untangle.sml b/src/core_untangle.sml
new file mode 100644
index 0000000..a3bb559
--- /dev/null
+++ b/src/core_untangle.sml
@@ -0,0 +1,237 @@
+(* Copyright (c) 2008, 2013, 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 CoreUntangle :> CORE_UNTANGLE = struct
+
+open Core
+
+structure U = CoreUtil
+structure E = CoreEnv
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+fun default (k, s) = s
+
+fun exp thisGroup (e, s) =
+ let
+ fun try n =
+ if IS.member (thisGroup, n) then
+ IS.add (s, n)
+ else
+ s
+ in
+ case e of
+ ENamed n => try n
+ | EClosure (n, _) => try n
+ | EServerCall (n, _, _, _) => try n
+ | _ => s
+ end
+
+fun untangle file =
+ let
+ fun expUsed thisGroup = U.Exp.fold {con = default,
+ kind = default,
+ exp = exp thisGroup} IS.empty
+
+ fun decl (dAll as (d, loc)) =
+ case d of
+ DValRec vis =>
+ let
+ val thisGroup = foldl (fn ((_, n, _, _, _), thisGroup) =>
+ IS.add (thisGroup, n)) IS.empty vis
+
+ val edefs = foldl (fn ((_, n, _, e, _), edefs) =>
+ IM.insert (edefs, n, expUsed thisGroup e))
+ IM.empty vis
+
+ val used = edefs
+
+ fun expand used =
+ IS.foldl (fn (n, used) =>
+ case IM.find (edefs, n) of
+ NONE => used
+ | SOME usedHere =>
+ if IS.isEmpty (IS.difference (usedHere, used)) then
+ used
+ else
+ expand (IS.union (usedHere, used)))
+ used used
+
+ fun p_graph reachable =
+ IM.appi (fn (n, reachableHere) =>
+ (print (Int.toString n);
+ print ":";
+ IS.app (fn n' => (print " ";
+ print (Int.toString n'))) reachableHere;
+ print "\n")) reachable
+
+ (*val () = print "used:\n"
+ val () = p_graph used*)
+
+ fun expand reachable =
+ let
+ val changed = ref false
+
+ val reachable =
+ IM.mapi (fn (n, reachableHere) =>
+ IS.foldl (fn (n', reachableHere) =>
+ let
+ val more = valOf (IM.find (reachable, n'))
+ in
+ if IS.isEmpty (IS.difference (more, reachableHere)) then
+ reachableHere
+ else
+ (changed := true;
+ IS.union (more, reachableHere))
+ end)
+ reachableHere reachableHere) reachable
+ in
+ (reachable, !changed)
+ end
+
+ fun iterate reachable =
+ let
+ val (reachable, changed) = expand reachable
+ in
+ if changed then
+ iterate reachable
+ else
+ reachable
+ end
+
+ val reachable = iterate used
+
+ (*val () = print "reachable:\n"
+ val () = p_graph reachable*)
+
+ fun sccs (nodes, acc) =
+ case IS.find (fn _ => true) nodes of
+ NONE => acc
+ | SOME rep =>
+ let
+ val reachableHere = valOf (IM.find (reachable, rep))
+
+ val (nodes, scc) = IS.foldl (fn (node, (nodes, scc)) =>
+ if node = rep then
+ (nodes, scc)
+ else
+ let
+ val reachableThere =
+ valOf (IM.find (reachable, node))
+ in
+ if IS.member (reachableThere, rep) then
+ (IS.delete (nodes, node),
+ IS.add (scc, node))
+ else
+ (nodes, scc)
+ end)
+ (IS.delete (nodes, rep), IS.singleton rep) reachableHere
+ in
+ sccs (nodes, scc :: acc)
+ end
+
+ val sccs = sccs (thisGroup, [])
+
+ (*val () = app (fn nodes => (print "SCC:";
+ IS.app (fn i => (print " ";
+ print (Int.toString i))) nodes;
+ print "\n")) sccs*)
+
+ fun depends nodes1 nodes2 =
+ let
+ val node1 = valOf (IS.find (fn _ => true) nodes1)
+ val node2 = valOf (IS.find (fn _ => true) nodes2)
+ val reachable1 = valOf (IM.find (reachable, node1))
+ in
+ IS.member (reachable1, node2)
+ end
+
+ fun findReady (sccs, passed) =
+ case sccs of
+ [] => raise Fail "Untangle: Unable to topologically sort 'val rec'"
+ | nodes :: sccs =>
+ if List.exists (depends nodes) passed
+ orelse List.exists (depends nodes) sccs then
+ findReady (sccs, nodes :: passed)
+ else
+ (nodes, List.revAppend (passed, sccs))
+
+ fun topo (sccs, acc) =
+ case sccs of
+ [] => rev acc
+ | _ =>
+ let
+ val (node, sccs) = findReady (sccs, [])
+ in
+ topo (sccs, node :: acc)
+ end
+
+ val sccs = topo (sccs, [])
+
+ (*val () = app (fn nodes => (print "SCC':";
+ IS.app (fn i => (print " ";
+ print (Int.toString i))) nodes;
+ print "\n")) sccs*)
+
+ fun isNonrec nodes =
+ case IS.find (fn _ => true) nodes of
+ NONE => NONE
+ | SOME node =>
+ let
+ val nodes = IS.delete (nodes, node)
+ val reachableHere = valOf (IM.find (reachable, node))
+ in
+ if IS.isEmpty nodes then
+ if IS.member (reachableHere, node) then
+ NONE
+ else
+ SOME node
+ else
+ NONE
+ end
+
+ val ds = map (fn nodes =>
+ case isNonrec nodes of
+ SOME node =>
+ let
+ val vi = valOf (List.find (fn (_, n, _, _, _) => n = node) vis)
+ in
+ (DVal vi, loc)
+ end
+ | NONE =>
+ (DValRec (List.filter (fn (_, n, _, _, _) => IS.member (nodes, n)) vis), loc))
+ sccs
+ in
+ ds
+ end
+ | _ => [dAll]
+ in
+ ListUtil.mapConcat decl file
+ end
+
+end
diff --git a/src/core_util.sig b/src/core_util.sig
new file mode 100644
index 0000000..835577a
--- /dev/null
+++ b/src/core_util.sig
@@ -0,0 +1,232 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature CORE_UTIL = sig
+
+structure Kind : sig
+ val compare : Core.kind * Core.kind -> order
+
+ val mapfoldB : {kind : ('context, Core.kind', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * string -> 'context}
+ -> ('context, Core.kind, 'state, 'abort) Search.mapfolderB
+ val mapfold : (Core.kind', 'state, 'abort) Search.mapfolder
+ -> (Core.kind, 'state, 'abort) Search.mapfolder
+ val map : (Core.kind' -> Core.kind') -> Core.kind -> Core.kind
+ val exists : (Core.kind' -> bool) -> Core.kind -> bool
+ val mapB : {kind : 'context -> Core.kind' -> Core.kind',
+ bind : 'context * string -> 'context}
+ -> 'context -> (Core.kind -> Core.kind)
+end
+
+structure Con : sig
+ val compare : Core.con * Core.con -> order
+
+ datatype binder =
+ RelK of string
+ | RelC of string * Core.kind
+ | NamedC of string * int * Core.kind * Core.con option
+
+ val mapfoldB : {kind : ('context, Core.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Core.con', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Core.con, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Core.kind', 'state, 'abort) Search.mapfolder,
+ con : (Core.con', 'state, 'abort) Search.mapfolder}
+ -> (Core.con, 'state, 'abort) Search.mapfolder
+
+ val map : {kind : Core.kind' -> Core.kind',
+ con : Core.con' -> Core.con'}
+ -> Core.con -> Core.con
+
+ val mapB : {kind : 'context -> Core.kind' -> Core.kind',
+ con : 'context -> Core.con' -> Core.con',
+ bind : 'context * binder -> 'context}
+ -> 'context -> (Core.con -> Core.con)
+
+ val fold : {kind : Core.kind' * 'state -> 'state,
+ con : Core.con' * 'state -> 'state}
+ -> 'state -> Core.con -> 'state
+
+ val exists : {kind : Core.kind' -> bool,
+ con : Core.con' -> bool} -> Core.con -> bool
+
+ val existsB : {kind : 'context * Core.kind' -> bool,
+ con : 'context * Core.con' -> bool,
+ bind : 'context * binder -> 'context}
+ -> 'context -> Core.con -> bool
+
+ val foldMap : {kind : Core.kind' * 'state -> Core.kind' * 'state,
+ con : Core.con' * 'state -> Core.con' * 'state}
+ -> 'state -> Core.con -> Core.con * 'state
+end
+
+structure Exp : sig
+ val compare : Core.exp * Core.exp -> order
+
+ datatype binder =
+ RelK of string
+ | RelC of string * Core.kind
+ | NamedC of string * int * Core.kind * Core.con option
+ | RelE of string * Core.con
+ | NamedE of string * int * Core.con * Core.exp option * string
+
+ val mapfoldB : {kind : ('context, Core.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Core.con', 'state, 'abort) Search.mapfolderB,
+ exp : ('context, Core.exp', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Core.exp, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Core.kind', 'state, 'abort) Search.mapfolder,
+ con : (Core.con', 'state, 'abort) Search.mapfolder,
+ exp : (Core.exp', 'state, 'abort) Search.mapfolder}
+ -> (Core.exp, 'state, 'abort) Search.mapfolder
+
+ val map : {kind : Core.kind' -> Core.kind',
+ con : Core.con' -> Core.con',
+ exp : Core.exp' -> Core.exp'}
+ -> Core.exp -> Core.exp
+ val mapB : {kind : 'context -> Core.kind' -> Core.kind',
+ con : 'context -> Core.con' -> Core.con',
+ exp : 'context -> Core.exp' -> Core.exp',
+ bind : 'context * binder -> 'context}
+ -> 'context -> (Core.exp -> Core.exp)
+
+ val fold : {kind : Core.kind' * 'state -> 'state,
+ con : Core.con' * 'state -> 'state,
+ exp : Core.exp' * 'state -> 'state}
+ -> 'state -> Core.exp -> 'state
+
+ val foldB : {kind : 'context * Core.kind' * 'state -> 'state,
+ con : 'context * Core.con' * 'state -> 'state,
+ exp : 'context * Core.exp' * 'state -> 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Core.exp -> 'state
+
+ val exists : {kind : Core.kind' -> bool,
+ con : Core.con' -> bool,
+ exp : Core.exp' -> bool} -> Core.exp -> bool
+
+ val existsB : {kind : 'context * Core.kind' -> bool,
+ con : 'context * Core.con' -> bool,
+ exp : 'context * Core.exp' -> bool,
+ bind : 'context * binder -> 'context}
+ -> 'context -> Core.exp -> bool
+
+ val foldMap : {kind : Core.kind' * 'state -> Core.kind' * 'state,
+ con : Core.con' * 'state -> Core.con' * 'state,
+ exp : Core.exp' * 'state -> Core.exp' * 'state}
+ -> 'state -> Core.exp -> Core.exp * 'state
+ val foldMapB : {kind : 'context * Core.kind' * 'state -> Core.kind' * 'state,
+ con : 'context * Core.con' * 'state -> Core.con' * 'state,
+ exp : 'context * Core.exp' * 'state -> Core.exp' * 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Core.exp -> Core.exp * 'state
+end
+
+structure Decl : sig
+ datatype binder = datatype Exp.binder
+
+ val mapfoldB : {kind : ('context, Core.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Core.con', 'state, 'abort) Search.mapfolderB,
+ exp : ('context, Core.exp', 'state, 'abort) Search.mapfolderB,
+ decl : ('context, Core.decl', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Core.decl, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Core.kind', 'state, 'abort) Search.mapfolder,
+ con : (Core.con', 'state, 'abort) Search.mapfolder,
+ exp : (Core.exp', 'state, 'abort) Search.mapfolder,
+ decl : (Core.decl', 'state, 'abort) Search.mapfolder}
+ -> (Core.decl, 'state, 'abort) Search.mapfolder
+
+ val fold : {kind : Core.kind' * 'state -> 'state,
+ con : Core.con' * 'state -> 'state,
+ exp : Core.exp' * 'state -> 'state,
+ decl : Core.decl' * 'state -> 'state}
+ -> 'state -> Core.decl -> 'state
+
+ val foldMap : {kind : Core.kind' * 'state -> Core.kind' * 'state,
+ con : Core.con' * 'state -> Core.con' * 'state,
+ exp : Core.exp' * 'state -> Core.exp' * 'state,
+ decl : Core.decl' * 'state -> Core.decl' * 'state}
+ -> 'state -> Core.decl -> Core.decl * 'state
+ val foldMapB : {kind : 'context * Core.kind' * 'state -> Core.kind' * 'state,
+ con : 'context * Core.con' * 'state -> Core.con' * 'state,
+ exp : 'context * Core.exp' * 'state -> Core.exp' * 'state,
+ decl : 'context * Core.decl' * 'state -> Core.decl' * 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Core.decl -> Core.decl * 'state
+
+ val exists : {kind : Core.kind' -> bool,
+ con : Core.con' -> bool,
+ exp : Core.exp' -> bool,
+ decl : Core.decl' -> bool} -> Core.decl -> bool
+end
+
+structure File : sig
+ val maxName : Core.file -> int
+
+ datatype binder = datatype Exp.binder
+
+ val mapfoldB : {kind : ('context, Core.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Core.con', 'state, 'abort) Search.mapfolderB,
+ exp : ('context, Core.exp', 'state, 'abort) Search.mapfolderB,
+ decl : ('context, Core.decl', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Core.file, 'state, 'abort) Search.mapfolderB
+
+ val mapfold : {kind : (Core.kind', 'state, 'abort) Search.mapfolder,
+ con : (Core.con', 'state, 'abort) Search.mapfolder,
+ exp : (Core.exp', 'state, 'abort) Search.mapfolder,
+ decl : (Core.decl', 'state, 'abort) Search.mapfolder}
+ -> (Core.file, 'state, 'abort) Search.mapfolder
+
+ val mapB : {kind : 'context -> Core.kind' -> Core.kind',
+ con : 'context -> Core.con' -> Core.con',
+ exp : 'context -> Core.exp' -> Core.exp',
+ decl : 'context -> Core.decl' -> Core.decl',
+ bind : 'context * binder -> 'context}
+ -> 'context -> Core.file -> Core.file
+
+ val map : {kind : Core.kind' -> Core.kind',
+ con : Core.con' -> Core.con',
+ exp : Core.exp' -> Core.exp',
+ decl : Core.decl' -> Core.decl'}
+ -> Core.file -> Core.file
+
+ val fold : {kind : Core.kind' * 'state -> 'state,
+ con : Core.con' * 'state -> 'state,
+ exp : Core.exp' * 'state -> 'state,
+ decl : Core.decl' * 'state -> 'state}
+ -> 'state -> Core.file -> 'state
+
+ val foldMap : {kind : Core.kind' * 'state -> Core.kind' * 'state,
+ con : Core.con' * 'state -> Core.con' * 'state,
+ exp : Core.exp' * 'state -> Core.exp' * 'state,
+ decl : Core.decl' * 'state -> Core.decl' * 'state}
+ -> 'state -> Core.file -> Core.file * 'state
+end
+
+end
diff --git a/src/core_util.sml b/src/core_util.sml
new file mode 100644
index 0000000..57ef16f
--- /dev/null
+++ b/src/core_util.sml
@@ -0,0 +1,1240 @@
+(* Copyright (c) 2008-2010, 2013, 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 CoreUtil :> CORE_UTIL = struct
+
+open Core
+
+structure S = Search
+
+structure Kind = struct
+
+open Order
+
+fun compare ((k1, _), (k2, _)) =
+ case (k1, k2) of
+ (KType, KType) => EQUAL
+ | (KType, _) => LESS
+ | (_, KType) => GREATER
+
+ | (KArrow (d1, r1), KArrow (d2, r2)) => join (compare (d1, d2), fn () => compare (r1, r2))
+ | (KArrow _, _) => LESS
+ | (_, KArrow _) => GREATER
+
+ | (KName, KName) => EQUAL
+ | (KName, _) => LESS
+ | (_, KName) => GREATER
+
+ | (KRecord k1, KRecord k2) => compare (k1, k2)
+ | (KRecord _, _) => LESS
+ | (_, KRecord _) => GREATER
+
+ | (KUnit, KUnit) => EQUAL
+ | (KUnit, _) => LESS
+ | (_, KUnit) => GREATER
+
+ | (KTuple ks1, KTuple ks2) => joinL compare (ks1, ks2)
+ | (KTuple _, _) => LESS
+ | (_, KTuple _) => GREATER
+
+ | (KRel n1, KRel n2) => Int.compare (n1, n2)
+ | (KRel _, _) => LESS
+ | (_, KRel _) => GREATER
+
+ | (KFun (_, k1), KFun (_, k2)) => compare (k1, k2)
+
+fun mapfoldB {kind = f, bind} =
+ let
+ fun mfk ctx k acc =
+ S.bindP (mfk' ctx k acc, f ctx)
+
+ and mfk' ctx (kAll as (k, loc)) =
+ case k of
+ KType => S.return2 kAll
+
+ | KArrow (k1, k2) =>
+ S.bind2 (mfk ctx k1,
+ fn k1' =>
+ S.map2 (mfk ctx k2,
+ fn k2' =>
+ (KArrow (k1', k2'), loc)))
+
+ | KName => S.return2 kAll
+
+ | KRecord k =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (KRecord k', loc))
+
+ | KUnit => S.return2 kAll
+
+ | KTuple ks =>
+ S.map2 (ListUtil.mapfold (mfk ctx) ks,
+ fn ks' =>
+ (KTuple ks', loc))
+
+ | KRel _ => S.return2 kAll
+ | KFun (x, k) =>
+ S.map2 (mfk (bind (ctx, x)) k,
+ fn k' =>
+ (KFun (x, k'), loc))
+ in
+ mfk
+ end
+
+fun mapfold fk =
+ mapfoldB {kind = fn () => fk,
+ bind = fn ((), _) => ()} ()
+
+fun map f k =
+ case mapfold (fn k => fn () => S.Continue (f k, ())) k () of
+ S.Return () => raise Fail "CoreUtil.Kind.map"
+ | S.Continue (k, ()) => k
+
+fun mapB {kind, bind} ctx k =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ bind = bind} ctx k () of
+ S.Continue (k, ()) => k
+ | S.Return _ => raise Fail "CoreUtil.Kind.mapB: Impossible"
+
+fun exists f k =
+ case mapfold (fn k => fn () =>
+ if f k then
+ S.Return ()
+ else
+ S.Continue (k, ())) k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure Con = struct
+
+open Order
+
+fun compare ((c1, _), (c2, _)) =
+ case (c1, c2) of
+ (TFun (d1, r1), TFun (d2, r2)) => join (compare (d1, d2), fn () => compare (r1, r2))
+ | (TFun _, _) => LESS
+ | (_, TFun _) => GREATER
+
+ | (TCFun (x1, k1, r1), TCFun (x2, k2, r2)) =>
+ join (String.compare (x1, x2),
+ fn () => join (Kind.compare (k1, k2),
+ fn () => compare (r1, r2)))
+ | (TCFun _, _) => LESS
+ | (_, TCFun _) => GREATER
+
+ | (TRecord c1, TRecord c2) => compare (c1, c2)
+ | (TRecord _, _) => LESS
+ | (_, TRecord _) => GREATER
+
+ | (CRel n1, CRel n2) => Int.compare (n1, n2)
+ | (CRel _, _) => LESS
+ | (_, CRel _) => GREATER
+
+ | (CNamed n1, CNamed n2) => Int.compare (n1, n2)
+ | (CNamed _, _) => LESS
+ | (_, CNamed _) => GREATER
+
+ | (CFfi (m1, s1), CFfi (m2, s2)) => join (String.compare (m1, m2),
+ fn () => String.compare (s1, s2))
+ | (CFfi _, _) => LESS
+ | (_, CFfi _) => GREATER
+
+ | (CApp (f1, x1), CApp (f2, x2)) => join (compare (f1, f2),
+ fn () => compare (x1, x2))
+ | (CApp _, _) => LESS
+ | (_, CApp _) => GREATER
+
+ | (CAbs (x1, k1, b1), CAbs (x2, k2, b2)) =>
+ join (String.compare (x1, x2),
+ fn () => join (Kind.compare (k1, k2),
+ fn () => compare (b1, b2)))
+ | (CAbs _, _) => LESS
+ | (_, CAbs _) => GREATER
+
+ | (CName s1, CName s2) => String.compare (s1, s2)
+ | (CName _, _) => LESS
+ | (_, CName _) => GREATER
+
+ | (CRecord (k1, xvs1), CRecord (k2, xvs2)) =>
+ join (Kind.compare (k1, k2),
+ fn () =>
+ let
+ val sort = ListMergeSort.sort (fn ((x1, _), (x2, _)) =>
+ compare (x1, x2) = GREATER)
+ in
+ joinL (fn ((x1, v1), (x2, v2)) =>
+ join (compare (x1, x2),
+ fn () => compare (v1, v2))) (sort xvs1, sort xvs2)
+ end)
+ | (CRecord _, _) => LESS
+ | (_, CRecord _) => GREATER
+
+ | (CConcat (f1, s1), CConcat (f2, s2)) =>
+ join (compare (f1, f2),
+ fn () => compare (s1, s2))
+ | (CConcat _, _) => LESS
+ | (_, CConcat _) => GREATER
+
+ | (CMap (d1, r1), CMap (d2, r2)) =>
+ join (Kind.compare (d1, d2),
+ fn () => Kind.compare (r1, r2))
+ | (CMap _, _) => LESS
+ | (_, CMap _) => GREATER
+
+ | (CUnit, CUnit) => EQUAL
+ | (CUnit, _) => LESS
+ | (_, CUnit) => GREATER
+
+ | (CTuple cs1, CTuple cs2) => joinL compare (cs1, cs2)
+ | (CTuple _, _) => LESS
+ | (_, CTuple _) => GREATER
+
+ | (CProj (c1, n1), CProj (c2, n2)) => join (Int.compare (n1, n2),
+ fn () => compare (c1, c2))
+ | (CProj _, _) => LESS
+ | (_, CProj _) => GREATER
+
+ | (CKAbs (_, c1), CKAbs (_, c2)) => compare (c1, c2)
+ | (CKAbs _, _) => LESS
+ | (_, CKAbs _) => GREATER
+
+ | (CKApp (c1, k1), CKApp (c2, k2)) =>
+ join (compare (c1, c2),
+ fn () => Kind.compare (k1, k2))
+ | (CKApp _, _) => LESS
+ | (_, CKApp _) => GREATER
+
+ | (TKFun (_, c1), TKFun (_, c2)) => compare (c1, c2)
+
+datatype binder =
+ RelK of string
+ | RelC of string * kind
+ | NamedC of string * int * kind * con option
+
+fun mapfoldB {kind = fk, con = fc, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun mfc ctx c acc =
+ S.bindP (mfc' ctx c acc, fc ctx)
+
+ and mfc' ctx (cAll as (c, loc)) =
+ case c of
+ TFun (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (TFun (c1', c2'), loc)))
+ | TCFun (x, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc (bind (ctx, RelC (x, k))) c,
+ fn c' =>
+ (TCFun (x, k', c'), loc)))
+ | TRecord c =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (TRecord c', loc))
+
+ | CRel _ => S.return2 cAll
+ | CNamed _ => S.return2 cAll
+ | CFfi _ => S.return2 cAll
+ | CApp (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (CApp (c1', c2'), loc)))
+ | CAbs (x, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc (bind (ctx, RelC (x, k))) c,
+ fn c' =>
+ (CAbs (x, k', c'), loc)))
+
+ | CName _ => S.return2 cAll
+
+ | CRecord (k, xcs) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (ListUtil.mapfold (fn (x, c) =>
+ S.bind2 (mfc ctx x,
+ fn x' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (x', c'))))
+ xcs,
+ fn xcs' =>
+ (CRecord (k', xcs'), loc)))
+ | CConcat (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (CConcat (c1', c2'), loc)))
+ | CMap (k1, k2) =>
+ S.bind2 (mfk ctx k1,
+ fn k1' =>
+ S.map2 (mfk ctx k2,
+ fn k2' =>
+ (CMap (k1', k2'), loc)))
+
+ | CUnit => S.return2 cAll
+
+ | CTuple cs =>
+ S.map2 (ListUtil.mapfold (mfc ctx) cs,
+ fn cs' =>
+ (CTuple cs', loc))
+
+ | CProj (c, n) =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (CProj (c', n), loc))
+
+ | CKAbs (x, c) =>
+ S.map2 (mfc (bind (ctx, RelK x)) c,
+ fn c' =>
+ (CKAbs (x, c'), loc))
+ | CKApp (c, k) =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (CKApp (c', k'), loc)))
+ | TKFun (x, c) =>
+ S.map2 (mfc (bind (ctx, RelK x)) c,
+ fn c' =>
+ (TKFun (x, c'), loc))
+ in
+ mfc
+ end
+
+fun mapfold {kind = fk, con = fc} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ bind = fn ((), _) => ()} ()
+
+fun map {kind, con} c =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ())} c () of
+ S.Return () => raise Fail "Core_util.Con.map"
+ | S.Continue (c, ()) => c
+
+fun mapB {kind, con, bind} ctx c =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ bind = bind} ctx c () of
+ S.Continue (c, ()) => c
+ | S.Return _ => raise Fail "CoreUtil.Con.mapB: Impossible"
+
+fun fold {kind, con} s c =
+ case mapfold {kind = fn k => fn s => S.Continue (k, kind (k, s)),
+ con = fn c => fn s => S.Continue (c, con (c, s))} c s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "CoreUtil.Con.fold: Impossible"
+
+fun exists {kind, con} k =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun existsB {kind, con, bind} ctx c =
+ case mapfoldB {kind = fn ctx => fn k => fn () =>
+ if kind (ctx, k) then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn ctx => fn c => fn () =>
+ if con (ctx, c) then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ bind = bind} ctx c () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun foldMap {kind, con} s c =
+ case mapfold {kind = fn k => fn s => S.Continue (kind (k, s)),
+ con = fn c => fn s => S.Continue (con (c, s))} c s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "CoreUtil.Con.foldMap: Impossible"
+
+end
+
+structure Exp = struct
+
+open Order
+
+fun pcCompare (pc1, pc2) =
+ case (pc1, pc2) of
+ (PConVar n1, PConVar n2) => Int.compare (n1, n2)
+ | (PConVar _, _) => LESS
+ | (_, PConVar _) => GREATER
+
+ | (PConFfi {mod = m1, datatyp = d1, con = c1, ...},
+ PConFfi {mod = m2, datatyp = d2, con = c2, ...}) =>
+ join (String.compare (m1, m2),
+ fn () => join (String.compare (d1, d2),
+ fn () => String.compare (c1, c2)))
+
+fun pCompare ((p1, _), (p2, _)) =
+ case (p1, p2) of
+ (PVar _, PVar _) => EQUAL
+ | (PVar _, _) => LESS
+ | (_, PVar _) => GREATER
+
+ | (PPrim p1, PPrim p2) => Prim.compare (p1, p2)
+ | (PPrim _, _) => LESS
+ | (_, PPrim _) => GREATER
+
+ | (PCon (_, pc1, _, po1), PCon (_, pc2, _, po2)) =>
+ join (pcCompare (pc1, pc2),
+ fn () => joinO pCompare (po1, po2))
+ | (PCon _, _) => LESS
+ | (_, PCon _) => GREATER
+
+ | (PRecord xps1, PRecord xps2) =>
+ joinL (fn ((x1, p1, _), (x2, p2, _)) =>
+ join (String.compare (x1, x2),
+ fn () => pCompare (p1, p2))) (xps1, xps2)
+
+fun fmCompare (fm1, fm2) =
+ case (fm1, fm2) of
+ (None, None) => EQUAL
+ | (None, _) => LESS
+ | (_, None) => GREATER
+
+ | (Error, Error) => EQUAL
+
+fun compare ((e1, _), (e2, _)) =
+ case (e1, e2) of
+ (EPrim p1, EPrim p2) => Prim.compare (p1, p2)
+ | (EPrim _, _) => LESS
+ | (_, EPrim _) => GREATER
+
+ | (ERel n1, ERel n2) => Int.compare (n1, n2)
+ | (ERel _, _) => LESS
+ | (_, ERel _) => GREATER
+
+ | (ENamed n1, ENamed n2) => Int.compare (n1, n2)
+ | (ENamed _, _) => LESS
+ | (_, ENamed _) => GREATER
+
+ | (ECon (_, pc1, _, eo1), ECon (_, pc2, _, eo2)) =>
+ join (pcCompare (pc1, pc2),
+ fn () => joinO compare (eo1, eo2))
+ | (ECon _, _) => LESS
+ | (_, ECon _) => GREATER
+
+ | (EFfi (f1, x1), EFfi (f2, x2)) =>
+ join (String.compare (f1, f2),
+ fn () => String.compare (x1, x2))
+ | (EFfi _, _) => LESS
+ | (_, EFfi _) => GREATER
+
+ | (EFfiApp (f1, x1, es1), EFfiApp (f2, x2, es2)) =>
+ join (String.compare (f1, f2),
+ fn () => join (String.compare (x1, x2),
+ fn () => joinL (fn ((e1, _), (e2, _)) => compare (e1, e2))(es1, es2)))
+ | (EFfiApp _, _) => LESS
+ | (_, EFfiApp _) => GREATER
+
+ | (EApp (f1, x1), EApp (f2, x2)) =>
+ join (compare (f1, f2),
+ fn () => compare (x1, x2))
+ | (EApp _, _) => LESS
+ | (_, EApp _) => GREATER
+
+ | (EAbs (_, _, _, e1), EAbs (_, _, _, e2)) => compare (e1, e2)
+ | (EAbs _, _) => LESS
+ | (_, EAbs _) => GREATER
+
+ | (ECApp (f1, x1), ECApp (f2, x2)) =>
+ join (compare (f1, f2),
+ fn () => Con.compare (x1, x2))
+ | (ECApp _, _) => LESS
+ | (_, ECApp _) => GREATER
+
+ | (ECAbs (_, _, e1), ECAbs (_, _, e2)) => compare (e1, e2)
+ | (ECAbs _, _) => LESS
+ | (_, ECAbs _) => GREATER
+
+ | (ERecord xes1, ERecord xes2) =>
+ joinL (fn ((x1, e1, _), (x2, e2, _)) =>
+ join (Con.compare (x1, x2),
+ fn () => compare (e1, e2))) (xes1, xes2)
+ | (ERecord _, _) => LESS
+ | (_, ERecord _) => GREATER
+
+ | (EField (e1, c1, _), EField (e2, c2, _)) =>
+ join (compare (e1, e2),
+ fn () => Con.compare (c1, c2))
+ | (EField _, _) => LESS
+ | (_, EField _) => GREATER
+
+ | (EConcat (x1, _, y1, _), EConcat (x2, _, y2, _)) =>
+ join (compare (x1, x2),
+ fn () => compare (y1, y2))
+ | (EConcat _, _) => LESS
+ | (_, EConcat _) => GREATER
+
+ | (ECut (e1, c1, _), ECut (e2, c2, _)) =>
+ join (compare (e1, e2),
+ fn () => Con.compare (c1, c2))
+ | (ECut _, _) => LESS
+ | (_, ECut _) => GREATER
+
+ | (ECutMulti (e1, c1, _), ECutMulti (e2, c2, _)) =>
+ join (compare (e1, e2),
+ fn () => Con.compare (c1, c2))
+ | (ECutMulti _, _) => LESS
+ | (_, ECutMulti _) => GREATER
+
+ | (ECase (e1, pes1, _), ECase (e2, pes2, _)) =>
+ join (compare (e1, e2),
+ fn () => joinL (fn ((p1, e1), (p2, e2)) =>
+ join (pCompare (p1, p2),
+ fn () => compare (e1, e2))) (pes1, pes2))
+ | (ECase _, _) => LESS
+ | (_, ECase _) => GREATER
+
+ | (EWrite e1, EWrite e2) => compare (e1, e2)
+ | (EWrite _, _) => LESS
+ | (_, EWrite _) => GREATER
+
+ | (EClosure (n1, es1), EClosure (n2, es2)) =>
+ join (Int.compare (n1, n2),
+ fn () => joinL compare (es1, es2))
+ | (EClosure _, _) => LESS
+ | (_, EClosure _) => GREATER
+
+ | (ELet (_, _, x1, e1), ELet (_, _, x2, e2)) =>
+ join (compare (x1, x2),
+ fn () => compare (e1, e2))
+ | (ELet _, _) => LESS
+ | (_, ELet _) => GREATER
+
+ | (EServerCall (n1, es1, _, fm1), EServerCall (n2, es2, _, fm2)) =>
+ join (Int.compare (n1, n2),
+ fn () => join (fmCompare (fm1, fm2),
+ fn () => joinL compare (es1, es2)))
+ | (EServerCall _, _) => LESS
+ | (_, EServerCall _) => GREATER
+
+ | (EKAbs (_, e1), EKAbs (_, e2)) => compare (e1, e2)
+ | (EKAbs _, _) => LESS
+ | (_, EKAbs _) => GREATER
+
+ | (EKApp (e1, k1), EKApp (e2, k2)) =>
+ join (compare (e1, e2),
+ fn () => Kind.compare (k1, k2))
+
+datatype binder =
+ RelK of string
+ | RelC of string * kind
+ | NamedC of string * int * kind * con option
+ | RelE of string * con
+ | NamedE of string * int * con * exp option * string
+
+fun mapfoldB {kind = fk, con = fc, exp = fe, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val mfc = Con.mapfoldB {kind = fk, con = fc, bind = bind'}
+
+ fun mfe ctx e acc =
+ S.bindP (mfe' ctx e acc, fe ctx)
+
+ and mfet ctx (e, t) =
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx t,
+ fn t' => (e', t')))
+
+ and mfe' ctx (eAll as (e, loc)) =
+ case e of
+ EPrim _ => S.return2 eAll
+ | ERel _ => S.return2 eAll
+ | ENamed _ => S.return2 eAll
+ | ECon (dk, pc, cs, NONE) =>
+ S.bind2 (mfpc ctx pc,
+ fn pc' =>
+ S.map2 (ListUtil.mapfold (mfc ctx) cs,
+ fn cs' =>
+ (ECon (dk, pc', cs', NONE), loc)))
+ | ECon (dk, pc, cs, SOME e) =>
+ S.bind2 (mfpc ctx pc,
+ fn pc' =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (ListUtil.mapfold (mfc ctx) cs,
+ fn cs' =>
+ (ECon (dk, pc', cs', SOME e'), loc))))
+ | EFfi _ => S.return2 eAll
+ | EFfiApp (m, x, es) =>
+ S.map2 (ListUtil.mapfold (mfet ctx) es,
+ fn es' =>
+ (EFfiApp (m, x, es'), loc))
+ | EApp (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (EApp (e1', e2'), loc)))
+ | EAbs (x, dom, ran, e) =>
+ S.bind2 (mfc ctx dom,
+ fn dom' =>
+ S.bind2 (mfc ctx ran,
+ fn ran' =>
+ S.map2 (mfe (bind (ctx, RelE (x, dom'))) e,
+ fn e' =>
+ (EAbs (x, dom', ran', e'), loc))))
+
+ | ECApp (e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (ECApp (e', c'), loc)))
+ | ECAbs (x, k, e) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfe (bind (ctx, RelC (x, k))) e,
+ fn e' =>
+ (ECAbs (x, k', e'), loc)))
+
+ | ERecord xes =>
+ S.map2 (ListUtil.mapfold (fn (x, e, t) =>
+ S.bind2 (mfc ctx x,
+ fn x' =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (x', e', t')))))
+ xes,
+ fn xes' =>
+ (ERecord xes', loc))
+ | EField (e, c, {field, rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfc ctx field,
+ fn field' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (EField (e', c', {field = field', rest = rest'}), loc)))))
+ | EConcat (e1, c1, e2, c2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.bind2 (mfe ctx e2,
+ fn e2' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (EConcat (e1', c1', e2', c2'),
+ loc)))))
+ | ECut (e, c, {field, rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfc ctx field,
+ fn field' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (ECut (e', c', {field = field', rest = rest'}), loc)))))
+ | ECutMulti (e, c, {rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (ECutMulti (e', c', {rest = rest'}), loc))))
+
+ | ECase (e, pes, {disc, result}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (ListUtil.mapfold (fn (p, e) =>
+ let
+ fun pb ((p, _), ctx) =
+ case p of
+ PVar (x, t) => bind (ctx, RelE (x, t))
+ | PPrim _ => ctx
+ | PCon (_, _, _, NONE) => ctx
+ | PCon (_, _, _, SOME p) => pb (p, ctx)
+ | PRecord xps => foldl (fn ((_, p, _), ctx) =>
+ pb (p, ctx)) ctx xps
+ in
+ S.bind2 (mfp ctx p,
+ fn p' =>
+ S.map2 (mfe (pb (p', ctx)) e,
+ fn e' => (p', e')))
+ end) pes,
+ fn pes' =>
+ S.bind2 (mfc ctx disc,
+ fn disc' =>
+ S.map2 (mfc ctx result,
+ fn result' =>
+ (ECase (e', pes', {disc = disc', result = result'}), loc)))))
+
+ | EWrite e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EWrite e', loc))
+
+ | EClosure (n, es) =>
+ S.map2 (ListUtil.mapfold (mfe ctx) es,
+ fn es' =>
+ (EClosure (n, es'), loc))
+
+ | ELet (x, t, e1, e2) =>
+ S.bind2 (mfc ctx t,
+ fn t' =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe (bind (ctx, RelE (x, t'))) e2,
+ fn e2' =>
+ (ELet (x, t', e1', e2'), loc))))
+
+ | EServerCall (n, es, t, fm) =>
+ S.bind2 (ListUtil.mapfold (mfe ctx) es,
+ fn es' =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (EServerCall (n, es', t', fm), loc)))
+
+ | EKAbs (x, e) =>
+ S.map2 (mfe (bind (ctx, RelK x)) e,
+ fn e' =>
+ (EKAbs (x, e'), loc))
+ | EKApp (e, k) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (EKApp (e', k'), loc)))
+
+ and mfp ctx (pAll as (p, loc)) =
+ case p of
+ PVar (x, t) =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (PVar (x, t'), loc))
+ | PPrim _ => S.return2 pAll
+ | PCon (dk, pc, args, po) =>
+ S.bind2 (mfpc ctx pc,
+ fn pc' =>
+ S.bind2 (ListUtil.mapfold (mfc ctx) args,
+ fn args' =>
+ S.map2 ((case po of
+ NONE => S.return2 NONE
+ | SOME p => S.map2 (mfp ctx p, SOME)),
+ fn po' =>
+ (PCon (dk, pc', args', po'), loc))))
+ | PRecord xps =>
+ S.map2 (ListUtil.mapfold (fn (x, p, c) =>
+ S.bind2 (mfp ctx p,
+ fn p' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (x, p', c')))) xps,
+ fn xps' =>
+ (PRecord xps', loc))
+
+ and mfpc ctx pc =
+ case pc of
+ PConVar _ => S.return2 pc
+ | PConFfi {mod = m, datatyp, params, con, arg, kind} =>
+ S.map2 ((case arg of
+ NONE => S.return2 NONE
+ | SOME c =>
+ let
+ val k = (KType, ErrorMsg.dummySpan)
+ val ctx' = foldl (fn (x, ctx) => bind (ctx, RelC (x, k))) ctx params
+ in
+ S.map2 (mfc ctx' c, SOME)
+ end),
+ fn arg' =>
+ PConFfi {mod = m, datatyp = datatyp, params = params,
+ con = con, arg = arg', kind = kind})
+ in
+ mfe
+ end
+
+fun mapfold {kind = fk, con = fc, exp = fe} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ exp = fn () => fe,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, con, exp, bind} ctx e =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ exp = fn ctx => fn e => fn () => S.Continue (exp ctx e, ()),
+ bind = bind} ctx e () of
+ S.Continue (e, ()) => e
+ | S.Return _ => raise Fail "CoreUtil.Exp.mapB: Impossible"
+
+fun map {kind, con, exp} e =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ()),
+ exp = fn e => fn () => S.Continue (exp e, ())} e () of
+ S.Return () => raise Fail "Core_util.Exp.map"
+ | S.Continue (e, ()) => e
+
+fun fold {kind, con, exp} s e =
+ case mapfold {kind = fn k => fn s => S.Continue (k, kind (k, s)),
+ con = fn c => fn s => S.Continue (c, con (c, s)),
+ exp = fn e => fn s => S.Continue (e, exp (e, s))} e s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "CoreUtil.Exp.fold: Impossible"
+
+fun foldB {kind, con, exp, bind} ctx s e =
+ case mapfoldB {kind = fn ctx => fn k => fn s => S.Continue (k, kind (ctx, k, s)),
+ con = fn ctx => fn c => fn s => S.Continue (c, con (ctx, c, s)),
+ exp = fn ctx => fn e => fn s => S.Continue (e, exp (ctx, e, s)),
+ bind = bind} ctx e s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "CoreUtil.Exp.foldB: Impossible"
+
+fun exists {kind, con, exp} k =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun existsB {kind, con, exp, bind} ctx k =
+ case mapfoldB {kind = fn ctx => fn k => fn () =>
+ if kind (ctx, k) then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn ctx => fn c => fn () =>
+ if con (ctx, c) then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn ctx => fn e => fn () =>
+ if exp (ctx, e) then
+ S.Return ()
+ else
+ S.Continue (e, ()),
+ bind = bind} ctx k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun foldMap {kind, con, exp} s e =
+ case mapfold {kind = fn k => fn s => S.Continue (kind (k, s)),
+ con = fn c => fn s => S.Continue (con (c, s)),
+ exp = fn e => fn s => S.Continue (exp (e, s))} e s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "CoreUtil.Exp.foldMap: Impossible"
+
+fun foldMapB {kind, con, exp, bind} ctx s e =
+ case mapfoldB {kind = fn ctx => fn k => fn s => S.Continue (kind (ctx, k, s)),
+ con = fn ctx => fn c => fn s => S.Continue (con (ctx, c, s)),
+ exp = fn ctx => fn e => fn s => S.Continue (exp (ctx, e, s)),
+ bind = bind} ctx e s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "CoreUtil.Exp.foldMapB: Impossible"
+
+end
+
+structure Decl = struct
+
+datatype binder = datatype Exp.binder
+
+fun mapfoldB {kind = fk, con = fc, exp = fe, decl = fd, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val mfc = Con.mapfoldB {kind = fk, con = fc, bind = bind'}
+
+ val mfe = Exp.mapfoldB {kind = fk, con = fc, exp = fe, bind = bind}
+
+ fun mfd ctx d acc =
+ S.bindP (mfd' ctx d acc, fd ctx)
+
+ and mfd' ctx (dAll as (d, loc)) =
+ case d of
+ DCon (x, n, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DCon (x, n, k', c'), loc)))
+ | DDatatype dts =>
+ S.map2 (ListUtil.mapfold (fn (x, n, xs, xncs) =>
+ let
+ val k = (KType, loc)
+ val k' = foldl (fn (_, k') => (KArrow (k, k'), loc)) k xs
+ val ctx' = bind (ctx, NamedC (x, n, k', NONE))
+ in
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (mfc ctx' c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' => (x, n, xs, xncs'))
+ end) dts,
+ fn dts' =>
+ (DDatatype dts', loc))
+ | DVal vi =>
+ S.map2 (mfvi ctx vi,
+ fn vi' =>
+ (DVal vi', loc))
+ | DValRec vis =>
+ let
+ val ctx = foldl (fn ((x, n, t, e, s), ctx) => bind (ctx, NamedE (x, n, t, NONE, s)))
+ ctx vis
+ in
+ S.map2 (ListUtil.mapfold (mfvi ctx) vis,
+ fn vis' =>
+ (DValRec vis', loc))
+ end
+ | DExport _ => S.return2 dAll
+ | DTable (x, n, c, s, pe, pc, ce, cc) =>
+ let
+ val loc = #2 ce
+ val ct = (CFfi ("Basis", "sql_table"), loc)
+ val ct = (CApp (ct, (CConcat (pc, cc), loc)), loc)
+ val ct = (CApp (ct, cc), loc)
+ val ctx' = bind (ctx, NamedE (x, n, ct, NONE, s))
+ in
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfe ctx' pe,
+ fn pe' =>
+ S.bind2 (mfc ctx pc,
+ fn pc' =>
+ S.bind2 (mfe ctx' ce,
+ fn ce' =>
+ S.map2 (mfc ctx cc,
+ fn cc' =>
+ (DTable (x, n, c', s, pe', pc', ce', cc'), loc))))))
+ end
+ | DSequence _ => S.return2 dAll
+ | DView (x, n, s, e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DView (x, n, s, e', c'), loc)))
+ | DDatabase _ => S.return2 dAll
+ | DCookie (x, n, c, s) =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DCookie (x, n, c', s), loc))
+ | DStyle _ => S.return2 dAll
+ | DTask (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (DTask (e1', e2'), loc)))
+ | DPolicy e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (DPolicy e', loc))
+
+ | DOnError _ => S.return2 dAll
+
+ and mfvi ctx (x, n, t, e, s) =
+ S.bind2 (mfc ctx t,
+ fn t' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (x, n, t', e', s)))
+ in
+ mfd
+ end
+
+fun mapfold {kind = fk, con = fc, exp = fe, decl = fd} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ exp = fn () => fe,
+ decl = fn () => fd,
+ bind = fn ((), _) => ()} ()
+
+fun fold {kind, con, exp, decl} s d =
+ case mapfold {kind = fn k => fn s => S.Continue (k, kind (k, s)),
+ con = fn c => fn s => S.Continue (c, con (c, s)),
+ exp = fn e => fn s => S.Continue (e, exp (e, s)),
+ decl = fn d => fn s => S.Continue (d, decl (d, s))} d s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "CoreUtil.Decl.fold: Impossible"
+
+fun foldMap {kind, con, exp, decl} s d =
+ case mapfold {kind = fn k => fn s => S.Continue (kind (k, s)),
+ con = fn c => fn s => S.Continue (con (c, s)),
+ exp = fn e => fn s => S.Continue (exp (e, s)),
+ decl = fn d => fn s => S.Continue (decl (d, s))} d s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "CoreUtil.Decl.foldMap: Impossible"
+
+fun foldMapB {kind, con, exp, decl, bind} ctx s d =
+ case mapfoldB {kind = fn ctx => fn k => fn s => S.Continue (kind (ctx, k, s)),
+ con = fn ctx => fn c => fn s => S.Continue (con (ctx, c, s)),
+ exp = fn ctx => fn e => fn s => S.Continue (exp (ctx, e, s)),
+ decl = fn ctx => fn d => fn s => S.Continue (decl (ctx, d, s)),
+ bind = bind} ctx d s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "CoreUtil.Decl.foldMapB: Impossible"
+
+fun exists {kind, con, exp, decl} d =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ()),
+ decl = fn d => fn () =>
+ if decl d then
+ S.Return ()
+ else
+ S.Continue (d, ())} d () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure File = struct
+
+datatype binder = datatype Exp.binder
+
+fun mapfoldB (all as {bind, ...}) =
+ let
+ val mfd = Decl.mapfoldB all
+
+ fun mff ctx ds =
+ case ds of
+ nil => S.return2 nil
+ | d :: ds' =>
+ S.bind2 (mfd ctx d,
+ fn d' =>
+ let
+ val ctx' =
+ case #1 d' of
+ DCon (x, n, k, c) => bind (ctx, NamedC (x, n, k, SOME c))
+ | DDatatype dts =>
+ foldl (fn ((x, n, xs, xncs), ctx) =>
+ let
+ val loc = #2 d'
+ val k = (KType, loc)
+ val k' = foldl (fn (_, k') => (KArrow (k, k'), loc)) k xs
+
+ val ctx = bind (ctx, NamedC (x, n, k', NONE))
+ val t = (CNamed n, #2 d')
+ val t = ListUtil.foldli (fn (i, _, t) =>
+ (CApp (t, (CRel i, loc)), loc))
+ t xs
+ in
+ foldl (fn ((x, n, to), ctx) =>
+ let
+ val t = case to of
+ NONE => t
+ | SOME t' => (TFun (t', t), #2 d')
+ val t = foldr (fn (x, t) => (TCFun (x, k, t), loc))
+ t xs
+ in
+ bind (ctx, NamedE (x, n, t, NONE, ""))
+ end)
+ ctx xncs
+ end)
+ ctx dts
+ | DVal (x, n, t, e, s) => bind (ctx, NamedE (x, n, t, SOME e, s))
+ | DValRec vis =>
+ foldl (fn ((x, n, t, e, s), ctx) => bind (ctx, NamedE (x, n, t, NONE, s)))
+ ctx vis
+ | DExport _ => ctx
+ | DTable (x, n, c, s, _, pc, _, cc) =>
+ let
+ val loc = #2 d'
+ val ct = (CFfi ("Basis", "sql_table"), loc)
+ val ct = (CApp (ct, (CConcat (pc, cc), loc)), loc)
+ val ct = (CApp (ct, cc), loc)
+ in
+ bind (ctx, NamedE (x, n, ct, NONE, s))
+ end
+ | DSequence (x, n, s) =>
+ let
+ val t = (CFfi ("Basis", "sql_sequence"), #2 d')
+ in
+ bind (ctx, NamedE (x, n, t, NONE, s))
+ end
+ | DView (x, n, s, _, c) =>
+ let
+ val loc = #2 d'
+ val ct = (CFfi ("Basis", "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ bind (ctx, NamedE (x, n, ct, NONE, s))
+ end
+ | DDatabase _ => ctx
+ | DCookie (x, n, c, s) =>
+ let
+ val t = (CApp ((CFfi ("Basis", "http_cookie"), #2 d'), c), #2 d')
+ in
+ bind (ctx, NamedE (x, n, t, NONE, s))
+ end
+ | DStyle (x, n, s) =>
+ let
+ val t = (CFfi ("Basis", "css_class"), #2 d')
+ in
+ bind (ctx, NamedE (x, n, t, NONE, s))
+ end
+ | DTask _ => ctx
+ | DPolicy _ => ctx
+ | DOnError _ => ctx
+ in
+ S.map2 (mff ctx' ds',
+ fn ds' =>
+ d' :: ds')
+ end)
+ in
+ mff
+ end
+
+fun mapfold {kind = fk, con = fc, exp = fe, decl = fd} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ exp = fn () => fe,
+ decl = fn () => fd,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, con, exp, decl, bind} ctx ds =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ exp = fn ctx => fn e => fn () => S.Continue (exp ctx e, ()),
+ decl = fn ctx => fn d => fn () => S.Continue (decl ctx d, ()),
+ bind = bind} ctx ds () of
+ S.Continue (ds, ()) => ds
+ | S.Return _ => raise Fail "CoreUtil.File.mapB: Impossible"
+
+fun map {kind, con, exp, decl} ds =
+ mapB {kind = fn () => kind,
+ con = fn () => con,
+ exp = fn () => exp,
+ decl = fn () => decl,
+ bind = fn _ => ()} () ds
+
+fun fold {kind, con, exp, decl} s d =
+ case mapfold {kind = fn k => fn s => S.Continue (k, kind (k, s)),
+ con = fn c => fn s => S.Continue (c, con (c, s)),
+ exp = fn e => fn s => S.Continue (e, exp (e, s)),
+ decl = fn d => fn s => S.Continue (d, decl (d, s))} d s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "CoreUtil.File.fold: Impossible"
+
+fun foldMap {kind, con, exp, decl} s d =
+ case mapfold {kind = fn k => fn s => S.Continue (kind (k, s)),
+ con = fn c => fn s => S.Continue (con (c, s)),
+ exp = fn e => fn s => S.Continue (exp (e, s)),
+ decl = fn d => fn s => S.Continue (decl (d, s))} d s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "CoreUtil.File.foldMap: Impossible"
+
+val maxName = foldl (fn ((d, _) : decl, count) =>
+ case d of
+ DCon (_, n, _, _) => Int.max (n, count)
+ | DDatatype dts => foldl (fn ((_, n, _, ns), count) =>
+ foldl (fn ((_, n', _), m) => Int.max (n', m))
+ (Int.max (n, count)) ns) count dts
+ | DVal (_, n, _, _, _) => Int.max (n, count)
+ | DValRec vis => foldl (fn ((_, n, _, _, _), count) => Int.max (n, count)) count vis
+ | DExport _ => count
+ | DTable (_, n, _, _, _, _, _, _) => Int.max (n, count)
+ | DSequence (_, n, _) => Int.max (n, count)
+ | DView (_, n, _, _, _) => Int.max (n, count)
+ | DDatabase _ => count
+ | DCookie (_, n, _, _) => Int.max (n, count)
+ | DStyle (_, n, _) => Int.max (n, count)
+ | DTask _ => count
+ | DPolicy _ => count
+ | DOnError _ => count) 0
+
+end
+
+end
diff --git a/src/corify.sig b/src/corify.sig
new file mode 100644
index 0000000..0e1bb80
--- /dev/null
+++ b/src/corify.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature CORIFY = sig
+
+ val corify : Expl.file -> Core.file
+
+end
diff --git a/src/corify.sml b/src/corify.sml
new file mode 100644
index 0000000..19cd3ec
--- /dev/null
+++ b/src/corify.sml
@@ -0,0 +1,1330 @@
+(* Copyright (c) 2008-2012, 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 Corify :> CORIFY = struct
+
+structure EM = ErrorMsg
+structure L = Expl
+structure L' = Core
+
+structure IM = IntBinaryMap
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+fun doRestify k (mods, s) =
+ let
+ val s = if String.isPrefix "wrap_" s then
+ String.extract (s, 5, NONE)
+ else
+ s
+ val s = String.concatWith "/" (rev (s :: mods))
+ val s = String.implode (List.filter (fn ch => ch <> #"$") (String.explode s))
+ in
+ Settings.rewrite k s
+ end
+
+val relify = CharVector.map (fn #"/" => #"_"
+ | ch => ch)
+
+local
+ val count = ref 0
+in
+
+fun reset v = count := v
+
+fun alloc () =
+ let
+ val r = !count
+ in
+ count := r + 1;
+ r
+ end
+
+fun getCounter () = !count
+fun setCounter n = count := n
+
+end
+
+structure St : sig
+ type t
+
+ val empty : t
+
+ val debug : t -> unit
+
+ val name : t -> string list
+
+ val enter : t * string list -> t
+ val leave : t -> {outer : t, inner : t}
+ val ffi : string -> L'.con SM.map -> (string * string list * L'.con option * L'.datatype_kind) SM.map -> t
+
+ val basisIs : t * int -> t
+ val lookupBasis : t -> int option
+
+ datatype core_con =
+ CNormal of int
+ | CFfi of string
+ val bindCon : t -> string -> int -> t * int
+ val lookupConById : t -> int -> int option
+ val lookupConByName : t -> string -> core_con
+
+ val bindConstructor : t -> string -> int -> t * int
+ val bindConstructorAs : t -> string -> int -> L'.patCon -> t
+ val lookupConstructorByNameOpt : t -> string -> L'.patCon option
+ val lookupConstructorByName : t -> string -> L'.patCon
+ val lookupConstructorById : t -> int -> L'.patCon
+ val lookupConstructorByIdOpt : t -> int -> L'.patCon option
+
+ datatype core_val =
+ ENormal of int
+ | EFfi of string * L'.con
+ val bindVal : t -> string -> int -> t * int
+ val bindConstructorVal : t -> string -> int -> int -> t
+ val lookupValById : t -> int -> int option
+ val lookupValByName : t -> string -> core_val
+
+ val bindStr : t -> string -> int -> t -> t
+ val lookupStrById : t -> int -> t
+ val lookupStrByIdOpt : t -> int -> t option
+ val lookupStrByName : string * t -> t
+ val lookupStrByNameOpt : string * t -> t option
+
+ val bindFunctor : t -> string -> int -> string -> int -> L.str -> t
+ val lookupFunctorById : t -> int -> string * int * L.str
+ val lookupFunctorByIdOpt : t -> int -> (string * int * L.str) option
+ val lookupFunctorByName : string * t -> string * int * L.str
+end = struct
+
+datatype flattening =
+ FNormal of {name : string list,
+ cons : int SM.map,
+ constructors : L'.patCon SM.map,
+ vals : int SM.map,
+ strs : flattening SM.map,
+ funs : (string * int * L.str) SM.map}
+ | FFfi of {mod : string,
+ vals : L'.con SM.map,
+ constructors : (string * string list * L'.con option * L'.datatype_kind) SM.map}
+
+type t = {
+ basis : int option,
+ cons : int IM.map,
+ constructors : L'.patCon IM.map,
+ vals : int IM.map,
+ strs : flattening IM.map,
+ funs : (string * int * L.str) IM.map,
+ current : flattening,
+ nested : flattening list
+}
+
+val empty = {
+ basis = NONE,
+ cons = IM.empty,
+ constructors = IM.empty,
+ vals = IM.empty,
+ strs = IM.empty,
+ funs = IM.empty,
+ current = FNormal { name = [], cons = SM.empty, constructors = SM.empty,
+ vals = SM.empty, strs = SM.empty, funs = SM.empty },
+ nested = []
+}
+
+fun debug ({current = FNormal {cons, constructors, vals, strs, funs, ...}, ...} : t) =
+ print ("cons: " ^ Int.toString (SM.numItems cons) ^ "; "
+ ^ "constructors: " ^ Int.toString (SM.numItems constructors) ^ "; "
+ ^ "vals: " ^ Int.toString (SM.numItems vals) ^ "; "
+ ^ "strs: " ^ Int.toString (SM.numItems strs) ^ "; "
+ ^ "funs: " ^ Int.toString (SM.numItems funs) ^ "\n")
+ | debug _ = print "Not normal!\n"
+
+fun name ({current = FNormal {name, ...}, ...} : t) = name
+ | name {current = FFfi {mod = name, ...}, ...} = [name]
+
+fun basisIs ({cons, constructors, vals, strs, funs, current, nested, ...} : t, basis) =
+ {basis = SOME basis,
+ cons = cons,
+ constructors = constructors,
+ vals = vals,
+ strs = strs,
+ funs = funs,
+ current = current,
+ nested = nested}
+
+fun lookupBasis ({basis, ...} : t) = basis
+
+datatype core_con =
+ CNormal of int
+ | CFfi of string
+
+datatype core_val =
+ ENormal of int
+ | EFfi of string * L'.con
+
+fun bindCon {basis, cons, constructors, vals, strs, funs, current, nested} s n =
+ let
+ val n' = alloc ()
+
+ val current =
+ case current of
+ FFfi _ => raise Fail "Binding inside FFfi"
+ | FNormal {name, cons, constructors, vals, strs, funs} =>
+ FNormal {name = name,
+ cons = SM.insert (cons, s, n'),
+ constructors = constructors,
+ vals = vals,
+ strs = strs,
+ funs = funs}
+ in
+ ({basis = basis,
+ cons = IM.insert (cons, n, n'),
+ constructors = constructors,
+ vals = vals,
+ strs = strs,
+ funs = funs,
+ current = current,
+ nested = nested},
+ n')
+ end
+
+fun lookupConById ({cons, ...} : t) n = IM.find (cons, n)
+
+fun lookupConByName ({current, ...} : t) x =
+ case current of
+ FFfi {mod = m, ...} => CFfi m
+ | FNormal {cons, ...} =>
+ case SM.find (cons, x) of
+ NONE => raise Fail ("Corify.St.lookupConByName " ^ x)
+ | SOME n => CNormal n
+
+fun bindVal {basis, cons, constructors, vals, strs, funs, current, nested} s n =
+ let
+ val n' = alloc ()
+
+ val current =
+ case current of
+ FFfi _ => raise Fail "Binding inside FFfi"
+ | FNormal {name, cons, constructors, vals, strs, funs} =>
+ FNormal {name = name,
+ cons = cons,
+ constructors = constructors,
+ vals = SM.insert (vals, s, n'),
+ strs = strs,
+ funs = funs}
+ in
+ ({basis = basis,
+ cons = cons,
+ constructors = constructors,
+ vals = IM.insert (vals, n, n'),
+ strs = strs,
+ funs = funs,
+ current = current,
+ nested = nested},
+ n')
+ end
+
+fun bindConstructorVal {basis, cons, constructors, vals, strs, funs, current, nested} s n n' =
+ let
+ val current =
+ case current of
+ FFfi _ => raise Fail "Binding inside FFfi"
+ | FNormal {name, cons, constructors, vals, strs, funs} =>
+ FNormal {name = name,
+ cons = cons,
+ constructors = constructors,
+ vals = SM.insert (vals, s, n'),
+ strs = strs,
+ funs = funs}
+ in
+ {basis = basis,
+ cons = cons,
+ constructors = constructors,
+ vals = IM.insert (vals, n, n'),
+ strs = strs,
+ funs = funs,
+ current = current,
+ nested = nested}
+ end
+
+
+fun lookupValById ({vals, ...} : t) n = IM.find (vals, n)
+
+fun lookupValByName ({current, ...} : t) x =
+ case current of
+ FFfi {mod = m, vals, ...} =>
+ (case SM.find (vals, x) of
+ NONE => raise Fail ("Corify.St.lookupValByName: no type for FFI val " ^ x)
+ | SOME t => EFfi (m, t))
+ | FNormal {name, vals, ...} =>
+ case SM.find (vals, x) of
+ NONE => raise Fail ("Corify.St.lookupValByName " ^ String.concatWith "." (rev name) ^ "." ^ x)
+ | SOME n => ENormal n
+
+fun bindConstructorAs {basis, cons, constructors, vals, strs, funs, current, nested} s n c' =
+ let
+ val current =
+ case current of
+ FFfi _ => raise Fail "Binding inside FFfi"
+ | FNormal {name, cons, constructors, vals, strs, funs} =>
+ FNormal {name = name,
+ cons = cons,
+ constructors = SM.insert (constructors, s, c'),
+ vals = vals,
+ strs = strs,
+ funs = funs}
+ in
+ {basis = basis,
+ cons = cons,
+ constructors = IM.insert (constructors, n, c'),
+ vals = vals,
+ strs = strs,
+ funs = funs,
+ current = current,
+ nested = nested}
+ end
+
+fun bindConstructor st s n =
+ let
+ val n' = alloc ()
+ val c' = L'.PConVar n'
+ in
+ (bindConstructorAs st s n c', n')
+ end
+
+fun lookupConstructorById ({constructors, ...} : t) n =
+ case IM.find (constructors, n) of
+ NONE => raise Fail "Corify.St.lookupConstructorById"
+ | SOME x => x
+
+fun lookupConstructorByIdOpt ({constructors, ...} : t) n =
+ IM.find (constructors, n)
+
+fun lookupConstructorByNameOpt ({current, ...} : t) x =
+ case current of
+ FFfi {mod = m, constructors, ...} =>
+ (case SM.find (constructors, x) of
+ NONE => NONE
+ | SOME (n, xs, to, dk) => SOME (L'.PConFfi {mod = m, datatyp = n, params = xs, con = x, arg = to, kind = dk}))
+ | FNormal {constructors, ...} =>
+ case SM.find (constructors, x) of
+ NONE => NONE
+ | SOME n => SOME n
+
+fun lookupConstructorByName ({current, ...} : t) x =
+ case current of
+ FFfi {mod = m, constructors, ...} =>
+ (case SM.find (constructors, x) of
+ NONE => raise Fail "Corify.St.lookupConstructorByName [1]"
+ | SOME (n, xs, to, dk) => L'.PConFfi {mod = m, datatyp = n, params = xs, con = x, arg = to, kind = dk})
+ | FNormal {constructors, ...} =>
+ case SM.find (constructors, x) of
+ NONE => raise Fail "Corify.St.lookupConstructorByName [2]"
+ | SOME n => n
+
+fun enter ({basis, cons, constructors, vals, strs, funs, current, nested}, name) =
+ {basis = basis,
+ cons = cons,
+ constructors = constructors,
+ vals = vals,
+ strs = strs,
+ funs = funs,
+ current = FNormal {name = name,
+ cons = SM.empty,
+ constructors = SM.empty,
+ vals = SM.empty,
+ strs = SM.empty,
+ funs = SM.empty},
+ nested = current :: nested}
+
+fun dummy (b, f) = {basis = b,
+ cons = IM.empty,
+ constructors = IM.empty,
+ vals = IM.empty,
+ strs = IM.empty,
+ funs = IM.empty,
+ current = f,
+ nested = []}
+
+fun leave {basis, cons, constructors, vals, strs, funs, current, nested = m1 :: rest} =
+ {outer = {basis = basis,
+ cons = cons,
+ constructors = constructors,
+ vals = vals,
+ strs = strs,
+ funs = funs,
+ current = m1,
+ nested = rest},
+ inner = dummy (basis, current)}
+ | leave _ = raise Fail "Corify.St.leave"
+
+fun ffi m vals constructors = dummy (NONE, FFfi {mod = m, vals = vals, constructors = constructors})
+
+fun bindStr ({basis, cons, constructors, vals, strs, funs,
+ current = FNormal {name, cons = mcons, constructors = mconstructors,
+ vals = mvals, strs = mstrs, funs = mfuns}, nested} : t)
+ x n ({current = f, ...} : t) =
+ {basis = basis,
+ cons = cons,
+ constructors = constructors,
+ vals = vals,
+ strs = IM.insert (strs, n, f),
+ funs = funs,
+ current = FNormal {name = name,
+ cons = mcons,
+ constructors = mconstructors,
+ vals = mvals,
+ strs = SM.insert (mstrs, x, f),
+ funs = mfuns},
+ nested = nested}
+ | bindStr _ _ _ _ = raise Fail "Corify.St.bindStr"
+
+fun lookupStrById ({basis, strs, ...} : t) n =
+ case IM.find (strs, n) of
+ NONE => raise Fail ("Corify.St.lookupStrById(" ^ Int.toString n ^ ")")
+ | SOME f => dummy (basis, f)
+
+fun lookupStrByIdOpt ({basis, strs, ...} : t) n =
+ case IM.find (strs, n) of
+ NONE => NONE
+ | SOME f => SOME (dummy (basis, f))
+
+fun lookupStrByName (m, {basis, current = FNormal {strs, ...}, ...} : t) =
+ (case SM.find (strs, m) of
+ NONE => raise Fail "Corify.St.lookupStrByName [1]"
+ | SOME f => dummy (basis, f))
+ | lookupStrByName _ = raise Fail "Corify.St.lookupStrByName [2]"
+
+fun lookupStrByNameOpt (m, {basis, current = FNormal {strs, ...}, ...} : t) =
+ (case SM.find (strs, m) of
+ NONE => NONE
+ | SOME f => SOME (dummy (basis, f)))
+ | lookupStrByNameOpt _ = NONE
+
+fun bindFunctor ({basis, cons, constructors, vals, strs, funs,
+ current = FNormal {name, cons = mcons, constructors = mconstructors,
+ vals = mvals, strs = mstrs, funs = mfuns}, nested} : t)
+ x n xa na str =
+ {basis = basis,
+ cons = cons,
+ constructors = constructors,
+ vals = vals,
+ strs = strs,
+ funs = IM.insert (funs, n, (xa, na, str)),
+ current = FNormal {name = name,
+ cons = mcons,
+ constructors = mconstructors,
+ vals = mvals,
+ strs = mstrs,
+ funs = SM.insert (mfuns, x, (xa, na, str))},
+ nested = nested}
+ | bindFunctor _ _ _ _ _ _ = raise Fail "Corify.St.bindFunctor"
+
+fun lookupFunctorById ({funs, ...} : t) n =
+ case IM.find (funs, n) of
+ NONE => raise Fail "Corify.St.lookupFunctorById"
+ | SOME v => v
+
+fun lookupFunctorByIdOpt ({funs, ...} : t) n =
+ IM.find (funs, n)
+
+fun lookupFunctorByName (m, {current = FNormal {funs, ...}, ...} : t) =
+ (case SM.find (funs, m) of
+ NONE => raise Fail ("Corify.St.lookupFunctorByName " ^ m ^ "[1]")
+ | SOME v => v)
+ | lookupFunctorByName _ = raise Fail "Corify.St.lookupFunctorByName [2]"
+
+end
+
+
+fun corifyKind (k, loc) =
+ case k of
+ L.KType => (L'.KType, loc)
+ | L.KArrow (k1, k2) => (L'.KArrow (corifyKind k1, corifyKind k2), loc)
+ | L.KName => (L'.KName, loc)
+ | L.KRecord k => (L'.KRecord (corifyKind k), loc)
+ | L.KUnit => (L'.KUnit, loc)
+ | L.KTuple ks => (L'.KTuple (map corifyKind ks), loc)
+
+ | L.KRel n => (L'.KRel n, loc)
+ | L.KFun (x, k) => (L'.KFun (x, corifyKind k), loc)
+
+fun corifyCon st (c, loc) =
+ case c of
+ L.TFun (t1, t2) => (L'.TFun (corifyCon st t1, corifyCon st t2), loc)
+ | L.TCFun (x, k, t) => (L'.TCFun (x, corifyKind k, corifyCon st t), loc)
+ | L.TKFun (x, t) => (L'.TKFun (x, corifyCon st t), loc)
+ | L.TRecord c => (L'.TRecord (corifyCon st c), loc)
+
+ | L.CRel n => (L'.CRel n, loc)
+ | L.CNamed n =>
+ (case St.lookupConById st n of
+ NONE => (L'.CNamed n, loc)
+ | SOME n => (L'.CNamed n, loc))
+ | L.CModProj (m, ms, x) =>
+ let
+ val st = St.lookupStrById st m
+ val st = foldl St.lookupStrByName st ms
+ in
+ case St.lookupConByName st x of
+ St.CNormal n => (L'.CNamed n, loc)
+ | St.CFfi m =>
+ if (m, x) = ("Basis", "unit") then
+ (L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc)
+ else
+ (L'.CFfi (m, x), loc)
+ end
+
+ | L.CApp (c1, c2) => (L'.CApp (corifyCon st c1, corifyCon st c2), loc)
+ | L.CAbs (x, k, c) => (L'.CAbs (x, corifyKind k, corifyCon st c), loc)
+
+ | L.CKApp (c1, k) => (L'.CKApp (corifyCon st c1, corifyKind k), loc)
+ | L.CKAbs (x, c) => (L'.CKAbs (x, corifyCon st c), loc)
+
+ | L.CName s => (L'.CName s, loc)
+
+ | L.CRecord (k, xcs) =>
+ (L'.CRecord (corifyKind k, map (fn (c1, c2) => (corifyCon st c1, corifyCon st c2)) xcs), loc)
+ | L.CConcat (c1, c2) => (L'.CConcat (corifyCon st c1, corifyCon st c2), loc)
+ | L.CMap (k1, k2) => (L'.CMap (corifyKind k1, corifyKind k2), loc)
+ | L.CUnit => (L'.CUnit, loc)
+
+ | L.CTuple cs => (L'.CTuple (map (corifyCon st) cs), loc)
+ | L.CProj (c, n) => (L'.CProj (corifyCon st c, n), loc)
+
+fun corifyPatCon st pc =
+ case pc of
+ L.PConVar n => St.lookupConstructorById st n
+ | L.PConProj (m1, ms, x) =>
+ let
+ val st = St.lookupStrById st m1
+ val st = foldl St.lookupStrByName st ms
+ in
+ St.lookupConstructorByName st x
+ end
+
+fun corifyPat st (p, loc) =
+ case p of
+ L.PVar (x, t) => (L'.PVar (x, corifyCon st t), loc)
+ | L.PPrim p => (L'.PPrim p, loc)
+ | L.PCon (dk, pc, ts, po) => (L'.PCon (dk, corifyPatCon st pc, map (corifyCon st) ts,
+ Option.map (corifyPat st) po), loc)
+ | L.PRecord xps => (L'.PRecord (map (fn (x, p, t) => (x, corifyPat st p, corifyCon st t)) xps), loc)
+
+fun corifyExp st (e, loc) =
+ case e of
+ L.EPrim p => (L'.EPrim p, loc)
+ | L.ERel n => (L'.ERel n, loc)
+ | L.ENamed n =>
+ (case St.lookupValById st n of
+ NONE => (L'.ENamed n, loc)
+ | SOME n => (L'.ENamed n, loc))
+ | L.EModProj (m, ms, x) =>
+ let
+ val st = St.lookupStrById st m
+ val st = foldl St.lookupStrByName st ms
+ in
+ case St.lookupConstructorByNameOpt st x of
+ SOME (pc as L'.PConFfi {mod = m, datatyp, params, arg, kind, ...}) =>
+ let
+ val args = ListUtil.mapi (fn (i, _) => (L'.CRel i, loc)) params
+ val e = case arg of
+ NONE => (L'.ECon (kind, pc, args, NONE), loc)
+ | SOME dom => (L'.EAbs ("x", dom, (L'.CFfi (m, datatyp), loc),
+ (L'.ECon (kind, pc, args, SOME (L'.ERel 0, loc)), loc)), loc)
+
+ val k = (L'.KType, loc)
+ in
+ foldr (fn (x, e) => (L'.ECAbs (x, k, e), loc)) e params
+ end
+ | _ =>
+ case St.lookupValByName st x of
+ St.ENormal n => (L'.ENamed n, loc)
+ | St.EFfi (m, t) =>
+ case t of
+ (L'.CApp ((L'.CFfi ("Basis", "transaction"), _), dom), _) =>
+ (L'.EAbs ("arg", dom, (L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc),
+ (L'.EFfiApp (m, x, []), loc)), loc)
+ | t as (L'.TFun _, _) =>
+ let
+ fun getArgs (all as (t, _), args) =
+ case t of
+ L'.TFun (dom, ran) => getArgs (ran, dom :: args)
+ | _ => (all, rev args)
+
+ val (result, args) = getArgs (t, [])
+ val (isTransaction, result) =
+ case result of
+ (L'.CApp ((L'.CFfi ("Basis", "transaction"), _),
+ result), _) => (true, result)
+ | _ => (false, result)
+
+ fun makeApp n =
+ let
+ val (actuals, _) = foldr (fn (t, (actuals, n)) =>
+ (((L'.ERel n, loc), t) :: actuals,
+ n + 1)) ([], n) args
+ in
+ (L'.EFfiApp (m, x, actuals), loc)
+ end
+ val unit = (L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc)
+ val (result, app) =
+ if isTransaction then
+ ((L'.TFun (unit, result), loc),
+ (L'.EAbs ("_",
+ unit,
+ result,
+ makeApp 1), loc))
+ else
+ (result, makeApp 0)
+
+ val (abs, _, _) = foldr (fn (t, (abs, ran, n)) =>
+ ((L'.EAbs ("arg" ^ Int.toString n,
+ t,
+ ran,
+ abs), loc),
+ (L'.TFun (t, ran), loc),
+ n - 1)) (app, result, length args - 1) args
+ in
+ abs
+ end
+ | _ => (L'.EFfi (m, x), loc)
+ end
+ | L.EApp (e1, e2) => (L'.EApp (corifyExp st e1, corifyExp st e2), loc)
+ | L.EAbs (x, dom, ran, e1) => (L'.EAbs (x, corifyCon st dom, corifyCon st ran, corifyExp st e1), loc)
+ | L.ECApp (e1, c) => (L'.ECApp (corifyExp st e1, corifyCon st c), loc)
+ | L.ECAbs (x, k, e1) => (L'.ECAbs (x, corifyKind k, corifyExp st e1), loc)
+ | L.EKApp (e1, k) => (L'.EKApp (corifyExp st e1, corifyKind k), loc)
+ | L.EKAbs (x, e1) => (L'.EKAbs (x, corifyExp st e1), loc)
+
+ | L.ERecord xes => (L'.ERecord (map (fn (c, e, t) =>
+ (corifyCon st c, corifyExp st e, corifyCon st t)) xes), loc)
+ | L.EField (e1, c, {field, rest}) => (L'.EField (corifyExp st e1, corifyCon st c,
+ {field = corifyCon st field, rest = corifyCon st rest}), loc)
+ | L.EConcat (e1, c1, e2, c2) => (L'.EConcat (corifyExp st e1, corifyCon st c1, corifyExp st e2,
+ corifyCon st c2), loc)
+ | L.ECut (e1, c, {field, rest}) => (L'.ECut (corifyExp st e1, corifyCon st c,
+ {field = corifyCon st field, rest = corifyCon st rest}), loc)
+ | L.ECutMulti (e1, c, {rest}) => (L'.ECutMulti (corifyExp st e1, corifyCon st c,
+ {rest = corifyCon st rest}), loc)
+
+ | L.ECase (e, pes, {disc, result}) =>
+ (L'.ECase (corifyExp st e,
+ map (fn (p, e) => (corifyPat st p, corifyExp st e)) pes,
+ {disc = corifyCon st disc, result = corifyCon st result}),
+ loc)
+
+ | L.EWrite e => (L'.EWrite (corifyExp st e), loc)
+
+ | L.ELet (x, t, e1, e2) => (L'.ELet (x, corifyCon st t, corifyExp st e1, corifyExp st e2), loc)
+
+fun isTransactional (c, _) =
+ case c of
+ L'.TFun (_, c) => isTransactional c
+ | L'.CApp ((L'.CFfi ("Basis", "transaction"), _), _) => true
+ | _ => false
+
+fun corifyDecl mods (all as (d, loc : EM.span), st) =
+ case d of
+ L.DCon (x, n, k, c) =>
+ let
+ val (st, n) = St.bindCon st x n
+ in
+ ([(L'.DCon (x, n, corifyKind k, corifyCon st c), loc)], st)
+ end
+ | L.DDatatype dts =>
+ let
+ val (dts, st) = ListUtil.foldlMap (fn ((x, n, xs, xncs), st) =>
+ let
+ val (st, n) = St.bindCon st x n
+ in
+ ((x, n, xs, xncs), st)
+ end)
+ st dts
+
+ val (dts, (st, dcons)) =
+ ListUtil.foldlMap
+ (fn ((x, n, xs, xncs), (st, dcons)) =>
+ let
+ val (xncs, st) = ListUtil.foldlMap
+ (fn ((x, n, co), st) =>
+ let
+ val (st, n') = St.bindConstructor st x n
+ val st = St.bindConstructorVal st x n n'
+ val co = Option.map (corifyCon st) co
+ in
+ ((x, n', co), st)
+ end) st xncs
+
+ val dk = ElabUtil.classifyDatatype xncs
+ val t = (L'.CNamed n, loc)
+ val nxs = length xs - 1
+ val t = ListUtil.foldli
+ (fn (i, _, t) => (L'.CApp (t, (L'.CRel (nxs - i), loc)), loc)) t xs
+ val k = (L'.KType, loc)
+ val dcons' = map (fn (x, n, to) =>
+ let
+ val args = ListUtil.mapi
+ (fn (i, _) => (L'.CRel (nxs - i), loc)) xs
+ val (e, t) =
+ case to of
+ NONE => ((L'.ECon (dk, L'.PConVar n, args, NONE),
+ loc), t)
+ | SOME t' => ((L'.EAbs ("x", t', t,
+ (L'.ECon (dk, L'.PConVar n,
+ args,
+ SOME (L'.ERel 0,
+ loc)),
+ loc)),
+ loc),
+ (L'.TFun (t', t), loc))
+
+ val t = foldr (fn (x, t) => (L'.TCFun (x, k, t), loc)) t xs
+ val e = foldr (fn (x, e) => (L'.ECAbs (x, k, e), loc)) e xs
+ in
+ (L'.DVal (x, n, t, e, ""), loc)
+ end) xncs
+ in
+ ((x, n, xs, xncs), (st, dcons' @ dcons))
+ end)
+ (st, []) dts
+ in
+ ((L'.DDatatype dts, loc) :: dcons, st)
+ end
+ | L.DDatatypeImp (x, n, m1, ms, s, xs, xncs) =>
+ let
+ val (st, n) = St.bindCon st x n
+ val c = corifyCon st (L.CModProj (m1, ms, s), loc)
+
+ val m = foldl (fn (x, m) => (L.StrProj (m, x), loc)) (L.StrVar m1, loc) ms
+ val (_, {inner, ...}) = corifyStr mods (m, st)
+
+ val (xncs, st) = ListUtil.foldlMap (fn ((x, n, co), st) =>
+ let
+ val n' = St.lookupConstructorByName inner x
+ val st = St.bindConstructorAs st x n n'
+ val (st, n) = St.bindVal st x n
+ val co = Option.map (corifyCon st) co
+ in
+ ((x, n, co), st)
+ end) st xncs
+
+ val nxs = length xs - 1
+ val cBase = c
+ val c = ListUtil.foldli (fn (i, _, c) => (L'.CApp (c, (L'.CRel (nxs - i), loc)), loc)) c xs
+ val k = (L'.KType, loc)
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+
+ val cds = map (fn (x, n, co) =>
+ let
+ val t = case co of
+ NONE => c
+ | SOME t' => (L'.TFun (t', c), loc)
+ val e = corifyExp st (L.EModProj (m1, ms, x), loc)
+
+ val t = foldr (fn (x, t) => (L'.TCFun (x, k, t), loc)) t xs
+ in
+ (L'.DVal (x, n, t, e, x), loc)
+ end) xncs
+ in
+ ((L'.DCon (x, n, k', cBase), loc) :: cds, st)
+ end
+ | L.DVal (x, n, t, e as (L.ENamed n', _)) =>
+ let
+ val st =
+ case St.lookupConstructorByIdOpt st n' of
+ SOME pc => St.bindConstructorAs st x n pc
+ | _ => st
+
+ val (st, n) = St.bindVal st x n
+ val s = doRestify Settings.Url (mods, x)
+ in
+ ([(L'.DVal (x, n, corifyCon st t, corifyExp st e, s), loc)], st)
+ end
+ | L.DVal (x, n, t, e) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = doRestify Settings.Url (mods, x)
+ in
+ ([(L'.DVal (x, n, corifyCon st t, corifyExp st e, s), loc)], st)
+ end
+ | L.DValRec vis =>
+ let
+ val (vis, st) = ListUtil.foldlMap
+ (fn ((x, n, t, e), st) =>
+ let
+ val (st, n) = St.bindVal st x n
+ in
+ ((x, n, t, e), st)
+ end)
+ st vis
+
+ val vis = map
+ (fn (x, n, t, e) =>
+ let
+ val s = doRestify Settings.Url (mods, x)
+ in
+ (x, n, corifyCon st t, corifyExp st e, s)
+ end)
+ vis
+ in
+ ([(L'.DValRec vis, loc)], st)
+ end
+ | L.DSgn _ => ([], st)
+
+ | L.DStr (x, n, _, (L.StrFun (xa, na, _, _, str), _)) =>
+ ([], St.bindFunctor st x n xa na str)
+
+ | L.DStr (x, n, _, (L.StrProj (str, x'), _)) =>
+ let
+ val (ds, {inner, outer}) = corifyStr mods (str, st)
+
+ val st = case St.lookupStrByNameOpt (x', inner) of
+ SOME st' => St.bindStr st x n st'
+ | NONE =>
+ let
+ val (x', n', str') = St.lookupFunctorByName (x', inner)
+ in
+ St.bindFunctor st x n x' n' str'
+ end
+ in
+ ([], st)
+ end
+
+ | L.DStr (x, n, _, (L.StrVar n', _)) =>
+ (case St.lookupFunctorByIdOpt st n' of
+ SOME (arg, dom, body) => ([], St.bindFunctor st x n arg dom body)
+ | NONE => ([], St.bindStr st x n (St.lookupStrById st n')))
+
+ | L.DStr (x, n, _, str) =>
+ let
+ val mods' =
+ if x = "anon" then
+ mods
+ else
+ x :: mods
+
+ val (ds, {inner, outer}) = corifyStr mods' (str, st)
+ val st = St.bindStr outer x n inner
+ in
+ (ds, st)
+ end
+
+ | L.DFfiStr (m, n, (sgn, _)) =>
+ (case sgn of
+ L.SgnConst sgis =>
+ let
+ val (ds, cmap, conmap, st, _) =
+ foldl (fn ((sgi, _), (ds, cmap, conmap, st, trans)) =>
+ case sgi of
+ L.SgiConAbs (x, n, k) =>
+ let
+ val (st, n') = St.bindCon st x n
+
+ val trans =
+ if x = "transaction" then
+ SOME n
+ else
+ trans
+ in
+ ((L'.DCon (x, n', corifyKind k, (L'.CFfi (m, x), loc)), loc) :: ds,
+ cmap,
+ conmap,
+ st,
+ trans)
+ end
+ | L.SgiCon (x, n, k, _) =>
+ let
+ val (st, n') = St.bindCon st x n
+ in
+ ((L'.DCon (x, n', corifyKind k, (L'.CFfi (m, x), loc)), loc) :: ds,
+ cmap,
+ conmap,
+ st,
+ trans)
+ end
+
+ | L.SgiDatatype dts =>
+ let
+ val k = (L'.KType, loc)
+
+ val (dts, (ds', st, cmap, conmap)) =
+ ListUtil.foldlMap
+ (fn ((x, n, xs, xnts), (ds', st, cmap, conmap)) =>
+ let
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc))
+ k xs
+
+ val dk = ElabUtil.classifyDatatype xnts
+ val (st, n') = St.bindCon st x n
+ val (xnts, (ds', st, cmap, conmap)) =
+ ListUtil.foldlMap
+ (fn ((x', n, to), (ds', st, cmap, conmap)) =>
+ let
+ val dt = (L'.CNamed n', loc)
+ val args = ListUtil.mapi (fn (i, _) => (L'.CRel i, loc)) xs
+
+ val to = Option.map (corifyCon st) to
+
+ val pc = L'.PConFfi {mod = m,
+ datatyp = x,
+ params = xs,
+ con = x',
+ arg = to,
+ kind = dk}
+
+ fun wrapT t =
+ foldr (fn (x, t) => (L'.TCFun (x, k, t), loc))
+ t xs
+ fun wrapE e =
+ foldr (fn (x, e) => (L'.ECAbs (x, k, e), loc))
+ e xs
+
+ val (cmap, d) =
+ case to of
+ NONE => (SM.insert (cmap, x', wrapT dt),
+ (L'.DVal (x', n, wrapT dt,
+ wrapE
+ (L'.ECon (dk, pc,
+ args,
+ NONE),
+ loc),
+ ""), loc))
+ | SOME t =>
+ let
+ val tf = (L'.TFun (t, dt), loc)
+ val e = wrapE
+ (L'.EAbs ("x", t, tf,
+ (L'.ECon (dk,
+ pc,
+ args,
+ SOME
+ (L'.ERel 0,
+ loc)),
+ loc)), loc)
+ val d = (L'.DVal (x', n, wrapT tf,
+ e, ""), loc)
+ in
+ (SM.insert (cmap, x', wrapT tf), d)
+ end
+
+ val st = St.bindConstructorAs st x' n pc
+
+ val conmap = SM.insert (conmap, x',
+ (x, xs, to, dk))
+ in
+ ((x', n, to),
+ (d :: ds', st, cmap, conmap))
+ end) (ds', st, cmap, conmap) xnts
+
+ val d = (L'.DCon (x, n', k', (L'.CFfi (m, x), loc)), loc)
+ in
+ ((x, n', xs, xnts), (d :: ds', st, cmap, conmap))
+ end)
+ ([], st, cmap, conmap) dts
+ in
+ (List.revAppend (ds', ds),
+ cmap,
+ conmap,
+ st,
+ trans)
+ end
+
+ | L.SgiVal (x, _, c) =>
+ let
+ val c =
+ case trans of
+ NONE => corifyCon st c
+ | SOME trans =>
+ let
+ fun transactify (all as (c, loc)) =
+ case c of
+ L.TFun (dom, ran) =>
+ (L'.TFun (corifyCon st dom, transactify ran), loc)
+ | L.CApp ((L.CNamed trans', _), t) =>
+ if trans' = trans then
+ (L'.CApp ((L'.CFfi (m, "transaction"), loc),
+ corifyCon st t), loc)
+ else
+ corifyCon st all
+ | _ => corifyCon st all
+ in
+ transactify c
+ end
+ in
+ if isTransactional c then
+ let
+ val ffi = (m, x)
+ in
+ if Settings.isBenignEffectful ffi then
+ ()
+ else
+ Settings.addEffectful ffi
+ end
+ else
+ ();
+ (ds,
+ SM.insert (cmap, x, c),
+ conmap,
+ st,
+ trans)
+ end
+ | _ => (ds, cmap, conmap, st, trans))
+ ([], SM.empty, SM.empty, st, NONE) sgis
+
+ val st = St.bindStr st m n (St.ffi m cmap conmap)
+ in
+ (rev ds, if m = "Basis" then St.basisIs (st, n) else st)
+ end
+ | _ => raise Fail "Non-const signature for FFI structure")
+
+ | L.DExport (en, sgn, str) =>
+ (case #1 sgn of
+ L.SgnConst sgis =>
+ let
+ fun pathify (str, _) =
+ case str of
+ L.StrVar m => SOME (m, [])
+ | L.StrProj (str, s) =>
+ Option.map (fn (m, ms) => (m, ms @ [s])) (pathify str)
+ | _ => NONE
+ in
+ case pathify str of
+ NONE => (ErrorMsg.errorAt loc "Structure is too fancy to export";
+ ([], st))
+ | SOME (m, ms) =>
+ let
+ val basis_n = case St.lookupBasis st of
+ NONE => raise Fail "Corify: Don't know number of Basis"
+ | SOME n => n
+
+ fun wrapSgi ((sgi, _), (wds, eds)) =
+ case sgi of
+ L.SgiVal (s, _, t) =>
+ let
+ fun getPage (t, args) =
+ case #1 t of
+ L.CApp ((L.CModProj (basis, [], "transaction"), _),
+ t' as
+ (L.CApp
+ ((L.CApp
+ ((L.CApp ((L.CModProj (basis', [], "xml"), _),
+ (L.CRecord (_, [((L.CName "Html", _),
+ _)]), _)), _), _),
+ _), _), _)) =>
+ if basis = basis_n andalso basis' = basis_n then
+ SOME (t', rev args)
+ else
+ NONE
+ | L.TFun (dom, ran) => getPage (ran, dom :: args)
+ | _ => NONE
+ in
+ case getPage (t, []) of
+ NONE => (wds, eds)
+ | SOME (ran', args) =>
+ let
+ val ran = (L.TRecord (L.CRecord ((L.KType, loc), []), loc), loc)
+ val ranT = (L.CApp ((L.CModProj (basis_n, [], "transaction"), loc),
+ ran), loc)
+ val e = (L.EModProj (m, ms, s), loc)
+
+ val ef = (L.EModProj (basis_n, [], "bind"), loc)
+ val ef = (L.ECApp (ef, (L.CModProj (basis_n, [], "transaction"), loc)), loc)
+ val ef = (L.ECApp (ef, ran'), loc)
+ val ef = (L.ECApp (ef, ran), loc)
+ val ef = (L.EApp (ef, (L.EModProj (basis_n, [], "transaction_monad"), loc)),
+ loc)
+ val ea = ListUtil.foldri (fn (i, _, ea) =>
+ (L.EApp (ea, (L.ERel i, loc)), loc)) e args
+ val ef = (L.EApp (ef, ea), loc)
+
+ val eat = (L.CApp ((L.CModProj (basis_n, [], "transaction"), loc),
+ ran), loc)
+ val ea = (L.EAbs ("p", ran', eat,
+ (L.EWrite (L.ERel 0, loc), loc)), loc)
+
+ val (e, tf) = ListUtil.foldri (fn (i, t, (e, tf)) =>
+ ((L.EAbs ("x" ^ Int.toString i,
+ t, tf, e), loc),
+ (L.TFun (t, tf), loc)))
+ ((L.EApp (ef, ea), loc), ranT) args
+
+ val expKind = if List.exists (fn t =>
+ case corifyCon st t of
+ (L'.CFfi ("Basis", "postBody"), _) => true
+ | _ => false) args then
+ L'.Extern L'.ReadCookieWrite
+ else
+ L'.Link L'.ReadCookieWrite
+ in
+ ((L.DVal ("wrap_" ^ s, 0, tf, e), loc) :: wds,
+ (fn st =>
+ case #1 (corifyExp st (L.EModProj (en, [], "wrap_" ^ s), loc)) of
+ L'.ENamed n => (L'.DExport (expKind, n, false), loc)
+ | _ => raise Fail "Corify: Value to export didn't corify properly")
+ :: eds)
+ end
+ end
+ | _ => (wds, eds)
+
+ val (wds, eds) = foldl wrapSgi ([], []) sgis
+ val wrapper = (L.StrConst wds, loc)
+ val mst = St.lookupStrById st m
+ val mst = foldl St.lookupStrByName mst ms
+ val (ds, {inner, outer}) = corifyStr (St.name mst) (wrapper, st)
+ val st = St.bindStr outer "wrapper" en inner
+
+ val ds = ds @ map (fn f => f st) eds
+ in
+ (ds, st)
+ end
+ end
+ | _ => raise Fail "Non-const signature for 'export'")
+
+ | L.DTable (_, x, n, c, pe, pc, ce, cc) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = relify (doRestify Settings.Table (mods, x))
+ in
+ ([(L'.DTable (x, n, corifyCon st c, s,
+ corifyExp st pe, corifyCon st pc,
+ corifyExp st ce, corifyCon st cc), loc)], st)
+ end
+ | L.DSequence (_, x, n) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = relify (doRestify Settings.Sequence (mods, x))
+ in
+ ([(L'.DSequence (x, n, s), loc)], st)
+ end
+ | L.DView (_, x, n, e, c) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = relify (doRestify Settings.View (mods, x))
+ in
+ ([(L'.DView (x, n, s, corifyExp st e, corifyCon st c), loc)], st)
+ end
+
+ | L.DDatabase s => ([(L'.DDatabase s, loc)], st)
+
+ | L.DCookie (_, x, n, c) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = doRestify Settings.Cookie (mods, x)
+ in
+ ([(L'.DCookie (x, n, corifyCon st c, s), loc)], st)
+ end
+ | L.DStyle (_, x, n) =>
+ let
+ val (st, n) = St.bindVal st x n
+ val s = relify (doRestify Settings.Style (mods, x))
+ in
+ ([(L'.DStyle (x, n, s), loc)], st)
+ end
+
+ | L.DTask (e1, e2) =>
+ ([(L'.DTask (corifyExp st e1, corifyExp st e2), loc)], st)
+
+ | L.DPolicy e1 =>
+ ([(L'.DPolicy (corifyExp st e1), loc)], st)
+
+ | L.DOnError (m, ms, x) =>
+ let
+ val st = St.lookupStrById st m
+ val st = foldl St.lookupStrByName st ms
+ in
+ case St.lookupValByName st x of
+ St.ENormal n => ([(L'.DOnError n, loc)], st)
+ | _ => (ErrorMsg.errorAt loc "Wrong type of identifier for 'onError'";
+ ([], st))
+ end
+
+ | L.DFfi (x, n, modes, t) =>
+ let
+ val m = case St.name st of
+ [m] => m
+ | _ => (ErrorMsg.errorAt loc "Used 'ffi' declaration beneath module top level";
+ "")
+
+ val name = (m, x)
+
+ val (st, n) = St.bindVal st x n
+ val s = doRestify Settings.Url (mods, x)
+
+ val t' = corifyCon st t
+
+ fun numArgs (t : L'.con) =
+ case #1 t of
+ L'.TFun (_, ran) => 1 + numArgs ran
+ | _ => 0
+
+ fun makeArgs (i, t : L'.con, acc) =
+ case #1 t of
+ L'.TFun (dom, ran) => makeArgs (i-1, ran, ((L'.ERel i, loc), dom) :: acc)
+ | _ => rev acc
+
+ fun wrapAbs (i, t : L'.con, tTrans, e) =
+ case (#1 t, #1 tTrans) of
+ (L'.TFun (dom, ran), L'.TFun (_, ran')) => (L'.EAbs ("x" ^ Int.toString i, dom, ran, wrapAbs (i+1, ran, ran', e)), loc)
+ | _ => e
+
+ fun getRan (t : L'.con) =
+ case #1 t of
+ L'.TFun (_, ran) => getRan ran
+ | _ => t
+
+ fun addLastBit (t : L'.con) =
+ case #1 t of
+ L'.TFun (dom, ran) => (L'.TFun (dom, addLastBit ran), #2 t)
+ | _ => (L'.TFun ((L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc), t), loc)
+
+ val isTrans = isTransactional t'
+ val e = (L'.EFfiApp (m, x, makeArgs (numArgs t' -
+ (if isTrans then
+ 0
+ else
+ 1), t', [])), loc)
+ val (e, tTrans) = if isTrans then
+ ((L'.EAbs ("_", (L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc), getRan t', e), loc), addLastBit t')
+ else
+ (e, t')
+ val e = wrapAbs (0, t', tTrans, e)
+ in
+ app (fn Source.Effectful => Settings.addEffectful name
+ | Source.BenignEffectful => Settings.addBenignEffectful name
+ | Source.ClientOnly => Settings.addClientOnly name
+ | Source.ServerOnly => Settings.addServerOnly name
+ | Source.JsFunc s => Settings.addJsFunc (name, s)) modes;
+
+ if List.exists (fn Source.JsFunc _ => true | _ => false) modes then
+ ()
+ else
+ Settings.addJsFunc (name, #2 name);
+
+ if isTrans andalso not (Settings.isBenignEffectful name) then
+ Settings.addEffectful name
+ else
+ ();
+
+ ([(L'.DVal (x, n, t', e, s), loc)], st)
+ end
+
+and corifyStr mods ((str, loc), st) =
+ case str of
+ L.StrConst ds =>
+ let
+ val st = St.enter (st, mods)
+ val (ds, st) = ListUtil.foldlMapConcat (corifyDecl mods) st ds
+ in
+ (ds, St.leave st)
+ end
+ | L.StrVar n => ([], {inner = St.lookupStrById st n, outer = st})
+ | L.StrProj (str, x) =>
+ let
+ val (ds, {inner, outer}) = corifyStr mods (str, st)
+ in
+ (ds, {inner = St.lookupStrByName (x, inner), outer = outer})
+ end
+ | L.StrFun _ => raise Fail "Corify of nested functor definition"
+ | L.StrApp (str1, str2) =>
+ let
+ fun unwind' (str, _) =
+ case str of
+ L.StrVar n => St.lookupStrById st n
+ | L.StrProj (str, x) => St.lookupStrByName (x, unwind' str)
+ | _ => raise Fail "Corify of fancy functor application [1]"
+
+ fun unwind (str, _) =
+ case str of
+ L.StrVar n => St.lookupFunctorById st n
+ | L.StrProj (str, x) => St.lookupFunctorByName (x, unwind' str)
+ | _ => raise Fail "Corify of fancy functor application [2]"
+
+ val (xa, na, body) = unwind str1
+
+ (* An important step to make sure that nested functors
+ * "close under their environments": *)
+ val (next, body') = ExplRename.rename {NextId = getCounter (),
+ FormalName = xa,
+ FormalId = na,
+ Body = body}
+
+ (*val () = Print.prefaces ("RENAME " ^ ErrorMsg.spanToString loc)
+ [("FROM", ExplPrint.p_str ExplEnv.empty body),
+ ("TO", ExplPrint.p_str ExplEnv.empty body')]*)
+ val body = body'
+
+ val () = setCounter next
+
+ val (ds1, {inner = inner', outer}) = corifyStr mods (str2, st)
+
+ val (ds2, {inner, outer}) = corifyStr mods (body, St.bindStr outer xa na inner')
+ in
+ (ds1 @ ds2, {inner = St.bindStr inner xa na inner', outer = outer})
+ end
+
+fun maxName ds = foldl (fn ((d, _), n) =>
+ case d of
+ L.DCon (_, n', _, _) => Int.max (n, n')
+ | L.DDatatype dts => foldl (fn ((_, n', _, _), n) => Int.max (n, n')) n dts
+ | L.DDatatypeImp (_, n', _, _, _, _, _) => Int.max (n, n')
+ | L.DVal (_, n', _, _) => Int.max (n, n')
+ | L.DValRec vis => foldl (fn ((_, n', _, _), n) => Int.max (n, n)) n vis
+ | L.DSgn (_, n', _) => Int.max (n, n')
+ | L.DStr (_, n', _, str) => Int.max (n, Int.max (n', maxNameStr str))
+ | L.DFfiStr (_, n', _) => Int.max (n, n')
+ | L.DExport _ => n
+ | L.DTable (_, _, n', _, _, _, _, _) => Int.max (n, n')
+ | L.DSequence (_, _, n') => Int.max (n, n')
+ | L.DView (_, _, n', _, _) => Int.max (n, n')
+ | L.DDatabase _ => n
+ | L.DCookie (_, _, n', _) => Int.max (n, n')
+ | L.DStyle (_, _, n') => Int.max (n, n')
+ | L.DTask _ => n
+ | L.DPolicy _ => n
+ | L.DOnError _ => n
+ | L.DFfi (_, n', _, _) => Int.max (n, n'))
+ 0 ds
+
+and maxNameStr (str, _) =
+ case str of
+ L.StrConst ds => maxName ds
+ | L.StrVar n => n
+ | L.StrProj (str, _) => maxNameStr str
+ | L.StrFun (_, _, _, _, str) => maxNameStr str
+ | L.StrApp (str1, str2) => Int.max (maxNameStr str1, maxNameStr str2)
+
+fun corify ds =
+ let
+ val () = reset (maxName ds + 1)
+
+ val (ds, _) = ListUtil.foldlMapConcat (corifyDecl []) St.empty ds
+ in
+ ds
+ end
+
+end
diff --git a/src/css.sig b/src/css.sig
new file mode 100644
index 0000000..c7243cf
--- /dev/null
+++ b/src/css.sig
@@ -0,0 +1,43 @@
+(* Copyright (c) 2010, 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.
+ *)
+
+signature CSS = sig
+
+ datatype inheritable = Block | List | Table | Caption | Td
+ datatype others = OBlock | OTable | OTd | Tr | NonReplacedInline | ReplacedInline | Width | Height
+
+ val inheritableToString : inheritable -> string
+ val othersToString : others -> string
+
+ type summary = inheritable list * others list
+
+ type report = {Overall : inheritable list,
+ Classes : (string * summary) list}
+
+ val summarize : Core.file -> report
+
+end
diff --git a/src/css.sml b/src/css.sml
new file mode 100644
index 0000000..9e50686
--- /dev/null
+++ b/src/css.sml
@@ -0,0 +1,320 @@
+(* Copyright (c) 2010, 2013, 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 Css :> CSS = struct
+
+structure IM = IntBinaryMap
+
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+datatype inheritable = Block | List | Table | Caption | Td
+datatype others = OBlock | OTable | OTd | Tr | NonReplacedInline | ReplacedInline | Width | Height
+
+fun inheritableToString x =
+ case x of
+ Block => "B"
+ | List => "L"
+ | Table => "T"
+ | Caption => "C"
+ | Td => "D"
+
+fun othersToString x =
+ case x of
+ OBlock => "b"
+ | OTable => "t"
+ | OTd => "d"
+ | Tr => "-"
+ | NonReplacedInline => "N"
+ | ReplacedInline => "R"
+ | Width => "W"
+ | Height => "H"
+
+type summary = inheritable list * others list
+
+fun merge' (ls1, ls2) = foldl (fn (x, ls) => if List.exists (fn y => y = x) ls then ls else x :: ls) ls2 ls1
+fun merge ((in1, ot1), (in2, ot2)) = (merge' (in1, in2), merge' (ot1, ot2))
+fun mergePC {parent = (in1, ot1), child = in2} = (merge' (in1, in2), ot1)
+
+val nada = ([], [])
+val block = ([Block], [OBlock, Width, Height])
+val inline = ([], [NonReplacedInline])
+val list = ([Block, List], [OBlock, Width, Height])
+val replaced = ([], [ReplacedInline, Width, Height])
+val table = ([Block, Table], [OBlock, OTable, Width, Height])
+val tr = ([Block], [OBlock, Tr, Height])
+val td = ([Block, Td], [OBlock, OTd, Width])
+
+val tags = [("span", inline),
+ ("div", block),
+ ("p", block),
+ ("b", inline),
+ ("i", inline),
+ ("tt", inline),
+ ("h1", block),
+ ("h2", block),
+ ("h3", block),
+ ("h4", block),
+ ("h5", block),
+ ("h6", block),
+ ("li", list),
+ ("ol", list),
+ ("ul", list),
+ ("hr", block),
+ ("a", inline),
+ ("img", replaced),
+ ("form", block),
+ ("hidden", replaced),
+ ("textbox", replaced),
+ ("password", replaced),
+ ("textarea", replaced),
+ ("checkbox", replaced),
+ ("upload", replaced),
+ ("radio", replaced),
+ ("select", replaced),
+ ("submit", replaced),
+ ("label", inline),
+ ("ctextbox", replaced),
+ ("cpassword", replaced),
+ ("button", replaced),
+ ("ccheckbox", replaced),
+ ("cselect", replaced),
+ ("ctextarea", replaced),
+ ("tabl", table),
+ ("tr", tr),
+ ("th", td),
+ ("td", td)]
+
+val tags = foldl (fn ((tag, css), tags) =>
+ SM.insert (tags, tag, css)) SM.empty tags
+
+open Core
+
+fun summarize file =
+ let
+ fun decl ((d, _), st as (globals, classes)) =
+ let
+ fun getTag (e, _) =
+ case e of
+ EFfi ("Basis", tag) => SOME tag
+ | ECApp (e, _) => getTag e
+ | EApp (e, _) => getTag e
+ | _ => NONE
+
+ fun exp ((e, _), classes) =
+ case e of
+ EPrim _ => ([], classes)
+ | ERel _ => ([], classes)
+ | ENamed n =>
+ (case IM.find (globals, n) of
+ NONE => []
+ | SOME (_, sm) => sm,
+ classes)
+ | ECon (_, _, _, NONE) => ([], classes)
+ | ECon (_, _, _, SOME e) => exp (e, classes)
+ | EFfi _ => ([], classes)
+ | EFfiApp (_, _, es) => expList (map #1 es, classes)
+
+ | EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (EFfi ("Basis", "tag"),
+ _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _),
+ (ENamed class, _)), _),
+ _), _),
+ _), _),
+ _), _),
+ attrs), _),
+ tag), _),
+ xml) =>
+ let
+ val (sm, classes) = exp (xml, classes)
+ val (sm', classes) = exp (attrs, classes)
+ val sm = merge' (sm, sm')
+ in
+ case getTag tag of
+ NONE => (sm, classes)
+ | SOME tag =>
+ case SM.find (tags, tag) of
+ NONE => (sm, classes)
+ | SOME sm' =>
+ let
+ val sm'' = mergePC {parent = sm', child = sm}
+ val old = Option.getOpt (IM.find (classes, class), nada)
+ val classes = IM.insert (classes, class, merge (old, sm''))
+ in
+ (merge' (#1 sm', sm), classes)
+ end
+ end
+
+ | EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (EFfi ("Basis", "tag"),
+ _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ attrs), _),
+ tag), _),
+ xml) =>
+ let
+ val (sm, classes) = exp (xml, classes)
+ val (sm', classes) = exp (attrs, classes)
+ val sm = merge' (sm, sm')
+ in
+ case getTag tag of
+ NONE => (sm, classes)
+ | SOME tag =>
+ case SM.find (tags, tag) of
+ NONE => (sm, classes)
+ | SOME sm' => (merge' (#1 sm', sm), classes)
+ end
+
+ | EApp (e1, e2) =>
+ let
+ val (sm1, classes) = exp (e1, classes)
+ val (sm2, classes) = exp (e2, classes)
+ in
+ (merge' (sm1, sm2), classes)
+ end
+ | EAbs (_, _, _, e) => exp (e, classes)
+ | ECApp (e, _) => exp (e, classes)
+ | ECAbs (_, _, e) => exp (e, classes)
+ | EKAbs (_, e) => exp (e, classes)
+ | EKApp (e, _) => exp (e, classes)
+ | ERecord xets => expList (map #2 xets, classes)
+ | EField (e, _, _) => exp (e, classes)
+ | EConcat (e1, _, e2, _) =>
+ let
+ val (sm1, classes) = exp (e1, classes)
+ val (sm2, classes) = exp (e2, classes)
+ in
+ (merge' (sm1, sm2), classes)
+ end
+ | ECut (e, _, _) => exp (e, classes)
+ | ECutMulti (e, _, _) => exp (e, classes)
+ | ECase (e, pes, _) =>
+ let
+ val (sm, classes) = exp (e, classes)
+ val (sms, classes) = expList (map #2 pes, classes)
+ in
+ (merge' (sm, sms), classes)
+ end
+ | EWrite e => exp (e, classes)
+ | EClosure (_, es) => expList (es, classes)
+ | ELet (_, _, e1, e2) =>
+ let
+ val (sm1, classes) = exp (e1, classes)
+ val (sm2, classes) = exp (e2, classes)
+ in
+ (merge' (sm1, sm2), classes)
+ end
+ | EServerCall (_, es, _, _) => expList (es, classes)
+
+ and expList (es, classes) = foldl (fn (e, (sm, classes)) =>
+ let
+ val (sm', classes) = exp (e, classes)
+ in
+ (merge' (sm, sm'), classes)
+ end) ([], classes) es
+ in
+ case d of
+ DCon _ => st
+ | DDatatype _ => st
+ | DVal (_, n, _, e, _) =>
+ let
+ val (sm, classes) = exp (e, classes)
+ in
+ (IM.insert (globals, n, (NONE, sm)), classes)
+ end
+ | DValRec vis =>
+ let
+ val (sm, classes) = foldl (fn ((_, _, _, e, _),
+ (sm, classes)) =>
+ let
+ val (sm', classes) = exp (e, classes)
+ in
+ (merge' (sm', sm), classes)
+ end) ([], classes) vis
+ in
+ (foldl (fn ((_, n, _, _, _), globals) => IM.insert (globals, n, (NONE, sm))) globals vis,
+ classes)
+ end
+ | DExport _ => st
+ | DTable _ => st
+ | DSequence _ => st
+ | DView _ => st
+ | DDatabase _ => st
+ | DCookie _ => st
+ | DStyle (_, n, s) => (IM.insert (globals, n, (SOME s, [])), classes)
+ | DTask _ => st
+ | DPolicy _ => st
+ | DOnError _ => st
+ end
+
+ val (globals, classes) = foldl decl (IM.empty, IM.empty) file
+ in
+ {Overall = IM.foldl (fn ((_, sm), sm') => merge' (sm, sm')) [] globals,
+ Classes = ListMergeSort.sort (fn ((s1, _), (s2, _)) => String.compare (s1, s2) = GREATER)
+ (List.mapPartial (fn (i, sm) =>
+ case IM.find (globals, i) of
+ SOME (SOME s, _) => SOME (s, sm)
+ | _ => NONE) (IM.listItemsi classes))}
+ end
+
+type report = {Overall : inheritable list,
+ Classes : (string * summary) list}
+
+end
diff --git a/src/datatype_kind.sml b/src/datatype_kind.sml
new file mode 100644
index 0000000..140a012
--- /dev/null
+++ b/src/datatype_kind.sml
@@ -0,0 +1,35 @@
+(* Copyright (c) 2009, 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 DatatypeKind = struct
+
+datatype datatype_kind =
+ Enum
+ | Option
+ | Default
+
+end
diff --git a/src/dbmodecheck.sig b/src/dbmodecheck.sig
new file mode 100644
index 0000000..4d4873c
--- /dev/null
+++ b/src/dbmodecheck.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2014, 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.
+ *)
+
+signature DB_MODE_CHECK = sig
+
+ val classify : Mono.file -> Mono.file
+
+end
diff --git a/src/dbmodecheck.sml b/src/dbmodecheck.sml
new file mode 100644
index 0000000..eb416ce
--- /dev/null
+++ b/src/dbmodecheck.sml
@@ -0,0 +1,86 @@
+(* Copyright (c) 2014, 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 DbModeCheck :> DB_MODE_CHECK = struct
+
+open Mono
+
+structure IM = IntBinaryMap
+
+fun classify (ds, ps) =
+ let
+ fun mergeModes (m1, m2) =
+ case (m1, m2) of
+ (NoDb, _) => m2
+ | (_, NoDb) => m1
+ | _ => AnyDb
+
+ fun modeOf modes =
+ MonoUtil.Exp.fold {typ = fn (_, dbm) => dbm,
+ exp = fn (EQuery _, dbm) => mergeModes (OneQuery, dbm)
+ | (EDml _, _) => AnyDb
+ | (ENextval _, _) => AnyDb
+ | (ESetval _, _) => AnyDb
+ | (ENamed n, dbm) =>
+ (case IM.find (modes, n) of
+ NONE => dbm
+ | SOME dbm' => mergeModes (dbm, dbm'))
+ | (_, dbm) => dbm} NoDb
+
+ fun decl ((d, _), modes) =
+ case d of
+ DVal (x, n, _, e, _) => IM.insert (modes, n, modeOf modes e)
+ | DValRec xes =>
+ let
+ val mode = foldl (fn ((_, _, _, e, _), mode) =>
+ let
+ val mode' = modeOf modes e
+ in
+ case mode' of
+ NoDb => mode
+ | _ => AnyDb
+ end) NoDb xes
+ in
+ foldl (fn ((_, n, _, _, _), modes) => IM.insert (modes, n, mode)) modes xes
+ end
+ | _ => modes
+
+ val modes = foldl decl IM.empty ds
+
+ val (ps, modes) = ListUtil.foldlMap (fn ((n, side, _), modes) =>
+ case IM.find (modes, n) of
+ NONE => ((n, side, AnyDb), modes)
+ | SOME mode => ((n, side, mode), #1 (IM.remove (modes, n))))
+ modes ps
+
+ val ps = IM.foldli (fn (n, mode, ps) => (n, ServerOnly, mode) :: ps) ps modes
+ in
+ (ds, ps)
+ end
+
+end
+
diff --git a/src/demo.sig b/src/demo.sig
new file mode 100644
index 0000000..57154ed
--- /dev/null
+++ b/src/demo.sig
@@ -0,0 +1,35 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature DEMO = sig
+
+ val make : {prefix : string, dirname : string, guided : bool} -> unit
+ val make' : {prefix : string, dirname : string, guided : bool} -> bool
+
+ val noEmacs : bool ref
+
+end
diff --git a/src/demo.sml b/src/demo.sml
new file mode 100644
index 0000000..62b9037
--- /dev/null
+++ b/src/demo.sml
@@ -0,0 +1,477 @@
+(* Copyright (c) 2008-2010, 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 Demo :> DEMO = struct
+
+val noEmacs = ref false
+
+fun make' {prefix, dirname, guided} =
+ let
+ val prose = OS.Path.joinDirFile {dir = dirname,
+ file = "prose"}
+ val inf = FileIO.txtOpenIn prose
+
+ val outDir = OS.Path.concat (dirname, "out")
+
+ val () = if OS.FileSys.access (outDir, []) then
+ ()
+ else
+ OS.FileSys.mkDir outDir
+
+ val fname = OS.Path.joinDirFile {dir = outDir,
+ file = "index.html"}
+
+ val out = TextIO.openOut fname
+ val () = (TextIO.output (out, "<frameset cols=\"15%,85%\">\n");
+ TextIO.output (out, "<frame src=\"demos.html\">\n");
+ TextIO.output (out, "<frame src=\"intro.html\" name=\"staging\">\n");
+ TextIO.output (out, "</frameset>\n");
+ TextIO.closeOut out)
+
+ val fname = OS.Path.joinDirFile {dir = outDir,
+ file = "demos.html"}
+
+ val demosOut = TextIO.openOut fname
+ val () = (TextIO.output (demosOut, "<html><body>\n\n");
+ TextIO.output (demosOut, "<li> <a target=\"staging\" href=\"intro.html\">Intro</a></li>\n\n"))
+
+ val fname = OS.Path.joinDirFile {dir = dirname,
+ file = "demo.urs"}
+ val ursOut = TextIO.openOut fname
+ val () = (TextIO.output (ursOut, "val main : unit -> transaction page\n");
+ TextIO.closeOut ursOut)
+
+ val fname = OS.Path.joinDirFile {dir = dirname,
+ file = "demo.ur"}
+ val urOut = TextIO.openOut fname
+ val () = TextIO.output (urOut, "fun main () = return <xml><body>\n")
+
+ fun mergeWith f (o1, o2) =
+ case (o1, o2) of
+ (NONE, _) => o2
+ | (_, NONE) => o1
+ | (SOME v1, SOME v2) => SOME (f (v1, v2))
+
+ fun combiner (combined : Compiler.job, urp : Compiler.job) = {
+ prefix = prefix,
+ database = mergeWith (fn (v1, v2) =>
+ if v1 = v2 then
+ v1
+ else
+ raise Fail "Different demos want to use different database strings")
+ (#database combined, #database urp),
+ sources = foldl (fn (file, files) =>
+ if List.exists (fn x => x = file) files then
+ files
+ else
+ files @ [file])
+ (#sources combined) (#sources urp),
+ exe = case Settings.getExe () of
+ NONE => OS.Path.joinDirFile {dir = dirname,
+ file = "demo.exe"}
+ | SOME s => s,
+ sql = SOME (case Settings.getSql () of
+ NONE => OS.Path.joinDirFile {dir = dirname,
+ file = "demo.sql"}
+ | SOME s => s),
+ debug = Settings.getDebug (),
+ timeout = Int.max (#timeout combined, #timeout urp),
+ profile = false,
+ ffi = [],
+ link = [],
+ linker = NONE,
+ headers = [],
+ scripts = [],
+ clientToServer = [],
+ effectful = [],
+ benignEffectful = [],
+ clientOnly = [],
+ serverOnly = [],
+ jsModule = NONE,
+ jsFuncs = [],
+ rewrites = #rewrites combined @ #rewrites urp,
+ filterUrl = #filterUrl combined @ #filterUrl urp,
+ filterMime = #filterMime combined @ #filterMime urp,
+ filterRequest = #filterRequest combined @ #filterRequest urp,
+ filterResponse = #filterResponse combined @ #filterResponse urp,
+ filterEnv = #filterEnv combined @ #filterEnv urp,
+ filterMeta = #filterMeta combined @ #filterMeta urp,
+ protocol = mergeWith #2 (#protocol combined, #protocol urp),
+ dbms = mergeWith #2 (#dbms combined, #dbms urp),
+ sigFile = mergeWith #2 (#sigFile combined, #sigFile urp),
+ safeGets = #safeGets combined @ #safeGets urp,
+ onError = NONE,
+ minHeap = 0
+ }
+
+ val parse = Compiler.run (Compiler.transform Compiler.parseUrp "Demo parseUrp")
+
+ fun capitalize "" = ""
+ | capitalize s = str (Char.toUpper (String.sub (s, 0)))
+ ^ String.extract (s, 1, NONE)
+
+ fun startUrp urp =
+ let
+ val base = OS.Path.base urp
+ val name = capitalize base
+
+ val () = (TextIO.output (demosOut, "<li> <a target=\"staging\" href=\"");
+ TextIO.output (demosOut, base);
+ TextIO.output (demosOut, ".html\">");
+ TextIO.output (demosOut, name);
+ TextIO.output (demosOut, "</a></li>\n"))
+
+ val () = (TextIO.output (urOut, " <li> <a link={");
+ TextIO.output (urOut, name);
+ TextIO.output (urOut, ".main ()}>");
+ TextIO.output (urOut, name);
+ TextIO.output (urOut, "</a></li>\n"))
+
+ val urp_file = OS.Path.joinDirFile {dir = dirname,
+ file = urp}
+
+ val out = OS.Path.joinBaseExt {base = base,
+ ext = SOME "html"}
+ val out = OS.Path.joinDirFile {dir = outDir,
+ file = out}
+ val out = TextIO.openOut out
+
+ val () = (TextIO.output (out, "<frameset rows=\"");
+ TextIO.output (out, if guided then
+ "*,100"
+ else
+ "50%,*");
+ TextIO.output (out, "\">\n");
+ TextIO.output (out, "<frame src=\"");
+ TextIO.output (out, prefix);
+ TextIO.output (out, "/");
+ TextIO.output (out, name);
+ TextIO.output (out, "/main\" name=\"showcase\">\n");
+ TextIO.output (out, "<frame src=\"");
+ TextIO.output (out, base);
+ TextIO.output (out, ".desc.html\">\n");
+ TextIO.output (out, "</frameset>\n");
+ TextIO.closeOut out)
+ val () = TextIO.closeOut out
+
+ val out = OS.Path.joinBaseExt {base = base,
+ ext = SOME "desc"}
+ val out = OS.Path.joinBaseExt {base = out,
+ ext = SOME "html"}
+ val out = TextIO.openOut (OS.Path.joinDirFile {dir = outDir,
+ file = out})
+ in
+ case parse (OS.Path.base urp_file) of
+ NONE => raise Fail ("Can't parse " ^ urp_file)
+ | SOME urpData =>
+ (TextIO.output (out, "<html><head>\n<title>");
+ TextIO.output (out, name);
+ TextIO.output (out, "</title>\n</head><body>\n\n<h1>");
+ TextIO.output (out, name);
+ TextIO.output (out, "</h1>\n\n<center>[ <a target=\"showcase\" href=\"");
+ TextIO.output (out, prefix);
+ TextIO.output (out, "/");
+ TextIO.output (out, name);
+ TextIO.output (out, "/main\">Application</a>");
+ TextIO.output (out, " | <a target=\"showcase\" href=\"");
+ TextIO.output (out, urp);
+ TextIO.output (out, ".html\"><tt>");
+ TextIO.output (out, urp);
+ TextIO.output (out, "</tt></a>");
+ app (fn file =>
+ let
+ fun ifEx s =
+ let
+ val src = OS.Path.joinBaseExt {base = file,
+ ext = SOME s}
+ val src' = OS.Path.file src
+ in
+ if String.isPrefix (OS.Path.mkAbsolute {path = dirname,
+ relativeTo = OS.FileSys.getDir ()}) src
+ andalso OS.FileSys.access (src, []) then
+ (TextIO.output (out, " | <a target=\"showcase\" href=\"");
+ TextIO.output (out, src');
+ TextIO.output (out, ".html\"><tt>");
+ TextIO.output (out, src');
+ TextIO.output (out, "</tt></a>"))
+ else
+ ()
+ end
+ in
+ ifEx "urs";
+ ifEx "ur"
+ end) (#sources urpData);
+ TextIO.output (out, " ]</center>\n\n");
+
+ (urpData, out))
+ end
+
+ fun endUrp out =
+ (TextIO.output (out, "\n</body></html>\n");
+ TextIO.closeOut out)
+
+ fun readUrp (combined, out) =
+ let
+ fun finished () = endUrp out
+
+ fun readUrp' () =
+ case TextIO.inputLine inf of
+ NONE => (finished ();
+ combined)
+ | SOME line =>
+ if String.isSuffix ".urp\n" line then
+ let
+ val urp = String.substring (line, 0, size line - 1)
+ val (urpData, out) = startUrp urp
+ in
+ finished ();
+
+ readUrp (combiner (combined, urpData),
+ out)
+ end
+ else
+ (TextIO.output (out, line);
+ readUrp' ())
+ in
+ readUrp' ()
+ end
+
+ val indexFile = OS.Path.joinDirFile {dir = outDir,
+ file = "intro.html"}
+
+ val out = TextIO.openOut indexFile
+ val () = TextIO.output (out, "<html><head>\n<title>Ur/Web Demo</title>\n</head><body>\n\n")
+
+ fun readIndex () =
+ let
+ fun finished () = (TextIO.output (out, "\n</body></html>\n");
+ TextIO.closeOut out)
+ in
+ case TextIO.inputLine inf of
+ NONE => (finished ();
+ NONE)
+ | SOME line =>
+ if String.isSuffix ".urp\n" line then
+ let
+ val urp = String.substring (line, 0, size line - 1)
+ val (urpData, out) = startUrp urp
+ in
+ finished ();
+
+ SOME (readUrp (urpData,
+ out))
+ end
+ else
+ (TextIO.output (out, line);
+ readIndex ())
+ end
+
+ fun prettyPrint () =
+ let
+ val dir = Posix.FileSys.opendir dirname
+
+ fun loop () =
+ case Posix.FileSys.readdir dir of
+ NONE => Posix.FileSys.closedir dir
+ | SOME file =>
+ let
+ fun doit f =
+ f (OS.Path.joinDirFile {dir = dirname,
+ file = file},
+ OS.Path.mkAbsolute
+ {relativeTo = OS.FileSys.getDir (),
+ path = OS.Path.joinDirFile {dir = outDir,
+ file = OS.Path.joinBaseExt {base = file,
+ ext = SOME "html"}}})
+
+ fun highlight () =
+ doit (fn (src, html) =>
+ let
+ val dirty =
+ let
+ val srcSt = Posix.FileSys.stat src
+ val htmlSt = Posix.FileSys.stat html
+ in
+ Time.> (Posix.FileSys.ST.mtime srcSt,
+ Posix.FileSys.ST.mtime htmlSt)
+ end handle OS.SysErr _ => true
+
+ val cmd = "emacs -no-init-file --eval \"(progn "
+ ^ "(global-font-lock-mode t) "
+ ^ "(add-to-list 'load-path \\\""
+ ^ !Settings.configSitelisp
+ ^ "/\\\") "
+ ^ "(load \\\"urweb-mode-startup\\\") "
+ ^ "(load \\\"htmlize\\\") "
+ ^ "(urweb-mode) "
+ ^ "(find-file \\\""
+ ^ src
+ ^ "\\\") "
+ ^ "(switch-to-buffer (htmlize-buffer)) "
+ ^ "(write-file \\\""
+ ^ html
+ ^ "\\\") "
+ ^ "(kill-emacs))\""
+ in
+ if dirty then
+ (print (">>> " ^ cmd ^ "\n");
+ ignore (OS.Process.system cmd))
+ else
+ ()
+ end)
+
+ val highlight = fn () => if !noEmacs then () else highlight ()
+ in
+ if OS.Path.base file = "demo" then
+ ()
+ else case OS.Path.ext file of
+ SOME "urp" =>
+ doit (fn (src, html) =>
+ let
+ val inf = FileIO.txtOpenIn src
+ val out = TextIO.openOut html
+
+ fun loop () =
+ case TextIO.inputLine inf of
+ NONE => ()
+ | SOME line => (TextIO.output (out, line);
+ loop ())
+ in
+ TextIO.output (out, "<html><body>\n\n<pre>");
+ loop ();
+ TextIO.output (out, "</pre>\n\n</body></html>");
+
+ TextIO.closeIn inf;
+ TextIO.closeOut out
+ end)
+ | SOME "urs" => highlight ()
+ | SOME "ur" => highlight ()
+ | _ => ();
+ loop ()
+ end
+ in
+ loop ()
+ end
+ in
+ case readIndex () of
+ NONE => raise Fail "No demo applications!"
+ | SOME combined =>
+ let
+ val () = (TextIO.output (urOut, "</body></xml>\n");
+ TextIO.closeOut urOut)
+
+ val fname = OS.Path.joinDirFile {dir = dirname,
+ file = "demo.urp"}
+ val outf = TextIO.openOut fname
+
+ fun filters kind =
+ app (fn rule : Settings.rule =>
+ (TextIO.output (outf, case #action rule of
+ Settings.Allow => "allow"
+ | Settings.Deny => "deny");
+ TextIO.output (outf, " ");
+ TextIO.output (outf, kind);
+ TextIO.output (outf, " ");
+ TextIO.output (outf, #pattern rule);
+ case #kind rule of
+ Settings.Exact => ()
+ | Settings.Prefix => TextIO.output (outf, "*");
+ TextIO.output (outf, "\n")))
+ in
+ Option.app (fn db => (TextIO.output (outf, "database ");
+ TextIO.output (outf, db);
+ TextIO.output (outf, "\n")))
+ (#database combined);
+ TextIO.output (outf, "sql demo.sql\n");
+ TextIO.output (outf, "prefix ");
+ TextIO.output (outf, prefix);
+ TextIO.output (outf, "\n");
+ app (fn rule =>
+ (TextIO.output (outf, "rewrite ");
+ TextIO.output (outf, case #pkind rule of
+ Settings.Any => "all"
+ | Settings.Url => "url"
+ | Settings.Table => "table"
+ | Settings.Sequence => "sequence"
+ | Settings.View => "view"
+ | Settings.Relation => "relation"
+ | Settings.Cookie => "cookie"
+ | Settings.Style => "style");
+ TextIO.output (outf, " ");
+ TextIO.output (outf, #from rule);
+ case #kind rule of
+ Settings.Exact => ()
+ | Settings.Prefix => TextIO.output (outf, "*");
+ TextIO.output (outf, " ");
+ TextIO.output (outf, #to rule);
+ if #hyphenate rule then
+ TextIO.output (outf, " [-]")
+ else
+ ();
+ TextIO.output (outf, "\n"))) (#rewrites combined);
+ filters "url" (#filterUrl combined);
+ filters "mime" (#filterMime combined);
+ app (fn path =>
+ (TextIO.output (outf, "safeGet ");
+ TextIO.output (outf, path);
+ TextIO.output (outf, "\n"))) (#safeGets combined);
+ TextIO.output (outf, "\n");
+
+ app (fn s =>
+ let
+ val s = OS.Path.mkAbsolute {relativeTo = OS.FileSys.getDir (),
+ path = s}
+ in
+ TextIO.output (outf, s);
+ TextIO.output (outf, "\n")
+ end)
+ (#sources combined);
+ TextIO.output (outf, "\n");
+ TextIO.output (outf, "demo\n");
+
+ TextIO.closeOut outf;
+
+ let
+ val b = Compiler.compile (OS.Path.base fname)
+ in
+ TextIO.output (demosOut, "\n</body></html>\n");
+ TextIO.closeOut demosOut;
+ if b then
+ prettyPrint ()
+ else
+ ();
+ b
+ end
+ end
+ end
+
+fun make args = if make' args then
+ ()
+ else
+ OS.Process.exit OS.Process.failure
+
+end
diff --git a/src/disjoint.sig b/src/disjoint.sig
new file mode 100644
index 0000000..7ca05fd
--- /dev/null
+++ b/src/disjoint.sig
@@ -0,0 +1,46 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature DISJOINT = sig
+
+ type env
+
+ val empty : env
+ val enter : env -> env
+
+ type goal = ErrorMsg.span * ElabEnv.env * env * Elab.con * Elab.con
+
+ val assert : ElabEnv.env -> env -> Elab.con * Elab.con -> env
+
+ val prove : ElabEnv.env -> env -> Elab.con * Elab.con * ErrorMsg.span -> goal list
+
+ val p_env : env -> unit
+
+ val proved : int ref
+ val reset : unit -> unit
+
+end
diff --git a/src/disjoint.sml b/src/disjoint.sml
new file mode 100644
index 0000000..8fa8834
--- /dev/null
+++ b/src/disjoint.sml
@@ -0,0 +1,285 @@
+(* Copyright (c) 2008, 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 Disjoint :> DISJOINT = struct
+
+open Elab
+open ElabOps
+
+datatype piece_fst =
+ NameC of string
+ | NameR of int
+ | NameN of int
+ | NameM of int * string list * string
+ | RowR of int
+ | RowN of int
+ | RowM of int * string list * string
+
+type piece = piece_fst * int list
+
+fun p2s p =
+ case p of
+ NameC s => "NameC(" ^ s ^ ")"
+ | NameR n => "NameR(" ^ Int.toString n ^ ")"
+ | NameN n => "NameN(" ^ Int.toString n ^ ")"
+ | NameM (n, _, s) => "NameR(" ^ Int.toString n ^ ", " ^ s ^ ")"
+ | RowR n => "RowR(" ^ Int.toString n ^ ")"
+ | RowN n => "RowN(" ^ Int.toString n ^ ")"
+ | RowM (n, _, s) => "RowR(" ^ Int.toString n ^ ", " ^ s ^ ")"
+
+fun pp p = print (p2s p ^ "\n")
+
+fun rp2s (p, ns) = String.concatWith " " (p2s p :: map Int.toString ns)
+
+structure PK = struct
+
+type ord_key = piece
+
+open Order
+
+fun compare' (p1, p2) =
+ case (p1, p2) of
+ (NameC s1, NameC s2) => String.compare (s1, s2)
+ | (NameR n1, NameR n2) => Int.compare (n1, n2)
+ | (NameN n1, NameN n2) => Int.compare (n1, n2)
+ | (NameM (n1, ss1, s1), NameM (n2, ss2, s2)) =>
+ join (Int.compare (n1, n2),
+ fn () => join (String.compare (s1, s2), fn () =>
+ joinL String.compare (ss1, ss2)))
+ | (RowR n1, RowR n2) => Int.compare (n1, n2)
+ | (RowN n1, RowN n2) => Int.compare (n1, n2)
+ | (RowM (n1, ss1, s1), RowM (n2, ss2, s2)) =>
+ join (Int.compare (n1, n2),
+ fn () => join (String.compare (s1, s2), fn () =>
+ joinL String.compare (ss1, ss2)))
+
+ | (NameC _, _) => LESS
+ | (_, NameC _) => GREATER
+
+ | (NameR _, _) => LESS
+ | (_, NameR _) => GREATER
+
+ | (NameN _, _) => LESS
+ | (_, NameN _) => GREATER
+
+ | (NameM _, _) => LESS
+ | (_, NameM _) => GREATER
+
+ | (RowR _, _) => LESS
+ | (_, RowR _) => GREATER
+
+ | (RowN _, _) => LESS
+ | (_, RowN _) => GREATER
+
+fun compare ((p1, ns1), (p2, ns2)) =
+ join (compare' (p1, p2),
+ fn () => joinL Int.compare (ns1, ns2))
+
+end
+
+structure PS = BinarySetFn(PK)
+structure PM = BinaryMapFn(PK)
+
+type env = PS.set PM.map
+
+fun p_env x =
+ (print "\nDENV:\n";
+ PM.appi (fn (p1, ps) =>
+ PS.app (fn p2 =>
+ print (rp2s p1 ^ " ~ " ^ rp2s p2 ^ "\n")) ps) x)
+
+structure E = ElabEnv
+
+type goal = ErrorMsg.span * E.env * env * Elab.con * Elab.con
+
+val empty = PM.empty
+
+fun nameToRow (c, loc) =
+ (CRecord ((KUnit, loc), [((c, loc), (CUnit, loc))]), loc)
+
+fun pieceToRow' (p, loc) =
+ case p of
+ NameC s => nameToRow (CName s, loc)
+ | NameR n => nameToRow (CRel n, loc)
+ | NameN n => nameToRow (CNamed n, loc)
+ | NameM (n, xs, x) => nameToRow (CModProj (n, xs, x), loc)
+ | RowR n => (CRel n, loc)
+ | RowN n => (CNamed n, loc)
+ | RowM (n, xs, x) => (CModProj (n, xs, x), loc)
+
+fun pieceToRow ((p, ns), loc) =
+ foldl (fn (n, c) => (CProj (c, n), loc)) (pieceToRow' (p, loc)) ns
+
+datatype piece' =
+ Piece of piece
+ | Unknown of con
+
+fun pieceEnter' p =
+ case p of
+ NameR n => NameR (n + 1)
+ | RowR n => RowR (n + 1)
+ | _ => p
+
+fun pieceEnter (p, n) = (pieceEnter' p, n)
+
+fun enter denv =
+ PM.foldli (fn (p, pset, denv') =>
+ PM.insert (denv', pieceEnter p, PS.map pieceEnter pset))
+ PM.empty denv
+
+val lowercase = CharVector.map Char.toLower
+
+fun prove1 denv (p1, p2) =
+ case (p1, p2) of
+ ((NameC s1, _), (NameC s2, _)) => lowercase s1 <> lowercase s2
+ | _ =>
+ case PM.find (denv, p1) of
+ NONE => false
+ | SOME pset => PS.member (pset, p2)
+
+val proved = ref 0
+fun reset () = (ElabOps.reset ();
+ proved := 0)
+
+fun decomposeRow env c =
+ let
+ val loc = #2 c
+
+ fun decomposeProj c =
+ let
+ val c = hnormCon env c
+ in
+ case #1 c of
+ CProj (c, n) =>
+ let
+ val (c', ns) = decomposeProj c
+ in
+ (c', ns @ [n])
+ end
+ | _ => (c, [])
+ end
+
+ fun decomposeName (c, acc) =
+ let
+ val (cAll as (c, _), ns) = decomposeProj c
+ in
+ case c of
+ CName s => Piece (NameC s, ns) :: acc
+ | CRel n => Piece (NameR n, ns) :: acc
+ | CNamed n => Piece (NameN n, ns) :: acc
+ | CModProj (m1, ms, x) => Piece (NameM (m1, ms, x), ns) :: acc
+ | _ => Unknown cAll :: acc
+ end
+
+ fun decomposeRow' (c, acc) =
+ let
+ fun default () =
+ let
+ val (cAll as (c, _), ns) = decomposeProj c
+ in
+ case c of
+ CRecord (_, xcs) => foldl (fn ((x, _), acc) => decomposeName (x, acc)) acc xcs
+ | CConcat (c1, c2) => decomposeRow' (c1, decomposeRow' (c2, acc))
+ | CRel n => Piece (RowR n, ns) :: acc
+ | CNamed n => Piece (RowN n, ns) :: acc
+ | CModProj (m1, ms, x) => Piece (RowM (m1, ms, x), ns) :: acc
+ | _ => Unknown cAll :: acc
+ end
+ in
+ case #1 (hnormCon env c) of
+ CApp (
+ (CApp ((CMap _, _), _), _),
+ r) => decomposeRow' (r, acc)
+ | _ => default ()
+ end
+ in
+ decomposeRow' (c, [])
+ end
+
+and assert env denv (c1, c2) =
+ let
+ val ps1 = decomposeRow env c1
+ val ps2 = decomposeRow env c2
+
+ val unUnknown = List.mapPartial (fn Unknown _ => NONE | Piece p => SOME p)
+ val ps1 = unUnknown ps1
+ val ps2 = unUnknown ps2
+
+ (*val () = print "APieces1:\n"
+ val () = app pp ps1
+ val () = print "APieces2:\n"
+ val () = app pp ps2*)
+
+ fun assertPiece ps (p, denv) =
+ let
+ val pset = Option.getOpt (PM.find (denv, p), PS.empty)
+ val ps = case p of
+ (NameC _, _) => List.filter (fn (NameC _, _) => false | _ => true) ps
+ | _ => ps
+ val pset = PS.addList (pset, ps)
+ in
+ PM.insert (denv, p, pset)
+ end
+
+ val denv = foldl (assertPiece ps2) denv ps1
+ in
+ foldl (assertPiece ps1) denv ps2
+ end
+
+and prove env denv (c1, c2, loc) =
+ let
+ val () = proved := !proved + 1
+ val ps1 = decomposeRow env c1
+ val ps2 = decomposeRow env c2
+
+ val hasUnknown = List.exists (fn Unknown _ => true | _ => false)
+ val unUnknown = List.mapPartial (fn Unknown _ => NONE | Piece p => SOME p)
+ in
+ if (hasUnknown ps1 andalso not (List.null ps2))
+ orelse (hasUnknown ps2 andalso not (List.null ps1)) then
+ [(loc, env, denv, c1, c2)]
+ else
+ let
+ val ps1 = unUnknown ps1
+ val ps2 = unUnknown ps2
+ in
+ (*print "Pieces1:\n";
+ app pp ps1;
+ print "Pieces2:\n";
+ app pp ps2;*)
+
+ foldl (fn (p1, rem) =>
+ foldl (fn (p2, rem) =>
+ if prove1 denv (p1, p2) then
+ rem
+ else
+ (loc, env, denv, pieceToRow (p1, loc), pieceToRow (p2, loc)) :: rem) rem ps2)
+ [] ps1
+ end
+ end
+
+end
diff --git a/src/effectize.sig b/src/effectize.sig
new file mode 100644
index 0000000..1b638a3
--- /dev/null
+++ b/src/effectize.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+signature EFFECTIZE = sig
+
+ val effectize : Core.file -> Core.file
+
+end
diff --git a/src/effectize.sml b/src/effectize.sml
new file mode 100644
index 0000000..2c9b237
--- /dev/null
+++ b/src/effectize.sml
@@ -0,0 +1,208 @@
+(* Copyright (c) 2009-2010, 2013, 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 Effective :> EFFECTIZE = struct
+
+open Core
+
+structure U = CoreUtil
+
+structure IM = IntBinaryMap
+structure SS = BinarySetFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+fun effectful x = Settings.isEffectful x andalso not (Settings.isClientOnly x)
+
+fun effectize file =
+ let
+ fun expOnload evs e =
+ case e of
+ EFfi f => effectful f
+ | EFfiApp (m, x, _) => effectful (m, x)
+ | ENamed n => IM.inDomain (evs, n)
+ | EServerCall (n, _, _, _) => IM.inDomain (evs, n)
+ | _ => false
+
+ fun couldWriteOnload evs = U.Exp.exists {kind = fn _ => false,
+ con = fn _ => false,
+ exp = expOnload evs}
+
+ fun exp evs e =
+ case e of
+ EFfi f => effectful f
+ | EFfiApp (m, x, _) => effectful (m, x)
+ | ENamed n => IM.inDomain (evs, n)
+ | ERecord xets => List.exists (fn ((CName "Onload", _), e, _) => couldWriteOnload evs e
+ | _ => false) xets
+ | _ => false
+
+ fun couldWrite evs = U.Exp.exists {kind = fn _ => false,
+ con = fn _ => false,
+ exp = exp evs}
+
+ fun exp writers readers pushers e =
+ case e of
+ ENamed n => IM.inDomain (pushers, n)
+ | EServerCall (n, _, _, _) => IM.inDomain (writers, n) andalso IM.inDomain (readers, n)
+ | _ => false
+
+ fun couldWriteWithRpc writers readers pushers = U.Exp.exists {kind = fn _ => false,
+ con = fn _ => false,
+ exp = exp writers readers pushers}
+
+ 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
+
+ fun couldReadCookie evs = U.Exp.exists {kind = fn _ => false,
+ con = fn _ => false,
+ exp = exp evs}
+
+ val dejs = U.Exp.map {kind = fn x => x,
+ con = fn c => c,
+ exp = fn ERecord xets => ERecord (List.filter (fn ((CName x, _), _ , _) => x = "Onload" orelse not (String.isPrefix "On" x)
+ | _ => true) xets)
+ | e => e}
+
+ fun doDecl (d, evs as (writers, readers, pushers)) =
+ case #1 d of
+ DVal (x, n, t, e, s) =>
+ let
+ val e' = dejs e
+ in
+ (d, (if couldWrite writers e' then
+ IM.insert (writers, n, (#2 d, s))
+ else
+ writers,
+ if couldReadCookie readers e' then
+ IM.insert (readers, n, (#2 d, s))
+ else
+ readers,
+ if couldWriteWithRpc writers readers pushers e then
+ IM.insert (pushers, n, (#2 d, s))
+ else
+ pushers))
+ end
+ | DValRec vis =>
+ let
+ fun oneRound evs =
+ foldl (fn ((_, n, _, e, s), (changed, (writers, readers, pushers))) =>
+ let
+ val e' = dejs e
+
+ val (changed, writers) =
+ if couldWrite writers e' andalso not (IM.inDomain (writers, n)) then
+ (true, IM.insert (writers, n, (#2 d, s)))
+ else
+ (changed, writers)
+
+ val (changed, readers) =
+ if couldReadCookie readers e' andalso not (IM.inDomain (readers, n)) then
+ (true, IM.insert (readers, n, (#2 d, s)))
+ else
+ (changed, readers)
+
+ val (changed, pushers) =
+ if couldWriteWithRpc writers readers pushers e
+ andalso not (IM.inDomain (pushers, n)) then
+ (true, IM.insert (pushers, n, (#2 d, s)))
+ else
+ (changed, pushers)
+ in
+ (changed, (writers, readers, pushers))
+ end) (false, evs) vis
+
+ fun loop evs =
+ let
+ val (b, evs) = oneRound evs
+ in
+ if b then
+ loop evs
+ else
+ evs
+ end
+ in
+ (d, loop (writers, readers, pushers))
+ end
+ | DExport (Link _, n, t) =>
+ (case IM.find (writers, n) of
+ NONE => ()
+ | SOME (loc, s) =>
+ if Settings.isSafeGet s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("A handler (URI prefix \"" ^ s
+ ^ "\") accessible via GET could cause side effects; try accessing it only via forms, removing it from the signature of the main program module, or whitelisting it with the 'safeGet' .urp directive");
+ ((DExport (Link (if IM.inDomain (writers, n) then
+ if IM.inDomain (readers, n) then
+ ReadCookieWrite
+ else
+ ReadWrite
+ else
+ ReadOnly), n, IM.inDomain (pushers, n)), #2 d), evs))
+ | DExport (Action _, n, _) =>
+ ((DExport (Action (if IM.inDomain (writers, n) then
+ if IM.inDomain (readers, n) then
+ ReadCookieWrite
+ else
+ ReadWrite
+ else
+ ReadOnly), n, IM.inDomain (pushers, n)), #2 d),
+ evs)
+ | DExport (Rpc _, n, _) =>
+ ((DExport (Rpc (if IM.inDomain (writers, n) then
+ if IM.inDomain (readers, n) then
+ ReadCookieWrite
+ else
+ ReadWrite
+ else
+ ReadOnly), n, IM.inDomain (pushers, n)), #2 d),
+ evs)
+ | DExport (Extern _, n, _) =>
+ ((DExport (Extern (if IM.inDomain (writers, n) then
+ if IM.inDomain (readers, n) then
+ ReadCookieWrite
+ else
+ ReadWrite
+ else
+ ReadOnly), n, IM.inDomain (pushers, n)), #2 d),
+ evs)
+ | _ => (d, evs)
+
+ val (file, _) = ListUtil.foldlMap doDecl (IM.empty, IM.empty, IM.empty) file
+ in
+ file
+ end
+
+end
diff --git a/src/elab.sml b/src/elab.sml
new file mode 100644
index 0000000..90c14e4
--- /dev/null
+++ b/src/elab.sml
@@ -0,0 +1,204 @@
+(* Copyright (c) 2008-2011, 2014, 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 Elab = struct
+
+type 'a located = 'a ErrorMsg.located
+
+datatype kind' =
+ KType
+ | KArrow of kind * kind
+ | KName
+ | KRecord of kind
+ | KUnit
+ | KTuple of kind list
+
+ | KError
+ | KUnif of ErrorMsg.span * string * kunif ref
+ | KTupleUnif of ErrorMsg.span * (int * kind) list * kunif ref
+
+ | KRel of int
+ | KFun of string * kind
+
+and kunif =
+ KUnknown of kind -> bool (* Is the kind a valid unification? *)
+ | KKnown of kind
+
+withtype kind = kind' located
+
+datatype explicitness =
+ Explicit
+ | Implicit
+
+datatype con' =
+ TFun of con * con
+ | TCFun of explicitness * string * kind * con
+ | TRecord of con
+ | TDisjoint of con * con * con
+
+ | CRel of int
+ | CNamed of int
+ | CModProj of int * string list * string
+ | CApp of con * con
+ | CAbs of string * kind * con
+
+ | CKAbs of string * con
+ | CKApp of con * kind
+ | TKFun of string * con
+
+ | CName of string
+
+ | CRecord of kind * (con * con) list
+ | CConcat of con * con
+ | CMap of kind * kind
+
+ | CUnit
+
+ | CTuple of con list
+ | CProj of con * int
+
+ | CError
+ | CUnif of int * ErrorMsg.span * kind * string * cunif ref
+
+and cunif =
+ Unknown of con -> bool (* Is the constructor a valid unification? *)
+ | Known of con
+
+withtype con = con' located
+
+datatype datatype_kind = datatype DatatypeKind.datatype_kind
+
+datatype patCon =
+ PConVar of int
+ | PConProj of int * string list * string
+
+datatype pat' =
+ PVar of string * con
+ | PPrim of Prim.t
+ | PCon of datatype_kind * patCon * con list * pat option
+ | PRecord of (string * pat * con) list
+
+withtype pat = pat' located
+
+datatype exp' =
+ EPrim of Prim.t
+ | ERel of int
+ | ENamed of int
+ | EModProj of int * string list * string
+ | EApp of exp * exp
+ | EAbs of string * con * con * exp
+ | ECApp of exp * con
+ | ECAbs of explicitness * string * kind * exp
+
+ | EKAbs of string * exp
+ | EKApp of exp * kind
+
+ | ERecord of (con * exp * con) list
+ | EField of exp * con * { field : con, rest : con }
+ | EConcat of exp * con * exp * con
+ | ECut of exp * con * { field : con, rest : con }
+ | ECutMulti of exp * con * { rest : con }
+
+ | ECase of exp * (pat * exp) list * { disc : con, result : con }
+
+ | EError
+ | EUnif of exp option ref
+
+ | ELet of edecl list * exp * con
+
+and edecl' =
+ EDVal of pat * con * exp
+ | EDValRec of (string * con * exp) list
+
+withtype exp = exp' located
+ and edecl = edecl' located
+
+(* We have to be careful about crawling automatically generated signatures recursively,
+ * importing all type-class instances that we find.
+ * The reason is that selfification will add signatures of anonymous structures,
+ * and it's counterintuitive for instances to escape anonymous structures! *)
+datatype import_mode = Import | Skip
+
+datatype sgn_item' =
+ SgiConAbs of string * int * kind
+ | SgiCon of string * int * kind * con
+ | SgiDatatype of (string * int * string list * (string * int * con option) list) list
+ | SgiDatatypeImp of string * int * int * string list * string * string list * (string * int * con option) list
+ | SgiVal of string * int * con
+ | SgiStr of import_mode * string * int * sgn
+ | SgiSgn of string * int * sgn
+ | SgiConstraint of con * con
+ | SgiClassAbs of string * int * kind
+ | SgiClass of string * int * kind * con
+
+and sgn' =
+ SgnConst of sgn_item list
+ | SgnVar of int
+ | SgnFun of string * int * sgn * sgn
+ | SgnWhere of sgn * string list * string * con
+ | SgnProj of int * string list * string
+ | SgnError
+
+withtype sgn_item = sgn_item' located
+and sgn = sgn' located
+
+datatype decl' =
+ DCon of string * int * kind * con
+ | DDatatype of (string * int * string list * (string * int * con option) list) list
+ | DDatatypeImp of string * int * int * string list * string * string list * (string * int * con option) list
+ | DVal of string * int * con * exp
+ | DValRec of (string * int * con * exp) list
+ | DSgn of string * int * sgn
+ | DStr of string * int * sgn * str
+ | DFfiStr of string * int * sgn
+ | DConstraint of con * con
+ | DExport of int * sgn * str
+ | DTable of int * string * int * con * exp * con * exp * con
+ | DSequence of int * string * int
+ | DView of int * string * int * exp * con
+ | DDatabase of string
+ | DCookie of int * string * int * con
+ | DStyle of int * string * int
+ | DTask of exp * exp
+ | DPolicy of exp
+ | DOnError of int * string list * string
+ | DFfi of string * int * Source.ffi_mode list * con
+
+ and str' =
+ StrConst of decl list
+ | StrVar of int
+ | StrProj of str * string
+ | StrFun of string * int * sgn * sgn * str
+ | StrApp of str * str
+ | StrError
+
+withtype decl = decl' located
+ and str = str' located
+
+type file = decl list
+
+end
diff --git a/src/elab_env.sig b/src/elab_env.sig
new file mode 100644
index 0000000..47b31c0
--- /dev/null
+++ b/src/elab_env.sig
@@ -0,0 +1,127 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature ELAB_ENV = sig
+
+ val liftConInCon : int -> Elab.con -> Elab.con
+ val mliftConInCon : int -> Elab.con -> Elab.con
+
+ val liftConInExp : int -> Elab.exp -> Elab.exp
+ val liftExpInExp : int -> Elab.exp -> Elab.exp
+
+ val subExpInExp : (int * Elab.exp) -> Elab.exp -> Elab.exp
+
+ type env
+
+ val dump : env -> unit
+
+ val empty : env
+
+ exception UnboundRel of int
+ exception UnboundNamed of int
+
+ datatype 'a var =
+ NotBound
+ | Rel of int * 'a
+ | Named of int * 'a
+
+ val pushKRel : env -> string -> env
+ val lookupKRel : env -> int -> string
+ val lookupK : env -> string -> int option
+
+ val pushCRel : env -> string -> Elab.kind -> env
+ val lookupCRel : env -> int -> string * Elab.kind
+
+ val pushCNamed : env -> string -> Elab.kind -> Elab.con option -> env * int
+ val pushCNamedAs : env -> string -> int -> Elab.kind -> Elab.con option -> env
+ val lookupCNamed : env -> int -> string * Elab.kind * Elab.con option
+
+ val lookupC : env -> string -> Elab.kind var
+
+ val pushDatatype : env -> int -> string list -> (string * int * Elab.con option) list -> env
+ type datatyp
+ val lookupDatatype : env -> int -> datatyp
+ val lookupDatatypeConstructor : datatyp -> int -> string * Elab.con option
+ val datatypeArgs : datatyp -> string list
+ val constructors : datatyp -> (string * int * Elab.con option) list
+
+ val lookupConstructor : env -> string -> (Elab.datatype_kind * int * string list * Elab.con option * int) option
+
+ val pushClass : env -> int -> env
+ val isClass : env -> Elab.con -> bool
+ val resolveClass : (Elab.con -> Elab.con) -> (Elab.con * Elab.con -> bool)
+ -> env -> Elab.con -> Elab.exp option
+ val resolveFailureCause : unit -> Elab.con option
+ val listClasses : env -> (Elab.con * (Elab.con * Elab.exp) list) list
+
+ val pushERel : env -> string -> Elab.con -> env
+ val lookupERel : env -> int -> string * Elab.con
+
+ val pushENamed : env -> string -> Elab.con -> env * int
+ val pushENamedAs : env -> string -> int -> Elab.con -> env
+ val lookupENamed : env -> int -> string * Elab.con
+ val checkENamed : env -> int -> bool
+
+ val lookupE : env -> string -> Elab.con var
+
+ val pushSgnNamed : env -> string -> Elab.sgn -> env * int
+ val pushSgnNamedAs : env -> string -> int -> Elab.sgn -> env
+ val lookupSgnNamed : env -> int -> string * Elab.sgn
+
+ val lookupSgn : env -> string -> (int * Elab.sgn) option
+
+ val pushStrNamed : env -> string -> Elab.sgn -> env * int
+ val pushStrNamedAs : env -> string -> int -> Elab.sgn -> env
+ val pushStrNamedAs' : bool (* also enrich typeclass instances? *) -> env -> string -> int -> Elab.sgn -> env
+ val lookupStrNamed : env -> int -> string * Elab.sgn
+
+ val lookupStr : env -> string -> (int * Elab.sgn) option
+
+ val edeclBinds : env -> Elab.edecl -> env
+ val declBinds : env -> Elab.decl -> env
+ val sgiBinds : env -> Elab.sgn_item -> env
+
+ val hnormSgn : env -> Elab.sgn -> Elab.sgn
+
+ val projectCon : env -> { sgn : Elab.sgn, str : Elab.str, field : string } -> (Elab.kind * Elab.con option) option
+ val projectDatatype : env -> { sgn : Elab.sgn, str : Elab.str, field : string }
+ -> (string list * (string * int * Elab.con option) list) option
+ val projectConstructor : env -> { sgn : Elab.sgn, str : Elab.str, field : string }
+ -> (Elab.datatype_kind * int * string list * Elab.con option * Elab.con) option
+ val projectVal : env -> { sgn : Elab.sgn, str : Elab.str, field : string } -> Elab.con option
+ val projectSgn : env -> { sgn : Elab.sgn, str : Elab.str, field : string } -> Elab.sgn option
+ val projectStr : env -> { sgn : Elab.sgn, str : Elab.str, field : string } -> Elab.sgn option
+ val projectConstraints : env -> { sgn : Elab.sgn, str : Elab.str } -> (Elab.con * Elab.con) list option
+
+ val newNamed : unit -> int
+
+ val chaseMpath : env -> (int * string list) -> Elab.str * Elab.sgn
+
+ val patBinds : env -> Elab.pat -> env
+ val patBindsN : Elab.pat -> int
+
+end
diff --git a/src/elab_env.sml b/src/elab_env.sml
new file mode 100644
index 0000000..8402bcb
--- /dev/null
+++ b/src/elab_env.sml
@@ -0,0 +1,1709 @@
+(* Copyright (c) 2008-2009, 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 ElabEnv :> ELAB_ENV = struct
+
+open Elab
+
+structure U = ElabUtil
+
+structure IM = IntBinaryMap
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+exception UnboundRel of int
+exception UnboundNamed of int
+
+
+(* AST utility functions *)
+
+val liftKindInKind =
+ U.Kind.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ bind = fn (bound, _) => bound + 1}
+
+val liftKindInCon =
+ U.Con.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ con = fn _ => fn c => c,
+ bind = fn (bound, U.Con.RelK _) => bound + 1
+ | (bound, _) => bound}
+
+val liftConInCon =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + 1)
+ | CUnif (nl, loc, k, s, r) => CUnif (nl+1, loc, k, s, r)
+ | _ => c,
+ bind = fn (bound, U.Con.RelC _) => bound + 1
+ | (bound, _) => bound}
+
+val lift = liftConInCon 0
+
+fun mliftConInCon by c =
+ if by = 0 then
+ c
+ else
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + by)
+ | CUnif (nl, loc, k, s, r) => CUnif (nl+by, loc, k, s, r)
+ | _ => c,
+ bind = fn (bound, U.Con.RelC _) => bound + 1
+ | (bound, _) => bound} 0 c
+
+val () = U.mliftConInCon := mliftConInCon
+
+val liftKindInExp =
+ U.Exp.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ con = fn _ => fn c => c,
+ exp = fn _ => fn e => e,
+ bind = fn (bound, U.Exp.RelK _) => bound + 1
+ | (bound, _) => bound}
+
+val liftConInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + 1)
+ | CUnif (nl, loc, k, s, r) => CUnif (nl+1, loc, k, s, r)
+ | _ => c,
+ exp = fn _ => fn e => e,
+ bind = fn (bound, U.Exp.RelC _) => bound + 1
+ | (bound, _) => bound}
+
+val liftExpInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn bound => fn e =>
+ case e of
+ ERel xn =>
+ if xn < bound then
+ e
+ else
+ ERel (xn + 1)
+ | _ => e,
+ bind = fn (bound, U.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+
+
+val liftExp = liftExpInExp 0
+
+val subExpInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn (xn, rep) => fn e =>
+ case e of
+ ERel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 rep
+ | GREATER=> ERel (xn' - 1)
+ | LESS => e)
+ | _ => e,
+ bind = fn ((xn, rep), U.Exp.RelE _) => (xn+1, liftExpInExp 0 rep)
+ | ((xn, rep), U.Exp.RelC _) => (xn, liftConInExp 0 rep)
+ | (ctx, _) => ctx}
+
+(* Back to environments *)
+
+datatype 'a var' =
+ Rel' of int * 'a
+ | Named' of int * 'a
+
+datatype 'a var =
+ NotBound
+ | Rel of int * 'a
+ | Named of int * 'a
+
+type datatyp = string list * (string * con option) IM.map
+
+datatype class_name =
+ ClNamed of int
+ | ClProj of int * string list * string
+
+fun class_name_out cn =
+ case cn of
+ ClNamed n => (CNamed n, ErrorMsg.dummySpan)
+ | ClProj x => (CModProj x, ErrorMsg.dummySpan)
+
+fun cn2s cn =
+ case cn of
+ ClNamed n => "Named(" ^ Int.toString n ^ ")"
+ | ClProj (m, ms, x) => "Proj(" ^ Int.toString m ^ "," ^ String.concatWith "," ms ^ "," ^ x ^ ")"
+
+structure CK = struct
+type ord_key = class_name
+open Order
+fun compare x =
+ case x of
+ (ClNamed n1, ClNamed n2) => Int.compare (n1, n2)
+ | (ClNamed _, _) => LESS
+ | (_, ClNamed _) => GREATER
+
+ | (ClProj (m1, ms1, x1), ClProj (m2, ms2, x2)) =>
+ join (Int.compare (m1, m2),
+ fn () => join (joinL String.compare (ms1, ms2),
+ fn () => String.compare (x1, x2)))
+end
+
+structure CS = BinarySetFn(CK)
+structure CM = BinaryMapFn(CK)
+
+type rules = (int * con list * con * exp) list
+
+type class = {closedRules : rules,
+ openRules : rules}
+val empty_class = {closedRules = [],
+ openRules = []}
+
+type env = {
+ renameK : int SM.map,
+ relK : string list,
+
+ renameC : kind var' SM.map,
+ relC : (string * kind) list,
+ namedC : (string * kind * con option) IM.map,
+
+ datatypes : datatyp IM.map,
+ constructors : (datatype_kind * int * string list * con option * int) SM.map,
+
+ classes : class CM.map,
+
+ renameE : con var' SM.map,
+ relE : (string * con) list,
+ namedE : (string * con) IM.map,
+
+ renameSgn : (int * sgn) SM.map,
+ sgn : (string * sgn) IM.map,
+
+ renameStr : (int * sgn) SM.map,
+ str : (string * sgn) IM.map
+}
+
+fun dump (env : env) =
+ (print "NamedC:\n";
+ IM.appi (fn (n, (x, k, co)) => print (x ^ " [" ^ Int.toString n ^ "]\n")) (#namedC env))
+
+val namedCounter = ref 0
+
+fun newNamed () =
+ let
+ val r = !namedCounter
+ in
+ namedCounter := r + 1;
+ r
+ end
+
+val empty = {
+ renameK = SM.empty,
+ relK = [],
+
+ renameC = SM.empty,
+ relC = [],
+ namedC = IM.empty,
+
+ datatypes = IM.empty,
+ constructors = SM.empty,
+
+ classes = CM.empty,
+
+ renameE = SM.empty,
+ relE = [],
+ namedE = IM.empty,
+
+ renameSgn = SM.empty,
+ sgn = IM.empty,
+
+ renameStr = SM.empty,
+ str = IM.empty
+}
+
+fun pushKRel (env : env) x =
+ let
+ val renameK = SM.map (fn n => n+1) (#renameK env)
+ in
+ {renameK = SM.insert (renameK, x, 0),
+ relK = x :: #relK env,
+
+ renameC = SM.map (fn Rel' (n, k) => Rel' (n, liftKindInKind 0 k)
+ | x => x) (#renameC env),
+ relC = map (fn (x, k) => (x, liftKindInKind 0 k)) (#relC env),
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = CM.map (fn cl => {closedRules = #closedRules cl,
+ openRules = map (fn (nvs, cs, c, e) =>
+ (nvs,
+ map (liftKindInCon 0) cs,
+ liftKindInCon 0 c,
+ liftKindInExp 0 e))
+ (#openRules cl)})
+ (#classes env),
+
+ renameE = SM.map (fn Rel' (n, c) => Rel' (n, liftKindInCon 0 c)
+ | Named' (n, c) => Named' (n, c)) (#renameE env),
+ relE = map (fn (x, c) => (x, liftKindInCon 0 c)) (#relE env),
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env
+ }
+ end
+
+fun lookupKRel (env : env) n =
+ (List.nth (#relK env, n))
+ handle Subscript => raise UnboundRel n
+
+fun lookupK (env : env) x = SM.find (#renameK env, x)
+
+fun pushCRel (env : env) x k =
+ let
+ val renameC = SM.map (fn Rel' (n, k) => Rel' (n+1, k)
+ | x => x) (#renameC env)
+ in
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = SM.insert (renameC, x, Rel' (0, k)),
+ relC = (x, k) :: #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = CM.map (fn class =>
+ {closedRules = #closedRules class,
+ openRules = map (fn (nvs, cs, c, e) =>
+ (nvs,
+ map (liftConInCon 0) cs,
+ liftConInCon 0 c,
+ liftConInExp 0 e))
+ (#openRules class)})
+ (#classes env),
+
+ renameE = SM.map (fn Rel' (n, c) => Rel' (n, lift c)
+ | Named' (n, c) => Named' (n, c)) (#renameE env),
+ relE = map (fn (x, c) => (x, lift c)) (#relE env),
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env
+ }
+ end
+
+fun lookupCRel (env : env) n =
+ (List.nth (#relC env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushCNamedAs (env : env) x n k co =
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = SM.insert (#renameC env, x, Named' (n, k)),
+ relC = #relC env,
+ namedC = IM.insert (#namedC env, n, (x, k, co)),
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = #classes env,
+
+ renameE = #renameE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env}
+
+fun pushCNamed env x k co =
+ let
+ val n = !namedCounter
+ in
+ namedCounter := n + 1;
+ (pushCNamedAs env x n k co, n)
+ end
+
+fun lookupCNamed (env : env) n =
+ case IM.find (#namedC env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupC (env : env) x =
+ case SM.find (#renameC env, x) of
+ NONE => NotBound
+ | SOME (Rel' x) => Rel x
+ | SOME (Named' x) => Named x
+
+fun pushDatatype (env : env) n xs xncs =
+ let
+ val dk = U.classifyDatatype xncs
+ in
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = #renameC env,
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = IM.insert (#datatypes env, n,
+ (xs, foldl (fn ((x, n, to), cons) =>
+ IM.insert (cons, n, (x, to))) IM.empty xncs)),
+ constructors = foldl (fn ((x, n', to), cmap) =>
+ SM.insert (cmap, x, (dk, n', xs, to, n)))
+ (#constructors env) xncs,
+
+ classes = #classes env,
+
+ renameE = #renameE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env}
+ end
+
+fun lookupDatatype (env : env) n =
+ case IM.find (#datatypes env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupDatatypeConstructor (_, dt) n =
+ case IM.find (dt, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupConstructor (env : env) s = SM.find (#constructors env, s)
+
+fun datatypeArgs (xs, _) = xs
+fun constructors (_, dt) = IM.foldri (fn (n, (x, to), ls) => (x, n, to) :: ls) [] dt
+
+fun listClasses (env : env) =
+ map (fn (cn, {closedRules, openRules}) =>
+ (class_name_out cn,
+ map (fn (nvs, cs, c, e) =>
+ let
+ val loc = #2 c
+ val c = foldr (fn (c', c) => (TFun (c', c), loc)) c cs
+ val c = ListUtil.foldli (fn (n, (), c) => (TCFun (Explicit,
+ "x" ^ Int.toString n,
+ (KError, loc),
+ c), loc))
+ c (List.tabulate (nvs, fn _ => ()))
+ in
+ (c, e)
+ end) (closedRules @ openRules))) (CM.listItemsi (#classes env))
+
+fun pushClass (env : env) n =
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = #renameC env,
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = CM.insert (#classes env, ClNamed n, empty_class),
+
+ renameE = #renameE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env}
+
+fun class_name_in (c, _) =
+ case c of
+ CNamed n => SOME (ClNamed n)
+ | CModProj x => SOME (ClProj x)
+ | CUnif (_, _, _, _, ref (Known c)) => class_name_in c
+ | _ => NONE
+
+fun isClass (env : env) c =
+ let
+ fun find NONE = false
+ | find (SOME c) = Option.isSome (CM.find (#classes env, c))
+ in
+ find (class_name_in c)
+ end
+
+fun class_head_in c =
+ case #1 c of
+ CApp (f, _) => class_head_in f
+ | CUnif (_, _, _, _, ref (Known c)) => class_head_in c
+ | _ => class_name_in c
+
+exception Unify
+
+fun unifyKinds (k1, k2) =
+ case (#1 k1, #1 k2) of
+ (KType, KType) => ()
+ | (KArrow (d1, r1), KArrow (d2, r2)) => (unifyKinds (d1, d2); unifyKinds (r1, r2))
+ | (KName, KName) => ()
+ | (KRecord k1, KRecord k2) => unifyKinds (k1, k2)
+ | (KUnit, KUnit) => ()
+ | (KTuple ks1, KTuple ks2) => (ListPair.appEq unifyKinds (ks1, ks2)
+ handle ListPair.UnequalLengths => raise Unify)
+ | (KUnif (_, _, ref (KKnown k1)), _) => unifyKinds (k1, k2)
+ | (_, KUnif (_, _, ref (KKnown k2))) => unifyKinds (k1, k2)
+ | (KRel n1, KRel n2) => if n1 = n2 then () else raise Unify
+ | (KFun (_, k1), KFun (_, k2)) => unifyKinds (k1, k2)
+ | _ => raise Unify
+
+fun eqCons (c1, c2) =
+ case (#1 c1, #1 c2) of
+ (CUnif (nl, _, _, _, ref (Known c1)), _) => eqCons (mliftConInCon nl c1, c2)
+ | (_, CUnif (nl, _, _, _, ref (Known c2))) => eqCons (c1, mliftConInCon nl c2)
+
+ | (CRel n1, CRel n2) => if n1 = n2 then () else raise Unify
+
+ | (TFun (d1, r1), TFun (d2, r2)) => (eqCons (d1, d2); eqCons (r1, r2))
+ | (TCFun (_, _, k1, r1), TCFun (_, _, k2, r2)) => (unifyKinds (k1, k2); eqCons (r1, r2))
+ | (TRecord c1, TRecord c2) => eqCons (c1, c2)
+ | (TDisjoint (a1, b1, c1), TDisjoint (a2, b2, c2)) =>
+ (eqCons (a1, a2); eqCons (b1, b2); eqCons (c1, c2))
+
+ | (CNamed n1, CNamed n2) => if n1 = n2 then () else raise Unify
+ | (CModProj (n1, ms1, x1), CModProj (n2, ms2, x2)) =>
+ if n1 = n2 andalso ms1 = ms2 andalso x1 = x2 then () else raise Unify
+ | (CApp (f1, x1), CApp (f2, x2)) => (eqCons (f1, f2); eqCons (x1, x2))
+ | (CAbs (_, k1, b1), CAbs (_, k2, b2)) => (unifyKinds (k1, k2); eqCons (b1, b2))
+
+ | (CKAbs (_, b1), CKAbs (_, b2)) => eqCons (b1, b2)
+ | (CKApp (c1, k1), CKApp (c2, k2)) => (eqCons (c1, c2); unifyKinds (k1, k2))
+ | (TKFun (_, c1), TKFun (_, c2)) => eqCons (c1, c2)
+
+ | (CName s1, CName s2) => if s1 = s2 then () else raise Unify
+
+ | (CRecord (k1, xcs1), CRecord (k2, xcs2)) =>
+ (unifyKinds (k1, k2);
+ if length xcs1 <> length xcs2 then
+ raise Unify
+ else
+ List.app (fn (x1, c1) =>
+ if List.exists (fn (x2, c2) => (eqCons (x1, x2); eqCons (c1, c2); true) handle Unify => false) xcs2 then
+ ()
+ else
+ raise Unify) xcs1)
+ | (CConcat (f1, x1), CConcat (f2, x2)) => (eqCons (f1, f2); eqCons (x1, x2))
+ | (CMap (d1, r1), CMap (d2, r2)) => (unifyKinds (d1, d2); unifyKinds (r1, r2))
+
+ | (CUnit, CUnit) => ()
+
+ | (CTuple cs1, CTuple cs2) => (ListPair.appEq (eqCons) (cs1, cs2)
+ handle ListPair.UnequalLengths => raise Unify)
+ | (CProj (c1, n1), CProj (c2, n2)) => (eqCons (c1, c2);
+ if n1 = n2 then () else raise Unify)
+
+ | _ => raise Unify
+
+fun unifyCons (hnorm : con -> con) rs =
+ let
+ fun unify d (c1, c2) =
+ case (#1 (hnorm c1), #1 (hnorm c2)) of
+ (CUnif (nl, _, _, _, ref (Known c1)), _) => unify d (mliftConInCon nl c1, c2)
+ | (_, CUnif (nl, _, _, _, ref (Known c2))) => unify d (c1, mliftConInCon nl c2)
+
+ | (CUnif _, _) => ()
+
+ | (c1', CRel n2) =>
+ if n2 < d then
+ case c1' of
+ CRel n1 => if n1 = n2 then () else raise Unify
+ | _ => raise Unify
+ else if n2 - d >= length rs then
+ case c1' of
+ CRel n1 => if n1 = n2 - length rs then () else raise Unify
+ | _ => raise Unify
+ else
+ let
+ val r = List.nth (rs, n2 - d)
+ in
+ case !r of
+ NONE => r := SOME c1
+ | SOME c2 => eqCons (c1, c2)
+ end
+
+ | (TFun (d1, r1), TFun (d2, r2)) => (unify d (d1, d2); unify d (r1, r2))
+ | (TCFun (_, _, k1, r1), TCFun (_, _, k2, r2)) => (unifyKinds (k1, k2); unify (d + 1) (r1, r2))
+ | (TRecord c1, TRecord c2) => unify d (c1, c2)
+ | (TDisjoint (a1, b1, c1), TDisjoint (a2, b2, c2)) =>
+ (unify d (a1, a2); unify d (b1, b2); unify d (c1, c2))
+
+ | (CNamed n1, CNamed n2) => if n1 = n2 then () else raise Unify
+ | (CModProj (n1, ms1, x1), CModProj (n2, ms2, x2)) =>
+ if n1 = n2 andalso ms1 = ms2 andalso x1 = x2 then () else raise Unify
+ | (CApp (f1, x1), CApp (f2, x2)) => (unify d (f1, f2); unify d (x1, x2))
+ | (CAbs (_, k1, b1), CAbs (_, k2, b2)) => (unifyKinds (k1, k2); unify (d + 1) (b1, b2))
+
+ | (CKAbs (_, b1), CKAbs (_, b2)) => unify d (b1, b2)
+ | (CKApp (c1, k1), CKApp (c2, k2)) => (unify d (c1, c2); unifyKinds (k1, k2))
+ | (TKFun (_, c1), TKFun (_, c2)) => unify d (c1, c2)
+
+ | (CName s1, CName s2) => if s1 = s2 then () else raise Unify
+
+ | (CRecord (k1, xcs1), CRecord (k2, xcs2)) =>
+ (unifyKinds (k1, k2);
+ if length xcs1 <> length xcs2 then
+ raise Unify
+ else
+ app (fn (x1, c1) =>
+ if List.exists (fn (x2, c2) => (unify d (x1, x2); unify d (c1, c2); true) handle Unify => false) xcs2 then
+ ()
+ else
+ raise Unify) xcs1)
+ | (CConcat (f1, x1), CConcat (f2, x2)) => (unify d (f1, f2); unify d (x1, x2))
+ | (CMap (d1, r1), CMap (d2, r2)) => (unifyKinds (d1, d2); unifyKinds (r1, r2))
+
+ | (CUnit, CUnit) => ()
+
+ | (CTuple cs1, CTuple cs2) => (ListPair.appEq (unify d) (cs1, cs2)
+ handle ListPair.UnequalLengths => raise Unify)
+ | (CProj (c1, n1), CProj (c2, n2)) => (unify d (c1, c2);
+ if n1 = n2 then () else raise Unify)
+
+ | _ => raise Unify
+ in
+ unify
+ end
+
+fun tryUnify hnorm nRs (c1, c2) =
+ let
+ val rs = List.tabulate (nRs, fn _ => ref NONE)
+ in
+ (unifyCons hnorm rs 0 (c1, c2);
+ SOME (map (fn r => case !r of
+ NONE => raise Unify
+ | SOME c => c) rs))
+ handle Unify => NONE
+ end
+
+fun unifySubst (rs : con list) =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn d => fn c =>
+ case c of
+ CRel n =>
+ if n < d then
+ c
+ else if n - d >= length rs then
+ CRel (n - d)
+ else
+ #1 (List.nth (rs, n - d))
+ | _ => c,
+ bind = fn (d, U.Con.RelC _) => d + 1
+ | (d, _) => d}
+ 0
+
+exception Bad of con * con
+
+val hasUnif = U.Con.exists {kind = fn _ => false,
+ con = fn CUnif (_, _, _, _, ref (Unknown _)) => true
+ | _ => false}
+
+fun startsWithUnif c =
+ let
+ fun firstArg (c, acc) =
+ case #1 c of
+ CApp (f, x) => firstArg (f, SOME x)
+ | _ => acc
+ in
+ case firstArg (c, NONE) of
+ NONE => false
+ | SOME x => hasUnif x
+ end
+
+val cause = ref (NONE : con option)
+fun resolveFailureCause () = !cause
+
+fun resolveClass (hnorm : con -> con) (consEq : con * con -> bool) (env : env) =
+ let
+ fun resolve firstLevel c =
+ let
+ fun notFound () = (if firstLevel then () else cause := SOME c; NONE)
+
+ fun doHead f =
+ case CM.find (#classes env, f) of
+ NONE => notFound ()
+ | SOME class =>
+ let
+ val loc = #2 c
+
+ fun generalize (c as (_, loc)) =
+ case #1 c of
+ CApp (f, x) =>
+ let
+ val (f, equate) = generalize f
+
+ fun isRecord () =
+ let
+ val rk = ref (KUnknown (fn _ => true))
+ val k = (KUnif (loc, "k", rk), loc)
+ val r = ref (Unknown (fn _ => true))
+ val rc = (CUnif (0, loc, k, "x", r), loc)
+ in
+ ((CApp (f, rc), loc),
+ fn () => (if consEq (rc, x) then
+ true
+ else
+ (raise Bad (rc, x);
+ false))
+ andalso equate ())
+ end
+ in
+ case #1 x of
+ CConcat _ => isRecord ()
+ | CRecord _ => isRecord ()
+ | _ => ((CApp (f, x), loc), equate)
+ end
+ | _ => (c, fn () => true)
+
+ val (c, equate) = generalize c
+
+ fun tryRules rules =
+ case rules of
+ [] => notFound ()
+ | (nRs, cs, c', e) :: rules' =>
+ case tryUnify hnorm nRs (c, c') of
+ NONE => tryRules rules'
+ | SOME rs =>
+ let
+ val eos = map (resolve false o unifySubst rs) cs
+ in
+ if List.exists (not o Option.isSome) eos
+ orelse not (equate ())
+ orelse not (consEq (c, unifySubst rs c')) then
+ tryRules rules'
+ else
+ let
+ val es = List.mapPartial (fn x => x) eos
+
+ val e = foldr (fn (c, e) => (ECApp (e, c), loc)) e rs
+ val e = foldl (fn (e', e) => (EApp (e, e'), loc)) e es
+ in
+ SOME e
+ end
+ end
+ in
+ tryRules (#openRules class @ #closedRules class)
+ end
+ in
+ if startsWithUnif c then
+ notFound ()
+ else
+ case #1 c of
+ TRecord c =>
+ (case #1 (hnorm c) of
+ CRecord (_, xts) =>
+ let
+ fun resolver (xts, acc) =
+ case xts of
+ [] => SOME (ERecord acc, #2 c)
+ | (x, t) :: xts =>
+ let
+ val t = hnorm t
+
+ val t = case t of
+ (CApp (f, x), loc) => (CApp (hnorm f, hnorm x), loc)
+ | _ => t
+ in
+ case resolve false t of
+ NONE => notFound ()
+ | SOME e => resolver (xts, (x, e, t) :: acc)
+ end
+ in
+ resolver (xts, [])
+ end
+ | _ => notFound ())
+ | _ =>
+ case class_head_in c of
+ SOME f => doHead f
+ | _ => notFound ()
+ end
+ in
+ cause := NONE;
+ resolve true
+ end
+
+fun rule_in c =
+ let
+ fun quantifiers (c, nvars) =
+ case #1 c of
+ CUnif (_, _, _, _, ref (Known c)) => quantifiers (c, nvars)
+ | TCFun (_, _, _, c) => quantifiers (c, nvars + 1)
+ | _ =>
+ let
+ fun clauses (c, hyps) =
+ case #1 c of
+ TFun (hyp, c) =>
+ (case class_head_in hyp of
+ SOME _ => clauses (c, hyp :: hyps)
+ | NONE => NONE)
+ | _ =>
+ case class_head_in c of
+ NONE => NONE
+ | SOME f => SOME (f, nvars, rev hyps, c)
+ in
+ clauses (c, [])
+ end
+ in
+ quantifiers (c, 0)
+ end
+
+fun pushERel (env : env) x t =
+ let
+ val renameE = SM.map (fn Rel' (n, t) => Rel' (n+1, t)
+ | x => x) (#renameE env)
+
+ val classes = CM.map (fn class =>
+ {openRules = map (fn (nvs, cs, c, e) => (nvs, cs, c, liftExp e)) (#openRules class),
+ closedRules = #closedRules class}) (#classes env)
+ val classes = case rule_in t of
+ NONE => classes
+ | SOME (f, nvs, cs, c) =>
+ case CM.find (classes, f) of
+ NONE => classes
+ | SOME class =>
+ let
+ val rule = (nvs, cs, c, (ERel 0, #2 t))
+
+ val class = {openRules = rule :: #openRules class,
+ closedRules = #closedRules class}
+ in
+ CM.insert (classes, f, class)
+ end
+ in
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = #renameC env,
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = classes,
+
+ renameE = SM.insert (renameE, x, Rel' (0, t)),
+ relE = (x, t) :: #relE env,
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env}
+ end
+
+fun lookupERel (env : env) n =
+ (List.nth (#relE env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushENamedAs (env : env) x n t =
+ let
+ val classes = #classes env
+ val classes = case rule_in t of
+ NONE => classes
+ | SOME (f, nvs, cs, c) =>
+ case CM.find (classes, f) of
+ NONE => classes
+ | SOME class =>
+ let
+ val e = (ENamed n, #2 t)
+
+ val class =
+ {openRules = #openRules class,
+ closedRules = (nvs, cs, c, e) :: #closedRules class}
+ in
+ CM.insert (classes, f, class)
+ end
+ in
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = #renameC env,
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = classes,
+
+ renameE = SM.insert (#renameE env, x, Named' (n, t)),
+ relE = #relE env,
+ namedE = IM.insert (#namedE env, n, (x, t)),
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = #renameStr env,
+ str = #str env}
+ end
+
+fun pushENamed env x t =
+ let
+ val n = !namedCounter
+ in
+ namedCounter := n + 1;
+ (pushENamedAs env x n t, n)
+ end
+
+fun lookupENamed (env : env) n =
+ case IM.find (#namedE env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun checkENamed (env : env) n =
+ Option.isSome (IM.find (#namedE env, n))
+
+fun lookupE (env : env) x =
+ case SM.find (#renameE env, x) of
+ NONE => NotBound
+ | SOME (Rel' x) => Rel x
+ | SOME (Named' x) => Named x
+
+fun pushSgnNamedAs (env : env) x n sgis =
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = #renameC env,
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = #classes env,
+
+ renameE = #renameE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ renameSgn = SM.insert (#renameSgn env, x, (n, sgis)),
+ sgn = IM.insert (#sgn env, n, (x, sgis)),
+
+ renameStr = #renameStr env,
+ str = #str env}
+
+fun pushSgnNamed env x sgis =
+ let
+ val n = !namedCounter
+ in
+ namedCounter := n + 1;
+ (pushSgnNamedAs env x n sgis, n)
+ end
+
+fun lookupSgnNamed (env : env) n =
+ case IM.find (#sgn env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupSgn (env : env) x = SM.find (#renameSgn env, x)
+
+fun lookupStrNamed (env : env) n =
+ case IM.find (#str env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupStr (env : env) x = SM.find (#renameStr env, x)
+
+
+fun sgiSeek (sgi, (sgns, strs, cons)) =
+ case sgi of
+ SgiConAbs (x, n, _) => (sgns, strs, IM.insert (cons, n, x))
+ | SgiCon (x, n, _, _) => (sgns, strs, IM.insert (cons, n, x))
+ | SgiDatatype dts => (sgns, strs, foldl (fn ((x, n, _, _), cons) => IM.insert (cons, n, x)) cons dts)
+ | SgiDatatypeImp (x, n, _, _, _, _, _) => (sgns, strs, IM.insert (cons, n, x))
+ | SgiVal _ => (sgns, strs, cons)
+ | SgiSgn (x, n, _) => (IM.insert (sgns, n, x), strs, cons)
+ | SgiStr (_, x, n, _) => (sgns, IM.insert (strs, n, x), cons)
+ | SgiConstraint _ => (sgns, strs, cons)
+ | SgiClassAbs (x, n, _) => (sgns, strs, IM.insert (cons, n, x))
+ | SgiClass (x, n, _, _) => (sgns, strs, IM.insert (cons, n, x))
+
+fun sgnSeek f sgis =
+ let
+ fun seek (sgis, sgns, strs, cons) =
+ case sgis of
+ [] => NONE
+ | (sgi, _) :: sgis =>
+ case f sgi of
+ SOME v =>
+ let
+ val cons =
+ case sgi of
+ SgiDatatype dts => foldl (fn ((x, n, _, _), cons) => IM.insert (cons, n, x)) cons dts
+ | SgiDatatypeImp (x, n, _, _, _, _, _) => IM.insert (cons, n, x)
+ | _ => cons
+ in
+ SOME (v, (sgns, strs, cons))
+ end
+ | NONE =>
+ let
+ val (sgns, strs, cons) = sgiSeek (sgi, (sgns, strs, cons))
+ in
+ seek (sgis, sgns, strs, cons)
+ end
+ in
+ seek (sgis, IM.empty, IM.empty, IM.empty)
+ end
+
+fun id x = x
+
+fun unravelStr (str, _) =
+ case str of
+ StrVar x => (x, [])
+ | StrProj (str, m) =>
+ let
+ val (x, ms) = unravelStr str
+ in
+ (x, ms @ [m])
+ end
+ | _ => raise Fail "unravelStr"
+
+fun sgnS_con (str, (sgns, strs, cons)) c =
+ case c of
+ CModProj (m1, ms, x) =>
+ (case IM.find (strs, m1) of
+ NONE => c
+ | SOME m1x =>
+ let
+ val (m1, ms') = unravelStr str
+ in
+ CModProj (m1, ms' @ m1x :: ms, x)
+ end)
+ | CNamed n =>
+ (case IM.find (cons, n) of
+ NONE => c
+ | SOME nx =>
+ let
+ val (m1, ms) = unravelStr str
+ in
+ CModProj (m1, ms, nx)
+ end)
+ | _ => c
+
+fun sgnS_con' (m1, ms', (sgns, strs, cons)) =
+ U.Con.map {kind = fn x => x,
+ con = fn c =>
+ case c of
+ CModProj (m1', ms, x) =>
+ (case IM.find (strs, m1') of
+ NONE => c
+ | SOME m1x => CModProj (m1, ms' @ m1x :: ms, x))
+ | CNamed n =>
+ (case IM.find (cons, n) of
+ NONE => c
+ | SOME nx => CModProj (m1, ms', nx))
+ | _ => c}
+
+fun sgnS_sgn (str, (sgns, strs, cons)) sgn =
+ case sgn of
+ SgnProj (m1, ms, x) =>
+ (case IM.find (strs, m1) of
+ NONE => sgn
+ | SOME m1x =>
+ let
+ val (m1, ms') = unravelStr str
+ in
+ SgnProj (m1, ms' @ m1x :: ms, x)
+ end)
+ | SgnVar n =>
+ (case IM.find (sgns, n) of
+ NONE => sgn
+ | SOME nx =>
+ let
+ val (m1, ms) = unravelStr str
+ in
+ SgnProj (m1, ms, nx)
+ end)
+ | _ => sgn
+
+fun projectStr env {sgn, str, field} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ (case sgnSeek (fn SgiStr (_, x, _, sgn) => if x = field then SOME sgn else NONE | _ => NONE) sgis of
+ NONE => NONE
+ | SOME (sgn, subs) => SOME (sgnSubSgn (str, subs) sgn))
+ | SgnError => SOME (SgnError, ErrorMsg.dummySpan)
+ | _ => NONE
+
+and sgnSubSgn x =
+ ElabUtil.Sgn.map {kind = id,
+ con = sgnS_con x,
+ sgn_item = id,
+ sgn = sgnS_sgn x}
+
+and projectSgn env {sgn, str, field} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ (case sgnSeek (fn SgiSgn (x, _, sgn) => if x = field then SOME sgn else NONE | _ => NONE) sgis of
+ NONE => NONE
+ | SOME (sgn, subs) => SOME (sgnSubSgn (str, subs) sgn))
+ | SgnError => SOME (SgnError, ErrorMsg.dummySpan)
+ | _ => NONE
+
+and hnormSgn env (all as (sgn, loc)) =
+ case sgn of
+ SgnError => all
+ | SgnVar n => hnormSgn env (#2 (lookupSgnNamed env n))
+ | SgnConst _ => all
+ | SgnFun _ => all
+ | SgnProj (m, ms, x) =>
+ let
+ val (_, sgn) = lookupStrNamed env m
+
+ fun doProjection (m1, NONE) = NONE
+ | doProjection (m1, SOME (str, sgn)) =
+ case projectStr env {str = str,
+ sgn = sgn,
+ field = m1} of
+ NONE => NONE
+ | SOME sgn' => SOME ((StrProj (str, m1), loc), sgn')
+ in
+ case foldl doProjection (SOME ((StrVar m, loc), sgn)) ms of
+ NONE => raise Fail "ElabEnv.hnormSgn: pre-projectSgn failed"
+ | SOME (str, sgn) =>
+ case projectSgn env {str = str,
+ sgn = sgn,
+ field = x} of
+ NONE => raise Fail "ElabEnv.hnormSgn: projectSgn failed"
+ | SOME sgn => hnormSgn env sgn
+ end
+ | SgnWhere (sgn, ms, x, c) =>
+ let
+ fun rewrite (sgn, ms) =
+ case #1 (hnormSgn env sgn) of
+ SgnError => (SgnError, loc)
+ | SgnConst sgis =>
+ let
+ fun traverse (ms, pre, post) =
+ case post of
+ [] => raise Fail "ElabEnv.hnormSgn: Can't reduce 'where' [1]"
+
+ | (sgi as (SgiConAbs (x', n, k), loc)) :: rest =>
+ if List.null ms andalso x = x' then
+ List.revAppend (pre, (SgiCon (x', n, k, c), loc) :: rest)
+ else
+ traverse (ms, sgi :: pre, rest)
+
+ | (sgi as (SgiStr (im, x', n, sgn'), loc)) :: rest =>
+ (case ms of
+ [] => traverse (ms, sgi :: pre, rest)
+ | x :: ms' =>
+ if x = x' then
+ List.revAppend (pre,
+ (SgiStr (im, x', n,
+ rewrite (sgn', ms')), loc) :: rest)
+ else
+ traverse (ms, sgi :: pre, rest))
+
+ | sgi :: rest => traverse (ms, sgi :: pre, rest)
+
+ val sgis = traverse (ms, [], sgis)
+ in
+ (SgnConst sgis, loc)
+ end
+ | _ => raise Fail "ElabEnv.hnormSgn: Can't reduce 'where' [2]"
+ in
+ rewrite (sgn, ms)
+ end
+
+fun manifest (m, ms, loc) =
+ foldl (fn (m, str) => (StrProj (str, m), loc)) (StrVar m, loc) ms
+
+fun enrichClasses env classes (m1, ms) sgn =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ let
+ val (classes, _, _, _) =
+ foldl (fn (sgi, (classes, newClasses, fmap, env)) =>
+ let
+ fun found (x, n) =
+ (CM.insert (classes,
+ ClProj (m1, ms, x),
+ empty_class),
+ IM.insert (newClasses, n, x),
+ sgiSeek (#1 sgi, fmap),
+ env)
+
+ fun default () = (classes, newClasses, sgiSeek (#1 sgi, fmap), env)
+ in
+ case #1 sgi of
+ SgiStr (Import, x, _, sgn) =>
+ let
+ val str = manifest (m1, ms, #2 sgi)
+ val sgn' = sgnSubSgn (str, fmap) sgn
+ in
+ (enrichClasses env classes (m1, ms @ [x]) sgn',
+ newClasses,
+ sgiSeek (#1 sgi, fmap),
+ env)
+ end
+ | SgiSgn (x, n, sgn) =>
+ (classes,
+ newClasses,
+ fmap,
+ pushSgnNamedAs env x n sgn)
+
+ | SgiClassAbs (x, n, _) => found (x, n)
+ | SgiClass (x, n, _, _) => found (x, n)
+ | SgiVal (x, n, c) =>
+ (case rule_in c of
+ NONE => default ()
+ | SOME (cn, nvs, cs, c) =>
+ let
+ val loc = #2 c
+ val globalize = sgnS_con' (m1, ms, fmap)
+
+ val nc =
+ case cn of
+ ClNamed f => IM.find (newClasses, f)
+ | _ => NONE
+ in
+ case nc of
+ NONE =>
+ let
+ val classes =
+ case CM.find (classes, cn) of
+ NONE => classes
+ | SOME class =>
+ let
+ val e = (EModProj (m1, ms, x), #2 sgn)
+
+ val class =
+ {openRules = #openRules class,
+ closedRules = (nvs,
+ map globalize cs,
+ globalize c,
+ e) :: #closedRules class}
+ in
+ CM.insert (classes, cn, class)
+ end
+ in
+ (classes,
+ newClasses,
+ fmap,
+ env)
+ end
+ | SOME fx =>
+ let
+ val cn = ClProj (m1, ms, fx)
+
+ val classes =
+ case CM.find (classes, cn) of
+ NONE => classes
+ | SOME class =>
+ let
+ val e = (EModProj (m1, ms, x), #2 sgn)
+
+ val class =
+ {openRules = #openRules class,
+ closedRules = (nvs,
+ map globalize cs,
+ globalize c,
+ e) :: #closedRules class}
+ in
+ CM.insert (classes, cn, class)
+ end
+ in
+ (classes,
+ newClasses,
+ fmap,
+ env)
+ end
+ end)
+ | _ => default ()
+ end)
+ (classes, IM.empty, (IM.empty, IM.empty, IM.empty), env) sgis
+ in
+ classes
+ end
+ | _ => classes
+
+and pushStrNamedAs' enrich (env : env) x n sgn =
+ let
+ val renameStr = SM.insert (#renameStr env, x, (n, sgn))
+ val str = IM.insert (#str env, n, (x, sgn))
+ fun newEnv classes =
+ {renameK = #renameK env,
+ relK = #relK env,
+
+ renameC = #renameC env,
+ relC = #relC env,
+ namedC = #namedC env,
+
+ datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ classes = classes,
+
+ renameE = #renameE env,
+ relE = #relE env,
+ namedE = #namedE env,
+
+ renameSgn = #renameSgn env,
+ sgn = #sgn env,
+
+ renameStr = renameStr,
+ str = str}
+ in
+ if enrich then
+ newEnv (enrichClasses (newEnv (#classes env)) (#classes env) (n, []) sgn)
+ else
+ newEnv (#classes env)
+ end
+
+and pushStrNamedAs env = pushStrNamedAs' true env
+
+fun pushStrNamed env x sgn =
+ let
+ val n = !namedCounter
+ in
+ namedCounter := n + 1;
+ (pushStrNamedAs env x n sgn, n)
+ end
+
+fun sgiBinds env (sgi, loc) =
+ case sgi of
+ SgiConAbs (x, n, k) => pushCNamedAs env x n k NONE
+ | SgiCon (x, n, k, c) => pushCNamedAs env x n k (SOME c)
+ | SgiDatatype dts =>
+ let
+ fun doOne ((x, n, xs, xncs), env) =
+ let
+ val k = (KType, loc)
+ val k' = foldr (fn (_, k') => (KArrow (k, k'), loc)) k xs
+
+ val env = pushCNamedAs env x n k' NONE
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => (CNamed n, loc)
+ | SOME t => (TFun (t, (CNamed n, loc)), loc)
+
+ val k = (KType, loc)
+ val t = foldr (fn (x, t) => (TCFun (Explicit, x, k, t), loc)) t xs
+ in
+ pushENamedAs env x' n' t
+ end)
+ env xncs
+ end
+ in
+ foldl doOne env dts
+ end
+ | SgiDatatypeImp (x, n, m1, ms, x', xs, xncs) =>
+ let
+ val k = (KType, loc)
+ val k' = foldr (fn (_, k') => (KArrow (k, k'), loc)) k xs
+
+ val env = pushCNamedAs env x n k' (SOME (CModProj (m1, ms, x'), loc))
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => (CNamed n, loc)
+ | SOME t => (TFun (t, (CNamed n, loc)), loc)
+
+ val k = (KType, loc)
+ val t = foldr (fn (x, t) => (TCFun (Explicit, x, k, t), loc)) t xs
+ in
+ pushENamedAs env x' n' t
+ end)
+ env xncs
+ end
+ | SgiVal (x, n, t) => pushENamedAs env x n t
+ | SgiStr (_, x, n, sgn) => pushStrNamedAs' false env x n sgn
+ | SgiSgn (x, n, sgn) => pushSgnNamedAs env x n sgn
+ | SgiConstraint _ => env
+
+ | SgiClassAbs (x, n, k) => pushCNamedAs env x n k NONE
+ | SgiClass (x, n, k, c) => pushCNamedAs env x n k (SOME c)
+
+fun sgnSubCon x =
+ ElabUtil.Con.map {kind = id,
+ con = sgnS_con x}
+
+fun chaseMpath env (n, ms) =
+ let
+ val (_, sgn) = lookupStrNamed env n
+ in
+ foldl (fn (m, (str, sgn)) =>
+ case projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => raise Fail "kindof: Unknown substructure"
+ | SOME sgn => ((StrProj (str, m), #2 sgn), sgn))
+ ((StrVar n, #2 sgn), sgn) ms
+ end
+
+fun projectCon env {sgn, str, field} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ (case sgnSeek (fn SgiConAbs (x, _, k) => if x = field then SOME (k, NONE) else NONE
+ | SgiCon (x, _, k, c) => if x = field then SOME (k, SOME c) else NONE
+ | SgiDatatype dts =>
+ (case List.find (fn (x, _, xs, _) => x = field) dts of
+ SOME (_, _, xs, _) =>
+ let
+ val k = (KType, #2 sgn)
+ val k' = foldl (fn (_, k') => (KArrow (k, k'), #2 sgn)) k xs
+ in
+ SOME (k', NONE)
+ end
+ | NONE => NONE)
+ | SgiDatatypeImp (x, _, m1, ms, x', xs, _) =>
+ if x = field then
+ let
+ val k = (KType, #2 sgn)
+ val k' = foldl (fn (_, k') => (KArrow (k, k'), #2 sgn)) k xs
+ in
+ SOME (k', SOME (CModProj (m1, ms, x'), #2 sgn))
+ end
+ else
+ NONE
+ | SgiClassAbs (x, _, k) => if x = field then
+ SOME (k, NONE)
+ else
+ NONE
+ | SgiClass (x, _, k, c) => if x = field then
+ SOME (k, SOME c)
+ else
+ NONE
+ | _ => NONE) sgis of
+ NONE => NONE
+ | SOME ((k, co), subs) => SOME (k, Option.map (sgnSubCon (str, subs)) co))
+ | SgnError => SOME ((KError, ErrorMsg.dummySpan), SOME (CError, ErrorMsg.dummySpan))
+ | _ => NONE
+
+fun projectDatatype env {sgn, str, field} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ (case sgnSeek (fn SgiDatatype dts =>
+ (case List.find (fn (x, _, _, _) => x = field) dts of
+ SOME (_, _, xs, xncs) => SOME (xs, xncs)
+ | NONE => NONE)
+ | SgiDatatypeImp (x, _, _, _, _, xs, xncs) => if x = field then SOME (xs, xncs) else NONE
+ | _ => NONE) sgis of
+ NONE => NONE
+ | SOME ((xs, xncs), subs) => SOME (xs,
+ map (fn (x, n, to) => (x, n, Option.map (sgnSubCon (str, subs)) to)) xncs))
+ | _ => NONE
+
+fun projectConstructor env {sgn, str, field} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ let
+ fun consider (n, xs, xncs) =
+ ListUtil.search (fn (x, n', to) =>
+ if x <> field then
+ NONE
+ else
+ SOME (U.classifyDatatype xncs, n', xs, to, (CNamed n, #2 str))) xncs
+ in
+ case sgnSeek (fn SgiDatatype dts =>
+ let
+ fun search dts =
+ case dts of
+ [] => NONE
+ | (_, n, xs, xncs) :: dts =>
+ case consider (n, xs, xncs) of
+ NONE => search dts
+ | v => v
+ in
+ search dts
+ end
+ | SgiDatatypeImp (_, n, _, _, _, xs, xncs) => consider (n, xs, xncs)
+ | _ => NONE) sgis of
+ NONE => NONE
+ | SOME ((dk, n, xs, to, t), subs) => SOME (dk, n, xs, Option.map (sgnSubCon (str, subs)) to,
+ sgnSubCon (str, subs) t)
+ end
+ | _ => NONE
+
+fun projectVal env {sgn, str, field} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis =>
+ let
+ fun seek (n, xs, xncs) =
+ ListUtil.search (fn (x, _, to) =>
+ if x = field then
+ SOME (let
+ val base = (CNamed n, #2 sgn)
+ val nxs = length xs
+ val base = ListUtil.foldli (fn (i, _, base) =>
+ (CApp (base,
+ (CRel (nxs - i - 1), #2 sgn)),
+ #2 sgn))
+ base xs
+
+ val t =
+ case to of
+ NONE => base
+ | SOME t => (TFun (t, base), #2 sgn)
+ val k = (KType, #2 sgn)
+ in
+ foldr (fn (x, t) => (TCFun (Implicit, x, k, t), #2 sgn))
+ t xs
+ end)
+ else
+ NONE) xncs
+ in
+ case sgnSeek (fn SgiVal (x, _, c) => if x = field then SOME c else NONE
+ | SgiDatatype dts =>
+ let
+ fun search dts =
+ case dts of
+ [] => NONE
+ | (_, n, xs, xncs) :: dts =>
+ case seek (n, xs, xncs) of
+ NONE => search dts
+ | v => v
+ in
+ search dts
+ end
+ | SgiDatatypeImp (_, n, _, _, _, xs, xncs) => seek (n, xs, xncs)
+ | _ => NONE) sgis of
+ NONE => NONE
+ | SOME (c, subs) => SOME (sgnSubCon (str, subs) c)
+ end
+ | SgnError => SOME (CError, ErrorMsg.dummySpan)
+ | _ => NONE
+
+fun sgnSeekConstraints (str, sgis) =
+ let
+ fun seek (sgis, sgns, strs, cons, acc) =
+ case sgis of
+ [] => acc
+ | (sgi, _) :: sgis =>
+ case sgi of
+ SgiConstraint (c1, c2) =>
+ let
+ val sub = sgnSubCon (str, (sgns, strs, cons))
+ in
+ seek (sgis, sgns, strs, cons, (sub c1, sub c2) :: acc)
+ end
+ | SgiConAbs (x, n, _) => seek (sgis, sgns, strs, IM.insert (cons, n, x), acc)
+ | SgiCon (x, n, _, _) => seek (sgis, sgns, strs, IM.insert (cons, n, x), acc)
+ | SgiDatatype dts => seek (sgis, sgns, strs,
+ foldl (fn ((x, n, _, _), cons) => IM.insert (cons, n, x)) cons dts, acc)
+ | SgiDatatypeImp (x, n, _, _, _, _, _) => seek (sgis, sgns, strs, IM.insert (cons, n, x), acc)
+ | SgiVal _ => seek (sgis, sgns, strs, cons, acc)
+ | SgiSgn (x, n, _) => seek (sgis, IM.insert (sgns, n, x), strs, cons, acc)
+ | SgiStr (_, x, n, _) => seek (sgis, sgns, IM.insert (strs, n, x), cons, acc)
+ | SgiClassAbs (x, n, _) => seek (sgis, sgns, strs, IM.insert (cons, n, x), acc)
+ | SgiClass (x, n, _, _) => seek (sgis, sgns, strs, IM.insert (cons, n, x), acc)
+ in
+ seek (sgis, IM.empty, IM.empty, IM.empty, [])
+ end
+
+fun projectConstraints env {sgn, str} =
+ case #1 (hnormSgn env sgn) of
+ SgnConst sgis => SOME (sgnSeekConstraints (str, sgis))
+ | SgnError => SOME []
+ | _ => NONE
+
+fun patBinds env (p, loc) =
+ case p of
+ PVar (x, t) => pushERel env x t
+ | PPrim _ => env
+ | PCon (_, _, _, NONE) => env
+ | PCon (_, _, _, SOME p) => patBinds env p
+ | PRecord xps => foldl (fn ((_, p, _), env) => patBinds env p) env xps
+
+fun patBindsN (p, _) =
+ case p of
+ PVar _ => 1
+ | PPrim _ => 0
+ | PCon (_, _, _, NONE) => 0
+ | PCon (_, _, _, SOME p) => patBindsN p
+ | PRecord xps => foldl (fn ((_, p, _), n) => patBindsN p + n) 0 xps
+
+fun edeclBinds env (d, loc) =
+ case d of
+ EDVal (p, _, _) => patBinds env p
+ | EDValRec vis => foldl (fn ((x, t, _), env) => pushERel env x t) env vis
+
+fun declBinds env (d, loc) =
+ case d of
+ DCon (x, n, k, c) => pushCNamedAs env x n k (SOME c)
+ | DDatatype dts =>
+ let
+ fun doOne ((x, n, xs, xncs), env) =
+ let
+ val k = (KType, loc)
+ val nxs = length xs
+ val (tb, kb) = ListUtil.foldli (fn (i, x', (tb, kb)) =>
+ ((CApp (tb, (CRel (nxs - i - 1), loc)), loc),
+ (KArrow (k, kb), loc)))
+ ((CNamed n, loc), k) xs
+
+ val env = pushCNamedAs env x n kb NONE
+ val env = pushDatatype env n xs xncs
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => tb
+ | SOME t => (TFun (t, tb), loc)
+ val t = foldr (fn (x, t) => (TCFun (Implicit, x, k, t), loc)) t xs
+ in
+ pushENamedAs env x' n' t
+ end)
+ env xncs
+ end
+ in
+ foldl doOne env dts
+ end
+ | DDatatypeImp (x, n, m, ms, x', xs, xncs) =>
+ let
+ val t = (CModProj (m, ms, x'), loc)
+ val k = (KType, loc)
+ val nxs = length xs
+ val (tb, kb) = ListUtil.foldli (fn (i, x', (tb, kb)) =>
+ ((CApp (tb, (CRel (nxs - i - 1), loc)), loc),
+ (KArrow (k, kb), loc)))
+ ((CNamed n, loc), k) xs
+
+ val env = pushCNamedAs env x n kb (SOME t)
+ val env = pushDatatype env n xs xncs
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => tb
+ | SOME t => (TFun (t, tb), loc)
+ val t = foldr (fn (x, t) => (TCFun (Implicit, x, k, t), loc)) t xs
+ in
+ pushENamedAs env x' n' t
+ end)
+ env xncs
+ end
+ | DVal (x, n, t, _) => pushENamedAs env x n t
+ | DValRec vis => foldl (fn ((x, n, t, _), env) => pushENamedAs env x n t) env vis
+ | DSgn (x, n, sgn) => pushSgnNamedAs env x n sgn
+ | DStr (x, n, sgn, _) => pushStrNamedAs' false env x n sgn
+ | DFfiStr (x, n, sgn) => pushStrNamedAs' false env x n sgn
+ | DConstraint _ => env
+ | DExport _ => env
+ | DTable (tn, x, n, c, _, pc, _, cc) =>
+ let
+ val ct = (CModProj (tn, [], "sql_table"), loc)
+ val ct = (CApp (ct, c), loc)
+ val ct = (CApp (ct, (CConcat (pc, cc), loc)), loc)
+ in
+ pushENamedAs env x n ct
+ end
+ | DSequence (tn, x, n) =>
+ let
+ val t = (CModProj (tn, [], "sql_sequence"), loc)
+ in
+ pushENamedAs env x n t
+ end
+ | DView (tn, x, n, _, c) =>
+ let
+ val ct = (CModProj (tn, [], "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ pushENamedAs env x n ct
+ end
+ | DDatabase _ => env
+ | DCookie (tn, x, n, c) =>
+ let
+ val t = (CApp ((CModProj (tn, [], "cookie"), loc), c), loc)
+ in
+ pushENamedAs env x n t
+ end
+ | DStyle (tn, x, n) =>
+ let
+ val t = (CModProj (tn, [], "css_class"), loc)
+ in
+ pushENamedAs env x n t
+ end
+ | DTask _ => env
+ | DPolicy _ => env
+ | DOnError _ => env
+ | DFfi (x, n, _, t) => pushENamedAs env x n t
+
+end
diff --git a/src/elab_err.sig b/src/elab_err.sig
new file mode 100644
index 0000000..acf137d
--- /dev/null
+++ b/src/elab_err.sig
@@ -0,0 +1,125 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+signature ELAB_ERR = sig
+
+ datatype kind_error =
+ UnboundKind of ErrorMsg.span * string
+
+ val kindError : ElabEnv.env -> kind_error -> unit
+
+ datatype kunify_error =
+ KOccursCheckFailed of Elab.kind * Elab.kind
+ | KIncompatible of Elab.kind * Elab.kind
+ | KScope of Elab.kind * Elab.kind
+
+ val kunifyError : ElabEnv.env -> kunify_error -> unit
+
+ datatype con_error =
+ UnboundCon of ErrorMsg.span * string
+ | UnboundDatatype of ErrorMsg.span * string
+ | UnboundStrInCon of ErrorMsg.span * string
+ | WrongKind of Elab.con * Elab.kind * Elab.kind * ElabEnv.env * kunify_error
+ | DuplicateField of ErrorMsg.span * string
+ | ProjBounds of Elab.con * int
+ | ProjMismatch of Elab.con * Elab.kind
+
+ val conError : ElabEnv.env -> con_error -> unit
+
+ datatype cunify_error =
+ CKind of Elab.kind * Elab.kind * ElabEnv.env * kunify_error
+ | COccursCheckFailed of Elab.con * Elab.con
+ | CIncompatible of Elab.con * Elab.con
+ | CExplicitness of Elab.con * Elab.con
+ | CKindof of Elab.kind * Elab.con * string
+ | CRecordFailure of Elab.con * Elab.con * (Elab.con * Elab.con * Elab.con * (ElabEnv.env * cunify_error) option) option
+ | TooLifty of ErrorMsg.span * ErrorMsg.span
+ | TooUnify of Elab.con * Elab.con
+ | TooDeep
+ | CScope of Elab.con * Elab.con
+
+ val cunifyError : ElabEnv.env -> cunify_error -> unit
+
+ datatype exp_error =
+ UnboundExp of ErrorMsg.span * string
+ | UnboundStrInExp of ErrorMsg.span * string
+ | Unify of Elab.exp * Elab.con * Elab.con * ElabEnv.env * cunify_error
+ | Unif of string * ErrorMsg.span * Elab.con
+ | WrongForm of string * Elab.exp * Elab.con
+ | IncompatibleCons of Elab.con * Elab.con
+ | DuplicatePatternVariable of ErrorMsg.span * string
+ | PatUnify of Elab.pat * Elab.con * Elab.con * ElabEnv.env * cunify_error
+ | UnboundConstructor of ErrorMsg.span * string list * string
+ | PatHasArg of ErrorMsg.span
+ | PatHasNoArg of ErrorMsg.span
+ | Inexhaustive of ErrorMsg.span * Elab.pat
+ | DuplicatePatField of ErrorMsg.span * string
+ | Unresolvable of ErrorMsg.span * Elab.con
+ | OutOfContext of ErrorMsg.span * (Elab.exp * Elab.con) option
+ | IllegalRec of string * Elab.exp
+ | IllegalFlex of Source.exp
+
+ val expError : ElabEnv.env -> exp_error -> unit
+
+ datatype decl_error =
+ KunifsRemain of Elab.decl list
+ | CunifsRemain of Elab.decl list
+ | Nonpositive of Elab.decl
+
+ val declError : ElabEnv.env -> decl_error -> unit
+
+ datatype sgn_error =
+ UnboundSgn of ErrorMsg.span * string
+ | UnmatchedSgi of ErrorMsg.span * Elab.sgn_item
+ | SgiWrongKind of ErrorMsg.span * Elab.sgn_item * Elab.kind * Elab.sgn_item * Elab.kind * ElabEnv.env * kunify_error
+ | SgiWrongCon of ErrorMsg.span * Elab.sgn_item * Elab.con * Elab.sgn_item * Elab.con * ElabEnv.env * cunify_error
+ | SgiMismatchedDatatypes of ErrorMsg.span * Elab.sgn_item * Elab.sgn_item
+ * (Elab.con * Elab.con * ElabEnv.env * cunify_error) option
+ | SgnWrongForm of ErrorMsg.span * Elab.sgn * Elab.sgn
+ | UnWhereable of Elab.sgn * string
+ | WhereWrongKind of Elab.kind * Elab.kind * ElabEnv.env * kunify_error
+ | NotIncludable of Elab.sgn
+ | DuplicateCon of ErrorMsg.span * string
+ | DuplicateVal of ErrorMsg.span * string
+ | DuplicateSgn of ErrorMsg.span * string
+ | DuplicateStr of ErrorMsg.span * string
+ | NotConstraintsable of Elab.sgn
+
+ val sgnError : ElabEnv.env -> sgn_error -> unit
+
+ datatype str_error =
+ UnboundStr of ErrorMsg.span * string
+ | NotFunctor of Elab.sgn
+ | FunctorRebind of ErrorMsg.span
+ | UnOpenable of Elab.sgn
+ | NotType of ErrorMsg.span * Elab.kind * (Elab.kind * Elab.kind * ElabEnv.env * kunify_error)
+ | DuplicateConstructor of string * ErrorMsg.span
+ | NotDatatype of ErrorMsg.span
+
+ val strError : ElabEnv.env -> str_error -> unit
+
+end
diff --git a/src/elab_err.sml b/src/elab_err.sml
new file mode 100644
index 0000000..385caca
--- /dev/null
+++ b/src/elab_err.sml
@@ -0,0 +1,440 @@
+(* Copyright (c) 2008-2010, 2012, 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 ElabErr :> ELAB_ERR = struct
+
+structure L = Source
+open Elab
+
+structure E = ElabEnv
+structure U = ElabUtil
+
+open Print
+structure P = ElabPrint
+
+val p_kind = P.p_kind
+
+datatype kind_error =
+ UnboundKind of ErrorMsg.span * string
+
+fun kindError env err =
+ case err of
+ UnboundKind (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound kind variable " ^ s)
+
+datatype kunify_error =
+ KOccursCheckFailed of kind * kind
+ | KIncompatible of kind * kind
+ | KScope of kind * kind
+
+fun kunifyError env err =
+ case err of
+ KOccursCheckFailed (k1, k2) =>
+ eprefaces "Kind occurs check failed"
+ [("Kind 1", p_kind env k1),
+ ("Kind 2", p_kind env k2)]
+ | KIncompatible (k1, k2) =>
+ eprefaces "Incompatible kinds"
+ [("Kind 1", p_kind env k1),
+ ("Kind 2", p_kind env k2)]
+ | KScope (k1, k2) =>
+ eprefaces "Scoping prevents kind unification"
+ [("Kind 1", p_kind env k1),
+ ("Kind 2", p_kind env k2)]
+
+fun p_con env c = P.p_con env (ElabOps.reduceCon env c)
+
+datatype con_error =
+ UnboundCon of ErrorMsg.span * string
+ | UnboundDatatype of ErrorMsg.span * string
+ | UnboundStrInCon of ErrorMsg.span * string
+ | WrongKind of con * kind * kind * E.env * kunify_error
+ | DuplicateField of ErrorMsg.span * string
+ | ProjBounds of con * int
+ | ProjMismatch of con * kind
+
+fun conError env err =
+ case err of
+ UnboundCon (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound constructor variable " ^ s)
+ | UnboundDatatype (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound datatype " ^ s)
+ | UnboundStrInCon (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound structure " ^ s)
+ | WrongKind (c, k1, k2, env', kerr) =>
+ (ErrorMsg.errorAt (#2 c) "Wrong kind";
+ eprefaces' [("Constructor", p_con env c),
+ ("Have kind", p_kind env k1),
+ ("Need kind", p_kind env k2)];
+ kunifyError env' kerr)
+ | DuplicateField (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate record field " ^ s)
+ | ProjBounds (c, n) =>
+ (ErrorMsg.errorAt (#2 c) "Out of bounds constructor projection";
+ eprefaces' [("Constructor", p_con env c),
+ ("Index", Print.PD.string (Int.toString n))])
+ | ProjMismatch (c, k) =>
+ (ErrorMsg.errorAt (#2 c) "Projection from non-tuple constructor";
+ eprefaces' [("Constructor", p_con env c),
+ ("Kind", p_kind env k)])
+
+datatype cunify_error =
+ CKind of kind * kind * E.env * kunify_error
+ | COccursCheckFailed of con * con
+ | CIncompatible of con * con
+ | CExplicitness of con * con
+ | CKindof of kind * con * string
+ | CRecordFailure of con * con * (con * con * con * (E.env * cunify_error) option) option
+ | TooLifty of ErrorMsg.span * ErrorMsg.span
+ | TooUnify of con * con
+ | TooDeep
+ | CScope of con * con
+
+fun cunifyError env err : unit =
+ case err of
+ CKind (k1, k2, env', kerr) =>
+ (eprefaces "Kind unification failure"
+ [("Have", p_kind env k1),
+ ("Need", p_kind env k2)];
+ kunifyError env' kerr)
+ | COccursCheckFailed (c1, c2) =>
+ eprefaces "Constructor occurs check failed"
+ [("Have", p_con env c1),
+ ("Need", p_con env c2)]
+ | CIncompatible (c1, c2) =>
+ eprefaces "Incompatible constructors"
+ [("Have", p_con env c1),
+ ("Need", p_con env c2)]
+ | CExplicitness (c1, c2) =>
+ eprefaces "Differing constructor function explicitness"
+ [("Have", p_con env c1),
+ ("Need", p_con env c2)]
+ | CKindof (k, c, expected) =>
+ eprefaces ("Unexpected kind for kindof calculation (expecting " ^ expected ^ ")")
+ [("Kind", p_kind env k),
+ ("Con", p_con env c)]
+ | CRecordFailure (c1, c2, fo) =>
+ (eprefaces "Can't unify record constructors"
+ (("Have", p_con env c1)
+ :: ("Need", p_con env c2)
+ :: (case fo of
+ NONE => []
+ | SOME (nm, t1, t2, _) =>
+ [("Field", p_con env nm),
+ ("Value 1", p_con env t1),
+ ("Value 2", p_con env t2)]));
+ case fo of
+ SOME (_, _, _, SOME (env', err')) => cunifyError env' err'
+ | _ => ())
+ | TooLifty (loc1, loc2) =>
+ (ErrorMsg.errorAt loc1 "Can't unify two unification variables that both have suspended liftings";
+ eprefaces' [("Other location", Print.PD.string (ErrorMsg.spanToString loc2))])
+ | TooUnify (c1, c2) =>
+ (ErrorMsg.errorAt (#2 c1) "Substitution in constructor is blocked by a too-deep unification variable";
+ eprefaces' [("Replacement", p_con env c1),
+ ("Body", p_con env c2)])
+ | TooDeep => ErrorMsg.error "Can't reverse-engineer unification variable lifting"
+ | CScope (c1, c2) =>
+ eprefaces "Scoping prevents constructor unification"
+ [("Have", p_con env c1),
+ ("Need", p_con env c2)]
+
+datatype exp_error =
+ UnboundExp of ErrorMsg.span * string
+ | UnboundStrInExp of ErrorMsg.span * string
+ | Unify of exp * con * con * E.env * cunify_error
+ | Unif of string * ErrorMsg.span * con
+ | WrongForm of string * exp * con
+ | IncompatibleCons of con * con
+ | DuplicatePatternVariable of ErrorMsg.span * string
+ | PatUnify of pat * con * con * E.env * cunify_error
+ | UnboundConstructor of ErrorMsg.span * string list * string
+ | PatHasArg of ErrorMsg.span
+ | PatHasNoArg of ErrorMsg.span
+ | Inexhaustive of ErrorMsg.span * pat
+ | DuplicatePatField of ErrorMsg.span * string
+ | Unresolvable of ErrorMsg.span * con
+ | OutOfContext of ErrorMsg.span * (exp * con) option
+ | IllegalRec of string * exp
+ | IllegalFlex of Source.exp
+
+val simplExp = U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn env => fn c => #1 (ElabOps.reduceCon env (c, ErrorMsg.dummySpan)),
+ exp = fn _ => fn e => e,
+ bind = fn (env, U.Exp.RelC (x, k)) => E.pushCRel env x k
+ | (env, U.Exp.NamedC (x, n, k, co)) => E.pushCNamedAs env x n k co
+ | (env, _) => env}
+
+fun p_exp env e = P.p_exp env (simplExp env e)
+val p_pat = P.p_pat
+
+fun expError env err =
+ case err of
+ UnboundExp (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound expression variable " ^ s)
+ | UnboundStrInExp (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound structure " ^ s)
+ | Unify (e, c1, c2, env', uerr) =>
+ (ErrorMsg.errorAt (#2 e) "Unification failure";
+ eprefaces' [("Expression", p_exp env e),
+ ("Have con", p_con env c1),
+ ("Need con", p_con env c2)];
+ cunifyError env' uerr)
+ | Unif (action, loc, c) =>
+ (ErrorMsg.errorAt loc ("Unification variable blocks " ^ action);
+ eprefaces' [("Con", p_con env c)])
+ | WrongForm (variety, e, t) =>
+ (ErrorMsg.errorAt (#2 e) ("Expression is not a " ^ variety);
+ eprefaces' [("Expression", p_exp env e),
+ ("Type", p_con env t)])
+ | IncompatibleCons (c1, c2) =>
+ (ErrorMsg.errorAt (#2 c1) "Incompatible constructors";
+ eprefaces' [("Have", p_con env c1),
+ ("Need", p_con env c2)])
+ | DuplicatePatternVariable (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate pattern variable " ^ s)
+ | PatUnify (p, c1, c2, env', uerr) =>
+ (ErrorMsg.errorAt (#2 p) "Unification failure for pattern";
+ eprefaces' [("Pattern", p_pat env p),
+ ("Have con", p_con env c1),
+ ("Need con", p_con env c2)];
+ cunifyError env' uerr)
+ | UnboundConstructor (loc, ms, s) =>
+ ErrorMsg.errorAt loc ("Unbound constructor " ^ String.concatWith "." (ms @ [s]) ^ " in pattern")
+ | PatHasArg loc =>
+ ErrorMsg.errorAt loc "Constructor expects no argument but is used with argument"
+ | PatHasNoArg loc =>
+ ErrorMsg.errorAt loc "Constructor expects argument but is used with no argument"
+ | Inexhaustive (loc, p) =>
+ (ErrorMsg.errorAt loc "Inexhaustive 'case'";
+ eprefaces' [("Missed case", p_pat env p)])
+ | DuplicatePatField (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate record field " ^ s ^ " in pattern")
+ | OutOfContext (loc, co) =>
+ (ErrorMsg.errorAt loc "Type class wildcard occurs out of context";
+ Option.app (fn (e, c) => eprefaces' [("Function", p_exp env e),
+ ("Type", p_con env c)]) co)
+ | Unresolvable (loc, c) =>
+ (ErrorMsg.errorAt loc "Can't resolve type class instance";
+ eprefaces' ([("Class constraint", p_con env c)]
+ @ (case E.resolveFailureCause () of
+ NONE => []
+ | SOME c' => [("Reduced to unresolvable", p_con env c')]))(*;
+ app (fn (c, rs) => (eprefaces' [("CLASS", p_con env c)];
+ app (fn (c, e) => eprefaces' [("RULE", p_con env c),
+ ("IMPL", p_exp env e)]) rs))
+ (E.listClasses env)*))
+ | IllegalRec (x, e) =>
+ (ErrorMsg.errorAt (#2 e) "Illegal 'val rec' righthand side (must be a function abstraction)";
+ eprefaces' [("Variable", PD.string x),
+ ("Expression", p_exp env e)])
+ | IllegalFlex e =>
+ (ErrorMsg.errorAt (#2 e) "Flex record syntax (\"...\") only allowed in patterns";
+ eprefaces' [("Expression", SourcePrint.p_exp e)])
+
+
+datatype decl_error =
+ KunifsRemain of decl list
+ | CunifsRemain of decl list
+ | Nonpositive of decl
+
+fun lspan [] = ErrorMsg.dummySpan
+ | lspan ((_, loc) :: _) = loc
+
+val baseLen = 2000
+
+fun p_decl env d =
+ let
+ val fname = OS.FileSys.tmpName ()
+ val out' = TextIO.openOut fname
+ val out = Print.openOut {dst = out', wid = 80}
+
+ fun readFromFile () =
+ let
+ val inf = FileIO.txtOpenIn fname
+
+ fun loop acc =
+ case TextIO.inputLine inf of
+ NONE => String.concat (rev acc)
+ | SOME line => loop (line :: acc)
+ in
+ loop []
+ before TextIO.closeIn inf
+ end
+ in
+ Print.fprint out (P.p_decl env d);
+ TextIO.closeOut out';
+ let
+ val content = readFromFile ()
+ in
+ OS.FileSys.remove fname;
+ Print.PD.string (if size content <= baseLen then
+ content
+ else
+ let
+ val (befor, after) = Substring.position "<UNIF:" (Substring.full content)
+ in
+ if Substring.isEmpty after then
+ raise Fail "No unification variables in rendering"
+ else
+ Substring.concat [Substring.full "\n.....\n",
+ if Substring.size befor <= baseLen then
+ befor
+ else
+ Substring.slice (befor, Substring.size befor - baseLen, SOME baseLen),
+ if Substring.size after <= baseLen then
+ after
+ else
+ Substring.slice (after, 0, SOME baseLen),
+ Substring.full "\n.....\n"]
+ end)
+ end
+ end
+
+fun declError env err =
+ case err of
+ KunifsRemain ds =>
+ (ErrorMsg.errorAt (lspan ds) "Some kind unification variables are undetermined in declaration\n(look for them as \"<UNIF:...>\")";
+ eprefaces' [("Decl", p_list_sep PD.newline (p_decl env) ds)])
+ | CunifsRemain ds =>
+ (ErrorMsg.errorAt (lspan ds) "Some constructor unification variables are undetermined in declaration\n(look for them as \"<UNIF:...>\")";
+ eprefaces' [("Decl", p_list_sep PD.newline (p_decl env) ds)])
+ | Nonpositive d =>
+ (ErrorMsg.errorAt (#2 d) "Non-strictly-positive datatype declaration (could allow non-termination)";
+ eprefaces' [("Decl", p_decl env d)])
+
+datatype sgn_error =
+ UnboundSgn of ErrorMsg.span * string
+ | UnmatchedSgi of ErrorMsg.span * sgn_item
+ | SgiWrongKind of ErrorMsg.span * sgn_item * kind * sgn_item * kind * E.env * kunify_error
+ | SgiWrongCon of ErrorMsg.span * sgn_item * con * sgn_item * con * E.env * cunify_error
+ | SgiMismatchedDatatypes of ErrorMsg.span * sgn_item * sgn_item
+ * (con * con * E.env * cunify_error) option
+ | SgnWrongForm of ErrorMsg.span * sgn * sgn
+ | UnWhereable of sgn * string
+ | WhereWrongKind of kind * kind * E.env * kunify_error
+ | NotIncludable of sgn
+ | DuplicateCon of ErrorMsg.span * string
+ | DuplicateVal of ErrorMsg.span * string
+ | DuplicateSgn of ErrorMsg.span * string
+ | DuplicateStr of ErrorMsg.span * string
+ | NotConstraintsable of sgn
+
+val p_sgn_item = P.p_sgn_item
+val p_sgn = P.p_sgn
+
+fun sgnError env err =
+ case err of
+ UnboundSgn (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound signature variable " ^ s)
+ | UnmatchedSgi (loc, sgi) =>
+ (ErrorMsg.errorAt loc "Unmatched signature item";
+ eprefaces' [("Item", p_sgn_item env sgi)])
+ | SgiWrongKind (loc, sgi1, k1, sgi2, k2, env', kerr) =>
+ (ErrorMsg.errorAt loc "Kind unification failure in signature matching:";
+ eprefaces' [("Have", p_sgn_item env sgi1),
+ ("Need", p_sgn_item env sgi2),
+ ("Kind 1", p_kind env k1),
+ ("Kind 2", p_kind env k2)];
+ kunifyError env' kerr)
+ | SgiWrongCon (loc, sgi1, c1, sgi2, c2, env', cerr) =>
+ (ErrorMsg.errorAt loc "Constructor unification failure in signature matching:";
+ eprefaces' [("Have", p_sgn_item env sgi1),
+ ("Need", p_sgn_item env sgi2),
+ ("Con 1", p_con env c1),
+ ("Con 2", p_con env c2)];
+ cunifyError env' cerr)
+ | SgiMismatchedDatatypes (loc, sgi1, sgi2, cerro) =>
+ (ErrorMsg.errorAt loc "Mismatched 'datatype' specifications:";
+ eprefaces' [("Have", p_sgn_item env sgi1),
+ ("Need", p_sgn_item env sgi2)];
+ Option.app (fn (c1, c2, env', ue) =>
+ (eprefaces "Unification error"
+ [("Con 1", p_con env' c1),
+ ("Con 2", p_con env' c2)];
+ cunifyError env' ue)) cerro)
+ | SgnWrongForm (loc, sgn1, sgn2) =>
+ (ErrorMsg.errorAt loc "Incompatible signatures:";
+ eprefaces' [("Sig 1", p_sgn env sgn1),
+ ("Sig 2", p_sgn env sgn2)])
+ | UnWhereable (sgn, x) =>
+ (ErrorMsg.errorAt (#2 sgn) "Unavailable field for 'where'";
+ eprefaces' [("Signature", p_sgn env sgn),
+ ("Field", PD.string x)])
+ | WhereWrongKind (k1, k2, env', kerr) =>
+ (ErrorMsg.errorAt (#2 k1) "Wrong kind for 'where'";
+ eprefaces' [("Have", p_kind env k1),
+ ("Need", p_kind env k2)];
+ kunifyError env' kerr)
+ | NotIncludable sgn =>
+ (ErrorMsg.errorAt (#2 sgn) "Invalid signature to 'include'";
+ eprefaces' [("Signature", p_sgn env sgn)])
+ | DuplicateCon (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate constructor " ^ s ^ " in signature")
+ | DuplicateVal (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate value " ^ s ^ " in signature")
+ | DuplicateSgn (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate signature " ^ s ^ " in signature")
+ | DuplicateStr (loc, s) =>
+ ErrorMsg.errorAt loc ("Duplicate structure " ^ s ^ " in signature")
+ | NotConstraintsable sgn =>
+ (ErrorMsg.errorAt (#2 sgn) "Invalid signature for 'open constraints'";
+ eprefaces' [("Signature", p_sgn env sgn)])
+
+datatype str_error =
+ UnboundStr of ErrorMsg.span * string
+ | NotFunctor of sgn
+ | FunctorRebind of ErrorMsg.span
+ | UnOpenable of sgn
+ | NotType of ErrorMsg.span * kind * (kind * kind * E.env * kunify_error)
+ | DuplicateConstructor of string * ErrorMsg.span
+ | NotDatatype of ErrorMsg.span
+
+fun strError env err =
+ case err of
+ UnboundStr (loc, s) =>
+ ErrorMsg.errorAt loc ("Unbound structure variable " ^ s)
+ | NotFunctor sgn =>
+ (ErrorMsg.errorAt (#2 sgn) "Application of non-functor";
+ eprefaces' [("Signature", p_sgn env sgn)])
+ | FunctorRebind loc =>
+ ErrorMsg.errorAt loc "Attempt to rebind functor"
+ | UnOpenable sgn =>
+ (ErrorMsg.errorAt (#2 sgn) "Un-openable structure";
+ eprefaces' [("Signature", p_sgn env sgn)])
+ | NotType (loc, k, (k1, k2, env', ue)) =>
+ (ErrorMsg.errorAt loc "'val' type kind is not 'Type'";
+ eprefaces' [("Kind", p_kind env k),
+ ("Subkind 1", p_kind env k1),
+ ("Subkind 2", p_kind env k2)];
+ kunifyError env' ue)
+ | DuplicateConstructor (x, loc) =>
+ ErrorMsg.errorAt loc ("Duplicate datatype constructor " ^ x)
+ | NotDatatype loc =>
+ ErrorMsg.errorAt loc "Trying to import non-datatype as a datatype"
+
+end
diff --git a/src/elab_ops.sig b/src/elab_ops.sig
new file mode 100644
index 0000000..97e4b60
--- /dev/null
+++ b/src/elab_ops.sig
@@ -0,0 +1,50 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature ELAB_OPS = sig
+
+ exception SubUnif
+
+ val liftKindInKind : int -> Elab.kind -> Elab.kind
+ val subKindInKind : int * Elab.kind -> Elab.kind -> Elab.kind
+
+ val liftKindInCon : int -> Elab.con -> Elab.con
+ val subKindInCon : int * Elab.kind -> Elab.con -> Elab.con
+
+ val liftConInCon : int -> Elab.con -> Elab.con
+ val subConInCon : int * Elab.con -> Elab.con -> Elab.con
+ val subStrInSgn : int * int -> Elab.sgn -> Elab.sgn
+
+ val hnormCon : ElabEnv.env -> Elab.con -> Elab.con
+ val reduceCon : ElabEnv.env -> Elab.con -> Elab.con
+
+ val identity : int ref
+ val distribute : int ref
+ val fuse : int ref
+ val reset : unit -> unit
+
+end
diff --git a/src/elab_ops.sml b/src/elab_ops.sml
new file mode 100644
index 0000000..6ff5e03
--- /dev/null
+++ b/src/elab_ops.sml
@@ -0,0 +1,517 @@
+(* Copyright (c) 2008, 2012, 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 ElabOps :> ELAB_OPS = struct
+
+open Elab
+
+structure E = ElabEnv
+structure U = ElabUtil
+
+fun liftKindInKind' by =
+ U.Kind.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + by)
+ | _ => k,
+ bind = fn (bound, _) => bound + 1}
+
+fun subKindInKind' rep =
+ U.Kind.mapB {kind = fn (by, xn) => fn k =>
+ case k of
+ KRel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 (liftKindInKind' by 0 rep)
+ | GREATER => KRel (xn' - 1)
+ | LESS => k)
+ | _ => k,
+ bind = fn ((by, xn), _) => (by+1, xn+1)}
+
+val liftKindInKind = liftKindInKind' 1
+
+fun subKindInKind (xn, rep) = subKindInKind' rep (0, xn)
+
+fun liftKindInCon by =
+ U.Con.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + by)
+ | _ => k,
+ con = fn _ => fn c => c,
+ bind = fn (bound, U.Con.RelK _) => bound + 1
+ | (bound, _) => bound}
+
+fun subKindInCon' rep =
+ U.Con.mapB {kind = fn (by, xn) => fn k =>
+ case k of
+ KRel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 (liftKindInKind' by 0 rep)
+ | GREATER => KRel (xn' - 1)
+ | LESS => k)
+ | _ => k,
+ con = fn _ => fn c => c,
+ bind = fn ((by, xn), U.Con.RelK _) => (by+1, xn+1)
+ | (st, _) => st}
+
+val liftKindInCon = liftKindInCon 1
+
+fun subKindInCon (xn, rep) = subKindInCon' rep (0, xn)
+
+fun liftConInCon by =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + by)
+ | CUnif (nl, loc, k, s, r) => CUnif (nl+by, loc, k, s, r)
+ | _ => c,
+ bind = fn (bound, U.Con.RelC _) => bound + 1
+ | (bound, _) => bound}
+
+exception SubUnif
+
+fun subConInCon' rep =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn (by, xn) => fn c =>
+ case c of
+ CRel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 (liftConInCon by 0 rep)
+ | GREATER => CRel (xn' - 1)
+ | LESS => c)
+ | CUnif (0, _, _, _, _) => raise SubUnif
+ | CUnif (n, loc, k, s, r) => CUnif (n-1, loc, k, s, r)
+ | _ => c,
+ bind = fn ((by, xn), U.Con.RelC _) => (by+1, xn+1)
+ | (ctx, _) => ctx}
+
+val liftConInCon = liftConInCon 1
+
+fun subConInCon (xn, rep) = subConInCon' rep (0, xn)
+
+fun subStrInSgn (m1, m2) =
+ U.Sgn.map {kind = fn k => k,
+ con = fn c as CModProj (m1', ms, x) =>
+ if m1 = m1' then
+ CModProj (m2, ms, x)
+ else
+ c
+ | c => c,
+ sgn_item = fn sgi => sgi,
+ sgn = fn sgn => sgn}
+
+val occurs =
+ U.Con.existsB {kind = fn _ => false,
+ con = fn (n, c) =>
+ case c of
+ CRel n' => n' = n
+ | _ => false,
+ bind = fn (n, b) =>
+ case b of
+ U.Con.RelC _ => n + 1
+ | _ => n}
+ 0
+
+val identity = ref 0
+val distribute = ref 0
+val fuse = ref 0
+
+fun reset () = (identity := 0;
+ distribute := 0;
+ fuse := 0)
+
+fun hnormCon env (cAll as (c, loc)) =
+ case c of
+ CUnif (nl, _, _, _, ref (Known c)) => (#1 (hnormCon env (E.mliftConInCon nl c)), loc)
+
+ | CNamed xn =>
+ (case E.lookupCNamed env xn of
+ (_, _, SOME c') => hnormCon env c'
+ | _ => cAll)
+
+ | CModProj (n, ms, x) =>
+ let
+ val (_, sgn) = E.lookupStrNamed env n
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => raise Fail "hnormCon: Unknown substructure"
+ | SOME sgn => ((StrProj (str, m), loc), sgn))
+ ((StrVar n, loc), sgn) ms
+ in
+ case E.projectCon env {sgn = sgn, str = str, field = x} of
+ NONE => raise Fail "kindof: Unknown con in structure"
+ | SOME (_, NONE) => cAll
+ | SOME (_, SOME c) => hnormCon env c
+ end
+
+ (* Eta reduction *)
+ | CAbs (x, k, b) =>
+ (case #1 (hnormCon (E.pushCRel env x k) b) of
+ CApp (f, (CRel 0, _)) =>
+ if occurs f then
+ cAll
+ else
+ hnormCon env (subConInCon (0, (CUnit, loc)) f)
+ | _ => cAll)
+
+ | CApp (c1, c2) =>
+ (case #1 (hnormCon env c1) of
+ CAbs (x, k, cb) =>
+ let
+ val sc = (hnormCon env (subConInCon (0, c2) cb))
+ handle SynUnif => cAll
+ (*val env' = E.pushCRel env x k*)
+ in
+ (*Print.eprefaces "Subst" [("x", Print.PD.string x),
+ ("cb", ElabPrint.p_con env' cb),
+ ("c2", ElabPrint.p_con env c2),
+ ("sc", ElabPrint.p_con env sc)];*)
+ sc
+ end
+ | c1' as CApp (c', f) =>
+ let
+ fun default () = (CApp ((c1', loc), hnormCon env c2), loc)
+ in
+ case #1 (hnormCon env c') of
+ CMap (ks as (k1, k2)) =>
+ (case #1 (hnormCon env c2) of
+ CRecord (_, []) => (CRecord (k2, []), loc)
+ | CRecord (_, (x, c) :: rest) =>
+ hnormCon env
+ (CConcat ((CRecord (k2, [(x, (CApp (f, c), loc))]), loc),
+ (CApp (c1, (CRecord (k2, rest), loc)), loc)), loc)
+ | CConcat ((CRecord (k, (x, c) :: rest), _), rest') =>
+ let
+ val rest'' = (CConcat ((CRecord (k, rest), loc), rest'), loc)
+ in
+ hnormCon env
+ (CConcat ((CRecord (k2, [(x, (CApp (f, c), loc))]), loc),
+ (CApp (c1, rest''), loc)), loc)
+ end
+ | _ =>
+ let
+ fun unconstraint c =
+ case hnormCon env c of
+ (TDisjoint (_, _, c), _) => unconstraint c
+ | c => c
+
+ fun inc r = r := !r + 1
+
+ fun tryDistributivity () =
+ case hnormCon env c2 of
+ (CConcat (c1, c2'), _) =>
+ let
+ val c = (CMap ks, loc)
+ val c = (CApp (c, f), loc)
+
+ val c1 = (CApp (c, c1), loc)
+ val c2 = (CApp (c, c2'), loc)
+ val c = (CConcat (c1, c2), loc)
+ in
+ inc distribute;
+ hnormCon env c
+ end
+ | _ => default ()
+
+ fun tryFusion () =
+ case #1 (hnormCon env c2) of
+ CApp (f', r') =>
+ (case #1 (hnormCon env f') of
+ CApp (f', inner_f) =>
+ (case #1 (hnormCon env f') of
+ CMap (dom, _) =>
+ let
+ val inner_f = liftConInCon 0 inner_f
+ val f = liftConInCon 0 f
+
+ val f' = (CApp (inner_f, (CRel 0, loc)), loc)
+ val f' = (CApp (f, f'), loc)
+ val f' = (CAbs ("v", dom, f'), loc)
+
+ val c = (CMap (dom, k2), loc)
+ val c = (CApp (c, f'), loc)
+ val c = (CApp (c, r'), loc)
+ in
+ inc fuse;
+ hnormCon env c
+ end
+ | _ => tryDistributivity ())
+ | _ => tryDistributivity ())
+ | _ => tryDistributivity ()
+
+ fun tryIdentity () =
+ let
+ fun cunif () =
+ let
+ val r = ref (Unknown (fn _ => true))
+ in
+ (r, (CUnif (0, loc, (KType, loc), "_", r), loc))
+ end
+
+ val (vR, v) = cunif ()
+
+ val c = (CApp (f, v), loc)
+ in
+ case unconstraint c of
+ (CUnif (_, _, _, _, vR'), _) =>
+ if vR' = vR then
+ (inc identity;
+ hnormCon env c2)
+ else
+ tryFusion ()
+ | _ => tryFusion ()
+ end
+ in
+ tryIdentity ()
+ end)
+ | _ => default ()
+ end
+ | c1' => (CApp ((c1', loc), hnormCon env c2), loc))
+
+ | CKApp (c1, k) =>
+ (case hnormCon env c1 of
+ (CKAbs (_, body), _) => hnormCon env (subKindInCon (0, k) body)
+ | _ => cAll)
+
+ | CConcat (c1, c2) =>
+ (case (hnormCon env c1, hnormCon env c2) of
+ ((CRecord (k, xcs1), loc), (CRecord (_, xcs2), _)) =>
+ (CRecord (k, xcs1 @ xcs2), loc)
+ | ((CRecord (_, []), _), c2') => c2'
+ | ((CConcat (c11, c12), loc), c2') =>
+ hnormCon env (CConcat (c11, (CConcat (c12, c2'), loc)), loc)
+ | (c1', (CRecord (_, []), _)) => c1'
+ | (c1', c2') => (CConcat (c1', c2'), loc))
+
+ | CProj (c, n) =>
+ (case hnormCon env c of
+ (CTuple cs, _) => hnormCon env (List.nth (cs, n - 1))
+ | _ => cAll)
+
+ | _ => cAll
+
+fun reduceCon env (cAll as (c, loc)) =
+ case c of
+ TFun (c1, c2) => (TFun (reduceCon env c1, reduceCon env c2), loc)
+ | TCFun (exp, x, k, c) => (TCFun (exp, x, k, reduceCon env c), loc)
+ | TRecord c => (TRecord (reduceCon env c), loc)
+ | TDisjoint (c1, c2, c3) => (TDisjoint (reduceCon env c1, reduceCon env c2, reduceCon env c3), loc)
+
+ | CRel _ => cAll
+ | CNamed xn =>
+ (case E.lookupCNamed env xn of
+ (_, _, SOME c') => reduceCon env c'
+ | _ => cAll)
+ | CModProj (n, ms, x) =>
+ let
+ val (_, sgn) = E.lookupStrNamed env n
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => raise Fail "reduceCon: Unknown substructure"
+ | SOME sgn => ((StrProj (str, m), loc), sgn))
+ ((StrVar n, loc), sgn) ms
+ in
+ case E.projectCon env {sgn = sgn, str = str, field = x} of
+ NONE => raise Fail "reduceCon: kindof: Unknown con in structure"
+ | SOME (_, NONE) => cAll
+ | SOME (_, SOME c) => reduceCon env c
+ end
+
+ | CApp (c1, c2) =>
+ let
+ val c1 = reduceCon env c1
+ val c2 = reduceCon env c2
+ fun default () = (CApp (c1, c2), loc)
+ in
+ case #1 c1 of
+ CAbs (x, k, cb) =>
+ ((reduceCon env (subConInCon (0, c2) cb))
+ handle SynUnif => default ())
+ | CApp (c', f) =>
+ let
+ val c' = reduceCon env c'
+ val f = reduceCon env f
+ in
+ case #1 c' of
+ CMap (ks as (k1, k2)) =>
+ (case #1 c2 of
+ CRecord (_, []) => (CRecord (k2, []), loc)
+ | CRecord (_, (x, c) :: rest) =>
+ reduceCon env
+ (CConcat ((CRecord (k2, [(x, (CApp (f, c), loc))]), loc),
+ (CApp (c1, (CRecord (k2, rest), loc)), loc)), loc)
+ | CConcat ((CRecord (k, (x, c) :: rest), _), rest') =>
+ let
+ val rest'' = (CConcat ((CRecord (k, rest), loc), rest'), loc)
+ in
+ reduceCon env
+ (CConcat ((CRecord (k2, [(x, (CApp (f, c), loc))]), loc),
+ (CApp (c1, rest''), loc)), loc)
+ end
+ | _ =>
+ let
+ fun unconstraint c =
+ case reduceCon env c of
+ (TDisjoint (_, _, c), _) => unconstraint c
+ | c => c
+
+ fun inc r = r := !r + 1
+
+ fun tryDistributivity () =
+ case reduceCon env c2 of
+ (CConcat (c1, c2), _) =>
+ let
+ val c = (CMap ks, loc)
+ val c = (CApp (c, f), loc)
+
+ val c1 = (CApp (c, c1), loc)
+ val c2 = (CApp (c, c2), loc)
+ val c = (CConcat (c1, c2), loc)
+ in
+ inc distribute;
+ reduceCon env c
+ end
+ | _ => default ()
+
+ fun tryFusion () =
+ case #1 (reduceCon env c2) of
+ CApp (f', r') =>
+ (case #1 (reduceCon env f') of
+ CApp (f', inner_f) =>
+ (case #1 (reduceCon env f') of
+ CMap (dom, _) =>
+ let
+ val inner_f = liftConInCon 0 inner_f
+ val f = liftConInCon 0 f
+
+ val f' = (CApp (inner_f, (CRel 0, loc)), loc)
+ val f' = (CApp (f, f'), loc)
+ val f' = (CAbs ("v", dom, f'), loc)
+
+ val c = (CMap (dom, k2), loc)
+ val c = (CApp (c, f'), loc)
+ val c = (CApp (c, r'), loc)
+ in
+ inc fuse;
+ reduceCon env c
+ end
+ | _ => tryDistributivity ())
+ | _ => tryDistributivity ())
+ | _ => tryDistributivity ()
+
+ fun tryIdentity () =
+ let
+ fun cunif () =
+ let
+ val r = ref (Unknown (fn _ => true))
+ in
+ (r, (CUnif (0, loc, (KType, loc), "_", r), loc))
+ end
+
+ val (vR, v) = cunif ()
+
+ val c = (CApp (f, v), loc)
+ in
+ case unconstraint c of
+ (CUnif (_, _, _, _, vR'), _) =>
+ if vR' = vR then
+ (inc identity;
+ reduceCon env c2)
+ else
+ tryFusion ()
+ | _ => tryFusion ()
+ end
+ in
+ tryIdentity ()
+ end)
+ | _ => default ()
+ end
+ | _ => default ()
+ end
+ | CAbs (x, k, b) =>
+ let
+ val b = reduceCon (E.pushCRel env x k) b
+ fun default () = (CAbs (x, k, b), loc)
+ in
+ case #1 b of
+ CApp (f, (CRel 0, _)) =>
+ if occurs f then
+ default ()
+ else
+ reduceCon env (subConInCon (0, (CUnit, loc)) f)
+ | _ => default ()
+ end
+
+ | CKAbs (x, b) => (CKAbs (x, reduceCon (E.pushKRel env x) b), loc)
+ | CKApp (c1, k) =>
+ (case reduceCon env c1 of
+ (CKAbs (_, body), _) => reduceCon env (subKindInCon (0, k) body)
+ | c1 => (CKApp (c1, k), loc))
+ | TKFun (x, c) => (TKFun (x, reduceCon env c), loc)
+
+ | CName _ => cAll
+
+ | CRecord (k, xcs) => (CRecord (k, map (fn (x, c) => (reduceCon env x, reduceCon env c)) xcs), loc)
+ | CConcat (c1, c2) =>
+ let
+ val c1 = reduceCon env c1
+ val c2 = reduceCon env c2
+ in
+ case (c1, c2) of
+ ((CRecord (k, xcs1), loc), (CRecord (_, xcs2), _)) => (CRecord (k, xcs1 @ xcs2), loc)
+ | ((CRecord (_, []), _), _) => c2
+ | ((CConcat (c11, c12), loc), _) => reduceCon env (CConcat (c11, (CConcat (c12, c2), loc)), loc)
+ | (_, (CRecord (_, []), _)) => c1
+ | ((CRecord (k, xcs1), loc), (CConcat ((CRecord (_, xcs2), _), c2'), _)) => (CConcat ((CRecord (k, xcs1 @ xcs2), loc), c2'), loc)
+ | _ => (CConcat (c1, c2), loc)
+ end
+ | CMap _ => cAll
+
+ | CUnit => cAll
+
+ | CTuple cs => (CTuple (map (reduceCon env) cs), loc)
+ | CProj (c, n) =>
+ (case reduceCon env c of
+ (CTuple cs, _) => reduceCon env (List.nth (cs, n - 1))
+ | c => (CProj (c, n), loc))
+
+ | CError => cAll
+
+ | CUnif (nl, _, _, _, ref (Known c)) => reduceCon env (E.mliftConInCon nl c)
+ | CUnif _ => cAll
+
+end
diff --git a/src/elab_print.sig b/src/elab_print.sig
new file mode 100644
index 0000000..1eb832b
--- /dev/null
+++ b/src/elab_print.sig
@@ -0,0 +1,44 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing Ur/Web *)
+
+signature ELAB_PRINT = sig
+ val p_kind : ElabEnv.env -> Elab.kind Print.printer
+ val p_explicitness : Elab.explicitness Print.printer
+ val p_con : ElabEnv.env -> Elab.con Print.printer
+ val p_pat : ElabEnv.env -> Elab.pat Print.printer
+ val p_exp : ElabEnv.env -> Elab.exp Print.printer
+ val p_decl : ElabEnv.env -> Elab.decl Print.printer
+ val p_sgn_item : ElabEnv.env -> Elab.sgn_item Print.printer
+ val p_sgn : ElabEnv.env -> Elab.sgn Print.printer
+ val p_str : ElabEnv.env -> Elab.str Print.printer
+ val p_file : ElabEnv.env -> Elab.file Print.printer
+
+ val debug : bool ref
+end
+
diff --git a/src/elab_print.sml b/src/elab_print.sml
new file mode 100644
index 0000000..8a6a651
--- /dev/null
+++ b/src/elab_print.sml
@@ -0,0 +1,906 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing elaborated Ur/Web *)
+
+structure ElabPrint :> ELAB_PRINT = struct
+
+open Print.PD
+open Print
+
+open Elab
+
+structure E = ElabEnv
+
+val debug = ref false
+
+fun p_kind' par env (k, _) =
+ case k of
+ KType => string "Type"
+ | KArrow (k1, k2) => parenIf par (box [p_kind' true env k1,
+ space,
+ string "->",
+ space,
+ p_kind env k2])
+ | KName => string "Name"
+ | KRecord k => box [string "{", p_kind env k, string "}"]
+ | KUnit => string "Unit"
+ | KTuple ks => box [string "(",
+ p_list_sep (box [space, string "*", space]) (p_kind env) ks,
+ string ")"]
+
+ | KError => string "<ERROR>"
+ | KUnif (_, _, ref (KKnown k)) => p_kind' par env k
+ | KUnif (_, s, _) => string ("<UNIF:" ^ s ^ ">")
+ | KTupleUnif (_, _, ref (KKnown k)) => p_kind' par env k
+ | KTupleUnif (_, nks, _) => box [string "(",
+ p_list_sep (box [space, string "*", space])
+ (fn (n, k) => box [string (Int.toString n ^ ":"),
+ space,
+ p_kind env k]) nks,
+ space,
+ string "*",
+ space,
+ string "...)"]
+
+ | KRel n => ((if !debug then
+ string (E.lookupKRel env n ^ "_" ^ Int.toString n)
+ else
+ string (E.lookupKRel env n))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | KFun (x, k) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_kind (E.pushKRel env x) k]
+
+and p_kind env = p_kind' false env
+
+fun p_explicitness e =
+ case e of
+ Explicit => string "::"
+ | Implicit => string ":::"
+
+fun p_con' par env (c, _) =
+ case c of
+ TFun (t1, t2) => parenIf par (box [p_con' true env t1,
+ space,
+ string "->",
+ space,
+ p_con env t2])
+ | TCFun (e, x, k, c) => parenIf par (box [string x,
+ space,
+ p_explicitness e,
+ space,
+ p_kind env k,
+ space,
+ string "->",
+ space,
+ p_con (E.pushCRel env x k) c])
+ | TDisjoint (c1, c2, c3) => parenIf par (box [string "[",
+ p_con env c1,
+ space,
+ string "~",
+ space,
+ p_con env c2,
+ string "]",
+ space,
+ string "=>",
+ space,
+ p_con env c3])
+ | TRecord (CRecord (_, xcs), _) =>
+ let
+ fun isTuple (n, xcs) =
+ case xcs of
+ [] => n > 2
+ | ((CName s, _), _) :: xcs' =>
+ s = Int.toString n andalso isTuple (n+1, xcs')
+ | _ => false
+ in
+ if isTuple (1, xcs) then
+ case xcs of
+ (_, c) :: xcs =>
+ parenIf par (box [p_con' true env c,
+ p_list_sep (box []) (fn (_, c) => box [space,
+ string "*",
+ space,
+ p_con' true env c]) xcs])
+ | _ => raise Fail "ElabPrint: surprise empty tuple"
+ else
+ box [string "{",
+ p_list (fn (x, c) =>
+ box [p_name env x,
+ space,
+ string ":",
+ space,
+ p_con env c]) xcs,
+ string "}"]
+ end
+ | TRecord c => box [string "$",
+ p_con' true env c]
+
+ | CRel n =>
+ ((if !debug then
+ string (#1 (E.lookupCRel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupCRel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | CNamed n =>
+ ((if !debug then
+ string (#1 (E.lookupCNamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupCNamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_NAMED" ^ Int.toString n))
+ | CModProj (m1, ms, x) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ if m1x = "Basis" andalso (case E.lookupC env x of
+ E.Named (n, _) =>
+ let
+ val (_, _, co) = E.lookupCNamed env n
+ in
+ case co of
+ SOME (CModProj (m1', [], x'), _) => m1' = m1 andalso x' = x
+ | _ => false
+ end
+ | E.NotBound => true
+ | _ => false) then
+ string x
+ else
+ p_list_sep (string ".") string (m1s :: ms @ [x])
+ end
+
+ | CApp (c1, c2) => parenIf par (box [p_con env c1,
+ space,
+ p_con' true env c2])
+ | CAbs (x, k, c) => parenIf true (box [string "fn",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=>",
+ space,
+ p_con (E.pushCRel env x k) c])
+
+ | CName s => box [string "#", string s]
+
+ | CRecord (k, xcs) =>
+ if !debug then
+ parenIf par (box [string "[",
+ p_list (fn (x, c) =>
+ box [p_name env x,
+ space,
+ string "=",
+ space,
+ p_con env c]) xcs,
+ string "]::",
+ p_kind env k])
+ else
+ parenIf par (box [string "[",
+ p_list (fn (x, c) =>
+ box [p_name env x,
+ space,
+ string "=",
+ space,
+ p_con env c]) xcs,
+ string "]"])
+ | CConcat (c1, c2) => parenIf par (box [p_con' true env c1,
+ space,
+ string "++",
+ space,
+ p_con env c2])
+ | CMap _ => string "map"
+
+ | CUnit => string "()"
+
+ | CTuple cs => box [string "(",
+ p_list (p_con env) cs,
+ string ")"]
+ | CProj (c, n) => box [p_con env c,
+ string ".",
+ string (Int.toString n)]
+
+ | CError => string "<ERROR>"
+ | CUnif (nl, _, _, _, ref (Known c)) => p_con' par env (E.mliftConInCon nl c)
+ | CUnif (nl, _, k, s, _) => box [string ("<UNIF:" ^ s ^ "::"),
+ p_kind env k,
+ case nl of
+ 0 => box []
+ | _ => string ("+" ^ Int.toString nl),
+ string ">"]
+
+ | CKAbs (x, c) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_con (E.pushKRel env x) c]
+ | CKApp (c, k) => box [p_con env c,
+ string "[[",
+ p_kind env k,
+ string "]]"]
+ | TKFun (x, c) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_con (E.pushKRel env x) c]
+
+
+and p_con env = p_con' false env
+
+and p_name env (all as (c, _)) =
+ case c of
+ CName s => string s
+ | _ => p_con env all
+
+fun p_patCon env pc =
+ case pc of
+ PConVar n =>
+ ((if !debug then
+ string (#1 (E.lookupENamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupENamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_NAMED" ^ Int.toString n))
+ | PConProj (m1, ms, x) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1x :: ms @ [x])
+ end
+
+fun p_pat' par env (p, _) =
+ case p of
+ PVar (s, _) => string s
+ | PPrim p => Prim.p_t p
+ | PCon (_, pc, _, NONE) => p_patCon env pc
+ | PCon (_, pc, _, SOME p) => parenIf par (box [p_patCon env pc,
+ space,
+ p_pat' true env p])
+ | PRecord xps =>
+ box [string "{",
+ p_list_sep (box [string ",", space]) (fn (x, p, t) =>
+ box [string x,
+ space,
+ string "=",
+ space,
+ p_pat env p,
+ if !debug then
+ box [space,
+ string ":",
+ space,
+ p_con env t]
+ else
+ box []]) xps,
+ string "}"]
+
+and p_pat x = p_pat' false x
+
+fun p_exp' par env (e, _) =
+ case e of
+ EPrim p => Prim.p_t p
+ | ERel n =>
+ ((if !debug then
+ string (#1 (E.lookupERel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupERel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | ENamed n =>
+ ((if !debug then
+ string (#1 (E.lookupENamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupENamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_NAMED" ^ Int.toString n))
+ | EModProj (m1, ms, x) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1x :: ms @ [x])
+ end
+
+ | EApp (e1, e2) => parenIf par (box [p_exp env e1,
+ space,
+ p_exp' true env e2])
+ | EAbs (x, t, _, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushERel env x t) e])
+ | ECApp (e, c) => parenIf par (box [p_exp env e,
+ space,
+ string "[",
+ p_con env c,
+ string "]"])
+ | ECAbs (exp, x, k, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ p_explicitness exp,
+ space,
+ p_kind env k,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushCRel env x k) e])
+
+ | ERecord xes => box [string "{",
+ p_list (fn (x, e, _) =>
+ box [p_name env x,
+ space,
+ string "=",
+ space,
+ p_exp env e]) xes,
+ string "}"]
+ | EField (e, c, {field, rest}) =>
+ if !debug then
+ box [p_exp' true env e,
+ string ".",
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env field,
+ space,
+ string " in ",
+ space,
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ string ".",
+ p_con' true env c]
+ | EConcat (e1, c1, e2, c2) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e1,
+ space,
+ string ":",
+ space,
+ p_con env c1,
+ space,
+ string "++",
+ space,
+ p_exp' true env e2,
+ space,
+ string ":",
+ space,
+ p_con env c2]
+ else
+ box [p_exp' true env e1,
+ space,
+ string "++",
+ space,
+ p_exp' true env e2])
+ | ECut (e, c, {field, rest}) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e,
+ space,
+ string "--",
+ space,
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env field,
+ space,
+ string " in ",
+ space,
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ space,
+ string "--",
+ space,
+ p_con' true env c])
+ | ECutMulti (e, c, {rest}) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e,
+ space,
+ string "---",
+ space,
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ space,
+ string "---",
+ space,
+ p_con' true env c])
+
+ | ECase (e, pes, _) => parenIf par (box [string "case",
+ space,
+ p_exp env e,
+ space,
+ string "of",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (p, e) => box [p_pat env p,
+ space,
+ string "=>",
+ space,
+ p_exp (E.patBinds env p) e]) pes])
+
+ | EError => string "<ERROR>"
+ | EUnif (ref (SOME e)) => p_exp env e
+ | EUnif _ => string "_"
+
+ | ELet (ds, e, _) =>
+ let
+ val (dsp, env) = ListUtil.foldlMap
+ (fn (d, env) =>
+ (p_edecl env d,
+ E.edeclBinds env d))
+ env ds
+ in
+ box [string "let",
+ newline,
+ box [p_list_sep newline (fn x => x) dsp],
+ newline,
+ string "in",
+ newline,
+ box [p_exp env e],
+ newline,
+ string "end"]
+ end
+
+ | EKAbs (x, e) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_exp (E.pushKRel env x) e]
+ | EKApp (e, k) => box [p_exp env e,
+ string "[[",
+ p_kind env k,
+ string "]]"]
+
+and p_exp env = p_exp' false env
+
+and p_edecl env (dAll as (d, _)) =
+ case d of
+ EDVal (p, t, e) => box [string "val",
+ space,
+ p_pat env p,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e]
+ | EDValRec vis =>
+ let
+ val env = E.edeclBinds env dAll
+ in
+ box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) (p_evali env) vis]
+ end
+
+and p_evali env (x, t, e) = box [string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e]
+
+fun p_datatype env (x, n, xs, cons) =
+ let
+ val k = (KType, ErrorMsg.dummySpan)
+ val env = E.pushCNamedAs env x n k NONE
+ val env = foldl (fn (x, env) => E.pushCRel env x k) env xs
+ in
+ box [string x,
+ p_list_sep (box []) (fn x => box [space, string x]) xs,
+ space,
+ string "=",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (x, _, NONE) => string x
+ | (x, _, SOME t) => box [string x, space, string "of", space, p_con env t])
+ cons]
+ end
+
+fun p_named x n =
+ if !debug then
+ box [string x,
+ string "__",
+ string (Int.toString n)]
+ else
+ string x
+
+fun p_sgn_item env (sgiAll as (sgi, _)) =
+ case sgi of
+ SgiConAbs (x, n, k) => box [string "con",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k]
+ | SgiCon (x, n, k, c) => box [string "con",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=",
+ space,
+ p_con env c]
+ | SgiDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) (p_datatype (E.sgiBinds env sgiAll)) x]
+ | SgiDatatypeImp (x, _, m1, ms, x', _, _) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+ in
+ box [string "datatype",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ string "datatype",
+ space,
+ p_list_sep (string ".") string (m1x :: ms @ [x'])]
+ end
+ | SgiVal (x, n, c) => box [string "val",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env c]
+ | SgiStr (_, x, n, sgn) => box [string "structure",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn]
+ | SgiSgn (x, n, sgn) => box [string "signature",
+ space,
+ p_named x n,
+ space,
+ string "=",
+ space,
+ p_sgn env sgn]
+ | SgiConstraint (c1, c2) => box [string "constraint",
+ space,
+ p_con env c1,
+ space,
+ string "~",
+ space,
+ p_con env c2]
+ | SgiClassAbs (x, n, k) => box [string "class",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k]
+ | SgiClass (x, n, k, c) => box [string "class",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=",
+ space,
+ p_con env c]
+
+and p_sgn env (sgn, _) =
+ case sgn of
+ SgnConst sgis => box [string "sig",
+ newline,
+ let
+ val (psgis, _) = ListUtil.foldlMap (fn (sgi, env) =>
+ (p_sgn_item env sgi,
+ E.sgiBinds env sgi))
+ env sgis
+ in
+ p_list_sep newline (fn x => x) psgis
+ end,
+ newline,
+ string "end"]
+ | SgnVar n => ((string (#1 (E.lookupSgnNamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_SGN_" ^ Int.toString n))
+ | SgnFun (x, n, sgn, sgn') => box [string "functor",
+ space,
+ string "(",
+ string x,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn,
+ string ")",
+ space,
+ string ":",
+ space,
+ p_sgn (E.pushStrNamedAs' false env x n sgn) sgn']
+ | SgnWhere (sgn, ms, x, c) => box [p_sgn env sgn,
+ space,
+ string "where",
+ space,
+ string "con",
+ space,
+ p_list_sep (string ".") string (ms @ [x]),
+ space,
+ string "=",
+ space,
+ p_con env c]
+ | SgnProj (m1, ms, x) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_SGN_" ^ Int.toString m1
+
+ val m1x = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1x :: ms @ [x])
+ end
+ | SgnError => string "<ERROR>"
+
+fun p_vali env (x, n, t, e) = box [p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e]
+
+
+
+fun p_decl env (dAll as (d, _) : decl) =
+ case d of
+ DCon (x, n, k, c) => box [string "con",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=",
+ space,
+ p_con env c]
+ | DDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) (p_datatype (E.declBinds env dAll)) x]
+ | DDatatypeImp (x, _, m1, ms, x', _, _) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+ in
+ box [string "datatype",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ string "datatype",
+ space,
+ p_list_sep (string ".") string (m1x :: ms @ [x'])]
+ end
+ | DVal vi => box [string "val",
+ space,
+ p_vali env vi]
+ | DValRec vis =>
+ let
+ val env = E.declBinds env dAll
+ in
+ box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) (p_vali env) vis]
+ end
+
+ | DSgn (x, n, sgn) => box [string "signature",
+ space,
+ p_named x n,
+ space,
+ string "=",
+ space,
+ p_sgn env sgn]
+ | DStr (x, n, sgn, str) => box [string "structure",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn,
+ space,
+ string "=",
+ space,
+ p_str env str]
+ | DFfiStr (x, n, sgn) => box [string "extern",
+ space,
+ string "structure",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn]
+ | DConstraint (c1, c2) => box [string "constraint",
+ space,
+ p_con env c1,
+ space,
+ string "~",
+ space,
+ p_con env c2]
+ | DExport (_, sgn, str) => box [string "export",
+ space,
+ p_str env str,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn]
+ | DTable (_, x, n, c, pe, _, ce, _) => box [string "table",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env c,
+ space,
+ string "keys",
+ space,
+ p_exp env pe,
+ space,
+ string "constraints",
+ space,
+ p_exp env ce]
+ | DSequence (_, x, n) => box [string "sequence",
+ space,
+ p_named x n]
+ | DView (_, x, n, e, _) => box [string "view",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ p_exp env e]
+ | DDatabase s => box [string "database",
+ space,
+ string s]
+ | DCookie (_, x, n, c) => box [string "cookie",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env c]
+ | DStyle (_, x, n) => box [string "style",
+ space,
+ p_named x n]
+ | DTask (e1, e2) => box [string "task",
+ space,
+ p_exp env e1,
+ space,
+ string "=",
+ space,
+ p_exp env e2]
+ | DPolicy e1 => box [string "policy",
+ space,
+ p_exp env e1]
+ | DOnError _ => string "ONERROR"
+ | DFfi _ => string "FFI"
+
+and p_str env (str, _) =
+ case str of
+ StrConst ds => box [string "struct",
+ newline,
+ p_file env ds,
+ newline,
+ string "end"]
+ | StrVar n => ((string (#1 (E.lookupStrNamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_STR_" ^ Int.toString n))
+ | StrProj (str, s) => box [p_str env str,
+ string ".",
+ string s]
+ | StrFun (x, n, sgn, sgn', str) =>
+ let
+ val env' = E.pushStrNamedAs' false env x n sgn
+ in
+ box [string "functor",
+ space,
+ string "(",
+ string x,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn,
+ string ")",
+ space,
+ string ":",
+ space,
+ p_sgn env' sgn',
+ space,
+ string "=>",
+ space,
+ p_str env' str]
+ end
+ | StrApp (str1, str2) => box [p_str env str1,
+ string "(",
+ p_str env str2,
+ string ")"]
+ | StrError => string "<ERROR>"
+
+and p_file env file =
+ let
+ val (pds, _) = ListUtil.foldlMap (fn (d, env) =>
+ (p_decl env d,
+ E.declBinds env d))
+ env file
+ in
+ p_list_sep newline (fn x => x) pds
+ end
+
+end
diff --git a/src/elab_util.sig b/src/elab_util.sig
new file mode 100644
index 0000000..dc07f6f
--- /dev/null
+++ b/src/elab_util.sig
@@ -0,0 +1,257 @@
+(* Copyright (c) 2008-2010, 2012, 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.
+ *)
+
+signature ELAB_UTIL = sig
+
+val classifyDatatype : (string * int * 'a option) list -> Elab.datatype_kind
+
+val mliftConInCon : (int -> Elab.con -> Elab.con) ref
+
+structure Kind : sig
+ val mapfoldB : {kind : ('context, Elab.kind', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * string -> 'context}
+ -> ('context, Elab.kind, 'state, 'abort) Search.mapfolderB
+ val mapfold : (Elab.kind', 'state, 'abort) Search.mapfolder
+ -> (Elab.kind, 'state, 'abort) Search.mapfolder
+ val exists : (Elab.kind' -> bool) -> Elab.kind -> bool
+ val mapB : {kind : 'context -> Elab.kind' -> Elab.kind',
+ bind : 'context * string -> 'context}
+ -> 'context -> (Elab.kind -> Elab.kind)
+ val foldB : {kind : 'context * Elab.kind' * 'state -> 'state,
+ bind : 'context * string -> 'context}
+ -> 'context -> 'state -> Elab.kind -> 'state
+end
+
+structure Con : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+
+ val mapfoldB : {kind : ('context, Elab.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Elab.con', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Elab.con, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Elab.kind', 'state, 'abort) Search.mapfolder,
+ con : (Elab.con', 'state, 'abort) Search.mapfolder}
+ -> (Elab.con, 'state, 'abort) Search.mapfolder
+
+ val mapB : {kind : 'context -> Elab.kind' -> Elab.kind',
+ con : 'context -> Elab.con' -> Elab.con',
+ bind : 'context * binder -> 'context}
+ -> 'context -> (Elab.con -> Elab.con)
+ val map : {kind : Elab.kind' -> Elab.kind',
+ con : Elab.con' -> Elab.con'}
+ -> Elab.con -> Elab.con
+ val appB : {kind : 'context -> Elab.kind' -> unit,
+ con : 'context -> Elab.con' -> unit,
+ bind : 'context * binder -> 'context}
+ -> 'context -> (Elab.con -> unit)
+ val app : {kind : Elab.kind' -> unit,
+ con : Elab.con' -> unit}
+ -> Elab.con -> unit
+ val existsB : {kind : 'context * Elab.kind' -> bool,
+ con : 'context * Elab.con' -> bool,
+ bind : 'context * binder -> 'context}
+ -> 'context -> Elab.con -> bool
+ val exists : {kind : Elab.kind' -> bool,
+ con : Elab.con' -> bool} -> Elab.con -> bool
+
+ val foldB : {kind : 'context * Elab.kind' * 'state -> 'state,
+ con : 'context * Elab.con' * 'state -> 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Elab.con -> 'state
+ val fold : {kind : Elab.kind' * 'state -> 'state,
+ con : Elab.con' * 'state -> 'state}
+ -> 'state -> Elab.con -> 'state
+end
+
+structure Exp : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+ | RelE of string * Elab.con
+ | NamedE of string * Elab.con
+
+ val mapfoldB : {kind : ('context, Elab.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Elab.con', 'state, 'abort) Search.mapfolderB,
+ exp : ('context, Elab.exp', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Elab.exp, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Elab.kind', 'state, 'abort) Search.mapfolder,
+ con : (Elab.con', 'state, 'abort) Search.mapfolder,
+ exp : (Elab.exp', 'state, 'abort) Search.mapfolder}
+ -> (Elab.exp, 'state, 'abort) Search.mapfolder
+ val mapB : {kind : 'context -> Elab.kind' -> Elab.kind',
+ con : 'context -> Elab.con' -> Elab.con',
+ exp : 'context -> Elab.exp' -> Elab.exp',
+ bind : 'context * binder -> 'context}
+ -> 'context -> (Elab.exp -> Elab.exp)
+ val exists : {kind : Elab.kind' -> bool,
+ con : Elab.con' -> bool,
+ exp : Elab.exp' -> bool} -> Elab.exp -> bool
+ val existsB : {kind : 'context * Elab.kind' -> bool,
+ con : 'context * Elab.con' -> bool,
+ exp : 'context * Elab.exp' -> bool,
+ bind : 'context * binder -> 'context}
+ -> 'context -> Elab.exp -> bool
+
+ val foldB : {kind : 'context * Elab.kind' * 'state -> 'state,
+ con : 'context * Elab.con' * 'state -> 'state,
+ exp : 'context * Elab.exp' * 'state -> 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Elab.exp -> 'state
+end
+
+structure Sgn : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+ | Str of string * int * Elab.sgn
+ | Sgn of string * int * Elab.sgn
+
+ val mapfoldB : {kind : ('context, Elab.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Elab.con', 'state, 'abort) Search.mapfolderB,
+ sgn_item : ('context, Elab.sgn_item', 'state, 'abort) Search.mapfolderB,
+ sgn : ('context, Elab.sgn', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Elab.sgn, 'state, 'abort) Search.mapfolderB
+
+
+ val mapfold : {kind : (Elab.kind', 'state, 'abort) Search.mapfolder,
+ con : (Elab.con', 'state, 'abort) Search.mapfolder,
+ sgn_item : (Elab.sgn_item', 'state, 'abort) Search.mapfolder,
+ sgn : (Elab.sgn', 'state, 'abort) Search.mapfolder}
+ -> (Elab.sgn, 'state, 'abort) Search.mapfolder
+
+ val map : {kind : Elab.kind' -> Elab.kind',
+ con : Elab.con' -> Elab.con',
+ sgn_item : Elab.sgn_item' -> Elab.sgn_item',
+ sgn : Elab.sgn' -> Elab.sgn'}
+ -> Elab.sgn -> Elab.sgn
+
+ val mapB : {kind : 'context -> Elab.kind' -> Elab.kind',
+ con : 'context -> Elab.con' -> Elab.con',
+ sgn_item : 'context -> Elab.sgn_item' -> Elab.sgn_item',
+ sgn : 'context -> Elab.sgn' -> Elab.sgn',
+ bind : 'context * binder -> 'context}
+ -> 'context -> Elab.sgn -> Elab.sgn
+
+end
+
+structure Decl : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+ | RelE of string * Elab.con
+ | NamedE of string * Elab.con
+ | Str of string * int * Elab.sgn
+ | Sgn of string * int * Elab.sgn
+
+ val mapfoldB : {kind : ('context, Elab.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Elab.con', 'state, 'abort) Search.mapfolderB,
+ exp : ('context, Elab.exp', 'state, 'abort) Search.mapfolderB,
+ sgn_item : ('context, Elab.sgn_item', 'state, 'abort) Search.mapfolderB,
+ sgn : ('context, Elab.sgn', 'state, 'abort) Search.mapfolderB,
+ str : ('context, Elab.str', 'state, 'abort) Search.mapfolderB,
+ decl : ('context, Elab.decl', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Elab.decl, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Elab.kind', 'state, 'abort) Search.mapfolder,
+ con : (Elab.con', 'state, 'abort) Search.mapfolder,
+ exp : (Elab.exp', 'state, 'abort) Search.mapfolder,
+ sgn_item : (Elab.sgn_item', 'state, 'abort) Search.mapfolder,
+ sgn : (Elab.sgn', 'state, 'abort) Search.mapfolder,
+ str : (Elab.str', 'state, 'abort) Search.mapfolder,
+ decl : (Elab.decl', 'state, 'abort) Search.mapfolder}
+ -> (Elab.decl, 'state, 'abort) Search.mapfolder
+ val exists : {kind : Elab.kind' -> bool,
+ con : Elab.con' -> bool,
+ exp : Elab.exp' -> bool,
+ sgn_item : Elab.sgn_item' -> bool,
+ sgn : Elab.sgn' -> bool,
+ str : Elab.str' -> bool,
+ decl : Elab.decl' -> bool}
+ -> Elab.decl -> bool
+ val search : {kind : Elab.kind' -> 'a option,
+ con : Elab.con' -> 'a option,
+ exp : Elab.exp' -> 'a option,
+ sgn_item : Elab.sgn_item' -> 'a option,
+ sgn : Elab.sgn' -> 'a option,
+ str : Elab.str' -> 'a option,
+ decl : Elab.decl' -> 'a option}
+ -> Elab.decl -> 'a option
+
+ val foldMapB : {kind : 'context * Elab.kind' * 'state -> Elab.kind' * 'state,
+ con : 'context * Elab.con' * 'state -> Elab.con' * 'state,
+ exp : 'context * Elab.exp' * 'state -> Elab.exp' * 'state,
+ sgn_item : 'context * Elab.sgn_item' * 'state -> Elab.sgn_item' * 'state,
+ sgn : 'context * Elab.sgn' * 'state -> Elab.sgn' * 'state,
+ str : 'context * Elab.str' * 'state -> Elab.str' * 'state,
+ decl : 'context * Elab.decl' * 'state -> Elab.decl' * 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Elab.decl -> Elab.decl * 'state
+
+ val map : {kind : Elab.kind' -> Elab.kind',
+ con : Elab.con' -> Elab.con',
+ exp : Elab.exp' -> Elab.exp',
+ sgn_item : Elab.sgn_item' -> Elab.sgn_item',
+ sgn : Elab.sgn' -> Elab.sgn',
+ str : Elab.str' -> Elab.str',
+ decl : Elab.decl' -> Elab.decl'}
+ -> Elab.decl -> Elab.decl
+
+ val mapB : {kind : 'context -> Elab.kind' -> Elab.kind',
+ con : 'context -> Elab.con' -> Elab.con',
+ exp : 'context -> Elab.exp' -> Elab.exp',
+ sgn_item : 'context -> Elab.sgn_item' -> Elab.sgn_item',
+ sgn : 'context -> Elab.sgn' -> Elab.sgn',
+ str : 'context -> Elab.str' -> Elab.str',
+ decl : 'context -> Elab.decl' -> Elab.decl',
+ bind : 'context * binder -> 'context}
+ -> 'context -> Elab.decl -> Elab.decl
+
+ val fold : {kind : Elab.kind' * 'state -> 'state,
+ con : Elab.con' * 'state -> 'state,
+ exp : Elab.exp' * 'state -> 'state,
+ sgn_item : Elab.sgn_item' * 'state -> 'state,
+ sgn : Elab.sgn' * 'state -> 'state,
+ str : Elab.str' * 'state -> 'state,
+ decl : Elab.decl' * 'state -> 'state}
+ -> 'state -> Elab.decl -> 'state
+end
+
+structure File : sig
+ val maxName : Elab.file -> int
+
+ val findDecl : (Elab.decl -> bool) -> Elab.file -> Elab.decl option
+end
+
+end
diff --git a/src/elab_util.sml b/src/elab_util.sml
new file mode 100644
index 0000000..0cdb9cc
--- /dev/null
+++ b/src/elab_util.sml
@@ -0,0 +1,1310 @@
+(* Copyright (c) 2008-2010, 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 ElabUtil :> ELAB_UTIL = struct
+
+open Elab
+
+fun classifyDatatype xncs =
+ case xncs of
+ [(_, _, NONE), (_, _, SOME _)] => Option
+ | [(_, _, SOME _), (_, _, NONE)] => Option
+ | _ =>
+ if List.all (fn (_, _, NONE) => true | _ => false) xncs then
+ Enum
+ else
+ Default
+
+structure S = Search
+
+structure Kind = struct
+
+fun mapfoldB {kind, bind} =
+ let
+ fun mfk ctx k acc =
+ S.bindP (mfk' ctx k acc, kind ctx)
+
+ and mfk' ctx (kAll as (k, loc)) =
+ case k of
+ KType => S.return2 kAll
+
+ | KArrow (k1, k2) =>
+ S.bind2 (mfk ctx k1,
+ fn k1' =>
+ S.map2 (mfk ctx k2,
+ fn k2' =>
+ (KArrow (k1', k2'), loc)))
+
+ | KName => S.return2 kAll
+
+ | KRecord k =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (KRecord k', loc))
+
+ | KUnit => S.return2 kAll
+
+ | KTuple ks =>
+ S.map2 (ListUtil.mapfold (mfk ctx) ks,
+ fn ks' =>
+ (KTuple ks', loc))
+
+ | KError => S.return2 kAll
+
+ | KUnif (_, _, ref (KKnown k)) => mfk' ctx k
+ | KUnif _ => S.return2 kAll
+
+ | KTupleUnif (_, _, ref (KKnown k)) => mfk' ctx k
+ | KTupleUnif (loc, nks, r) =>
+ S.map2 (ListUtil.mapfold (fn (n, k) =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (n, k'))) nks,
+ fn nks' =>
+ (KTupleUnif (loc, nks', r), loc))
+
+
+ | KRel _ => S.return2 kAll
+ | KFun (x, k) =>
+ S.map2 (mfk (bind (ctx, x)) k,
+ fn k' =>
+ (KFun (x, k'), loc))
+ in
+ mfk
+ end
+
+fun mapfold fk =
+ mapfoldB {kind = fn () => fk,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, bind} ctx k =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ bind = bind} ctx k () of
+ S.Continue (k, ()) => k
+ | S.Return _ => raise Fail "ElabUtil.Kind.mapB: Impossible"
+
+fun exists f k =
+ case mapfold (fn k => fn () =>
+ if f k then
+ S.Return ()
+ else
+ S.Continue (k, ())) k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun foldB {kind, bind} ctx st k =
+ case mapfoldB {kind = fn ctx => fn k => fn st => S.Continue (k, kind (ctx, k, st)),
+ bind = bind} ctx k st of
+ S.Continue (_, st) => st
+ | S.Return _ => raise Fail "ElabUtil.Kind.foldB: Impossible"
+
+end
+
+val mliftConInCon = ref (fn n : int => fn c : con => (raise Fail "You didn't set ElabUtil.mliftConInCon!") : con)
+
+structure Con = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+
+fun mapfoldB {kind = fk, con = fc, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, s) => bind (ctx, RelK s)}
+
+ fun mfc ctx c acc =
+ S.bindP (mfc' ctx c acc, fc ctx)
+
+ and mfc' ctx (cAll as (c, loc)) =
+ case c of
+ TFun (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (TFun (c1', c2'), loc)))
+ | TCFun (e, x, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc (bind (ctx, RelC (x, k))) c,
+ fn c' =>
+ (TCFun (e, x, k', c'), loc)))
+ | TDisjoint (c1, c2, c3) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.bind2 (mfc ctx c2,
+ fn c2' =>
+ S.map2 (mfc ctx c3,
+ fn c3' =>
+ (TDisjoint (c1', c2', c3'), loc))))
+ | TRecord c =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (TRecord c', loc))
+
+ | CRel _ => S.return2 cAll
+ | CNamed _ => S.return2 cAll
+ | CModProj _ => S.return2 cAll
+ | CApp (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (CApp (c1', c2'), loc)))
+ | CAbs (x, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc (bind (ctx, RelC (x, k))) c,
+ fn c' =>
+ (CAbs (x, k', c'), loc)))
+
+ | CName _ => S.return2 cAll
+
+ | CRecord (k, xcs) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (ListUtil.mapfold (fn (x, c) =>
+ S.bind2 (mfc ctx x,
+ fn x' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (x', c'))))
+ xcs,
+ fn xcs' =>
+ (CRecord (k', xcs'), loc)))
+ | CConcat (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (CConcat (c1', c2'), loc)))
+ | CMap (k1, k2) =>
+ S.bind2 (mfk ctx k1,
+ fn k1' =>
+ S.map2 (mfk ctx k2,
+ fn k2' =>
+ (CMap (k1', k2'), loc)))
+
+ | CUnit => S.return2 cAll
+
+ | CTuple cs =>
+ S.map2 (ListUtil.mapfold (mfc ctx) cs,
+ fn cs' =>
+ (CTuple cs', loc))
+
+ | CProj (c, n) =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (CProj (c', n), loc))
+
+ | CError => S.return2 cAll
+ | CUnif (nl, _, _, _, ref (Known c)) => mfc' ctx (!mliftConInCon nl c)
+ | CUnif _ => S.return2 cAll
+
+ | CKAbs (x, c) =>
+ S.map2 (mfc (bind (ctx, RelK x)) c,
+ fn c' =>
+ (CKAbs (x, c'), loc))
+ | CKApp (c, k) =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (CKApp (c', k'), loc)))
+ | TKFun (x, c) =>
+ S.map2 (mfc (bind (ctx, RelK x)) c,
+ fn c' =>
+ (TKFun (x, c'), loc))
+ in
+ mfc
+ end
+
+fun mapfold {kind = fk, con = fc} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, con, bind} ctx c =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ bind = bind} ctx c () of
+ S.Continue (c, ()) => c
+ | S.Return _ => raise Fail "ElabUtil.Con.mapB: Impossible"
+
+fun map {kind, con} s =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ())} s () of
+ S.Return () => raise Fail "ElabUtil.Con.map: Impossible"
+ | S.Continue (s, ()) => s
+
+fun appB {kind, con, bind} ctx c =
+ case mapfoldB {kind = fn ctx => fn k => fn () => (kind ctx k; S.Continue (k, ())),
+ con = fn ctx => fn c => fn () => (con ctx c; S.Continue (c, ())),
+ bind = bind} ctx c () of
+ S.Continue _ => ()
+ | S.Return _ => raise Fail "ElabUtil.Con.appB: Impossible"
+
+fun app {kind, con} s =
+ case mapfold {kind = fn k => fn () => (kind k; S.Continue (k, ())),
+ con = fn c => fn () => (con c; S.Continue (c, ()))} s () of
+ S.Return () => raise Fail "ElabUtil.Con.app: Impossible"
+ | S.Continue _ => ()
+
+fun existsB {kind, con, bind} ctx c =
+ case mapfoldB {kind = fn ctx => fn k => fn () =>
+ if kind (ctx, k) then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn ctx => fn c => fn () =>
+ if con (ctx, c) then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ bind = bind} ctx c () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun exists {kind, con} c =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ())} c () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun foldB {kind, con, bind} ctx st c =
+ case mapfoldB {kind = fn ctx => fn k => fn st => S.Continue (k, kind (ctx, k, st)),
+ con = fn ctx => fn c => fn st => S.Continue (c, con (ctx, c, st)),
+ bind = bind} ctx c st of
+ S.Continue (_, st) => st
+ | S.Return _ => raise Fail "ElabUtil.Con.foldB: Impossible"
+
+fun fold {kind, con} st c =
+ case mapfoldB {kind = fn () => fn k => fn st => S.Continue (k, kind (k, st)),
+ con = fn () => fn c => fn st => S.Continue (c, con (c, st)),
+ bind = fn ((), _) => ()} () c st of
+ S.Continue (_, st) => st
+ | S.Return _ => raise Fail "ElabUtil.Con.fold: Impossible"
+
+end
+
+structure Exp = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+ | RelE of string * Elab.con
+ | NamedE of string * Elab.con
+
+fun mapfoldB {kind = fk, con = fc, exp = fe, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val mfc = Con.mapfoldB {kind = fk, con = fc, bind = bind'}
+
+ fun doVars ((p, _), ctx) =
+ case p of
+ PVar xt => bind (ctx, RelE xt)
+ | PPrim _ => ctx
+ | PCon (_, _, _, NONE) => ctx
+ | PCon (_, _, _, SOME p) => doVars (p, ctx)
+ | PRecord xpcs =>
+ foldl (fn ((_, p, _), ctx) => doVars (p, ctx))
+ ctx xpcs
+
+ fun mfe ctx e acc =
+ S.bindP (mfe' ctx e acc, fe ctx)
+
+ and mfe' ctx (eAll as (e, loc)) =
+ case e of
+ EPrim _ => S.return2 eAll
+ | ERel _ => S.return2 eAll
+ | ENamed _ => S.return2 eAll
+ | EModProj _ => S.return2 eAll
+ | EApp (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (EApp (e1', e2'), loc)))
+ | EAbs (x, dom, ran, e) =>
+ S.bind2 (mfc ctx dom,
+ fn dom' =>
+ S.bind2 (mfc ctx ran,
+ fn ran' =>
+ S.map2 (mfe (bind (ctx, RelE (x, dom'))) e,
+ fn e' =>
+ (EAbs (x, dom', ran', e'), loc))))
+
+ | ECApp (e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (ECApp (e', c'), loc)))
+ | ECAbs (expl, x, k, e) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfe (bind (ctx, RelC (x, k))) e,
+ fn e' =>
+ (ECAbs (expl, x, k', e'), loc)))
+
+ | ERecord xes =>
+ S.map2 (ListUtil.mapfold (fn (x, e, t) =>
+ S.bind2 (mfc ctx x,
+ fn x' =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (x', e', t')))))
+ xes,
+ fn xes' =>
+ (ERecord xes', loc))
+ | EField (e, c, {field, rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfc ctx field,
+ fn field' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (EField (e', c', {field = field', rest = rest'}), loc)))))
+ | EConcat (e1, c1, e2, c2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.bind2 (mfe ctx e2,
+ fn e2' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (EConcat (e1', c1', e2', c2'),
+ loc)))))
+ | ECut (e, c, {field, rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfc ctx field,
+ fn field' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (ECut (e', c', {field = field', rest = rest'}), loc)))))
+
+ | ECutMulti (e, c, {rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (ECutMulti (e', c', {rest = rest'}), loc))))
+
+ | ECase (e, pes, {disc, result}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (ListUtil.mapfold (fn (p, e) =>
+ let
+ fun pb ((p, _), ctx) =
+ case p of
+ PVar (x, t) => bind (ctx, RelE (x, t))
+ | PPrim _ => ctx
+ | PCon (_, _, _, NONE) => ctx
+ | PCon (_, _, _, SOME p) => pb (p, ctx)
+ | PRecord xps => foldl (fn ((_, p, _), ctx) =>
+ pb (p, ctx)) ctx xps
+ in
+ S.bind2 (mfp ctx p,
+ fn p' =>
+ S.map2 (mfe (pb (p', ctx)) e,
+ fn e' => (p', e')))
+ end) pes,
+ fn pes' =>
+ S.bind2 (mfc ctx disc,
+ fn disc' =>
+ S.map2 (mfc ctx result,
+ fn result' =>
+ (ECase (e', pes', {disc = disc', result = result'}), loc)))))
+
+ | EError => S.return2 eAll
+ | EUnif (ref (SOME e)) => mfe ctx e
+ | EUnif _ => S.return2 eAll
+
+ | ELet (des, e, t) =>
+ let
+ val (des, ctx') = foldl (fn (ed, (des, ctx)) =>
+ let
+ val ctx' =
+ case #1 ed of
+ EDVal (p, _, _) => doVars (p, ctx)
+ | EDValRec vis =>
+ foldl (fn ((x, t, _), ctx) => bind (ctx, RelE (x, t)))
+ ctx vis
+ in
+ (S.bind2 (des,
+ fn des' =>
+ S.map2 (mfed ctx ed,
+ fn ed' => ed' :: des')),
+ ctx')
+ end)
+ (S.return2 [], ctx) des
+ in
+ S.bind2 (des,
+ fn des' =>
+ S.bind2 (mfe ctx' e,
+ fn e' =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (ELet (rev des', e', t'), loc))))
+ end
+
+ | EKAbs (x, e) =>
+ S.map2 (mfe (bind (ctx, RelK x)) e,
+ fn e' =>
+ (EKAbs (x, e'), loc))
+ | EKApp (e, k) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (EKApp (e', k'), loc)))
+
+ and mfp ctx (pAll as (p, loc)) =
+ case p of
+ PVar (x, t) =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (PVar (x, t'), loc))
+ | PPrim _ => S.return2 pAll
+ | PCon (dk, pc, args, po) =>
+ S.bind2 (ListUtil.mapfold (mfc ctx) args,
+ fn args' =>
+ S.map2 ((case po of
+ NONE => S.return2 NONE
+ | SOME p => S.map2 (mfp ctx p, SOME)),
+ fn po' =>
+ (PCon (dk, pc, args', po'), loc)))
+ | PRecord xps =>
+ S.map2 (ListUtil.mapfold (fn (x, p, c) =>
+ S.bind2 (mfp ctx p,
+ fn p' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (x, p', c')))) xps,
+ fn xps' =>
+ (PRecord xps', loc))
+
+ and mfed ctx (dAll as (d, loc)) =
+ case d of
+ EDVal (p, t, e) =>
+ S.bind2 (mfc ctx t,
+ fn t' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EDVal (p, t', e'), loc)))
+ | EDValRec vis =>
+ let
+ val ctx = foldl (fn ((x, t, _), ctx) => bind (ctx, RelE (x, t))) ctx vis
+ in
+ S.map2 (ListUtil.mapfold (mfvi ctx) vis,
+ fn vis' =>
+ (EDValRec vis', loc))
+ end
+
+ and mfvi ctx (x, c, e) =
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (x, c', e')))
+ in
+ mfe
+ end
+
+fun mapfold {kind = fk, con = fc, exp = fe} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ exp = fn () => fe,
+ bind = fn ((), _) => ()} ()
+
+fun existsB {kind, con, exp, bind} ctx e =
+ case mapfoldB {kind = fn ctx => fn k => fn () =>
+ if kind (ctx, k) then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn ctx => fn c => fn () =>
+ if con (ctx, c) then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn ctx => fn e => fn () =>
+ if exp (ctx, e) then
+ S.Return ()
+ else
+ S.Continue (e, ()),
+ bind = bind} ctx e () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun exists {kind, con, exp} k =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun mapB {kind, con, exp, bind} ctx e =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ exp = fn ctx => fn e => fn () => S.Continue (exp ctx e, ()),
+ bind = bind} ctx e () of
+ S.Continue (e, ()) => e
+ | S.Return _ => raise Fail "ElabUtil.Exp.mapB: Impossible"
+
+fun foldB {kind, con, exp, bind} ctx st e =
+ case mapfoldB {kind = fn ctx => fn k => fn st => S.Continue (k, kind (ctx, k, st)),
+ con = fn ctx => fn c => fn st => S.Continue (c, con (ctx, c, st)),
+ exp = fn ctx => fn e => fn st => S.Continue (e, exp (ctx, e, st)),
+ bind = bind} ctx e st of
+ S.Continue (_, st) => st
+ | S.Return _ => raise Fail "ElabUtil.Exp.foldB: Impossible"
+
+end
+
+structure Sgn = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+ | Str of string * int * Elab.sgn
+ | Sgn of string * int * Elab.sgn
+
+fun mapfoldB {kind, con, sgn_item, sgn, bind} =
+ let
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val con = Con.mapfoldB {kind = kind, con = con, bind = bind'}
+
+ val kind = Kind.mapfoldB {kind = kind, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun sgi ctx si acc =
+ S.bindP (sgi' ctx si acc, sgn_item ctx)
+
+ and sgi' ctx (siAll as (si, loc)) =
+ case si of
+ SgiConAbs (x, n, k) =>
+ S.map2 (kind ctx k,
+ fn k' =>
+ (SgiConAbs (x, n, k'), loc))
+ | SgiCon (x, n, k, c) =>
+ S.bind2 (kind ctx k,
+ fn k' =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgiCon (x, n, k', c'), loc)))
+ | SgiDatatype dts =>
+ S.map2 (ListUtil.mapfold (fn (x, n, xs, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (con ctx c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' => (x, n, xs, xncs'))) dts,
+ fn dts' =>
+ (SgiDatatype dts', loc))
+ | SgiDatatypeImp (x, n, m1, ms, s, xs, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (con ctx c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' =>
+ (SgiDatatypeImp (x, n, m1, ms, s, xs, xncs'), loc))
+ | SgiVal (x, n, c) =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgiVal (x, n, c'), loc))
+ | SgiStr (im, x, n, s) =>
+ S.map2 (sg ctx s,
+ fn s' =>
+ (SgiStr (im, x, n, s'), loc))
+ | SgiSgn (x, n, s) =>
+ S.map2 (sg ctx s,
+ fn s' =>
+ (SgiSgn (x, n, s'), loc))
+ | SgiConstraint (c1, c2) =>
+ S.bind2 (con ctx c1,
+ fn c1' =>
+ S.map2 (con ctx c2,
+ fn c2' =>
+ (SgiConstraint (c1', c2'), loc)))
+ | SgiClassAbs (x, n, k) =>
+ S.map2 (kind ctx k,
+ fn k' =>
+ (SgiClassAbs (x, n, k'), loc))
+ | SgiClass (x, n, k, c) =>
+ S.bind2 (kind ctx k,
+ fn k' =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgiClass (x, n, k', c'), loc)))
+
+ and sg ctx s acc =
+ S.bindP (sg' ctx s acc, sgn ctx)
+
+ and sg' ctx (sAll as (s, loc)) =
+ case s of
+ SgnConst sgis =>
+ S.map2 (ListUtil.mapfoldB (fn (ctx, si) =>
+ (case #1 si of
+ SgiConAbs (x, n, k) =>
+ bind (ctx, NamedC (x, n, k, NONE))
+ | SgiCon (x, n, k, c) =>
+ bind (ctx, NamedC (x, n, k, SOME c))
+ | SgiDatatype dts =>
+ foldl (fn ((x, n, ks, _), ctx) =>
+ let
+ val k' = (KType, loc)
+ val k = foldl (fn (_, k) => (KArrow (k', k), loc))
+ k' ks
+ in
+ bind (ctx, NamedC (x, n, k, NONE))
+ end) ctx dts
+ | SgiDatatypeImp (x, n, m1, ms, s, _, _) =>
+ bind (ctx, NamedC (x, n, (KType, loc),
+ SOME (CModProj (m1, ms, s), loc)))
+ | SgiVal _ => ctx
+ | SgiStr (_, x, n, sgn) =>
+ bind (ctx, Str (x, n, sgn))
+ | SgiSgn (x, n, sgn) =>
+ bind (ctx, Sgn (x, n, sgn))
+ | SgiConstraint _ => ctx
+ | SgiClassAbs (x, n, k) =>
+ bind (ctx, NamedC (x, n, (KArrow (k, (KType, loc)), loc), NONE))
+ | SgiClass (x, n, k, c) =>
+ bind (ctx, NamedC (x, n, (KArrow (k, (KType, loc)), loc), SOME c)),
+ sgi ctx si)) ctx sgis,
+ fn sgis' =>
+ (SgnConst sgis', loc))
+
+ | SgnVar _ => S.return2 sAll
+ | SgnFun (m, n, s1, s2) =>
+ S.bind2 (sg ctx s1,
+ fn s1' =>
+ S.map2 (sg (bind (ctx, Str (m, n, s1'))) s2,
+ fn s2' =>
+ (SgnFun (m, n, s1', s2'), loc)))
+ | SgnProj _ => S.return2 sAll
+ | SgnWhere (sgn, ms, x, c) =>
+ S.bind2 (sg ctx sgn,
+ fn sgn' =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgnWhere (sgn', ms, x, c'), loc)))
+ | SgnError => S.return2 sAll
+ in
+ sg
+ end
+
+fun mapfold {kind, con, sgn_item, sgn} =
+ mapfoldB {kind = fn () => kind,
+ con = fn () => con,
+ sgn_item = fn () => sgn_item,
+ sgn = fn () => sgn,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, con, sgn_item, sgn, bind} ctx s =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ sgn_item = fn ctx => fn sgi => fn () => S.Continue (sgn_item ctx sgi, ()),
+ sgn = fn ctx => fn s => fn () => S.Continue (sgn ctx s, ()),
+ bind = bind} ctx s () of
+ S.Continue (s, ()) => s
+ | S.Return _ => raise Fail "ElabUtil.Sgn.mapB: Impossible"
+
+fun map {kind, con, sgn_item, sgn} s =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ()),
+ sgn_item = fn si => fn () => S.Continue (sgn_item si, ()),
+ sgn = fn s => fn () => S.Continue (sgn s, ())} s () of
+ S.Return () => raise Fail "Elab_util.Sgn.map"
+ | S.Continue (s, ()) => s
+
+end
+
+structure Decl = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Elab.kind
+ | NamedC of string * int * Elab.kind * Elab.con option
+ | RelE of string * Elab.con
+ | NamedE of string * Elab.con
+ | Str of string * int * Elab.sgn
+ | Sgn of string * int * Elab.sgn
+
+fun mapfoldB {kind = fk, con = fc, exp = fe, sgn_item = fsgi, sgn = fsg, str = fst, decl = fd, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val mfc = Con.mapfoldB {kind = fk, con = fc, bind = bind'}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Exp.RelK x => RelK x
+ | Exp.RelC x => RelC x
+ | Exp.NamedC x => NamedC x
+ | Exp.RelE x => RelE x
+ | Exp.NamedE x => NamedE x
+ in
+ bind (ctx, b')
+ end
+ val mfe = Exp.mapfoldB {kind = fk, con = fc, exp = fe, bind = bind'}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Sgn.RelK x => RelK x
+ | Sgn.RelC x => RelC x
+ | Sgn.NamedC x => NamedC x
+ | Sgn.Sgn x => Sgn x
+ | Sgn.Str x => Str x
+ in
+ bind (ctx, b')
+ end
+ val mfsg = Sgn.mapfoldB {kind = fk, con = fc, sgn_item = fsgi, sgn = fsg, bind = bind'}
+
+ fun mfst ctx str acc =
+ S.bindP (mfst' ctx str acc, fst ctx)
+
+ and mfst' ctx (strAll as (str, loc)) =
+ case str of
+ StrConst ds =>
+ S.map2 (ListUtil.mapfoldB (fn (ctx, d) =>
+ (case #1 d of
+ DCon (x, n, k, c) =>
+ bind (ctx, NamedC (x, n, k, SOME c))
+ | DDatatype dts =>
+ let
+ fun doOne ((x, n, xs, xncs), ctx) =
+ let
+ val ctx = bind (ctx, NamedC (x, n, (KType, loc), NONE))
+ in
+ foldl (fn ((x, _, co), ctx) =>
+ let
+ val t =
+ case co of
+ NONE => CNamed n
+ | SOME t => TFun (t, (CNamed n, loc))
+
+ val k = (KType, loc)
+ val t = (t, loc)
+ val t = foldr (fn (x, t) =>
+ (TCFun (Explicit,
+ x,
+ k,
+ t), loc))
+ t xs
+ in
+ bind (ctx, NamedE (x, t))
+ end)
+ ctx xncs
+ end
+ in
+ foldl doOne ctx dts
+ end
+ | DDatatypeImp (x, n, m, ms, x', _, _) =>
+ bind (ctx, NamedC (x, n, (KType, loc),
+ SOME (CModProj (m, ms, x'), loc)))
+ | DVal (x, _, c, _) =>
+ bind (ctx, NamedE (x, c))
+ | DValRec vis =>
+ foldl (fn ((x, _, c, _), ctx) => bind (ctx, NamedE (x, c))) ctx vis
+ | DSgn (x, n, sgn) =>
+ bind (ctx, Sgn (x, n, sgn))
+ | DStr (x, n, sgn, _) =>
+ bind (ctx, Str (x, n, sgn))
+ | DFfiStr (x, n, sgn) =>
+ bind (ctx, Str (x, n, sgn))
+ | DConstraint _ => ctx
+ | DExport _ => ctx
+ | DTable (tn, x, n, c, _, pc, _, cc) =>
+ let
+ val ct = (CModProj (n, [], "sql_table"), loc)
+ val ct = (CApp (ct, c), loc)
+ val ct = (CApp (ct, (CConcat (pc, cc), loc)), loc)
+ in
+ bind (ctx, NamedE (x, ct))
+ end
+ | DSequence (tn, x, n) =>
+ bind (ctx, NamedE (x, (CModProj (n, [], "sql_sequence"), loc)))
+ | DView (tn, x, n, _, c) =>
+ let
+ val ct = (CModProj (n, [], "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ bind (ctx, NamedE (x, ct))
+ end
+ | DDatabase _ => ctx
+ | DCookie (tn, x, n, c) =>
+ bind (ctx, NamedE (x, (CApp ((CModProj (n, [], "cookie"), loc),
+ c), loc)))
+ | DStyle (tn, x, n) =>
+ bind (ctx, NamedE (x, (CModProj (n, [], "css_class"), loc)))
+ | DTask _ => ctx
+ | DPolicy _ => ctx
+ | DOnError _ => ctx
+ | DFfi (x, _, _, t) => bind (ctx, NamedE (x, t)),
+ mfd ctx d)) ctx ds,
+ fn ds' => (StrConst ds', loc))
+ | StrVar _ => S.return2 strAll
+ | StrProj (str, x) =>
+ S.map2 (mfst ctx str,
+ fn str' =>
+ (StrProj (str', x), loc))
+ | StrFun (x, n, sgn1, sgn2, str) =>
+ S.bind2 (mfsg ctx sgn1,
+ fn sgn1' =>
+ S.bind2 (mfsg ctx sgn2,
+ fn sgn2' =>
+ S.map2 (mfst ctx str,
+ fn str' =>
+ (StrFun (x, n, sgn1', sgn2', str'), loc))))
+ | StrApp (str1, str2) =>
+ S.bind2 (mfst ctx str1,
+ fn str1' =>
+ S.map2 (mfst ctx str2,
+ fn str2' =>
+ (StrApp (str1', str2'), loc)))
+ | StrError => S.return2 strAll
+
+ and mfd ctx d acc =
+ S.bindP (mfd' ctx d acc, fd ctx)
+
+ and mfd' ctx (dAll as (d, loc)) =
+ case d of
+ DCon (x, n, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DCon (x, n, k', c'), loc)))
+ | DDatatype dts =>
+ S.map2 (ListUtil.mapfold (fn (x, n, xs, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (mfc ctx c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' =>
+ (x, n, xs, xncs'))) dts,
+ fn dts' =>
+ (DDatatype dts', loc))
+ | DDatatypeImp (x, n, m1, ms, s, xs, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (mfc ctx c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' =>
+ (DDatatypeImp (x, n, m1, ms, s, xs, xncs'), loc))
+ | DVal vi =>
+ S.map2 (mfvi ctx vi,
+ fn vi' =>
+ (DVal vi', loc))
+ | DValRec vis =>
+ S.map2 (ListUtil.mapfold (mfvi ctx) vis,
+ fn vis' =>
+ (DValRec vis', loc))
+ | DSgn (x, n, sgn) =>
+ S.map2 (mfsg ctx sgn,
+ fn sgn' =>
+ (DSgn (x, n, sgn'), loc))
+ | DStr (x, n, sgn, str) =>
+ S.bind2 (mfsg ctx sgn,
+ fn sgn' =>
+ S.map2 (mfst ctx str,
+ fn str' =>
+ (DStr (x, n, sgn', str'), loc)))
+ | DFfiStr (x, n, sgn) =>
+ S.map2 (mfsg ctx sgn,
+ fn sgn' =>
+ (DFfiStr (x, n, sgn'), loc))
+ | DConstraint (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (DConstraint (c1', c2'), loc)))
+ | DExport (en, sgn, str) =>
+ S.bind2 (mfsg ctx sgn,
+ fn sgn' =>
+ S.map2 (mfst ctx str,
+ fn str' =>
+ (DExport (en, sgn', str'), loc)))
+
+ | DTable (tn, x, n, c, pe, pc, ce, cc) =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfe ctx pe,
+ fn pe' =>
+ S.bind2 (mfc ctx pc,
+ fn pc' =>
+ S.bind2 (mfe ctx ce,
+ fn ce' =>
+ S.map2 (mfc ctx cc,
+ fn cc' =>
+ (DTable (tn, x, n, c', pe', pc', ce', cc'), loc))))))
+ | DSequence _ => S.return2 dAll
+ | DView (tn, x, n, e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DView (tn, x, n, e', c'), loc)))
+
+ | DDatabase _ => S.return2 dAll
+
+ | DCookie (tn, x, n, c) =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (DCookie (tn, x, n, c'), loc))
+ | DStyle _ => S.return2 dAll
+ | DTask (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (DTask (e1', e2'), loc)))
+ | DPolicy e1 =>
+ S.map2 (mfe ctx e1,
+ fn e1' =>
+ (DPolicy e1', loc))
+ | DOnError _ => S.return2 dAll
+ | DFfi (x, n, modes, t) =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (DFfi (x, n, modes, t'), loc))
+
+ and mfvi ctx (x, n, c, e) =
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (x, n, c', e')))
+ in
+ mfd
+ end
+
+fun mapfold {kind, con, exp, sgn_item, sgn, str, decl} =
+ mapfoldB {kind = fn () => kind,
+ con = fn () => con,
+ exp = fn () => exp,
+ sgn_item = fn () => sgn_item,
+ sgn = fn () => sgn,
+ str = fn () => str,
+ decl = fn () => decl,
+ bind = fn ((), _) => ()} ()
+
+fun exists {kind, con, exp, sgn_item, sgn, str, decl} k =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ()),
+ sgn_item = fn sgi => fn () =>
+ if sgn_item sgi then
+ S.Return ()
+ else
+ S.Continue (sgi, ()),
+ sgn = fn x => fn () =>
+ if sgn x then
+ S.Return ()
+ else
+ S.Continue (x, ()),
+ str = fn x => fn () =>
+ if str x then
+ S.Return ()
+ else
+ S.Continue (x, ()),
+ decl = fn x => fn () =>
+ if decl x then
+ S.Return ()
+ else
+ S.Continue (x, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun search {kind, con, exp, sgn_item, sgn, str, decl} k =
+ case mapfold {kind = fn x => fn () =>
+ case kind x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v,
+
+ con = fn x => fn () =>
+ case con x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v,
+
+ exp = fn x => fn () =>
+ case exp x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v,
+
+ sgn_item = fn x => fn () =>
+ case sgn_item x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v,
+
+ sgn = fn x => fn () =>
+ case sgn x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v,
+
+ str = fn x => fn () =>
+ case str x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v,
+
+ decl = fn x => fn () =>
+ case decl x of
+ NONE => S.Continue (x, ())
+ | SOME v => S.Return v
+
+ } k () of
+ S.Return x => SOME x
+ | S.Continue _ => NONE
+
+fun foldMapB {kind, con, exp, sgn_item, sgn, str, decl, bind} ctx st d =
+ case mapfoldB {kind = fn ctx => fn x => fn st => S.Continue (kind (ctx, x, st)),
+ con = fn ctx => fn x => fn st => S.Continue (con (ctx, x, st)),
+ exp = fn ctx => fn x => fn st => S.Continue (exp (ctx, x, st)),
+ sgn_item = fn ctx => fn x => fn st => S.Continue (sgn_item (ctx, x, st)),
+ sgn = fn ctx => fn x => fn st => S.Continue (sgn (ctx, x, st)),
+ str = fn ctx => fn x => fn st => S.Continue (str (ctx, x, st)),
+ decl = fn ctx => fn x => fn st => S.Continue (decl (ctx, x, st)),
+ bind = bind} ctx d st of
+ S.Continue x => x
+ | S.Return _ => raise Fail "ElabUtil.Decl.foldMapB: Impossible"
+
+fun map {kind, con, exp, sgn_item, sgn, str, decl} s =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ()),
+ exp = fn e => fn () => S.Continue (exp e, ()),
+ sgn_item = fn si => fn () => S.Continue (sgn_item si, ()),
+ sgn = fn s => fn () => S.Continue (sgn s, ()),
+ str = fn si => fn () => S.Continue (str si, ()),
+ decl = fn s => fn () => S.Continue (decl s, ())} s () of
+ S.Return () => raise Fail "Elab_util.Decl.map"
+ | S.Continue (s, ()) => s
+
+fun mapB {kind, con, exp, sgn_item, sgn, str, decl, bind} ctx s =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ exp = fn ctx => fn c => fn () => S.Continue (exp ctx c, ()),
+ sgn_item = fn ctx => fn sgi => fn () => S.Continue (sgn_item ctx sgi, ()),
+ sgn = fn ctx => fn s => fn () => S.Continue (sgn ctx s, ()),
+ str = fn ctx => fn sgi => fn () => S.Continue (str ctx sgi, ()),
+ decl = fn ctx => fn s => fn () => S.Continue (decl ctx s, ()),
+ bind = bind} ctx s () of
+ S.Continue (s, ()) => s
+ | S.Return _ => raise Fail "ElabUtil.Decl.mapB: Impossible"
+
+fun fold {kind, con, exp, sgn_item, sgn, str, decl} (st : 'a) d : 'a =
+ case mapfold {kind = fn k => fn st => S.Continue (k, kind (k, st)),
+ con = fn c => fn st => S.Continue (c, con (c, st)),
+ exp = fn e => fn st => S.Continue (e, exp (e, st)),
+ sgn_item = fn sgi => fn st => S.Continue (sgi, sgn_item (sgi, st)),
+ sgn = fn s => fn st => S.Continue (s, sgn (s, st)),
+ str = fn str' => fn st => S.Continue (str', str (str', st)),
+ decl = fn d => fn st => S.Continue (d, decl (d, st))} d st of
+ S.Continue (_, st) => st
+ | S.Return _ => raise Fail "ElabUtil.Decl.fold: Impossible"
+
+end
+
+structure File = struct
+
+fun maxName ds = foldl (fn (d, count) => Int.max (maxNameDecl d, count)) 0 ds
+
+and maxNameDecl (d, _) =
+ case d of
+ DCon (_, n, _, _) => n
+ | DDatatype dts =>
+ foldl (fn ((_, n, _, ns), max) =>
+ foldl (fn ((_, n', _), m) => Int.max (n', m))
+ (Int.max (n, max)) ns) 0 dts
+ | DDatatypeImp (_, n1, n2, _, _, _, ns) =>
+ foldl (fn ((_, n', _), m) => Int.max (n', m))
+ (Int.max (n1, n2)) ns
+ | DVal (_, n, _, _) => n
+ | DValRec vis => foldl (fn ((_, n, _, _), count) => Int.max (n, count)) 0 vis
+ | DStr (_, n, sgn, str) => Int.max (n, Int.max (maxNameSgn sgn, maxNameStr str))
+ | DSgn (_, n, sgn) => Int.max (n, maxNameSgn sgn)
+ | DFfiStr (_, n, sgn) => Int.max (n, maxNameSgn sgn)
+ | DConstraint _ => 0
+ | DExport _ => 0
+ | DTable (n1, _, n2, _, _, _, _, _) => Int.max (n1, n2)
+ | DSequence (n1, _, n2) => Int.max (n1, n2)
+ | DView (n1, _, n2, _, _) => Int.max (n1, n2)
+ | DDatabase _ => 0
+ | DCookie (n1, _, n2, _) => Int.max (n1, n2)
+ | DStyle (n1, _, n2) => Int.max (n1, n2)
+ | DTask _ => 0
+ | DPolicy _ => 0
+ | DOnError _ => 0
+ | DFfi (_, n, _, _) => n
+and maxNameStr (str, _) =
+ case str of
+ StrConst ds => maxName ds
+ | StrVar n => n
+ | StrProj (str, _) => maxNameStr str
+ | StrFun (_, n, dom, ran, str) => foldl Int.max n [maxNameSgn dom, maxNameSgn ran, maxNameStr str]
+ | StrApp (str1, str2) => Int.max (maxNameStr str1, maxNameStr str2)
+ | StrError => 0
+
+and maxNameSgn (sgn, _) =
+ case sgn of
+ SgnConst sgis => foldl (fn (sgi, count) => Int.max (maxNameSgi sgi, count)) 0 sgis
+ | SgnVar n => n
+ | SgnFun (_, n, dom, ran) => Int.max (n, Int.max (maxNameSgn dom, maxNameSgn ran))
+ | SgnWhere (sgn, _, _, _) => maxNameSgn sgn
+ | SgnProj (n, _, _) => n
+ | SgnError => 0
+
+and maxNameSgi (sgi, _) =
+ case sgi of
+ SgiConAbs (_, n, _) => n
+ | SgiCon (_, n, _, _) => n
+ | SgiDatatype dts =>
+ foldl (fn ((_, n, _, ns), max) =>
+ foldl (fn ((_, n', _), m) => Int.max (n', m))
+ (Int.max (n, max)) ns) 0 dts
+ | SgiDatatypeImp (_, n1, n2, _, _, _, ns) =>
+ foldl (fn ((_, n', _), m) => Int.max (n', m))
+ (Int.max (n1, n2)) ns
+ | SgiVal (_, n, _) => n
+ | SgiStr (_, _, n, sgn) => Int.max (n, maxNameSgn sgn)
+ | SgiSgn (_, n, sgn) => Int.max (n, maxNameSgn sgn)
+ | SgiConstraint _ => 0
+ | SgiClassAbs (_, n, _) => n
+ | SgiClass (_, n, _, _) => n
+
+fun findDecl pred file =
+ let
+ fun decl d =
+ let
+ val r = case #1 d of
+ DStr (_, _, _, s) => str s
+ | _ => NONE
+ in
+ case r of
+ NONE => if pred d then SOME d else NONE
+ | _ => r
+ end
+
+ and str s =
+ case #1 s of
+ StrConst ds => ListUtil.search decl ds
+ | StrFun (_, _, _, _, s) => str s
+ | StrApp (s1, s2) =>
+ (case str s1 of
+ NONE => str s2
+ | r => r)
+ | _ => NONE
+ in
+ ListUtil.search decl file
+ end
+
+end
+
+end
diff --git a/src/elaborate.sig b/src/elaborate.sig
new file mode 100644
index 0000000..d60cff4
--- /dev/null
+++ b/src/elaborate.sig
@@ -0,0 +1,50 @@
+(* Copyright (c) 2008, 2012, 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.
+ *)
+
+signature ELABORATE = sig
+
+ val elabFile : Source.sgn_item list -> Time.time
+ -> Source.decl list -> Source.sgn_item list -> Time.time
+ -> ElabEnv.env -> Source.file -> Elab.file
+
+ val resolveClass : ElabEnv.env -> Elab.con -> Elab.exp option
+
+ val dumpTypes : bool ref
+ (* After elaboration (successful or failed), should I output a mapping from
+ * all identifiers to their kinds/types? *)
+
+ val dumpTypesOnError : bool ref
+ (* Like above, but only used if there are compile errors. *)
+
+ val unifyMore : bool ref
+ (* Run all phases of type inference, even if an error is detected by an
+ * early phase. *)
+
+ val incremental : bool ref
+ val verbose : bool ref
+
+end
diff --git a/src/elaborate.sml b/src/elaborate.sml
new file mode 100644
index 0000000..4a04d4b
--- /dev/null
+++ b/src/elaborate.sml
@@ -0,0 +1,5100 @@
+(* Copyright (c) 2008-2014, 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 Elaborate :> ELABORATE = struct
+
+ structure P = Prim
+ structure L = Source
+ structure L' = Elab
+ structure E = ElabEnv
+ structure U = ElabUtil
+ structure D = Disjoint
+
+ open Print
+ open ElabPrint
+ open ElabErr
+
+ val dumpTypes = ref false
+ val dumpTypesOnError = ref false
+ val unifyMore = ref false
+ val incremental = ref false
+ val verbose = ref false
+
+ structure IS = IntBinarySet
+ structure IM = IntBinaryMap
+
+ structure SK = struct
+ type ord_key = string
+ val compare = String.compare
+ end
+
+ structure SS = BinarySetFn(SK)
+ structure SM = BinaryMapFn(SK)
+
+ val basis_r = ref 0
+ val top_r = ref 0
+
+ fun elabExplicitness e =
+ case e of
+ L.Explicit => L'.Explicit
+ | L.Implicit => L'.Implicit
+
+ fun occursKind r =
+ U.Kind.exists (fn L'.KUnif (_, _, r') => r = r'
+ | _ => false)
+
+ fun validateCon env c =
+ (U.Con.appB {kind = fn env' => fn k => case k of
+ L'.KRel n => ignore (E.lookupKRel env' n)
+ | L'.KUnif (_, _, r as ref (L'.KUnknown f)) =>
+ r := L'.KUnknown (fn k => f k andalso validateKind env' k)
+ | _ => (),
+ con = fn env' => fn c => case c of
+ L'.CRel n => ignore (E.lookupCRel env' n)
+ | L'.CNamed n => ignore (E.lookupCNamed env' n)
+ | L'.CModProj (n, _, _) => ignore (E.lookupStrNamed env' n)
+ | L'.CUnif (_, _, _, _, r as ref (L'.Unknown f)) =>
+ r := L'.Unknown (fn c => f c andalso validateCon env' c)
+ | _ => (),
+ bind = fn (env', b) => case b of
+ U.Con.RelK x => E.pushKRel env' x
+ | U.Con.RelC (x, k) => E.pushCRel env' x k
+ | U.Con.NamedC (x, n, k, co) => E.pushCNamedAs env x n k co}
+ env c;
+ true)
+ handle _ => false
+
+ and validateKind env k = validateCon env (L'.CRecord (k, []), ErrorMsg.dummySpan)
+
+ exception KUnify' of E.env * kunify_error
+
+ fun unifyKinds' env (k1All as (k1, _)) (k2All as (k2, _)) =
+ let
+ fun err f = raise KUnify' (env, f (k1All, k2All))
+ in
+ case (k1, k2) of
+ (L'.KType, L'.KType) => ()
+ | (L'.KUnit, L'.KUnit) => ()
+
+ | (L'.KArrow (d1, r1), L'.KArrow (d2, r2)) =>
+ (unifyKinds' env d1 d2;
+ unifyKinds' env r1 r2)
+ | (L'.KName, L'.KName) => ()
+ | (L'.KRecord k1, L'.KRecord k2) => unifyKinds' env k1 k2
+ | (L'.KTuple ks1, L'.KTuple ks2) =>
+ ((ListPair.appEq (fn (k1, k2) => unifyKinds' env k1 k2) (ks1, ks2))
+ handle ListPair.UnequalLengths => err KIncompatible)
+
+ | (L'.KRel n1, L'.KRel n2) =>
+ if n1 = n2 then
+ ()
+ else
+ err KIncompatible
+ | (L'.KFun (x, k1), L'.KFun (_, k2)) =>
+ unifyKinds' (E.pushKRel env x) k1 k2
+
+ | (L'.KError, _) => ()
+ | (_, L'.KError) => ()
+
+ | (L'.KUnif (_, _, ref (L'.KKnown k1All)), _) => unifyKinds' env k1All k2All
+ | (_, L'.KUnif (_, _, ref (L'.KKnown k2All))) => unifyKinds' env k1All k2All
+
+ | (L'.KTupleUnif (_, _, ref (L'.KKnown k)), _) => unifyKinds' env k k2All
+ | (_, L'.KTupleUnif (_, _, ref (L'.KKnown k))) => unifyKinds' env k1All k
+
+ | (L'.KUnif (_, _, r1 as ref (L'.KUnknown f1)), L'.KUnif (_, _, r2 as ref (L'.KUnknown f2))) =>
+ if r1 = r2 then
+ ()
+ else
+ (r1 := L'.KKnown k2All;
+ r2 := L'.KUnknown (fn x => f1 x andalso f2 x))
+
+ | (L'.KUnif (_, _, r as ref (L'.KUnknown f)), _) =>
+ if occursKind r k2All then
+ err KOccursCheckFailed
+ else if not (f k2All) then
+ err KScope
+ else
+ r := L'.KKnown k2All
+ | (_, L'.KUnif (_, _, r as ref (L'.KUnknown f))) =>
+ if occursKind r k1All then
+ err KOccursCheckFailed
+ else if not (f k1All) then
+ err KScope
+ else
+ r := L'.KKnown k1All
+
+ | (L'.KTupleUnif (_, nks, r as ref (L'.KUnknown f)), L'.KTuple ks) =>
+ if not (f k2All) then
+ err KScope
+ else
+ ((app (fn (n, k) => unifyKinds' env k (List.nth (ks, n-1))) nks;
+ r := L'.KKnown k2All)
+ handle Subscript => err KIncompatible)
+ | (L'.KTuple ks, L'.KTupleUnif (_, nks, r as ref (L'.KUnknown f))) =>
+ if not (f k2All) then
+ err KScope
+ else
+ ((app (fn (n, k) => unifyKinds' env (List.nth (ks, n-1)) k) nks;
+ r := L'.KKnown k1All)
+ handle Subscript => err KIncompatible)
+ | (L'.KTupleUnif (loc, nks1, r1 as ref (L'.KUnknown f1)), L'.KTupleUnif (_, nks2, r2 as ref (L'.KUnknown f2))) =>
+ if r1 = r2 then
+ ()
+ else
+ let
+ val nks = foldl (fn (p as (n, k1), nks) =>
+ case ListUtil.search (fn (n', k2) =>
+ if n' = n then
+ SOME k2
+ else
+ NONE) nks2 of
+ NONE => p :: nks
+ | SOME k2 => (unifyKinds' env k1 k2;
+ nks)) nks2 nks1
+
+ val k = (L'.KTupleUnif (loc, nks, ref (L'.KUnknown (fn x => f1 x andalso f2 x))), loc)
+ in
+ r1 := L'.KKnown k;
+ r2 := L'.KKnown k
+ end
+
+ | _ => err KIncompatible
+ end
+
+ exception KUnify of L'.kind * L'.kind * E.env * kunify_error
+
+ fun unifyKinds env k1 k2 =
+ unifyKinds' env k1 k2
+ handle KUnify' (env', err) => raise KUnify (k1, k2, env', err)
+
+ fun checkKind env c k1 k2 =
+ unifyKinds env k1 k2
+ handle KUnify (k1, k2, env', err) =>
+ conError env (WrongKind (c, k1, k2, env', err))
+
+ val dummy = ErrorMsg.dummySpan
+
+ val ktype = (L'.KType, dummy)
+ val kname = (L'.KName, dummy)
+ val ktype_record = (L'.KRecord ktype, dummy)
+
+ val cerror = (L'.CError, dummy)
+ val kerror = (L'.KError, dummy)
+ val eerror = (L'.EError, dummy)
+ val sgnerror = (L'.SgnError, dummy)
+ val strerror = (L'.StrError, dummy)
+
+ val int = ref cerror
+ val float = ref cerror
+ val string = ref cerror
+ val char = ref cerror
+ val table = ref cerror
+
+
+ local
+ val count = ref 0
+ in
+
+ fun resetKunif () = count := 0
+
+ fun kunif' f loc =
+ let
+ val n = !count
+ val s = if n <= 26 then
+ str (chr (ord #"A" + n))
+ else
+ "U" ^ Int.toString (n - 26)
+ in
+ count := n + 1;
+ (L'.KUnif (loc, s, ref (L'.KUnknown f)), loc)
+ end
+
+ fun kunif env = kunif' (validateKind env)
+
+ end
+
+ local
+ val count = ref 0
+ in
+
+ fun resetCunif () = count := 0
+
+ fun cunif' f (loc, k) =
+ let
+ val n = !count
+ val s = if n < 26 then
+ str (chr (ord #"A" + n))
+ else
+ "U" ^ Int.toString (n - 26)
+ in
+ count := n + 1;
+ (L'.CUnif (0, loc, k, s, ref (L'.Unknown f)), loc)
+ end
+
+ fun cunif env = cunif' (validateCon env)
+
+ end
+
+ fun elabKind env (k, loc) =
+ case k of
+ L.KType => (L'.KType, loc)
+ | L.KArrow (k1, k2) => (L'.KArrow (elabKind env k1, elabKind env k2), loc)
+ | L.KName => (L'.KName, loc)
+ | L.KRecord k => (L'.KRecord (elabKind env k), loc)
+ | L.KUnit => (L'.KUnit, loc)
+ | L.KTuple ks => (L'.KTuple (map (elabKind env) ks), loc)
+ | L.KWild => kunif env loc
+
+ | L.KVar s => (case E.lookupK env s of
+ NONE =>
+ (kindError env (UnboundKind (loc, s));
+ kerror)
+ | SOME n => (L'.KRel n, loc))
+ | L.KFun (x, k) => (L'.KFun (x, elabKind (E.pushKRel env x) k), loc)
+
+ fun mapKind (dom, ran, loc)=
+ (L'.KArrow ((L'.KArrow (dom, ran), loc),
+ (L'.KArrow ((L'.KRecord dom, loc),
+ (L'.KRecord ran, loc)), loc)), loc)
+
+ fun hnormKind (kAll as (k, _)) =
+ case k of
+ L'.KUnif (_, _, ref (L'.KKnown k)) => hnormKind k
+ | L'.KTupleUnif (_, _, ref (L'.KKnown k)) => hnormKind k
+ | _ => kAll
+
+ open ElabOps
+
+ fun elabConHead env (c as (_, loc)) k =
+ let
+ fun unravel (k, c) =
+ case hnormKind k of
+ (L'.KFun (x, k'), _) =>
+ let
+ val u = kunif env loc
+
+ val k'' = subKindInKind (0, u) k'
+ in
+ unravel (k'', (L'.CKApp (c, u), loc))
+ end
+ | _ => (c, k)
+ in
+ unravel (k, c)
+ end
+
+ fun elabCon (env, denv) (c, loc) =
+ case c of
+ L.CAnnot (c, k) =>
+ let
+ val k' = elabKind env k
+ val (c', ck, gs) = elabCon (env, denv) c
+ in
+ checkKind env c' ck k';
+ (c', k', gs)
+ end
+
+ | L.TFun (t1, t2) =>
+ let
+ val (t1', k1, gs1) = elabCon (env, denv) t1
+ val (t2', k2, gs2) = elabCon (env, denv) t2
+ in
+ checkKind env t1' k1 ktype;
+ checkKind env t2' k2 ktype;
+ ((L'.TFun (t1', t2'), loc), ktype, gs1 @ gs2)
+ end
+ | L.TCFun (e, x, k, t) =>
+ let
+ val e' = elabExplicitness e
+ val k' = elabKind env k
+ val env' = E.pushCRel env x k'
+ val (t', tk, gs) = elabCon (env', D.enter denv) t
+ in
+ checkKind env t' tk ktype;
+ ((L'.TCFun (e', x, k', t'), loc), ktype, gs)
+ end
+ | L.TKFun (x, t) =>
+ let
+ val env' = E.pushKRel env x
+ val (t', tk, gs) = elabCon (env', denv) t
+ in
+ checkKind env t' tk ktype;
+ ((L'.TKFun (x, t'), loc), ktype, gs)
+ end
+ | L.TDisjoint (c1, c2, c) =>
+ let
+ val (c1', k1, gs1) = elabCon (env, denv) c1
+ val (c2', k2, gs2) = elabCon (env, denv) c2
+
+ val ku1 = kunif env loc
+ val ku2 = kunif env loc
+
+ val denv' = D.assert env denv (c1', c2')
+ val (c', k, gs4) = elabCon (env, denv') c
+ in
+ checkKind env c1' k1 (L'.KRecord ku1, loc);
+ checkKind env c2' k2 (L'.KRecord ku2, loc);
+ checkKind env c' k (L'.KType, loc);
+
+ ((L'.TDisjoint (c1', c2', c'), loc), k, gs1 @ gs2 @ gs4)
+ end
+ | L.TRecord c =>
+ let
+ val (c', ck, gs) = elabCon (env, denv) c
+ val k = (L'.KRecord ktype, loc)
+ in
+ checkKind env c' ck k;
+ ((L'.TRecord c', loc), ktype, gs)
+ end
+
+ | L.CVar ([], s) =>
+ (case E.lookupC env s of
+ E.NotBound =>
+ (conError env (UnboundCon (loc, s));
+ (cerror, kerror, []))
+ | E.Rel (n, k) =>
+ let
+ val (c, k) = elabConHead env (L'.CRel n, loc) k
+ in
+ (c, k, [])
+ end
+ | E.Named (n, k) =>
+ let
+ val (c, k) = elabConHead env (L'.CNamed n, loc) k
+ in
+ (c, k, [])
+ end)
+ | L.CVar (m1 :: ms, s) =>
+ (case E.lookupStr env m1 of
+ NONE => (conError env (UnboundStrInCon (loc, m1));
+ (cerror, kerror, []))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => (conError env (UnboundStrInCon (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+
+ val (c, k) = case E.projectCon env {sgn = sgn, str = str, field = s} of
+ NONE => (conError env (UnboundCon (loc, s));
+ (cerror, kerror))
+ | SOME (k, _) => elabConHead env (L'.CModProj (n, ms, s), loc) k
+ in
+ (c, k, [])
+ end)
+
+ | L.CApp (c1, c2) =>
+ let
+ val (c1', k1, gs1) = elabCon (env, denv) c1
+ val (c2', k2, gs2) = elabCon (env, denv) c2
+ val dom = kunif env loc
+ val ran = kunif env loc
+ in
+ checkKind env c1' k1 (L'.KArrow (dom, ran), loc);
+ checkKind env c2' k2 dom;
+ ((L'.CApp (c1', c2'), loc), ran, gs1 @ gs2)
+ end
+ | L.CAbs (x, ko, t) =>
+ let
+ val k' = case ko of
+ NONE => kunif env loc
+ | SOME k => elabKind env k
+ val env' = E.pushCRel env x k'
+ val (t', tk, gs) = elabCon (env', D.enter denv) t
+ in
+ ((L'.CAbs (x, k', t'), loc),
+ (L'.KArrow (k', tk), loc),
+ gs)
+ end
+ | L.CKAbs (x, t) =>
+ let
+ val env' = E.pushKRel env x
+ val (t', tk, gs) = elabCon (env', denv) t
+ in
+ ((L'.CKAbs (x, t'), loc),
+ (L'.KFun (x, tk), loc),
+ gs)
+ end
+
+ | L.CName s =>
+ ((L'.CName s, loc), kname, [])
+
+ | L.CRecord xcs =>
+ let
+ val k = kunif env loc
+
+ val (xcs', gs) = ListUtil.foldlMap (fn ((x, c), gs) =>
+ let
+ val (x', xk, gs1) = elabCon (env, denv) x
+ val (c', ck, gs2) = elabCon (env, denv) c
+ in
+ checkKind env x' xk kname;
+ checkKind env c' ck k;
+ ((x', c'), gs1 @ gs2 @ gs)
+ end) [] xcs
+
+ val rc = (L'.CRecord (k, xcs'), loc)
+ (* Add duplicate field checking later. *)
+
+ fun prove (xcs, ds) =
+ case xcs of
+ [] => ds
+ | xc :: rest =>
+ let
+ val r1 = (L'.CRecord (k, [xc]), loc)
+ val ds = foldl (fn (xc', ds) =>
+ let
+ val r2 = (L'.CRecord (k, [xc']), loc)
+ in
+ D.prove env denv (r1, r2, loc) @ ds
+ end)
+ ds rest
+ in
+ prove (rest, ds)
+ end
+ in
+ (rc, (L'.KRecord k, loc), prove (xcs', gs))
+ end
+ | L.CConcat (c1, c2) =>
+ let
+ val (c1', k1, gs1) = elabCon (env, denv) c1
+ val (c2', k2, gs2) = elabCon (env, denv) c2
+ val ku = kunif env loc
+ val k = (L'.KRecord ku, loc)
+ in
+ checkKind env c1' k1 k;
+ checkKind env c2' k2 k;
+ ((L'.CConcat (c1', c2'), loc), k,
+ D.prove env denv (c1', c2', loc) @ gs1 @ gs2)
+ end
+ | L.CMap =>
+ let
+ val dom = kunif env loc
+ val ran = kunif env loc
+ in
+ ((L'.CMap (dom, ran), loc),
+ mapKind (dom, ran, loc),
+ [])
+ end
+
+ | L.CUnit => ((L'.CUnit, loc), (L'.KUnit, loc), [])
+
+ | L.CTuple cs =>
+ let
+ val (cs', ks, gs) = foldl (fn (c, (cs', ks, gs)) =>
+ let
+ val (c', k, gs') = elabCon (env, denv) c
+ in
+ (c' :: cs', k :: ks, gs' @ gs)
+ end) ([], [], []) cs
+ in
+ ((L'.CTuple (rev cs'), loc), (L'.KTuple (rev ks), loc), gs)
+ end
+ | L.CProj (c, n) =>
+ let
+ val (c', k, gs) = elabCon (env, denv) c
+
+ val k' = kunif env loc
+ in
+ if n <= 0 then
+ (conError env (ProjBounds (c', n));
+ (cerror, kerror, []))
+ else
+ (checkKind env c' k (L'.KTupleUnif (loc, [(n, k')], ref (L'.KUnknown (validateKind env))), loc);
+ ((L'.CProj (c', n), loc), k', gs))
+ end
+
+ | L.CWild k =>
+ let
+ val k' = elabKind env k
+ in
+ (cunif env (loc, k'), k', [])
+ end
+
+ fun kunifsRemain k =
+ case k of
+ L'.KUnif (_, _, ref (L'.KUnknown _)) => true
+ | L'.KTupleUnif (_, _, ref (L'.KUnknown _)) => true
+ | _ => false
+ fun cunifsRemain c =
+ case c of
+ L'.CUnif (_, loc, k, _, r as ref (L'.Unknown _)) =>
+ (case #1 (hnormKind k) of
+ L'.KUnit => (r := L'.Known (L'.CUnit, loc); false)
+ | _ => true)
+ | _ => false
+
+ val kunifsInDecl = U.Decl.exists {kind = kunifsRemain,
+ con = fn _ => false,
+ exp = fn _ => false,
+ sgn_item = fn _ => false,
+ sgn = fn _ => false,
+ str = fn _ => false,
+ decl = fn _ => false}
+
+ val cunifsInDecl = U.Decl.exists {kind = fn _ => false,
+ con = cunifsRemain,
+ exp = fn _ => false,
+ sgn_item = fn _ => false,
+ sgn = fn _ => false,
+ str = fn _ => false,
+ decl = fn _ => false}
+
+ fun occursCon r =
+ U.Con.exists {kind = fn _ => false,
+ con = fn L'.CUnif (_, _, _, _, r') => r = r'
+ | _ => false}
+
+ exception CUnify' of E.env * cunify_error
+
+ type record_summary = {
+ fields : (L'.con * L'.con) list,
+ unifs : (L'.con * L'.cunif ref) list,
+ others : L'.con list
+ }
+
+ fun summaryToCon {fields, unifs, others} =
+ let
+ fun concat (c1, c2) =
+ case #1 c1 of
+ L'.CRecord (_, []) => c2
+ | _ => case #1 c2 of
+ L'.CRecord (_, []) => c1
+ | _ => (L'.CConcat (c1, c2), dummy)
+
+ val c = (L'.CRecord (ktype, []), dummy)
+ val c = List.foldr concat c others
+ val c = List.foldr (fn ((c', _), c) => concat (c', c)) c unifs
+ in
+ concat ((L'.CRecord (ktype, fields), dummy), c)
+ end
+
+ fun p_summary env s = p_con env (summaryToCon s)
+
+ exception CUnify of L'.con * L'.con * E.env * cunify_error
+
+ fun kindof env (c, loc) =
+ case c of
+ L'.TFun _ => ktype
+ | L'.TCFun _ => ktype
+ | L'.TRecord _ => ktype
+ | L'.TDisjoint _ => ktype
+
+ | L'.CRel xn => #2 (E.lookupCRel env xn)
+ | L'.CNamed xn => #2 (E.lookupCNamed env xn)
+ | L'.CModProj (n, ms, x) =>
+ let
+ val (_, sgn) = E.lookupStrNamed env n
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => raise Fail "kindof: Unknown substructure"
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+ in
+ case E.projectCon env {sgn = sgn, str = str, field = x} of
+ NONE => raise Fail "kindof: Unknown con in structure"
+ | SOME (k, _) => k
+ end
+
+ | L'.CApp (c, _) =>
+ (case hnormKind (kindof env c) of
+ (L'.KArrow (_, k), _) => k
+ | (L'.KError, _) => kerror
+ | k => raise CUnify' (env, CKindof (k, c, "arrow")))
+ | L'.CAbs (x, k, c) => (L'.KArrow (k, kindof (E.pushCRel env x k) c), loc)
+
+
+ | L'.CName _ => kname
+
+ | L'.CRecord (k, _) => (L'.KRecord k, loc)
+ | L'.CConcat (c, _) => kindof env c
+ | L'.CMap (dom, ran) => mapKind (dom, ran, loc)
+
+ | L'.CUnit => (L'.KUnit, loc)
+
+ | L'.CTuple cs => (L'.KTuple (map (kindof env) cs), loc)
+ | L'.CProj (c, n) =>
+ (case hnormKind (kindof env c) of
+ (L'.KTuple ks, _) => List.nth (ks, n - 1)
+ | (L'.KUnif (_, _, r), _) =>
+ let
+ val ku = kunif env loc
+ val k = (L'.KTupleUnif (loc, [(n, ku)], ref (L'.KUnknown (fn _ => true))), loc)
+ in
+ r := L'.KKnown k;
+ ku
+ end
+ | (L'.KTupleUnif (_, nks, r), _) =>
+ (case ListUtil.search (fn (n', k) => if n' = n then SOME k else NONE) nks of
+ SOME k => k
+ | NONE =>
+ let
+ val ku = kunif env loc
+ val k = (L'.KTupleUnif (loc, ((n, ku) :: nks), ref (L'.KUnknown (fn _ => true))), loc)
+ in
+ r := L'.KKnown k;
+ ku
+ end)
+ | k => raise CUnify' (env, CKindof (k, c, "tuple")))
+
+ | L'.CError => kerror
+ | L'.CUnif (_, _, k, _, _) => k
+
+ | L'.CKAbs (x, c) => (L'.KFun (x, kindof (E.pushKRel env x) c), loc)
+ | L'.CKApp (c, k) =>
+ (case hnormKind (kindof env c) of
+ (L'.KFun (_, k'), _) => subKindInKind (0, k) k'
+ | k => raise CUnify' (env, CKindof (k, c, "kapp")))
+ | L'.TKFun _ => ktype
+
+ exception GuessFailure
+
+ fun isUnitCon env (c, loc) =
+ case c of
+ L'.TFun _ => false
+ | L'.TCFun _ => false
+ | L'.TRecord _ => false
+ | L'.TDisjoint _ => false
+
+ | L'.CRel xn => #1 (hnormKind (#2 (E.lookupCRel env xn))) = L'.KUnit
+ | L'.CNamed xn => #1 (hnormKind (#2 (E.lookupCNamed env xn))) = L'.KUnit
+ | L'.CModProj (n, ms, x) => false
+ (*let
+ val (_, sgn) = E.lookupStrNamed env n
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => raise Fail "kindof: Unknown substructure"
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+ in
+ case E.projectCon env {sgn = sgn, str = str, field = x} of
+ NONE => raise Fail "kindof: Unknown con in structure"
+ | SOME ((k, _), _) => k = L'.KUnit
+ end*)
+
+ | L'.CApp (c, _) => false
+ (*(case hnormKind (kindof env c) of
+ (L'.KArrow (_, k), _) => #1 k = L'.KUnit
+ | (L'.KError, _) => false
+ | k => raise CUnify' (CKindof (k, c, "arrow")))*)
+ | L'.CAbs _ => false
+
+ | L'.CName _ => false
+
+ | L'.CRecord _ => false
+ | L'.CConcat _ => false
+ | L'.CMap _ => false
+
+ | L'.CUnit => true
+
+ | L'.CTuple _ => false
+ | L'.CProj (c, n) => false
+ (*(case hnormKind (kindof env c) of
+ (L'.KTuple ks, _) => #1 (List.nth (ks, n - 1)) = L'.KUnit
+ | k => raise CUnify' (CKindof (k, c, "tuple")))*)
+
+ | L'.CError => false
+ | L'.CUnif (_, _, k, _, _) => #1 (hnormKind k) = L'.KUnit
+
+ | L'.CKAbs _ => false
+ | L'.CKApp _ => false
+ | L'.TKFun _ => false
+
+ val recdCounter = ref 0
+
+ val mayDelay = ref false
+ val delayedUnifs = ref ([] : (ErrorMsg.span * E.env * L'.kind * record_summary * record_summary) list)
+
+ val delayedExhaustives = ref ([] : (E.env * L'.con * L'.pat list * ErrorMsg.span) list)
+
+ exception CantSquish
+
+ fun squish by =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ L'.CRel xn =>
+ if xn < bound then
+ c
+ else if bound <= xn andalso xn < bound + by then
+ raise CantSquish
+ else
+ L'.CRel (xn - by)
+ | L'.CUnif _ => raise CantSquish
+ | _ => c,
+ bind = fn (bound, U.Con.RelC _) => bound + 1
+ | (bound, _) => bound} 0
+
+ val reducedSummaries = ref (NONE : (Print.PD.pp_desc * Print.PD.pp_desc) option)
+
+ fun unifyRecordCons env (loc, c1, c2) =
+ let
+ fun rkindof c =
+ case hnormKind (kindof env c) of
+ (L'.KRecord k, _) => k
+ | (L'.KError, _) => kerror
+ | (L'.KUnif (_, _, r as ref (L'.KUnknown f)), _) =>
+ let
+ val k = kunif' f (#2 c)
+ in
+ r := L'.KKnown (L'.KRecord k, #2 c);
+ k
+ end
+ | k => raise CUnify' (env, CKindof (k, c, "record"))
+
+ val k1 = rkindof c1
+ val k2 = rkindof c2
+
+ val r1 = recordSummary env c1
+ val r2 = recordSummary env c2
+ in
+ unifyKinds env k1 k2;
+ unifySummaries env (loc, k1, r1, r2)
+ end
+
+ and normalizeRecordSummary env (r : record_summary) =
+ recordSummary env (summaryToCon r)
+
+ and recordSummary env c =
+ let
+ val c = hnormCon env c
+
+ val sum =
+ case c of
+ (L'.CRecord (_, xcs), _) => {fields = map (fn (x, c) => (hnormCon env x, hnormCon env c)) xcs,
+ unifs = [], others = []}
+ | (L'.CConcat (c1, c2), _) =>
+ let
+ val s1 = recordSummary env c1
+ val s2 = recordSummary env c2
+ in
+ {fields = #fields s1 @ #fields s2,
+ unifs = #unifs s1 @ #unifs s2,
+ others = #others s1 @ #others s2}
+ end
+ | (L'.CUnif (nl, _, _, _, ref (L'.Known c)), _) => recordSummary env (E.mliftConInCon nl c)
+ | c' as (L'.CUnif (0, _, _, _, r), _) => {fields = [], unifs = [(c', r)], others = []}
+ | c' => {fields = [], unifs = [], others = [c']}
+ in
+ sum
+ end
+
+ and consEq env loc (c1, c2) =
+ let
+ val mayDelay' = !mayDelay
+ in
+ (mayDelay := false;
+ unifyCons env loc c1 c2;
+ mayDelay := mayDelay';
+ true)
+ handle CUnify _ => (mayDelay := mayDelay'; false)
+ end
+
+ and consNeq env (c1, c2) =
+ case (#1 (hnormCon env c1), #1 (hnormCon env c2)) of
+ (L'.CName x1, L'.CName x2) => x1 <> x2
+ | (L'.CName _, L'.CRel _) => true
+ | (L'.CRel _, L'.CName _) => true
+ | (L'.CRel n1, L'.CRel n2) => n1 <> n2
+ | (L'.CRel _, L'.CNamed _) => true
+ | (L'.CNamed _, L'.CRel _) => true
+ | (L'.CRel _, L'.CModProj _) => true
+ | (L'.CModProj _, L'.CRel _) => true
+ | (L'.CModProj (_, _, n1), L'.CModProj (_, _, n2)) => n1 <> n2
+ | (L'.CModProj _, L'.CName _) => true
+ | (L'.CName _, L'.CModProj _) => true
+ | (L'.CNamed _, L'.CName _) => true
+ | (L'.CName _, L'.CNamed _) => true
+ | _ => false
+
+ and unifySummaries env (loc, k, s1 : record_summary, s2 : record_summary) =
+ let
+ val () = reducedSummaries := NONE
+
+ (*val () = eprefaces "Summaries" [("loc", PD.string (ErrorMsg.spanToString loc)),
+ ("#1", p_summary env s1),
+ ("#2", p_summary env s2)]*)
+
+ fun eatMatching p (ls1, ls2) =
+ let
+ fun em (ls1, ls2, passed1) =
+ case ls1 of
+ [] => (rev passed1, ls2)
+ | h1 :: t1 =>
+ let
+ fun search (ls2', passed2) =
+ case ls2' of
+ [] => em (t1, ls2, h1 :: passed1)
+ | h2 :: t2 =>
+ if p (h1, h2) then
+ em (t1, List.revAppend (passed2, t2), passed1)
+ else
+ search (t2, h2 :: passed2)
+ in
+ search (ls2, [])
+ end
+ in
+ em (ls1, ls2, [])
+ end
+
+ val (fs1, fs2) = eatMatching (fn ((x1, c1), (x2, c2)) =>
+ not (consNeq env (x1, x2))
+ andalso consEq env loc (c1, c2)
+ andalso consEq env loc (x1, x2))
+ (#fields s1, #fields s2)
+ (*val () = eprefaces "Summaries2" [("#1", p_summary env {fields = fs1, unifs = #unifs s1, others = #others s1}),
+ ("#2", p_summary env {fields = fs2, unifs = #unifs s2, others = #others s2})]*)
+
+ val (unifs1, unifs2) = eatMatching (fn ((_, r1), (_, r2)) => r1 = r2) (#unifs s1, #unifs s2)
+
+ val hasUnifs = U.Con.exists {kind = fn _ => false,
+ con = fn L'.CUnif _ => true
+ | _ => false}
+
+ val (others1, others2) = eatMatching (fn (c1, c2) =>
+ c1 = c2
+ orelse (not (hasUnifs c1 andalso hasUnifs c2)
+ andalso consEq env loc (c1, c2))) (#others s1, #others s2)
+ (*val () = eprefaces "Summaries3" [("#1", p_summary env {fields = fs1, unifs = unifs1, others = others1}),
+ ("#2", p_summary env {fields = fs2, unifs = unifs2, others = others2})]*)
+
+ fun unsummarize {fields, unifs, others} =
+ let
+ val c = (L'.CRecord (k, fields), loc)
+
+ val c = foldl (fn ((c1, _), c2) => (L'.CConcat (c1, c2), loc))
+ c unifs
+ in
+ foldl (fn (c1, c2) => (L'.CConcat (c1, c2), loc))
+ c others
+ end
+
+ val empties = ([], [], [], [], [], [])
+
+ val (unifs1, fs1, others1, unifs2, fs2, others2) =
+ case (unifs1, fs1, others1, unifs2, fs2, others2) of
+ orig as ([(_, r as ref (L'.Unknown f))], [], [], _, _, _) =>
+ let
+ val c = unsummarize {fields = fs2, others = others2, unifs = unifs2}
+ in
+ if occursCon r c orelse not (f c) then
+ orig
+ else
+ (r := L'.Known c;
+ empties)
+ end
+ | orig as (_, _, _, [(_, r as ref (L'.Unknown f))], [], []) =>
+ let
+ val c = unsummarize {fields = fs1, others = others1, unifs = unifs1}
+ in
+ if occursCon r c orelse not (f c) then
+ orig
+ else
+ (r := L'.Known c;
+ empties)
+ end
+ | orig as ([(_, r1 as ref (L'.Unknown f1))], _, [], [(_, r2 as ref (L'.Unknown f2))], _, []) =>
+ if List.all (fn (x1, _) => List.all (fn (x2, _) => consNeq env (x1, x2)) fs2) fs1 then
+ let
+ val kr = (L'.KRecord k, loc)
+ val u = cunif env (loc, kr)
+
+ val c1 = (L'.CConcat ((L'.CRecord (k, fs2), loc), u), loc)
+ val c2 = (L'.CConcat ((L'.CRecord (k, fs1), loc), u), loc)
+ in
+ if not (f1 c1) orelse not (f2 c2) then
+ orig
+ else
+ (r1 := L'.Known c1;
+ r2 := L'.Known c2;
+ empties)
+ end
+ else
+ orig
+ | orig => orig
+
+ (*val () = eprefaces "Summaries4" [("#1", p_summary env {fields = fs1, unifs = unifs1, others = others1}),
+ ("#2", p_summary env {fields = fs2, unifs = unifs2, others = others2})]*)
+
+ fun isGuessable (other, fs, unifs) =
+ let
+ val c = (L'.CRecord (k, fs), loc)
+ val c = foldl (fn ((c', _), c) => (L'.CConcat (c', c), loc)) c unifs
+ in
+ (guessMap env loc (other, c, GuessFailure);
+ true)
+ handle GuessFailure => false
+ end
+
+ val (fs1, fs2, others1, others2, unifs1, unifs2) =
+ case (fs1, fs2, others1, others2, unifs1, unifs2) of
+ ([], _, [other1], [], [], _) =>
+ if isGuessable (other1, fs2, unifs2) then
+ ([], [], [], [], [], [])
+ else
+ (fs1, fs2, others1, others2, unifs1, unifs2)
+ | (_, [], [], [other2], _, []) =>
+ if isGuessable (other2, fs1, unifs1) then
+ ([], [], [], [], [], [])
+ else
+ (fs1, fs2, others1, others2, unifs1, unifs2)
+ | _ => (fs1, fs2, others1, others2, unifs1, unifs2)
+
+ val () = if !mayDelay then
+ ()
+ else
+ let
+ val c1 = summaryToCon {fields = fs1, unifs = unifs1, others = others1}
+ val c2 = summaryToCon {fields = fs2, unifs = unifs2, others = others2}
+ in
+ case (c1, c2) of
+ ((L'.CRecord (_, []), _), (L'.CRecord (_, []), _)) => reducedSummaries := NONE
+ | _ => reducedSummaries := SOME (p_con env c1, p_con env c2)
+ end
+
+ (*val () = eprefaces "Summaries5" [("#1", p_summary env {fields = fs1, unifs = unifs1, others = others1}),
+ ("#2", p_summary env {fields = fs2, unifs = unifs2, others = others2})]*)
+
+ val empty = (L'.CRecord (k, []), loc)
+ fun failure () =
+ let
+ val fs2 = #fields s2
+
+ fun findPointwise fs1 =
+ case fs1 of
+ [] => NONE
+ | (nm1, c1) :: fs1 =>
+ case List.find (fn (nm2, _) => consEq env loc (nm1, nm2)) fs2 of
+ NONE => findPointwise fs1
+ | SOME (_, c2) =>
+ if consEq env loc (c1, c2) then
+ findPointwise fs1
+ else
+ SOME (nm1, c1, c2, (unifyCons env loc c1 c2; NONE)
+ handle CUnify (_, _, env', err) => (reducedSummaries := NONE;
+ SOME (env', err)))
+ in
+ raise CUnify' (env, CRecordFailure (unsummarize s1, unsummarize s2, findPointwise (#fields s1)))
+ end
+
+ fun default () = if !mayDelay then
+ delayedUnifs := (loc, env, k, s1, s2) :: !delayedUnifs
+ else
+ failure ()
+ in
+ (case (unifs1, fs1, others1, unifs2, fs2, others2) of
+ (_, [], [], [], [], []) =>
+ app (fn (_, r) => r := L'.Known empty) unifs1
+ | ([], [], [], _, [], []) =>
+ app (fn (_, r) => r := L'.Known empty) unifs2
+ | (_, _, _, [], [], [cr as (L'.CUnif (nl, _, _, _, r as ref (L'.Unknown f)), _)]) =>
+ let
+ val c = summaryToCon {fields = fs1, unifs = unifs1, others = others1}
+ in
+ if occursCon r c then
+ (reducedSummaries := NONE;
+ raise CUnify' (env, COccursCheckFailed (cr, c)))
+ else
+ let
+ val sq = squish nl c
+ in
+ if not (f sq) then
+ default ()
+ else
+ r := L'.Known sq
+ end
+ handle CantSquish => default ()
+ end
+ | ([], [], [cr as (L'.CUnif (nl, _, _, _, r as ref (L'.Unknown f)), _)], _, _, _) =>
+ let
+ val c = summaryToCon {fields = fs2, unifs = unifs2, others = others2}
+ in
+ if occursCon r c then
+ (reducedSummaries := NONE;
+ raise CUnify' (env, COccursCheckFailed (cr, c)))
+ else
+ let
+ val sq = squish nl c
+ in
+ if not (f sq) then
+ default ()
+ else
+ r := L'.Known sq
+ end
+ handle CantSquish => default ()
+ end
+ | _ => default ())
+
+ (*before eprefaces "Summaries'" [("#1", p_summary env (normalizeRecordSummary env s1)),
+ ("#2", p_summary env (normalizeRecordSummary env s2))]*)
+ end
+
+ and guessMap env loc (c1, c2, ex) =
+ let
+ fun unfold (dom, ran, f, r, c) =
+ let
+ fun unfold (r, c) =
+ case #1 (hnormCon env c) of
+ L'.CRecord (_, []) => unifyCons env loc r (L'.CRecord (dom, []), loc)
+ | L'.CRecord (_, [(x, v)]) =>
+ let
+ val v' = case dom of
+ (L'.KUnit, _) => (L'.CUnit, loc)
+ | _ => cunif env (loc, dom)
+ in
+ unifyCons env loc v (L'.CApp (f, v'), loc);
+ unifyCons env loc r (L'.CRecord (dom, [(x, v')]), loc)
+ end
+ | L'.CRecord (_, (x, v) :: rest) =>
+ let
+ val r1 = cunif env (loc, (L'.KRecord dom, loc))
+ val r2 = cunif env (loc, (L'.KRecord dom, loc))
+ in
+ unfold (r1, (L'.CRecord (ran, [(x, v)]), loc));
+ unfold (r2, (L'.CRecord (ran, rest), loc));
+ unifyCons env loc r (L'.CConcat (r1, r2), loc)
+ end
+ | L'.CConcat (c1', c2') =>
+ let
+ val r1 = cunif env (loc, (L'.KRecord dom, loc))
+ val r2 = cunif env (loc, (L'.KRecord dom, loc))
+ in
+ unfold (r1, c1');
+ unfold (r2, c2');
+ unifyCons env loc r (L'.CConcat (r1, r2), loc)
+ end
+ | L'.CUnif (0, _, _, _, ur as ref (L'.Unknown rf)) =>
+ let
+ val c' = (L'.CApp ((L'.CApp ((L'.CMap (dom, ran), loc), f), loc), r), loc)
+ in
+ if not (rf c') then
+ cunifyError env (CScope (c, c'))
+ else
+ ur := L'.Known c'
+ end
+ | _ => raise ex
+ in
+ unfold (r, c)
+ end
+ handle _ => raise ex
+ in
+ case (#1 c1, #1 c2) of
+ (L'.CApp ((L'.CApp ((L'.CMap (dom, ran), _), f), _), r), _) =>
+ unfold (dom, ran, f, r, c2)
+ | (_, L'.CApp ((L'.CApp ((L'.CMap (dom, ran), _), f), _), r)) =>
+ unfold (dom, ran, f, r, c1)
+ | _ => raise ex
+ end
+
+ and unifyCons' env loc c1 c2 =
+ if isUnitCon env c1 andalso isUnitCon env c2 then
+ ()
+ else
+ let
+ (*val befor = Time.now ()
+ val old1 = c1
+ val old2 = c2*)
+ val c1 = hnormCon env c1
+ val c2 = hnormCon env c2
+ in
+ unifyCons'' env loc c1 c2
+ handle ex => guessMap env loc (c1, c2, ex)
+ end
+
+ and unifyCons'' env loc (c1All as (c1, _)) (c2All as (c2, _)) =
+ let
+ fun err f = raise CUnify' (env, f (c1All, c2All))
+
+ fun projSpecial1 (c1, n1, onFail) =
+ let
+ fun trySnd () =
+ case #1 (hnormCon env c2All) of
+ L'.CProj (c2, n2) =>
+ let
+ fun tryNormal () =
+ if n1 = n2 then
+ unifyCons' env loc c1 c2
+ else
+ onFail ()
+ in
+ case #1 (hnormCon env c2) of
+ L'.CUnif (0, _, k, _, r as ref (L'.Unknown f)) =>
+ (case #1 (hnormKind k) of
+ L'.KTuple ks =>
+ let
+ val loc = #2 c2
+ val us = map (fn k => cunif' f (loc, k)) ks
+ in
+ r := L'.Known (L'.CTuple us, loc);
+ unifyCons' env loc c1All (List.nth (us, n2 - 1))
+ end
+ | _ => tryNormal ())
+ | _ => tryNormal ()
+ end
+ | _ => onFail ()
+ in
+ case #1 (hnormCon env c1) of
+ L'.CUnif (0, _, k, _, r as ref (L'.Unknown f)) =>
+ (case #1 (hnormKind k) of
+ L'.KTuple ks =>
+ let
+ val loc = #2 c1
+ val us = map (fn k => cunif' f (loc, k)) ks
+ in
+ r := L'.Known (L'.CTuple us, loc);
+ unifyCons' env loc (List.nth (us, n1 - 1)) c2All
+ end
+ | _ => trySnd ())
+ | _ => trySnd ()
+ end
+
+ fun projSpecial2 (c2, n2, onFail) =
+ case #1 (hnormCon env c2) of
+ L'.CUnif (0, _, k, _, r as ref (L'.Unknown f)) =>
+ (case #1 (hnormKind k) of
+ L'.KTuple ks =>
+ let
+ val loc = #2 c2
+ val us = map (fn k => cunif' f (loc, k)) ks
+ in
+ r := L'.Known (L'.CTuple us, loc);
+ unifyCons' env loc c1All (List.nth (us, n2 - 1))
+ end
+ | _ => onFail ())
+ | _ => onFail ()
+
+ fun isRecord' () = unifyRecordCons env (loc, c1All, c2All)
+
+ fun isRecord () =
+ case (c1, c2) of
+ (L'.CProj (c1, n1), _) => projSpecial1 (c1, n1, isRecord')
+ | (_, L'.CProj (c2, n2)) => projSpecial2 (c2, n2, isRecord')
+ | _ => isRecord' ()
+
+ fun maybeIsRecord c =
+ case c of
+ L'.CRecord _ => isRecord ()
+ | L'.CConcat _ => isRecord ()
+ | _ => err COccursCheckFailed
+ in
+ (*eprefaces "unifyCons''" [("c1", p_con env c1All),
+ ("c2", p_con env c2All)];*)
+
+ (case (c1, c2) of
+ (L'.CError, _) => ()
+ | (_, L'.CError) => ()
+
+ | (L'.CUnif (nl1, loc1, k1, _, r1 as ref (L'.Unknown f1)), L'.CUnif (nl2, loc2, k2, _, r2 as ref (L'.Unknown f2))) =>
+ if r1 = r2 then
+ if nl1 = nl2 then
+ ()
+ else
+ err (fn _ => TooLifty (loc1, loc2))
+ else if nl1 = 0 then
+ (unifyKinds env k1 k2;
+ if f1 c2All then
+ r1 := L'.Known c2All
+ else
+ err CScope)
+ else if nl2 = 0 then
+ (unifyKinds env k1 k2;
+ if f2 c1All then
+ r2 := L'.Known c1All
+ else
+ err CScope)
+ else
+ err (fn _ => TooLifty (loc1, loc2))
+
+ | (L'.CUnif (0, _, k1, _, r as ref (L'.Unknown f)), _) =>
+ (unifyKinds env k1 (kindof env c2All);
+ if occursCon r c2All then
+ maybeIsRecord c2
+ else if f c2All then
+ r := L'.Known c2All
+ else
+ err CScope)
+ | (_, L'.CUnif (0, _, k2, _, r as ref (L'.Unknown f))) =>
+ (unifyKinds env (kindof env c1All) k2;
+ if occursCon r c1All then
+ maybeIsRecord c1
+ else if f c1All then
+ r := L'.Known c1All
+ else
+ err CScope)
+
+ | (L'.CUnif (nl, _, k1, _, r as ref (L'.Unknown f)), _) =>
+ if occursCon r c2All then
+ maybeIsRecord c2
+ else
+ (unifyKinds env k1 (kindof env c2All);
+ let
+ val sq = squish nl c2All
+ in
+ if f sq then
+ r := L'.Known sq
+ else
+ err CScope
+ end
+ handle CantSquish => err (fn _ => TooDeep))
+ | (_, L'.CUnif (nl, _, k2, _, r as ref (L'.Unknown f))) =>
+ if occursCon r c1All then
+ maybeIsRecord c1
+ else
+ (unifyKinds env (kindof env c1All) k2;
+ let
+ val sq = squish nl c1All
+ in
+ if f sq then
+ r := L'.Known sq
+ else
+ err CScope
+ end
+ handle CantSquish => err (fn _ => TooDeep))
+
+ | (L'.CRecord _, _) => isRecord ()
+ | (_, L'.CRecord _) => isRecord ()
+ | (L'.CConcat _, _) => isRecord ()
+ | (_, L'.CConcat _) => isRecord ()
+
+
+ | (L'.CUnit, L'.CUnit) => ()
+
+ | (L'.TFun (d1, r1), L'.TFun (d2, r2)) =>
+ (unifyCons' env loc d1 d2;
+ unifyCons' env loc r1 r2)
+ | (L'.TCFun (expl1, x1, d1, r1), L'.TCFun (expl2, _, d2, r2)) =>
+ if expl1 <> expl2 then
+ err CExplicitness
+ else
+ (unifyKinds env d1 d2;
+ let
+ (*val befor = Time.now ()*)
+ val env' = E.pushCRel env x1 d1
+ in
+ (*TextIO.print ("E.pushCRel: "
+ ^ LargeReal.toString (Time.toReal (Time.- (Time.now (), befor)))
+ ^ "\n");*)
+ unifyCons' env' loc r1 r2
+ end)
+ | (L'.TRecord r1, L'.TRecord r2) => unifyCons' env loc r1 r2
+ | (L'.TDisjoint (c1, d1, e1), L'.TDisjoint (c2, d2, e2)) =>
+ (unifyCons' env loc c1 c2;
+ unifyCons' env loc d1 d2;
+ unifyCons' env loc e1 e2)
+
+ | (L'.CRel n1, L'.CRel n2) =>
+ if n1 = n2 then
+ ()
+ else
+ err CIncompatible
+ | (L'.CNamed n1, L'.CNamed n2) =>
+ if n1 = n2 then
+ ()
+ else
+ err CIncompatible
+
+ | (L'.CApp (d1, r1), L'.CApp (d2, r2)) =>
+ (unifyCons' env loc d1 d2;
+ unifyCons' env loc r1 r2)
+ | (L'.CAbs (x1, k1, c1), L'.CAbs (_, k2, c2)) =>
+ (unifyKinds env k1 k2;
+ unifyCons' (E.pushCRel env x1 k1) loc c1 c2)
+
+ | (L'.CName n1, L'.CName n2) =>
+ if n1 = n2 then
+ ()
+ else
+ err CIncompatible
+
+ | (L'.CModProj (n1, ms1, x1), L'.CModProj (n2, ms2, x2)) =>
+ if n1 = n2 andalso ms1 = ms2 andalso x1 = x2 then
+ ()
+ else
+ err CIncompatible
+
+ | (L'.CTuple cs1, L'.CTuple cs2) =>
+ ((ListPair.appEq (fn (c1, c2) => unifyCons' env loc c1 c2) (cs1, cs2))
+ handle ListPair.UnequalLengths => err CIncompatible)
+
+ | (L'.CProj (c1, n1), _) => projSpecial1 (c1, n1, fn () => err CIncompatible)
+ | (_, L'.CProj (c2, n2)) => projSpecial2 (c2, n2, fn () => err CIncompatible)
+
+ | (L'.CTuple cs, L'.CRel x) =>
+ (case hnormKind (kindof env c2All) of
+ (L'.KTuple ks, _) =>
+ if length cs <> length ks then
+ err CIncompatible
+ else
+ let
+ fun rightProjs (cs, n) =
+ case cs of
+ c :: cs' =>
+ (case hnormCon env c of
+ (L'.CProj ((L'.CRel x', _), n'), _) =>
+ x' = x andalso n' = n andalso rightProjs (cs', n+1)
+ | _ => false)
+ | [] => true
+ in
+ if rightProjs (cs, 1) then
+ ()
+ else
+ err CIncompatible
+ end
+ | _ => err CIncompatible)
+ | (L'.CRel x, L'.CTuple cs) =>
+ unifyCons'' env loc c2All c1All
+
+ | (L'.CMap (dom1, ran1), L'.CMap (dom2, ran2)) =>
+ (unifyKinds env dom1 dom2;
+ unifyKinds env ran1 ran2)
+
+ | (L'.CKAbs (x, c1), L'.CKAbs (_, c2)) =>
+ unifyCons' (E.pushKRel env x) loc c1 c2
+ | (L'.CKApp (c1, k1), L'.CKApp (c2, k2)) =>
+ (unifyKinds env k1 k2;
+ unifyCons' env loc c1 c2)
+ | (L'.TKFun (x, c1), L'.TKFun (_, c2)) =>
+ unifyCons' (E.pushKRel env x) loc c1 c2
+
+ | _ => err CIncompatible)(*;
+ eprefaces "/unifyCons''" [("c1", p_con env c1All),
+ ("c2", p_con env c2All)]*)
+ end
+
+ and unifyCons env loc c1 c2 =
+ ((*Print.prefaces "uc" [("c1", p_con env c1),
+ ("c2", p_con env c2)];*)
+ unifyCons' env loc c1 c2)
+ handle CUnify' (env', err) => raise CUnify (c1, c2, env', err)
+ | KUnify (arg as {3 = env', ...}) => raise CUnify (c1, c2, env', CKind arg)
+
+ fun checkCon env e c1 c2 =
+ unifyCons env (#2 e) c1 c2
+ handle CUnify (c1, c2, env', err) =>
+ expError env (Unify (e, c1, c2, env', err))
+
+ fun checkPatCon env p c1 c2 =
+ unifyCons env (#2 p) c1 c2
+ handle CUnify (c1, c2, env', err) =>
+ expError env (PatUnify (p, c1, c2, env', err))
+
+ fun primType env p =
+ case p of
+ P.Int _ => !int
+ | P.Float _ => !float
+ | P.String _ => !string
+ | P.Char _ => !char
+
+ datatype constraint =
+ Disjoint of D.goal
+ | TypeClass of E.env * L'.con * L'.exp option ref * ErrorMsg.span
+
+ fun relocConstraint loc c =
+ case c of
+ Disjoint (_, a, b, c, d) => Disjoint (loc, a, b, c, d)
+ | TypeClass (a, b, c, _) => TypeClass (a, b, c, loc)
+
+ val enD = map Disjoint
+
+ fun isClassOrFolder env cl =
+ E.isClass env cl
+ orelse case hnormCon env cl of
+ (L'.CKApp (cl, _), _) =>
+ (case hnormCon env cl of
+ (L'.CModProj (top_n, [], "folder"), _) => top_n = !top_r
+ | _ => false)
+ | _ => false
+
+ fun subConInCon env x y =
+ ElabOps.subConInCon x y
+ handle SubUnif => (cunifyError env (TooUnify (#2 x, y));
+ cerror)
+
+ fun elabHead (env, denv) infer (e as (_, loc)) t =
+ let
+ fun unravelKind (t, e) =
+ case hnormCon env t of
+ (L'.TKFun (x, t'), _) =>
+ let
+ val u = kunif env loc
+
+ val t'' = subKindInCon (0, u) t'
+ in
+ unravelKind (t'', (L'.EKApp (e, u), loc))
+ end
+ | t => (e, t, [])
+
+ fun unravel (t, e) =
+ case hnormCon env t of
+ (L'.TKFun (x, t'), _) =>
+ let
+ val u = kunif env loc
+
+ val t'' = subKindInCon (0, u) t'
+ in
+ unravel (t'', (L'.EKApp (e, u), loc))
+ end
+ | (L'.TCFun (L'.Implicit, x, k, t'), _) =>
+ let
+ val u = cunif env (loc, k)
+
+ val t'' = subConInCon env (0, u) t'
+ in
+ unravel (t'', (L'.ECApp (e, u), loc))
+ end
+ | (L'.TFun (dom, ran), _) =>
+ let
+ fun default () = (e, t, [])
+
+ fun isInstance () =
+ if infer <> L.TypesOnly then
+ let
+ val r = ref NONE
+ val (e, t, gs) = unravel (ran, (L'.EApp (e, (L'.EUnif r, loc)), loc))
+ in
+ (e, t, TypeClass (env, dom, r, loc) :: gs)
+ end
+ else
+ default ()
+
+ fun hasInstance c =
+ case hnormCon env c of
+ (L'.TRecord c, _) => U.Con.exists {kind = fn _ => false,
+ con = fn c =>
+ isClassOrFolder env (hnormCon env (c, loc))} c
+ | c =>
+ let
+ fun findHead c =
+ case #1 c of
+ L'.CApp (f, _) => findHead f
+ | _ => c
+
+ val cl = hnormCon env (findHead c)
+ in
+ isClassOrFolder env cl
+ end
+ in
+ if hasInstance dom then
+ isInstance ()
+ else
+ default ()
+ end
+ | (L'.TDisjoint (r1, r2, t'), loc) =>
+ if infer <> L.TypesOnly then
+ let
+ val gs = D.prove env denv (r1, r2, #2 e)
+ val (e, t, gs') = unravel (t', e)
+ in
+ (e, t, enD gs @ gs')
+ end
+ else
+ (e, t, [])
+ | t => (e, t, [])
+
+ val (e, t, gs) = case infer of
+ L.DontInfer => unravelKind (t, e)
+ | _ => unravel (t, e)
+ in
+ ((#1 e, loc), (#1 t, loc), map (relocConstraint loc) gs)
+ end
+
+fun elabPat (pAll as (p, loc), (env, bound)) =
+ let
+ val terror = (L'.CError, loc)
+ val perror = (L'.PVar ("_", terror), loc)
+ val pterror = (perror, terror)
+ val rerror = (pterror, (env, bound))
+
+ fun pcon (pc, po, xs, to, dn, dk) =
+ case (po, to) of
+ (NONE, SOME _) => (expError env (PatHasNoArg loc);
+ rerror)
+ | (SOME _, NONE) => (expError env (PatHasArg loc);
+ rerror)
+ | (NONE, NONE) =>
+ let
+ val k = (L'.KType, loc)
+ val unifs = map (fn _ => cunif env (loc, k)) xs
+ val dn = foldl (fn (u, dn) => (L'.CApp (dn, u), loc)) dn unifs
+ in
+ (((L'.PCon (dk, pc, unifs, NONE), loc), dn),
+ (env, bound))
+ end
+ | (SOME p, SOME t) =>
+ let
+ val ((p', pt), (env, bound)) = elabPat (p, (env, bound))
+
+ val k = (L'.KType, loc)
+ val unifs = map (fn _ => cunif env (loc, k)) xs
+ val nxs = length unifs - 1
+ val t = ListUtil.foldli (fn (i, u, t) => subConInCon env (nxs - i,
+ E.mliftConInCon (nxs - i) u) t) t unifs
+ val dn = foldl (fn (u, dn) => (L'.CApp (dn, u), loc)) dn unifs
+ in
+ ignore (checkPatCon env p' pt t);
+ (((L'.PCon (dk, pc, unifs, SOME p'), loc), dn),
+ (env, bound))
+ end
+ in
+ case p of
+ L.PVar x =>
+ let
+ val t = if x <> "_" andalso SS.member (bound, x) then
+ (expError env (DuplicatePatternVariable (loc, x));
+ terror)
+ else
+ cunif env (loc, (L'.KType, loc))
+ in
+ (((L'.PVar (x, t), loc), t),
+ (E.pushERel env x t, SS.add (bound, x)))
+ end
+ | L.PPrim p => (((L'.PPrim p, loc), primType env p),
+ (env, bound))
+ | L.PCon ([], x, po) =>
+ (case E.lookupConstructor env x of
+ NONE => (expError env (UnboundConstructor (loc, [], x));
+ rerror)
+ | SOME (dk, n, xs, to, dn) => pcon (L'.PConVar n, po, xs, to, (L'.CNamed dn, loc), dk))
+ | L.PCon (m1 :: ms, x, po) =>
+ (case E.lookupStr env m1 of
+ NONE => (expError env (UnboundStrInExp (loc, m1));
+ rerror)
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => raise Fail "elabPat: Unknown substructure"
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+ in
+ case E.projectConstructor env {str = str, sgn = sgn, field = x} of
+ NONE => (expError env (UnboundConstructor (loc, m1 :: ms, x));
+ rerror)
+ | SOME (dk, _, xs, to, dn) => pcon (L'.PConProj (n, ms, x), po, xs, to, dn, dk)
+ end)
+
+ | L.PRecord (xps, flex) =>
+ let
+ val (xpts, (env, bound, _)) =
+ ListUtil.foldlMap (fn ((x, p), (env, bound, fbound)) =>
+ let
+ val ((p', t), (env, bound)) = elabPat (p, (env, bound))
+ in
+ if SS.member (fbound, x) then
+ expError env (DuplicatePatField (loc, x))
+ else
+ ();
+ ((x, p', t), (env, bound, SS.add (fbound, x)))
+ end)
+ (env, bound, SS.empty) xps
+
+ val k = (L'.KType, loc)
+ val c = (L'.CRecord (k, map (fn (x, _, t) => ((L'.CName x, loc), t)) xpts), loc)
+ val c =
+ if flex then
+ (L'.CConcat (c, cunif env (loc, (L'.KRecord k, loc))), loc)
+ else
+ c
+ in
+ (((L'.PRecord xpts, loc),
+ (L'.TRecord c, loc)),
+ (env, bound))
+ end
+
+ | L.PAnnot (p, t) =>
+ let
+ val ((p', pt), (env, bound)) = elabPat (p, (env, bound))
+ val (t', k, _) = elabCon (env, D.empty) t
+ in
+ checkPatCon env p' pt t';
+ ((p', t'), (env, bound))
+ end
+ end
+
+(* This exhaustiveness checking follows Luc Maranget's paper "Warnings for pattern matching." *)
+fun exhaustive (env, t, ps, loc) =
+ let
+ val pwild = L'.PVar ("_", t)
+
+ fun fail n = raise Fail ("Elaborate.exhaustive: Impossible " ^ Int.toString n)
+
+ fun patConNum pc =
+ case pc of
+ L'.PConVar n => n
+ | L'.PConProj (m1, ms, x) =>
+ let
+ val (str, sgn) = E.chaseMpath env (m1, ms)
+ in
+ case E.projectConstructor env {str = str, sgn = sgn, field = x} of
+ NONE => raise Fail "exhaustive: Can't project datatype"
+ | SOME (_, n, _, _, _) => n
+ end
+
+ fun nameOfNum (t, n) =
+ case t of
+ L'.CModProj (m1, ms, x) =>
+ let
+ val (str, sgn) = E.chaseMpath env (m1, ms)
+ in
+ case E.projectDatatype env {str = str, sgn = sgn, field = x} of
+ NONE => raise Fail "exhaustive: Can't project datatype"
+ | SOME (_, cons) =>
+ case ListUtil.search (fn (name, n', _) =>
+ if n' = n then
+ SOME name
+ else
+ NONE) cons of
+ NONE => fail 9
+ | SOME name => L'.PConProj (m1, ms, name)
+ end
+ | _ => L'.PConVar n
+
+ fun S (args, c, P) =
+ List.mapPartial
+ (fn [] => fail 1
+ | p1 :: ps =>
+ let
+ val loc = #2 p1
+
+ fun wild () =
+ SOME (map (fn _ => (pwild, loc)) args @ ps)
+ in
+ case #1 p1 of
+ L'.PPrim _ => NONE
+ | L'.PCon (_, c', _, NONE) =>
+ if patConNum c' = c then
+ SOME ps
+ else
+ NONE
+ | L'.PCon (_, c', _, SOME p) =>
+ if patConNum c' = c then
+ SOME (p :: ps)
+ else
+ NONE
+ | L'.PRecord xpts =>
+ SOME (map (fn x =>
+ case ListUtil.search (fn (x', p, _) =>
+ if x = x' then
+ SOME p
+ else
+ NONE) xpts of
+ NONE => (pwild, loc)
+ | SOME p => p) args @ ps)
+ | L'.PVar _ => wild ()
+ end)
+ P
+
+ fun D P =
+ List.mapPartial
+ (fn [] => fail 2
+ | (p1, _) :: ps =>
+ case p1 of
+ L'.PVar _ => SOME ps
+ | L'.PPrim _ => NONE
+ | L'.PCon _ => NONE
+ | L'.PRecord _ => NONE)
+ P
+
+ fun I (P, q) =
+ (*(prefaces "I" [("P", p_list (fn P' => box [PD.string "[", p_list (p_pat env) P', PD.string "]"]) P),
+ ("q", p_list (p_con env) q)];*)
+ case q of
+ [] => (case P of
+ [] => SOME []
+ | _ => NONE)
+ | q1 :: qs =>
+ let
+ val loc = #2 q1
+
+ fun unapp (t, acc) =
+ case #1 t of
+ L'.CApp (t, arg) => unapp (t, arg :: acc)
+ | _ => (t, rev acc)
+
+ val (t1, args) = unapp (hnormCon env q1, [])
+ val t1 = hnormCon env t1
+ fun doSub t = foldl (fn (arg, t) => subConInCon env (0, arg) t) t args
+
+ fun dtype (dtO, names) =
+ let
+ val nameSet = IS.addList (IS.empty, names)
+ val nameSet = foldl (fn (ps, nameSet) =>
+ case ps of
+ [] => fail 4
+ | (L'.PCon (_, pc, _, _), _) :: _ =>
+ (IS.delete (nameSet, patConNum pc)
+ handle NotFound => nameSet)
+ | _ => nameSet)
+ nameSet P
+ in
+ nameSet
+ end
+
+ fun default () = (NONE, IS.singleton 0, [])
+
+ val (dtO, unused, cons) =
+ case #1 t1 of
+ L'.CNamed n =>
+ let
+ val dt = E.lookupDatatype env n
+ val cons = E.constructors dt
+ in
+ (SOME dt,
+ dtype (SOME dt, map #2 cons),
+ map (fn (_, n, co) =>
+ (n,
+ case co of
+ NONE => []
+ | SOME t => [("", doSub t)])) cons)
+ end
+ | L'.CModProj (m1, ms, x) =>
+ let
+ val (str, sgn) = E.chaseMpath env (m1, ms)
+ in
+ case E.projectDatatype env {str = str, sgn = sgn, field = x} of
+ NONE => default ()
+ | SOME (_, cons) =>
+ (NONE,
+ dtype (NONE, map #2 cons),
+ map (fn (s, _, co) =>
+ (patConNum (L'.PConProj (m1, ms, s)),
+ case co of
+ NONE => []
+ | SOME t => [("", doSub t)])) cons)
+ end
+ | L'.TRecord t =>
+ (case #1 (hnormCon env t) of
+ L'.CRecord (_, xts) =>
+ let
+ val xts = map (fn ((L'.CName x, _), co) => SOME (x, co)
+ | _ => NONE) xts
+ in
+ if List.all Option.isSome xts then
+ let
+ val xts = List.mapPartial (fn x => x) xts
+ val xts = ListMergeSort.sort (fn ((x1, _), (x2, _)) =>
+ String.compare (x1, x2) = GREATER) xts
+ in
+ (NONE, IS.empty, [(0, xts)])
+ end
+ else
+ default ()
+ end
+ | _ => default ())
+ | _ => default ()
+ in
+ if IS.isEmpty unused then
+ let
+ fun recurse cons =
+ case cons of
+ [] => NONE
+ | (name, args) :: cons =>
+ case I (S (map #1 args, name, P),
+ map #2 args @ qs) of
+ NONE => recurse cons
+ | SOME ps =>
+ let
+ val nargs = length args
+ val argPs = List.take (ps, nargs)
+ val restPs = List.drop (ps, nargs)
+
+ val p = case name of
+ 0 => L'.PRecord (ListPair.map
+ (fn ((name, t), p) => (name, p, t))
+ (args, argPs))
+ | _ => L'.PCon (L'.Default, nameOfNum (#1 t1, name), [],
+ case argPs of
+ [] => NONE
+ | [p] => SOME p
+ | _ => fail 3)
+ in
+ SOME ((p, loc) :: restPs)
+ end
+ in
+ recurse cons
+ end
+ else
+ case I (D P, qs) of
+ NONE => NONE
+ | SOME ps =>
+ let
+ val p = case cons of
+ [] => pwild
+ | (0, _) :: _ => pwild
+ | _ =>
+ case IS.find (fn _ => true) unused of
+ NONE => fail 6
+ | SOME name =>
+ case ListUtil.search (fn (name', args) =>
+ if name = name' then
+ SOME (name', args)
+ else
+ NONE) cons of
+ SOME (n, []) =>
+ L'.PCon (L'.Default, nameOfNum (#1 t1, n), [], NONE)
+ | SOME (n, [_]) =>
+ L'.PCon (L'.Default, nameOfNum (#1 t1, n), [], SOME (pwild, loc))
+ | _ => fail 7
+ in
+ SOME ((p, loc) :: ps)
+ end
+ end
+ in
+ case I (map (fn x => [x]) ps, [t]) of
+ NONE => NONE
+ | SOME [p] => SOME p
+ | _ => fail 7
+ end
+
+fun unmodCon env (c, loc) =
+ case c of
+ L'.CNamed n =>
+ (case E.lookupCNamed env n of
+ (_, _, SOME (c as (L'.CModProj _, _))) => unmodCon env c
+ | _ => (c, loc))
+ | L'.CModProj (m1, ms, x) =>
+ let
+ val (str, sgn) = E.chaseMpath env (m1, ms)
+ in
+ case E.projectCon env {str = str, sgn = sgn, field = x} of
+ NONE => raise Fail "unmodCon: Can't projectCon"
+ | SOME (_, SOME (c as (L'.CModProj _, _))) => unmodCon env c
+ | _ => (c, loc)
+ end
+ | _ => (c, loc)
+
+fun normClassKey env c =
+ let
+ val c = hnormCon env c
+ in
+ case #1 c of
+ L'.CApp (c1, c2) =>
+ let
+ val c1 = normClassKey env c1
+ val c2 = normClassKey env c2
+ in
+ (L'.CApp (c1, c2), #2 c)
+ end
+ | L'.CRecord (k, xcs) => (L'.CRecord (k, map (fn (x, c) => (normClassKey env x,
+ normClassKey env c)) xcs), #2 c)
+ | _ => unmodCon env c
+ end
+
+fun normClassConstraint env (c, loc) =
+ case c of
+ L'.CApp (f, x) =>
+ let
+ val f = normClassKey env f
+ val x = normClassKey env x
+ in
+ (L'.CApp (f, x), loc)
+ end
+ | L'.TFun (c1, c2) =>
+ let
+ val c1 = normClassConstraint env c1
+ val c2 = normClassConstraint env c2
+ in
+ (L'.TFun (c1, c2), loc)
+ end
+ | L'.TCFun (expl, x, k, c1) => (L'.TCFun (expl, x, k, normClassConstraint env c1), loc)
+ | L'.CUnif (nl, _, _, _, ref (L'.Known c)) => normClassConstraint env (E.mliftConInCon nl c)
+ | _ => unmodCon env (c, loc)
+
+fun findHead e e' =
+ let
+ fun findHead (e, _) =
+ case e of
+ L.EVar (_, _, infer) =>
+ let
+ fun findHead' (e, _) =
+ case e of
+ L'.ENamed _ => true
+ | L'.EModProj _ => true
+ | L'.ERel _ => true
+ | L'.EApp (e, _) => findHead' e
+ | L'.ECApp (e, _) => findHead' e
+ | L'.EKApp (e, _) => findHead' e
+ | _ => false
+ in
+ if findHead' e' then
+ SOME infer
+ else
+ NONE
+ end
+ | L.EApp (e, _) => findHead e
+ | L.ECApp (e, _) => findHead e
+ | L.EDisjointApp e => findHead e
+ | _ => NONE
+ in
+ findHead e
+ end
+
+datatype needed = Needed of {Cons : (L'.kind * int) SM.map,
+ NextCon : int,
+ Constraints : (E.env * (L'.con * L'.con) * ErrorMsg.span) list,
+ Vals : SS.set,
+ Mods : (E.env * needed) SM.map}
+
+fun ncons (Needed r) = map (fn (k, (v, _)) => (k, v))
+ (ListMergeSort.sort (fn ((_, (_, n1)), (_, (_, n2))) => n1 > n2)
+ (SM.listItemsi (#Cons r)))
+fun nconstraints (Needed r) = #Constraints r
+fun nvals (Needed r) = #Vals r
+fun nmods (Needed r) = #Mods r
+
+val nempty = Needed {Cons = SM.empty,
+ NextCon = 0,
+ Constraints = nil,
+ Vals = SS.empty,
+ Mods = SM.empty}
+
+fun naddCon (r : needed, k, v) =
+ let
+ val Needed r = r
+ in
+ Needed {Cons = SM.insert (#Cons r, k, (v, #NextCon r)),
+ NextCon = #NextCon r + 1,
+ Constraints = #Constraints r,
+ Vals = #Vals r,
+ Mods = #Mods r}
+ end
+
+fun naddConstraint (r : needed, v) =
+ let
+ val Needed r = r
+ in
+ Needed {Cons = #Cons r,
+ NextCon = #NextCon r,
+ Constraints = v :: #Constraints r,
+ Vals = #Vals r,
+ Mods = #Mods r}
+ end
+
+fun naddVal (r : needed, k) =
+ let
+ val Needed r = r
+ in
+ Needed {Cons = #Cons r,
+ NextCon = #NextCon r,
+ Constraints = #Constraints r,
+ Vals = SS.add (#Vals r, k),
+ Mods = #Mods r}
+ end
+
+fun naddMod (r : needed, k, v) =
+ let
+ val Needed r = r
+ in
+ Needed {Cons = #Cons r,
+ NextCon = #NextCon r,
+ Constraints = #Constraints r,
+ Vals = #Vals r,
+ Mods = SM.insert (#Mods r, k, v)}
+ end
+
+fun ndelCon (r : needed, k) =
+ let
+ val Needed r = r
+ in
+ Needed {Cons = #1 (SM.remove (#Cons r, k)) handle NotFound => #Cons r,
+ NextCon = #NextCon r,
+ Constraints = #Constraints r,
+ Vals = #Vals r,
+ Mods = #Mods r}
+ end
+
+fun ndelVal (r : needed, k) =
+ let
+ val Needed r = r
+ in
+ Needed {Cons = #Cons r,
+ NextCon = #NextCon r,
+ Constraints = #Constraints r,
+ Vals = SS.delete (#Vals r, k) handle NotFound => #Vals r,
+ Mods = #Mods r}
+ end
+
+fun chaseUnifs c =
+ case #1 c of
+ 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)]*)
+ (*val befor = Time.now ()*)
+
+ val r = case e of
+ L.EAnnot (e, t) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ val (t', _, gs2) = elabCon (env, denv) t
+ in
+ checkCon env e' et t';
+ (e', t', gs1 @ enD gs2)
+ end
+
+ | L.EPrim p => ((L'.EPrim p, loc), primType env p, [])
+ | L.EVar ([], s, infer) =>
+ (case E.lookupE env s of
+ E.NotBound =>
+ (expError env (UnboundExp (loc, s));
+ (eerror, cerror, []))
+ | E.Rel (n, t) => elabHead (env, denv) infer (L'.ERel n, loc) t
+ | E.Named (n, t) => elabHead (env, denv) infer (L'.ENamed n, loc) t)
+ | L.EVar (m1 :: ms, s, infer) =>
+ (case E.lookupStr env m1 of
+ NONE => (expError env (UnboundStrInExp (loc, m1));
+ (eerror, cerror, []))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => (conError env (UnboundStrInCon (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+
+ val t = case E.projectVal env {sgn = sgn, str = str, field = s} of
+ NONE => (expError env (UnboundExp (loc, s));
+ cerror)
+ | SOME t => t
+ in
+ elabHead (env, denv) infer (L'.EModProj (n, ms, s), loc) t
+ end)
+
+ | L.EWild =>
+ let
+ val r = ref NONE
+ val c = cunif env (loc, (L'.KType, loc))
+ in
+ ((L'.EUnif r, loc), c, [TypeClass (env, c, r, loc)])
+ end
+
+ | L.EApp (e1, e2) =>
+ let
+ val (e1', t1, gs1) = elabExp (env, denv) e1
+
+ val (e2', t2, gs2) = elabExp (env, denv) e2
+
+ val dom = cunif env (loc, ktype)
+ val ran = cunif env (loc, ktype)
+ val t = (L'.TFun (dom, ran), loc)
+
+ val () = checkCon env e1' t1 t
+ val () = checkCon env e2' t2 dom
+
+ val ef = (L'.EApp (e1', e2'), loc)
+ val (ef, et, gs3) =
+ case findHead e1 e1' of
+ NONE => (ef, (#1 (chaseUnifs ran), loc), [])
+ | SOME infer => elabHead (env, denv) infer ef ran
+ in
+ (ef, et, gs1 @ gs2 @ gs3)
+ end
+ | L.EAbs (x, to, e) =>
+ let
+ val (t', gs1) = case to of
+ NONE => (cunif env (loc, ktype), [])
+ | SOME t =>
+ let
+ val (t', tk, gs) = elabCon (env, denv) t
+ in
+ checkKind env t' tk ktype;
+ (t', gs)
+ end
+ val dom = normClassConstraint env t'
+ val (e', et, gs2) = elabExp (E.pushERel env x dom, denv) e
+ in
+ ((L'.EAbs (x, t', et, e'), loc),
+ (L'.TFun (t', et), loc),
+ enD gs1 @ gs2)
+ end
+ | L.ECApp (e, c) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+
+ val oldEt = et
+ val (c', ck, gs2) = elabCon (env, denv) c
+ val (et', _) = hnormCon env et
+ in
+ case et' of
+ L'.CError => (eerror, cerror, [])
+ | L'.TCFun (_, x, k, eb) =>
+ let
+ val () = checkKind env c' ck k
+ val eb' = subConInCon env (0, c') eb
+
+ val ef = (L'.ECApp (e', c'), loc)
+ val (ef, eb', gs3) =
+ case findHead e e' of
+ NONE => (ef, eb', [])
+ | SOME infer => elabHead (env, denv) infer ef eb'
+ in
+ (*prefaces "Elab ECApp" [("e", SourcePrint.p_exp eAll),
+ ("et", p_con env oldEt),
+ ("x", PD.string x),
+ ("eb", p_con (E.pushCRel env x k) eb),
+ ("c", p_con env c'),
+ ("eb'", p_con env eb')];*)
+ (ef, (#1 eb', loc), gs1 @ enD gs2 @ gs3)
+ end
+
+ | _ =>
+ (expError env (WrongForm ("constructor function", e', et));
+ (eerror, cerror, []))
+ end
+ | L.ECAbs (expl, x, k, e) =>
+ let
+ val expl' = elabExplicitness expl
+ val k' = elabKind env k
+
+ val env' = E.pushCRel env x k'
+ val (e', et, gs) = elabExp (env', D.enter denv) e
+ in
+ ((L'.ECAbs (expl', x, k', e'), loc),
+ (L'.TCFun (expl', x, k', et), loc),
+ gs)
+ end
+ | L.EKAbs (x, e) =>
+ let
+ val env' = E.pushKRel env x
+ val (e', et, gs) = elabExp (env', denv) e
+ in
+ ((L'.EKAbs (x, e'), loc),
+ (L'.TKFun (x, et), loc),
+ gs)
+ end
+
+ | L.EDisjoint (c1, c2, e) =>
+ let
+ val (c1', k1, gs1) = elabCon (env, denv) c1
+ val (c2', k2, gs2) = elabCon (env, denv) c2
+
+ val ku1 = kunif env loc
+ val ku2 = kunif env loc
+
+ val denv' = D.assert env denv (c1', c2')
+ val (e', t, gs3) = elabExp (env, denv') e
+ in
+ checkKind env c1' k1 (L'.KRecord ku1, loc);
+ checkKind env c2' k2 (L'.KRecord ku2, loc);
+
+ (e', (L'.TDisjoint (c1', c2', t), loc), enD gs1 @ enD gs2 @ gs3)
+ end
+ | L.EDisjointApp e =>
+ let
+ val (e', t, gs1) = elabExp (env, denv) e
+
+ val k1 = kunif env loc
+ val c1 = cunif env (loc, (L'.KRecord k1, loc))
+ val k2 = kunif env loc
+ val c2 = cunif env (loc, (L'.KRecord k2, loc))
+ val t' = cunif env (loc, ktype)
+ val () = checkCon env e' t (L'.TDisjoint (c1, c2, t'), loc)
+ val gs2 = D.prove env denv (c1, c2, loc)
+ in
+ (e', (#1 (chaseUnifs t'), loc), enD gs2 @ gs1)
+ end
+
+ | L.ERecord (xes, flex) =>
+ let
+ val () = if flex then
+ expError env (IllegalFlex eAll)
+ else
+ ()
+
+ val (xes', gs) = ListUtil.foldlMap (fn ((x, e), gs) =>
+ let
+ val (x', xk, gs1) = elabCon (env, denv) x
+ val (e', et, gs2) = elabExp (env, denv) e
+ in
+ checkKind env x' xk kname;
+ ((x', e', et), enD gs1 @ gs2 @ gs)
+ end)
+ [] xes
+
+ val k = (L'.KType, loc)
+
+ fun prove (xets, gs) =
+ case xets of
+ [] => gs
+ | (x, _, t) :: rest =>
+ let
+ val xc = (x, t)
+ val r1 = (L'.CRecord (k, [xc]), loc)
+ val gs = foldl (fn ((x', _, t'), gs) =>
+ let
+ val xc' = (x', t')
+ val r2 = (L'.CRecord (k, [xc']), loc)
+ in
+ D.prove env denv (r1, r2, loc) @ gs
+ end)
+ gs rest
+ in
+ prove (rest, gs)
+ end
+
+ val gsD = List.mapPartial (fn Disjoint d => SOME d | _ => NONE) gs
+ val gsO = List.filter (fn Disjoint _ => false | _ => true) gs
+ in
+ (*TextIO.print ("|gsO| = " ^ Int.toString (length gsO) ^ "\n");*)
+ ((L'.ERecord xes', loc),
+ (L'.TRecord (L'.CRecord (ktype, map (fn (x', _, et) => (x', et)) xes'), loc), loc),
+ enD (prove (xes', gsD)) @ gsO)
+ end
+
+ | L.EField (e, c) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ val (c', ck, gs2) = elabCon (env, denv) c
+
+ val ft = cunif env (loc, ktype)
+ val rest = cunif env (loc, ktype_record)
+ val first = (L'.CRecord (ktype, [(c', ft)]), loc)
+ val () = checkCon env e' et
+ (L'.TRecord (L'.CConcat (first, rest), loc), loc);
+ val gs3 = D.prove env denv (first, rest, loc)
+ in
+ ((L'.EField (e', c', {field = ft, rest = rest}), loc), ft, gs1 @ enD gs2 @ enD gs3)
+ end
+
+ | L.EConcat (e1, e2) =>
+ let
+ val (e1', e1t, gs1) = elabExp (env, denv) e1
+ val (e2', e2t, gs2) = elabExp (env, denv) e2
+
+ val r1 = cunif env (loc, ktype_record)
+ val r2 = cunif env (loc, ktype_record)
+
+ val () = checkCon env e1' e1t (L'.TRecord r1, loc)
+ val () = checkCon env e2' e2t (L'.TRecord r2, loc)
+
+ val gs3 = D.prove env denv (r1, r2, loc)
+ in
+ ((L'.EConcat (e1', r1, e2', r2), loc),
+ (L'.TRecord ((L'.CConcat (r1, r2), loc)), loc),
+ gs1 @ gs2 @ enD gs3)
+ end
+ | L.ECut (e, c) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ val (c', ck, gs2) = elabCon (env, denv) c
+
+ val ft = cunif env (loc, ktype)
+ val rest = cunif env (loc, ktype_record)
+ val first = (L'.CRecord (ktype, [(c', ft)]), loc)
+
+ val () = checkCon env e' et
+ (L'.TRecord (L'.CConcat (first, rest), loc), loc)
+
+ val gs3 = D.prove env denv (first, rest, loc)
+ in
+ checkKind env c' ck kname;
+ ((L'.ECut (e', c', {field = ft, rest = rest}), loc), (L'.TRecord rest, loc),
+ gs1 @ enD gs2 @ enD gs3)
+ end
+ | L.ECutMulti (e, c) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ val (c', ck, gs2) = elabCon (env, denv) c
+
+ val rest = cunif env (loc, ktype_record)
+
+ val () = checkCon env e' et
+ (L'.TRecord (L'.CConcat (c', rest), loc), loc)
+
+ val gs3 = D.prove env denv (c', rest, loc)
+ in
+ checkKind env c' ck (L'.KRecord ktype, loc);
+ ((L'.ECutMulti (e', c', {rest = rest}), loc), (L'.TRecord rest, loc),
+ gs1 @ enD gs2 @ enD gs3)
+ end
+
+ | L.ECase (e, pes) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ val result = cunif env (loc, (L'.KType, loc))
+ val (pes', gs) = ListUtil.foldlMap
+ (fn ((p, e), gs) =>
+ let
+ val ((p', pt), (env, _)) = elabPat (p, (env, SS.empty))
+
+ val (e', et', gs1) = elabExp (env, denv) e
+ in
+ checkPatCon env p' pt et;
+ checkCon env e' et' result;
+ ((p', e'), gs1 @ gs)
+ end)
+ gs1 pes
+ in
+ case exhaustive (env, et, map #1 pes', loc) of
+ NONE => ()
+ | SOME p => if !mayDelay then
+ delayedExhaustives := (env, et, map #1 pes', loc) :: !delayedExhaustives
+ else
+ expError env (Inexhaustive (loc, p));
+
+ ((L'.ECase (e', pes', {disc = et, result = result}), loc), result, gs)
+ end
+
+ | L.ELet (eds, e) =>
+ let
+ val (eds, (env, gs1)) = ListUtil.foldlMap (elabEdecl denv) (env, []) eds
+ val (e, t, gs2) = elabExp (env, denv) e
+ in
+ ((L'.ELet (eds, e, t), loc), t, gs1 @ gs2)
+ end
+ in
+ (*prefaces "/elabExp" [("e", SourcePrint.p_exp eAll)];*)
+ r
+ end
+
+and elabEdecl denv (dAll as (d, loc), (env, gs)) =
+ let
+ val r =
+ case d of
+ L.EDVal (p, e) =>
+ let
+ val ((p', pt), (env', _)) = elabPat (p, (env, SS.empty))
+ val (e', et, gs1) = elabExp (env, denv) e
+
+ val () = checkCon env e' et pt
+
+ val env' = E.patBinds env p'
+ (* Redo to get proper detection of type class witnesses *)
+
+ val pt = normClassConstraint env pt
+ in
+ case exhaustive (env, et, [p'], loc) of
+ NONE => ()
+ | SOME p => if !mayDelay then
+ delayedExhaustives := (env, et, [p'], loc) :: !delayedExhaustives
+ else
+ expError env (Inexhaustive (loc, p));
+
+ ((L'.EDVal (p', pt, e'), loc), (env', gs1 @ gs))
+ end
+ | L.EDValRec vis =>
+ let
+ fun allowable (e, _) =
+ case e of
+ L.EAbs _ => true
+ | L.ECAbs (_, _, _, e) => allowable e
+ | L.EKAbs (_, e) => allowable e
+ | L.EDisjoint (_, _, e) => allowable e
+ | _ => false
+
+ val (vis, gs) = ListUtil.foldlMap
+ (fn ((x, co, e), gs) =>
+ let
+ val (c', _, gs1) = case co of
+ NONE => (cunif env (loc, ktype), ktype, [])
+ | SOME c => elabCon (env, denv) c
+ in
+ ((x, c', e), enD gs1 @ gs)
+ end) gs vis
+
+ val env = foldl (fn ((x, c', _), env) => E.pushERel env x c') env vis
+
+ val (vis, gs) = ListUtil.foldlMap (fn ((x, c', e), gs) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ in
+ checkCon env e' et c';
+ if allowable e then
+ ()
+ else
+ expError env (IllegalRec (x, e'));
+ ((x, c', e'), gs1 @ gs)
+ end) gs vis
+ in
+ ((L'.EDValRec vis, loc), (env, gs))
+ end
+ in
+ r
+ end
+
+val hnormSgn = E.hnormSgn
+
+fun tableOf () = (L'.CModProj (!basis_r, [], "sql_table"), ErrorMsg.dummySpan)
+fun sequenceOf () = (L'.CModProj (!basis_r, [], "sql_sequence"), ErrorMsg.dummySpan)
+fun viewOf () = (L'.CModProj (!basis_r, [], "sql_view"), ErrorMsg.dummySpan)
+fun queryOf () = (L'.CModProj (!basis_r, [], "sql_query"), ErrorMsg.dummySpan)
+fun cookieOf () = (L'.CModProj (!basis_r, [], "http_cookie"), ErrorMsg.dummySpan)
+fun styleOf () = (L'.CModProj (!basis_r, [], "css_class"), ErrorMsg.dummySpan)
+
+fun patVarsOf (p : L.pat) =
+ case #1 p of
+ L.PVar x => [x]
+ | L.PPrim _ => []
+ | L.PCon (_, _, NONE) => []
+ | L.PCon (_, _, SOME p) => patVarsOf p
+ | L.PRecord (xps, _) => ListUtil.mapConcat (fn (_, p) => patVarsOf p) xps
+ | L.PAnnot (p', _) => patVarsOf p'
+
+fun dopenConstraints (loc, env, denv) {str, strs} =
+ case E.lookupStr env str of
+ NONE => (strError env (UnboundStr (loc, str));
+ denv)
+ | SOME (n, sgn) =>
+ let
+ val (st, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {str = str, sgn = sgn, field = m} of
+ NONE => (strError env (UnboundStr (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) strs
+
+ fun collect first (st, sgn) =
+ case E.projectConstraints env {sgn = sgn, str = st} of
+ NONE => []
+ | SOME cs =>
+ case #1 (hnormSgn env sgn) of
+ L'.SgnConst sgis =>
+ foldl (fn (sgi, cs) =>
+ case #1 sgi of
+ L'.SgiStr (L'.Import, x, _, _) =>
+ (case E.projectStr env {sgn = sgn, str = st, field = x} of
+ NONE => raise Fail "Elaborate: projectStr in collect"
+ | SOME sgn' =>
+ List.revAppend (collect false ((L'.StrProj (st, x), loc), sgn'),
+ cs))
+ | _ => cs) cs sgis
+ | _ => cs
+ in
+ foldl (fn ((c1, c2), denv) =>
+ D.assert env denv (c1, c2)) denv (collect true (st, sgn))
+ end
+
+fun tcdump env =
+ Print.preface("Instances", p_list_sep Print.PD.newline
+ (fn (cl, ls) =>
+ box [p_con env cl,
+ box [Print.PD.string "{",
+ p_list (fn (t, e) =>
+ box [p_exp env e,
+ Print.PD.string " : ",
+ p_con env t]) ls,
+ Print.PD.string "}"]])
+ (E.listClasses env))
+
+fun elabSgn_item ((sgi, loc), (env, denv, gs)) =
+ ((*Print.preface ("elabSgi", SourcePrint.p_sgn_item (sgi, loc));*)
+ case sgi of
+ L.SgiConAbs (x, k) =>
+ let
+ val k' = elabKind env k
+
+ val (env', n) = E.pushCNamed env x k' NONE
+ in
+ ([(L'.SgiConAbs (x, n, k'), loc)], (env', denv, gs))
+ end
+
+ | L.SgiCon (x, ko, c) =>
+ let
+ val k' = case ko of
+ NONE => kunif env loc
+ | SOME k => elabKind env k
+
+ val (c', ck, gs') = elabCon (env, denv) c
+ val (env', n) = E.pushCNamed env x k' (SOME c')
+ in
+ checkKind env c' ck k';
+
+ ([(L'.SgiCon (x, n, k', c'), loc)], (env', denv, gs' @ gs))
+ end
+
+ | L.SgiDatatype dts =>
+ let
+ val k = (L'.KType, loc)
+
+ val (dts, env) = ListUtil.foldlMap (fn ((x, xs, xcs), env) =>
+ let
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+ val (env, n) = E.pushCNamed env x k' NONE
+ in
+ ((x, n, xs, xcs), env)
+ end)
+ env dts
+
+ val (dts, env) = ListUtil.foldlMap
+ (fn ((x, n, xs, xcs), env) =>
+ let
+ val t = (L'.CNamed n, loc)
+ val nxs = length xs - 1
+ val t = ListUtil.foldli (fn (i, _, t) =>
+ (L'.CApp (t, (L'.CRel (nxs - i), loc)), loc)) t xs
+
+ val (env', denv') = foldl (fn (x, (env', denv')) =>
+ (E.pushCRel env' x k,
+ D.enter denv')) (env, denv) xs
+
+ val (xcs, (used, env, gs)) =
+ ListUtil.foldlMap
+ (fn ((x, to), (used, env, gs)) =>
+ let
+ val (to, t, gs') = case to of
+ NONE => (NONE, t, gs)
+ | SOME t' =>
+ let
+ val (t', tk, gs') =
+ elabCon (env', denv') t'
+ in
+ checkKind env' t' tk k;
+ (SOME t',
+ (L'.TFun (t', t), loc),
+ gs' @ gs)
+ end
+ val t = foldl (fn (x, t) => (L'.TCFun (L'.Implicit, x, k, t), loc))
+ t xs
+
+ val (env, n') = E.pushENamed env x t
+ in
+ if SS.member (used, x) then
+ strError env (DuplicateConstructor (x, loc))
+ else
+ ();
+ ((x, n', to), (SS.add (used, x), env, gs'))
+ end)
+ (SS.empty, env, []) xcs
+ in
+ ((x, n, xs, xcs), E.pushDatatype env n xs xcs)
+ end)
+ env dts
+ in
+ ([(L'.SgiDatatype dts, loc)], (env, denv, gs))
+ end
+
+ | L.SgiDatatypeImp (_, [], _) => raise Fail "Empty SgiDatatypeImp"
+
+ | L.SgiDatatypeImp (x, m1 :: ms, s) =>
+ (case E.lookupStr env m1 of
+ NONE => (strError env (UnboundStr (loc, m1));
+ ([], (env, denv, gs)))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => (conError env (UnboundStrInCon (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+ in
+ case hnormCon env (L'.CModProj (n, ms, s), loc) of
+ (L'.CModProj (n, ms, s), _) =>
+ (case E.projectDatatype env {sgn = sgn, str = str, field = s} of
+ NONE => (conError env (UnboundDatatype (loc, s));
+ ([], (env, denv, [])))
+ | SOME (xs, xncs) =>
+ let
+ val k = (L'.KType, loc)
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+
+ val t = (L'.CModProj (n, ms, s), loc)
+ val (env, n') = E.pushCNamed env x k' (SOME t)
+ val env = E.pushDatatype env n' xs xncs
+
+ val t = (L'.CNamed n', loc)
+ val env = foldl (fn ((x, n, to), env) =>
+ let
+ val t = case to of
+ NONE => t
+ | SOME t' => (L'.TFun (t', t), loc)
+
+ val t = foldr (fn (x, t) =>
+ (L'.TCFun (L'.Implicit, x, k, t), loc))
+ t xs
+ in
+ E.pushENamedAs env x n t
+ end) env xncs
+ in
+ ([(L'.SgiDatatypeImp (x, n', n, ms, s, xs, xncs), loc)], (env, denv, []))
+ end)
+ | _ => (strError env (NotDatatype loc);
+ ([], (env, denv, [])))
+ end)
+
+ | L.SgiVal (x, c) =>
+ let
+ val (c', ck, gs') = elabCon (env, denv) c
+
+ val old = c'
+ val c' = normClassConstraint env c'
+ val (env', n) = E.pushENamed env x c'
+ in
+ (unifyKinds env ck ktype
+ handle KUnify arg => strError env (NotType (loc, ck, arg)));
+
+ ([(L'.SgiVal (x, n, c'), loc)], (env', denv, gs' @ gs))
+ end
+
+ | L.SgiTable (x, c, pe, ce) =>
+ let
+ val cstK = (L'.KRecord (L'.KRecord (L'.KUnit, loc), loc), loc)
+
+ val (c', ck, gs') = elabCon (env, denv) c
+ val pkey = cunif env (loc, cstK)
+ val visible = cunif env (loc, cstK)
+
+ val x' = x ^ "_hidden_constraints"
+ val (env', hidden_n) = E.pushCNamed env x' cstK NONE
+ val hidden = (L'.CNamed hidden_n, loc)
+
+ val uniques = (L'.CConcat (visible, hidden), loc)
+
+ val ct = tableOf ()
+ val ct = (L'.CApp (ct, c'), loc)
+ val ct = (L'.CApp (ct, (L'.CConcat (pkey, uniques), loc)), loc)
+
+ val (pe', pet, gs'') = elabExp (env', denv) pe
+ val gs'' = List.mapPartial (fn Disjoint x => SOME x
+ | _ => NONE) gs''
+
+ val pst = (L'.CModProj (!basis_r, [], "primary_key"), loc)
+ val pst = (L'.CApp (pst, c'), loc)
+ val pst = (L'.CApp (pst, pkey), loc)
+
+ val (ce', cet, gs''') = elabExp (env', denv) ce
+ val gs''' = List.mapPartial (fn Disjoint x => SOME x
+ | _ => NONE) gs'''
+
+ val cst = (L'.CModProj (!basis_r, [], "sql_constraints"), loc)
+ val cst = (L'.CApp (cst, c'), loc)
+ val cst = (L'.CApp (cst, visible), loc)
+
+ val (env', n) = E.pushENamed env' x ct
+ in
+ checkKind env c' ck (L'.KRecord (L'.KType, loc), loc);
+ checkCon env' pe' pet pst;
+ checkCon env' ce' cet cst;
+
+ ([(L'.SgiConAbs (x', hidden_n, cstK), loc),
+ (L'.SgiConstraint ((L'.CConcat (pkey, visible), loc), hidden), loc),
+ (L'.SgiVal (x, n, ct), loc)], (env', denv, gs''' @ gs'' @ gs' @ gs))
+ end
+
+ | L.SgiStr (x, sgn) =>
+ let
+ val (sgn', gs') = elabSgn (env, denv) sgn
+ val (env', n) = E.pushStrNamed env x sgn'
+ val denv' = dopenConstraints (loc, env', denv) {str = x, strs = []}
+ in
+ ([(L'.SgiStr (L'.Import, x, n, sgn'), loc)], (env', denv', gs' @ gs))
+ end
+
+ | L.SgiSgn (x, sgn) =>
+ let
+ val (sgn', gs') = elabSgn (env, denv) sgn
+ val (env', n) = E.pushSgnNamed env x sgn'
+ in
+ ([(L'.SgiSgn (x, n, sgn'), loc)], (env', denv, gs' @ gs))
+ end
+
+ | L.SgiInclude sgn =>
+ let
+ val (sgn', gs') = elabSgn (env, denv) sgn
+ in
+ case #1 (hnormSgn env sgn') of
+ L'.SgnConst sgis =>
+ (sgis, (foldl (fn (sgi, env) => E.sgiBinds env sgi) env sgis, denv, gs' @ gs))
+ | _ => (sgnError env (NotIncludable sgn');
+ ([], (env, denv, [])))
+ end
+
+ | L.SgiConstraint (c1, c2) =>
+ let
+ val (c1', k1, gs1) = elabCon (env, denv) c1
+ val (c2', k2, gs2) = elabCon (env, denv) c2
+
+ val denv = D.assert env denv (c1', c2')
+ in
+ checkKind env c1' k1 (L'.KRecord (kunif env loc), loc);
+ checkKind env c2' k2 (L'.KRecord (kunif env loc), loc);
+
+ ([(L'.SgiConstraint (c1', c2'), loc)], (env, denv, gs1 @ gs2))
+ end
+
+ | L.SgiClassAbs (x, k) =>
+ let
+ val k = elabKind env k
+ val (env, n) = E.pushCNamed env x k NONE
+ val env = E.pushClass env n
+ in
+ ([(L'.SgiClassAbs (x, n, k), loc)], (env, denv, []))
+ end
+
+ | L.SgiClass (x, k, c) =>
+ let
+ val k = elabKind env k
+ val (c', ck, gs) = elabCon (env, denv) c
+ val (env, n) = E.pushCNamed env x k (SOME c')
+ val env = E.pushClass env n
+ in
+ checkKind env c' ck k;
+ ([(L'.SgiClass (x, n, k, c'), loc)], (env, denv, []))
+ end)
+
+and elabSgn (env, denv) (sgn, loc) =
+ case sgn of
+ L.SgnConst sgis =>
+ let
+ val (sgis', (_, _, gs)) = ListUtil.foldlMapConcat elabSgn_item (env, denv, []) sgis
+
+ val _ = foldl (fn ((sgi, loc), (cons, vals, sgns, strs)) =>
+ case sgi of
+ L'.SgiConAbs (x, _, _) =>
+ (if SS.member (cons, x) then
+ sgnError env (DuplicateCon (loc, x))
+ else
+ ();
+ (SS.add (cons, x), vals, sgns, strs))
+ | L'.SgiCon (x, _, _, _) =>
+ (if SS.member (cons, x) then
+ sgnError env (DuplicateCon (loc, x))
+ else
+ ();
+ (SS.add (cons, x), vals, sgns, strs))
+ | L'.SgiDatatype dts =>
+ let
+ val (cons, vals) =
+ let
+ fun doOne ((x, _, _, xncs), (cons, vals)) =
+ let
+ val vals = foldl (fn ((x, _, _), vals) =>
+ (if SS.member (vals, x) then
+ sgnError env (DuplicateVal (loc, x))
+ else
+ ();
+ SS.add (vals, x)))
+ vals xncs
+ in
+ if SS.member (cons, x) then
+ sgnError env (DuplicateCon (loc, x))
+ else
+ ();
+ (SS.add (cons, x), vals)
+ end
+ in
+ foldl doOne (cons, vals) dts
+ end
+ in
+ (cons, vals, sgns, strs)
+ end
+ | L'.SgiDatatypeImp (x, _, _, _, _, _, _) =>
+ (if SS.member (cons, x) then
+ sgnError env (DuplicateCon (loc, x))
+ else
+ ();
+ (SS.add (cons, x), vals, sgns, strs))
+ | L'.SgiVal (x, _, _) =>
+ (if SS.member (vals, x) then
+ sgnError env (DuplicateVal (loc, x))
+ else
+ ();
+ (cons, SS.add (vals, x), sgns, strs))
+ | L'.SgiSgn (x, _, _) =>
+ (if SS.member (sgns, x) then
+ sgnError env (DuplicateSgn (loc, x))
+ else
+ ();
+ (cons, vals, SS.add (sgns, x), strs))
+ | L'.SgiStr (_, x, _, _) =>
+ (if SS.member (strs, x) then
+ sgnError env (DuplicateStr (loc, x))
+ else
+ ();
+ (cons, vals, sgns, SS.add (strs, x)))
+ | L'.SgiConstraint _ => (cons, vals, sgns, strs)
+ | L'.SgiClassAbs (x, _, _) =>
+ (if SS.member (cons, x) then
+ sgnError env (DuplicateCon (loc, x))
+ else
+ ();
+ (SS.add (cons, x), vals, sgns, strs))
+ | L'.SgiClass (x, _, _, _) =>
+ (if SS.member (cons, x) then
+ sgnError env (DuplicateCon (loc, x))
+ else
+ ();
+ (SS.add (cons, x), vals, sgns, strs)))
+ (SS.empty, SS.empty, SS.empty, SS.empty) sgis'
+ in
+ ((L'.SgnConst sgis', loc), gs)
+ end
+ | L.SgnVar x =>
+ (case E.lookupSgn env x of
+ NONE =>
+ (sgnError env (UnboundSgn (loc, x));
+ ((L'.SgnError, loc), []))
+ | SOME (n, sgis) => ((L'.SgnVar n, loc), []))
+ | L.SgnFun (m, dom, ran) =>
+ let
+ val (dom', gs1) = elabSgn (env, denv) dom
+ val (env', n) = E.pushStrNamed env m dom'
+ val denv' = dopenConstraints (loc, env', denv) {str = m, strs = []}
+ val (ran', gs2) = elabSgn (env', denv') ran
+ in
+ ((L'.SgnFun (m, n, dom', ran'), loc), gs1 @ gs2)
+ end
+ | L.SgnWhere (sgn, ms, x, c) =>
+ let
+ val (sgn', ds1) = elabSgn (env, denv) sgn
+ val (c', ck, ds2) = elabCon (env, denv) c
+
+ fun checkPath (ms, sgn') =
+ case #1 (hnormSgn env sgn') of
+ L'.SgnConst sgis =>
+ List.exists (fn (L'.SgiConAbs (x', _, k), _) =>
+ List.null ms andalso x' = x andalso
+ (unifyKinds env k ck
+ handle KUnify x => sgnError env (WhereWrongKind x);
+ true)
+ | (L'.SgiStr (_, x', _, sgn''), _) =>
+ (case ms of
+ [] => false
+ | m :: ms' =>
+ m = x' andalso
+ checkPath (ms', sgn''))
+ | _ => false) sgis
+ | _ => false
+ in
+ if checkPath (ms, sgn') then
+ ((L'.SgnWhere (sgn', ms, x, c'), loc), ds1 @ ds2)
+ else
+ (sgnError env (UnWhereable (sgn', x));
+ (sgnerror, []))
+ end
+ | L.SgnProj (m, ms, x) =>
+ (case E.lookupStr env m of
+ NONE => (strError env (UnboundStr (loc, m));
+ (sgnerror, []))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => (strError env (UnboundStr (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+ in
+ case E.projectSgn env {sgn = sgn, str = str, field = x} of
+ NONE => (sgnError env (UnboundSgn (loc, x));
+ (sgnerror, []))
+ | SOME _ => ((L'.SgnProj (n, ms, x), loc), [])
+ end)
+
+
+and selfify env {str, strs, sgn} =
+ case #1 (hnormSgn env sgn) of
+ L'.SgnError => sgn
+ | L'.SgnVar _ => sgn
+
+ | L'.SgnConst sgis =>
+ (L'.SgnConst (#1 (ListUtil.foldlMapConcat
+ (fn (sgi, env) =>
+ (case sgi of (L'.SgiConAbs (x, n, k), loc) =>
+ [(L'.SgiCon (x, n, k, (L'.CModProj (str, strs, x), loc)), loc)]
+ | (L'.SgiDatatype dts, loc) =>
+ map (fn (x, n, xs, xncs) => (L'.SgiDatatypeImp (x, n, str, strs, x, xs, xncs), loc)) dts
+ | (L'.SgiClassAbs (x, n, k), loc) =>
+ [(L'.SgiClass (x, n, k, (L'.CModProj (str, strs, x), loc)), loc)]
+ | (L'.SgiStr (im, x, n, sgn), loc) =>
+ [(L'.SgiStr (im, x, n, selfify env {str = str, strs = strs @ [x], sgn = sgn}), loc)]
+ | x => [x],
+ E.sgiBinds env sgi)) env sgis)), #2 sgn)
+ | L'.SgnFun _ => sgn
+ | L'.SgnWhere _ => sgn
+ | L'.SgnProj (m, ms, x) =>
+ case E.projectSgn env {str = foldl (fn (m, str) => (L'.StrProj (str, m), #2 sgn))
+ (L'.StrVar m, #2 sgn) ms,
+ sgn = #2 (E.lookupStrNamed env m),
+ field = x} of
+ NONE => raise Fail "Elaborate.selfify: projectSgn returns NONE"
+ | SOME sgn => selfify env {str = str, strs = strs, sgn = sgn}
+
+and selfifyAt env {str, sgn} =
+ let
+ fun self (str, _) =
+ case str of
+ L'.StrVar x => SOME (x, [])
+ | L'.StrProj (str, x) =>
+ (case self str of
+ NONE => NONE
+ | SOME (m, ms) => SOME (m, ms @ [x]))
+ | _ => NONE
+ in
+ case self str of
+ NONE => sgn
+ | SOME (str, strs) => selfify env {sgn = sgn, str = str, strs = strs}
+ end
+
+and dopen env {str, strs, sgn} =
+ let
+ fun isVisible x = x <> "" andalso String.sub (x, 0) <> #"?"
+
+ val m = foldl (fn (m, str) => (L'.StrProj (str, m), #2 sgn))
+ (L'.StrVar str, #2 sgn) strs
+ in
+ case #1 (hnormSgn env sgn) of
+ L'.SgnConst sgis =>
+ ListUtil.foldlMapConcat
+ (fn ((sgi, loc), env') =>
+ let
+ val d =
+ case sgi of
+ L'.SgiConAbs (x, n, k) =>
+ if isVisible x then
+ let
+ val c = (L'.CModProj (str, strs, x), loc)
+ in
+ [(L'.DCon (x, n, k, c), loc)]
+ end
+ else
+ []
+ | L'.SgiCon (x, n, k, c) =>
+ if isVisible x then
+ [(L'.DCon (x, n, k, (L'.CModProj (str, strs, x), loc)), loc)]
+ else
+ []
+ | L'.SgiDatatype dts =>
+ List.mapPartial (fn (x, n, xs, xncs) => if isVisible x then
+ SOME (L'.DDatatypeImp (x, n, str, strs, x, xs, xncs), loc)
+ else
+ NONE) dts
+ | L'.SgiDatatypeImp (x, n, m1, ms, x', xs, xncs) =>
+ if isVisible x then
+ [(L'.DDatatypeImp (x, n, m1, ms, x', xs, xncs), loc)]
+ else
+ []
+ | L'.SgiVal (x, n, t) =>
+ if isVisible x then
+ [(L'.DVal (x, n, t, (L'.EModProj (str, strs, x), loc)), loc)]
+ else
+ []
+ | L'.SgiStr (_, x, n, sgn) =>
+ if isVisible x then
+ [(L'.DStr (x, n, sgn, (L'.StrProj (m, x), loc)), loc)]
+ else
+ []
+ | L'.SgiSgn (x, n, sgn) =>
+ if isVisible x then
+ [(L'.DSgn (x, n, (L'.SgnProj (str, strs, x), loc)), loc)]
+ else
+ []
+ | L'.SgiConstraint (c1, c2) =>
+ [(L'.DConstraint (c1, c2), loc)]
+ | L'.SgiClassAbs (x, n, k) =>
+ if isVisible x then
+ let
+ val c = (L'.CModProj (str, strs, x), loc)
+ in
+ [(L'.DCon (x, n, k, c), loc)]
+ end
+ else
+ []
+ | L'.SgiClass (x, n, k, _) =>
+ if isVisible x then
+ let
+ val c = (L'.CModProj (str, strs, x), loc)
+ in
+ [(L'.DCon (x, n, k, c), loc)]
+ end
+ else
+ []
+ in
+ (d, foldl (fn (d, env') => E.declBinds env' d) env' d)
+ end)
+ env sgis
+ | _ => (strError env (UnOpenable sgn);
+ ([], env))
+ end
+
+and sgiOfDecl (d, loc) =
+ case d of
+ L'.DCon (x, n, k, c) => [(L'.SgiCon (x, n, k, c), loc)]
+ | L'.DDatatype x => [(L'.SgiDatatype x, loc)]
+ | L'.DDatatypeImp x => [(L'.SgiDatatypeImp x, loc)]
+ | L'.DVal (x, n, t, _) => [(L'.SgiVal (x, n, t), loc)]
+ | L'.DValRec vis => map (fn (x, n, t, _) => (L'.SgiVal (x, n, t), loc)) vis
+ | L'.DSgn (x, n, sgn) => [(L'.SgiSgn (x, n, sgn), loc)]
+ | L'.DStr (x, n, sgn, _) => [(L'.SgiStr (L'.Import, x, n, sgn), loc)]
+ | L'.DFfiStr (x, n, sgn) => [(L'.SgiStr (L'.Import, x, n, sgn), loc)]
+ | L'.DConstraint cs => [(L'.SgiConstraint cs, loc)]
+ | L'.DExport _ => []
+ | L'.DTable (tn, x, n, c, _, pc, _, cc) =>
+ [(L'.SgiVal (x, n, (L'.CApp ((L'.CApp (tableOf (), c), loc),
+ (L'.CConcat (pc, cc), loc)), loc)), loc)]
+ | L'.DSequence (tn, x, n) => [(L'.SgiVal (x, n, sequenceOf ()), loc)]
+ | L'.DView (tn, x, n, _, c) =>
+ [(L'.SgiVal (x, n, (L'.CApp (viewOf (), c), loc)), loc)]
+ | L'.DDatabase _ => []
+ | L'.DCookie (tn, x, n, c) => [(L'.SgiVal (x, n, (L'.CApp (cookieOf (), c), loc)), loc)]
+ | L'.DStyle (tn, x, n) => [(L'.SgiVal (x, n, styleOf ()), loc)]
+ | L'.DTask _ => []
+ | L'.DPolicy _ => []
+ | L'.DOnError _ => []
+ | L'.DFfi (x, n, _, t) => [(L'.SgiVal (x, n, t), loc)]
+
+and subSgn' counterparts env strLoc sgn1 (sgn2 as (_, loc2)) =
+ ((*prefaces "subSgn" [("sgn1", p_sgn env sgn1),
+ ("sgn2", p_sgn env sgn2)];*)
+ case (#1 (hnormSgn env sgn1), #1 (hnormSgn env sgn2)) of
+ (L'.SgnError, _) => ()
+ | (_, L'.SgnError) => ()
+
+ | (L'.SgnConst sgis1, L'.SgnConst sgis2) =>
+ let
+ (*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))]*)
+
+ fun cpart n = IM.find (!counterparts, n)
+ fun cparts (n2, n1) = counterparts := IM.insert (!counterparts, n2, n1)
+ fun uncparts n2 = (counterparts := #1 (IM.remove (!counterparts, n2)))
+ handle NotFound => ()
+
+ val sub2 = U.Con.map {kind = fn k => k,
+ con = fn c =>
+ case c of
+ L'.CNamed n2 =>
+ (case cpart n2 of
+ NONE => c
+ | SOME n1 => L'.CNamed n1)
+ | _ => c}
+
+ fun folder (sgi2All as (sgi, loc), env) =
+ let
+ (*val () = prefaces "folder" [("sgi2", p_sgn_item env sgi2All)]*)
+
+ fun seek' f p =
+ let
+ fun seek env ls =
+ case ls of
+ [] => f env
+ | h :: t =>
+ case p (env, h) of
+ NONE =>
+ let
+ val env = case #1 h of
+ L'.SgiCon (x, n, k, c) =>
+ if E.checkENamed env n then
+ env
+ else
+ (uncparts n;
+ E.pushCNamedAs env x n k (SOME c))
+ | L'.SgiConAbs (x, n, k) =>
+ if E.checkENamed env n then
+ env
+ else
+ E.pushCNamedAs env x n k NONE
+ | _ => env
+ in
+ seek (E.sgiBinds env h) t
+ end
+ | SOME envs => envs
+ in
+ seek env sgis1
+ end
+
+ val seek = seek' (fn env => (sgnError env (UnmatchedSgi (strLoc, sgi2All));
+ env))
+ in
+ case sgi of
+ L'.SgiConAbs (x, n2, k2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ let
+ fun found (x', n1, k1, co1) =
+ if x = x' then
+ let
+ val () = unifyKinds env k1 k2
+ handle KUnify (k1, k2, env', err) =>
+ sgnError env (SgiWrongKind (loc, sgi1All, k1,
+ sgi2All, k2, env', err))
+ val env = E.pushCNamedAs env x n1 k1 co1
+ in
+ SOME (if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushCNamedAs env x n2 k2 (SOME (L'.CNamed n1, loc2))))
+ end
+ else
+ NONE
+ in
+ case sgi1 of
+ L'.SgiConAbs (x', n1, k1) => found (x', n1, k1, NONE)
+ | L'.SgiCon (x', n1, k1, c1) => found (x', n1, k1, SOME c1)
+ | L'.SgiDatatype dts =>
+ let
+ val k = (L'.KType, loc)
+
+ fun search dts =
+ case dts of
+ [] => NONE
+ | (x', n1, xs, _) :: dts =>
+ let
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+ in
+ case found (x', n1, k', NONE) of
+ NONE => search dts
+ | x => x
+ end
+ in
+ search dts
+ end
+ | L'.SgiDatatypeImp (x', n1, m1, ms, s, xs, _) =>
+ let
+ val k = (L'.KType, loc)
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+ in
+ found (x', n1, k', SOME (L'.CModProj (m1, ms, s), loc))
+ end
+ | L'.SgiClassAbs (x', n1, k) => found (x', n1, k, NONE)
+ | L'.SgiClass (x', n1, k, c) => found (x', n1, k, SOME c)
+ | _ => NONE
+ end)
+
+ | L'.SgiCon (x, n2, k2, c2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ let
+ fun found (x', n1, k1, c1) =
+ if x = x' then
+ let
+ val c2 = sub2 c2
+
+ fun good () =
+ let
+ val env = E.pushCNamedAs env x n2 k2 (SOME c2)
+ val env = if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushCNamedAs env x n1 k1 (SOME c1))
+ in
+ SOME env
+ end
+ in
+ (unifyCons env loc c1 c2;
+ good ())
+ handle CUnify (c1, c2, env', err) =>
+ (sgnError env (SgiWrongCon (loc, sgi1All, c1,
+ sgi2All, c2, env', err));
+ good ())
+ end
+ else
+ NONE
+ in
+ case sgi1 of
+ L'.SgiCon (x', n1, k1, c1) => found (x', n1, k1, c1)
+ | L'.SgiClass (x', n1, k1, c1) => found (x', n1, k1, c1)
+ | _ => NONE
+ end)
+
+ | L'.SgiDatatype dts2 =>
+ let
+ fun found' (sgi1All as (_, loc), (x1, n1, xs1, xncs1), (x2, n2, xs2, xncs2), env) =
+ if x1 <> x2 then
+ NONE
+ else
+ let
+ fun mismatched ue =
+ (sgnError env (SgiMismatchedDatatypes (loc, sgi1All, sgi2All, ue));
+ SOME env)
+
+ val k = (L'.KType, loc)
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs1
+
+ fun good () =
+ let
+ val env = E.sgiBinds env sgi1All
+ val env = if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushCNamedAs env x1 n2 k'
+ (SOME (L'.CNamed n1, loc)))
+ in
+ SOME env
+ end
+
+ val env = E.pushCNamedAs env x1 n1 k' NONE
+ val env = if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushCNamedAs env x1 n2 k' (SOME (L'.CNamed n1, loc)))
+ val env = foldl (fn (x, env) => E.pushCRel env x k) env xs1
+ fun xncBad ((x1, _, t1), (x2, _, t2)) =
+ String.compare (x1, x2) <> EQUAL
+ orelse case (t1, t2) of
+ (NONE, NONE) => false
+ | (SOME t1, SOME t2) =>
+ (unifyCons env loc t1 (sub2 t2); false)
+ | _ => true
+ in
+ (if xs1 <> xs2
+ orelse length xncs1 <> length xncs2
+ orelse ListPair.exists xncBad (xncs1, xncs2) then
+ mismatched NONE
+ else
+ good ())
+ handle CUnify ue => mismatched (SOME ue)
+ end
+ in
+ seek'
+ (fn _ =>
+ let
+ fun seekOne (dt2, env) =
+ seek (fn (env, sgi1All as (sgi1, _)) =>
+ case sgi1 of
+ L'.SgiDatatypeImp (x', n1, _, _, _, xs, xncs1) =>
+ found' (sgi1All, (x', n1, xs, xncs1), dt2, env)
+ | _ => NONE)
+
+ fun seekAll (dts, env) =
+ case dts of
+ [] => env
+ | dt :: dts => seekAll (dts, seekOne (dt, env))
+ in
+ seekAll (dts2, env)
+ end)
+ (fn (env, sgi1All as (sgi1, _)) =>
+ let
+ fun found dts1 =
+ let
+ fun iter (dts1, dts2, env) =
+ case (dts1, dts2) of
+ ([], []) => SOME env
+ | (dt1 :: dts1, dt2 :: dts2) =>
+ (case found' (sgi1All, dt1, dt2, env) of
+ NONE => NONE
+ | SOME env => iter (dts1, dts2, env))
+ | _ => NONE
+ in
+ iter (dts1, dts2, env)
+ end
+ in
+ case sgi1 of
+ L'.SgiDatatype dts1 => found dts1
+ | _ => NONE
+ end)
+ end
+
+ | L'.SgiDatatypeImp (x, n2, m12, ms2, s2, xs, _) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ case sgi1 of
+ L'.SgiDatatypeImp (x', n1, m11, ms1, s1, _, _) =>
+ if x = x' then
+ let
+ val k = (L'.KType, loc)
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+ val t1 = (L'.CModProj (m11, ms1, s1), loc)
+ val t2 = (L'.CModProj (m12, ms2, s2), loc)
+
+ fun good () =
+ let
+ val env = E.pushCNamedAs env x n1 k' (SOME t1)
+ val env = E.pushCNamedAs env x n2 k' (SOME t2)
+ in
+ cparts (n2, n1);
+ SOME env
+ end
+ in
+ (unifyCons env loc t1 t2;
+ good ())
+ handle CUnify (c1, c2, env', err) =>
+ (sgnError env (SgiWrongCon (loc, sgi1All, c1, sgi2All, c2, env', err));
+ good ())
+ end
+ else
+ NONE
+
+ | _ => NONE)
+
+ | L'.SgiVal (x, n2, c2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ case sgi1 of
+ L'.SgiVal (x', n1, c1) =>
+ if x = x' then
+ ((*prefaces "val" [("x", PD.string x),
+ ("n1", PD.string (Int.toString n1)),
+ ("c1", p_con env c1),
+ ("c2", p_con env c2),
+ ("c2'", p_con env (sub2 c2))];*)
+ unifyCons env loc c1 (sub2 c2);
+ SOME env)
+ handle CUnify (c1, c2, env', err) =>
+ (sgnError env (SgiWrongCon (loc, sgi1All, c1, sgi2All, c2, env', err));
+ SOME env)
+ else
+ NONE
+ | _ => NONE)
+
+ | L'.SgiStr (_, x, n2, sgn2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ case sgi1 of
+ 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
+ else
+ E.pushStrNamedAs env x n2
+ (selfifyAt env {str = (L'.StrVar n1, #2 sgn2),
+ sgn = sgn2})
+ in
+ SOME env
+ end
+ else
+ NONE
+ | _ => NONE)
+
+ | L'.SgiSgn (x, n2, sgn2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ case sgi1 of
+ L'.SgiSgn (x', n1, sgn1) =>
+ if x = x' then
+ let
+ val () = subSgn' counterparts env loc sgn1 sgn2
+ val () = subSgn' counterparts env loc sgn2 sgn1
+
+ val env = E.pushSgnNamedAs env x n2 sgn2
+ val env = if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushSgnNamedAs env x n1 sgn2)
+ in
+ SOME env
+ end
+ else
+ NONE
+ | _ => NONE)
+
+ | L'.SgiConstraint (c2, d2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ case sgi1 of
+ L'.SgiConstraint (c1, d1) =>
+ (* 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
+ | _ => NONE)
+
+ | L'.SgiClassAbs (x, n2, k2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ let
+ fun found (x', n1, k1, co) =
+ if x = x' then
+ let
+ val () = unifyKinds env k1 k2
+ handle KUnify (k1, k2, env', err) =>
+ sgnError env (SgiWrongKind (loc, sgi1All, k1,
+ sgi2All, k2, env', err))
+
+ val env = E.pushCNamedAs env x n1 k1 co
+ in
+ SOME (if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushCNamedAs env x n2 k1 (SOME (L'.CNamed n1, loc2))))
+ end
+ else
+ NONE
+ in
+ case sgi1 of
+ L'.SgiClassAbs (x', n1, k1) => found (x', n1, k1, NONE)
+ | L'.SgiClass (x', n1, k1, c) => found (x', n1, k1, SOME c)
+ | L'.SgiConAbs (x', n1, k1) => found (x', n1, k1, NONE)
+ | L'.SgiCon (x', n1, k1, c) => found (x', n1, k1, SOME c)
+ | _ => NONE
+ end)
+ | L'.SgiClass (x, n2, k2, c2) =>
+ seek (fn (env, sgi1All as (sgi1, loc)) =>
+ let
+ fun found (x', n1, k1, c1) =
+ if x = x' then
+ let
+ val () = unifyKinds env k1 k2
+ handle KUnify (k1, k2, env', err) =>
+ sgnError env (SgiWrongKind (loc, sgi1All, k1,
+ sgi2All, k2, env', err))
+
+ val c2 = sub2 c2
+
+ fun good () =
+ let
+ val env = E.pushCNamedAs env x n2 k2 (SOME c2)
+ val env = if n1 = n2 then
+ env
+ else
+ (cparts (n2, n1);
+ E.pushCNamedAs env x n1 k2 (SOME c1))
+ in
+ SOME env
+ end
+ in
+ (unifyCons env loc c1 c2;
+ good ())
+ handle CUnify (c1, c2, env', err) =>
+ (sgnError env (SgiWrongCon (loc, sgi1All, c1,
+ sgi2All, c2, env', err));
+ good ())
+ end
+ else
+ NONE
+ in
+ case sgi1 of
+ L'.SgiClass (x', n1, k1, c1) => found (x', n1, k1, c1)
+ | L'.SgiCon (x', n1, k1, c1) => found (x', n1, k1, c1)
+ | _ => NONE
+ end)
+ end
+ in
+ ignore (foldl folder env sgis2)
+ end
+
+ | (L'.SgnFun (m1, n1, dom1, ran1), L'.SgnFun (m2, n2, dom2, ran2)) =>
+ let
+ val ran2 =
+ if n1 = n2 then
+ ran2
+ else
+ subStrInSgn (n2, n1) ran2
+ in
+ subSgn' counterparts env strLoc dom2 dom1;
+ subSgn' counterparts (E.pushStrNamedAs env m1 n1 dom2) strLoc ran1 ran2
+ end
+
+ | _ => sgnError env (SgnWrongForm (strLoc, sgn1, sgn2)))
+
+and subSgn env x y z = subSgn' (ref IM.empty) env x y z
+ handle e as E.UnboundNamed _ => if ErrorMsg.anyErrors () then () else raise e
+
+and positive self =
+ let
+ open L
+
+ fun none (c, _) =
+ case c of
+ CAnnot (c, _) => none c
+
+ | TFun (c1, c2) => none c1 andalso none c2
+ | TCFun (_, _, _, c) => none c
+ | TRecord c => none c
+
+ | CVar ([], x) => x <> self
+ | CVar _ => true
+ | CApp (c1, c2) => none c1 andalso none c2
+ | CAbs _ => false
+ | TDisjoint (c1, c2, c3) => none c1 andalso none c2 andalso none c3
+
+ | CKAbs _ => false
+ | TKFun _ => false
+
+ | CName _ => true
+
+ | CRecord xcs => List.all (fn (c1, c2) => none c1 andalso none c2) xcs
+ | CConcat (c1, c2) => none c1 andalso none c2
+ | CMap => true
+
+ | CUnit => true
+
+ | CTuple cs => List.all none cs
+ | CProj (c, _) => none c
+
+ | CWild _ => false
+
+ fun pos (c, _) =
+ case c of
+ CAnnot (c, _) => pos c
+
+ | TFun (c1, c2) => none c1 andalso pos c2
+ | TCFun (_, _, _, c) => pos c
+ | TRecord c => pos c
+
+ | CVar _ => true
+ | CApp (c1, c2) => pos c1 andalso none c2
+ | CAbs _ => false
+ | TDisjoint (c1, c2, c3) => none c1 andalso none c2 andalso none c3
+
+ | CKAbs _ => false
+ | TKFun _ => false
+
+ | CName _ => true
+
+ | CRecord xcs => List.all (fn (c1, c2) => none c1 andalso pos c2) xcs
+ | CConcat (c1, c2) => pos c1 andalso pos c2
+ | CMap => true
+
+ | CUnit => true
+
+ | CTuple cs => List.all pos cs
+ | CProj (c, _) => pos c
+
+ | CWild _ => false
+ in
+ pos
+ end
+
+and wildifyStr env (str, sgn) =
+ case #1 (hnormSgn env sgn) of
+ L'.SgnConst sgis =>
+ (case #1 str of
+ L.StrConst ds =>
+ let
+ fun cname d =
+ case d of
+ L'.SgiCon (x, _, _, _) => SOME x
+ | L'.SgiConAbs (x, _, _) => SOME x
+ | L'.SgiClass (x, _, _, _) => SOME x
+ | L'.SgiClassAbs (x, _, _) => SOME x
+ | _ => NONE
+
+ fun dname (d, _) =
+ case d of
+ L.DCon (x, _, _) => SOME x
+ | _ => NONE
+
+ fun decompileKind (k, loc) =
+ case k of
+ L'.KType => SOME (L.KType, loc)
+ | L'.KArrow (k1, k2) =>
+ (case (decompileKind k1, decompileKind k2) of
+ (SOME k1, SOME k2) => SOME (L.KArrow (k1, k2), loc)
+ | _ => NONE)
+ | L'.KName => SOME (L.KName, loc)
+ | L'.KRecord k =>
+ (case decompileKind k of
+ SOME k => SOME (L.KRecord k, loc)
+ | _ => NONE)
+ | L'.KUnit => SOME (L.KUnit, loc)
+ | L'.KTuple ks =>
+ let
+ val ks' = List.mapPartial decompileKind ks
+ in
+ if length ks' = length ks then
+ SOME (L.KTuple ks', loc)
+ else
+ NONE
+ end
+
+ | L'.KError => NONE
+ | L'.KUnif (_, _, ref (L'.KKnown k)) => decompileKind k
+ | L'.KUnif _ => NONE
+ | L'.KTupleUnif (_, _, ref (L'.KKnown k)) => decompileKind k
+ | L'.KTupleUnif _ => NONE
+
+ | L'.KRel _ => NONE
+ | L'.KFun _ => NONE
+
+ fun maybeHnorm env c =
+ hnormCon env c
+ handle E.UnboundNamed _ => c
+
+ fun decompileCon env c =
+ case decompileCon' env c of
+ SOME v => SOME v
+ | NONE => decompileCon' env (maybeHnorm env c)
+
+ and decompileCon' env (c as (_, loc)) =
+ case #1 c of
+ L'.CRel i =>
+ let
+ val (s, _) = E.lookupCRel env i
+ in
+ SOME (L.CVar ([], s), loc)
+ end
+ | L'.CNamed i =>
+ let
+ val (s, _, _) = E.lookupCNamed env i
+ in
+ SOME (L.CVar ([], s), loc)
+ end
+ | L'.CModProj (m1, ms, x) =>
+ let
+ val (s, _) = E.lookupStrNamed env m1
+ in
+ SOME (L.CVar (s :: ms, x), loc)
+ end
+ | L'.CName s => SOME (L.CName s, loc)
+ | L'.CRecord (k, xcs) =>
+ let
+ fun fields xcs =
+ case xcs of
+ [] => SOME []
+ | (x, t) :: xcs =>
+ case (decompileCon env x, decompileCon env t, fields xcs) of
+ (SOME x, SOME t, SOME xcs) => SOME ((x, t) :: xcs)
+ | _ => NONE
+
+ val c' = Option.map (fn xcs => (L.CRecord xcs, loc))
+ (fields xcs)
+ in
+ Option.map (fn c' =>
+ case decompileKind k of
+ NONE => c'
+ | SOME k' => (L.CAnnot (c', (L.KRecord k', loc)), loc)) c'
+ end
+ | L'.CConcat (c1, c2) =>
+ (case (decompileCon env c1, decompileCon env c2) of
+ (SOME c1, SOME c2) => SOME (L.CConcat (c1, c2), loc)
+ | _ => NONE)
+ | L'.CUnit => SOME (L.CUnit, loc)
+ | L'.CUnif (nl, _, _, _, ref (L'.Known c)) => decompileCon env (E.mliftConInCon nl c)
+
+ | L'.CApp (f, x) =>
+ (case (decompileCon env f, decompileCon env x) of
+ (SOME f, SOME x) => SOME (L.CApp (f, x), loc)
+ | _ => NONE)
+
+ | L'.CTuple cs =>
+ let
+ val cs' = foldr (fn (c, cs') =>
+ case cs' of
+ NONE => NONE
+ | SOME cs' =>
+ case decompileCon env c of
+ NONE => NONE
+ | SOME c' => SOME (c' :: cs'))
+ (SOME []) cs
+ in
+ case cs' of
+ NONE => NONE
+ | SOME cs' => SOME (L.CTuple cs', loc)
+ end
+
+ | L'.CMap _ => SOME (L.CMap, loc)
+ | L'.TRecord c =>
+ (case decompileCon env c of
+ NONE => NONE
+ | SOME c' => SOME (L.TRecord c', loc))
+
+ | 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
+ L'.SgiCon (x, _, k, _) => naddCon (nd, x, k)
+ | L'.SgiConAbs (x, _, k) => naddCon (nd, x, k)
+ | L'.SgiConstraint cs => naddConstraint (nd, (env', cs, loc))
+ | L'.SgiVal (x, _, t) =>
+ let
+ 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 => shouldR t
+ | _ => false
+ end
+ in
+ if should t then
+ naddVal (nd, x)
+ else
+ nd
+ end
+ | L'.SgiStr (_, x, _, s) =>
+ (case #1 (hnormSgn env' s) of
+ L'.SgnConst sgis' => naddMod (nd, x, (env', buildNeeded env' sgis'))
+ | _ => nd)
+ | _ => nd,
+ E.sgiBinds env' (sgi, loc)))
+ (nempty, env) sgis)
+
+ val nd = buildNeeded env sgis
+
+ fun removeUsed (nd, ds) =
+ foldl (fn ((d, _), nd) =>
+ case d of
+ L.DCon (x, _, _) => ndelCon (nd, x)
+ | L.DVal (p, _) =>
+ foldl (fn (x, nd) => ndelVal (nd, x)) nd (patVarsOf p)
+ | L.DOpen _ => nempty
+ | L.DStr (x, _, _, (L.StrConst ds', _), _) =>
+ (case SM.find (nmods nd, x) of
+ NONE => nd
+ | SOME (env, nd') => naddMod (nd, x, (env, removeUsed (nd', ds'))))
+ | _ => nd)
+ nd ds
+
+ val nd = removeUsed (nd, ds)
+
+ (* Among the declarations present explicitly in the program, find the last constructor or constraint declaration.
+ * The new constructor/constraint declarations that we add may safely be put after that point. *)
+ fun findLast (ds, acc) =
+ case ds of
+ [] => ([], acc)
+ | (d : L.decl) :: ds' =>
+ let
+ val isCony = case #1 d of
+ L.DCon _ => true
+ | L.DDatatype _ => true
+ | L.DDatatypeImp _ => true
+ | L.DStr _ => true
+ | L.DConstraint _ => true
+ | _ => false
+ in
+ if isCony then
+ (ds, acc)
+ else
+ findLast (ds', d :: acc)
+ end
+
+ val (dPrefix, dSuffix) = findLast (rev ds, [])
+
+ fun extend (env, nd, ds) =
+ let
+ val ds' = List.mapPartial (fn (env', (c1, c2), loc) =>
+ case (decompileCon env' c1, decompileCon env' c2) of
+ (SOME c1, SOME c2) =>
+ SOME (L.DConstraint (c1, c2), loc)
+ | _ => NONE) (nconstraints nd)
+
+ val ds' =
+ case SS.listItems (nvals nd) of
+ [] => ds'
+ | xs =>
+ let
+ val ewild = (L.EWild, #2 str)
+ val ds'' = map (fn x => (L.DVal ((L.PVar x, #2 str), ewild), #2 str)) xs
+ in
+ ds'' @ ds'
+ end
+
+ val ds' =
+ case ncons nd of
+ [] => ds'
+ | xs =>
+ map (fn (x, k) =>
+ let
+ val k =
+ case decompileKind k of
+ NONE => (L.KWild, #2 str)
+ | SOME k => k
+
+ val cwild = (L.CWild k, #2 str)
+ in
+ (L.DCon (x, NONE, cwild), #2 str)
+ end) xs @ ds'
+
+ val ds = ds @ ds'
+ in
+ map (fn d as (L.DStr (x, s, tm, (L.StrConst ds', loc'), r), loc) =>
+ (case SM.find (nmods nd, x) of
+ NONE => d
+ | SOME (env, nd') =>
+ (L.DStr (x, s, tm, (L.StrConst (extend (env, nd', ds')), loc'), r), loc))
+ | d => d) ds
+ end
+ in
+ (L.StrConst (extend (env, nd, rev dPrefix) @ dSuffix), #2 str)
+ end
+ | _ => str)
+ | _ => str
+
+and elabDecl (dAll as (d, loc), (env, denv, gs)) =
+ let
+ (*val () = preface ("elabDecl", SourcePrint.p_decl dAll)*)
+ (*val befor = Time.now ()*)
+
+ val r =
+ case d of
+ L.DCon (x, ko, c) =>
+ let
+ val k' = case ko of
+ NONE => kunif env loc
+ | SOME k => elabKind env k
+
+ val (c', ck, gs') = elabCon (env, denv) c
+ val (env', n) = E.pushCNamed env x k' (SOME c')
+ in
+ checkKind env c' ck k';
+
+ ([(L'.DCon (x, n, k', c'), loc)], (env', denv, enD gs' @ gs))
+ end
+ | L.DDatatype dts =>
+ let
+ val k = (L'.KType, loc)
+
+ val (dts, env) = ListUtil.foldlMap
+ (fn ((x, xs, xcs), env) =>
+ let
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+ val (env, n) = E.pushCNamed env x k' NONE
+ in
+ ((x, n, xs, xcs), env)
+ end)
+ env dts
+
+ val (dts, (env, gs')) = ListUtil.foldlMap
+ (fn ((x, n, xs, xcs), (env, gs')) =>
+ let
+ val t = (L'.CNamed n, loc)
+ val nxs = length xs - 1
+ val t = ListUtil.foldli
+ (fn (i, _, t) =>
+ (L'.CApp (t, (L'.CRel (nxs - i), loc)), loc)) t xs
+
+ val (env', denv') = foldl (fn (x, (env', denv')) =>
+ (E.pushCRel env' x k,
+ D.enter denv')) (env, denv) xs
+
+ val (xcs, (used, env, gs')) =
+ ListUtil.foldlMap
+ (fn ((x, to), (used, env, gs)) =>
+ let
+ val (to, t, gs') = case to of
+ NONE => (NONE, t, gs)
+ | SOME t' =>
+ let
+ val (t', tk, gs') = elabCon (env', denv') t'
+ in
+ checkKind env' t' tk k;
+ (SOME t', (L'.TFun (t', t), loc), enD gs' @ gs)
+ end
+ val t = foldr (fn (x, t) => (L'.TCFun (L'.Implicit, x, k, t), loc)) t xs
+
+ val (env, n') = E.pushENamed env x t
+ in
+ if SS.member (used, x) then
+ strError env (DuplicateConstructor (x, loc))
+ else
+ ();
+ ((x, n', to), (SS.add (used, x), env, gs'))
+ end)
+ (SS.empty, env, gs') xcs
+ in
+ ((x, n, xs, xcs), (E.pushDatatype env n xs xcs, gs'))
+ end)
+ (env, []) dts
+ in
+ ([(L'.DDatatype dts, loc)], (env, denv, gs' @ gs))
+ end
+
+ | L.DDatatypeImp (_, [], _) => raise Fail "Empty DDatatypeImp"
+
+ | L.DDatatypeImp (x, m1 :: ms, s) =>
+ (case E.lookupStr env m1 of
+ NONE => (expError env (UnboundStrInExp (loc, m1));
+ ([], (env, denv, gs)))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => (conError env (UnboundStrInCon (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+ in
+ case hnormCon env (L'.CModProj (n, ms, s), loc) of
+ (L'.CModProj (n, ms, s), _) =>
+ (case E.projectDatatype env {sgn = sgn, str = str, field = s} of
+ NONE => (conError env (UnboundDatatype (loc, s));
+ ([], (env, denv, gs)))
+ | SOME (xs, xncs) =>
+ let
+ val k = (L'.KType, loc)
+ val k' = foldl (fn (_, k') => (L'.KArrow (k, k'), loc)) k xs
+ val t = (L'.CModProj (n, ms, s), loc)
+ val (env, n') = E.pushCNamed env x k' (SOME t)
+ val env = E.pushDatatype env n' xs xncs
+
+ val t = (L'.CNamed n', loc)
+ val nxs = length xs
+ val t = ListUtil.foldli (fn (i, _, t) =>
+ (L'.CApp (t, (L'.CRel (nxs - 1 - i), loc)), loc))
+ t xs
+ val env = foldl (fn ((x, n, to), env) =>
+ let
+ val t = case to of
+ NONE => t
+ | SOME t' => (L'.TFun (t', t), loc)
+
+ val t = foldr (fn (x, t) =>
+ (L'.TCFun (L'.Implicit, x, k, t), loc))
+ t xs
+ in
+ E.pushENamedAs env x n t
+ end) env xncs
+ in
+ ([(L'.DDatatypeImp (x, n', n, ms, s, xs, xncs), loc)], (env, denv, gs))
+ end)
+ | _ => (strError env (NotDatatype loc);
+ ([], (env, denv, [])))
+ end)
+
+ | L.DVal (p, e) =>
+ let
+ val ((p', pt), (env', bound)) = elabPat (p, (env, SS.empty))
+
+ val (e', et, gs1) = elabExp (env, denv) e
+
+ val c' = normClassConstraint env et
+
+ fun singleVar (p : L.pat) =
+ case #1 p of
+ L.PVar x => SOME x
+ | L.PAnnot (p', _) => singleVar p'
+ | _ => NONE
+ in
+ unifyCons env loc et pt;
+
+ (case exhaustive (env, et, [p'], loc) of
+ NONE => ()
+ | SOME p => if !mayDelay then
+ delayedExhaustives := (env, et, [p'], loc) :: !delayedExhaustives
+ else
+ expError env (Inexhaustive (loc, p)));
+
+ case singleVar p of
+ SOME x =>
+ let
+ val (env', n) = E.pushENamed env x et
+ in
+ ([(L'.DVal (x, n, c', e'), loc)], (env', denv, gs1 @ gs))
+ end
+ | NONE =>
+ let
+ val (env', n) = E.pushENamed env "$tmp" et
+ val vars = SS.listItems bound
+ val (decls, env') =
+ ListUtil.foldlMap (fn (x, env') =>
+ let
+ val e = (L.ECase ((L.EVar ([], "$tmp", L.Infer), loc),
+ [(p, (L.EVar ([], x, L.Infer), loc))]), loc)
+ val (e', t, _) = elabExp (env', denv) e
+ val (env', n) = E.pushENamed env' x t
+ in
+ ((L'.DVal (x, n, t, e'), loc),
+ env')
+ end) env' vars
+ in
+ ((L'.DVal ("$tmp", n, c', e'), loc) :: decls,
+ (env', denv, gs1 @ gs))
+ end
+ end
+ | L.DValRec vis =>
+ let
+ fun allowable (e, _) =
+ case e of
+ L.EAbs _ => true
+ | L.ECAbs (_, _, _, e) => allowable e
+ | L.EKAbs (_, e) => allowable e
+ | L.EDisjoint (_, _, e) => allowable e
+ | _ => false
+
+ val (vis, gs) = ListUtil.foldlMap
+ (fn ((x, co, e), gs) =>
+ let
+ val (c', _, gs1) = case co of
+ NONE => (cunif env (loc, ktype), ktype, [])
+ | SOME c => elabCon (env, denv) c
+ val c' = normClassConstraint env c'
+ in
+ ((x, c', e), enD gs1 @ gs)
+ end) gs vis
+
+ val (vis, env) = ListUtil.foldlMap (fn ((x, c', e), env) =>
+ let
+ val (env, n) = E.pushENamed env x c'
+ in
+ ((x, n, c', e), env)
+ end) env vis
+
+ val (vis, gs) = ListUtil.foldlMap (fn ((x, n, c', e), gs) =>
+ let
+ val (e', et, gs1) = elabExp (env, denv) e
+ in
+ checkCon env e' et c';
+ if allowable e then
+ ()
+ else
+ expError env (IllegalRec (x, e'));
+ ((x, n, c', e'), gs1 @ gs)
+ end) gs vis
+
+ val vis = map (fn (x, n, t, e) => (x, n, normClassConstraint env t, e)) vis
+ val d = (L'.DValRec vis, loc)
+ in
+ ([d], (E.declBinds env d, denv, gs))
+ end
+
+ | L.DSgn (x, sgn) =>
+ let
+ val (sgn', gs') = elabSgn (env, denv) sgn
+ val (env', n) = E.pushSgnNamed env x sgn'
+ in
+ ([(L'.DSgn (x, n, sgn'), loc)], (env', denv, enD gs' @ gs))
+ end
+
+ | L.DStr (x, sgno, tmo, str, _) =>
+ (case ModDb.lookup dAll of
+ SOME d =>
+ let
+ val () = if !verbose then TextIO.print ("REUSE: " ^ x ^ "\n") else ()
+ val env' = E.declBinds env d
+ val denv' = dopenConstraints (loc, env', denv) {str = x, strs = []}
+ in
+ ([d], (env', denv', gs))
+ end
+ | NONE =>
+ let
+ val () = if !verbose then TextIO.print ("CHECK: " ^ x ^ "\n") else ()
+
+ val () = if x = "Basis" then
+ raise Fail "Not allowed to redefine structure 'Basis'"
+ else
+ ()
+
+ val formal = Option.map (elabSgn (env, denv)) sgno
+
+ val (str', sgn', gs') =
+ case formal of
+ NONE =>
+ let
+ val (str', actual, gs') = elabStr (env, denv) str
+ in
+ (str', selfifyAt env {str = str', sgn = actual}, gs')
+ end
+ | SOME (formal, gs1) =>
+ let
+ val str = wildifyStr env (str, formal)
+ val (str', actual, gs2) = elabStr (env, denv) str
+ in
+ subSgn env loc (selfifyAt env {str = str', sgn = actual}) formal;
+ (str', formal, enD gs1 @ gs2)
+ end
+
+ val (env', n) = E.pushStrNamed env x sgn'
+
+ val denv' =
+ case #1 str' of
+ L'.StrConst _ => dopenConstraints (loc, env', denv) {str = x, strs = []}
+ | L'.StrApp _ => dopenConstraints (loc, env', denv) {str = x, strs = []}
+ | _ => denv
+
+ val dNew = (L'.DStr (x, n, sgn', str'), loc)
+ in
+ case #1 (hnormSgn env sgn') of
+ L'.SgnFun _ =>
+ (case #1 str' of
+ L'.StrFun _ => ()
+ | _ => strError env (FunctorRebind loc))
+ | _ => ();
+ Option.map (fn tm => ModDb.insert (dNew, tm)) tmo;
+ ([dNew], (env', denv', gs' @ gs))
+ end)
+
+ | L.DFfiStr (x, sgn, tmo) =>
+ (case ModDb.lookup dAll of
+ SOME d =>
+ let
+ val env' = E.declBinds env d
+ val denv' = dopenConstraints (loc, env', denv) {str = x, strs = []}
+ in
+ ([d], (env', denv', []))
+ end
+ | NONE =>
+ let
+ val (sgn', gs') = elabSgn (env, denv) sgn
+
+ val (env', n) = E.pushStrNamed env x sgn'
+
+ val dNew = (L'.DFfiStr (x, n, sgn'), loc)
+ in
+ case #1 sgn' of
+ L'.SgnConst sgis =>
+ (case List.find (fn (L'.SgiConAbs _, _) => false
+ | (L'.SgiCon _, _) => false
+ | (L'.SgiDatatype _, _) => false
+ | (L'.SgiVal _, _) => false
+ | _ => true) sgis of
+ NONE => ()
+ | SOME sgi => (ErrorMsg.errorAt loc "Disallowed signature item for FFI module";
+ epreface ("item", p_sgn_item env sgi)))
+ | _ => raise Fail "FFI signature isn't SgnConst";
+
+ Option.map (fn tm => ModDb.insert (dNew, tm)) tmo;
+ ([dNew], (env', denv, enD gs' @ gs))
+ end)
+
+ | L.DOpen (m, ms) =>
+ (case E.lookupStr env m of
+ NONE => (strError env (UnboundStr (loc, m));
+ ([], (env, denv, gs)))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {str = str, sgn = sgn, field = m} of
+ NONE => (strError env (UnboundStr (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+
+ val sgn = selfifyAt env {str = str, sgn = sgn}
+
+ val (ds, env') = dopen env {str = n, strs = ms, sgn = sgn}
+ val denv' = dopenConstraints (loc, env', denv) {str = m, strs = ms}
+ in
+ (ds, (env', denv', gs))
+ end)
+
+ | L.DConstraint (c1, c2) =>
+ let
+ val (c1', k1, gs1) = elabCon (env, denv) c1
+ val (c2', k2, gs2) = elabCon (env, denv) c2
+ val gs3 = D.prove env denv (c1', c2', loc)
+
+ val denv' = D.assert env denv (c1', c2')
+ in
+ checkKind env c1' k1 (L'.KRecord (kunif env loc), loc);
+ checkKind env c2' k2 (L'.KRecord (kunif env loc), loc);
+
+ ([(L'.DConstraint (c1', c2'), loc)], (env, denv', enD gs1 @ enD gs2 @ enD gs3 @ gs))
+ end
+
+ | L.DOpenConstraints (m, ms) =>
+ let
+ val denv = dopenConstraints (loc, env, denv) {str = m, strs = ms}
+ in
+ ([], (env, denv, gs))
+ end
+
+ | L.DExport str =>
+ let
+ val (str', sgn, gs') = elabStr (env, denv) str
+
+ val sgn =
+ case #1 (hnormSgn env sgn) of
+ L'.SgnConst sgis =>
+ let
+ fun doOne (all as (sgi, _), env) =
+ (case sgi of
+ L'.SgiVal (x, n, t) =>
+ let
+ fun doPage (makeRes, ran) =
+ case hnormCon env ran of
+ (L'.CApp (tf, arg), _) =>
+ (case (hnormCon env tf, hnormCon env arg) of
+ ((L'.CModProj (basis, [], "transaction"), _),
+ (L'.CApp (tf, arg3), _)) =>
+ (case (basis = !basis_r,
+ hnormCon env tf, hnormCon env arg3) of
+ (true,
+ (L'.CApp (tf, arg2), _),
+ ((L'.CRecord (_, []), _))) =>
+ (case (hnormCon env tf) of
+ (L'.CApp (tf, arg1), _) =>
+ (case (hnormCon env tf,
+ hnormCon env arg1,
+ hnormCon env arg2) of
+ (tf, arg1,
+ (L'.CRecord (_, []), _)) =>
+ let
+ val t = (L'.CApp (tf, arg1), loc)
+ val t = (L'.CApp (t, arg2), loc)
+ val t = (L'.CApp (t, arg3), loc)
+ val t = (L'.CApp (
+ (L'.CModProj
+ (basis, [], "transaction"), loc),
+ t), loc)
+
+ fun normArgs t =
+ case hnormCon env t of
+ (L'.TFun (dom, ran), loc) =>
+ (L'.TFun (hnormCon env dom, normArgs ran), loc)
+ | t' => t'
+ in
+ (L'.SgiVal (x, n, normArgs (makeRes t)), loc)
+ end
+ | _ => all)
+ | _ => all)
+ | _ => all)
+ | _ => all)
+ | _ => all
+ in
+ case hnormCon env t of
+ (L'.TFun (dom, ran), _) =>
+ (case hnormCon env dom of
+ (L'.TRecord domR, _) =>
+ doPage (fn t => (L'.TFun ((L'.TRecord domR,
+ loc),
+ t), loc), ran)
+ | _ => all)
+ | _ => doPage (fn t => t, t)
+ end
+ | _ => all,
+ E.sgiBinds env all)
+ in
+ (L'.SgnConst (#1 (ListUtil.foldlMap doOne env sgis)), loc)
+ end
+ | _ => sgn
+ in
+ ([(L'.DExport (E.newNamed (), sgn, str'), loc)], (env, denv, gs' @ gs))
+ end
+
+ | L.DTable (x, c, pe, ce) =>
+ let
+ val cstK = (L'.KRecord (L'.KRecord (L'.KUnit, loc), loc), loc)
+
+ val (c', k, gs') = elabCon (env, denv) c
+ val pkey = cunif env (loc, cstK)
+ val uniques = cunif env (loc, cstK)
+
+ val ct = tableOf ()
+ val ct = (L'.CApp (ct, c'), loc)
+ val ct = (L'.CApp (ct, (L'.CConcat (pkey, uniques), loc)), loc)
+
+ val (env, n) = E.pushENamed env x ct
+ val (pe', pet, gs'') = elabExp (env, denv) pe
+ val (ce', cet, gs''') = elabExp (env, denv) ce
+
+ val pst = (L'.CModProj (!basis_r, [], "primary_key"), loc)
+ val pst = (L'.CApp (pst, c'), loc)
+ val pst = (L'.CApp (pst, pkey), loc)
+
+ val cst = (L'.CModProj (!basis_r, [], "sql_constraints"), loc)
+ val cst = (L'.CApp (cst, c'), loc)
+ val cst = (L'.CApp (cst, uniques), loc)
+ in
+ checkKind env c' k (L'.KRecord (L'.KType, loc), loc);
+ checkCon env pe' pet pst;
+ checkCon env ce' cet cst;
+ ([(L'.DTable (!basis_r, x, n, c', pe', pkey, ce', uniques), loc)],
+ (env, denv, gs''' @ gs'' @ enD gs' @ gs))
+ end
+ | L.DSequence x =>
+ let
+ val (env, n) = E.pushENamed env x (sequenceOf ())
+ in
+ ([(L'.DSequence (!basis_r, x, n), loc)], (env, denv, gs))
+ end
+ | L.DView (x, e) =>
+ let
+ val (e', t, gs') = elabExp (env, denv) e
+
+ val k = (L'.KRecord (L'.KType, loc), loc)
+ val fs = cunif env (loc, k)
+ val ts = cunif env (loc, (L'.KRecord k, loc))
+ val tf = (L'.CApp ((L'.CMap (k, k), loc),
+ (L'.CAbs ("_", k, (L'.CRecord ((L'.KType, loc), []), loc)), loc)), loc)
+ val ts = (L'.CApp (tf, ts), loc)
+
+ val cv = viewOf ()
+ val cv = (L'.CApp (cv, fs), loc)
+ val (env', n) = E.pushENamed env x cv
+
+ val ct = queryOf ()
+ val ct = (L'.CApp (ct, (L'.CRecord ((L'.KRecord (L'.KType, loc), loc), []), loc)), loc)
+ val ct = (L'.CApp (ct, (L'.CRecord ((L'.KRecord (L'.KType, loc), loc), []), loc)), loc)
+ val ct = (L'.CApp (ct, ts), loc)
+ val ct = (L'.CApp (ct, fs), loc)
+ in
+ checkCon env e' t ct;
+ ([(L'.DView (!basis_r, x, n, e', fs), loc)],
+ (env', denv, gs' @ gs))
+ end
+
+ | L.DDatabase s => ([(L'.DDatabase s, loc)], (env, denv, gs))
+
+ | L.DCookie (x, c) =>
+ let
+ val (c', k, gs') = elabCon (env, denv) c
+ val (env, n) = E.pushENamed env x (L'.CApp (cookieOf (), c'), loc)
+ in
+ checkKind env c' k (L'.KType, loc);
+ ([(L'.DCookie (!basis_r, x, n, c'), loc)], (env, denv, enD gs' @ gs))
+ end
+ | L.DStyle x =>
+ let
+ val (env, n) = E.pushENamed env x (styleOf ())
+ in
+ ([(L'.DStyle (!basis_r, x, n), loc)], (env, denv, gs))
+ end
+ | L.DTask (e1, e2) =>
+ let
+ val (e1', t1, gs1) = elabExp (env, denv) e1
+ val (e2', t2, gs2) = elabExp (env, denv) e2
+
+ val targ = cunif env (loc, (L'.KType, loc))
+
+ val t1' = (L'.CModProj (!basis_r, [], "task_kind"), loc)
+ val t1' = (L'.CApp (t1', targ), loc)
+
+ val t2' = (L'.CApp ((L'.CModProj (!basis_r, [], "transaction"), loc),
+ (L'.TRecord (L'.CRecord ((L'.KType, loc), []), loc), loc)), loc)
+ val t2' = (L'.TFun (targ, t2'), loc)
+ in
+ checkCon env e1' t1 t1';
+ checkCon env e2' t2 t2';
+ ([(L'.DTask (e1', e2'), loc)], (env, denv, gs2 @ gs1 @ gs))
+ end
+ | L.DPolicy e1 =>
+ let
+ val (e1', t1, gs1) = elabExp (env, denv) e1
+
+ val t1' = (L'.CModProj (!basis_r, [], "sql_policy"), loc)
+ in
+ checkCon env e1' t1 t1';
+ ([(L'.DPolicy e1', loc)], (env, denv, gs1 @ gs))
+ end
+
+ | L.DOnError (m1, ms, s) =>
+ (case E.lookupStr env m1 of
+ NONE => (expError env (UnboundStrInExp (loc, m1));
+ ([], (env, denv, [])))
+ | SOME (n, sgn) =>
+ let
+ val (str, sgn) = foldl (fn (m, (str, sgn)) =>
+ case E.projectStr env {sgn = sgn, str = str, field = m} of
+ NONE => (conError env (UnboundStrInCon (loc, m));
+ (strerror, sgnerror))
+ | SOME sgn => ((L'.StrProj (str, m), loc), sgn))
+ ((L'.StrVar n, loc), sgn) ms
+
+ val t = case E.projectVal env {sgn = sgn, str = str, field = s} of
+ NONE => (expError env (UnboundExp (loc, s));
+ cerror)
+ | SOME t => t
+
+ val page = (L'.CModProj (!basis_r, [], "page"), loc)
+ val xpage = (L'.CApp ((L'.CModProj (!basis_r, [], "transaction"), loc), page), loc)
+ val func = (L'.TFun ((L'.CModProj (!basis_r, [], "xbody"), loc), xpage), loc)
+ in
+ (unifyCons env loc t func
+ handle CUnify _ => ErrorMsg.error "onError handler has wrong type.");
+ ([(L'.DOnError (n, ms, s), loc)], (env, denv, gs))
+ end)
+
+ | L.DFfi (x, modes, t) =>
+ let
+ val () = if Settings.getLessSafeFfi () then
+ ()
+ else
+ ErrorMsg.errorAt loc "To enable 'ffi' declarations, the .urp directive 'lessSafeFfi' is mandatory."
+
+ val (t', _, gs1) = elabCon (env, denv) t
+ val t' = normClassConstraint env t'
+ val (env', n) = E.pushENamed env x t'
+ in
+ ([(L'.DFfi (x, n, modes, t'), loc)], (env', denv, enD gs1 @ gs))
+ end
+
+ (*val tcs = List.filter (fn TypeClass _ => true | _ => false) (#3 (#2 r))*)
+ in
+ (*prefaces "/elabDecl" [("d", SourcePrint.p_decl dAll),
+ ("d'", p_list_sep PD.newline (ElabPrint.p_decl env) (#1 r))];*)
+ r
+ end
+
+and elabStr (env, denv) (str, loc) =
+ case str of
+ L.StrConst ds =>
+ let
+ val (ds', (_, _, gs)) = ListUtil.foldlMapConcat elabDecl (env, denv, []) ds
+ val sgis = ListUtil.mapConcat sgiOfDecl ds'
+
+ val (sgis, _, _, _, _) =
+ foldr (fn ((sgi, loc), (sgis, cons, vals, sgns, strs)) =>
+ case sgi of
+ L'.SgiConAbs (x, n, k) =>
+ let
+ val (cons, x) =
+ if SS.member (cons, x) then
+ (cons, "?" ^ x)
+ else
+ (SS.add (cons, x), x)
+ in
+ ((L'.SgiConAbs (x, n, k), loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiCon (x, n, k, c) =>
+ let
+ val (cons, x) =
+ if SS.member (cons, x) then
+ (cons, "?" ^ x)
+ else
+ (SS.add (cons, x), x)
+ in
+ ((L'.SgiCon (x, n, k, c), loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiDatatype dts =>
+ let
+ fun doOne ((x, n, xs, xncs), (cons, vals)) =
+ let
+ val (cons, x) =
+ if SS.member (cons, x) then
+ (cons, "?" ^ x)
+ else
+ (SS.add (cons, x), x)
+
+ val (xncs, vals) =
+ ListUtil.foldlMap
+ (fn ((x, n, t), vals) =>
+ if SS.member (vals, x) then
+ (("?" ^ x, n, t), vals)
+ else
+ ((x, n, t), SS.add (vals, x)))
+ vals xncs
+ in
+ ((x, n, xs, xncs), (cons, vals))
+ end
+
+ val (dts, (cons, vals)) = ListUtil.foldlMap doOne (cons, vals) dts
+ in
+ ((L'.SgiDatatype dts, loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiDatatypeImp (x, n, m1, ms, x', xs, xncs) =>
+ let
+ val (cons, x) =
+ if SS.member (cons, x) then
+ (cons, "?" ^ x)
+ else
+ (SS.add (cons, x), x)
+ in
+ ((L'.SgiDatatypeImp (x, n, m1, ms, x', xs, xncs), loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiVal (x, n, c) =>
+ let
+ val (vals, x) =
+ if SS.member (vals, x) then
+ (vals, "?" ^ x)
+ else
+ (SS.add (vals, x), x)
+ in
+ ((L'.SgiVal (x, n, c), loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiSgn (x, n, sgn) =>
+ let
+ val (sgns, x) =
+ if SS.member (sgns, x) then
+ (sgns, "?" ^ x)
+ else
+ (SS.add (sgns, x), x)
+ in
+ ((L'.SgiSgn (x, n, sgn), loc) :: sgis, cons, vals, sgns, strs)
+ end
+
+ | L'.SgiStr (im, x, n, sgn) =>
+ let
+ val (strs, x) =
+ if SS.member (strs, x) then
+ (strs, "?" ^ x)
+ else
+ (SS.add (strs, x), x)
+ in
+ ((L'.SgiStr (im, x, n, sgn), loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiConstraint _ => ((sgi, loc) :: sgis, cons, vals, sgns, strs)
+ | L'.SgiClassAbs (x, n, k) =>
+ let
+ val (cons, x) =
+ if SS.member (cons, x) then
+ (cons, "?" ^ x)
+ else
+ (SS.add (cons, x), x)
+ in
+ ((L'.SgiClassAbs (x, n, k), loc) :: sgis, cons, vals, sgns, strs)
+ end
+ | L'.SgiClass (x, n, k, c) =>
+ let
+ val (cons, x) =
+ if SS.member (cons, x) then
+ (cons, "?" ^ x)
+ else
+ (SS.add (cons, x), x)
+ in
+ ((L'.SgiClass (x, n, k, c), loc) :: sgis, cons, vals, sgns, strs)
+ end)
+
+ ([], SS.empty, SS.empty, SS.empty, SS.empty) sgis
+ in
+ ((L'.StrConst ds', loc), (L'.SgnConst sgis, loc), gs)
+ end
+ | L.StrVar x =>
+ (case E.lookupStr env x of
+ NONE =>
+ (strError env (UnboundStr (loc, x));
+ (strerror, sgnerror, []))
+ | SOME (n, sgn) => ((L'.StrVar n, loc), sgn, []))
+ | L.StrProj (str, x) =>
+ let
+ val (str', sgn, gs) = elabStr (env, denv) str
+ in
+ case E.projectStr env {str = str', sgn = sgn, field = x} of
+ NONE => (strError env (UnboundStr (loc, x));
+ (strerror, sgnerror, []))
+ | SOME sgn => ((L'.StrProj (str', x), loc), sgn, gs)
+ end
+ | L.StrFun (m, dom, ranO, str) =>
+ let
+ val (dom', gs1) = elabSgn (env, denv) dom
+ val (env', n) = E.pushStrNamed env m dom'
+ val denv' = dopenConstraints (loc, env', denv) {str = m, strs = []}
+ val (str', actual, gs2) = elabStr (env', denv') str
+
+ val (formal, gs3) =
+ case ranO of
+ NONE => (actual, [])
+ | SOME ran =>
+ let
+ val (ran', gs) = elabSgn (env', denv') ran
+ in
+ subSgn env' loc actual ran';
+ (ran', gs)
+ end
+ in
+ ((L'.StrFun (m, n, dom', formal, str'), loc),
+ (L'.SgnFun (m, n, dom', formal), loc),
+ enD gs1 @ gs2 @ enD gs3)
+ end
+ | L.StrApp (str1, str2) =>
+ let
+ val (str1', sgn1, gs1) = elabStr (env, denv) str1
+ val str2 =
+ case sgn1 of
+ (L'.SgnFun (_, _, dom, _), _) =>
+ let
+ val s = wildifyStr env (str2, dom)
+ in
+ (*Print.preface ("Wild", SourcePrint.p_str s);*)
+ s
+ end
+ | _ => str2
+ val (str2', sgn2, gs2) = elabStr (env, denv) str2
+ in
+ case #1 (hnormSgn env sgn1) of
+ L'.SgnError => (strerror, sgnerror, [])
+ | L'.SgnFun (m, n, dom, ran) =>
+ (subSgn env loc sgn2 dom;
+ case #1 (hnormSgn env ran) of
+ L'.SgnError => (strerror, sgnerror, [])
+ | L'.SgnConst sgis =>
+ let
+ (* This code handles a tricky case that led to a very nasty bug.
+ * An invariant about signatures of elaborated modules is that no
+ * identifier that could appear directly in a program is defined
+ * twice. We add "?" in front of identifiers where necessary to
+ * maintain the invariant, but the code below, to extend a functor
+ * body with a binding for the functor argument, wasn't written
+ * with the invariant in mind. Luckily for us, references to
+ * an identifier later within a signature work by globally
+ * unique index, so we just need to change the string name in the
+ * new declaration.
+ *
+ * ~~~ A few days later.... ~~~
+ * This is trickier than I thought! We might need to add
+ * arbitarily many question marks before the module name to
+ * avoid a clash, since some other code might depend on
+ * question-mark identifiers generated previously by this
+ * very code fragment. *)
+ fun mungeName m =
+ if List.exists (fn (L'.SgiStr (_, x, _, _), _) => x = m
+ | _ => false) sgis then
+ mungeName ("?" ^ m)
+ else
+ m
+
+ val m = mungeName m
+ in
+ ((L'.StrApp (str1', str2'), loc),
+ (L'.SgnConst ((L'.SgiStr (L'.Skip, m, n, selfifyAt env {str = str2', sgn = sgn2}), loc) :: sgis), loc),
+ gs1 @ gs2)
+ end
+ | _ => raise Fail "Unable to hnormSgn in functor application")
+ | _ => (strError env (NotFunctor sgn1);
+ (strerror, sgnerror, []))
+ end
+
+fun resolveClass env = E.resolveClass (hnormCon env) (consEq env dummy) env
+
+fun elabFile basis basis_tm topStr topSgn top_tm env file =
+ let
+ val () = ModDb.snapshot ()
+
+ val () = mayDelay := true
+ val () = delayedUnifs := []
+ val () = delayedExhaustives := []
+
+ val d = (L.DFfiStr ("Basis", (L.SgnConst basis, ErrorMsg.dummySpan), SOME basis_tm), ErrorMsg.dummySpan)
+ val (basis_n, env', sgn) =
+ case (if !incremental then ModDb.lookup d else NONE) of
+ NONE =>
+ let
+ val (sgn, gs) = elabSgn (env, D.empty) (L.SgnConst basis, ErrorMsg.dummySpan)
+ val () = case gs of
+ [] => ()
+ | _ => (app (fn (_, env, _, c1, c2) =>
+ prefaces "Unresolved"
+ [("c1", p_con env c1),
+ ("c2", p_con env c2)]) gs;
+ raise Fail "Unresolved disjointness constraints in Basis")
+
+ val (env', basis_n) = E.pushStrNamed env "Basis" sgn
+ in
+ ModDb.insert ((L'.DFfiStr ("Basis", basis_n, sgn), ErrorMsg.dummySpan), basis_tm);
+ (basis_n, env', sgn)
+ end
+ | SOME (d' as (L'.DFfiStr (_, basis_n, sgn), _)) =>
+ (basis_n, E.pushStrNamedAs env "Basis" basis_n sgn, sgn)
+ | _ => raise Fail "Elaborate: Basis impossible"
+
+ val () = basis_r := basis_n
+ val (ds, env') = dopen env' {str = basis_n, strs = [], sgn = sgn}
+
+ fun discoverC r x =
+ case E.lookupC env' x of
+ E.NotBound => raise Fail ("Constructor " ^ x ^ " unbound in Basis")
+ | E.Rel _ => raise Fail ("Constructor " ^ x ^ " bound relatively in Basis")
+ | E.Named (n, (_, loc)) => r := (L'.CNamed n, loc)
+
+ val () = discoverC int "int"
+ val () = discoverC float "float"
+ val () = discoverC string "string"
+ val () = discoverC char "char"
+ val () = discoverC table "sql_table"
+
+ val d = (L.DStr ("Top", SOME (L.SgnConst topSgn, ErrorMsg.dummySpan),
+ SOME (if Time.< (top_tm, basis_tm) then basis_tm else top_tm),
+ (L.StrConst topStr, ErrorMsg.dummySpan), false), ErrorMsg.dummySpan)
+ val (top_n, env', topSgn, topStr) =
+ case (if !incremental then ModDb.lookup d else NONE) of
+ NONE =>
+ let
+ val (topSgn, gs) = elabSgn (env', D.empty) (L.SgnConst topSgn, ErrorMsg.dummySpan)
+ val () = case gs of
+ [] => ()
+ | _ => raise Fail "Unresolved disjointness constraints in top.urs"
+ val (topStr, topSgn', gs) = elabStr (env', D.empty) (L.StrConst topStr, ErrorMsg.dummySpan)
+
+ val () = case gs of
+ [] => ()
+ | _ => app (fn Disjoint (loc, env, denv, c1, c2) =>
+ (case D.prove env denv (c1, c2, loc) of
+ [] => ()
+ | _ =>
+ (prefaces "Unresolved constraint in top.ur"
+ [("loc", PD.string (ErrorMsg.spanToString loc)),
+ ("c1", p_con env c1),
+ ("c2", p_con env c2)];
+ raise Fail "Unresolved constraint in top.ur"))
+ | TypeClass (env, c, r, loc) =>
+ let
+ val c = normClassKey env c
+ in
+ case resolveClass env c of
+ SOME e => r := SOME e
+ | NONE => expError env (Unresolvable (loc, c))
+ end) gs
+
+ val () = subSgn env' ErrorMsg.dummySpan topSgn' topSgn
+
+ val (env', top_n) = E.pushStrNamed env' "Top" topSgn
+ in
+ ModDb.insert ((L'.DStr ("Top", top_n, topSgn, topStr), ErrorMsg.dummySpan), top_tm);
+ (top_n, env', topSgn, topStr)
+ end
+ | SOME (d' as (L'.DStr (_, top_n, topSgn, topStr), _)) =>
+ (top_n, E.declBinds env' d', topSgn, topStr)
+ | _ => raise Fail "Elaborate: Top impossible"
+
+ val () = top_r := top_n
+
+ val (ds', env') = dopen env' {str = top_n, strs = [], sgn = topSgn}
+
+ fun elabDecl' x =
+ (resetKunif ();
+ resetCunif ();
+ elabDecl x)
+
+ val (file, (env'', _, gs)) = ListUtil.foldlMapConcat elabDecl' (env', D.empty, []) file
+
+ fun oneSummaryRound () =
+ if ErrorMsg.anyErrors () then
+ ()
+ else
+ let
+ val delayed = !delayedUnifs
+ in
+ delayedUnifs := [];
+ app (fn (loc, env, k, s1, s2) =>
+ unifySummaries env (loc, k, normalizeRecordSummary env s1, normalizeRecordSummary env s2))
+ delayed
+ end
+
+ val checkConstraintErrors = ref (fn () => ())
+ fun stopHere () = not (!unifyMore) andalso ErrorMsg.anyErrors ()
+ in
+ oneSummaryRound ();
+
+ if stopHere () then
+ ()
+ else
+ let
+ fun solver (gs : constraint list) =
+ let
+ val (gs, solved) =
+ ListUtil.foldlMapPartial
+ (fn (g : constraint, solved) =>
+ case g of
+ Disjoint (loc, env, denv, c1, c2) =>
+ (case D.prove env denv (c1, c2, loc) of
+ [] => (NONE, true)
+ | _ => (SOME g, solved))
+ | TypeClass (env, c, r, loc) =>
+ let
+ fun default () = (SOME g, solved)
+
+ fun resolver r c =
+ let
+ val c = normClassKey env c
+ in
+ case resolveClass env c of
+ SOME e => (r := SOME e;
+ (NONE, true))
+ | NONE =>
+ case #1 (hnormCon env c) of
+ L'.CApp (f, x) =>
+ (case (#1 (hnormCon env f), #1 (hnormCon env x)) of
+ (L'.CKApp (f, _), L'.CRecord (k, xcs)) =>
+ (case #1 (hnormCon env f) of
+ L'.CModProj (top_n', [], "folder") =>
+ if top_n' = top_n then
+ let
+ val e = (L'.EModProj (top_n, ["Folder"], "nil"), loc)
+ val e = (L'.EKApp (e, k), loc)
+
+ val (folder, _) = foldr (fn ((x, c), (folder, xcs)) =>
+ let
+ val e = (L'.EModProj (top_n, ["Folder"],
+ "cons"), loc)
+ val e = (L'.EKApp (e, k), loc)
+ val e = (L'.ECApp (e,
+ (L'.CRecord (k, xcs),
+ loc)), loc)
+ val e = (L'.ECApp (e, x), loc)
+ val e = (L'.ECApp (e, c), loc)
+ val e = (L'.EApp (e, folder), loc)
+ in
+ (e, (x, c) :: xcs)
+ end)
+ (e, []) xcs
+ in
+ (r := SOME folder;
+ (NONE, true))
+ end
+ else
+ default ()
+ | _ => default ())
+ | _ => default ())
+
+ | L'.TRecord c' =>
+ (case #1 (hnormCon env c') of
+ L'.CRecord (_, xts) =>
+ let
+ val witnesses = map (fn (x, t) =>
+ let
+ val r = ref NONE
+ val (opt, _) = resolver r t
+ in
+ case opt of
+ SOME _ => NONE
+ | NONE =>
+ case !r of
+ NONE => NONE
+ | SOME e =>
+ SOME (x, e, t)
+ end) xts
+ in
+ if List.all Option.isSome witnesses then
+ (r := SOME (L'.ERecord (map valOf witnesses), loc);
+ (NONE, true))
+ else
+ (SOME g, solved)
+ end
+ | _ => (SOME g, solved))
+
+ | _ => default ()
+ end
+ in
+ resolver r c
+ end)
+ false gs
+ in
+ case (gs, solved) of
+ ([], _) => ()
+ | (_, true) => (oneSummaryRound (); solver gs)
+ | _ =>
+ checkConstraintErrors :=
+ (fn () => app (fn Disjoint (loc, env, denv, c1, c2) =>
+ let
+ val c1' = ElabOps.hnormCon env c1
+ val c2' = ElabOps.hnormCon env c2
+
+ fun isUnif (c, _) =
+ case c of
+ L'.CUnif _ => true
+ | _ => false
+
+ fun maybeAttr (c, _) =
+ case c of
+ L'.CRecord ((L'.KType, _), xts) => true
+ | _ => false
+ in
+ ErrorMsg.errorAt loc "Couldn't prove field name disjointness";
+ eprefaces' [("Con 1", p_con env c1),
+ ("Con 2", p_con env c2),
+ ("Hnormed 1", p_con env c1'),
+ ("Hnormed 2", p_con env c2')]
+
+ (*app (fn (loc, env, k, s1, s2) =>
+ eprefaces' [("s1", p_summary env (normalizeRecordSummary env s1)),
+ ("s2", p_summary env (normalizeRecordSummary env s2))])
+ (!delayedUnifs);*)
+ end
+ | TypeClass (env, c, r, loc) =>
+ let
+ val c = normClassKey env c
+ in
+ case resolveClass env c of
+ SOME e => r := SOME e
+ | NONE => expError env (Unresolvable (loc, c))
+ end)
+ gs)
+ end
+ in
+ solver gs
+ end;
+
+ mayDelay := false;
+
+ if stopHere () then
+ ()
+ else
+ (app (fn (loc, env, k, s1, s2) =>
+ unifySummaries env (loc, k, normalizeRecordSummary env s1, normalizeRecordSummary env s2)
+ handle CUnify' (env', err) => (ErrorMsg.errorAt loc "Error in final record unification";
+ cunifyError env' err;
+ case !reducedSummaries of
+ NONE => ()
+ | SOME (s1, s2) =>
+ (ErrorMsg.errorAt loc "Stuck unifying these records after canceling matching pieces:";
+ eprefaces' [("Have", s1),
+ ("Need", s2)])))
+ (!delayedUnifs);
+ delayedUnifs := []);
+
+ if stopHere () then
+ ()
+ else
+ if List.exists kunifsInDecl file then
+ case U.File.findDecl kunifsInDecl file of
+ NONE => ()
+ | SOME d => declError env'' (KunifsRemain [d])
+ else
+ ();
+
+ if stopHere () then
+ ()
+ else
+ if List.exists cunifsInDecl file then
+ case U.File.findDecl cunifsInDecl file of
+ NONE => ()
+ | SOME d => declError env'' (CunifsRemain [d])
+ else
+ ();
+
+ if stopHere () then
+ ()
+ else
+ app (fn all as (env, _, _, loc) =>
+ case exhaustive all of
+ NONE => ()
+ | SOME p => expError env (Inexhaustive (loc, p)))
+ (!delayedExhaustives);
+
+ if stopHere () then
+ ()
+ else
+ !checkConstraintErrors ();
+
+ (*preface ("file", p_file env' file);*)
+
+ if !dumpTypes orelse (!dumpTypesOnError andalso ErrorMsg.anyErrors ()) then
+ let
+ open L'
+ open Print.PD
+ open Print
+
+ fun p_con env c = ElabPrint.p_con env (ElabOps.reduceCon env c)
+
+ fun dumpDecl (d, env) =
+ case #1 d of
+ DCon (x, _, k, _) => (print (box [string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ newline,
+ newline]);
+ E.declBinds env d)
+ | DVal (x, _, t, _) => (print (box [string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ newline,
+ newline]);
+ E.declBinds env d)
+ | DValRec vis => (app (fn (x, _, t, _) => print (box [string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ newline,
+ newline])) vis;
+ E.declBinds env d)
+ | DStr (x, _, _, str) => (print (box [string ("<" ^ x ^ ">"),
+ newline,
+ newline]);
+ dumpStr (str, env);
+ print (box [string ("</" ^ x ^ ">"),
+ newline,
+ newline]);
+ E.declBinds env d)
+ | _ => E.declBinds env d
+
+ and dumpStr (str, env) =
+ case #1 str of
+ StrConst ds => ignore (foldl dumpDecl env ds)
+ | _ => ()
+ in
+ ignore (foldl dumpDecl env' file)
+ end
+ else
+ ();
+
+ if ErrorMsg.anyErrors () then
+ ModDb.revert ()
+ else
+ ();
+
+ (*Print.preface("File", ElabPrint.p_file env file);*)
+
+ (L'.DFfiStr ("Basis", basis_n, sgn), ErrorMsg.dummySpan)
+ :: ds
+ @ (L'.DStr ("Top", top_n, topSgn, topStr), ErrorMsg.dummySpan)
+ :: ds' @ file
+ end
+ handle e => (ModDb.revert ();
+ raise e)
+
+end
diff --git a/src/elisp/urweb-compat.el b/src/elisp/urweb-compat.el
new file mode 100644
index 0000000..b94c2f4
--- /dev/null
+++ b/src/elisp/urweb-compat.el
@@ -0,0 +1,111 @@
+;;; urweb-compat.el --- Compatibility functions for Emacs variants for urweb-mode
+
+;; Based on sml-mode:
+;; Copyright (C) 1999, 2000, 2004 Stefan Monnier <monnier@gnu.org>
+;;
+;; Modified for urweb-mode:
+;; Copyright (C) 2008 Adam Chlipala <adamc@hcoop.net>
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'cl)
+
+(unless (fboundp 'set-keymap-parents)
+ (defun set-keymap-parents (m parents)
+ (if (keymapp parents) (setq parents (list parents)))
+ (set-keymap-parent
+ m
+ (if (cdr parents)
+ (reduce (lambda (m1 m2)
+ (let ((m (copy-keymap m1)))
+ (set-keymap-parent m m2) m))
+ parents
+ :from-end t)
+ (car parents)))))
+
+;; for XEmacs
+(when (fboundp 'temp-directory)
+ (defvar temporary-file-directory (temp-directory)))
+
+(unless (fboundp 'make-temp-file)
+ ;; Copied from Emacs-21's subr.el
+ (defun make-temp-file (prefix &optional dir-flag)
+ "Create a temporary file.
+The returned file name (created by appending some random characters at the end
+of PREFIX, and expanding against `temporary-file-directory' if necessary,
+is guaranteed to point to a newly created empty file.
+You can then use `write-region' to write new data into the file.
+
+If DIR-FLAG is non-nil, create a new empty directory instead of a file."
+ (let (file)
+ (while (condition-case ()
+ (progn
+ (setq file
+ (make-temp-name
+ (expand-file-name prefix temporary-file-directory)))
+ (if dir-flag
+ (make-directory file)
+ (write-region "" nil file nil 'silent))
+ nil)
+ (file-already-exists t))
+ ;; the file was somehow created by someone else between
+ ;; `make-temp-name' and `write-region', let's try again.
+ nil)
+ file)))
+
+
+
+(unless (fboundp 'regexp-opt)
+ (defun regexp-opt (strings &optional paren)
+ (let ((open (if paren "\\(" "")) (close (if paren "\\)" "")))
+ (concat open (mapconcat 'regexp-quote strings "\\|") close))))
+
+
+;;;;
+;;;; Custom
+;;;;
+
+;; doesn't exist in Emacs < 20.1
+(unless (fboundp 'set-face-bold-p)
+ (defun set-face-bold-p (face v &optional f)
+ (when v (ignore-errors (make-face-bold face)))))
+(unless (fboundp 'set-face-italic-p)
+ (defun set-face-italic-p (face v &optional f)
+ (when v (ignore-errors (make-face-italic face)))))
+
+;; doesn't exist in Emacs < 20.1
+(ignore-errors (require 'custom))
+(unless (fboundp 'defgroup)
+ (defmacro defgroup (&rest rest) ()))
+(unless (fboundp 'defcustom)
+ (defmacro defcustom (sym val str &rest rest) `(defvar ,sym ,val ,str)))
+(unless (fboundp 'defface)
+ (defmacro defface (sym val str &rest rest)
+ `(defvar ,sym (make-face ',sym) ,str)))
+
+(defvar :group ':group)
+(defvar :type ':type)
+(defvar :copy ':copy)
+(defvar :dense ':dense)
+(defvar :inherit ':inherit)
+(defvar :suppress ':suppress)
+
+(provide 'urweb-compat)
+
+;;; urweb-compat.el ends here
diff --git a/src/elisp/urweb-defs.el b/src/elisp/urweb-defs.el
new file mode 100644
index 0000000..1b21cba
--- /dev/null
+++ b/src/elisp/urweb-defs.el
@@ -0,0 +1,206 @@
+;;; urweb-defs.el --- Various definitions for urweb-mode
+
+;; Based on sml-mode:
+;; Copyright (C) 1999,2000,2003 Stefan Monnier <monnier@cs.yale.edu>
+;;
+;; Modified for urweb-mode:
+;; Copyright (C) 2008 Adam Chlipala <adamc@hcoop.net>
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'urweb-util)
+
+
+(defgroup urweb ()
+ "Editing Ur/Web code."
+ :group 'languages)
+
+(defvar urweb-outline-regexp
+ ;; `st' and `si' are to match structure and signature.
+ " \\|s[ti]\\|[ \t]*\\(let[ \t]+\\)?\\(fun\\|and\\)\\>"
+ "Regexp matching a major heading.
+This actually can't work without extending `outline-minor-mode' with the
+notion of \"the end of an outline\".")
+
+;;;
+;;; Internal defines
+;;;
+
+(defmap urweb-mode-map
+ ;; smarter cursor movement
+ '(("\C-c\C-i" . urweb-mode-info))
+ "The keymap used in `urweb-mode'."
+ ;; :inherit urweb-bindings
+ :group 'urweb)
+
+(defsyntax urweb-mode-syntax-table
+ `((?\* . ,(if urweb-builtin-nested-comments-flag ". 23n" ". 23"))
+ (?\( . "()1")
+ (?\) . ")(4")
+ ("._'" . "_")
+ (",;" . ".")
+ ;; `!' is not really a prefix-char, oh well!
+ ("~#!" . "'")
+ ("%&$+-/:<=>?@`^|" . "."))
+ "The syntax table used in `urweb-mode'.")
+
+
+(easy-menu-define urweb-mode-menu urweb-mode-map "Menu used in `urweb-mode'."
+ '("Ur/Web"
+ ["Ur/Web mode help (brief)" describe-mode t]
+ ["Ur/Web mode *info*" urweb-mode-info t]
+ ))
+
+;; Make's sure they appear in the menu bar when urweb-mode-map is active.
+;; On the hook for XEmacs only -- see easy-menu-add in auc-menu.el.
+;; (defun urweb-mode-menu-bar ()
+;; "Make sure menus appear in the menu bar as well as under mouse 3."
+;; (and (eq major-mode 'urweb-mode)
+;; (easy-menu-add urweb-mode-menu urweb-mode-map)))
+;; (add-hook 'urweb-mode-hook 'urweb-mode-menu-bar)
+
+;;
+;; regexps
+;;
+
+(defun urweb-syms-re (&rest syms)
+ (concat "\\<" (regexp-opt (flatten syms) t) "\\>"))
+
+;;
+
+(defconst urweb-module-head-syms
+ '("signature" "structure" "functor"))
+
+
+(defconst urweb-begin-syms
+ '("let" "struct" "sig")
+ "Symbols matching the `end' symbol.")
+
+(defconst urweb-begin-syms-re
+ (urweb-syms-re urweb-begin-syms)
+ "Symbols matching the `end' symbol.")
+
+;; (defconst urweb-user-begin-symbols-re
+;; (urweb-syms-re "let" "abstype" "local" "struct" "sig" "in" "with")
+;; "Symbols matching (loosely) the `end' symbol.")
+
+(defconst urweb-sexp-head-symbols-re
+ (urweb-syms-re "let" "struct" "sig" "in" "with"
+ "if" "then" "else" "case" "of" "fn" "fun" "val" "and"
+ "datatype" "type" "open" "include"
+ urweb-module-head-syms
+ "con" "map" "where" "extern" "constraint" "constraints"
+ "table" "sequence" "class" "cookie" "style" "task" "policy")
+ "Symbols starting an sexp.")
+
+;; (defconst urweb-not-arg-start-re
+;; (urweb-syms-re "in" "of" "end" "andalso")
+;; "Symbols that can't be found at the head of an arg.")
+
+;; (defconst urweb-not-arg-re
+;; (urweb-syms-re "in" "of" "end" "andalso")
+;; "Symbols that should not be confused with an arg.")
+
+(defconst urweb-=-starter-syms
+ (list* "|" "val" "fun" "and" "datatype" "con" "type" "class"
+ urweb-module-head-syms)
+ "Symbols that can be followed by a `='.")
+(defconst urweb-=-starter-re
+ (concat "\\S.|\\S.\\|" (urweb-syms-re (cdr urweb-=-starter-syms)))
+ "Symbols that can be followed by a `='.")
+
+(defconst urweb-indent-rule
+ (urweb-preproc-alist
+ `((,urweb-module-head-syms "d=" 0)
+ ("if" "else" 0)
+ (,urweb-=-starter-syms nil)
+ (("case" "datatype" "if" "then" "else"
+ "let" "open" "sig" "struct" "type" "val"
+ "con" "constraint" "table" "sequence" "class" "cookie"
+ "style" "task" "policy")))))
+
+(defconst urweb-starters-indent-after
+ (urweb-syms-re "let" "in" "struct" "sig")
+ "Indent after these.")
+
+(defconst urweb-delegate
+ (urweb-preproc-alist
+ `((("of" "else" "then" "with" "d=") . (not (urweb-bolp)))
+ ("in" . t)))
+ "Words which might delegate indentation to their parent.")
+
+(defcustom urweb-symbol-indent
+ '(("fn" . -3)
+ ("of" . 1)
+ ("|" . -2)
+ ("," . -2)
+ (";" . -2)
+ ;;("in" . 1)
+ ("d=" . 2))
+ "Special indentation alist for some symbols.
+An entry like (\"in\" . 1) indicates that a line starting with the
+symbol `in' should be indented one char further to the right.
+This is only used in a few specific cases, so it does not work
+for all symbols and in all lines starting with the given symbol."
+ :group 'urweb
+ :type '(repeat (cons string integer)))
+
+(defconst urweb-open-paren
+ (urweb-preproc-alist
+ `((,(list* "in" urweb-begin-syms) ,urweb-begin-syms-re "\\<end\\>")))
+ "Symbols that should behave somewhat like opening parens.")
+
+(defconst urweb-close-paren
+ `(("in" "\\<let\\>")
+ ("end" ,urweb-begin-syms-re)
+ ("then" "\\<if\\>")
+ ("else" "\\<if\\>" (urweb-bolp))
+ ("of" "\\<case\\>")
+ ("</xml>" "<xml>")
+ ("d=" nil))
+ "Symbols that should behave somewhat like close parens.")
+
+(defconst urweb-agglomerate-re "\\<else[ \t]+if\\>"
+ "Regexp of compound symbols (pairs of symbols to be considered as one).")
+
+(defconst urweb-non-nested-of-starter-re
+ (urweb-syms-re "datatype")
+ "Symbols that can introduce an `of' that shouldn't behave like a paren.")
+
+(defconst urweb-starters-syms
+ (append urweb-module-head-syms
+ '("datatype" "fun"
+ "open" "type" "val" "and"
+ "con" "constraint" "table" "sequence" "class" "cookie"
+ "style" "task" "policy"))
+ "The starters of new expressions.")
+
+(defconst urweb-exptrail-syms
+ '("if" "then" "else" "case" "of" "fn" "with" "map"))
+
+(defconst urweb-pipeheads
+ '("|" "of" "fun" "fn" "and" "datatype")
+ "A `|' corresponds to one of these.")
+
+
+(provide 'urweb-defs)
+
+;;; urweb-defs.el ends here
diff --git a/src/elisp/urweb-mode-startup.el b/src/elisp/urweb-mode-startup.el
new file mode 100644
index 0000000..4812599
--- /dev/null
+++ b/src/elisp/urweb-mode-startup.el
@@ -0,0 +1,20 @@
+
+;;; Generated autoloads from urweb-mode.el
+ (add-to-list 'load-path (file-name-directory load-file-name))
+
+(add-to-list (quote auto-mode-alist) (quote ("\\.ur\\(s\\)?\\'" . urweb-mode)))
+
+(autoload (quote urweb-mode) "urweb-mode" "\
+\\<urweb-mode-map>Major mode for editing Ur/Web code.
+This mode runs `urweb-mode-hook' just before exiting.
+\\{urweb-mode-map}
+
+\(fn)" t nil)
+
+;;;***
+
+;;;### (autoloads nil nil ("urweb-compat.el" "urweb-defs.el"
+;;;;;; "urweb-util.el") (18072 34664 948142))
+
+;;;***
+
diff --git a/src/elisp/urweb-mode.el b/src/elisp/urweb-mode.el
new file mode 100644
index 0000000..69b0e23
--- /dev/null
+++ b/src/elisp/urweb-mode.el
@@ -0,0 +1,930 @@
+;;; urweb-mode.el --- Major mode for editing (Standard) ML
+
+;; Based on sml-mode:
+;; Copyright (C) 1999,2000,2004 Stefan Monnier
+;; Copyright (C) 1994-1997 Matthew J. Morley
+;; Copyright (C) 1989 Lars Bo Nielsen
+;;
+;; Modified for urweb-mode:
+;; Copyright (C) 2008 Adam Chlipala <adamc@hcoop.net>
+
+;; Author: Lars Bo Nielsen
+;; Olin Shivers
+;; Fritz Knabe (?)
+;; Steven Gilmore (?)
+;; Matthew Morley <mjm@scs.leeds.ac.uk> (aka <matthew@verisity.com>)
+;; Matthias Blume <blume@cs.princeton.edu> (aka <blume@kurims.kyoto-u.ac.jp>)
+;; (Stefan Monnier) monnier@cs.yale.edu
+;; Adam Chlipala
+
+;; This file is not part of GNU Emacs, but it is distributed under the
+;; same conditions.
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2, or (at
+;; your option) any later version.
+
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Commentary:
+
+;;; HISTORY
+
+;; Still under construction: History obscure, needs a biographer as
+;; well as a M-x doctor. Change Log on request.
+
+;; Hacked by Olin Shivers for comint from Lars Bo Nielsen's sml.el.
+
+;; Hacked by Matthew Morley to incorporate Fritz Knabe's hilite and
+;; font-lock patterns, some of Steven Gilmore's (reduced) easy-menus,
+;; and numerous bugs and bug-fixes.
+
+;;; DESCRIPTION
+
+;; See accompanying info file: urweb-mode.info
+
+;;; FOR YOUR .EMACS FILE
+
+;; If urweb-mode.el lives in some non-standard directory, you must tell
+;; emacs where to get it. This may or may not be necessary:
+
+;; (add-to-list 'load-path "~jones/lib/emacs/")
+
+;; Then to access the commands autoload urweb-mode with that command:
+
+;; (load "urweb-mode-startup")
+
+;; urweb-mode-hook is run whenever a new urweb-mode buffer is created.
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'compile)
+(require 'urweb-util)
+(require 'urweb-move)
+(require 'urweb-defs)
+(condition-case nil (require 'skeleton) (error nil))
+
+;;; VARIABLES CONTROLLING INDENTATION
+
+(defcustom urweb-indent-level 4
+ "*Indentation of blocks in Ur/Web (see also `urweb-structure-indent')."
+ :group 'urweb
+ :type '(integer))
+
+(defcustom urweb-indent-args urweb-indent-level
+ "*Indentation of args placed on a separate line."
+ :group 'urweb
+ :type '(integer))
+
+(defcustom urweb-electric-semi-mode nil
+ "*If non-nil, `\;' will self insert, reindent the line, and do a newline.
+If nil, just insert a `\;'. (To insert while t, do: \\[quoted-insert] \;)."
+ :group 'urweb
+ :type 'boolean)
+
+(defcustom urweb-rightalign-and t
+ "If non-nil, right-align `and' with its leader.
+If nil: If t:
+ datatype a = A datatype a = A
+ and b = B and b = B"
+ :group 'urweb
+ :type 'boolean)
+
+;;; OTHER GENERIC MODE VARIABLES
+
+(defvar urweb-mode-info "urweb-mode"
+ "*Where to find Info file for `urweb-mode'.
+The default assumes the info file \"urweb-mode.info\" is on Emacs' info
+directory path. If it is not, either put the file on the standard path
+or set the variable `urweb-mode-info' to the exact location of this file
+
+ (setq urweb-mode-info \"/usr/me/lib/info/urweb-mode\")
+
+in your .emacs file. You can always set it interactively with the
+set-variable command.")
+
+(defvar urweb-mode-hook nil
+ "*Run upon entering `urweb-mode'.
+This is a good place to put your preferred key bindings.")
+
+;;; CODE FOR Ur/Web-MODE
+
+(defun urweb-mode-info ()
+ "Command to access the TeXinfo documentation for `urweb-mode'.
+See doc for the variable `urweb-mode-info'."
+ (interactive)
+ (require 'info)
+ (condition-case nil
+ (info urweb-mode-info)
+ (error (progn
+ (describe-variable 'urweb-mode-info)
+ (message "Can't find it... set this variable first!")))))
+
+
+;; font-lock setup
+
+(defconst urweb-keywords-regexp
+ (urweb-syms-re "and" "case" "class" "con" "constraint" "constraints"
+ "datatype" "else" "end" "extern" "fn" "map"
+ "fun" "functor" "if" "include"
+ "of" "open" "let" "in"
+ "rec" "sequence" "sig" "signature" "cookie" "style" "task" "policy"
+ "struct" "structure" "table" "view" "then" "type" "val" "where"
+ "with" "ffi"
+
+ "Name" "Type" "Unit")
+ "A regexp that matches any non-SQL keywords of Ur/Web.")
+
+(defconst urweb-sql-keywords-regexp
+ (urweb-syms-re "SELECT" "DISTINCT" "FROM" "AS" "WHERE" "SQL" "GROUP" "ORDER" "BY"
+ "HAVING" "LIMIT" "OFFSET" "ALL" "UNION" "INTERSECT" "EXCEPT"
+ "TRUE" "FALSE" "AND" "OR" "NOT" "COUNT" "AVG" "SUM" "MIN" "MAX"
+ "ASC" "DESC" "INSERT" "INTO" "VALUES" "UPDATE" "SET" "DELETE"
+ "PRIMARY" "KEY" "CONSTRAINT" "UNIQUE" "CHECK"
+ "FOREIGN" "REFERENCES" "ON" "NO" "ACTION" "CASCADE" "RESTRICT" "NULL"
+ "JOIN" "INNER" "OUTER" "LEFT" "RIGHT" "FULL" "CROSS" "SELECT1"
+ "IF" "THEN" "ELSE" "COALESCE" "LIKE" "RANDOM")
+ "A regexp that matches SQL keywords.")
+
+(defconst urweb-lident-regexp "\\<[a-z_][A-Za-z0-9_']*\\>"
+ "A regexp that matches lowercase Ur/Web identifiers.")
+
+(defconst urweb-cident-regexp "\\<[A-Z][A-Za-z0-9_']*\\>"
+ "A regexp that matches uppercase Ur/Web identifiers.")
+
+;;; Font-lock settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; The font lock regular expressions.
+
+(defun urweb-in-xml ()
+ (save-excursion
+ (let (
+ (depth 0)
+ (finished nil)
+ (answer nil)
+ (bound (max 0 (- (point) 1024)))
+ )
+ (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))
+
+ ((equal ch "-")
+ (if (looking-at "->")
+ (setq finished (= depth 0))))
+
+ ((and (= depth 0)
+ (not (looking-at "<xml")) ;; ignore <xml/>
+ (let ((face (get-text-property (point) 'face)))
+ (funcall (if (listp face) #'member #'equal) 'font-lock-tag-face 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))
+ )))
+ answer)))
+
+(defun amAttribute (face)
+ (if (ignore-errors (save-excursion (backward-word 2) (backward-char 1) (looking-at "<")))
+ nil
+ face))
+
+(defconst urweb-font-lock-keywords
+ `(;;(urweb-font-comments-and-strings)
+ ("\\(<\\sw+\\)\\(\\s-\\|\\sw\\|=\\|\"[^\"]*\"\\|{[^}]*}\\)*\\(/?>\\)"
+ (1 font-lock-tag-face)
+ (3 font-lock-tag-face))
+ ("\\(</\\sw+>\\)"
+ (1 font-lock-tag-face))
+ ("\\([^<>{}]+\\)"
+ (1 (if (urweb-in-xml)
+ font-lock-string-face
+ nil)))
+
+ ("\\<\\(fun\\|and\\)\\s-+\\(\\sw+\\)\\s-+[^ \t\n=]"
+ (1 font-lock-keyword-face)
+ (2 (amAttribute font-lock-function-name-face)))
+ ("\\<\\(\\(data\\)?type\\|con\\|class\\)\\s-+\\(\\sw+\\)"
+ (1 font-lock-keyword-face)
+ (3 (amAttribute font-lock-type-def-face)))
+ ("\\<\\(val\\|table\\|sequence\\|cookie\\|style\\|task\\|policy\\)\\s-+\\(\\sw+\\>\\s-*\\)?\\(\\sw+\\)\\s-*[=:]"
+ (1 font-lock-keyword-face)
+ (3 (amAttribute font-lock-variable-name-face)))
+ ("\\<\\(structure\\|functor\\)\\s-+\\(\\sw+\\)"
+ (1 font-lock-keyword-face)
+ (2 (amAttribute font-lock-module-def-face)))
+ ("\\<\\(signature\\)\\s-+\\(\\sw+\\)"
+ (1 font-lock-keyword-face)
+ (2 (amAttribute font-lock-interface-def-face)))
+
+ (,urweb-keywords-regexp . font-lock-keyword-face)
+ (,urweb-sql-keywords-regexp . font-lock-sql-face)
+ (,urweb-cident-regexp . font-lock-cvariable-face))
+ "Regexps matching standard Ur/Web keywords.")
+
+(defface font-lock-type-def-face
+ '((t (:bold t)))
+ "Font Lock mode face used to highlight type definitions."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-type-def-face 'font-lock-type-def-face
+ "Face name to use for type definitions.")
+
+(defface font-lock-module-def-face
+ '((t (:bold t)))
+ "Font Lock mode face used to highlight module definitions."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-module-def-face 'font-lock-module-def-face
+ "Face name to use for module definitions.")
+
+(defface font-lock-interface-def-face
+ '((t (:bold t)))
+ "Font Lock mode face used to highlight interface definitions."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-interface-def-face 'font-lock-interface-def-face
+ "Face name to use for interface definitions.")
+
+(defface font-lock-sql-face
+ '((t (:bold t)))
+ "Font Lock mode face used to highlight SQL keywords."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-sql-face 'font-lock-sql-face
+ "Face name to use for SQL keywords.")
+
+(defface font-lock-cvariable-face
+ '((t (:inherit font-lock-type-face)))
+ "Font Lock mode face used to highlight capitalized identifiers."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-cvariable-face 'font-lock-cvariable-face
+ "Face name to use for capitalized identifiers.")
+
+(defface font-lock-tag-face
+ '((t (:bold t)))
+ "Font Lock mode face used to highlight XML tags."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-tag-face 'font-lock-tag-face
+ "Face name to use for XML tags.")
+
+(defface font-lock-attr-face
+ '((t (:bold t)))
+ "Font Lock mode face used to highlight XML attributes."
+ :group 'font-lock-highlighting-faces)
+(defvar font-lock-attr-face 'font-lock-attr-face
+ "Face name to use for XML attributes.")
+
+;;
+;; Code to handle nested comments and unusual string escape sequences
+;;
+
+(defsyntax urweb-syntax-prop-table
+ '((?\\ . ".") (?* . "."))
+ "Syntax table for text-properties")
+
+;; For Emacsen that have no built-in support for nested comments
+(defun urweb-get-depth-st ()
+ (save-excursion
+ (let* ((disp (if (eq (char-before) ?\)) (progn (backward-char) -1) nil))
+ (_ (backward-char))
+ (disp (if (eq (char-before) ?\() (progn (backward-char) 0) disp))
+ (pt (point)))
+ (when disp
+ (let* ((depth
+ (save-match-data
+ (if (re-search-backward "\\*)\\|(\\*" nil t)
+ (+ (or (get-char-property (point) 'comment-depth) 0)
+ (case (char-after) (?\( 1) (?* 0))
+ disp)
+ 0)))
+ (depth (if (> depth 0) depth)))
+ (put-text-property pt (1+ pt) 'comment-depth depth)
+ (when depth urweb-syntax-prop-table))))))
+
+(defconst urweb-font-lock-syntactic-keywords
+ `(("^\\s-*\\(\\\\\\)" (1 ',urweb-syntax-prop-table))
+ ,@(unless urweb-builtin-nested-comments-flag
+ '(("(?\\(\\*\\))?" (1 (urweb-get-depth-st)))))))
+
+(defconst urweb-font-lock-defaults
+ '(urweb-font-lock-keywords nil nil ((?_ . "w") (?' . "w")) nil
+ (font-lock-syntactic-keywords . urweb-font-lock-syntactic-keywords)))
+
+;;;;
+;;;; Imenu support
+;;;;
+
+(defvar urweb-imenu-regexp
+ (concat "^[ \t]*\\(let[ \t]+\\)?"
+ (regexp-opt (append urweb-module-head-syms
+ '("and" "fun" "datatype" "type")) t)
+ "\\>"))
+
+(defun urweb-imenu-create-index ()
+ (let (alist)
+ (goto-char (point-max))
+ (while (re-search-backward urweb-imenu-regexp nil t)
+ (save-excursion
+ (let ((kind (match-string 2))
+ (column (progn (goto-char (match-beginning 2)) (current-column)))
+ (location
+ (progn (goto-char (match-end 0))
+ (urweb-forward-spaces)
+ (when (looking-at urweb-tyvarseq-re)
+ (goto-char (match-end 0)))
+ (point)))
+ (name (urweb-forward-sym)))
+ ;; Eliminate trivial renamings.
+ (when (or (not (member kind '("structure" "signature")))
+ (progn (search-forward "=")
+ (urweb-forward-spaces)
+ (looking-at "sig\\|struct")))
+ (push (cons (concat (make-string (/ column 2) ?\ ) name) location)
+ alist)))))
+ alist))
+
+;;; MORE CODE FOR URWEB-MODE
+
+;;;###autoload (add-to-list 'load-path (file-name-directory load-file-name))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.urs?\\'" . urweb-mode))
+
+;;;###autoload
+(defalias 'urweb-mode-derived-from
+ (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
+
+;;;###autoload
+(define-derived-mode urweb-mode urweb-mode-derived-from "Ur/Web"
+ "\\<urweb-mode-map>Major mode for editing Ur/Web code.
+This mode runs `urweb-mode-hook' just before exiting.
+\\{urweb-mode-map}"
+ (set (make-local-variable 'font-lock-defaults) urweb-font-lock-defaults)
+ (set (make-local-variable 'font-lock-multiline) 'undecided)
+ (set (make-local-variable 'outline-regexp) urweb-outline-regexp)
+ (set (make-local-variable 'imenu-create-index-function)
+ 'urweb-imenu-create-index)
+ (set (make-local-variable 'add-log-current-defun-function)
+ 'urweb-current-fun-name)
+ ;; Treat paragraph-separators in comments as paragraph-separators.
+ (set (make-local-variable 'paragraph-separate)
+ (concat "\\([ \t]*\\*)?\\)?\\(" paragraph-separate "\\)"))
+ ;; forward-sexp-function is an experimental variable in my hacked Emacs.
+ (set (make-local-variable 'forward-sexp-function) 'urweb-user-forward-sexp)
+ ;; For XEmacs
+ (easy-menu-add urweb-mode-menu)
+
+ ;; Compatibility. FIXME: we should use `-' in Emacs-CVS.
+ (unless (boundp 'skeleton-positions) (set (make-local-variable '@) nil))
+
+ (local-set-key (kbd "C-c C-c") 'compile)
+ (local-set-key (kbd "C-c /") 'urweb-close-matching-tag)
+
+ (urweb-mode-variables))
+
+(defun urweb-mode-variables ()
+ (set-syntax-table urweb-mode-syntax-table)
+ (setq local-abbrev-table urweb-mode-abbrev-table)
+ ;; A paragraph is separated by blank lines or ^L only.
+
+ (set (make-local-variable 'indent-line-function) 'urweb-indent-line)
+ (set (make-local-variable 'comment-start) "(* ")
+ (set (make-local-variable 'comment-end) " *)")
+ (set (make-local-variable 'comment-nested) t)
+ ;;(set (make-local-variable 'block-comment-start) "* ")
+ ;;(set (make-local-variable 'block-comment-end) "")
+ ;; (set (make-local-variable 'comment-column) 40)
+ (set (make-local-variable 'comment-start-skip) "(\\*+\\s-*"))
+
+(defun urweb-funname-of-and ()
+ "Name of the function this `and' defines, or nil if not a function.
+Point has to be right after the `and' symbol and is not preserved."
+ (urweb-forward-spaces)
+ (if (looking-at urweb-tyvarseq-re) (goto-char (match-end 0)))
+ (let ((sym (urweb-forward-sym)))
+ (urweb-forward-spaces)
+ (unless (or (member sym '(nil "d="))
+ (member (urweb-forward-sym) '("d=")))
+ sym)))
+
+;;; INDENTATION !!!
+
+(defun urweb-mark-function ()
+ "Synonym for `mark-paragraph' -- sorry.
+If anyone has a good algorithm for this..."
+ (interactive)
+ (mark-paragraph))
+
+(defun urweb-indent-line ()
+ "Indent current line of Ur/Web code."
+ (interactive)
+ (let ((savep (> (current-column) (current-indentation)))
+ (indent (max (or (ignore-errors (urweb-calculate-indentation)) 0) 0)))
+ (if savep
+ (save-excursion (indent-line-to indent))
+ (indent-line-to indent))))
+
+(defun urweb-back-to-outer-indent ()
+ "Unindents to the next outer level of indentation."
+ (interactive)
+ (save-excursion
+ (beginning-of-line)
+ (skip-chars-forward "\t ")
+ (let ((start-column (current-column))
+ (indent (current-column)))
+ (if (> start-column 0)
+ (progn
+ (save-excursion
+ (while (>= indent start-column)
+ (if (re-search-backward "^[^\n]" nil t)
+ (setq indent (current-indentation))
+ (setq indent 0))))
+ (backward-delete-char-untabify (- start-column indent)))))))
+
+(defun urweb-find-comment-indent ()
+ (save-excursion
+ (let ((depth 1))
+ (while (> depth 0)
+ (if (re-search-backward "(\\*\\|\\*)" nil t)
+ (cond
+ ;; FIXME: That's just a stop-gap.
+ ((eq (get-text-property (point) 'face) 'font-lock-string-face))
+ ((looking-at "*)") (incf depth))
+ ((looking-at comment-start-skip) (decf depth)))
+ (setq depth -1)))
+ (if (= depth 0)
+ (1+ (current-column))
+ nil))))
+
+(defun urweb-empty-line ()
+ (save-excursion
+ (beginning-of-line)
+ (let ((start-pos (point)))
+ (end-of-line)
+ (not (re-search-backward "[^\n \t]" start-pos t)))))
+
+(defun urweb-seek-back ()
+ (while (urweb-empty-line) (previous-line 1)))
+
+(defun urweb-skip-matching-braces ()
+ "Skip backwards past matching brace pairs, to calculate XML indentation after quoted Ur code"
+ (beginning-of-line)
+ (let ((start-pos (point))
+ (depth 0))
+ (end-of-line)
+ (while (re-search-backward "[{}]" start-pos t)
+ (cond
+ ((looking-at "}")
+ (incf depth))
+ ((looking-at "{")
+ (decf depth))))
+ (while (and (> depth 0) (re-search-backward "[{}]" nil t)
+ (cond
+ ((looking-at "}")
+ (incf depth))
+ ((looking-at "{")
+ (decf depth)))))))
+
+(defun urweb-new-tags ()
+ "Decide if the previous line of XML introduced unclosed tags"
+ (save-excursion
+ (let ((start-pos (point))
+ (depth 0)
+ (done nil))
+ (previous-line 1)
+ (urweb-seek-back)
+ (urweb-skip-matching-braces)
+ (urweb-seek-back)
+ (beginning-of-line)
+ (while (and (not done) (search-forward "<" start-pos t))
+ (cond
+ ((or (looking-at " ") (looking-at "="))
+ nil)
+ ((looking-at "/")
+ (if (re-search-forward "[^\\sw]>" start-pos t)
+ (when (> depth 0) (decf depth))
+ (setq done t)))
+ (t
+ (if (re-search-forward "[^\\sw]>" start-pos t)
+ (if (not (save-excursion (backward-char 2) (looking-at "/")))
+ (incf depth))
+ (setq done t)))))
+ (and (not done) (> depth 0)))))
+
+(defun urweb-tag-matching-indent ()
+ "Seek back to a matching opener tag and get its line's indent"
+ (save-excursion
+ (end-of-line)
+ (search-backward "</" nil t)
+ (urweb-tag-matcher)
+ (beginning-of-line)
+ (current-indentation)))
+
+(defun urweb-close-matching-tag ()
+ "Insert a closing XML tag for whatever tag is open at the point."
+ (interactive)
+ (assert (urweb-in-xml))
+ (save-excursion
+ (urweb-tag-matcher)
+ (re-search-forward "<\\([^ ={/>]+\\)" nil t))
+ (let ((tag (match-string-no-properties 1)))
+ (insert "</" tag ">")))
+
+(defconst urweb-sql-main-starters
+ '("SQL" "SELECT" "INSERT" "UPDATE" "DELETE" "FROM" "SELECT1" "WHERE"))
+
+(defconst urweb-sql-starters
+ (append urweb-sql-main-starters
+ '("^\\s-+FROM" "WHERE" "GROUP" "ORDER" "HAVING" "LIMIT" "OFFSET"
+ "VALUES" "SET")))
+
+(defconst urweb-sql-main-starters-re
+ (urweb-syms-re urweb-sql-main-starters))
+(defconst urweb-sql-starters-re
+ (urweb-syms-re urweb-sql-starters))
+
+(defconst urweb-sql-main-starters-paren-re
+ (concat "(" urweb-sql-main-starters-re))
+
+(defun urweb-in-sql ()
+ "Check if the point is in a block of SQL syntax."
+ (save-excursion
+ (let ((start-pos (point))
+ (depth 0)
+ done
+ (good t))
+ (when (re-search-backward urweb-sql-main-starters-paren-re nil t)
+ (forward-char)
+ (while (and (not done) (re-search-forward "[()]" start-pos t))
+ (save-excursion
+ (backward-char)
+ (cond
+ ((looking-at ")")
+ (cond
+ ((= depth 0) (setq done t) (setq good nil))
+ (t (decf depth))))
+ ((looking-at "(")
+ (incf depth)))))
+ good))))
+
+(defun urweb-sql-depth ()
+ "Check if the point is in a block of SQL syntax.
+ Returns the paren nesting depth if so, and nil otherwise."
+ (save-excursion
+ (let ((depth 0)
+ done)
+ (while (and (not done)
+ (re-search-backward "[()]" nil t))
+ (cond
+ ((looking-at ")")
+ (decf depth))
+ ((looking-at "(")
+ (if (looking-at urweb-sql-main-starters-paren-re)
+ (setq done t)
+ (incf depth)))))
+ (max 0 depth))))
+
+(defun urweb-calculate-indentation ()
+ (save-excursion
+ (beginning-of-line) (skip-chars-forward "\t ")
+ (urweb-with-ist
+ ;; Indentation for comments alone on a line, matches the
+ ;; proper indentation of the next line.
+ (when (looking-at "(\\*") (urweb-forward-spaces))
+ (let (data
+ (sym (save-excursion (urweb-forward-sym))))
+ (or
+ ;; Allow the user to override the indentation.
+ (when (looking-at (concat ".*" (regexp-quote comment-start)
+ "[ \t]*fixindent[ \t]*"
+ (regexp-quote comment-end)))
+ (current-indentation))
+
+ ;; Continued comment.
+ (and (looking-at "\\*") (urweb-find-comment-indent))
+
+ (and (urweb-in-xml)
+ (let ((prev-indent (save-excursion
+ (previous-line 1)
+ (urweb-seek-back)
+ (urweb-skip-matching-braces)
+ (urweb-seek-back)
+ (current-indentation))))
+ (cond
+ ((looking-at "</")
+ (urweb-tag-matching-indent))
+ ((urweb-new-tags)
+ (+ prev-indent 2))
+ (t
+ prev-indent))))
+
+ ;; Continued string ? (Added 890113 lbn)
+ (and (looking-at "\\\\")
+ (save-excursion
+ (if (save-excursion (previous-line 1)
+ (beginning-of-line)
+ (looking-at "[\t ]*\\\\"))
+ (progn (previous-line 1) (current-indentation))
+ (if (re-search-backward "[^\\\\]\"" nil t)
+ (1+ (current-column))
+ 0))))
+
+ ;; Closing parens. Could be handled below with `urweb-indent-relative'?
+ (and (looking-at "\\s)")
+ (save-excursion
+ (skip-syntax-forward ")")
+ (backward-sexp 1)
+ (if (urweb-dangling-sym)
+ (urweb-indent-default 'noindent)
+ (current-column))))
+
+ (and (or (looking-at "FROM") (looking-at urweb-sql-starters-re))
+
+ (save-excursion
+ (and (re-search-backward urweb-sql-starters-re nil t)
+ (if (looking-at urweb-sql-main-starters-re)
+ (current-column)
+ (current-indentation)))))
+
+ (and (urweb-in-sql)
+ (setq data (urweb-sql-depth))
+ (save-excursion
+ (re-search-backward urweb-sql-starters-re nil t)
+ (+ (current-column) 2 (* 2 data))))
+
+ (and (setq data (assoc sym urweb-close-paren))
+ (urweb-indent-relative sym data))
+
+ (and (member sym urweb-starters-syms)
+ (urweb-indent-starter sym))
+
+ (and (string= sym "|") (urweb-indent-pipe))
+
+ (urweb-indent-arg)
+ (urweb-indent-default))))))
+
+(defsubst urweb-bolp ()
+ (save-excursion (skip-chars-backward " \t|") (bolp)))
+
+(defun urweb-indent-starter (orig-sym)
+ "Return the indentation to use for a symbol in `urweb-starters-syms'.
+Point should be just before the symbol ORIG-SYM and is not preserved."
+ (let ((sym (unless (save-excursion (urweb-backward-arg))
+ (urweb-backward-spaces)
+ (urweb-backward-sym))))
+ (if (member sym '(";" "d=")) (setq sym nil))
+ (if sym (urweb-get-sym-indent sym)
+ ;; FIXME: this can take a *long* time !!
+ (setq sym (urweb-find-matching-starter urweb-starters-syms))
+ ;; Don't align with `and' because it might be specially indented.
+ (if (and (or (equal orig-sym "and") (not (equal sym "and")))
+ (urweb-bolp))
+ (+ (current-column)
+ (if (and urweb-rightalign-and (equal orig-sym "and"))
+ (- (length sym) 3) 0))
+ (urweb-indent-starter orig-sym)))))
+
+(defun urweb-indent-relative (sym data)
+ (save-excursion
+ (urweb-forward-sym) (urweb-backward-sexp nil)
+ (unless (second data) (urweb-backward-spaces) (urweb-backward-sym))
+ (+ (or (cdr (assoc sym urweb-symbol-indent)) 0)
+ (urweb-delegated-indent))))
+
+(defun urweb-indent-pipe ()
+ (let ((sym (urweb-find-matching-starter urweb-pipeheads
+ (urweb-op-prec "|" 'back))))
+ (when sym
+ (if (string= sym "|")
+ (if (urweb-bolp) (current-column) (urweb-indent-pipe))
+ (let ((pipe-indent (or (cdr (assoc "|" urweb-symbol-indent)) -2)))
+ (when (or (member sym '("datatype"))
+ (and (equal sym "and")
+ (save-excursion
+ (forward-word 1)
+ (not (urweb-funname-of-and)))))
+ (re-search-forward "="))
+ (urweb-forward-sym)
+ (urweb-forward-spaces)
+ (+ pipe-indent (current-column)))))))
+
+(defun urweb-find-forward (re)
+ (urweb-forward-spaces)
+ (while (and (not (looking-at re))
+ (progn
+ (or (ignore-errors (forward-sexp 1) t) (forward-char 1))
+ (urweb-forward-spaces)
+ (not (looking-at re))))))
+
+(defun urweb-indent-arg ()
+ (and (save-excursion (ignore-errors (urweb-forward-arg)))
+ ;;(not (looking-at urweb-not-arg-re))
+ ;; looks like a function or an argument
+ (urweb-move-if (urweb-backward-arg))
+ ;; an argument
+ (if (save-excursion (not (urweb-backward-arg)))
+ ;; a first argument
+ (+ (current-column) urweb-indent-args)
+ ;; not a first arg
+ (while (and (/= (current-column) (current-indentation))
+ (urweb-move-if (urweb-backward-arg))))
+ (unless (save-excursion (urweb-backward-arg))
+ ;; all earlier args are on the same line
+ (urweb-forward-arg) (urweb-forward-spaces))
+ (current-column))))
+
+(defun urweb-get-indent (data sym)
+ (let (d)
+ (cond
+ ((not (listp data)) data)
+ ((setq d (member sym data)) (cadr d))
+ ((and (consp data) (not (stringp (car data)))) (car data))
+ (t urweb-indent-level))))
+
+(defun urweb-dangling-sym ()
+ "Non-nil if the symbol after point is dangling.
+The symbol can be an Ur/Web symbol or an open-paren. \"Dangling\" means that
+it is not on its own line but is the last element on that line."
+ (save-excursion
+ (and (not (urweb-bolp))
+ (< (urweb-point-after (end-of-line))
+ (urweb-point-after (or (urweb-forward-sym) (skip-syntax-forward "("))
+ (urweb-forward-spaces))))))
+
+(defun urweb-delegated-indent ()
+ (if (urweb-dangling-sym)
+ (urweb-indent-default 'noindent)
+ (urweb-move-if (backward-word 1)
+ (looking-at urweb-agglomerate-re))
+ (current-column)))
+
+(defun urweb-get-sym-indent (sym &optional style)
+ "Find the indentation for the SYM we're `looking-at'.
+If indentation is delegated, point will move to the start of the parent.
+Optional argument STYLE is currently ignored."
+;;(assert (equal sym (save-excursion (urweb-forward-sym))))
+ (save-excursion
+ (let ((delegate (and (not (equal sym "end")) (assoc sym urweb-close-paren)))
+ (head-sym sym))
+ (when (and delegate (not (eval (third delegate))))
+ ;;(urweb-find-match-backward sym delegate)
+ (urweb-forward-sym) (urweb-backward-sexp nil)
+ (setq head-sym
+ (if (second delegate)
+ (save-excursion (urweb-forward-sym))
+ (urweb-backward-spaces) (urweb-backward-sym))))
+ (let ((idata (assoc head-sym urweb-indent-rule)))
+ (when idata
+ ;;(if (or style (not delegate))
+ ;; normal indentation
+ (let ((indent (urweb-get-indent (cdr idata) sym)))
+ (when indent (+ (urweb-delegated-indent) indent)))
+ ;; delgate indentation to the parent
+ ;;(urweb-forward-sym) (urweb-backward-sexp nil)
+ ;;(let* ((parent-sym (save-excursion (urweb-forward-sym)))
+ ;; (parent-indent (cdr (assoc parent-sym urweb-indent-starters))))
+ ;; check the special rules
+ ;;(+ (urweb-delegated-indent)
+ ;; (or (urweb-get-indent (cdr indent-data) 1 'strict)
+ ;; (urweb-get-indent (cdr parent-indent) 1 'strict)
+ ;; (urweb-get-indent (cdr indent-data) 0)
+ ;; (urweb-get-indent (cdr parent-indent) 0))))))))
+ )))))
+
+(defun urweb-indent-default (&optional noindent)
+ (condition-case nil
+ (progn
+ (let* ((sym-after (save-excursion (urweb-forward-sym)))
+ (_ (urweb-backward-spaces))
+ (sym-before (urweb-backward-sym))
+ (sym-indent (and sym-before (urweb-get-sym-indent sym-before)))
+ (indent-after (or (cdr (assoc sym-after urweb-symbol-indent)) 0)))
+ (when (equal sym-before "end")
+ ;; I don't understand what's really happening here, but when
+ ;; it's `end' clearly, we need to do something special.
+ (forward-word 1)
+ (setq sym-before nil sym-indent nil))
+ (cond
+ (sym-indent
+ ;; the previous sym is an indentation introducer: follow the rule
+ (if noindent
+ ;;(current-column)
+ sym-indent
+ (+ sym-indent indent-after)))
+ ;; If we're just after a hanging open paren.
+ ((and (eq (char-syntax (preceding-char)) ?\()
+ (save-excursion (backward-char) (urweb-dangling-sym)))
+ (backward-char)
+ (urweb-indent-default))
+ (t
+ ;; default-default
+ (let* ((prec-after (urweb-op-prec sym-after 'back))
+ (prec (or (urweb-op-prec sym-before 'back) prec-after 100)))
+ ;; go back until you hit a symbol that has a lower prec than the
+ ;; "current one", or until you backed over a sym that has the same prec
+ ;; but is at the beginning of a line.
+ (while (and (not (urweb-bolp))
+ (while (urweb-move-if (urweb-backward-sexp (1- prec))))
+ (not (urweb-bolp)))
+ (while (urweb-move-if (urweb-backward-sexp prec))))
+ (if noindent
+ ;; the `noindent' case does back over an introductory symbol
+ ;; such as `fun', ...
+ (progn
+ (urweb-move-if
+ (urweb-backward-spaces)
+ (member (urweb-backward-sym) urweb-starters-syms))
+ (current-column))
+ ;; Use `indent-after' for cases such as when , or ; should be
+ ;; outdented so that their following terms are aligned.
+ (+ (if (progn
+ (if (equal sym-after ";")
+ (urweb-move-if
+ (urweb-backward-spaces)
+ (member (urweb-backward-sym) urweb-starters-syms)))
+ (and sym-after (not (looking-at sym-after))))
+ indent-after 0)
+ (current-column))))))))
+ (error 0)))
+
+
+;; maybe `|' should be set to word-syntax in our temp syntax table ?
+(defun urweb-current-indentation ()
+ (save-excursion
+ (beginning-of-line)
+ (skip-chars-forward " \t|")
+ (current-column)))
+
+
+(defun urweb-find-matching-starter (syms &optional prec)
+ (let (sym)
+ (ignore-errors
+ (while
+ (progn (urweb-backward-sexp prec)
+ (setq sym (save-excursion (urweb-forward-sym)))
+ (not (or (member sym syms) (bobp)))))
+ (if (member sym syms) sym))))
+
+(defun urweb-skip-siblings ()
+ (while (and (not (bobp)) (urweb-backward-arg))
+ (urweb-find-matching-starter urweb-starters-syms)))
+
+(defun urweb-beginning-of-defun ()
+ (let ((sym (urweb-find-matching-starter urweb-starters-syms)))
+ (if (member sym '("fun" "and" "functor" "signature" "structure"
+ "datatype"))
+ (save-excursion (urweb-forward-sym) (urweb-forward-spaces)
+ (urweb-forward-sym))
+ ;; We're inside a "non function declaration": let's skip all other
+ ;; declarations that we find at the same level and try again.
+ (urweb-skip-siblings)
+ ;; Obviously, let's not try again if we're at bobp.
+ (unless (bobp) (urweb-beginning-of-defun)))))
+
+(defcustom urweb-max-name-components 3
+ "Maximum number of components to use for the current function name."
+ :group 'urweb
+ :type 'integer)
+
+(defun urweb-current-fun-name ()
+ (save-excursion
+ (let ((count urweb-max-name-components)
+ fullname name)
+ (end-of-line)
+ (while (and (> count 0)
+ (setq name (urweb-beginning-of-defun)))
+ (decf count)
+ (setq fullname (if fullname (concat name "." fullname) name))
+ ;; Skip all other declarations that we find at the same level.
+ (urweb-skip-siblings))
+ fullname)))
+
+(provide 'urweb-mode)
+
+;;; urweb-mode.el ends here
diff --git a/src/elisp/urweb-move.el b/src/elisp/urweb-move.el
new file mode 100644
index 0000000..08cd19e
--- /dev/null
+++ b/src/elisp/urweb-move.el
@@ -0,0 +1,373 @@
+;;; urweb-move.el --- Buffer navigation functions for urweb-mode
+
+;; Based on urweb-mode:
+;; Copyright (C) 1999, 2000, 2004 Stefan Monnier <monnier@gnu.org>
+;;
+;; Modified for urweb-mode:
+;; Copyright (C) 2008 Adam Chlipala <adamc@hcoop.net>
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+;;; Commentary:
+
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'urweb-util)
+(require 'urweb-defs)
+
+(defsyntax urweb-internal-syntax-table
+ '((?_ . "w")
+ (?' . "w")
+ (?. . "w"))
+ "Syntax table used for internal urweb-mode operation."
+ :copy urweb-mode-syntax-table)
+
+;;;
+;;; various macros
+;;;
+
+(defmacro urweb-with-ist (&rest r)
+ (let ((ost-sym (make-symbol "oldtable")))
+ `(let ((,ost-sym (syntax-table))
+ (case-fold-search nil)
+ (parse-sexp-lookup-properties t)
+ (parse-sexp-ignore-comments t))
+ (unwind-protect
+ (progn (set-syntax-table urweb-internal-syntax-table) . ,r)
+ (set-syntax-table ,ost-sym)))))
+(def-edebug-spec urweb-with-ist t)
+
+(defmacro urweb-move-if (&rest body)
+ (let ((pt-sym (make-symbol "point"))
+ (res-sym (make-symbol "result")))
+ `(let ((,pt-sym (point))
+ (,res-sym ,(cons 'progn body)))
+ (unless ,res-sym (goto-char ,pt-sym))
+ ,res-sym)))
+(def-edebug-spec urweb-move-if t)
+
+(defmacro urweb-point-after (&rest body)
+ `(save-excursion
+ ,@body
+ (point)))
+(def-edebug-spec urweb-point-after t)
+
+;;
+
+(defvar urweb-op-prec
+ (urweb-preproc-alist
+ '((("UNION" "INTERSECT" "EXCEPT") . 0)
+ (("AND" "OR") . 1)
+ ((">=" "<>" "<=" "=") . 4)
+ (("+" "-" "^") . 6)
+ (("*" "%") . 7)
+ (("NOT") 9)))
+ "Alist of Ur/Web infix operators and their precedence.")
+
+(defconst urweb-syntax-prec
+ (urweb-preproc-alist
+ `(("," . 20)
+ (("=>" "d=" "=of") . (65 . 40))
+ ("|" . (47 . 30))
+ (("case" "of" "fn") . 45)
+ (("if" "then" "else" ) . 50)
+ (";" . 53)
+ (("<-") . 55)
+ ("||" . 70)
+ ("&&" . 80)
+ ((":" ":>") . 90)
+ ("->" . 95)
+ ("with" . 100)
+ (,(cons "end" urweb-begin-syms) . 10000)))
+ "Alist of pseudo-precedence of syntactic elements.")
+
+(defun urweb-op-prec (op dir)
+ "Return the precedence of OP or nil if it's not an infix.
+DIR should be set to BACK if you want to precedence w.r.t the left side
+ and to FORW for the precedence w.r.t the right side.
+This assumes that we are `looking-at' the OP."
+ (when op
+ (let ((sprec (cdr (assoc op urweb-syntax-prec))))
+ (cond
+ ((consp sprec) (if (eq dir 'back) (car sprec) (cdr sprec)))
+ (sprec sprec)
+ (t
+ (let ((prec (cdr (assoc op urweb-op-prec))))
+ (when prec (+ prec 100))))))))
+
+;;
+
+(defun urweb-forward-spaces () (forward-comment 100000))
+(defun urweb-backward-spaces () (forward-comment -100000))
+
+
+;;
+;; moving forward around matching symbols
+;;
+
+(defun urweb-looking-back-at (re)
+ (save-excursion
+ (when (= 0 (skip-syntax-backward "w_")) (backward-char))
+ (looking-at re)))
+
+(defun urweb-find-match-forward (this match)
+ "Only works for word matches."
+ (let ((level 1)
+ (forward-sexp-function nil)
+ (either (concat this "\\|" match)))
+ (while (> level 0)
+ (forward-sexp 1)
+ (while (not (or (eobp) (urweb-looking-back-at either)))
+ (condition-case () (forward-sexp 1) (error (forward-char 1))))
+ (setq level
+ (cond
+ ((and (eobp) (> level 1)) (error "Unbalanced"))
+ ((urweb-looking-back-at this) (1+ level))
+ ((urweb-looking-back-at match) (1- level))
+ (t (error "Unbalanced")))))
+ t))
+
+(defun urweb-find-match-backward (this match)
+ (let ((level 1)
+ (forward-sexp-function nil)
+ (either (concat this "\\|" match)))
+ (while (> level 0)
+ (backward-sexp 1)
+ (while (not (or (bobp) (looking-at either)))
+ (condition-case () (backward-sexp 1) (error (backward-char 1))))
+ (setq level
+ (cond
+ ((and (bobp) (> level 1)) (error "Unbalanced"))
+ ((looking-at this) (1+ level))
+ ((looking-at match) (1- level))
+ (t (error "Unbalanced")))))
+ t))
+
+;;;
+;;; read a symbol, including the special "op <sym>" case
+;;;
+
+(defmacro urweb-move-read (&rest body)
+ (let ((pt-sym (make-symbol "point")))
+ `(let ((,pt-sym (point)))
+ ,@body
+ (when (/= (point) ,pt-sym)
+ (buffer-substring-no-properties (point) ,pt-sym)))))
+(def-edebug-spec urweb-move-read t)
+
+(defun urweb-poly-equal-p ()
+ (< (urweb-point-after (re-search-backward urweb-=-starter-re nil 'move))
+ (urweb-point-after (re-search-backward "=" nil 'move))))
+
+(defun urweb-nested-of-p ()
+ (< (urweb-point-after
+ (re-search-backward urweb-non-nested-of-starter-re nil 'move))
+ (urweb-point-after (re-search-backward "\\<case\\>" nil 'move))))
+
+(defun urweb-forward-sym-1 ()
+ (or (/= 0 (skip-syntax-forward "'w_"))
+ (/= 0 (skip-syntax-forward ".'"))))
+(defun urweb-forward-sym ()
+ (interactive)
+ (let ((sym (urweb-move-read (urweb-forward-sym-1))))
+ (cond
+ ((equal "op" sym)
+ (urweb-forward-spaces)
+ (concat "op " (or (urweb-move-read (urweb-forward-sym-1)) "")))
+ ((equal sym "=")
+ (save-excursion
+ (urweb-backward-sym-1)
+ (if (urweb-poly-equal-p) "=" "d=")))
+ ((equal sym "of")
+ (save-excursion
+ (urweb-backward-sym-1)
+ (if (urweb-nested-of-p) "of" "=of")))
+ ;; ((equal sym "datatype")
+ ;; (save-excursion
+ ;; (urweb-backward-sym-1)
+ ;; (urweb-backward-spaces)
+ ;; (if (eq (preceding-char) ?=) "=datatype" sym)))
+ (t sym))))
+
+(defun urweb-backward-sym-1 ()
+ (or (/= 0 (skip-syntax-backward ".'"))
+ (/= 0 (skip-syntax-backward "'w_"))))
+(defun urweb-backward-sym ()
+ (interactive)
+ (let ((sym (urweb-move-read (urweb-backward-sym-1))))
+ (let ((result
+ (when sym
+ ;; FIXME: what should we do if `sym' = "op" ?
+ (let ((point (point)))
+ (urweb-backward-spaces)
+ (if (equal "op" (urweb-move-read (urweb-backward-sym-1)))
+ (concat "op " sym)
+ (goto-char point)
+ (cond
+ ((string= sym "=") (if (urweb-poly-equal-p) "=" "d="))
+ ((string= sym "of") (if (urweb-nested-of-p) "of" "=of"))
+ ;; ((string= sym "datatype")
+ ;; (save-excursion (urweb-backward-spaces)
+ ;; (if (eq (preceding-char) ?=) "=datatype" sym)))
+ (t sym)))))))
+ (if (looking-at ">")
+ (substring result 1 nil)
+ result))))
+;; (if (save-excursion (backward-char 5) (looking-at "</xml>"))
+;; (progn
+;; (backward-char 5)
+;; (urweb-tag-matcher)
+;; (backward-char)
+;; (urweb-backward-sym))
+;; result))))
+
+(defun urweb-tag-matcher ()
+ "Seek back to a matching opener tag"
+ (let ((depth 0)
+ (done nil))
+ (while (and (not done) (search-backward ">" nil t))
+ (cond
+ ((save-excursion (backward-char 1) (looking-at " "))
+ nil)
+ ((save-excursion (backward-char 1) (looking-at "/"))
+ (when (not (re-search-backward "<[^ =]" nil t))
+ (setq done t)))
+ (t
+ (if (re-search-backward "<[^ =]" nil t)
+ (if (looking-at "</")
+ (incf depth)
+ (if (= depth 0)
+ (setq done t)
+ (decf depth)))
+ (setq done t)))))))
+
+(defun urweb-backward-sexp (prec)
+ "Move one sexp backward if possible, or one char else.
+Returns t if the move indeed moved through one sexp and nil if not.
+PREC is the precedence currently looked for."
+ (let ((result (let ((parse-sexp-lookup-properties t)
+ (parse-sexp-ignore-comments t))
+ (urweb-backward-spaces)
+ (let* ((op (urweb-backward-sym))
+ (op-prec (urweb-op-prec op 'back))
+ match)
+ (cond
+ ((not op)
+ (let ((point (point)))
+ (ignore-errors (let ((forward-sexp-function nil)) (backward-sexp 1)))
+ (if (/= point (point)) t (ignore-errors (backward-char 1)) nil)))
+ ;; stop as soon as precedence is smaller than `prec'
+ ((and prec op-prec (>= prec op-prec)) nil)
+ ;; special rules for nested constructs like if..then..else
+ ((and (or (not prec) (and prec op-prec))
+ (setq match (second (assoc op urweb-close-paren))))
+ (urweb-find-match-backward (concat "\\<" op "\\>") match))
+ ;; don't back over open-parens
+ ((assoc op urweb-open-paren) nil)
+ ;; infix ops precedence
+ ((and prec op-prec) (< prec op-prec))
+ ;; [ prec = nil ] a new operator, let's skip the sexps until the next
+ (op-prec (while (urweb-move-if (urweb-backward-sexp op-prec))) t)
+ ;; special symbols indicating we're getting out of a nesting level
+ ((string-match urweb-sexp-head-symbols-re op) nil)
+ ;; if the op was not alphanum, then we still have to do the backward-sexp
+ ;; this reproduces the usual backward-sexp, but it might be bogus
+ ;; in this case since !@$% is a perfectly fine symbol
+ (t t))))))
+ (if (save-excursion (backward-char 5) (looking-at "</xml>"))
+ (progn
+ (backward-char 5)
+ (urweb-tag-matcher)
+ (backward-char)
+ (urweb-backward-sexp prec))
+ result)))
+
+(defun urweb-forward-sexp (prec)
+ "Moves one sexp forward if possible, or one char else.
+Returns T if the move indeed moved through one sexp and NIL if not."
+ (let ((parse-sexp-lookup-properties t)
+ (parse-sexp-ignore-comments t))
+ (urweb-forward-spaces)
+ (let* ((op (urweb-forward-sym))
+ (op-prec (urweb-op-prec op 'forw))
+ match)
+ (cond
+ ((not op)
+ (let ((point (point)))
+ (ignore-errors (let ((forward-sexp-function nil)) (forward-sexp 1)))
+ (if (/= point (point)) t (forward-char 1) nil)))
+ ;; stop as soon as precedence is smaller than `prec'
+ ((and prec op-prec (>= prec op-prec)) nil)
+ ;; special rules for nested constructs like if..then..else
+ ((and (or (not prec) (and prec op-prec))
+ (setq match (cdr (assoc op urweb-open-paren))))
+ (urweb-find-match-forward (first match) (second match)))
+ ;; don't forw over close-parens
+ ((assoc op urweb-close-paren) nil)
+ ;; infix ops precedence
+ ((and prec op-prec) (< prec op-prec))
+ ;; [ prec = nil ] a new operator, let's skip the sexps until the next
+ (op-prec (while (urweb-move-if (urweb-forward-sexp op-prec))) t)
+ ;; special symbols indicating we're getting out of a nesting level
+ ((string-match urweb-sexp-head-symbols-re op) nil)
+ ;; if the op was not alphanum, then we still have to do the backward-sexp
+ ;; this reproduces the usual backward-sexp, but it might be bogus
+ ;; in this case since !@$% is a perfectly fine symbol
+ (t t))))) ;(or (string-match "\\sw" op) (urweb-backward-sexp prec))
+
+(defun urweb-in-word-p ()
+ (and (eq ?w (char-syntax (or (char-before) ? )))
+ (eq ?w (char-syntax (or (char-after) ? )))))
+
+(defun urweb-user-backward-sexp (&optional count)
+ "Like `backward-sexp' but tailored to the Ur/Web syntax."
+ (interactive "p")
+ (unless count (setq count 1))
+ (urweb-with-ist
+ (let ((point (point)))
+ (if (< count 0) (urweb-user-forward-sexp (- count))
+ (when (urweb-in-word-p) (forward-word 1))
+ (dotimes (i count)
+ (unless (urweb-backward-sexp nil)
+ (goto-char point)
+ (error "Containing expression ends prematurely")))))))
+
+
+(defun urweb-user-forward-sexp (&optional count)
+ "Like `forward-sexp' but tailored to the Ur/Web syntax."
+ (interactive "p")
+ (unless count (setq count 1))
+ (urweb-with-ist
+ (let ((point (point)))
+ (if (< count 0) (urweb-user-backward-sexp (- count))
+ (when (urweb-in-word-p) (backward-word 1))
+ (dotimes (i count)
+ (unless (urweb-forward-sexp nil)
+ (goto-char point)
+ (error "Containing expression ends prematurely")))))))
+
+;;(defun urweb-forward-thing ()
+;; (if (= ?w (char-syntax (char-after))) (forward-word 1) (forward-char 1)))
+
+(defun urweb-backward-arg () (interactive) (urweb-backward-sexp 1000))
+(defun urweb-forward-arg () (interactive) (urweb-forward-sexp 1000))
+
+
+(provide 'urweb-move)
+
+;;; urweb-move.el ends here
diff --git a/src/elisp/urweb-util.el b/src/elisp/urweb-util.el
new file mode 100644
index 0000000..55a1e27
--- /dev/null
+++ b/src/elisp/urweb-util.el
@@ -0,0 +1,123 @@
+;;; urweb-util.el --- Utility functions for urweb-mode
+
+;; Based on sml-mode:
+;; Copyright (C) 1999-2000 Stefan Monnier <monnier@cs.yale.edu>
+;;
+;; Modified for urweb-mode:
+;; Copyright (C) 2008 Adam Chlipala <adamc@hcoop.net>
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'cl) ;for `reduce'
+(require 'urweb-compat)
+
+;;
+
+(defun flatten (ls &optional acc)
+ (if (null ls) acc
+ (let ((rest (flatten (cdr ls) acc))
+ (head (car ls)))
+ (if (listp head)
+ (flatten head rest)
+ (cons head rest)))))
+
+(defun urweb-preproc-alist (al)
+ "Expand an alist AL where keys can be lists of keys into a normal one."
+ (reduce (lambda (x al)
+ (let ((k (car x))
+ (v (cdr x)))
+ (if (consp k)
+ (append (mapcar (lambda (y) (cons y v)) k) al)
+ (cons x al))))
+ al
+ :initial-value nil
+ :from-end t))
+
+;;;
+;;; defmap
+;;;
+
+(defun custom-create-map (m bs args)
+ (let (inherit dense suppress)
+ (while args
+ (let ((key (first args))
+ (val (second args)))
+ (cond
+ ((eq key :dense) (setq dense val))
+ ((eq key :inherit) (setq inherit val))
+ ((eq key :group) )
+ ;;((eq key :suppress) (setq suppress val))
+ (t (message "Uknown argument %s in defmap" key))))
+ (setq args (cddr args)))
+ (unless (keymapp m)
+ (setq bs (append m bs))
+ (setq m (if dense (make-keymap) (make-sparse-keymap))))
+ (dolist (b bs)
+ (let ((keys (car b))
+ (binding (cdr b)))
+ (dolist (key (if (consp keys) keys (list keys)))
+ (cond
+ ((symbolp key)
+ (substitute-key-definition key binding m global-map))
+ ((null binding)
+ (unless (keymapp (lookup-key m key)) (define-key m key binding)))
+ ((let ((o (lookup-key m key)))
+ (or (null o) (numberp o) (eq o 'undefined)))
+ (define-key m key binding))))))
+ (cond
+ ((keymapp inherit) (set-keymap-parent m inherit))
+ ((consp inherit) (set-keymap-parents m inherit)))
+ m))
+
+(defmacro defmap (m bs doc &rest args)
+ `(defconst ,m
+ (custom-create-map (if (boundp ',m) ,m) ,bs ,(cons 'list args))
+ ,doc))
+
+;; defsyntax ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun custom-create-syntax (css args)
+ (let ((st (make-syntax-table (cadr (memq :copy args)))))
+ (dolist (cs css)
+ (let ((char (car cs))
+ (syntax (cdr cs)))
+ (if (sequencep char)
+ (mapcar* (lambda (c) (modify-syntax-entry c syntax st)) char)
+ (modify-syntax-entry char syntax st))))
+ st))
+
+(defmacro defsyntax (st css doc &rest args)
+ `(defconst ,st (custom-create-syntax ,css ,(cons 'list args)) ,doc))
+
+;;;;
+;;;; Compatibility info
+;;;;
+
+(defvar urweb-builtin-nested-comments-flag
+ (ignore-errors
+ (not (equal (let ((st (make-syntax-table)))
+ (modify-syntax-entry ?\* ". 23n" st) st)
+ (let ((st (make-syntax-table)))
+ (modify-syntax-entry ?\* ". 23" st) st))))
+ "Non-nil means this Emacs understands the `n' in syntax entries.")
+
+(provide 'urweb-util)
+
+;;; urweb-util.el ends here
diff --git a/src/errormsg.sig b/src/errormsg.sig
new file mode 100644
index 0000000..9242584
--- /dev/null
+++ b/src/errormsg.sig
@@ -0,0 +1,56 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature ERROR_MSG = sig
+
+ type pos = {line : int,
+ char : int}
+
+ type span = {file : string,
+ first : pos,
+ last : pos}
+
+ type 'a located = 'a * span
+
+ val posToString : pos -> string
+ val spanToString : span -> string
+
+ val dummyPos : pos
+ val dummySpan : span
+
+ val resetPositioning : string -> unit
+ val newline : int -> unit
+ val lastLineStart : unit -> int
+ val posOf : int -> pos
+ val spanOf : int * int -> span
+
+ val resetErrors : unit -> unit
+ val anyErrors : unit -> bool
+ val error : string -> unit
+ val errorAt : span -> string -> unit
+ val errorAt' : int * int -> string -> unit
+end
diff --git a/src/errormsg.sml b/src/errormsg.sml
new file mode 100644
index 0000000..8f3c93b
--- /dev/null
+++ b/src/errormsg.sml
@@ -0,0 +1,107 @@
+(* Copyright (c) 2008, 2012, 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 ErrorMsg :> ERROR_MSG = struct
+
+type pos = {line : int,
+ char : int}
+
+type span = {file : string,
+ first : pos,
+ last : pos}
+
+type 'a located = 'a * span
+
+
+fun posToString {line, char} =
+ String.concat [Int.toString line, ":", Int.toString char]
+
+fun spanToString {file, first, last} =
+ String.concat [file, ":", posToString first, "-", posToString last]
+
+val dummyPos = {line = 0,
+ char = 0}
+val dummySpan = {file = "",
+ first = dummyPos,
+ last = dummyPos}
+
+
+val file = ref ""
+val numLines = ref 1
+val lines : int list ref = ref []
+
+fun resetPositioning fname = (file := fname;
+ numLines := 1;
+ lines := [])
+
+fun newline pos = (numLines := !numLines + 1;
+ lines := pos :: !lines)
+
+fun lastLineStart () =
+ case !lines of
+ [] => 0
+ | n :: _ => n+1
+
+fun posOf n =
+ let
+ fun search lineNum lines =
+ case lines of
+ [] => {line = 1,
+ char = n}
+ | bound :: rest =>
+ if n > bound then
+ {line = lineNum,
+ char = n - bound - 1}
+ else
+ search (lineNum - 1) rest
+ in
+ search (!numLines) (!lines)
+ end
+
+fun spanOf (pos1, pos2) = {file = !file,
+ first = posOf pos1,
+ last = posOf pos2}
+
+
+val errors = ref false
+
+fun resetErrors () = errors := false
+fun anyErrors () = !errors
+fun error s = (TextIO.output (TextIO.stdErr, s);
+ TextIO.output1 (TextIO.stdErr, #"\n");
+ errors := true)
+
+fun errorAt (span : span) s = (TextIO.output (TextIO.stdErr, #file span);
+ TextIO.output (TextIO.stdErr, ":");
+ TextIO.output (TextIO.stdErr, posToString (#first span));
+ TextIO.output (TextIO.stdErr, ": (to ");
+ TextIO.output (TextIO.stdErr, posToString (#last span));
+ TextIO.output (TextIO.stdErr, ") ");
+ error s)
+fun errorAt' span s = errorAt (spanOf span) s
+
+end
diff --git a/src/especialize.sig b/src/especialize.sig
new file mode 100644
index 0000000..135e3a0
--- /dev/null
+++ b/src/especialize.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature ESPECIALIZE = sig
+
+ val specialize : Core.file -> Core.file
+
+ val functionInside : IntBinarySet.set -> Core.con -> bool
+
+end
diff --git a/src/especialize.sml b/src/especialize.sml
new file mode 100644
index 0000000..7cf145c
--- /dev/null
+++ b/src/especialize.sml
@@ -0,0 +1,717 @@
+(* Copyright (c) 2008-2013, 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 ESpecialize :> ESPECIALIZE = struct
+
+open Core
+
+structure E = CoreEnv
+structure U = CoreUtil
+
+type skey = exp
+
+structure K = struct
+type ord_key = con list * exp list
+fun compare ((cs1, es1), (cs2, es2)) = Order.join (Order.joinL U.Con.compare (cs1, cs2),
+ fn () => Order.joinL U.Exp.compare (es1, es2))
+end
+
+structure KM = BinaryMapFn(K)
+structure IM = IntBinaryMap
+structure IS = IntBinarySet
+
+val freeVars = U.Exp.foldB {kind = fn (_, _, xs) => xs,
+ con = fn (_, _, xs) => xs,
+ exp = fn (bound, e, xs) =>
+ case e of
+ ERel x =>
+ if x >= bound then
+ IS.add (xs, x - bound)
+ else
+ xs
+ | _ => xs,
+ bind = fn (bound, b) =>
+ case b of
+ U.Exp.RelE _ => bound + 1
+ | _ => bound}
+ 0 IS.empty
+
+fun isPolyT (t, _) =
+ case t of
+ TFun (_, ran) => isPolyT ran
+ | TCFun _ => true
+ | TKFun _ => true
+ | _ => false
+
+fun isPoly (d, _) =
+ case d of
+ DVal (_, _, t, _, _) => isPolyT t
+ | DValRec vis => List.exists (isPolyT o #3) vis
+ | _ => false
+
+fun positionOf (v : int, ls) =
+ let
+ fun pof (pos, ls) =
+ case ls of
+ [] => raise Fail "Defunc.positionOf"
+ | v' :: ls' =>
+ if v = v' then
+ pos
+ else
+ pof (pos + 1, ls')
+ in
+ pof (0, ls)
+ end
+
+fun squish fvs =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn bound => fn e =>
+ case e of
+ ERel x =>
+ if x >= bound then
+ ERel (positionOf (x - bound, fvs) + bound)
+ else
+ e
+ | _ => e,
+ bind = fn (bound, b) =>
+ case b of
+ U.Exp.RelE _ => bound + 1
+ | _ => bound}
+ 0
+
+type func = {
+ name : string,
+ args : int KM.map,
+ body : exp,
+ typ : con,
+ tag : string,
+ constArgs : int (* What length prefix of the arguments never vary across recursive calls? *)
+}
+
+type state = {
+ maxName : int,
+ funcs : func IM.map,
+ decls : (string * int * con * exp * string) list,
+ specialized : IS.set
+}
+
+fun default (_, x, st) = (x, st)
+
+fun functionInside known =
+ U.Con.exists {kind = fn _ => false,
+ con = fn TFun _ => true
+ | TCFun _ => true
+ | CFfi ("Basis", "transaction") => true
+ | CFfi ("Basis", "eq") => true
+ | CFfi ("Basis", "num") => true
+ | CFfi ("Basis", "ord") => true
+ | CFfi ("Basis", "show") => true
+ | CFfi ("Basis", "read") => true
+ | CFfi ("Basis", "sql_injectable_prim") => true
+ | CFfi ("Basis", "sql_injectable") => true
+ | CNamed n => IS.member (known, n)
+ | _ => false}
+
+fun getApp (e, _) =
+ case e of
+ ENamed f => SOME (f, [])
+ | EApp (e1, e2) =>
+ (case getApp e1 of
+ NONE => NONE
+ | SOME (f, xs) => SOME (f, xs @ [e2]))
+ | _ => NONE
+
+val getApp = fn e => case getApp e of
+ v as SOME (_, _ :: _) => v
+ | _ => NONE
+
+val maxInt = Option.getOpt (Int.maxInt, 9999)
+
+fun calcConstArgs enclosingFunctions e =
+ let
+ fun ca depth e =
+ case #1 e of
+ EPrim _ => maxInt
+ | ERel _ => maxInt
+ | ENamed n => if IS.member (enclosingFunctions, n) then 0 else maxInt
+ | ECon (_, _, _, NONE) => maxInt
+ | ECon (_, _, _, SOME e) => ca depth e
+ | EFfi _ => maxInt
+ | EFfiApp (_, _, ecs) => foldl (fn ((e, _), d) => Int.min (ca depth e, d)) maxInt ecs
+ | EApp (e1, e2) =>
+ let
+ fun default () = Int.min (ca depth e1, ca depth e2)
+ in
+ case getApp e of
+ NONE => default ()
+ | SOME (f, args) =>
+ if not (IS.member (enclosingFunctions, f)) then
+ default ()
+ else
+ let
+ fun visitArgs (count, args) =
+ case args of
+ [] => count
+ | arg :: args' =>
+ let
+ fun default () = foldl (fn (e, d) => Int.min (ca depth e, d)) count args
+ in
+ case #1 arg of
+ ERel n =>
+ if n = depth - 1 - count then
+ visitArgs (count + 1, args')
+ else
+ default ()
+ | _ => default ()
+ end
+ in
+ visitArgs (0, args)
+ end
+ end
+ | EAbs (_, _, _, e1) => ca (depth + 1) e1
+ | ECApp (e1, _) => ca depth e1
+ | ECAbs (_, _, e1) => ca depth e1
+ | EKAbs (_, e1) => ca depth e1
+ | EKApp (e1, _) => ca depth e1
+ | ERecord xets => foldl (fn ((_, e, _), d) => Int.min (ca depth e, d)) maxInt xets
+ | EField (e1, _, _) => ca depth e1
+ | EConcat (e1, _, e2, _) => Int.min (ca depth e1, ca depth e2)
+ | ECut (e1, _, _) => ca depth e1
+ | ECutMulti (e1, _, _) => ca depth e1
+ | ECase (e1, pes, _) => foldl (fn ((p, e), d) => Int.min (ca (depth + E.patBindsN p) e, d)) (ca depth e1) pes
+ | EWrite e1 => ca depth e1
+ | EClosure (_, es) => foldl (fn (e, d) => Int.min (ca depth e, d)) maxInt es
+ | ELet (_, _, e1, e2) => Int.min (ca depth e1, ca (depth + 1) e2)
+ | EServerCall (_, es, _, _) => foldl (fn (e, d) => Int.min (ca depth e, d)) maxInt es
+
+ fun enterAbs depth e =
+ case #1 e of
+ EAbs (_, _, _, e1) => enterAbs (depth + 1) e1
+ | _ => ca depth e
+ in
+ enterAbs 0 e
+ end
+
+
+fun optionExists p opt =
+ case opt of
+ NONE => false
+ | SOME v => p v
+
+fun specialize' (funcs, specialized) file =
+ let
+ val known = foldl (fn (d, known) =>
+ case #1 d of
+ DCon (_, n, _, c) =>
+ if functionInside known c then
+ IS.add (known, n)
+ else
+ known
+ | DDatatype dts =>
+ if List.exists (List.exists (optionExists (functionInside known) o #3) o #4) dts then
+ foldl (fn (dt, known) => IS.add (known, #2 dt)) known dts
+ else
+ known
+ | _ => known)
+ IS.empty file
+
+ fun bind (env, b) =
+ case b of
+ U.Decl.RelE xt => xt :: env
+ | _ => env
+
+ fun exp (env, e as (_, loc), st : state) =
+ let
+ (*val () = Print.prefaces "exp" [("e", CorePrint.p_exp CoreEnv.empty
+ (e, ErrorMsg.dummySpan))]*)
+
+ fun default () =
+ case #1 e of
+ EPrim _ => (e, st)
+ | ERel _ => (e, st)
+ | ENamed _ => (e, st)
+ | ECon (_, _, _, NONE) => (e, st)
+ | ECon (dk, pc, cs, SOME e) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((ECon (dk, pc, cs, SOME 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 (env, 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 (env, e1, st)
+ val (e2, st) = exp (env, e2, st)
+ in
+ ((EApp (e1, e2), loc), st)
+ end
+ | EAbs (x, d, r, e) =>
+ let
+ val (e, st) = exp ((x, d) :: env, e, st)
+ in
+ ((EAbs (x, d, r, e), loc), st)
+ end
+ | ECApp (e, c) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((ECApp (e, c), loc), st)
+ end
+ | ECAbs _ => (e, st)
+ | EKAbs _ => (e, st)
+ | EKApp (e, k) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((EKApp (e, k), loc), st)
+ end
+ | ERecord fs =>
+ let
+ val (fs, st) = ListUtil.foldlMap (fn ((c1, e, c2), st) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((c1, e, c2), st)
+ end) st fs
+ in
+ ((ERecord fs, loc), st)
+ end
+ | EField (e, c, cs) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((EField (e, c, cs), loc), st)
+ end
+ | EConcat (e1, c1, e2, c2) =>
+ let
+ val (e1, st) = exp (env, e1, st)
+ val (e2, st) = exp (env, e2, st)
+ in
+ ((EConcat (e1, c1, e2, c2), loc), st)
+ end
+ | ECut (e, c, cs) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((ECut (e, c, cs), loc), st)
+ end
+ | ECutMulti (e, c, cs) =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((ECutMulti (e, c, cs), loc), st)
+ end
+
+ | ECase (e, pes, cs) =>
+ let
+ val (e, st) = exp (env, e, st)
+ val (pes, st) = ListUtil.foldlMap (fn ((p, e), st) =>
+ let
+ val (e, st) = exp (E.patBindsL p @ env, e, st)
+ in
+ ((p, e), st)
+ end) st pes
+ in
+ ((ECase (e, pes, cs), loc), st)
+ end
+
+ | EWrite e =>
+ let
+ val (e, st) = exp (env, e, st)
+ in
+ ((EWrite e, loc), st)
+ end
+ | EClosure (n, es) =>
+ let
+ val (es, st) = ListUtil.foldlMap (fn (e, st) => exp (env, e, st)) st es
+ in
+ ((EClosure (n, es), loc), st)
+ end
+ | ELet (x, t, e1, e2) =>
+ let
+ val (e1, st) = exp (env, e1, st)
+ val (e2, st) = exp ((x, t) :: env, e2, st)
+ in
+ ((ELet (x, t, e1, e2), loc), st)
+ end
+ | EServerCall (n, es, t, fm) =>
+ let
+ val (es, st) = ListUtil.foldlMap (fn (e, st) => exp (env, e, st)) st es
+ in
+ ((EServerCall (n, es, t, fm), loc), st)
+ end
+ in
+ case getApp e of
+ NONE => default ()
+ | SOME (f, xs) =>
+ case IM.find (#funcs st, f) of
+ NONE => ((*print ("No find: " ^ Int.toString f ^ "\n");*) default ())
+ | SOME {name, args, body, typ, tag, constArgs} =>
+ let
+ val (xs, st) = ListUtil.foldlMap (fn (e, st) => exp (env, e, st)) st xs
+
+ (*val () = Print.prefaces "Consider" [("e", CorePrint.p_exp CoreEnv.empty e)]*)
+
+ val loc = ErrorMsg.dummySpan
+
+ val oldXs = xs
+
+ fun findSplit av (initialPart, constArgs, xs, typ, fxs, fvs) =
+ let
+ fun default () =
+ if initialPart then
+ ([], oldXs, IS.empty)
+ else
+ (rev fxs, xs, fvs)
+ in
+ case (#1 typ, xs) of
+ (TFun (dom, ran), e :: xs') =>
+ if constArgs > 0 then
+ let
+ val fi = functionInside known dom
+ in
+ if initialPart orelse fi then
+ findSplit av (not fi andalso initialPart,
+ constArgs - 1,
+ xs',
+ ran,
+ e :: fxs,
+ IS.union (fvs, freeVars e))
+ else
+ default ()
+ end
+ else
+ default ()
+ | _ => default ()
+ end
+
+ val (fxs, xs, fvs) = findSplit true (true, constArgs, xs, typ, [], IS.empty)
+
+ val vts = map (fn n => #2 (List.nth (env, n))) (IS.listItems fvs)
+ val fxs' = map (squish (IS.listItems fvs)) fxs
+
+ val p_bool = Print.PD.string o Bool.toString
+ in
+ (*Print.prefaces "Func" [("name", Print.PD.string name),
+ ("e", CorePrint.p_exp CoreEnv.empty e),
+ ("fxs'", Print.p_list (CorePrint.p_exp CoreEnv.empty) fxs')];*)
+ if List.all (fn (ERel _, _) => true
+ | _ => false) fxs' then
+ default ()
+ else
+ case KM.find (args, (vts, fxs')) of
+ SOME f' =>
+ let
+ val e = (ENamed f', loc)
+ val e = IS.foldr (fn (arg, e) => (EApp (e, (ERel arg, loc)), loc))
+ e fvs
+ val e = foldl (fn (arg, e) => (EApp (e, arg), loc))
+ e xs
+ in
+ (*Print.prefaces "Brand new (reuse)"
+ [("e'", CorePrint.p_exp CoreEnv.empty e)];*)
+ (e, st)
+ end
+ | NONE =>
+ let
+ (*val () = Print.prefaces "New one"
+ [("name", Print.PD.string name),
+ ("f", Print.PD.string (Int.toString f)),
+ ("|fvs|", Print.PD.string (Int.toString (IS.numItems fvs))),
+ ("|fxs|", Print.PD.string (Int.toString (length fxs))),
+ ("fxs'", Print.p_list (CorePrint.p_exp CoreEnv.empty) fxs'),
+ ("spec", Print.PD.string (Bool.toString (IS.member (#specialized st, f))))]*)
+
+ (*val () = Print.prefaces ("Yes(" ^ name ^ ")")
+ [("fxs'",
+ Print.p_list (CorePrint.p_exp CoreEnv.empty) fxs')]*)
+
+ (*val () = Print.prefaces name
+ [("Available", Print.PD.string (Int.toString constArgs)),
+ ("Used", Print.PD.string (Int.toString (length fxs'))),
+ ("fxs'",
+ Print.p_list (CorePrint.p_exp CoreEnv.empty) fxs')]*)
+
+ fun subBody (body, typ, fxs') =
+ case (#1 body, #1 typ, fxs') of
+ (_, _, []) => SOME (body, typ)
+ | (EAbs (_, _, _, body'), TFun (_, typ'), x :: fxs'') =>
+ let
+ val body'' = E.subExpInExp (0, x) body'
+ in
+ subBody (body'',
+ typ',
+ fxs'')
+ end
+ | _ => NONE
+ in
+ case subBody (body, typ, fxs') of
+ NONE => default ()
+ | SOME (body', typ') =>
+ let
+ val f' = #maxName st
+ val args = KM.insert (args, (vts, fxs'), f')
+ val funcs = IM.insert (#funcs st, f, {name = name,
+ args = args,
+ body = body,
+ typ = typ,
+ tag = tag,
+ constArgs = calcConstArgs (IS.singleton f) body})
+
+ val st = {
+ maxName = f' + 1,
+ funcs = funcs,
+ decls = #decls st,
+ specialized = IS.add (#specialized st, f')
+ }
+
+ (*val () = Print.prefaces "specExp"
+ [("f", CorePrint.p_exp env (ENamed f, loc)),
+ ("f'", CorePrint.p_exp env (ENamed f', loc)),
+ ("xs", Print.p_list (CorePrint.p_exp env) xs),
+ ("fxs'", Print.p_list
+ (CorePrint.p_exp E.empty) fxs'),
+ ("e", CorePrint.p_exp env (e, loc))]*)
+ val (body', typ') = IS.foldl (fn (n, (body', typ')) =>
+ let
+ val (x, xt) = List.nth (env, n)
+ in
+ ((EAbs (x, xt, typ', body'),
+ loc),
+ (TFun (xt, typ'), loc))
+ end)
+ (body', typ') fvs
+ (*val () = print ("NEW: " ^ name ^ "__" ^ Int.toString f' ^ "\n")*)
+ val body' = ReduceLocal.reduceExp body'
+ (*val () = Print.preface ("PRE", CorePrint.p_exp CoreEnv.empty body')*)
+ val (body', st) = exp (env, body', st)
+
+ val e' = (ENamed f', loc)
+ val e' = IS.foldr (fn (arg, e) => (EApp (e, (ERel arg, loc)), loc))
+ e' fvs
+ val e' = foldl (fn (arg, e) => (EApp (e, arg), loc))
+ e' xs
+
+ (*val () = Print.prefaces "Brand new"
+ [("e'", CorePrint.p_exp CoreEnv.empty e'),
+ ("e", CorePrint.p_exp CoreEnv.empty e),
+ ("body'", CorePrint.p_exp CoreEnv.empty body')]*)
+ in
+ (e',
+ {maxName = #maxName st,
+ funcs = #funcs st,
+ decls = (name, f', typ', body', tag) :: #decls st,
+ specialized = #specialized st})
+ end
+ end
+ end
+ end
+
+ fun doDecl (d, (st : state, changed)) =
+ let
+ (*val befor = Time.now ()*)
+
+ val funcs = #funcs st
+ val funcs =
+ case #1 d of
+ DValRec vis =>
+ let
+ val fs = foldl (fn ((_, n, _, _, _), fs) => IS.add (fs, n)) IS.empty vis
+ val constArgs = foldl (fn ((_, _, _, e, _), constArgs) =>
+ Int.min (constArgs, calcConstArgs fs e))
+ maxInt vis
+ in
+ (*Print.prefaces "ConstArgs" [("d", CorePrint.p_decl CoreEnv.empty d),
+ ("ca", Print.PD.string (Int.toString constArgs))];*)
+ foldl (fn ((x, n, c, e, tag), funcs) =>
+ IM.insert (funcs, n, {name = x,
+ args = KM.empty,
+ body = e,
+ typ = c,
+ tag = tag,
+ constArgs = constArgs}))
+ funcs vis
+ end
+ | _ => funcs
+
+ val st = {maxName = #maxName st,
+ funcs = funcs,
+ decls = [],
+ specialized = #specialized st}
+
+ (*val () = Print.prefaces "decl" [("d", CorePrint.p_decl CoreEnv.empty d)]*)
+
+ val (d', st) =
+ if isPoly d then
+ (d, st)
+ else
+ case #1 d of
+ DVal (x, n, t, e, s) =>
+ let
+ (*val () = Print.preface ("Visiting", Print.box [Print.PD.string (x ^ "__" ^ Int.toString n),
+ Print.space,
+ Print.PD.string ":",
+ Print.space,
+ CorePrint.p_con CoreEnv.empty t])*)
+
+ val (e, st) = exp ([], e, st)
+ in
+ ((DVal (x, n, t, e, s), #2 d), st)
+ end
+ | DValRec vis =>
+ let
+ (*val () = Print.preface ("Visiting", Print.p_list (fn vi =>
+ Print.box [Print.PD.string (#1 vi ^ "__"
+ ^ Int.toString
+ (#2 vi)),
+ Print.space,
+ Print.PD.string ":",
+ Print.space,
+ CorePrint.p_con CoreEnv.empty (#3 vi)])
+ vis)*)
+
+ 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, #2 d), st)
+ end
+ | DTable (s, n, t, s1, e1, t1, e2, t2) =>
+ let
+ val (e1, st) = exp ([], e1, st)
+ val (e2, st) = exp ([], e2, st)
+ in
+ ((DTable (s, n, t, s1, e1, t2, e2, t2), #2 d), st)
+ end
+ | DView (x, n, s, e, t) =>
+ let
+ val (e, st) = exp ([], e, st)
+ in
+ ((DView (x, n, s, e, t), #2 d), st)
+ end
+ | DTask (e1, e2) =>
+ let
+ val (e1, st) = exp ([], e1, st)
+ val (e2, st) = exp ([], e2, st)
+ in
+ ((DTask (e1, e2), #2 d), st)
+ end
+ | _ => (d, st)
+
+ (*val () = print "/decl\n"*)
+
+ val funcs = #funcs st
+ val funcs =
+ case #1 d of
+ DVal (x, n, c, e as (EAbs _, _), tag) =>
+ ((*Print.prefaces "ConstArgs[2]" [("d", CorePrint.p_decl CoreEnv.empty d),
+ ("ca", Print.PD.string (Int.toString (calcConstArgs (IS.singleton n) e)))];*)
+ IM.insert (funcs, n, {name = x,
+ args = KM.empty,
+ body = e,
+ typ = c,
+ tag = tag,
+ constArgs = calcConstArgs (IS.singleton n) e}))
+ | DVal (_, n, _, (ENamed n', _), _) =>
+ (case IM.find (funcs, n') of
+ NONE => funcs
+ | SOME v => IM.insert (funcs, n, v))
+ | _ => funcs
+
+ val (changed, ds) =
+ case #decls st of
+ [] => (changed, [d'])
+ | vis =>
+ (true, case d' of
+ (DValRec vis', _) => [(DValRec (vis @ vis'), ErrorMsg.dummySpan)]
+ | _ => [(DValRec vis, ErrorMsg.dummySpan), d'])
+ in
+ (*Print.prefaces "doDecl" [("d", CorePrint.p_decl E.empty d),
+ ("d'", CorePrint.p_decl E.empty d')];*)
+ (ds, ({maxName = #maxName st,
+ funcs = funcs,
+ decls = [],
+ specialized = #specialized st}, changed))
+ end
+
+ (*val () = Print.preface ("RESET", CorePrint.p_file CoreEnv.empty file)*)
+ val (ds, (st, changed)) = ListUtil.foldlMapConcat doDecl
+ ({maxName = U.File.maxName file + 1,
+ funcs = funcs,
+ decls = [],
+ specialized = specialized},
+ false)
+ file
+ in
+ (*print ("Changed = " ^ Bool.toString changed ^ "\n");*)
+ (changed, ds, #funcs st, #specialized st)
+ end
+
+fun specializeL (funcs, specialized) file =
+ let
+ val file = ReduceLocal.reduce file
+ (*val file = ReduceLocal.reduce file*)
+ val (changed, file, funcs, specialized) = specialize' (funcs, specialized) file
+ (*val file = ReduceLocal.reduce file
+ val file = CoreUntangle.untangle file
+ val file = Shake.shake file*)
+ in
+ (*print "Round over\n";*)
+ if changed then
+ let
+ (*val file = ReduceLocal.reduce file*)
+ (*val () = Print.prefaces "Pre-untangle" [("file", CorePrint.p_file CoreEnv.empty file)]*)
+ val file = CoreUntangle.untangle file
+ (*val () = Print.prefaces "Post-untangle" [("file", CorePrint.p_file CoreEnv.empty file)]*)
+ val file = Shake.shake file
+ in
+ (*print "Again!\n";*)
+ (*Print.prefaces "Again" [("file", CorePrint.p_file CoreEnv.empty file)];*)
+ specializeL (funcs, specialized) file
+ end
+ else
+ file
+ end
+
+val specialize = specializeL (IM.empty, IS.empty)
+
+end
diff --git a/src/expl.sml b/src/expl.sml
new file mode 100644
index 0000000..994c05c
--- /dev/null
+++ b/src/expl.sml
@@ -0,0 +1,166 @@
+(* Copyright (c) 2008, 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 Expl = struct
+
+type 'a located = 'a ErrorMsg.located
+
+datatype kind' =
+ KType
+ | KArrow of kind * kind
+ | KName
+ | KUnit
+ | KTuple of kind list
+ | KRecord of kind
+
+ | KRel of int
+ | KFun of string * kind
+
+withtype kind = kind' located
+
+datatype con' =
+ TFun of con * con
+ | TCFun of string * kind * con
+ | TRecord of con
+
+ | CRel of int
+ | CNamed of int
+ | CModProj of int * string list * string
+ | CApp of con * con
+ | CAbs of string * kind * con
+
+ | CKAbs of string * con
+ | CKApp of con * kind
+ | TKFun of string * con
+
+ | CName of string
+
+ | CRecord of kind * (con * con) list
+ | CConcat of con * con
+ | CMap of kind * kind
+
+ | CUnit
+
+ | CTuple of con list
+ | CProj of con * int
+
+withtype con = con' located
+
+datatype datatype_kind = datatype DatatypeKind.datatype_kind
+
+datatype patCon =
+ PConVar of int
+ | PConProj of int * string list * string
+
+datatype pat' =
+ PVar of string * con
+ | PPrim of Prim.t
+ | PCon of datatype_kind * patCon * con list * pat option
+ | PRecord of (string * pat * con) list
+
+withtype pat = pat' located
+
+datatype exp' =
+ EPrim of Prim.t
+ | ERel of int
+ | ENamed of int
+ | EModProj of int * string list * string
+ | EApp of exp * exp
+ | EAbs of string * con * con * exp
+ | ECApp of exp * con
+ | ECAbs of string * kind * exp
+
+ | EKAbs of string * exp
+ | EKApp of exp * kind
+
+ | ERecord of (con * exp * con) list
+ | EField of exp * con * { field : con, rest : con }
+ | EConcat of exp * con * exp * con
+ | ECut of exp * con * { field : con, rest : con }
+ | ECutMulti of exp * con * { rest : con }
+
+ | ECase of exp * (pat * exp) list * { disc : con, result : con }
+
+ | EWrite of exp
+
+ | ELet of string * con * exp * exp
+
+withtype exp = exp' located
+
+datatype sgn_item' =
+ SgiConAbs of string * int * kind
+ | SgiCon of string * int * kind * con
+ | SgiDatatype of (string * int * string list * (string * int * con option) list) list
+ | SgiDatatypeImp of string * int * int * string list * string * string list * (string * int * con option) list
+ | SgiVal of string * int * con
+ | SgiSgn of string * int * sgn
+ | SgiStr of string * int * sgn
+
+and sgn' =
+ SgnConst of sgn_item list
+ | SgnVar of int
+ | SgnFun of string * int * sgn * sgn
+ | SgnWhere of sgn * string list * string * con
+ | SgnProj of int * string list * string
+
+withtype sgn_item = sgn_item' located
+and sgn = sgn' located
+
+datatype decl' =
+ DCon of string * int * kind * con
+ | DDatatype of (string * int * string list * (string * int * con option) list) list
+ | DDatatypeImp of string * int * int * string list * string * string list * (string * int * con option) list
+ | DVal of string * int * con * exp
+ | DValRec of (string * int * con * exp) list
+ | DSgn of string * int * sgn
+ | DStr of string * int * sgn * str
+ | DFfiStr of string * int * sgn
+ | DExport of int * sgn * str
+ | DTable of int * string * int * con * exp * con * exp * con
+ | DSequence of int * string * int
+ | DView of int * string * int * exp * con
+ | DDatabase of string
+ | DCookie of int * string * int * con
+ | DStyle of int * string * int
+ | DTask of exp * exp
+ | DPolicy of exp
+ | DOnError of int * string list * string
+ | DFfi of string * int * Source.ffi_mode list * con
+
+ and str' =
+ StrConst of decl list
+ | StrVar of int
+ | StrProj of str * string
+ | StrFun of string * int * sgn * sgn * str
+ | StrApp of str * str
+
+withtype decl = decl' located
+ and str = str' located
+
+type file = decl list
+
+end
diff --git a/src/expl_env.sig b/src/expl_env.sig
new file mode 100644
index 0000000..89594d0
--- /dev/null
+++ b/src/expl_env.sig
@@ -0,0 +1,71 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+signature EXPL_ENV = sig
+
+ exception SynUnif
+ val liftConInCon : int -> Expl.con -> Expl.con
+
+ type env
+
+ val empty : env
+
+ exception UnboundRel of int
+ exception UnboundNamed of int
+
+ datatype 'a var =
+ NotBound
+ | Rel of int * 'a
+ | Named of int * 'a
+
+ val pushKRel : env -> string -> env
+ val lookupKRel : env -> int -> string
+
+ val pushCRel : env -> string -> Expl.kind -> env
+ val lookupCRel : env -> int -> string * Expl.kind
+
+ val pushCNamed : env -> string -> int -> Expl.kind -> Expl.con option -> env
+ val lookupCNamed : env -> int -> string * Expl.kind * Expl.con option
+
+ val pushERel : env -> string -> Expl.con -> env
+ val lookupERel : env -> int -> string * Expl.con
+
+ val pushENamed : env -> string -> int -> Expl.con -> env
+ val lookupENamed : env -> int -> string * Expl.con
+
+ val pushSgnNamed : env -> string -> int -> Expl.sgn -> env
+ val lookupSgnNamed : env -> int -> string * Expl.sgn
+
+ val pushStrNamed : env -> string -> int -> Expl.sgn -> env
+ val lookupStrNamed : env -> int -> string * Expl.sgn
+
+ val declBinds : env -> Expl.decl -> env
+ val sgiBinds : env -> Expl.sgn_item -> env
+
+ val patBinds : env -> Expl.pat -> env
+
+end
diff --git a/src/expl_env.sml b/src/expl_env.sml
new file mode 100644
index 0000000..f7f51be
--- /dev/null
+++ b/src/expl_env.sml
@@ -0,0 +1,413 @@
+(* Copyright (c) 2008-2010, 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 ExplEnv :> EXPL_ENV = struct
+
+open Expl
+
+structure U = ExplUtil
+
+structure IM = IntBinaryMap
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+exception UnboundRel of int
+exception UnboundNamed of int
+
+
+(* AST utility functions *)
+
+exception SynUnif
+
+val liftKindInKind =
+ U.Kind.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ bind = fn (bound, _) => bound + 1}
+
+val liftKindInCon =
+ U.Con.mapB {kind = fn bound => fn k =>
+ case k of
+ KRel xn =>
+ if xn < bound then
+ k
+ else
+ KRel (xn + 1)
+ | _ => k,
+ con = fn _ => fn c => c,
+ bind = fn (bound, U.Con.RelK _) => bound + 1
+ | (bound, _) => bound}
+
+val liftConInCon =
+ U.Con.mapB {kind = fn _ => fn k => k,
+ con = fn bound => fn c =>
+ case c of
+ CRel xn =>
+ if xn < bound then
+ c
+ else
+ CRel (xn + 1)
+ (*| CUnif _ => raise SynUnif*)
+ | _ => c,
+ bind = fn (bound, U.Con.RelC _) => bound + 1
+ | (bound, _) => bound}
+
+val lift = liftConInCon 0
+
+
+(* Back to environments *)
+
+datatype 'a var' =
+ Rel' of int * 'a
+ | Named' of int * 'a
+
+datatype 'a var =
+ NotBound
+ | Rel of int * 'a
+ | Named of int * 'a
+
+type env = {
+ relK : string list,
+
+ relC : (string * kind) list,
+ namedC : (string * kind * con option) IM.map,
+
+ relE : (string * con) list,
+ namedE : (string * con) IM.map,
+
+ sgn : (string * sgn) IM.map,
+
+ str : (string * sgn) IM.map
+}
+
+val namedCounter = ref 0
+
+val empty = {
+ relK = [],
+
+ relC = [],
+ namedC = IM.empty,
+
+ relE = [],
+ namedE = IM.empty,
+
+ sgn = IM.empty,
+
+ str = IM.empty
+}
+
+fun pushKRel (env : env) x =
+ {relK = x :: #relK env,
+
+ relC = map (fn (x, k) => (x, liftKindInKind 0 k)) (#relC env),
+ namedC = #namedC env,
+
+ relE = map (fn (x, c) => (x, liftKindInCon 0 c)) (#relE env),
+ namedE = #namedE env,
+
+ sgn = #sgn env,
+
+ str = #str env
+ }
+
+fun lookupKRel (env : env) n =
+ (List.nth (#relK env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushCRel (env : env) x k =
+ {relK = #relK env,
+
+ relC = (x, k) :: #relC env,
+ namedC = IM.map (fn (x, k, co) => (x, k, Option.map lift co)) (#namedC env),
+
+ relE = map (fn (x, c) => (x, lift c)) (#relE env),
+ namedE = IM.map (fn (x, c) => (x, lift c)) (#namedE env),
+
+ sgn = #sgn env,
+
+ str = #str env
+ }
+
+fun lookupCRel (env : env) n =
+ (List.nth (#relC env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushCNamed (env : env) x n k co =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = IM.insert (#namedC env, n, (x, k, co)),
+
+ relE = #relE env,
+ namedE = #namedE env,
+
+ sgn = #sgn env,
+
+ str = #str env}
+
+fun lookupCNamed (env : env) n =
+ case IM.find (#namedC env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushERel (env : env) x t =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ relE = (x, t) :: #relE env,
+ namedE = #namedE env,
+
+ sgn = #sgn env,
+
+ str = #str env}
+
+fun lookupERel (env : env) n =
+ (List.nth (#relE env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushENamed (env : env) x n t =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ relE = #relE env,
+ namedE = IM.insert (#namedE env, n, (x, t)),
+
+ sgn = #sgn env,
+
+ str = #str env}
+
+fun lookupENamed (env : env) n =
+ case IM.find (#namedE env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushSgnNamed (env : env) x n sgis =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ relE = #relE env,
+ namedE = #namedE env,
+
+ sgn = IM.insert (#sgn env, n, (x, sgis)),
+
+ str = #str env}
+
+fun lookupSgnNamed (env : env) n =
+ case IM.find (#sgn env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun pushStrNamed (env : env) x n sgis =
+ {relK = #relK env,
+
+ relC = #relC env,
+ namedC = #namedC env,
+
+ relE = #relE env,
+ namedE = #namedE env,
+
+ sgn = #sgn env,
+
+ str = IM.insert (#str env, n, (x, sgis))}
+
+fun lookupStrNamed (env : env) n =
+ case IM.find (#str env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun declBinds env (d, loc) =
+ case d of
+ DCon (x, n, k, c) => pushCNamed env x n k (SOME c)
+ | DDatatype dts =>
+ let
+ fun doOne ((x, n, xs, xncs), env) =
+ let
+ val k = (KType, loc)
+ val nxs = length xs
+ val (tb, kb) = ListUtil.foldli (fn (i, x', (tb, kb)) =>
+ ((CApp (tb, (CRel (nxs - i - 1), loc)), loc),
+ (KArrow (k, kb), loc)))
+ ((CNamed n, loc), k) xs
+
+ val env = pushCNamed env x n kb NONE
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => tb
+ | SOME t => (TFun (t, tb), loc)
+ val t = foldr (fn (x, t) => (TCFun (x, k, t), loc)) t xs
+ in
+ pushENamed env x' n' t
+ end)
+ env xncs
+ end
+ in
+ foldl doOne env dts
+ end
+ | DDatatypeImp (x, n, m, ms, x', xs, xncs) =>
+ let
+ val t = (CModProj (m, ms, x'), loc)
+ val env = pushCNamed env x n (KType, loc) (SOME t)
+
+ val t = (CNamed n, loc)
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => (CNamed n, loc)
+ | SOME t => (TFun (t, (CNamed n, loc)), loc)
+ val k = (KType, loc)
+ val t = foldr (fn (x, t) => (TCFun (x, k, t), loc)) t xs
+ in
+ pushENamed env x' n' t
+ end)
+ env xncs
+ end
+ | DVal (x, n, t, _) => pushENamed env x n t
+ | DValRec vis => foldl (fn ((x, n, t, _), env) => pushENamed env x n t) env vis
+ | DSgn (x, n, sgn) => pushSgnNamed env x n sgn
+ | DStr (x, n, sgn, _) => pushStrNamed env x n sgn
+ | DFfiStr (x, n, sgn) => pushStrNamed env x n sgn
+ | DExport _ => env
+ | DTable (tn, x, n, c, _, pc, _, cc) =>
+ let
+ val ct = (CModProj (tn, [], "sql_table"), loc)
+ val ct = (CApp (ct, c), loc)
+ val ct = (CApp (ct, (CConcat (pc, cc), loc)), loc)
+ in
+ pushENamed env x n ct
+ end
+ | DSequence (tn, x, n) =>
+ let
+ val t = (CModProj (tn, [], "sql_sequence"), loc)
+ in
+ pushENamed env x n t
+ end
+ | DView (tn, x, n, _, c) =>
+ let
+ val ct = (CModProj (tn, [], "sql_view"), loc)
+ val ct = (CApp (ct, c), loc)
+ in
+ pushENamed env x n ct
+ end
+ | DDatabase _ => env
+ | DCookie (tn, x, n, c) =>
+ let
+ val t = (CApp ((CModProj (tn, [], "http_cookie"), loc), c), loc)
+ in
+ pushENamed env x n t
+ end
+ | DStyle (tn, x, n) =>
+ let
+ val t = (CModProj (tn, [], "css_class"), loc)
+ in
+ pushENamed env x n t
+ end
+ | DTask _ => env
+ | DPolicy _ => env
+ | DOnError _ => env
+ | DFfi (x, n, _, t) => pushENamed env x n t
+
+fun sgiBinds env (sgi, loc) =
+ case sgi of
+ SgiConAbs (x, n, k) => pushCNamed env x n k NONE
+ | SgiCon (x, n, k, c) => pushCNamed env x n k (SOME c)
+ | SgiDatatype dts =>
+ let
+ fun doOne ((x, n, xs, xncs), env) =
+ let
+ val k = (KType, loc)
+ val k' = foldr (fn (_, k') => (KArrow (k, k'), loc)) k xs
+
+ val env = pushCNamed env x n k' NONE
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => (CNamed n, loc)
+ | SOME t => (TFun (t, (CNamed n, loc)), loc)
+
+ val k = (KType, loc)
+ val t = foldr (fn (x, t) => (TCFun (x, k, t), loc)) t xs
+ in
+ pushENamed env x' n' t
+ end)
+ env xncs
+ end
+ in
+ foldl doOne env dts
+ end
+ | SgiDatatypeImp (x, n, m1, ms, x', xs, xncs) =>
+ let
+ val t = (CModProj (m1, ms, x'), loc)
+ val env = pushCNamed env x n (KType, loc) (SOME t)
+
+ val t = (CNamed n, loc)
+ in
+ foldl (fn ((x', n', to), env) =>
+ let
+ val t =
+ case to of
+ NONE => (CNamed n, loc)
+ | SOME t => (TFun (t, (CNamed n, loc)), loc)
+ val k = (KType, loc)
+ val t = foldr (fn (x, t) => (TCFun (x, k, t), loc)) t xs
+ in
+ pushENamed env x' n' t
+ end)
+ env xncs
+ end
+ | SgiVal (x, n, t) => pushENamed env x n t
+ | SgiSgn (x, n, sgn) => pushSgnNamed env x n sgn
+ | SgiStr (x, n, sgn) => pushStrNamed env x n sgn
+
+fun patBinds env (p, loc) =
+ case p of
+ PVar (x, t) => pushERel env x t
+ | PPrim _ => env
+ | PCon (_, _, _, NONE) => env
+ | PCon (_, _, _, SOME p) => patBinds env p
+ | PRecord xps => foldl (fn ((_, p, _), env) => patBinds env p) env xps
+
+end
diff --git a/src/expl_print.sig b/src/expl_print.sig
new file mode 100644
index 0000000..3b07401
--- /dev/null
+++ b/src/expl_print.sig
@@ -0,0 +1,39 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature EXPL_PRINT = sig
+ val p_kind : ExplEnv.env -> Expl.kind Print.printer
+ val p_con : ExplEnv.env -> Expl.con Print.printer
+ val p_exp : ExplEnv.env -> Expl.exp Print.printer
+ val p_decl : ExplEnv.env -> Expl.decl Print.printer
+ val p_sgn_item : ExplEnv.env -> Expl.sgn_item Print.printer
+ val p_str : ExplEnv.env -> Expl.str Print.printer
+ val p_file : ExplEnv.env -> Expl.file Print.printer
+
+ val debug : bool ref
+end
+
diff --git a/src/expl_print.sml b/src/expl_print.sml
new file mode 100644
index 0000000..10ea605
--- /dev/null
+++ b/src/expl_print.sml
@@ -0,0 +1,794 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+(* Pretty-printing elaborated Ur/Web *)
+
+structure ExplPrint :> EXPL_PRINT = struct
+
+open Print.PD
+open Print
+
+open Expl
+
+structure E = ExplEnv
+
+val debug = ref false
+
+fun p_kind' par env (k, _) =
+ case k of
+ KType => string "Type"
+ | KArrow (k1, k2) => parenIf par (box [p_kind' true env k1,
+ space,
+ string "->",
+ space,
+ p_kind env k2])
+ | KName => string "Name"
+ | KRecord k => box [string "{", p_kind env k, string "}"]
+ | KUnit => string "Unit"
+ | KTuple ks => box [string "(",
+ p_list_sep (box [space, string "*", space]) (p_kind env) ks,
+ string ")"]
+
+ | KRel n => ((if !debug then
+ string (E.lookupKRel env n ^ "_" ^ Int.toString n)
+ else
+ string (E.lookupKRel env n))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | KFun (x, k) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_kind (E.pushKRel env x) k]
+
+and p_kind env = p_kind' false env
+
+fun p_con' par env (c, _) =
+ case c of
+ TFun (t1, t2) => parenIf par (box [p_con' true env t1,
+ space,
+ string "->",
+ space,
+ p_con env t2])
+ | TCFun (x, k, c) => parenIf par (box [string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "->",
+ space,
+ p_con (E.pushCRel env x k) c])
+ | TRecord (CRecord (_, xcs), _) => box [string "{",
+ p_list (fn (x, c) =>
+ box [p_name env x,
+ space,
+ string ":",
+ space,
+ p_con env c]) xcs,
+ string "}"]
+ | TRecord c => box [string "$",
+ p_con' true env c]
+
+ | CRel n =>
+ ((if !debug then
+ string (#1 (E.lookupCRel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupCRel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | CNamed n =>
+ ((if !debug then
+ string (#1 (E.lookupCNamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupCNamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_NAMED" ^ Int.toString n))
+ | CModProj (m1, ms, x) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND" ^ Int.toString m1
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1s :: ms @ [x])
+ end
+
+ | CApp (c1, c2) => parenIf par (box [p_con env c1,
+ space,
+ p_con' true env c2])
+ | CAbs (x, k, c) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=>",
+ space,
+ p_con (E.pushCRel env x k) c])
+
+ | CName s => box [string "#", string s]
+
+ | CRecord (k, xcs) =>
+ if !debug then
+ parenIf par (box [string "[",
+ p_list (fn (x, c) =>
+ box [p_con env x,
+ space,
+ string "=",
+ space,
+ p_con env c]) xcs,
+ string "]::",
+ p_kind env k])
+ else
+ parenIf par (box [string "[",
+ p_list (fn (x, c) =>
+ box [p_con env x,
+ space,
+ string "=",
+ space,
+ p_con env c]) xcs,
+ string "]"])
+ | CConcat (c1, c2) => parenIf par (box [p_con' true env c1,
+ space,
+ string "++",
+ space,
+ p_con env c2])
+ | CMap _ => string "map"
+ | CUnit => string "()"
+
+ | CTuple cs => box [string "(",
+ p_list (p_con env) cs,
+ string ")"]
+ | CProj (c, n) => box [p_con env c,
+ string ".",
+ string (Int.toString n)]
+
+ | CKAbs (x, c) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_con (E.pushKRel env x) c]
+ | CKApp (c, k) => box [p_con env c,
+ string "[[",
+ p_kind env k,
+ string "]]"]
+ | TKFun (x, c) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_con (E.pushKRel env x) c]
+
+and p_con env = p_con' false env
+
+and p_name env (all as (c, _)) =
+ case c of
+ CName s => string s
+ | _ => p_con env all
+
+fun p_patCon env pc =
+ case pc of
+ PConVar n =>
+ ((if !debug then
+ string (#1 (E.lookupENamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupENamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_NAMED" ^ Int.toString n))
+ | PConProj (m1, ms, x) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1x :: ms @ [x])
+ end
+
+fun p_pat' par env (p, _) =
+ case p of
+ PVar (s, _) => string s
+ | PPrim p => Prim.p_t p
+ | PCon (_, pc, _, NONE) => p_patCon env pc
+ | PCon (_, pc, cs, SOME p) =>
+ if !debug then
+ parenIf par (box [p_patCon env pc,
+ string "[",
+ p_list (p_con env) cs,
+ string "]",
+ space,
+ p_pat' true env p])
+ else
+ parenIf par (box [p_patCon env pc,
+ space,
+ p_pat' true env p])
+
+ | PRecord xps =>
+ box [string "{",
+ p_list_sep (box [string ",", space]) (fn (x, p, t) =>
+ box [string x,
+ space,
+ string "=",
+ space,
+ p_pat env p,
+ if !debug then
+ box [space,
+ string ":",
+ space,
+ p_con env t]
+ else
+ box []]) xps,
+ string "}"]
+
+and p_pat x = p_pat' false x
+
+fun p_exp' par env (e, loc) =
+ case e of
+ EPrim p => Prim.p_t p
+ | ERel n =>
+ ((if !debug then
+ string (#1 (E.lookupERel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupERel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_REL" ^ Int.toString n))
+ | ENamed n =>
+ ((if !debug then
+ string (#1 (E.lookupENamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupENamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_NAMED" ^ Int.toString n))
+ | EModProj (m1, ms, x) =>
+ let
+ val (m1x, sgn) = E.lookupStrNamed env m1
+ handle E.UnboundNamed _ => ("UNBOUND" ^ Int.toString m1, (SgnConst [], loc))
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1s :: ms @ [x])
+ end
+
+ | EApp (e1, e2) => parenIf par (box [p_exp env e1,
+ space,
+ p_exp' true env e2])
+ | EAbs (x, t, _, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushERel env x t) e])
+ | ECApp (e, c) => parenIf par (box [p_exp env e,
+ space,
+ string "[",
+ p_con env c,
+ string "]"])
+ | ECAbs (x, k, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushCRel env x k) e])
+
+ | ERecord xes => box [string "{",
+ p_list (fn (x, e, _) =>
+ box [p_name env x,
+ space,
+ string "=",
+ space,
+ p_exp env e]) xes,
+ string "}"]
+ | EField (e, c, {field, rest}) =>
+ if !debug then
+ box [p_exp' true env e,
+ string ".",
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env field,
+ space,
+ string " in ",
+ space,
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ string ".",
+ p_con' true env c]
+ | EConcat (e1, c1, e2, c2) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e1,
+ space,
+ string ":",
+ space,
+ p_con env c1,
+ space,
+ string "++",
+ space,
+ p_exp' true env e2,
+ space,
+ string ":",
+ space,
+ p_con env c2]
+ else
+ box [p_exp' true env e1,
+ space,
+ string "with",
+ space,
+ p_exp' true env e2])
+ | ECut (e, c, {field, rest}) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e,
+ space,
+ string "--",
+ space,
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env field,
+ space,
+ string " in ",
+ space,
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ space,
+ string "--",
+ space,
+ p_con' true env c])
+ | ECutMulti (e, c, {rest}) =>
+ parenIf par (if !debug then
+ box [p_exp' true env e,
+ space,
+ string "---",
+ space,
+ p_con' true env c,
+ space,
+ string "[",
+ p_con env rest,
+ string "]"]
+ else
+ box [p_exp' true env e,
+ space,
+ string "---",
+ space,
+ p_con' true env c])
+
+ | EWrite e => box [string "write(",
+ p_exp env e,
+ string ")"]
+
+ | ECase (e, pes, {disc, result}) =>
+ parenIf par (box [string "case",
+ space,
+ p_exp env e,
+ space,
+ if !debug then
+ box [string "in",
+ space,
+ p_con env disc,
+ space,
+ string "return",
+ space,
+ p_con env result,
+ space]
+ else
+ box [],
+ string "of",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (p, e) => box [p_pat env p,
+ space,
+ string "=>",
+ space,
+ p_exp (E.patBinds env p) e]) pes])
+
+ | ELet (x, t, e1, e2) => box [string "let",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e1,
+ space,
+ string "in",
+ newline,
+ p_exp (E.pushERel env x t) e2]
+
+ | EKAbs (x, e) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_exp (E.pushKRel env x) e]
+ | EKApp (e, k) => box [p_exp env e,
+ string "[[",
+ p_kind env k,
+ string "]]"]
+
+
+and p_exp env = p_exp' false env
+
+fun p_named x n =
+ if !debug then
+ box [string x,
+ string "__",
+ string (Int.toString n)]
+ else
+ string x
+
+fun p_datatype env (x, n, xs, cons) =
+ let
+ val k = (KType, ErrorMsg.dummySpan)
+ val env = E.pushCNamed env x n k NONE
+ val env = foldl (fn (x, env) => E.pushCRel env x k) env xs
+ in
+ box [string x,
+ p_list_sep (box []) (fn x => box [space, string x]) xs,
+ space,
+ string "=",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (x, n, NONE) => if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x
+ | (x, n, SOME t) => box [if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x, space, string "of", space, p_con env t])
+ cons]
+ end
+
+fun p_sgn_item env (sgiAll as (sgi, _)) =
+ case sgi of
+ SgiConAbs (x, n, k) => box [string "con",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k]
+ | SgiCon (x, n, k, c) => box [string "con",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=",
+ space,
+ p_con env c]
+ | SgiDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) (p_datatype (E.sgiBinds env sgiAll)) x]
+ | SgiDatatypeImp (x, _, m1, ms, x', _, _) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+ in
+ box [string "datatype",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ string "datatype",
+ space,
+ p_list_sep (string ".") string (m1x :: ms @ [x'])]
+ end
+ | SgiVal (x, n, c) => box [string "val",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env c]
+ | SgiStr (x, n, sgn) => box [string "structure",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn]
+ | SgiSgn (x, n, sgn) => box [string "signature",
+ space,
+ p_named x n,
+ space,
+ string "=",
+ space,
+ p_sgn env sgn]
+
+and p_sgn env (sgn, loc) =
+ case sgn of
+ SgnConst sgis => box [string "sig",
+ newline,
+ let
+ val (psgis, _) = ListUtil.foldlMap (fn (sgi, env) =>
+ (p_sgn_item env sgi,
+ E.sgiBinds env sgi))
+ env sgis
+ in
+ p_list_sep newline (fn x => x) psgis
+ end,
+ newline,
+ string "end"]
+ | SgnVar n => string ((#1 (E.lookupSgnNamed env n))
+ handle E.UnboundNamed _ => "UNBOUND" ^ Int.toString n)
+ | SgnFun (x, n, sgn, sgn') => box [string "functor",
+ space,
+ string "(",
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn,
+ string ")",
+ space,
+ string ":",
+ space,
+ p_sgn (E.pushStrNamed env x n sgn) sgn']
+ | SgnWhere (sgn, ms, x, c) => box [p_sgn env sgn,
+ space,
+ string "where",
+ space,
+ string "con",
+ space,
+ p_list_sep (string ".") string (ms @ [x]),
+ space,
+ string "=",
+ space,
+ p_con env c]
+ | SgnProj (m1, ms, x) =>
+ let
+ val (m1x, sgn) = E.lookupStrNamed env m1
+ handle E.UnboundNamed _ => ("UNBOUND" ^ Int.toString m1, (SgnConst [], loc))
+
+ val m1s = if !debug then
+ m1x ^ "__" ^ Int.toString m1
+ else
+ m1x
+ in
+ p_list_sep (string ".") string (m1x :: ms @ [x])
+ end
+
+fun p_vali env (x, n, t, e) = box [p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env t,
+ space,
+ string "=",
+ space,
+ p_exp env e]
+
+fun p_decl env (dAll as (d, _) : decl) =
+ case d of
+ DCon (x, n, k, c) => box [string "con",
+ space,
+ p_named x n,
+ space,
+ string "::",
+ space,
+ p_kind env k,
+ space,
+ string "=",
+ space,
+ p_con env c]
+ | DDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) (p_datatype (E.declBinds env dAll)) x]
+ | DDatatypeImp (x, _, m1, ms, x', _, _) =>
+ let
+ val m1x = #1 (E.lookupStrNamed env m1)
+ handle E.UnboundNamed _ => "UNBOUND_STR_" ^ Int.toString m1
+ in
+ box [string "datatype",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ string "datatype",
+ space,
+ p_list_sep (string ".") string (m1x :: ms @ [x'])]
+ end
+ | DVal vi => box [string "val",
+ space,
+ p_vali env vi]
+ | DValRec vis =>
+ let
+ val env = E.declBinds env dAll
+ in
+ box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) (p_vali env) vis]
+ end
+
+ | DSgn (x, n, sgn) => box [string "signature",
+ space,
+ p_named x n,
+ space,
+ string "=",
+ space,
+ p_sgn env sgn]
+ | DStr (x, n, sgn, str) => box [string "structure",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn,
+ space,
+ string "=",
+ space,
+ p_str env str]
+ | DFfiStr (x, n, sgn) => box [string "extern",
+ space,
+ string "structure",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn]
+ | DExport (_, sgn, str) => box [string "export",
+ space,
+ p_str env str,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn]
+ | DTable (_, x, n, c, pe, _, ce, _) => box [string "table",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env c,
+ space,
+ string "keys",
+ space,
+ p_exp env pe,
+ space,
+ string "constraints",
+ space,
+ p_exp env ce]
+ | DSequence (_, x, n) => box [string "sequence",
+ space,
+ p_named x n]
+ | DView (_, x, n, e, _) => box [string "view",
+ space,
+ p_named x n,
+ space,
+ string "as",
+ space,
+ p_exp env e]
+ | DDatabase s => box [string "database",
+ space,
+ string s]
+ | DCookie (_, x, n, c) => box [string "cookie",
+ space,
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_con env c]
+ | DStyle (_, x, n) => box [string "style",
+ space,
+ p_named x n]
+ | DTask (e1, e2) => box [string "task",
+ space,
+ p_exp env e1,
+ space,
+ string "=",
+ space,
+ p_exp env e2]
+ | DPolicy e1 => box [string "policy",
+ space,
+ p_exp env e1]
+ | DOnError _ => string "ONERROR"
+ | DFfi _ => string "FFI"
+
+and p_str env (str, _) =
+ case str of
+ StrConst ds => box [string "struct",
+ newline,
+ p_file env ds,
+ newline,
+ string "end"]
+ | StrVar n =>
+ let
+ val x = #1 (E.lookupStrNamed env n)
+ handle E.UnboundNamed _ => "UNBOUND" ^ Int.toString n
+
+ val s = if !debug then
+ x ^ "__" ^ Int.toString n
+ else
+ x
+ in
+ string s
+ end
+ | StrProj (str, s) => box [p_str env str,
+ string ".",
+ string s]
+ | StrFun (x, n, sgn, sgn', str) =>
+ let
+ val env' = E.pushStrNamed env x n sgn
+ in
+ box [string "functor",
+ space,
+ string "(",
+ p_named x n,
+ space,
+ string ":",
+ space,
+ p_sgn env sgn,
+ string ")",
+ space,
+ string ":",
+ space,
+ p_sgn env' sgn',
+ space,
+ string "=>",
+ space,
+ p_str env' str]
+ end
+ | StrApp (str1, str2) => box [p_str env str1,
+ string "(",
+ p_str env str2,
+ string ")"]
+
+and p_file env file =
+ let
+ val (pds, _) = ListUtil.foldlMap (fn (d, env) =>
+ (p_decl env d,
+ E.declBinds env d))
+ env file
+ in
+ p_list_sep newline (fn x => x) pds
+ end
+
+end
diff --git a/src/expl_rename.sig b/src/expl_rename.sig
new file mode 100644
index 0000000..1aff315
--- /dev/null
+++ b/src/expl_rename.sig
@@ -0,0 +1,41 @@
+(* Copyright (c) 2014, 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.
+ *)
+
+(* To simplify Corify, it helps to apply a particular kind of renaming to functor
+ * bodies, so that nested functors refer only to fresh names. The payoff is that
+ * we can then implement applications of those nested functors by evaluating their
+ * bodies in arbitrary later contexts, even where identifiers defined in the
+ * outer functor body may have been shadowed. *)
+
+signature EXPL_RENAME = sig
+
+ val rename : {NextId : int,
+ FormalName : string,
+ FormalId : int,
+ Body : Expl.str} -> int * Expl.str
+
+end
diff --git a/src/expl_rename.sml b/src/expl_rename.sml
new file mode 100644
index 0000000..bdcf1aa
--- /dev/null
+++ b/src/expl_rename.sml
@@ -0,0 +1,454 @@
+(* Copyright (c) 2014, 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 ExplRename :> EXPL_RENAME = struct
+
+open Expl
+structure E = ExplEnv
+
+structure IM = IntBinaryMap
+
+structure St :> sig
+ type t
+
+ val create : int -> t
+ val next : t -> int
+
+ val bind : t * int -> t * int
+ val lookup: t * int -> int option
+end = struct
+
+type t = {next : int,
+ renaming : int IM.map}
+
+fun create next = {next = next,
+ renaming = IM.empty}
+
+fun next (t : t) = #next t
+
+fun bind ({next, renaming}, n) =
+ ({next = next + 1,
+ renaming = IM.insert (renaming, n, next)}, next)
+
+fun lookup ({next, renaming}, n) =
+ IM.find (renaming, n)
+
+end
+
+fun renameCon st (all as (c, loc)) =
+ case c of
+ TFun (c1, c2) => (TFun (renameCon st c1, renameCon st c2), loc)
+ | TCFun (x, k, c) => (TCFun (x, k, renameCon st c), loc)
+ | TRecord c => (TRecord (renameCon st c), loc)
+ | CRel _ => all
+ | CNamed n =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (CNamed n', loc))
+ | CModProj (n, ms, x) =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (CModProj (n', ms, x), loc))
+ | CApp (c1, c2) => (CApp (renameCon st c1, renameCon st c2), loc)
+ | CAbs (x, k, c) => (CAbs (x, k, renameCon st c), loc)
+ | CKAbs (x, c) => (CKAbs (x, renameCon st c), loc)
+ | CKApp (c, k) => (CKApp (renameCon st c, k), loc)
+ | TKFun (x, c) => (TKFun (x, renameCon st c), loc)
+ | CName _ => all
+ | CRecord (k, xcs) => (CRecord (k, map (fn (x, c) => (renameCon st x, renameCon st c)) xcs), loc)
+ | CConcat (c1, c2) => (CConcat (renameCon st c1, renameCon st c2), loc)
+ | CMap _ => all
+ | CUnit => all
+ | CTuple cs => (CTuple (map (renameCon st) cs), loc)
+ | CProj (c, n) => (CProj (renameCon st c, n), loc)
+
+fun renamePatCon st pc =
+ case pc of
+ PConVar n =>
+ (case St.lookup (st, n) of
+ NONE => pc
+ | SOME n' => PConVar n')
+ | PConProj (n, ms, x) =>
+ (case St.lookup (st, n) of
+ NONE => pc
+ | SOME n' => PConProj (n', ms, x))
+
+fun renamePat st (all as (p, loc)) =
+ case p of
+ PVar (x, c) => (PVar (x, renameCon st c), loc)
+ | PPrim _ => all
+ | PCon (dk, pc, cs, po) => (PCon (dk, renamePatCon st pc,
+ map (renameCon st) cs,
+ Option.map (renamePat st) po), loc)
+ | PRecord xpcs => (PRecord (map (fn (x, p, c) =>
+ (x, renamePat st p, renameCon st c)) xpcs), loc)
+
+fun renameExp st (all as (e, loc)) =
+ case e of
+ EPrim _ => all
+ | ERel _ => all
+ | ENamed n =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (ENamed n', loc))
+ | EModProj (n, ms, x) =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (EModProj (n', ms, x), loc))
+ | EApp (e1, e2) => (EApp (renameExp st e1, renameExp st e2), loc)
+ | EAbs (x, dom, ran, e) => (EAbs (x, renameCon st dom, renameCon st ran, renameExp st e), loc)
+ | ECApp (e, c) => (ECApp (renameExp st e, renameCon st c), loc)
+ | ECAbs (x, k, e) => (ECAbs (x, k, renameExp st e), loc)
+ | EKAbs (x, e) => (EKAbs (x, renameExp st e), loc)
+ | EKApp (e, k) => (EKApp (renameExp st e, k), loc)
+ | ERecord xecs => (ERecord (map (fn (x, e, c) => (renameCon st x,
+ renameExp st e,
+ renameCon st c)) xecs), loc)
+ | EField (e, c, {field, rest}) => (EField (renameExp st e,
+ renameCon st c,
+ {field = renameCon st field,
+ rest = renameCon st rest}), loc)
+ | EConcat (e1, c1, e2, c2) => (EConcat (renameExp st e1,
+ renameCon st c1,
+ renameExp st e2,
+ renameCon st c2), loc)
+ | ECut (e, c, {field, rest}) => (ECut (renameExp st e,
+ renameCon st c,
+ {field = renameCon st field,
+ rest = renameCon st rest}), loc)
+ | ECutMulti (e, c, {rest}) => (ECutMulti (renameExp st e,
+ renameCon st c,
+ {rest = renameCon st rest}), loc)
+ | ECase (e, pes, {disc, result}) => (ECase (renameExp st e,
+ map (fn (p, e) => (renamePat st p, renameExp st e)) pes,
+ {disc = renameCon st disc,
+ result = renameCon st result}), loc)
+ | EWrite e => (EWrite (renameExp st e), loc)
+ | ELet (x, c1, e1, e2) => (ELet (x, renameCon st c1,
+ renameExp st e1,
+ renameExp st e2), loc)
+
+fun renameSitem st (all as (si, loc)) =
+ case si of
+ SgiConAbs _ => all
+ | SgiCon (x, n, k, c) => (SgiCon (x, n, k, renameCon st c), loc)
+ | SgiDatatype dts => (SgiDatatype (map (fn (x, n, xs, cns) =>
+ (x, n, xs,
+ map (fn (x, n, co) =>
+ (x, n, Option.map (renameCon st) co)) cns)) dts),
+ loc)
+ | SgiDatatypeImp (x, n, n', xs, x', xs', cns) =>
+ (SgiDatatypeImp (x, n, n', xs, x', xs',
+ map (fn (x, n, co) =>
+ (x, n, Option.map (renameCon st) co)) cns), loc)
+ | SgiVal (x, n, c) => (SgiVal (x, n, renameCon st c), loc)
+ | SgiSgn (x, n, sg) => (SgiSgn (x, n, renameSgn st sg), loc)
+ | SgiStr (x, n, sg) => (SgiStr (x, n, renameSgn st sg), loc)
+
+and renameSgn st (all as (sg, loc)) =
+ case sg of
+ SgnConst sis => (SgnConst (map (renameSitem st) sis), loc)
+ | SgnVar n =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (SgnVar n', loc))
+ | SgnFun (x, n, dom, ran) => (SgnFun (x, n, renameSgn st dom, renameSgn st ran), loc)
+ | SgnWhere (sg, xs, s, c) => (SgnWhere (renameSgn st sg, xs, s, renameCon st c), loc)
+ | SgnProj (n, ms, x) =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (SgnProj (n', ms, x), loc))
+
+fun renameDecl st (all as (d, loc)) =
+ case d of
+ DCon (x, n, k, c) => (DCon (x, n, k, renameCon st c), loc)
+ | DDatatype dts => (DDatatype (map (fn (x, n, xs, cns) =>
+ (x, n, xs,
+ map (fn (x, n, co) =>
+ (x, n, Option.map (renameCon st) co)) cns)) dts),
+ loc)
+ | DDatatypeImp (x, n, n', xs, x', xs', cns) =>
+ (DDatatypeImp (x, n, n', xs, x', xs',
+ map (fn (x, n, co) =>
+ (x, n, Option.map (renameCon st) co)) cns), loc)
+ | DVal (x, n, c, e) => (DVal (x, n, renameCon st c, renameExp st e), loc)
+ | DValRec vis => (DValRec (map (fn (x, n, c, e) => (x, n, renameCon st c, renameExp st e)) vis), loc)
+ | DSgn (x, n, sg) => (DSgn (x, n, renameSgn st sg), loc)
+ | DStr (x, n, sg, str) => (DStr (x, n, renameSgn st sg, renameStr st str), loc)
+ | DFfiStr (x, n, sg) => (DFfiStr (x, n, renameSgn st sg), loc)
+ | DExport (n, sg, str) =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (DExport (n', renameSgn st sg, renameStr st str), loc))
+ | DTable (n, x, m, c1, e1, c2, e2, c3) =>
+ (DTable (n, x, m, renameCon st c1, renameExp st e1, renameCon st c2,
+ renameExp st e2, renameCon st c3), loc)
+ | DSequence _ => all
+ | DView (n, x, n', e, c) => (DView (n, x, n', renameExp st e, renameCon st c), loc)
+ | DDatabase _ => all
+ | DCookie (n, x, n', c) => (DCookie (n, x, n', renameCon st c), loc)
+ | DStyle _ => all
+ | DTask (e1, e2) => (DTask (renameExp st e1, renameExp st e2), loc)
+ | DPolicy e => (DPolicy (renameExp st e), loc)
+ | DOnError (n, xs, x) =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (DOnError (n', xs, x), loc))
+ | DFfi (x, n, modes, t) => (DFfi (x, n, modes, renameCon st t), loc)
+
+and renameStr st (all as (str, loc)) =
+ case str of
+ StrConst ds => (StrConst (map (renameDecl st) ds), loc)
+ | StrVar n =>
+ (case St.lookup (st, n) of
+ NONE => all
+ | SOME n' => (StrVar n', loc))
+ | StrProj (str, x) => (StrProj (renameStr st str, x), loc)
+ | StrFun (x, n, dom, ran, str) => (StrFun (x, n, renameSgn st dom,
+ renameSgn st ran,
+ renameStr st str), loc)
+ | StrApp (str1, str2) => (StrApp (renameStr st str1, renameStr st str2), loc)
+
+
+
+fun fromArity (n, loc) =
+ case n of
+ 0 => (KType, loc)
+ | _ => (KArrow ((KType, loc), fromArity (n - 1, loc)), loc)
+
+fun dupDecl (all as (d, loc), st) =
+ case d of
+ DCon (x, n, k, c) =>
+ let
+ val (st, n') = St.bind (st, n)
+ in
+ ([(DCon (x, n, k, renameCon st c), loc),
+ (DCon (x, n', k, (CNamed n, loc)), loc)],
+ st)
+ end
+ | DDatatype dts =>
+ let
+ val d = (DDatatype (map (fn (x, n, xs, cns) =>
+ (x, n, xs,
+ map (fn (x, n, co) =>
+ (x, n, Option.map (renameCon st) co)) cns)) dts),
+ loc)
+
+ val (dts', st) = ListUtil.foldlMap (fn ((x, n, xs, cns), st) =>
+ let
+ val (st, n') = St.bind (st, n)
+
+ val (cns', st) = ListUtil.foldlMap
+ (fn ((x, n, _), st) =>
+ let
+ val (st, n') =
+ St.bind (st, n)
+ in
+ ((x, n, n'), st)
+ end) st cns
+ in
+ ((x, n, length xs, n', cns'), st)
+ end) st dts
+
+ val env = E.declBinds E.empty d
+ in
+ (d
+ :: map (fn (x, n, arity, n', _) =>
+ (DCon (x, n', fromArity (arity, loc), (CNamed n, loc)), loc)) dts'
+ @ ListUtil.mapConcat (fn (_, _, _, _, cns') =>
+ map (fn (x, n, n') =>
+ (DVal (x, n', #2 (E.lookupENamed env n), (ENamed n, loc)),
+ loc)) cns') dts',
+ st)
+ end
+ | DDatatypeImp (x, n, n', xs, x', xs', cns) =>
+ let
+ val d = (DDatatypeImp (x, n, n', xs, x', xs',
+ map (fn (x, n, co) =>
+ (x, n, Option.map (renameCon st) co)) cns), loc)
+
+ val (cns', st) = ListUtil.foldlMap
+ (fn ((x, n, _), st) =>
+ let
+ val (st, n') =
+ St.bind (st, n)
+ in
+ ((x, n, n'), st)
+ end) st cns
+
+ val (st, n') = St.bind (st, n)
+
+ val env = E.declBinds E.empty d
+ in
+ (d
+ :: (DCon (x, n', fromArity (length xs, loc), (CNamed n, loc)), loc)
+ :: map (fn (x, n, n') =>
+ (DVal (x, n', #2 (E.lookupENamed env n), (ENamed n, loc)),
+ loc)) cns',
+ st)
+ end
+ | DVal (x, n, c, e) =>
+ let
+ val (st, n') = St.bind (st, n)
+ val c' = renameCon st c
+ in
+ ([(DVal (x, n, c', renameExp st e), loc),
+ (DVal (x, n', c', (ENamed n, loc)), loc)],
+ st)
+ end
+ | DValRec vis =>
+ let
+ val d = (DValRec (map (fn (x, n, c, e) => (x, n, renameCon st c, renameExp st e)) vis), loc)
+
+ val (vis', st) = ListUtil.foldlMap (fn ((x, n, _, _), st) =>
+ let
+ val (st, n') = St.bind (st, n)
+ in
+ ((x, n, n'), st)
+ end) st vis
+
+ val env = E.declBinds E.empty d
+ in
+ (d
+ :: map (fn (x, n, n') => (DVal (x, n', #2 (E.lookupENamed env n), (ENamed n, loc)), loc)) vis',
+ st)
+ end
+ | DSgn (x, n, sg) =>
+ let
+ val (st, n') = St.bind (st, n)
+ in
+ ([(DSgn (x, n, renameSgn st sg), loc),
+ (DSgn (x, n', (SgnVar n, loc)), loc)],
+ st)
+ end
+ | DStr (x, n, sg, str) =>
+ let
+ val (st, n') = St.bind (st, n)
+ val sg' = renameSgn st sg
+ in
+ ([(DStr (x, n, sg', renameStr st str), loc),
+ (DStr (x, n', sg', (StrVar n, loc)), loc)],
+ st)
+ end
+ | DFfiStr (x, n, sg) => ([(DFfiStr (x, n, renameSgn st sg), loc)], st)
+ | DExport (n, sg, str) =>
+ (case St.lookup (st, n) of
+ NONE => ([all], st)
+ | SOME n' => ([(DExport (n', renameSgn st sg, renameStr st str), loc)], st))
+ | DTable (n, x, m, c1, e1, c2, e2, c3) =>
+ let
+ val (st, m') = St.bind (st, m)
+
+ val d = (DTable (n, x, m, renameCon st c1, renameExp st e1, renameCon st c2,
+ renameExp st e2, renameCon st c3), loc)
+
+ val env = E.declBinds E.empty d
+ in
+ ([d, (DVal (x, m', #2 (E.lookupENamed env m), (ENamed m, loc)), loc)], st)
+ end
+ | DSequence (n, x, m) =>
+ let
+ val (st, m') = St.bind (st, m)
+
+ val env = E.declBinds E.empty all
+ in
+ ([all, (DVal (x, m', #2 (E.lookupENamed env m), (ENamed m, loc)), loc)], st)
+ end
+ | DView (n, x, m, e, c) =>
+ let
+ val (st, m') = St.bind (st, m)
+
+ val d = (DView (n, x, m, renameExp st e, renameCon st c), loc)
+
+ val env = E.declBinds E.empty d
+ in
+ ([d, (DVal (x, m', #2 (E.lookupENamed env m), (ENamed m, loc)), loc)], st)
+ end
+ | DDatabase _ => ([all], st)
+ | DCookie (n, x, m, c) =>
+ let
+ val (st, m') = St.bind (st, m)
+
+ val d = (DCookie (n, x, m, renameCon st c), loc)
+
+ val env = E.declBinds E.empty d
+ in
+ ([d, (DVal (x, m', #2 (E.lookupENamed env m), (ENamed m, loc)), loc)], st)
+ end
+ | DStyle (n, x, m) =>
+ let
+ val (st, m') = St.bind (st, m)
+
+ val env = E.declBinds E.empty all
+ in
+ ([all, (DVal (x, m', #2 (E.lookupENamed env m), (ENamed m, loc)), loc)], st)
+ end
+ | DTask (e1, e2) => ([(DTask (renameExp st e1, renameExp st e2), loc)], st)
+ | DPolicy e => ([(DPolicy (renameExp st e), loc)], st)
+ | DOnError (n, xs, x) =>
+ (case St.lookup (st, n) of
+ NONE => ([all], st)
+ | SOME n' => ([(DOnError (n', xs, x), loc)], st))
+ | DFfi (x, n, modes, t) =>
+ let
+ val (st, n') = St.bind (st, n)
+ val t' = renameCon st t
+ in
+ ([(DFfi (x, n, modes, t'), loc),
+ (DVal (x, n', t', (ENamed n, loc)), loc)],
+ st)
+ end
+
+fun rename {NextId, FormalName, FormalId, Body = all as (str, loc)} =
+ case str of
+ StrConst ds =>
+ let
+ val st = St.create NextId
+ val (st, n) = St.bind (st, FormalId)
+
+ val (ds, st) = ListUtil.foldlMapConcat dupDecl st ds
+
+ (* Revenge of the functor parameter renamer!
+ * See comment in elaborate.sml for the start of the saga.
+ * We need to alpha-rename the argument to allow sufficient shadowing in the body. *)
+
+ fun mungeName m =
+ if List.exists (fn (DStr (x, _, _, _), _) => x = m
+ | _ => false) ds then
+ mungeName ("?" ^ m)
+ else
+ m
+
+ val FormalName = mungeName FormalName
+
+ val ds = (DStr (FormalName, n, (SgnConst [], loc), (StrVar FormalId, loc)), loc) :: ds
+ in
+ (St.next st, (StrConst ds, loc))
+ end
+ | _ => (NextId, all)
+
+end
diff --git a/src/expl_util.sig b/src/expl_util.sig
new file mode 100644
index 0000000..3e5c333
--- /dev/null
+++ b/src/expl_util.sig
@@ -0,0 +1,119 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature EXPL_UTIL = sig
+
+structure Kind : sig
+ val mapfoldB : {kind : ('context, Expl.kind', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * string -> 'context}
+ -> ('context, Expl.kind, 'state, 'abort) Search.mapfolderB
+ val mapfold : (Expl.kind', 'state, 'abort) Search.mapfolder
+ -> (Expl.kind, 'state, 'abort) Search.mapfolder
+ val exists : (Expl.kind' -> bool) -> Expl.kind -> bool
+ val mapB : {kind : 'context -> Expl.kind' -> Expl.kind',
+ bind : 'context * string -> 'context}
+ -> 'context -> (Expl.kind -> Expl.kind)
+end
+
+structure Con : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Expl.kind
+ | NamedC of string * Expl.kind
+
+ val mapfoldB : {kind : ('context, Expl.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Expl.con', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Expl.con, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Expl.kind', 'state, 'abort) Search.mapfolder,
+ con : (Expl.con', 'state, 'abort) Search.mapfolder}
+ -> (Expl.con, 'state, 'abort) Search.mapfolder
+
+ val mapB : {kind : 'context -> Expl.kind' -> Expl.kind',
+ con : 'context -> Expl.con' -> Expl.con',
+ bind : 'context * binder -> 'context}
+ -> 'context -> (Expl.con -> Expl.con)
+ val map : {kind : Expl.kind' -> Expl.kind',
+ con : Expl.con' -> Expl.con'}
+ -> Expl.con -> Expl.con
+ val exists : {kind : Expl.kind' -> bool,
+ con : Expl.con' -> bool} -> Expl.con -> bool
+end
+
+structure Exp : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Expl.kind
+ | NamedC of string * Expl.kind
+ | RelE of string * Expl.con
+ | NamedE of string * Expl.con
+
+ val mapfoldB : {kind : ('context, Expl.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Expl.con', 'state, 'abort) Search.mapfolderB,
+ exp : ('context, Expl.exp', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Expl.exp, 'state, 'abort) Search.mapfolderB
+ val mapfold : {kind : (Expl.kind', 'state, 'abort) Search.mapfolder,
+ con : (Expl.con', 'state, 'abort) Search.mapfolder,
+ exp : (Expl.exp', 'state, 'abort) Search.mapfolder}
+ -> (Expl.exp, 'state, 'abort) Search.mapfolder
+ val exists : {kind : Expl.kind' -> bool,
+ con : Expl.con' -> bool,
+ exp : Expl.exp' -> bool} -> Expl.exp -> bool
+end
+
+structure Sgn : sig
+ datatype binder =
+ RelK of string
+ | RelC of string * Expl.kind
+ | NamedC of string * Expl.kind
+ | Sgn of string * Expl.sgn
+ | Str of string * Expl.sgn
+
+ val mapfoldB : {kind : ('context, Expl.kind', 'state, 'abort) Search.mapfolderB,
+ con : ('context, Expl.con', 'state, 'abort) Search.mapfolderB,
+ sgn_item : ('context, Expl.sgn_item', 'state, 'abort) Search.mapfolderB,
+ sgn : ('context, Expl.sgn', 'state, 'abort) Search.mapfolderB,
+ bind : 'context * binder -> 'context}
+ -> ('context, Expl.sgn, 'state, 'abort) Search.mapfolderB
+
+
+ val mapfold : {kind : (Expl.kind', 'state, 'abort) Search.mapfolder,
+ con : (Expl.con', 'state, 'abort) Search.mapfolder,
+ sgn_item : (Expl.sgn_item', 'state, 'abort) Search.mapfolder,
+ sgn : (Expl.sgn', 'state, 'abort) Search.mapfolder}
+ -> (Expl.sgn, 'state, 'abort) Search.mapfolder
+
+ val map : {kind : Expl.kind' -> Expl.kind',
+ con : Expl.con' -> Expl.con',
+ sgn_item : Expl.sgn_item' -> Expl.sgn_item',
+ sgn : Expl.sgn' -> Expl.sgn'}
+ -> Expl.sgn -> Expl.sgn
+
+end
+
+end
diff --git a/src/expl_util.sml b/src/expl_util.sml
new file mode 100644
index 0000000..ff55823
--- /dev/null
+++ b/src/expl_util.sml
@@ -0,0 +1,557 @@
+(* Copyright (c) 2008, 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 ExplUtil :> EXPL_UTIL = struct
+
+open Expl
+
+structure S = Search
+
+structure Kind = struct
+
+fun mapfoldB {kind, bind} =
+ let
+ fun mfk ctx k acc =
+ S.bindP (mfk' ctx k acc, kind ctx)
+
+ and mfk' ctx (kAll as (k, loc)) =
+ case k of
+ KType => S.return2 kAll
+
+ | KArrow (k1, k2) =>
+ S.bind2 (mfk ctx k1,
+ fn k1' =>
+ S.map2 (mfk ctx k2,
+ fn k2' =>
+ (KArrow (k1', k2'), loc)))
+
+ | KName => S.return2 kAll
+
+ | KRecord k =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (KRecord k', loc))
+
+ | KUnit => S.return2 kAll
+
+ | KTuple ks =>
+ S.map2 (ListUtil.mapfold (mfk ctx) ks,
+ fn ks' =>
+ (KTuple ks', loc))
+
+ | KRel _ => S.return2 kAll
+ | KFun (x, k) =>
+ S.map2 (mfk (bind (ctx, x)) k,
+ fn k' =>
+ (KFun (x, k'), loc))
+ in
+ mfk
+ end
+
+fun mapfold fk =
+ mapfoldB {kind = fn () => fk,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, bind} ctx k =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ bind = bind} ctx k () of
+ S.Continue (k, ()) => k
+ | S.Return _ => raise Fail "ExplUtil.Kind.mapB: Impossible"
+
+fun exists f k =
+ case mapfold (fn k => fn () =>
+ if f k then
+ S.Return ()
+ else
+ S.Continue (k, ())) k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure Con = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Expl.kind
+ | NamedC of string * Expl.kind
+
+fun mapfoldB {kind = fk, con = fc, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun mfc ctx c acc =
+ S.bindP (mfc' ctx c acc, fc ctx)
+
+ and mfc' ctx (cAll as (c, loc)) =
+ case c of
+ TFun (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (TFun (c1', c2'), loc)))
+ | TCFun (x, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc (bind (ctx, RelC (x, k))) c,
+ fn c' =>
+ (TCFun (x, k', c'), loc)))
+ | TRecord c =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (TRecord c', loc))
+
+ | CRel _ => S.return2 cAll
+ | CNamed _ => S.return2 cAll
+ | CModProj _ => S.return2 cAll
+ | CApp (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (CApp (c1', c2'), loc)))
+ | CAbs (x, k, c) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfc (bind (ctx, RelC (x, k))) c,
+ fn c' =>
+ (CAbs (x, k', c'), loc)))
+
+ | CName _ => S.return2 cAll
+
+ | CRecord (k, xcs) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (ListUtil.mapfold (fn (x, c) =>
+ S.bind2 (mfc ctx x,
+ fn x' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (x', c'))))
+ xcs,
+ fn xcs' =>
+ (CRecord (k', xcs'), loc)))
+ | CConcat (c1, c2) =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (CConcat (c1', c2'), loc)))
+ | CMap (k1, k2) =>
+ S.bind2 (mfk ctx k1,
+ fn k1' =>
+ S.map2 (mfk ctx k2,
+ fn k2' =>
+ (CMap (k1', k2'), loc)))
+
+ | CUnit => S.return2 cAll
+
+ | CTuple cs =>
+ S.map2 (ListUtil.mapfold (mfc ctx) cs,
+ fn cs' =>
+ (CTuple cs', loc))
+
+ | CProj (c, n) =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (CProj (c', n), loc))
+
+ | CKAbs (x, c) =>
+ S.map2 (mfc (bind (ctx, RelK x)) c,
+ fn c' =>
+ (CKAbs (x, c'), loc))
+ | CKApp (c, k) =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (CKApp (c', k'), loc)))
+ | TKFun (x, c) =>
+ S.map2 (mfc (bind (ctx, RelK x)) c,
+ fn c' =>
+ (TKFun (x, c'), loc))
+ in
+ mfc
+ end
+
+fun mapfold {kind = fk, con = fc} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {kind, con, bind} ctx c =
+ case mapfoldB {kind = fn ctx => fn k => fn () => S.Continue (kind ctx k, ()),
+ con = fn ctx => fn c => fn () => S.Continue (con ctx c, ()),
+ bind = bind} ctx c () of
+ S.Continue (c, ()) => c
+ | S.Return _ => raise Fail "ExplUtil.Con.mapB: Impossible"
+
+fun map {kind, con} s =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ())} s () of
+ S.Return () => raise Fail "ExplUtil.Con.map: Impossible"
+ | S.Continue (s, ()) => s
+
+fun exists {kind, con} k =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure Exp = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Expl.kind
+ | NamedC of string * Expl.kind
+ | RelE of string * Expl.con
+ | NamedE of string * Expl.con
+
+fun mapfoldB {kind = fk, con = fc, exp = fe, bind} =
+ let
+ val mfk = Kind.mapfoldB {kind = fk, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val mfc = Con.mapfoldB {kind = fk, con = fc, bind = bind'}
+
+ fun mfe ctx e acc =
+ S.bindP (mfe' ctx e acc, fe ctx)
+
+ and mfe' ctx (eAll as (e, loc)) =
+ case e of
+ EPrim _ => S.return2 eAll
+ | ERel _ => S.return2 eAll
+ | ENamed _ => S.return2 eAll
+ | EModProj _ => S.return2 eAll
+ | EApp (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (EApp (e1', e2'), loc)))
+ | EAbs (x, dom, ran, e) =>
+ S.bind2 (mfc ctx dom,
+ fn dom' =>
+ S.bind2 (mfc ctx ran,
+ fn ran' =>
+ S.map2 (mfe (bind (ctx, RelE (x, dom'))) e,
+ fn e' =>
+ (EAbs (x, dom', ran', e'), loc))))
+
+ | ECApp (e, c) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx c,
+ fn c' =>
+ (ECApp (e', c'), loc)))
+ | ECAbs (x, k, e) =>
+ S.bind2 (mfk ctx k,
+ fn k' =>
+ S.map2 (mfe (bind (ctx, RelC (x, k))) e,
+ fn e' =>
+ (ECAbs (x, k', e'), loc)))
+
+ | ERecord xes =>
+ S.map2 (ListUtil.mapfold (fn (x, e, t) =>
+ S.bind2 (mfc ctx x,
+ fn x' =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfc ctx t,
+ fn t' =>
+ (x', e', t')))))
+ xes,
+ fn xes' =>
+ (ERecord xes', loc))
+ | EField (e, c, {field, rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfc ctx field,
+ fn field' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (EField (e', c', {field = field', rest = rest'}), loc)))))
+ | EConcat (e1, c1, e2, c2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.bind2 (mfc ctx c1,
+ fn c1' =>
+ S.bind2 (mfe ctx e2,
+ fn e2' =>
+ S.map2 (mfc ctx c2,
+ fn c2' =>
+ (EConcat (e1', c1', e2', c2'),
+ loc)))))
+ | ECut (e, c, {field, rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.bind2 (mfc ctx field,
+ fn field' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (ECut (e', c', {field = field', rest = rest'}), loc)))))
+ | ECutMulti (e, c, {rest}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (mfc ctx c,
+ fn c' =>
+ S.map2 (mfc ctx rest,
+ fn rest' =>
+ (ECutMulti (e', c', {rest = rest'}), loc))))
+
+ | EWrite e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EWrite e', loc))
+
+ | ECase (e, pes, {disc, result}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (ListUtil.mapfold (fn (p, e) =>
+ S.map2 (mfe ctx e,
+ fn e' => (p, e'))) pes,
+ fn pes' =>
+ S.bind2 (mfc ctx disc,
+ fn disc' =>
+ S.map2 (mfc ctx result,
+ fn result' =>
+ (ECase (e', pes', {disc = disc', result = result'}), loc)))))
+
+ | ELet (x, t, e1, e2) =>
+ S.bind2 (mfc ctx t,
+ fn t' =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe (bind (ctx, RelE (x, t))) e2,
+ fn e2' =>
+ (ELet (x, t', e1', e2'), loc))))
+
+ | EKAbs (x, e) =>
+ S.map2 (mfe (bind (ctx, RelK x)) e,
+ fn e' =>
+ (EKAbs (x, e'), loc))
+ | EKApp (e, k) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mfk ctx k,
+ fn k' =>
+ (EKApp (e', k'), loc)))
+ in
+ mfe
+ end
+
+fun mapfold {kind = fk, con = fc, exp = fe} =
+ mapfoldB {kind = fn () => fk,
+ con = fn () => fc,
+ exp = fn () => fe,
+ bind = fn ((), _) => ()} ()
+
+fun exists {kind, con, exp} k =
+ case mapfold {kind = fn k => fn () =>
+ if kind k then
+ S.Return ()
+ else
+ S.Continue (k, ()),
+ con = fn c => fn () =>
+ if con c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure Sgn = struct
+
+datatype binder =
+ RelK of string
+ | RelC of string * Expl.kind
+ | NamedC of string * Expl.kind
+ | Str of string * Expl.sgn
+ | Sgn of string * Expl.sgn
+
+fun mapfoldB {kind, con, sgn_item, sgn, bind} =
+ let
+ fun bind' (ctx, b) =
+ let
+ val b' = case b of
+ Con.RelK x => RelK x
+ | Con.RelC x => RelC x
+ | Con.NamedC x => NamedC x
+ in
+ bind (ctx, b')
+ end
+ val con = Con.mapfoldB {kind = kind, con = con, bind = bind'}
+
+ val kind = Kind.mapfoldB {kind = kind, bind = fn (ctx, x) => bind (ctx, RelK x)}
+
+ fun sgi ctx si acc =
+ S.bindP (sgi' ctx si acc, sgn_item ctx)
+
+ and sgi' ctx (siAll as (si, loc)) =
+ case si of
+ SgiConAbs (x, n, k) =>
+ S.map2 (kind ctx k,
+ fn k' =>
+ (SgiConAbs (x, n, k'), loc))
+ | SgiCon (x, n, k, c) =>
+ S.bind2 (kind ctx k,
+ fn k' =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgiCon (x, n, k', c'), loc)))
+ | SgiDatatype dts =>
+ S.map2 (ListUtil.mapfold (fn (x, n, xs, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (con ctx c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' => (x, n, xs, xncs'))) dts,
+ fn dts' =>
+ (SgiDatatype dts', loc))
+ | SgiDatatypeImp (x, n, m1, ms, s, xs, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (con ctx c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' =>
+ (SgiDatatypeImp (x, n, m1, ms, s, xs, xncs'), loc))
+ | SgiVal (x, n, c) =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgiVal (x, n, c'), loc))
+ | SgiStr (x, n, s) =>
+ S.map2 (sg ctx s,
+ fn s' =>
+ (SgiStr (x, n, s'), loc))
+ | SgiSgn (x, n, s) =>
+ S.map2 (sg ctx s,
+ fn s' =>
+ (SgiSgn (x, n, s'), loc))
+
+ and sg ctx s acc =
+ S.bindP (sg' ctx s acc, sgn ctx)
+
+ and sg' ctx (sAll as (s, loc)) =
+ case s of
+ SgnConst sgis =>
+ S.map2 (ListUtil.mapfoldB (fn (ctx, si) =>
+ (case #1 si of
+ SgiConAbs (x, _, k) =>
+ bind (ctx, NamedC (x, k))
+ | SgiCon (x, _, k, _) =>
+ bind (ctx, NamedC (x, k))
+ | SgiDatatype dts =>
+ foldl (fn ((x, _, ks, _), ctx) =>
+ let
+ val k' = (KType, loc)
+ val k = foldl (fn (_, k) => (KArrow (k', k), loc))
+ k' ks
+ in
+ bind (ctx, NamedC (x, k))
+ end) ctx dts
+ | SgiDatatypeImp (x, _, _, _, _, _, _) =>
+ bind (ctx, NamedC (x, (KType, loc)))
+ | SgiVal _ => ctx
+ | SgiStr (x, _, sgn) =>
+ bind (ctx, Str (x, sgn))
+ | SgiSgn (x, _, sgn) =>
+ bind (ctx, Sgn (x, sgn)),
+ sgi ctx si)) ctx sgis,
+ fn sgis' =>
+ (SgnConst sgis', loc))
+
+ | SgnVar _ => S.return2 sAll
+
+ | SgnFun (m, n, s1, s2) =>
+ S.bind2 (sg ctx s1,
+ fn s1' =>
+ S.map2 (sg (bind (ctx, Str (m, s1'))) s2,
+ fn s2' =>
+ (SgnFun (m, n, s1', s2'), loc)))
+ | SgnWhere (sgn, ms, x, c) =>
+ S.bind2 (sg ctx sgn,
+ fn sgn' =>
+ S.map2 (con ctx c,
+ fn c' =>
+ (SgnWhere (sgn', ms, x, c'), loc)))
+ | SgnProj _ => S.return2 sAll
+ in
+ sg
+ end
+
+fun mapfold {kind, con, sgn_item, sgn} =
+ mapfoldB {kind = fn () => kind,
+ con = fn () => con,
+ sgn_item = fn () => sgn_item,
+ sgn = fn () => sgn,
+ bind = fn ((), _) => ()} ()
+
+fun map {kind, con, sgn_item, sgn} s =
+ case mapfold {kind = fn k => fn () => S.Continue (kind k, ()),
+ con = fn c => fn () => S.Continue (con c, ()),
+ sgn_item = fn si => fn () => S.Continue (sgn_item si, ()),
+ sgn = fn s => fn () => S.Continue (sgn s, ())} s () of
+ S.Return () => raise Fail "Expl_util.Sgn.map"
+ | S.Continue (s, ()) => s
+
+end
+
+end
diff --git a/src/explify.sig b/src/explify.sig
new file mode 100644
index 0000000..f839b3e
--- /dev/null
+++ b/src/explify.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature EXPLIFY = sig
+
+ val explify : Elab.file -> Expl.file
+
+end
diff --git a/src/explify.sml b/src/explify.sml
new file mode 100644
index 0000000..e2a317a
--- /dev/null
+++ b/src/explify.sml
@@ -0,0 +1,213 @@
+(* Copyright (c) 2008-2010, 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 Explify :> EXPLIFY = struct
+
+structure EM = ErrorMsg
+structure L = Elab
+structure L' = Expl
+
+fun explifyKind (k, loc) =
+ case k of
+ L.KType => (L'.KType, loc)
+ | L.KArrow (k1, k2) => (L'.KArrow (explifyKind k1, explifyKind k2), loc)
+ | L.KName => (L'.KName, loc)
+ | L.KRecord k => (L'.KRecord (explifyKind k), loc)
+
+ | L.KUnit => (L'.KUnit, loc)
+ | L.KTuple ks => (L'.KTuple (map explifyKind ks), loc)
+
+ | L.KError => raise Fail ("explifyKind: KError at " ^ EM.spanToString loc)
+ | L.KUnif (_, _, ref (L.KKnown k)) => explifyKind k
+ | L.KUnif _ => raise Fail ("explifyKind: KUnif at " ^ EM.spanToString loc)
+ | L.KTupleUnif (loc, _, ref (L.KKnown k)) => explifyKind k
+ | L.KTupleUnif _ => raise Fail ("explifyKind: KTupleUnif at " ^ EM.spanToString loc)
+
+ | L.KRel n => (L'.KRel n, loc)
+ | L.KFun (x, k) => (L'.KFun (x, explifyKind k), loc)
+
+fun explifyCon (c, loc) =
+ case c of
+ L.TFun (t1, t2) => (L'.TFun (explifyCon t1, explifyCon t2), loc)
+ | L.TCFun (_, x, k, t) => (L'.TCFun (x, explifyKind k, explifyCon t), loc)
+ | L.TDisjoint (_, _, t) => explifyCon t
+ | L.TRecord c => (L'.TRecord (explifyCon c), loc)
+
+ | L.CRel n => (L'.CRel n, loc)
+ | L.CNamed n => (L'.CNamed n, loc)
+ | L.CModProj (m, ms, x) => (L'.CModProj (m, ms, x), loc)
+
+ | L.CApp (c1, c2) => (L'.CApp (explifyCon c1, explifyCon c2), loc)
+ | L.CAbs (x, k, c) => (L'.CAbs (x, explifyKind k, explifyCon c), loc)
+
+ | L.CName s => (L'.CName s, loc)
+
+ | L.CRecord (k, xcs) => (L'.CRecord (explifyKind k, map (fn (c1, c2) => (explifyCon c1, explifyCon c2)) xcs), loc)
+ | L.CConcat (c1, c2) => (L'.CConcat (explifyCon c1, explifyCon c2), loc)
+ | L.CMap (dom, ran) => (L'.CMap (explifyKind dom, explifyKind ran), loc)
+
+ | L.CUnit => (L'.CUnit, loc)
+
+ | L.CTuple cs => (L'.CTuple (map explifyCon cs), loc)
+ | L.CProj (c, n) => (L'.CProj (explifyCon c, n), loc)
+
+ | L.CError => raise Fail ("explifyCon: CError at " ^ EM.spanToString loc)
+ | L.CUnif (nl, _, _, _, ref (L.Known c)) => explifyCon (ElabEnv.mliftConInCon nl c)
+ | L.CUnif _ => raise Fail ("explifyCon: CUnif at " ^ EM.spanToString loc)
+
+ | L.CKAbs (x, c) => (L'.CKAbs (x, explifyCon c), loc)
+ | L.CKApp (c, k) => (L'.CKApp (explifyCon c, explifyKind k), loc)
+ | L.TKFun (x, c) => (L'.TKFun (x, explifyCon c), loc)
+
+fun explifyPatCon pc =
+ case pc of
+ L.PConVar n => L'.PConVar n
+ | L.PConProj x => L'.PConProj x
+
+fun explifyPat (p, loc) =
+ case p of
+ L.PVar (x, t) => (L'.PVar (x, explifyCon t), loc)
+ | L.PPrim p => (L'.PPrim p, loc)
+ | L.PCon (dk, pc, cs, po) => (L'.PCon (dk, explifyPatCon pc, map explifyCon cs, Option.map explifyPat po), loc)
+ | L.PRecord xps => (L'.PRecord (map (fn (x, p, t) => (x, explifyPat p, explifyCon t)) xps), loc)
+
+fun explifyExp (e, loc) =
+ case e of
+ L.EPrim p => (L'.EPrim p, loc)
+ | L.ERel n => (L'.ERel n, loc)
+ | L.ENamed n => (L'.ENamed n, loc)
+ | L.EModProj (m, ms, x) => (L'.EModProj (m, ms, x), loc)
+ | L.EApp (e1, e2) => (L'.EApp (explifyExp e1, explifyExp e2), loc)
+ | L.EAbs (x, dom, ran, e1) => (L'.EAbs (x, explifyCon dom, explifyCon ran, explifyExp e1), loc)
+ | L.ECApp (e1, c) => (L'.ECApp (explifyExp e1, explifyCon c), loc)
+ | L.ECAbs (_, x, k, e1) => (L'.ECAbs (x, explifyKind k, explifyExp e1), loc)
+
+ | L.ERecord xes => (L'.ERecord (map (fn (c, e, t) => (explifyCon c, explifyExp e, explifyCon t)) xes), loc)
+ | L.EField (e1, c, {field, rest}) => (L'.EField (explifyExp e1, explifyCon c,
+ {field = explifyCon field, rest = explifyCon rest}), loc)
+ | L.EConcat (e1, c1, e2, c2) => (L'.EConcat (explifyExp e1, explifyCon c1, explifyExp e2, explifyCon c2),
+ loc)
+ | L.ECut (e1, c, {field, rest}) => (L'.ECut (explifyExp e1, explifyCon c,
+ {field = explifyCon field, rest = explifyCon rest}), loc)
+ | L.ECutMulti (e1, c, {rest}) => (L'.ECutMulti (explifyExp e1, explifyCon c,
+ {rest = explifyCon rest}), loc)
+ | L.ECase (e, pes, {disc, result}) =>
+ (L'.ECase (explifyExp e,
+ map (fn (p, e) => (explifyPat p, explifyExp e)) pes,
+ {disc = explifyCon disc, result = explifyCon result}), loc)
+
+ | L.EError => raise Fail ("explifyExp: EError at " ^ EM.spanToString loc)
+ | L.EUnif (ref (SOME e)) => explifyExp e
+ | L.EUnif _ => raise Fail ("explifyExp: Undetermined EUnif at " ^ EM.spanToString loc)
+
+ | L.ELet (des, e, t) =>
+ foldr (fn ((de, loc), e) =>
+ case de of
+ L.EDValRec _ => raise Fail "explifyExp: Local 'val rec' remains"
+ | L.EDVal ((L.PVar (x, _), _), t', e') => (L'.ELet (x, explifyCon t', explifyExp e', e), loc)
+ | L.EDVal (p, t', e') => (L'.ECase (explifyExp e',
+ [(explifyPat p, e)],
+ {disc = explifyCon t', result = explifyCon t}), loc))
+ (explifyExp e) des
+
+ | L.EKAbs (x, e) => (L'.EKAbs (x, explifyExp e), loc)
+ | L.EKApp (e, k) => (L'.EKApp (explifyExp e, explifyKind k), loc)
+
+fun explifySgi (sgi, loc) =
+ case sgi of
+ L.SgiConAbs (x, n, k) => SOME (L'.SgiConAbs (x, n, explifyKind k), loc)
+ | L.SgiCon (x, n, k, c) => SOME (L'.SgiCon (x, n, explifyKind k, explifyCon c), loc)
+ | L.SgiDatatype dts => SOME (L'.SgiDatatype (map (fn (x, n, xs, xncs) =>
+ (x, n, xs,
+ map (fn (x, n, co) =>
+ (x, n, Option.map explifyCon co)) xncs)) dts), loc)
+ | L.SgiDatatypeImp (x, n, m1, ms, s, xs, xncs) =>
+ SOME (L'.SgiDatatypeImp (x, n, m1, ms, s, xs, map (fn (x, n, co) =>
+ (x, n, Option.map explifyCon co)) xncs), loc)
+ | L.SgiVal (x, n, c) => SOME (L'.SgiVal (x, n, explifyCon c), loc)
+ | L.SgiStr (_, x, n, sgn) => SOME (L'.SgiStr (x, n, explifySgn sgn), loc)
+ | L.SgiSgn (x, n, sgn) => SOME (L'.SgiSgn (x, n, explifySgn sgn), loc)
+ | L.SgiConstraint _ => NONE
+ | L.SgiClassAbs (x, n, k) => SOME (L'.SgiConAbs (x, n, (L'.KArrow (explifyKind k, (L'.KType, loc)), loc)), loc)
+ | L.SgiClass (x, n, k, c) => SOME (L'.SgiCon (x, n, (L'.KArrow (explifyKind k, (L'.KType, loc)), loc),
+ explifyCon c), loc)
+
+and explifySgn (sgn, loc) =
+ case sgn of
+ L.SgnConst sgis => (L'.SgnConst (List.mapPartial explifySgi sgis), loc)
+ | L.SgnVar n => (L'.SgnVar n, loc)
+ | L.SgnFun (m, n, dom, ran) => (L'.SgnFun (m, n, explifySgn dom, explifySgn ran), loc)
+ | L.SgnWhere (sgn, ms, x, c) => (L'.SgnWhere (explifySgn sgn, ms, x, explifyCon c), loc)
+ | L.SgnProj x => (L'.SgnProj x, loc)
+ | L.SgnError => raise Fail ("explifySgn: SgnError at " ^ EM.spanToString loc)
+
+fun explifyDecl (d, loc : EM.span) =
+ case d of
+ L.DCon (x, n, k, c) => SOME (L'.DCon (x, n, explifyKind k, explifyCon c), loc)
+ | L.DDatatype dts => SOME (L'.DDatatype (map (fn (x, n, xs, xncs) =>
+ (x, n, xs,
+ map (fn (x, n, co) =>
+ (x, n, Option.map explifyCon co)) xncs)) dts), loc)
+ | L.DDatatypeImp (x, n, m1, ms, s, xs, xncs) =>
+ SOME (L'.DDatatypeImp (x, n, m1, ms, s, xs,
+ map (fn (x, n, co) =>
+ (x, n, Option.map explifyCon co)) xncs), loc)
+ | L.DVal (x, n, t, e) => SOME (L'.DVal (x, n, explifyCon t, explifyExp e), loc)
+ | L.DValRec vis => SOME (L'.DValRec (map (fn (x, n, t, e) => (x, n, explifyCon t, explifyExp e)) vis), loc)
+
+ | L.DSgn (x, n, sgn) => SOME (L'.DSgn (x, n, explifySgn sgn), loc)
+ | L.DStr (x, n, sgn, str) => SOME (L'.DStr (x, n, explifySgn sgn, explifyStr str), loc)
+ | L.DFfiStr (x, n, sgn) => SOME (L'.DFfiStr (x, n, explifySgn sgn), loc)
+ | L.DConstraint (c1, c2) => NONE
+ | L.DExport (en, sgn, str) => SOME (L'.DExport (en, explifySgn sgn, explifyStr str), loc)
+ | L.DTable (nt, x, n, c, pe, pc, ce, cc) =>
+ SOME (L'.DTable (nt, x, n, explifyCon c,
+ explifyExp pe, explifyCon pc,
+ explifyExp ce, explifyCon cc), loc)
+ | L.DView (nt, x, n, e, c) =>
+ SOME (L'.DView (nt, x, n, explifyExp e, explifyCon c), loc)
+ | L.DSequence (nt, x, n) => SOME (L'.DSequence (nt, x, n), loc)
+ | L.DDatabase s => SOME (L'.DDatabase s, loc)
+ | L.DCookie (nt, x, n, c) => SOME (L'.DCookie (nt, x, n, explifyCon c), loc)
+ | L.DStyle (nt, x, n) => SOME (L'.DStyle (nt, x, n), loc)
+ | L.DTask (e1, e2) => SOME (L'.DTask (explifyExp e1, explifyExp e2), loc)
+ | L.DPolicy e1 => SOME (L'.DPolicy (explifyExp e1), loc)
+ | L.DOnError v => SOME (L'.DOnError v, loc)
+ | L.DFfi (x, n, modes, t) => SOME (L'.DFfi (x, n, modes, explifyCon t), loc)
+
+and explifyStr (str, loc) =
+ case str of
+ L.StrConst ds => (L'.StrConst (List.mapPartial explifyDecl ds), loc)
+ | L.StrVar n => (L'.StrVar n, loc)
+ | L.StrProj (str, s) => (L'.StrProj (explifyStr str, s), loc)
+ | L.StrFun (m, n, dom, ran, str) => (L'.StrFun (m, n, explifySgn dom, explifySgn ran, explifyStr str), loc)
+ | L.StrApp (str1, str2) => (L'.StrApp (explifyStr str1, explifyStr str2), loc)
+ | L.StrError => raise Fail ("explifyStr: StrError at " ^ EM.spanToString loc)
+
+val explify = List.mapPartial explifyDecl
+
+end
diff --git a/src/export.sig b/src/export.sig
new file mode 100644
index 0000000..881459c
--- /dev/null
+++ b/src/export.sig
@@ -0,0 +1,44 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+signature EXPORT = sig
+
+datatype effect =
+ ReadOnly
+ | ReadCookieWrite
+ | ReadWrite
+
+datatype export_kind =
+ Link of effect
+ | Action of effect
+ | Rpc of effect
+ | Extern of effect
+
+val p_effect : effect Print.printer
+val p_export_kind : export_kind Print.printer
+
+end
diff --git a/src/export.sml b/src/export.sml
new file mode 100644
index 0000000..a99d0b7
--- /dev/null
+++ b/src/export.sml
@@ -0,0 +1,57 @@
+(* Copyright (c) 2009, 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 Export :> EXPORT = struct
+
+open Print.PD
+open Print
+
+datatype effect =
+ ReadOnly
+ | ReadCookieWrite
+ | ReadWrite
+
+datatype export_kind =
+ Link of effect
+ | Action of effect
+ | Rpc of effect
+ | Extern of effect
+
+fun p_effect ef =
+ case ef of
+ ReadOnly => string "r"
+ | ReadCookieWrite => string "rcw"
+ | ReadWrite => string "rw"
+
+fun p_export_kind ck =
+ case ck of
+ Link ef => box [string "link(", p_effect ef, string ")"]
+ | Action ef => box [string "action(", p_effect ef, string ")"]
+ | Rpc ef => box [string "rpc(", p_effect ef, string ")"]
+ | Extern ef => box [string "extern(", p_effect ef, string ")"]
+
+end
diff --git a/src/fastcgi.sig b/src/fastcgi.sig
new file mode 100644
index 0000000..c37fe68
--- /dev/null
+++ b/src/fastcgi.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+signature FASTCGI = sig
+
+end
diff --git a/src/fastcgi.sml b/src/fastcgi.sml
new file mode 100644
index 0000000..bf2a2a1
--- /dev/null
+++ b/src/fastcgi.sml
@@ -0,0 +1,53 @@
+(* Copyright (c) 2008-2010, 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 Fastcgi :> FASTCGI = struct
+
+open Settings
+open Print.PD Print
+
+val () = addProtocol {name = "fastcgi",
+ compile = "",
+ linkStatic = "liburweb_fastcgi.a",
+ linkDynamic = "-lurweb_fastcgi",
+ persistent = true,
+ code = fn () => box [string "void uw_global_custom() {",
+ newline,
+ case getSigFile () of
+ NONE => box []
+ | SOME sf => box [string "extern char *uw_sig_file;",
+ newline,
+ string "uw_sig_file = \"",
+ string sf,
+ string "\";",
+ newline],
+ string "uw_setup_limits();",
+ newline,
+ string "}",
+ newline]}
+
+end
diff --git a/src/fileio.sig b/src/fileio.sig
new file mode 100644
index 0000000..37b3b52
--- /dev/null
+++ b/src/fileio.sig
@@ -0,0 +1,9 @@
+signature FILE_IO = sig
+
+ (* When was a source file last modified (excluding files produced after [getResetTime])? *)
+ val mostRecentModTime : unit -> Time.time
+
+ val txtOpenIn : string -> TextIO.instream
+ val binOpenIn : string -> BinIO.instream
+
+end
diff --git a/src/fileio.sml b/src/fileio.sml
new file mode 100644
index 0000000..cab9d8a
--- /dev/null
+++ b/src/fileio.sml
@@ -0,0 +1,39 @@
+structure FileIO :> FILE_IO = struct
+
+val mostRecentModTimeRef = ref (Time.zeroTime)
+
+fun checkFileModTime fname =
+ let
+ val mtime = OS.FileSys.modTime fname
+ val mostRecentMod = !mostRecentModTimeRef
+ val resetTime = Globals.getResetTime ()
+ fun lessThan (a, b) = LargeInt.compare (Time.toSeconds a, Time.toSeconds b) = LESS
+ infix lessThan
+ in
+ if mostRecentMod lessThan mtime andalso mtime lessThan resetTime
+ then mostRecentModTimeRef := mtime
+ else ()
+ end
+
+fun mostRecentModTime () =
+ if Time.compare (!mostRecentModTimeRef, Time.zeroTime) = EQUAL
+ then Globals.getResetTime ()
+ else !mostRecentModTimeRef
+
+fun txtOpenIn fname =
+ let
+ val inf = TextIO.openIn fname
+ val () = checkFileModTime fname
+ in
+ inf
+ end
+
+fun binOpenIn fname =
+ let
+ val inf = BinIO.openIn fname
+ val () = checkFileModTime fname
+ in
+ inf
+ end
+
+end
diff --git a/src/fuse.sig b/src/fuse.sig
new file mode 100644
index 0000000..3ad45ac
--- /dev/null
+++ b/src/fuse.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature FUSE = sig
+
+ val fuse : Mono.file -> Mono.file
+
+end
diff --git a/src/fuse.sml b/src/fuse.sml
new file mode 100644
index 0000000..5193e59
--- /dev/null
+++ b/src/fuse.sml
@@ -0,0 +1,152 @@
+(* Copyright (c) 2008, 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 Fuse :> FUSE = struct
+
+open Mono
+structure U = MonoUtil
+
+structure IM = IntBinaryMap
+
+fun returnsString (t, loc) =
+ let
+ fun rs (t, loc) =
+ case t of
+ TFfi ("Basis", "string") => SOME ([], (TRecord [], loc))
+ | TFun (dom, ran) =>
+ (case rs ran of
+ NONE => NONE
+ | SOME (args, ran') => SOME (dom :: args, (TFun (dom, ran'), loc)))
+ | _ => NONE
+ in
+ case t of
+ TFun (dom, ran) =>
+ (case rs ran of
+ NONE => NONE
+ | SOME (args, ran') => SOME (dom :: args, (TFun (dom, ran'), loc)))
+ | _ => NONE
+ end
+
+fun fuse file =
+ let
+ fun doDecl (d as (_, loc), (funcs, maxName)) =
+ let
+ exception GetBody
+
+ fun doVi ((x, n, t, e, s), funcs, maxName) =
+ case returnsString t of
+ NONE => (NONE, funcs, maxName)
+ | SOME (args, t') =>
+ let
+ fun getBody (e, args) =
+ case (#1 e, args) of
+ (_, []) => (e, [])
+ | (EAbs (x, t, _, e), _ :: args) =>
+ let
+ val (body, args') = getBody (e, args)
+ in
+ (body, (x, t) :: args')
+ end
+ | _ => raise GetBody
+
+ val (body, args) = getBody (e, args)
+ val body = MonoOpt.optExp (EWrite body, loc)
+ val (body, _) = foldr (fn ((x, dom), (body, ran)) =>
+ ((EAbs (x, dom, ran, body), loc),
+ (TFun (dom, ran), loc)))
+ (body, (TRecord [], loc)) args
+ in
+ (SOME (x, maxName, t', body, s),
+ IM.insert (funcs, n, maxName),
+ maxName + 1)
+ end
+ handle GetBody => (NONE, funcs, maxName)
+
+ val (d, funcs, maxName) =
+ case #1 d of
+ DVal vi =>
+ let
+ val (vi', funcs, maxName) = doVi (vi, funcs, maxName)
+ in
+ (case vi' of
+ NONE => d
+ | SOME vi' => (DValRec [vi, vi'], loc),
+ funcs, maxName)
+ end
+ | DValRec vis =>
+ let
+ val (vis', funcs, maxName) =
+ foldl (fn (vi, (vis', funcs, maxName)) =>
+ let
+ val (vi', funcs, maxName) = doVi (vi, funcs, maxName)
+ in
+ (case vi' of
+ NONE => vis'
+ | SOME vi' => vi' :: vis',
+ funcs, maxName)
+ end)
+ ([], funcs, maxName) vis
+ in
+ ((DValRec (vis @ vis'), loc), funcs, maxName)
+ end
+ | _ => (d, funcs, maxName)
+
+ fun exp e =
+ case e of
+ EWrite e' =>
+ let
+ fun unravel (e, loc) =
+ case e of
+ ENamed n =>
+ (case IM.find (funcs, n) of
+ NONE => NONE
+ | SOME n' => SOME (ENamed n', loc))
+ | EApp (e1, e2) =>
+ (case unravel e1 of
+ NONE => NONE
+ | SOME e1 => SOME (EApp (e1, e2), loc))
+ | _ => NONE
+ in
+ case unravel e' of
+ NONE => e
+ | SOME (e', _) => e'
+ end
+ | _ => e
+ in
+ (U.Decl.map {typ = fn x => x,
+ exp = exp,
+ decl = fn x => x}
+ d,
+ (funcs, maxName))
+ end
+
+ val (ds, _) = ListUtil.foldlMap doDecl (IM.empty, U.File.maxName file + 1) (#1 file)
+ in
+ (ds, #2 file)
+ end
+
+end
diff --git a/src/globals.sig b/src/globals.sig
new file mode 100644
index 0000000..0cff65b
--- /dev/null
+++ b/src/globals.sig
@@ -0,0 +1,7 @@
+signature GLOBALS = sig
+
+ (* When was the Ur/Web compiler started or reset? *)
+ val setResetTime : unit -> unit
+ val getResetTime : unit -> Time.time
+
+end
diff --git a/src/globals.sml b/src/globals.sml
new file mode 100644
index 0000000..fafc043
--- /dev/null
+++ b/src/globals.sml
@@ -0,0 +1,7 @@
+structure Globals :> GLOBALS = struct
+
+val resetTime = ref (Time.zeroTime)
+fun setResetTime () = resetTime := Time.now ()
+fun getResetTime () = !resetTime
+
+end
diff --git a/src/http.sig b/src/http.sig
new file mode 100644
index 0000000..a9c13e8
--- /dev/null
+++ b/src/http.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+signature HTTP = sig
+
+end
diff --git a/src/http.sml b/src/http.sml
new file mode 100644
index 0000000..64dbb06
--- /dev/null
+++ b/src/http.sml
@@ -0,0 +1,55 @@
+(* Copyright (c) 2008-2010, 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 Http :> HTTP = struct
+
+open Settings
+open Print.PD Print
+
+val () = addProtocol {name = "http",
+ compile = "",
+ linkStatic = "liburweb_http.a",
+ linkDynamic = "-lurweb_http",
+ persistent = true,
+ code = fn () => box [string "void uw_global_custom() {",
+ newline,
+ case getSigFile () of
+ NONE => box []
+ | SOME sf => box [string "extern char *uw_sig_file;",
+ newline,
+ string "uw_sig_file = \"",
+ string sf,
+ string "\";",
+ newline],
+ string "uw_setup_limits();",
+ newline,
+ string "}",
+ newline]}
+
+val () = setProtocol "http"
+
+end
diff --git a/src/iflow.sig b/src/iflow.sig
new file mode 100644
index 0000000..3e624bb
--- /dev/null
+++ b/src/iflow.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2010, 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.
+ *)
+
+signature IFLOW = sig
+
+ val check : Mono.file -> unit
+
+ val debug : bool ref
+
+end
diff --git a/src/iflow.sml b/src/iflow.sml
new file mode 100644
index 0000000..5e8d697
--- /dev/null
+++ b/src/iflow.sml
@@ -0,0 +1,2184 @@
+(* Copyright (c) 2010, 2013, 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 Iflow :> IFLOW = struct
+
+open Mono
+open Sql
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+structure SK = struct
+type ord_key = string
+val compare = String.compare
+end
+
+structure SS = BinarySetFn(SK)
+structure SM = BinaryMapFn(SK)
+
+val writers = ["htmlifyInt_w",
+ "htmlifyFloat_w",
+ "htmlifyString_w",
+ "htmlifyBool_w",
+ "htmlifyTime_w",
+ "attrifyInt_w",
+ "attrifyFloat_w",
+ "attrifyString_w",
+ "attrifyChar_w",
+ "urlifyInt_w",
+ "urlifyFloat_w",
+ "urlifyString_w",
+ "urlifyBool_w",
+ "set_cookie"]
+
+val writers = SS.addList (SS.empty, writers)
+
+local
+ open Print
+ val string = PD.string
+in
+
+fun p_func f =
+ string (case f of
+ DtCon0 s => s
+ | DtCon1 s => s
+ | UnCon s => "un" ^ s
+ | Other s => s)
+
+fun p_exp e =
+ case e of
+ Const p => Prim.p_t p
+ | Var n => string ("x" ^ Int.toString n)
+ | Lvar n => string ("X" ^ Int.toString n)
+ | Func (f, es) => box [p_func f,
+ string "(",
+ p_list p_exp es,
+ string ")"]
+ | Recd xes => box [string "{",
+ p_list (fn (x, e) => box [string x,
+ space,
+ string "=",
+ space,
+ p_exp e]) xes,
+ string "}"]
+ | Proj (e, x) => box [p_exp e,
+ string ("." ^ x)]
+
+fun p_bop s es =
+ case es of
+ [e1, e2] => box [p_exp e1,
+ space,
+ string s,
+ space,
+ p_exp e2]
+ | _ => raise Fail "Iflow.p_bop"
+
+fun p_reln r es =
+ case r of
+ Known =>
+ (case es of
+ [e] => box [string "known(",
+ p_exp e,
+ string ")"]
+ | _ => raise Fail "Iflow.p_reln: Known")
+ | Sql s => box [string (s ^ "("),
+ p_list p_exp es,
+ string ")"]
+ | PCon0 s => box [string (s ^ "("),
+ p_list p_exp es,
+ string ")"]
+ | PCon1 s => box [string (s ^ "("),
+ p_list p_exp es,
+ string ")"]
+ | Cmp Eq => p_bop "=" es
+ | Cmp Ne => p_bop "<>" es
+ | Cmp Lt => p_bop "<" es
+ | Cmp Le => p_bop "<=" es
+ | Cmp Gt => p_bop ">" es
+ | Cmp Ge => p_bop ">=" es
+
+fun p_prop p =
+ case p of
+ True => string "True"
+ | False => string "False"
+ | Unknown => string "??"
+ | Lop (And, p1, p2) => box [string "(",
+ p_prop p1,
+ string ")",
+ space,
+ string "&&",
+ space,
+ string "(",
+ p_prop p2,
+ string ")"]
+ | Lop (Or, p1, p2) => box [string "(",
+ p_prop p1,
+ string ")",
+ space,
+ string "||",
+ space,
+ string "(",
+ p_prop p2,
+ string ")"]
+ | Reln (r, es) => p_reln r es
+ | Cond (e, p) => box [string "(",
+ p_exp e,
+ space,
+ string "==",
+ space,
+ p_prop p,
+ string ")"]
+
+end
+
+fun isKnown e =
+ case e of
+ Const _ => true
+ | Func (_, es) => List.all isKnown es
+ | Recd xes => List.all (isKnown o #2) xes
+ | Proj (e, _) => isKnown e
+ | _ => false
+
+fun simplify unif =
+ let
+ fun simplify e =
+ case e of
+ Const _ => e
+ | Var _ => e
+ | Lvar n =>
+ (case IM.find (unif, n) of
+ NONE => e
+ | SOME e => simplify e)
+ | Func (f, es) => Func (f, map simplify es)
+ | Recd xes => Recd (map (fn (x, e) => (x, simplify e)) xes)
+ | Proj (e, s) => Proj (simplify e, s)
+ in
+ simplify
+ end
+
+datatype atom =
+ AReln of reln * exp list
+ | ACond of exp * prop
+
+fun p_atom a =
+ p_prop (case a of
+ AReln x => Reln x
+ | ACond x => Cond x)
+
+(* Congruence closure *)
+structure Cc :> sig
+ type database
+
+ exception Contradiction
+
+ val database : unit -> database
+ val clear : database -> unit
+
+ val assert : database * atom -> unit
+ val check : database * atom -> bool
+
+ val p_database : database Print.printer
+
+ val builtFrom : database * {Base : exp list, Derived : exp} -> bool
+
+ val p_repOf : database -> exp Print.printer
+end = struct
+
+local
+ val count = ref 0
+in
+fun nodeId () =
+ let
+ val n = !count
+ in
+ count := n + 1;
+ n
+ end
+end
+
+exception Contradiction
+exception Undetermined
+
+structure CM = BinaryMapFn(struct
+ type ord_key = Prim.t
+ val compare = Prim.compare
+ end)
+
+datatype node = Node of {Id : int,
+ Rep : node ref option ref,
+ Cons : node ref SM.map ref,
+ Variety : variety,
+ Known : bool ref,
+ Ge : Int64.int option ref}
+
+ and variety =
+ Dt0 of string
+ | Dt1 of string * node ref
+ | Prim of Prim.t
+ | Recrd of node ref SM.map ref * bool
+ | Nothing
+
+type representative = node ref
+
+type database = {Vars : representative IM.map ref,
+ Consts : representative CM.map ref,
+ Con0s : representative SM.map ref,
+ Records : (representative SM.map * representative) list ref,
+ Funcs : ((string * representative list) * representative) list ref}
+
+fun database () = {Vars = ref IM.empty,
+ Consts = ref CM.empty,
+ Con0s = ref SM.empty,
+ Records = ref [],
+ Funcs = ref []}
+
+fun clear (t : database) = (#Vars t := IM.empty;
+ #Consts t := CM.empty;
+ #Con0s t := SM.empty;
+ #Records t := [];
+ #Funcs t := [])
+
+fun unNode n =
+ case !n of
+ Node r => r
+
+open Print
+val string = PD.string
+val newline = PD.newline
+
+fun p_rep n =
+ case !(#Rep (unNode n)) of
+ SOME n => p_rep n
+ | NONE =>
+ box [string (Int.toString (#Id (unNode n)) ^ ":"),
+ space,
+ case #Variety (unNode n) of
+ Nothing => string "?"
+ | Dt0 s => string ("Dt0(" ^ s ^ ")")
+ | Dt1 (s, n) => box[string ("Dt1(" ^ s ^ ","),
+ space,
+ p_rep n,
+ string ")"]
+ | Prim p => Prim.p_t p
+ | Recrd (ref m, b) => box [string "{",
+ p_list (fn (x, n) => box [string x,
+ space,
+ string "=",
+ space,
+ p_rep n]) (SM.listItemsi m),
+ string "}",
+ if b then
+ box [space,
+ string "(complete)"]
+ else
+ box []],
+ if !(#Known (unNode n)) then
+ string " (known)"
+ else
+ box [],
+ case !(#Ge (unNode n)) of
+ NONE => box []
+ | SOME n => string (" (>= " ^ Int64.toString n ^ ")")]
+
+fun p_database (db : database) =
+ box [string "Vars:",
+ newline,
+ p_list_sep newline (fn (i, n) => box [string ("x" ^ Int.toString i),
+ space,
+ string "=",
+ space,
+ p_rep n]) (IM.listItemsi (!(#Vars db)))]
+
+fun repOf (n : representative) : representative =
+ case !(#Rep (unNode n)) of
+ NONE => n
+ | SOME r =>
+ let
+ val r = repOf r
+ in
+ #Rep (unNode n) := SOME r;
+ r
+ end
+
+fun markKnown r =
+ let
+ val r = repOf r
+ in
+ (*Print.preface ("markKnown", p_rep r);*)
+ if !(#Known (unNode r)) then
+ ()(*TextIO.print "Already known\n"*)
+ else
+ (#Known (unNode r) := true;
+ SM.app markKnown (!(#Cons (unNode r)));
+ case #Variety (unNode r) of
+ Dt1 (_, r) => markKnown r
+ | Recrd (xes, _) => SM.app markKnown (!xes)
+ | _ => ())
+ end
+
+fun representative (db : database, e) =
+ let
+ fun rep e =
+ case e of
+ Const p => (case CM.find (!(#Consts db), p) of
+ SOME r => repOf r
+ | NONE =>
+ let
+ val r = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Prim p,
+ Known = ref true,
+ Ge = ref (case p of
+ Prim.Int n => SOME n
+ | _ => NONE)})
+ in
+ #Consts db := CM.insert (!(#Consts db), p, r);
+ r
+ end)
+ | Var n => (case IM.find (!(#Vars db), n) of
+ SOME r => repOf r
+ | NONE =>
+ let
+ val r = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Nothing,
+ Known = ref false,
+ Ge = ref NONE})
+ in
+ #Vars db := IM.insert (!(#Vars db), n, r);
+ r
+ end)
+ | Lvar _ => raise Undetermined
+ | Func (DtCon0 f, []) => (case SM.find (!(#Con0s db), f) of
+ SOME r => repOf r
+ | NONE =>
+ let
+ val r = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Dt0 f,
+ Known = ref true,
+ Ge = ref NONE})
+ in
+ #Con0s db := SM.insert (!(#Con0s db), f, r);
+ r
+ end)
+ | Func (DtCon0 _, _) => raise Fail "Iflow.rep: DtCon0"
+ | Func (DtCon1 f, [e]) =>
+ let
+ val r = rep e
+ in
+ case SM.find (!(#Cons (unNode r)), f) of
+ SOME r => repOf r
+ | NONE =>
+ let
+ val r' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Dt1 (f, r),
+ Known = ref (!(#Known (unNode r))),
+ Ge = ref NONE})
+ in
+ #Cons (unNode r) := SM.insert (!(#Cons (unNode r)), f, r');
+ r'
+ end
+ end
+ | Func (DtCon1 _, _) => raise Fail "Iflow.rep: DtCon1"
+ | Func (UnCon f, [e]) =>
+ let
+ val r = rep e
+ in
+ case #Variety (unNode r) of
+ Dt1 (f', n) => if f' = f then
+ repOf n
+ else
+ raise Contradiction
+ | Nothing =>
+ let
+ val cons = ref SM.empty
+ val r' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = cons,
+ Variety = Nothing,
+ Known = ref (!(#Known (unNode r))),
+ Ge = ref NONE})
+
+ val r'' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = #Cons (unNode r),
+ Variety = Dt1 (f, r'),
+ Known = #Known (unNode r),
+ Ge = ref NONE})
+ in
+ cons := SM.insert (!cons, f, r'');
+ #Rep (unNode r) := SOME r'';
+ r'
+ end
+ | _ => raise Contradiction
+ end
+ | Func (UnCon _, _) => raise Fail "Iflow.rep: UnCon"
+ | Func (Other f, es) =>
+ let
+ val rs = map rep es
+ in
+ case List.find (fn (x : string * representative list, _) => x = (f, rs)) (!(#Funcs db)) of
+ NONE =>
+ let
+ val r = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Nothing,
+ Known = ref (f = "allow"),
+ Ge = ref NONE})
+ in
+ #Funcs db := ((f, rs), r) :: (!(#Funcs db));
+ r
+ end
+ | SOME (_, r) => repOf r
+ end
+ | Recd xes =>
+ let
+ val xes = map (fn (x, e) => (x, rep e)) xes
+ val len = length xes
+ in
+ case List.find (fn (xes', _) =>
+ SM.numItems xes' = len
+ andalso List.all (fn (x, n) =>
+ case SM.find (xes', x) of
+ NONE => false
+ | SOME n' => n = repOf n') xes)
+ (!(#Records db)) of
+ SOME (_, r) => repOf r
+ | NONE =>
+ let
+ val xes = foldl SM.insert' SM.empty xes
+
+ val r' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Recrd (ref xes, true),
+ Known = ref false,
+ Ge = ref NONE})
+ in
+ #Records db := (xes, r') :: (!(#Records db));
+ r'
+ end
+ end
+ | Proj (e, f) =>
+ let
+ val r = rep e
+ in
+ case #Variety (unNode r) of
+ Recrd (xes, _) =>
+ (case SM.find (!xes, f) of
+ SOME r => repOf r
+ | NONE => let
+ val r = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Nothing,
+ Known = ref (!(#Known (unNode r))),
+ Ge = ref NONE})
+ in
+ xes := SM.insert (!xes, f, r);
+ r
+ end)
+ | Nothing =>
+ let
+ val r' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Nothing,
+ Known = ref (!(#Known (unNode r))),
+ Ge = ref NONE})
+
+ val r'' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = #Cons (unNode r),
+ Variety = Recrd (ref (SM.insert (SM.empty, f, r')), false),
+ Known = #Known (unNode r),
+ Ge = ref NONE})
+ in
+ #Rep (unNode r) := SOME r'';
+ r'
+ end
+ | _ => raise Contradiction
+ end
+ in
+ rep e
+ end
+
+fun p_repOf db e = p_rep (representative (db, e))
+
+fun assert (db, a) =
+ let
+ fun markEq (r1, r2) =
+ let
+ val r1 = repOf r1
+ val r2 = repOf r2
+ in
+ if r1 = r2 then
+ ()
+ else case (#Variety (unNode r1), #Variety (unNode r2)) of
+ (Prim p1, Prim p2) => if Prim.equal (p1, p2) then
+ ()
+ else
+ raise Contradiction
+ | (Dt0 f1, Dt0 f2) => if f1 = f2 then
+ ()
+ else
+ raise Contradiction
+ | (Dt1 (f1, r1), Dt1 (f2, r2)) => if f1 = f2 then
+ markEq (r1, r2)
+ else
+ raise Contradiction
+ | (Recrd (xes1, _), Recrd (xes2, _)) =>
+ let
+ fun unif (xes1, xes2) =
+ SM.appi (fn (x, r1) =>
+ case SM.find (!xes2, x) of
+ NONE => xes2 := SM.insert (!xes2, x, r1)
+ | SOME r2 => markEq (r1, r2)) (!xes1)
+ in
+ unif (xes1, xes2);
+ unif (xes2, xes1)
+ end
+ | (Nothing, _) => mergeNodes (r1, r2)
+ | (_, Nothing) => mergeNodes (r2, r1)
+ | _ => raise Contradiction
+ end
+
+ and mergeNodes (r1, r2) =
+ (#Rep (unNode r1) := SOME r2;
+ if !(#Known (unNode r1)) then
+ markKnown r2
+ else
+ ();
+ if !(#Known (unNode r2)) then
+ markKnown r1
+ else
+ ();
+ #Cons (unNode r2) := SM.unionWith #1 (!(#Cons (unNode r2)), !(#Cons (unNode r1)));
+
+ case !(#Ge (unNode r1)) of
+ NONE => ()
+ | SOME n1 =>
+ case !(#Ge (unNode r2)) of
+ NONE => #Ge (unNode r2) := SOME n1
+ | SOME n2 => #Ge (unNode r2) := SOME (Int64.max (n1, n2));
+
+ compactFuncs ())
+
+ and compactFuncs () =
+ let
+ fun loop funcs =
+ case funcs of
+ [] => []
+ | (fr as ((f, rs), r)) :: rest =>
+ let
+ val rest = List.filter (fn ((f' : string, rs'), r') =>
+ if f' = f
+ andalso ListPair.allEq (fn (r1, r2) =>
+ repOf r1 = repOf r2)
+ (rs, rs') then
+ (markEq (r, r');
+ false)
+ else
+ true) rest
+ in
+ fr :: loop rest
+ end
+ in
+ #Funcs db := loop (!(#Funcs db))
+ end
+ in
+ case a of
+ ACond _ => ()
+ | AReln x =>
+ case x of
+ (Known, [e]) =>
+ ((*Print.prefaces "Before" [("e", p_exp e),
+ ("db", p_database db)];*)
+ markKnown (representative (db, e))(*;
+ Print.prefaces "After" [("e", p_exp e),
+ ("db", p_database db)]*))
+ | (PCon0 f, [e]) =>
+ let
+ val r = representative (db, e)
+ in
+ case #Variety (unNode r) of
+ Dt0 f' => if f = f' then
+ ()
+ else
+ raise Contradiction
+ | Nothing =>
+ (case SM.find (!(#Con0s db), f) of
+ SOME r' => markEq (r, r')
+ | NONE =>
+ let
+ val r' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Dt0 f,
+ Known = ref false,
+ Ge = ref NONE})
+ in
+ #Rep (unNode r) := SOME r';
+ #Con0s db := SM.insert (!(#Con0s db), f, r')
+ end)
+ | _ => raise Contradiction
+ end
+ | (PCon1 f, [e]) =>
+ let
+ val r = representative (db, e)
+ in
+ case #Variety (unNode r) of
+ Dt1 (f', e') => if f = f' then
+ ()
+ else
+ raise Contradiction
+ | Nothing =>
+ let
+ val cons = ref SM.empty
+
+ val r'' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = cons,
+ Variety = Nothing,
+ Known = ref (!(#Known (unNode r))),
+ Ge = ref NONE})
+
+ val r' = ref (Node {Id = nodeId (),
+ Rep = ref NONE,
+ Cons = ref SM.empty,
+ Variety = Dt1 (f, r''),
+ Known = #Known (unNode r),
+ Ge = ref NONE})
+ in
+ cons := SM.insert (!cons, f, r');
+ #Rep (unNode r) := SOME r'
+ end
+ | _ => raise Contradiction
+ end
+ | (Cmp Eq, [e1, e2]) =>
+ markEq (representative (db, e1), representative (db, e2))
+ | (Cmp Ge, [e1, e2]) =>
+ let
+ val r1 = representative (db, e1)
+ val r2 = representative (db, e2)
+ in
+ case !(#Ge (unNode (repOf r2))) of
+ NONE => ()
+ | SOME n2 =>
+ case !(#Ge (unNode (repOf r1))) of
+ NONE => #Ge (unNode (repOf r1)) := SOME n2
+ | SOME n1 => #Ge (unNode (repOf r1)) := SOME (Int64.max (n1, n2))
+ end
+ | _ => ()
+ end handle Undetermined => ()
+
+fun check (db, a) =
+ (case a of
+ ACond _ => false
+ | AReln x =>
+ case x of
+ (Known, [e]) =>
+ let
+ fun isKnown r =
+ let
+ val r = repOf r
+ in
+ !(#Known (unNode r))
+ orelse case #Variety (unNode r) of
+ Dt1 (_, r) => isKnown r
+ | Recrd (xes, true) => List.all isKnown (SM.listItems (!xes))
+ | _ => false
+ end
+
+ val r = representative (db, e)
+ in
+ isKnown r
+ end
+ | (PCon0 f, [e]) =>
+ (case #Variety (unNode (representative (db, e))) of
+ Dt0 f' => f' = f
+ | _ => false)
+ | (PCon1 f, [e]) =>
+ (case #Variety (unNode (representative (db, e))) of
+ Dt1 (f', _) => f' = f
+ | _ => false)
+ | (Cmp Eq, [e1, e2]) =>
+ let
+ val r1 = representative (db, e1)
+ val r2 = representative (db, e2)
+ in
+ repOf r1 = repOf r2
+ end
+ | (Cmp Ge, [e1, e2]) =>
+ let
+ val r1 = representative (db, e1)
+ val r2 = representative (db, e2)
+ in
+ case (!(#Ge (unNode (repOf r1))), #Variety (unNode (repOf r2))) of
+ (SOME n1, Prim (Prim.Int n2)) => Int64.>= (n1, n2)
+ | _ => false
+ end
+ | _ => false)
+ handle Undetermined => false
+
+fun builtFrom (db, {Base = bs, Derived = d}) =
+ let
+ val bs = map (fn b => representative (db, b)) bs
+
+ fun loop d =
+ let
+ val d = repOf d
+ in
+ !(#Known (unNode d))
+ orelse List.exists (fn b => repOf b = d) bs
+ orelse (case #Variety (unNode d) of
+ Dt0 _ => true
+ | Dt1 (_, d) => loop d
+ | Prim _ => true
+ | Recrd (xes, _) => List.all loop (SM.listItems (!xes))
+ | Nothing => false)
+ orelse List.exists (fn r => List.exists (fn b => repOf b = repOf r) bs)
+ (SM.listItems (!(#Cons (unNode d))))
+ end
+
+ fun decomp e =
+ case e of
+ Func (Other _, es) => List.all decomp es
+ | _ => loop (representative (db, e))
+ in
+ decomp d
+ end handle Undetermined => false
+
+end
+
+val tabs = ref (SM.empty : (string list * string list list) SM.map)
+
+fun patCon pc =
+ case pc of
+ PConVar n => "C" ^ Int.toString n
+ | PConFfi {mod = m, datatyp = d, con = c, ...} => m ^ "." ^ d ^ "." ^ c
+
+type check = exp * ErrorMsg.span
+
+structure St :> sig
+ val reset : unit -> unit
+
+ type stashed
+ val stash : unit -> stashed
+ val reinstate : stashed -> unit
+
+ type stashedPath
+ val stashPath : unit -> stashedPath
+ val reinstatePath : stashedPath -> unit
+
+ val nextVar : unit -> int
+
+ val assert : atom list -> unit
+
+ val addPath : check -> unit
+
+ val allowSend : atom list * exp list -> unit
+ val send : check -> unit
+
+ val allowInsert : atom list -> unit
+ val insert : ErrorMsg.span -> unit
+
+ val allowDelete : atom list -> unit
+ val delete : ErrorMsg.span -> unit
+
+ val allowUpdate : atom list -> unit
+ val update : ErrorMsg.span -> unit
+
+ val havocReln : reln -> unit
+ val havocCookie : string -> unit
+
+ val check : atom -> bool
+
+ val debug : unit -> unit
+end = struct
+
+val hnames = ref 1
+
+type hyps = int * atom list * bool ref
+
+val db = Cc.database ()
+val path = ref ([] : ((int * atom list) * check) option ref list)
+val hyps = ref (0, [] : atom list, ref false)
+val nvar = ref 0
+
+fun setHyps (n', hs) =
+ let
+ val (n, _, _) = !hyps
+ in
+ if n' = n then
+ ()
+ else
+ (hyps := (n', hs, ref false);
+ Cc.clear db;
+ app (fn a => Cc.assert (db, a)) hs)
+ end
+
+fun useKeys () =
+ let
+ val changed = ref false
+
+ fun findKeys (hyps, acc) =
+ case hyps of
+ [] => rev acc
+ | (a as AReln (Sql tab, [r1])) :: hyps =>
+ (case SM.find (!tabs, tab) of
+ NONE => findKeys (hyps, a :: acc)
+ | SOME (_, []) => findKeys (hyps, a :: acc)
+ | SOME (_, ks) =>
+ let
+ fun finder (hyps, acc) =
+ case hyps of
+ [] => rev acc
+ | (a as AReln (Sql tab', [r2])) :: hyps =>
+ if tab' = tab andalso
+ List.exists (List.all (fn f =>
+ let
+ val r =
+ Cc.check (db,
+ AReln (Cmp Eq, [Proj (r1, f),
+ Proj (r2, f)]))
+ in
+ (*Print.prefaces "Fs"
+ [("tab",
+ Print.PD.string tab),
+ ("r1",
+ p_exp (Proj (r1, f))),
+ ("r2",
+ p_exp (Proj (r2, f))),
+ ("r",
+ Print.PD.string
+ (Bool.toString r))];*)
+ r
+ end)) ks then
+ (changed := true;
+ Cc.assert (db, AReln (Cmp Eq, [r1, r2]));
+ finder (hyps, acc))
+ else
+ finder (hyps, a :: acc)
+ | a :: hyps => finder (hyps, a :: acc)
+
+ val hyps = finder (hyps, [])
+ in
+ findKeys (hyps, a :: acc)
+ end)
+ | a :: hyps => findKeys (hyps, a :: acc)
+
+ fun loop hs =
+ let
+ val hs = findKeys (hs, [])
+ in
+ if !changed then
+ (changed := false;
+ loop hs)
+ else
+ ()
+ end
+
+ val (_, hs, _) = !hyps
+ in
+ (*print "useKeys\n";*)
+ loop hs
+ end
+
+fun complete () =
+ let
+ val (_, _, bf) = !hyps
+ in
+ if !bf then
+ ()
+ else
+ (bf := true;
+ useKeys ())
+ end
+
+type stashed = int * ((int * atom list) * check) option ref list * (int * atom list)
+fun stash () = (!nvar, !path, (#1 (!hyps), #2 (!hyps)))
+fun reinstate (nv, p, h) =
+ (nvar := nv;
+ path := p;
+ setHyps h)
+
+type stashedPath = ((int * atom list) * check) option ref list
+fun stashPath () = !path
+fun reinstatePath p = path := p
+
+fun nextVar () =
+ let
+ val n = !nvar
+ in
+ nvar := n + 1;
+ n
+ end
+
+fun assert ats =
+ let
+ val n = !hnames
+ val (_, hs, _) = !hyps
+ in
+ hnames := n + 1;
+ hyps := (n, ats @ hs, ref false);
+ app (fn a => Cc.assert (db, a)) ats
+ end
+
+fun addPath c = path := ref (SOME ((#1 (!hyps), #2 (!hyps)), c)) :: !path
+
+val sendable = ref ([] : (atom list * exp list) list)
+
+fun checkGoals goals k =
+ let
+ fun checkGoals goals unifs =
+ case goals of
+ [] => k unifs
+ | AReln (Sql tab, [Lvar lv]) :: goals =>
+ let
+ val saved = stash ()
+ val (_, hyps, _) = !hyps
+
+ fun tryAll unifs hyps =
+ case hyps of
+ [] => false
+ | AReln (Sql tab', [e]) :: hyps =>
+ (tab' = tab andalso
+ checkGoals goals (IM.insert (unifs, lv, e)))
+ orelse tryAll unifs hyps
+ | _ :: hyps => tryAll unifs hyps
+ in
+ tryAll unifs hyps
+ end
+ | (g as AReln (r, es)) :: goals =>
+ (complete ();
+ (if Cc.check (db, AReln (r, map (simplify unifs) es)) then
+ true
+ else
+ ((*Print.preface ("Fail", p_atom (AReln (r, map (simplify unifs) es)));*)
+ false))
+ andalso checkGoals goals unifs)
+ | ACond _ :: _ => false
+ in
+ checkGoals goals IM.empty
+ end
+
+fun buildable (e, loc) =
+ let
+ fun doPols pols acc =
+ case pols of
+ [] =>
+ let
+ val b = Cc.builtFrom (db, {Base = acc, Derived = e})
+ in
+ (*Print.prefaces "buildable" [("Base", Print.p_list p_exp acc),
+ ("Derived", p_exp e),
+ ("Hyps", Print.p_list p_atom (#2 (!hyps))),
+ ("Good", Print.PD.string (Bool.toString b))];*)
+ b
+ end
+ | (goals, es) :: pols =>
+ checkGoals goals (fn unifs => doPols pols (map (simplify unifs) es @ acc))
+ orelse doPols pols acc
+ in
+ if doPols (!sendable) [] then
+ ()
+ else
+ let
+ val (_, hs, _) = !hyps
+ in
+ ErrorMsg.errorAt loc "The information flow policy may be violated here.";
+ Print.prefaces "Situation" [("User learns", p_exp e),
+ ("Hypotheses", Print.p_list p_atom hs),
+ ("E-graph", Cc.p_database db)]
+ end
+ end
+
+fun checkPaths () =
+ let
+ val (n, hs, _) = !hyps
+ val hs = (n, hs)
+ in
+ app (fn r =>
+ case !r of
+ NONE => ()
+ | SOME (hs, e) =>
+ (r := NONE;
+ setHyps hs;
+ buildable e)) (!path);
+ setHyps hs
+ end
+
+fun allowSend v = ((*Print.prefaces "Allow" [("goals", Print.p_list p_atom (#1 v)),
+ ("exps", Print.p_list p_exp (#2 v))];*)
+ sendable := v :: !sendable)
+
+fun send (e, loc) = ((*Print.preface ("Send[" ^ Bool.toString uk ^ "]", p_exp e);*)
+ complete ();
+ checkPaths ();
+ if isKnown e then
+ ()
+ else
+ buildable (e, loc))
+
+fun doable pols (loc : ErrorMsg.span) =
+ let
+ val pols = !pols
+ in
+ complete ();
+ if List.exists (fn goals =>
+ if checkGoals goals (fn _ => true) then
+ ((*Print.prefaces "Match" [("goals", Print.p_list p_atom goals),
+ ("hyps", Print.p_list p_atom (#2 (!hyps)))];*)
+ true)
+ else
+ ((*Print.prefaces "No match" [("goals", Print.p_list p_atom goals)(*,
+ ("hyps", Print.p_list p_atom (#2 (!hyps)))*)];*)
+ false)) pols then
+ ()
+ else
+ let
+ val (_, hs, _) = !hyps
+ in
+ ErrorMsg.errorAt loc "The database update policy may be violated here.";
+ Print.prefaces "Situation" [("Hypotheses", Print.p_list p_atom hs)(*,
+ ("E-graph", Cc.p_database db)*)]
+ end
+ end
+
+val insertable = ref ([] : atom list list)
+fun allowInsert v = insertable := v :: !insertable
+val insert = doable insertable
+
+val updatable = ref ([] : atom list list)
+fun allowUpdate v = updatable := v :: !updatable
+val update = doable updatable
+
+val deletable = ref ([] : atom list list)
+fun allowDelete v = deletable := v :: !deletable
+val delete = doable deletable
+
+fun reset () = (Cc.clear db;
+ path := [];
+ hyps := (0, [], ref false);
+ nvar := 0;
+ sendable := [];
+ insertable := [];
+ updatable := [];
+ deletable := [])
+
+fun havocReln r =
+ let
+ val n = !hnames
+ val (_, hs, _) = !hyps
+ in
+ hnames := n + 1;
+ hyps := (n, List.filter (fn AReln (r', _) => r' <> r | _ => true) hs, ref false)
+ end
+
+fun havocCookie cname =
+ let
+ val cname = "cookie/" ^ cname
+ val n = !hnames
+ val (_, hs, _) = !hyps
+ in
+ hnames := n + 1;
+ hyps := (n, List.filter (fn AReln (Cmp Eq, [_, Func (Other f, [])]) => f <> cname | _ => true) hs, ref false)
+ end
+
+fun check a = Cc.check (db, a)
+
+fun debug () =
+ let
+ val (_, hs, _) = !hyps
+ in
+ Print.preface ("Hyps", Print.p_list p_atom hs)
+ end
+
+end
+
+
+fun removeDups (ls : (string * string) list) =
+ case ls of
+ [] => []
+ | x :: ls =>
+ let
+ val ls = removeDups ls
+ in
+ if List.exists (fn x' => x' = x) ls then
+ ls
+ else
+ x :: ls
+ end
+
+fun deinj env e =
+ case #1 e of
+ ERel n => SOME (List.nth (env, n))
+ | EField (e, f) =>
+ (case deinj env e of
+ NONE => NONE
+ | SOME e => SOME (Proj (e, f)))
+ | EApp ((EFfi mf, _), e) =>
+ if Settings.isEffectful mf orelse Settings.isBenignEffectful mf then
+ NONE
+ else (case deinj env e of
+ NONE => NONE
+ | SOME e => SOME (Func (Other (#1 mf ^ "." ^ #2 mf), [e])))
+ | _ => NONE
+
+fun expIn rv env rvOf =
+ let
+ fun expIn e =
+ let
+ fun default () = inl (rv ())
+ in
+ case e of
+ SqConst p => inl (Const p)
+ | SqTrue => inl (Func (DtCon0 "Basis.bool.True", []))
+ | SqFalse => inl (Func (DtCon0 "Basis.bool.False", []))
+ | Null => inl (Func (DtCon0 "None", []))
+ | SqNot e =>
+ inr (case expIn e of
+ inl e => Reln (Cmp Eq, [e, Func (DtCon0 "Basis.bool.False", [])])
+ | inr _ => Unknown)
+ | Field (v, f) => inl (Proj (rvOf v, f))
+ | Computed _ => default ()
+ | Binop (bo, e1, e2) =>
+ let
+ val e1 = expIn e1
+ val e2 = expIn e2
+ in
+ inr (case (bo, e1, e2) of
+ (RCmp c, inl e1, inl e2) => Reln (Cmp c, [e1, e2])
+ | (RLop l, v1, v2) =>
+ let
+ fun pin v =
+ case v of
+ inl e => Reln (Cmp Eq, [e, Func (DtCon0 "Basis.bool.True", [])])
+ | inr p => p
+ in
+ Lop (l, pin v1, pin v2)
+ end
+ | _ => Unknown)
+ end
+ | SqKnown e =>
+ (case expIn e of
+ inl e => inr (Reln (Known, [e]))
+ | _ => inr Unknown)
+ | Inj e =>
+ inl (case deinj env e of
+ NONE => rv ()
+ | SOME e => e)
+ | SqFunc (f, e) =>
+ (case expIn e of
+ inl e => inl (Func (Other f, [e]))
+ | _ => default ())
+
+ | Unmodeled => inl (Func (Other "allow", [rv ()]))
+ end
+ in
+ expIn
+ end
+
+fun decomp {Save = save, Restore = restore, Add = add} =
+ let
+ fun go p k =
+ case p of
+ True => (k () handle Cc.Contradiction => ())
+ | False => ()
+ | Unknown => ()
+ | Lop (And, p1, p2) => go p1 (fn () => go p2 k)
+ | Lop (Or, p1, p2) =>
+ let
+ val saved = save ()
+ in
+ go p1 k;
+ restore saved;
+ go p2 k
+ end
+ | Reln x => (add (AReln x); k ())
+ | Cond x => (add (ACond x); k ())
+ in
+ go
+ end
+
+datatype queryMode =
+ SomeCol of {New : (string * exp) option, Old : (string * exp) option, Outs : exp list} -> unit
+ | AllCols of exp -> unit
+
+type 'a doQuery = {
+ Env : exp list,
+ NextVar : unit -> exp,
+ Add : atom -> unit,
+ Save : unit -> 'a,
+ Restore : 'a -> unit,
+ Cont : queryMode
+}
+
+fun doQuery (arg : 'a doQuery) (e as (_, loc)) =
+ let
+ fun default () = (ErrorMsg.errorAt loc "Information flow checker can't parse SQL query";
+ Print.preface ("Query", MonoPrint.p_exp MonoEnv.empty e))
+ in
+ case parse query e of
+ NONE => default ()
+ | SOME q =>
+ let
+ fun doQuery q =
+ case q of
+ Query1 r =>
+ let
+ val new = ref NONE
+ val old = ref NONE
+
+ val rvs = map (fn Table (tab, v) =>
+ let
+ val nv = #NextVar arg ()
+ in
+ case v of
+ "New" => new := SOME (tab, nv)
+ | "Old" => old := SOME (tab, nv)
+ | _ => ();
+ (v, nv)
+ end
+ | _ => raise Fail "Iflow: not ready for joins or nesteds") (#From r)
+
+ fun rvOf v =
+ case List.find (fn (v', _) => v' = v) rvs of
+ NONE => raise Fail "Iflow.queryProp: Bad table variable"
+ | SOME (_, e) => e
+
+ val expIn = expIn (#NextVar arg) (#Env arg) rvOf
+
+ val saved = #Save arg ()
+ fun addFrom () = app (fn Table (t, v) => #Add arg (AReln (Sql t, [rvOf v]))
+ | _ => raise Fail "Iflow: not ready for joins or nesteds") (#From r)
+
+ fun usedFields e =
+ case e of
+ SqConst _ => []
+ | SqTrue => []
+ | SqFalse => []
+ | Null => []
+ | SqNot e => usedFields e
+ | Field (v, f) => [(false, Proj (rvOf v, f))]
+ | Computed _ => []
+ | Binop (_, e1, e2) => usedFields e1 @ usedFields e2
+ | SqKnown _ => []
+ | Inj e =>
+ (case deinj (#Env arg) e of
+ NONE => (ErrorMsg.errorAt loc "Expression injected into SQL is too complicated";
+ [])
+ | SOME e => [(true, e)])
+ | SqFunc (_, e) => usedFields e
+ | Unmodeled => []
+
+ fun normal' () =
+ case #Cont arg of
+ SomeCol k =>
+ let
+ val sis = map (fn si =>
+ case si of
+ SqField (v, f) => Proj (rvOf v, f)
+ | SqExp (e, f) =>
+ case expIn e of
+ inr _ => #NextVar arg ()
+ | inl e => e) (#Select r)
+ in
+ k {New = !new, Old = !old, Outs = sis}
+ end
+ | AllCols k =>
+ let
+ val (ts, es) =
+ foldl (fn (si, (ts, es)) =>
+ case si of
+ SqField (v, f) =>
+ let
+ val fs = getOpt (SM.find (ts, v), SM.empty)
+ in
+ (SM.insert (ts, v, SM.insert (fs, f, Proj (rvOf v, f))), es)
+ end
+ | SqExp (e, f) =>
+ let
+ val e =
+ case expIn e of
+ inr _ => #NextVar arg ()
+ | inl e => e
+ in
+ (ts, SM.insert (es, f, e))
+ end)
+ (SM.empty, SM.empty) (#Select r)
+ in
+ k (Recd (map (fn (t, fs) => (t, Recd (SM.listItemsi fs)))
+ (SM.listItemsi ts)
+ @ SM.listItemsi es))
+ end
+
+ fun doWhere final =
+ (addFrom ();
+ case #Where r of
+ NONE => final ()
+ | SOME e =>
+ let
+ val p = case expIn e of
+ inl e => Reln (Cmp Eq, [e, Func (DtCon0 "Basis.bool.True", [])])
+ | inr p => p
+
+ val saved = #Save arg ()
+ in
+ decomp {Save = #Save arg, Restore = #Restore arg, Add = #Add arg}
+ p (fn () => final () handle Cc.Contradiction => ());
+ #Restore arg saved
+ end)
+ handle Cc.Contradiction => ()
+
+ fun normal () = doWhere normal'
+ in
+ (case #Select r of
+ [SqExp (Binop (RCmp bo, Count, SqConst (Prim.Int 0)), f)] =>
+ (case bo of
+ Gt =>
+ (case #Cont arg of
+ SomeCol _ => ()
+ | AllCols k =>
+ let
+ fun answer e = k (Recd [(f, e)])
+
+ val saved = #Save arg ()
+ val () = (answer (Func (DtCon0 "Basis.bool.False", [])))
+ handle Cc.Contradiction => ()
+ in
+ #Restore arg saved;
+ (*print "True time!\n";*)
+ doWhere (fn () => answer (Func (DtCon0 "Basis.bool.True", [])));
+ #Restore arg saved
+ end)
+ | _ => normal ())
+ | _ => normal ())
+ before #Restore arg saved
+ end
+ | Union (q1, q2) =>
+ let
+ val saved = #Save arg ()
+ in
+ doQuery q1;
+ #Restore arg saved;
+ doQuery q2;
+ #Restore arg saved
+ end
+ in
+ doQuery q
+ end
+ end
+
+fun evalPat env e (pt, _) =
+ case pt of
+ PVar _ => e :: env
+ | PPrim _ => env
+ | PCon (_, pc, NONE) => (St.assert [AReln (PCon0 (patCon pc), [e])]; env)
+ | PCon (_, pc, SOME pt) =>
+ let
+ val env = evalPat env (Func (UnCon (patCon pc), [e])) pt
+ in
+ St.assert [AReln (PCon1 (patCon pc), [e])];
+ env
+ end
+ | PRecord xpts =>
+ foldl (fn ((x, pt, _), env) => evalPat env (Proj (e, x)) pt) env xpts
+ | PNone _ => (St.assert [AReln (PCon0 "None", [e])]; env)
+ | PSome (_, pt) =>
+ let
+ val env = evalPat env (Func (UnCon "Some", [e])) pt
+ in
+ St.assert [AReln (PCon1 "Some", [e])];
+ env
+ end
+
+datatype arg_mode = Fixed | Decreasing | Arbitrary
+type rfun = {args : arg_mode list, tables : SS.set, cookies : SS.set, body : Mono.exp}
+val rfuns = ref (IM.empty : rfun IM.map)
+
+fun evalExp env (e as (_, loc)) k =
+ let
+ (*val () = St.debug ()*)
+ (*val () = Print.preface ("evalExp", MonoPrint.p_exp MonoEnv.empty e)*)
+
+ fun default () = k (Var (St.nextVar ()))
+
+ fun doFfi (m, s, es) =
+ if m = "Basis" andalso SS.member (writers, s) then
+ let
+ fun doArgs es =
+ case es of
+ [] =>
+ (if s = "set_cookie" then
+ case es of
+ [_, (cname, _), _, _, _] =>
+ (case #1 cname of
+ EPrim (Prim.String (_, cname)) =>
+ St.havocCookie cname
+ | _ => ())
+ | _ => ()
+ else
+ ();
+ k (Recd []))
+ | (e, _) :: es =>
+ evalExp env e (fn e => (St.send (e, loc); doArgs es))
+ in
+ doArgs es
+ end
+ else if Settings.isEffectful (m, s) andalso not (Settings.isBenignEffectful (m, s)) then
+ default ()
+ else
+ let
+ fun doArgs (es, acc) =
+ case es of
+ [] => k (Func (Other (m ^ "." ^ s), rev acc))
+ | (e, _) :: es =>
+ evalExp env e (fn e => doArgs (es, e :: acc))
+ in
+ doArgs (es, [])
+ end
+ in
+ case #1 e of
+ EPrim p => k (Const p)
+ | ERel n => k (List.nth (env, n))
+ | ENamed _ => default ()
+ | ECon (_, pc, NONE) => k (Func (DtCon0 (patCon pc), []))
+ | ECon (_, pc, SOME e) => evalExp env e (fn e => k (Func (DtCon1 (patCon pc), [e])))
+ | ENone _ => k (Func (DtCon0 "None", []))
+ | ESome (_, e) => evalExp env e (fn e => k (Func (DtCon1 "Some", [e])))
+ | EFfi _ => default ()
+
+ | EFfiApp ("Basis", "rand", []) =>
+ let
+ val e = Var (St.nextVar ())
+ in
+ St.assert [AReln (Known, [e])];
+ k e
+ end
+ | EFfiApp x => doFfi x
+ | EApp ((EFfi (m, s), _), e) => doFfi (m, s, [(e, (TRecord [], loc))])
+
+ | EApp (e1 as (EError _, _), _) => evalExp env e1 k
+
+ | EApp (e1, e2) =>
+ let
+ fun adefault () = (ErrorMsg.errorAt loc "Excessively fancy function call";
+ Print.preface ("Call", MonoPrint.p_exp MonoEnv.empty e);
+ default ())
+
+ fun doArgs (e, args) =
+ case #1 e of
+ EApp (e1, e2) => doArgs (e1, e2 :: args)
+ | ENamed n =>
+ (case IM.find (!rfuns, n) of
+ NONE => adefault ()
+ | SOME rf =>
+ if length (#args rf) <> length args then
+ adefault ()
+ else
+ let
+ val () = (SS.app (St.havocReln o Sql) (#tables rf);
+ SS.app St.havocCookie (#cookies rf))
+ val saved = St.stash ()
+
+ fun doArgs (args, modes, env') =
+ case (args, modes) of
+ ([], []) => (evalExp env' (#body rf) (fn _ => ());
+ St.reinstate saved;
+ default ())
+
+ | (arg :: args, mode :: modes) =>
+ evalExp env arg (fn arg =>
+ let
+ val v = case mode of
+ Arbitrary => Var (St.nextVar ())
+ | Fixed => arg
+ | Decreasing =>
+ let
+ val v = Var (St.nextVar ())
+ in
+ if St.check (AReln (Known, [arg])) then
+ St.assert [(AReln (Known, [v]))]
+ else
+ ();
+ v
+ end
+ in
+ doArgs (args, modes, v :: env')
+ end)
+ | _ => raise Fail "Iflow.doArgs: Impossible"
+ in
+ doArgs (args, #args rf, [])
+ end)
+ | _ => adefault ()
+ in
+ doArgs (e, [])
+ end
+
+ | EAbs _ => default ()
+ | EUnop (s, e1) => evalExp env e1 (fn e1 => k (Func (Other s, [e1])))
+ | EBinop (_, s, e1, e2) => evalExp env e1 (fn e1 => evalExp env e2 (fn e2 => k (Func (Other s, [e1, e2]))))
+ | ERecord xets =>
+ let
+ fun doFields (xes, acc) =
+ case xes of
+ [] => k (Recd (rev acc))
+ | (x, e, _) :: xes =>
+ evalExp env e (fn e => doFields (xes, (x, e) :: acc))
+ in
+ doFields (xets, [])
+ end
+ | EField (e, s) => evalExp env e (fn e => k (Proj (e, s)))
+ | ECase (e, pes, {result = res, ...}) =>
+ evalExp env e (fn e =>
+ if List.all (fn (_, (EWrite (EPrim _, _), _)) => true
+ | _ => false) pes then
+ (St.send (e, loc);
+ k (Recd []))
+ else
+ (St.addPath (e, loc);
+ app (fn (p, pe) =>
+ let
+ val saved = St.stash ()
+ in
+ let
+ val env = evalPat env e p
+ in
+ evalExp env pe k;
+ St.reinstate saved
+ end
+ handle Cc.Contradiction => St.reinstate saved
+ end) pes))
+ | EStrcat (e1, e2) =>
+ evalExp env e1 (fn e1 =>
+ evalExp env e2 (fn e2 =>
+ k (Func (Other "cat", [e1, e2]))))
+ | EError (e, _) => evalExp env e (fn e => St.send (e, loc))
+ | EReturnBlob {blob = NONE, ...} => raise Fail "Iflow doesn't support blob optimization"
+ | EReturnBlob {blob = SOME b, mimeType = m, ...} =>
+ evalExp env b (fn b =>
+ (St.send (b, loc);
+ evalExp env m
+ (fn m => St.send (m, loc))))
+ | ERedirect (e, _) =>
+ evalExp env e (fn e => St.send (e, loc))
+ | EWrite e =>
+ evalExp env e (fn e => (St.send (e, loc);
+ k (Recd [])))
+ | ESeq (e1, e2) =>
+ let
+ val path = St.stashPath ()
+ in
+ evalExp env e1 (fn _ => (St.reinstatePath path; evalExp env e2 k))
+ end
+ | ELet (_, _, e1, e2) =>
+ evalExp env e1 (fn e1 => evalExp (e1 :: env) e2 k)
+ | EClosure (n, es) =>
+ let
+ fun doArgs (es, acc) =
+ case es of
+ [] => k (Func (Other ("Cl" ^ Int.toString n), rev acc))
+ | e :: es =>
+ evalExp env e (fn e => doArgs (es, e :: acc))
+ in
+ doArgs (es, [])
+ end
+
+ | EQuery {query = q, body = b, initial = i, state = state, ...} =>
+ evalExp env i (fn i =>
+ let
+ val r = Var (St.nextVar ())
+ val acc = Var (St.nextVar ())
+
+ val (ts, cs) = MonoUtil.Exp.fold {typ = fn (_, st) => st,
+ exp = fn (e, st as (cs, ts)) =>
+ case e of
+ EDml (e, _) =>
+ (case parse dml e of
+ NONE => st
+ | SOME c =>
+ case c of
+ Insert _ => st
+ | Delete (tab, _) =>
+ (cs, SS.add (ts, tab))
+ | Update (tab, _, _) =>
+ (cs, SS.add (ts, tab)))
+ | EFfiApp ("Basis", "set_cookie",
+ [_, ((EPrim (Prim.String (_, cname)), _), _),
+ _, _, _]) =>
+ (SS.add (cs, cname), ts)
+ | _ => st}
+ (SS.empty, SS.empty) b
+ in
+ case (#1 state, SS.isEmpty ts, SS.isEmpty cs) of
+ (TRecord [], true, true) => ()
+ | _ =>
+ let
+ val saved = St.stash ()
+ in
+ (k i)
+ handle Cc.Contradiction => ();
+ St.reinstate saved
+ end;
+
+ SS.app (St.havocReln o Sql) ts;
+ SS.app St.havocCookie cs;
+
+ doQuery {Env = env,
+ NextVar = Var o St.nextVar,
+ Add = fn a => St.assert [a],
+ Save = St.stash,
+ Restore = St.reinstate,
+ Cont = AllCols (fn x =>
+ (St.assert [AReln (Cmp Eq, [r, x])];
+ evalExp (acc :: r :: env) b k))} q
+ end)
+ | EDml (e, _) =>
+ (case parse dml e of
+ NONE => (print ("Warning: Information flow checker can't parse DML command at "
+ ^ ErrorMsg.spanToString loc ^ "\n");
+ default ())
+ | SOME d =>
+ case d of
+ Insert (tab, es) =>
+ let
+ val new = St.nextVar ()
+
+ val expIn = expIn (Var o St.nextVar) env
+ (fn _ => raise Fail "Iflow.evalExp: Bad field expression in INSERT [1]")
+
+ val es = map (fn (x, e) =>
+ case expIn e of
+ inl e => (x, e)
+ | inr _ => raise Fail "Iflow.evalExp: Bad field expression in INSERT [2]")
+ es
+
+ val saved = St.stash ()
+ in
+ St.assert [AReln (Sql (tab ^ "$New"), [Recd es])];
+ St.insert loc;
+ St.reinstate saved;
+ St.assert [AReln (Sql tab, [Recd es])];
+ k (Recd [])
+ end
+ | Delete (tab, e) =>
+ let
+ val old = St.nextVar ()
+
+ val expIn = expIn (Var o St.nextVar) env
+ (fn "T" => Var old
+ | _ => raise Fail "Iflow.evalExp: Bad field expression in DELETE")
+
+ val p = case expIn e of
+ inl e => raise Fail "Iflow.evalExp: DELETE with non-boolean"
+ | inr p => p
+
+ val saved = St.stash ()
+ in
+ St.assert [AReln (Sql (tab ^ "$Old"), [Var old]),
+ AReln (Sql (tab), [Var old])];
+ decomp {Save = St.stash,
+ Restore = St.reinstate,
+ Add = fn a => St.assert [a]} p
+ (fn () => (St.delete loc;
+ St.reinstate saved;
+ St.havocReln (Sql tab);
+ k (Recd []))
+ handle Cc.Contradiction => ())
+ end
+ | Update (tab, fs, e) =>
+ let
+ val new = St.nextVar ()
+ val old = St.nextVar ()
+
+ val expIn = expIn (Var o St.nextVar) env
+ (fn "T" => Var old
+ | _ => raise Fail "Iflow.evalExp: Bad field expression in UPDATE")
+
+ val fs = map
+ (fn (x, e) =>
+ (x, case expIn e of
+ inl e => e
+ | inr _ => raise Fail
+ ("Iflow.evalExp: Selecting "
+ ^ "boolean expression")))
+ fs
+
+ val fs' = case SM.find (!tabs, tab) of
+ NONE => raise Fail "Iflow.evalExp: Updating unknown table"
+ | SOME (fs', _) => fs'
+
+ val fs = foldl (fn (f, fs) =>
+ if List.exists (fn (f', _) => f' = f) fs then
+ fs
+ else
+ (f, Proj (Var old, f)) :: fs) fs fs'
+
+ val p = case expIn e of
+ inl e => raise Fail "Iflow.evalExp: UPDATE with non-boolean"
+ | inr p => p
+ val saved = St.stash ()
+ in
+ St.assert [AReln (Sql (tab ^ "$New"), [Recd fs]),
+ AReln (Sql (tab ^ "$Old"), [Var old]),
+ AReln (Sql tab, [Var old])];
+ decomp {Save = St.stash,
+ Restore = St.reinstate,
+ Add = fn a => St.assert [a]} p
+ (fn () => (St.update loc;
+ St.reinstate saved;
+ St.havocReln (Sql tab);
+ k (Recd []))
+ handle Cc.Contradiction => ())
+ end)
+
+ | ENextval (EPrim (Prim.String (_, seq)), _) =>
+ let
+ val nv = St.nextVar ()
+ in
+ St.assert [AReln (Sql (String.extract (seq, 3, NONE)), [Var nv])];
+ k (Var nv)
+ end
+ | ENextval _ => default ()
+ | ESetval _ => default ()
+
+ | EUnurlify ((EFfiApp ("Basis", "get_cookie", [((EPrim (Prim.String (_, cname)), _), _)]), _), _, _) =>
+ let
+ val e = Var (St.nextVar ())
+ val e' = Func (Other ("cookie/" ^ cname), [])
+ in
+ St.assert [AReln (Known, [e]), AReln (Cmp Eq, [e, e'])];
+ k e
+ end
+
+ | EUnurlify _ => default ()
+ | EJavaScript _ => default ()
+ | ESignalReturn _ => default ()
+ | ESignalBind _ => default ()
+ | ESignalSource _ => default ()
+ | EServerCall _ => default ()
+ | ERecv _ => default ()
+ | ESleep _ => default ()
+ | ESpawn _ => default ()
+ end
+
+datatype var_source = Input of int | SubInput of int | Unknown
+
+structure U = MonoUtil
+
+fun mliftExpInExp by =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn bound => fn e =>
+ case e of
+ ERel xn =>
+ if xn < bound then
+ e
+ else
+ ERel (xn + by)
+ | _ => e,
+ bind = fn (bound, U.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+
+fun nameSubexps k (e : Mono.exp) =
+ let
+ fun numParams (e : Mono.exp) =
+ case #1 e of
+ EStrcat (e1, e2) => numParams e1 + numParams e2
+ | EPrim (Prim.String _) => 0
+ | _ => 1
+
+ val nps = numParams e
+
+ fun getParams (e : Mono.exp) x =
+ case #1 e of
+ EStrcat (e1, e2) =>
+ let
+ val (ps1, e1') = getParams e1 x
+ val (ps2, e2') = getParams e2 (x - length ps1)
+ in
+ (ps2 @ ps1, (EStrcat (e1', e2'), #2 e))
+ end
+ | EPrim (Prim.String _) => ([], e)
+ | _ =>
+ let
+ val (e', k) =
+ case #1 e of
+ EFfiApp (m, f, [(e', t)]) =>
+ if Settings.isEffectful (m, f) orelse Settings.isBenignEffectful (m, f) then
+ (e, fn x => x)
+ else
+ (e', fn e' => (EFfiApp (m, f, [(e', t)]), #2 e))
+ | ECase (e', ps as
+ [((PCon (_, PConFfi {mod = "Basis", con = "True", ...}, NONE), _),
+ (EPrim (Prim.String (_, "TRUE")), _)),
+ ((PCon (_, PConFfi {mod = "Basis", con = "False", ...}, NONE), _),
+ (EPrim (Prim.String (_, "FALSE")), _))], q) =>
+ (e', fn e' => (ECase (e', ps, q), #2 e))
+ | _ => (e, fn x => x)
+ in
+ ([e'], k (ERel x, #2 e))
+ end
+
+ val (ps, e') = getParams e (nps - 1)
+
+ val string = (TFfi ("Basis", "string"), #2 e)
+
+ val (e', _) = foldl (fn (p, (e', liftBy)) =>
+ ((ELet ("p" ^ Int.toString liftBy,
+ string,
+ mliftExpInExp liftBy 0 p,
+ e'), #2 e), liftBy - 1)) (k (nps, e'), nps - 1) ps
+ in
+ #1 e'
+ end
+
+val namer = MonoUtil.File.map {typ = fn t => t,
+ exp = fn e =>
+ case e of
+ EDml (e, fm) =>
+ nameSubexps (fn (_, e') => (EDml (e', fm), #2 e)) e
+ | EQuery {exps, tables, state, query, body, initial} =>
+ nameSubexps (fn (liftBy, e') =>
+ (EQuery {exps = exps,
+ tables = tables,
+ state = state,
+ query = e',
+ body = mliftExpInExp liftBy 2 body,
+ initial = mliftExpInExp liftBy 0 initial},
+ #2 query)) query
+ | _ => e,
+ decl = fn d => d}
+
+fun check (file : file) =
+ let
+ val () = (St.reset ();
+ rfuns := IM.empty)
+
+ (*val () = Print.preface ("FilePre", MonoPrint.p_file MonoEnv.empty file)*)
+ val file = MonoReduce.reduce file
+ val file = MonoOpt.optimize file
+ val file = Fuse.fuse file
+ val file = MonoOpt.optimize file
+ val file = MonoShake.shake file
+ val file = namer file
+ (*val () = Print.preface ("File", MonoPrint.p_file MonoEnv.empty file)*)
+
+ val exptd = foldl (fn ((d, _), exptd) =>
+ case d of
+ DExport (_, _, n, _, _, _) => IS.add (exptd, n)
+ | _ => exptd) IS.empty (#1 file)
+
+ fun decl (d, loc) =
+ case d of
+ DTable (tab, fs, pk, _) =>
+ let
+ val ks =
+ case #1 pk of
+ EPrim (Prim.String (_, s)) =>
+ (case String.tokens (fn ch => ch = #"," orelse ch = #" ") s of
+ [] => []
+ | pk => [pk])
+ | _ => []
+ in
+ if size tab >= 3 then
+ tabs := SM.insert (!tabs, String.extract (tab, 3, NONE),
+ (map #1 fs,
+ map (map (fn s => str (Char.toUpper (String.sub (s, 3)))
+ ^ String.extract (s, 4, NONE))) ks))
+ else
+ raise Fail "Table name does not begin with uw_"
+ end
+ | DVal (x, n, _, e, _) =>
+ let
+ (*val () = print ("\n=== " ^ x ^ " ===\n\n");*)
+
+ val isExptd = IS.member (exptd, n)
+
+ val saved = St.stash ()
+
+ fun deAbs (e, env, ps) =
+ case #1 e of
+ EAbs (_, _, _, e) =>
+ let
+ val nv = Var (St.nextVar ())
+ in
+ deAbs (e, nv :: env,
+ if isExptd then
+ AReln (Known, [nv]) :: ps
+ else
+ ps)
+ end
+ | _ => (e, env, ps)
+
+ val (e, env, ps) = deAbs (e, [], [])
+ in
+ St.assert ps;
+ (evalExp env e (fn _ => ()) handle Cc.Contradiction => ());
+ St.reinstate saved
+ end
+
+ | DValRec [(x, n, _, e, _)] =>
+ let
+ val tables = ref SS.empty
+ val cookies = ref SS.empty
+
+ fun deAbs (e, env, modes) =
+ case #1 e of
+ EAbs (_, _, _, e) => deAbs (e, Input (length env) :: env, ref Fixed :: modes)
+ | _ => (e, env, rev modes)
+
+ val (e, env, modes) = deAbs (e, [], [])
+
+ fun doExp env (e as (_, loc)) =
+ case #1 e of
+ EPrim _ => e
+ | ERel _ => e
+ | ENamed _ => e
+ | ECon (_, _, NONE) => e
+ | ECon (dk, pc, SOME e) => (ECon (dk, pc, SOME (doExp env e)), loc)
+ | ENone _ => e
+ | ESome (t, e) => (ESome (t, doExp env e), loc)
+ | EFfi _ => e
+ | EFfiApp (m, f, es) =>
+ (case (m, f, es) of
+ ("Basis", "set_cookie", [_, ((EPrim (Prim.String (_, cname)), _), _), _, _, _]) =>
+ cookies := SS.add (!cookies, cname)
+ | _ => ();
+ (EFfiApp (m, f, map (fn (e, t) => (doExp env e, t)) es), loc))
+
+ | EApp (e1, e2) =>
+ let
+ fun default () = (EApp (doExp env e1, doExp env e2), loc)
+
+ fun explore (e, args) =
+ case #1 e of
+ EApp (e1, e2) => explore (e1, e2 :: args)
+ | ENamed n' =>
+ if n' = n then
+ let
+ fun doArgs (pos, args, modes) =
+ case (args, modes) of
+ ((e1, _) :: args, m1 :: modes) =>
+ (case e1 of
+ ERel n =>
+ (case List.nth (env, n) of
+ Input pos' =>
+ if pos' = pos then
+ ()
+ else
+ m1 := Arbitrary
+ | SubInput pos' =>
+ if pos' = pos then
+ if !m1 = Arbitrary then
+ ()
+ else
+ m1 := Decreasing
+ else
+ m1 := Arbitrary
+ | Unknown => m1 := Arbitrary)
+ | _ => m1 := Arbitrary;
+ doArgs (pos + 1, args, modes))
+ | (_ :: _, []) => ()
+ | ([], ms) => app (fn m => m := Arbitrary) ms
+ in
+ doArgs (0, args, modes);
+ (EFfi ("Basis", "?"), loc)
+ end
+ else
+ default ()
+ | _ => default ()
+ in
+ explore (e, [])
+ end
+ | EAbs (x, t1, t2, e) => (EAbs (x, t1, t2, doExp (Unknown :: env) e), loc)
+ | EUnop (uo, e1) => (EUnop (uo, doExp env e1), loc)
+ | EBinop (bi, bo, e1, e2) => (EBinop (bi, bo, doExp env e1, doExp env e2), loc)
+ | ERecord xets => (ERecord (map (fn (x, e, t) => (x, doExp env e, t)) xets), loc)
+ | EField (e1, f) => (EField (doExp env e1, f), loc)
+ | ECase (e, pes, ts) =>
+ let
+ val source =
+ case #1 e of
+ ERel n =>
+ (case List.nth (env, n) of
+ Input n => SOME n
+ | SubInput n => SOME n
+ | Unknown => NONE)
+ | _ => NONE
+
+ fun doV v =
+ let
+ fun doPat (p, env) =
+ case #1 p of
+ PVar _ => v :: env
+ | PPrim _ => env
+ | PCon (_, _, NONE) => env
+ | PCon (_, _, SOME p) => doPat (p, env)
+ | PRecord xpts => foldl (fn ((_, p, _), env) => doPat (p, env)) env xpts
+ | PNone _ => env
+ | PSome (_, p) => doPat (p, env)
+ in
+ (ECase (e, map (fn (p, e) => (p, doExp (doPat (p, env)) e)) pes, ts), loc)
+ end
+ in
+ case source of
+ NONE => doV Unknown
+ | SOME inp => doV (SubInput inp)
+ end
+ | EStrcat (e1, e2) => (EStrcat (doExp env e1, doExp env e2), loc)
+ | EError (e1, t) => (EError (doExp env e1, t), loc)
+ | EReturnBlob {blob = NONE, mimeType = m, t} =>
+ (EReturnBlob {blob = NONE, mimeType = doExp env m, t = t}, loc)
+ | EReturnBlob {blob = SOME b, mimeType = m, t} =>
+ (EReturnBlob {blob = SOME (doExp env b), mimeType = doExp env m, t = t}, loc)
+ | ERedirect (e1, t) => (ERedirect (doExp env e1, t), loc)
+ | EWrite e1 => (EWrite (doExp env e1), loc)
+ | ESeq (e1, e2) => (ESeq (doExp env e1, doExp env e2), loc)
+ | ELet (x, t, e1, e2) => (ELet (x, t, doExp env e1, doExp (Unknown :: env) e2), loc)
+ | EClosure (n, es) => (EClosure (n, map (doExp env) es), loc)
+ | EQuery {exps, tables, state, query, body, initial} =>
+ (EQuery {exps = exps, tables = tables, state = state,
+ query = doExp env query,
+ body = doExp (Unknown :: Unknown :: env) body,
+ initial = doExp env initial}, loc)
+ | EDml (e1, mode) =>
+ (case parse dml e1 of
+ NONE => ()
+ | SOME c =>
+ case c of
+ Insert _ => ()
+ | Delete (tab, _) =>
+ tables := SS.add (!tables, tab)
+ | Update (tab, _, _) =>
+ tables := SS.add (!tables, tab);
+ (EDml (doExp env e1, mode), loc))
+ | ENextval e1 => (ENextval (doExp env e1), loc)
+ | ESetval (e1, e2) => (ESetval (doExp env e1, doExp env e2), loc)
+ | EUnurlify (e1, t, b) => (EUnurlify (doExp env e1, t, b), loc)
+ | EJavaScript (m, e) => (EJavaScript (m, doExp env e), loc)
+ | ESignalReturn _ => e
+ | ESignalBind _ => e
+ | ESignalSource _ => e
+ | EServerCall _ => e
+ | ERecv _ => e
+ | ESleep _ => e
+ | ESpawn _ => e
+
+ val e = doExp env e
+ in
+ rfuns := IM.insert (!rfuns, n, {tables = !tables, cookies = !cookies,
+ args = map (fn r => !r) modes, body = e})
+ end
+
+ | DValRec _ => ErrorMsg.errorAt loc "Iflow can't check mutually-recursive functions yet."
+
+ | DPolicy pol =>
+ let
+ val rvN = ref 0
+ fun rv () =
+ let
+ val n = !rvN
+ in
+ rvN := n + 1;
+ Lvar n
+ end
+
+ val atoms = ref ([] : atom list)
+ fun doQ k = doQuery {Env = [],
+ NextVar = rv,
+ Add = fn a => atoms := a :: !atoms,
+ Save = fn () => !atoms,
+ Restore = fn ls => atoms := ls,
+ Cont = SomeCol (fn r => k (rev (!atoms), r))}
+
+ fun untab (tab, nams) = List.filter (fn AReln (Sql tab', [Lvar lv]) =>
+ tab' <> tab
+ orelse List.all (fn Lvar lv' => lv' <> lv
+ | _ => false) nams
+ | _ => true)
+ in
+ case pol of
+ PolClient e =>
+ doQ (fn (ats, {Outs = es, ...}) => St.allowSend (ats, es)) e
+ | PolInsert e =>
+ doQ (fn (ats, {New = SOME (tab, new), ...}) =>
+ St.allowInsert (AReln (Sql (tab ^ "$New"), [new]) :: untab (tab, [new]) ats)
+ | _ => raise Fail "Iflow: No New in mayInsert policy") e
+ | PolDelete e =>
+ doQ (fn (ats, {Old = SOME (tab, old), ...}) =>
+ St.allowDelete (AReln (Sql (tab ^ "$Old"), [old]) :: untab (tab, [old]) ats)
+ | _ => raise Fail "Iflow: No Old in mayDelete policy") e
+ | PolUpdate e =>
+ doQ (fn (ats, {New = SOME (tab, new), Old = SOME (_, old), ...}) =>
+ St.allowUpdate (AReln (Sql (tab ^ "$Old"), [old])
+ :: AReln (Sql (tab ^ "$New"), [new])
+ :: untab (tab, [new, old]) ats)
+ | _ => raise Fail "Iflow: No New or Old in mayUpdate policy") e
+ | PolSequence e =>
+ (case #1 e of
+ EPrim (Prim.String (_, seq)) =>
+ let
+ val p = AReln (Sql (String.extract (seq, 3, NONE)), [Lvar 0])
+ val outs = [Lvar 0]
+ in
+ St.allowSend ([p], outs)
+ end
+ | _ => ())
+ end
+
+ | _ => ()
+ in
+ app decl (#1 file)
+ end
+
+val check = fn file =>
+ let
+ val oldInline = Settings.getMonoInline ()
+ val oldFull = !MonoReduce.fullMode
+ in
+ (Settings.setMonoInline (case Int.maxInt of
+ NONE => 1000000
+ | SOME n => n);
+ MonoReduce.fullMode := true;
+ check file;
+ Settings.setMonoInline oldInline)
+ handle ex => (Settings.setMonoInline oldInline;
+ MonoReduce.fullMode := oldFull;
+ raise ex)
+ end
+
+end
diff --git a/src/jscomp.sig b/src/jscomp.sig
new file mode 100644
index 0000000..5b8723b
--- /dev/null
+++ b/src/jscomp.sig
@@ -0,0 +1,36 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature JSCOMP = sig
+
+ val process : Mono.file -> Mono.file
+
+ val explainEmbed : bool ref
+ (* Output verbose error messages about inability to embed server-side
+ * values in client-side code? *)
+
+end
diff --git a/src/jscomp.sml b/src/jscomp.sml
new file mode 100644
index 0000000..dedcb55
--- /dev/null
+++ b/src/jscomp.sml
@@ -0,0 +1,1369 @@
+(* Copyright (c) 2008-2013, 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)
+
+val explainEmbed = ref false
+
+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 (Prim.Normal, "")), 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 : 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) (#1 file)
+
+ fun str loc s = (EPrim (Prim.String (Prim.Normal, 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)
+
+ | _ => (if !explainEmbed then
+ Print.prefaces "Can't embed" [("loc", Print.PD.string (ErrorMsg.spanToString loc)),
+ ("e", MonoPrint.p_exp MonoEnv.empty e),
+ ("t", MonoPrint.p_typ MonoEnv.empty t)]
+ else
+ ();
+ 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 () = Print.prefaces "jsExp" [("e", MonoPrint.p_exp MonoEnv.empty e),
+ ("loc", Print.PD.string (ErrorMsg.spanToString loc))]*)
+
+ 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
+ PVar _ => str "{/*hoho*/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, loc)) =
+ 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 ^ "\""
+ | _ => (ErrorMsg.errorAt loc "Unexpected non-constant JavaScript code";
+ Print.prefaces "deStrcat" [("e", MonoPrint.p_exp MonoEnv.empty all)];
+ "")
+
+ 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"
+ | "powl" => "pow"
+ | "powf" => "pow"
+ | _ => 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, fm) =>
+ let
+ val (e, st) = jsE inner (e, st)
+ val (unurl, st) = unurlifyExp loc (t, st)
+ val lastArg = case fm of
+ None => "null"
+ | Error =>
+ let
+ val isN = if isNullable t then
+ "true"
+ else
+ "false"
+ in
+ "cons({c:\"c\",v:" ^ isN ^ "},null)"
+ end
+ 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")
+ ^ "}," ^ lastArg ^ ")))))}")],
+ 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
+ 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 = NONE, mimeType, t} =>
+ let
+ val (mimeType, st) = exp outer (mimeType, st)
+ in
+ ((EReturnBlob {blob = NONE, mimeType = mimeType, t = t}, loc), st)
+ end
+ | EReturnBlob {blob = SOME blob, mimeType, t} =>
+ let
+ val (blob, st) = exp outer (blob, st)
+ val (mimeType, st) = exp outer (mimeType, st)
+ in
+ ((EReturnBlob {blob = SOME 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, fm) =>
+ let
+ val (e1, st) = exp outer (e1, st)
+ in
+ ((EServerCall (e1, t, ef, fm), 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}
+ (#1 file)
+
+ val inf = FileIO.txtOpenIn (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
+ String.concatWith "" ((lines ^ urlRules ^ String.concat (rev (#script st))
+ ^ "\ntime_format = \"" ^ Prim.toCString (Settings.getTimeFormat ()) ^ "\";\n")
+ :: map (fn r => "\n// " ^ #Filename r ^ "\n\n" ^ #Content r ^ "\n") (Settings.listJsFiles ()))
+ else
+ ""
+ in
+ TextIO.closeIn inf;
+ ((DJavaScript script, ErrorMsg.dummySpan) :: ds, #2 file)
+ end
+
+end
diff --git a/src/list_key_fn.sml b/src/list_key_fn.sml
new file mode 100644
index 0000000..ec2bd26
--- /dev/null
+++ b/src/list_key_fn.sml
@@ -0,0 +1,14 @@
+functor ListKeyFn(K : ORD_KEY)
+ : ORD_KEY where type ord_key = K.ord_key list = struct
+
+type ord_key = K.ord_key list
+
+val rec compare =
+ fn ([], []) => EQUAL
+ | ([], _) => LESS
+ | (_, []) => GREATER
+ | (x::xs, y::ys) => case K.compare (x, y) of
+ EQUAL => compare (xs, ys)
+ | ord => ord
+
+end
diff --git a/src/list_util.sig b/src/list_util.sig
new file mode 100644
index 0000000..6e1cd5a
--- /dev/null
+++ b/src/list_util.sig
@@ -0,0 +1,59 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature LIST_UTIL = sig
+
+ val mapConcat : ('a -> 'b list) -> 'a list -> 'b list
+
+ val mapfold : ('data, 'state, 'abort) Search.mapfolder
+ -> ('data list, 'state, 'abort) Search.mapfolder
+ val mapfoldB : ('context * 'data -> 'context * ('state -> ('data * 'state, 'abort) Search.result))
+ -> ('context, 'data list, 'state, 'abort) Search.mapfolderB
+
+ val foldlMap : ('data1 * 'state -> 'data2 * 'state) -> 'state -> 'data1 list -> 'data2 list * 'state
+ val foldlMapPartial : ('data1 * 'state -> 'data2 option * 'state) -> 'state -> 'data1 list -> 'data2 list * 'state
+ val foldlMapiPartial : (int * 'data1 * 'state -> 'data2 option * 'state)
+ -> 'state -> 'data1 list -> 'data2 list * 'state
+ val foldlMapConcat : ('data1 * 'state -> 'data2 list * 'state) -> 'state -> 'data1 list -> 'data2 list * 'state
+ val foldlMapAbort : ('data1 * 'state -> ('data2 * 'state) option)
+ -> 'state -> 'data1 list -> ('data2 list * 'state) option
+
+ val search : ('a -> 'b option) -> 'a list -> 'b option
+ val searchi : (int * 'a -> 'b option) -> 'a list -> 'b option
+
+ val mapi : (int * 'a -> 'b) -> 'a list -> 'b list
+ val mapiPartial : (int * 'a -> 'b option) -> 'a list -> 'b list
+ val foldli : (int * 'a * 'b -> 'b) -> 'b -> 'a list -> 'b
+ val foldri : (int * 'a * 'b -> 'b) -> 'b -> 'a list -> 'b
+
+ val foldliMap : (int * 'data1 * 'state -> 'data2 * 'state) -> 'state -> 'data1 list -> 'data2 list * 'state
+
+ val appi : (int * 'a -> unit) -> 'a list -> unit
+
+ val appn : (int -> unit) -> int -> unit
+
+end
diff --git a/src/list_util.sml b/src/list_util.sml
new file mode 100644
index 0000000..03c9549
--- /dev/null
+++ b/src/list_util.sml
@@ -0,0 +1,260 @@
+(* Copyright (c) 2008, 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 ListUtil :> LIST_UTIL = struct
+
+structure S = Search
+
+fun mapConcat f =
+ let
+ fun mc acc ls =
+ case ls of
+ [] => rev acc
+ | h :: t => mc (List.revAppend (f h, acc)) t
+ in
+ mc []
+ end
+
+fun mapfold f =
+ let
+ fun mf ls s =
+ case ls of
+ nil => S.Continue (nil, s)
+ | h :: t =>
+ case f h s of
+ S.Return x => S.Return x
+ | S.Continue (h', s) =>
+ case mf t s of
+ S.Return x => S.Return x
+ | S.Continue (t', s) => S.Continue (h' :: t', s)
+ in
+ mf
+ end
+
+fun mapfoldB f =
+ let
+ fun mf ctx ls s =
+ case ls of
+ nil => S.Continue (nil, s)
+ | h :: t =>
+ let
+ val (ctx, r) = f (ctx, h)
+ in
+ case r s of
+ S.Return x => S.Return x
+ | S.Continue (h', s) =>
+ case mf ctx t s of
+ S.Return x => S.Return x
+ | S.Continue (t', s) => S.Continue (h' :: t', s)
+ end
+ in
+ mf
+ end
+
+fun foldlMap f s =
+ let
+ fun fm (ls', s) ls =
+ case ls of
+ nil => (rev ls', s)
+ | h :: t =>
+ let
+ val (h', s') = f (h, s)
+ in
+ fm (h' :: ls', s') t
+ end
+ in
+ fm ([], s)
+ end
+
+fun foldlMapConcat f s =
+ let
+ fun fm (ls', s) ls =
+ case ls of
+ nil => (rev ls', s)
+ | h :: t =>
+ let
+ val (h', s') = f (h, s)
+ in
+ fm (List.revAppend (h', ls'), s') t
+ end
+ in
+ fm ([], s)
+ end
+
+fun foldlMapPartial f s =
+ let
+ fun fm (ls', s) ls =
+ case ls of
+ nil => (rev ls', s)
+ | h :: t =>
+ let
+ val (h', s') = f (h, s)
+ val ls' = case h' of
+ NONE => ls'
+ | SOME h' => h' :: ls'
+ in
+ fm (ls', s') t
+ end
+ in
+ fm ([], s)
+ end
+
+fun foldlMapiPartial f s =
+ let
+ fun fm (n, ls', s) ls =
+ case ls of
+ nil => (rev ls', s)
+ | h :: t =>
+ let
+ val (h', s') = f (n, h, s)
+ val ls' = case h' of
+ NONE => ls'
+ | SOME h' => h' :: ls'
+ in
+ fm (n + 1, ls', s') t
+ end
+ in
+ fm (0, [], s)
+ end
+
+fun foldlMapAbort f s =
+ let
+ fun fm (ls', s) ls =
+ case ls of
+ nil => SOME (rev ls', s)
+ | h :: t =>
+ case f (h, s) of
+ NONE => NONE
+ | SOME (h', s') => fm (h' :: ls', s') t
+ in
+ fm ([], s)
+ end
+
+fun search f =
+ let
+ fun s ls =
+ case ls of
+ [] => NONE
+ | h :: t =>
+ case f h of
+ NONE => s t
+ | v => v
+ in
+ s
+ end
+
+fun searchi f =
+ let
+ fun s n ls =
+ case ls of
+ [] => NONE
+ | h :: t =>
+ case f (n, h) of
+ NONE => s (n + 1) t
+ | v => v
+ in
+ s 0
+ end
+
+fun mapi f =
+ let
+ fun m i acc ls =
+ case ls of
+ [] => rev acc
+ | h :: t => m (i + 1) (f (i, h) :: acc) t
+ in
+ m 0 []
+ end
+
+fun mapiPartial f =
+ let
+ fun m i acc ls =
+ case ls of
+ [] => rev acc
+ | h :: t =>
+ m (i + 1) (case f (i, h) of
+ NONE => acc
+ | SOME v => v :: acc) t
+ in
+ m 0 []
+ end
+
+fun appi f =
+ let
+ fun m i ls =
+ case ls of
+ [] => ()
+ | h :: t => (f (i, h); m (i + 1) t)
+ in
+ m 0
+ end
+
+fun foldli f =
+ let
+ fun m i acc ls =
+ case ls of
+ [] => acc
+ | h :: t => m (i + 1) (f (i, h, acc)) t
+ in
+ m 0
+ end
+
+fun foldri f i ls =
+ let
+ val len = length ls
+ in
+ foldli (fn (n, x, s) => f (len - n - 1, x, s)) i (rev ls)
+ end
+
+fun foldliMap f s =
+ let
+ fun fm (n, ls', s) ls =
+ case ls of
+ nil => (rev ls', s)
+ | h :: t =>
+ let
+ val (h', s') = f (n, h, s)
+ in
+ fm (n + 1, h' :: ls', s') t
+ end
+ in
+ fm (0, [], s)
+ end
+
+fun appn f n =
+ let
+ fun iter m =
+ if m >= n then
+ ()
+ else
+ (f m;
+ iter (m + 1))
+ in
+ iter 0
+ end
+
+end
diff --git a/src/lru_cache.sml b/src/lru_cache.sml
new file mode 100644
index 0000000..f582bf6
--- /dev/null
+++ b/src/lru_cache.sml
@@ -0,0 +1,207 @@
+structure LruCache : sig
+ val cache : Cache.cache
+end = struct
+
+
+(* Mono *)
+
+open Mono
+
+val dummyLoc = ErrorMsg.dummySpan
+val stringTyp = (TFfi ("Basis", "string"), dummyLoc)
+val optionStringTyp = (TOption stringTyp, dummyLoc)
+fun withTyp typ = map (fn exp => (exp, typ))
+
+fun ffiAppCache' (func, index, argTyps) =
+ EFfiApp ("Sqlcache", func ^ Int.toString index, argTyps)
+
+fun check (index, keys) =
+ ffiAppCache' ("check", index, withTyp stringTyp keys)
+
+fun store (index, keys, value) =
+ ffiAppCache' ("store", index, (value, stringTyp) :: withTyp stringTyp keys)
+
+fun flush (index, keys) =
+ ffiAppCache' ("flush", index, withTyp optionStringTyp keys)
+
+fun lock (index, write) =
+ ffiAppCache' ((if write then "w" else "r") ^ "lock", index, [])
+
+
+(* Cjr *)
+
+open Print
+open Print.PD
+
+fun setupQuery {index, params} =
+ let
+
+ val i = Int.toString index
+
+ fun paramRepeat itemi sep =
+ let
+ fun f n =
+ if n < 0 then ""
+ else if n = 0 then itemi (Int.toString 0)
+ else f (n-1) ^ sep ^ itemi (Int.toString n)
+ in
+ f (params - 1)
+ end
+
+ fun paramRepeatRev itemi sep =
+ let
+ fun f n =
+ if n < 0 then ""
+ else if n = 0 then itemi (Int.toString 0)
+ else itemi (Int.toString n) ^ sep ^ f (n-1)
+ in
+ f (params - 1)
+ end
+
+ fun paramRepeatInit itemi sep =
+ if params = 0 then "" else sep ^ paramRepeat itemi sep
+
+ val typedArgs = paramRepeatInit (fn p => "uw_Basis_string p" ^ p) ", "
+
+ val revArgs = paramRepeatRev (fn p => "p" ^ p) ", "
+
+ val argNums = List.tabulate (params, fn i => "p" ^ Int.toString i)
+ in
+ Print.box
+ [string ("static uw_Sqlcache_Cache cacheStruct" ^ i ^ " = {"),
+ newline,
+ string " .lockIn = PTHREAD_RWLOCK_INITIALIZER,",
+ newline,
+ string " .lockOut = PTHREAD_RWLOCK_INITIALIZER,",
+ newline,
+ string " .table = NULL,",
+ newline,
+ string (" .numKeys = " ^ Int.toString params ^ ","),
+ newline,
+ string " .timeInvalid = 0,",
+ newline,
+ string " .timeNow = 0};",
+ newline,
+ string ("static uw_Sqlcache_Cache *cache" ^ i ^ " = &cacheStruct" ^ i ^ ";"),
+ newline,
+ newline,
+
+ string ("static void uw_Sqlcache_rlock" ^ i ^ "(uw_context ctx) {"),
+ newline,
+ string (" uw_Sqlcache_rlock(ctx, cache" ^ i ^ ");"),
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string ("static void uw_Sqlcache_wlock" ^ i ^ "(uw_context ctx) {"),
+ newline,
+ string (" uw_Sqlcache_wlock(ctx, cache" ^ i ^ ");"),
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string ("static uw_Basis_string uw_Sqlcache_check" ^ i),
+ string ("(uw_context ctx" ^ typedArgs ^ ") {"),
+ newline,
+ string (" char *ks[] = {" ^ revArgs ^ "};"),
+ newline,
+ string (" uw_Sqlcache_Value *v = uw_Sqlcache_check(ctx, cache" ^ i ^ ", ks);"),
+ newline,
+ (* If the output is null, it means we had too much recursion, so it's a miss. *)
+ string " if (v && v->output != NULL) {",
+ newline,
+ (*string (" puts(\"SQLCACHE: hit " ^ i ^ ".\");"),
+ newline,*)
+ string " uw_write(ctx, v->output);",
+ newline,
+ string " uw_write_script(ctx, v->scriptOutput);",
+ newline,
+ string " return v->result;",
+ newline,
+ string " } else {",
+ newline,
+ (*string (" printf(\"SQLCACHE: miss " ^ i ^ " " ^ String.concatWith ", " (List.tabulate (params, fn _ => "%s")) ^ ".\\n\""),
+ (case argNums of
+ [] => Print.box []
+ | _ => Print.box [string ", ",
+ p_list string argNums]),
+ string ");",
+ newline,*)
+ string " uw_recordingStart(ctx);",
+ newline,
+ string " return NULL;",
+ newline,
+ string " }",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string ("static uw_unit uw_Sqlcache_store" ^ i),
+ string ("(uw_context ctx, uw_Basis_string s" ^ typedArgs ^ ") {"),
+ newline,
+ string (" char *ks[] = {" ^ revArgs ^ "};"),
+ newline,
+ string (" uw_Sqlcache_Value *v = malloc(sizeof(uw_Sqlcache_Value));"),
+ newline,
+ string " v->result = strdup(s);",
+ newline,
+ string " v->output = uw_recordingRead(ctx);",
+ newline,
+ string " v->scriptOutput = uw_recordingReadScript(ctx);",
+ newline,
+ (*string (" puts(\"SQLCACHE: stored " ^ i ^ ".\");"),
+ newline,*)
+ string (" uw_Sqlcache_store(ctx, cache" ^ i ^ ", ks, v);"),
+ newline,
+ string " return uw_unit_v;",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string ("static uw_unit uw_Sqlcache_flush" ^ i),
+ string ("(uw_context ctx" ^ typedArgs ^ ") {"),
+ newline,
+ string (" char *ks[] = {" ^ revArgs ^ "};"),
+ newline,
+ string (" uw_Sqlcache_flush(ctx, cache" ^ i ^ ", ks);"),
+ newline,
+ (*string (" puts(\"SQLCACHE: flushed " ^ i ^ ".\");"),
+ newline,*)
+ string " return uw_unit_v;",
+ newline,
+ string "}",
+ newline,
+ newline]
+ end
+
+val setupGlobal = string "/* No global setup for LRU cache. */"
+
+
+(* Bundled up. *)
+
+(* For now, use the toy implementation if there are no arguments. *)
+fun toyIfNoKeys numKeys implLru implToy args =
+ if numKeys args = 0
+ then implToy args
+ else implLru args
+
+val cache =
+ (* let *)
+ (* val {check = toyCheck, *)
+ (* store = toyStore, *)
+ (* flush = toyFlush, *)
+ (* setupQuery = toySetupQuery, *)
+ (* ...} = ToyCache.cache *)
+ (* in *)
+ (* {check = toyIfNoKeys (length o #2) check toyCheck, *)
+ (* store = toyIfNoKeys (length o #2) store toyStore, *)
+ (* flush = toyIfNoKeys (length o #2) flush toyFlush, *)
+ {check = check, store = store, flush = flush, lock = lock,
+ setupQuery = setupQuery, setupGlobal = setupGlobal}
+ (* end *)
+
+end
diff --git a/src/main.mlton.sml b/src/main.mlton.sml
new file mode 100644
index 0000000..2caa43f
--- /dev/null
+++ b/src/main.mlton.sml
@@ -0,0 +1,383 @@
+(* Copyright (c) 2008-2012, 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.
+ *)
+
+val socket = ".urweb_daemon"
+
+(* Encapsulate main invocation handler in a function, possibly to be called multiple times within a daemon. *)
+
+exception Code of OS.Process.status
+
+fun oneRun args =
+ let
+ val timing = ref false
+ val tc = ref false
+ val sources = ref ([] : string list)
+ val demo = ref (NONE : (string * bool) option)
+ val tutorial = ref false
+ val css = ref false
+
+ val () = (Compiler.debug := false;
+ Elaborate.verbose := false;
+ Elaborate.dumpTypes := false;
+ Elaborate.dumpTypesOnError := false;
+ Elaborate.unifyMore := false;
+ Compiler.dumpSource := false;
+ Compiler.doIflow := false;
+ Demo.noEmacs := false;
+ Settings.setDebug false)
+
+ val () = Compiler.beforeC := MLton.GC.pack
+
+ fun printVersion () = (print (Config.versionString ^ "\n");
+ raise Code OS.Process.success)
+ fun printNumericVersion () = (print (Config.versionNumber ^ "\n");
+ raise Code OS.Process.success)
+ fun printCCompiler () = (print (Settings.getCCompiler () ^ "\n");
+ raise Code OS.Process.success)
+ fun printCInclude () = (print (Config.includ ^ "\n");
+ raise Code OS.Process.success)
+
+ fun doArgs args =
+ case args of
+ [] => ()
+ | "-version" :: rest =>
+ printVersion ()
+ | "-numeric-version" :: rest =>
+ printNumericVersion ()
+ | "-css" :: rest =>
+ (css := true;
+ doArgs rest)
+ | "-print-ccompiler" :: rest =>
+ printCCompiler ()
+ | "-print-cinclude" :: rest =>
+ printCInclude ()
+ | "-ccompiler" :: ccomp :: rest =>
+ (Settings.setCCompiler ccomp;
+ doArgs rest)
+ | "-demo" :: prefix :: rest =>
+ (demo := SOME (prefix, false);
+ doArgs rest)
+ | "-guided-demo" :: prefix :: rest =>
+ (demo := SOME (prefix, true);
+ doArgs rest)
+ | "-tutorial" :: rest =>
+ (tutorial := true;
+ doArgs rest)
+ | "-protocol" :: name :: rest =>
+ (Settings.setProtocol name;
+ doArgs rest)
+ | "-prefix" :: prefix :: rest =>
+ (Settings.setUrlPrefix prefix;
+ doArgs rest)
+ | "-db" :: s :: rest =>
+ (Settings.setDbstring (SOME s);
+ doArgs rest)
+ | "-dbms" :: name :: rest =>
+ (Settings.setDbms name;
+ doArgs rest)
+ | "-debug" :: rest =>
+ (Settings.setDebug true;
+ doArgs rest)
+ | "-verbose" :: rest =>
+ (Compiler.debug := true;
+ Elaborate.verbose := true;
+ doArgs rest)
+ | "-timing" :: rest =>
+ (timing := true;
+ doArgs rest)
+ | "-tc" :: rest =>
+ (tc := true;
+ doArgs rest)
+ | "-dumpTypes" :: rest =>
+ (Elaborate.dumpTypes := true;
+ doArgs rest)
+ | "-dumpTypesOnError" :: rest =>
+ (Elaborate.dumpTypesOnError := true;
+ doArgs rest)
+ | "-unifyMore" :: rest =>
+ (Elaborate.unifyMore := true;
+ doArgs rest)
+ | "-dumpSource" :: rest =>
+ (Compiler.dumpSource := true;
+ doArgs rest)
+ | "-dumpVerboseSource" :: rest =>
+ (Compiler.dumpSource := true;
+ ElabPrint.debug := true;
+ ExplPrint.debug := true;
+ CorePrint.debug := true;
+ MonoPrint.debug := true;
+ doArgs rest)
+ | "-output" :: s :: rest =>
+ (Settings.setExe (SOME s);
+ doArgs rest)
+ | "-js" :: s :: rest =>
+ (Settings.setOutputJsFile (SOME s);
+ doArgs rest)
+ | "-sql" :: s :: rest =>
+ (Settings.setSql (SOME s);
+ doArgs rest)
+ | "-static" :: rest =>
+ (Settings.setStaticLinking true;
+ doArgs rest)
+ | "-stop" :: phase :: rest =>
+ (Compiler.setStop phase;
+ doArgs rest)
+ | "-path" :: name :: path :: rest =>
+ (Compiler.addPath (name, path);
+ doArgs rest)
+ | "-root" :: name :: root :: rest =>
+ (Compiler.addModuleRoot (root, name);
+ doArgs rest)
+ | "-boot" :: rest =>
+ (Compiler.enableBoot ();
+ Settings.setBootLinking true;
+ doArgs rest)
+ | "-sigfile" :: name :: rest =>
+ (Settings.setSigFile (SOME name);
+ doArgs rest)
+ | "-iflow" :: rest =>
+ (Compiler.doIflow := true;
+ doArgs rest)
+ | "-sqlcache" :: rest =>
+ (Settings.setSqlcache true;
+ doArgs rest)
+ | "-heuristic" :: h :: rest =>
+ (Sqlcache.setHeuristic h;
+ doArgs rest)
+ | "-moduleOf" :: fname :: _ =>
+ (print (Compiler.moduleOf fname ^ "\n");
+ raise Code OS.Process.success)
+ | "-noEmacs" :: rest =>
+ (Demo.noEmacs := true;
+ doArgs rest)
+ | "-limit" :: class :: num :: rest =>
+ (case Int.fromString num of
+ NONE => raise Fail ("Invalid limit number '" ^ num ^ "'")
+ | SOME n =>
+ if n < 0 then
+ raise Fail ("Invalid limit number '" ^ num ^ "'")
+ else
+ Settings.addLimit (class, n);
+ doArgs rest)
+ | "-explainEmbed" :: rest =>
+ (JsComp.explainEmbed := true;
+ doArgs rest)
+ | arg :: rest =>
+ (if size arg > 0 andalso String.sub (arg, 0) = #"-" then
+ raise Fail ("Unknown flag " ^ arg)
+ else
+ sources := arg :: !sources;
+ doArgs rest)
+
+ val () = case args of
+ ["daemon", "stop"] => OS.Process.exit OS.Process.success
+ | _ => ()
+
+ val () = doArgs args
+
+ val job =
+ case !sources of
+ [file] => file
+ | files =>
+ if List.exists (fn s => s <> "-version") args then
+ raise Fail ("Zero or multiple input files specified; only one is allowed.\nFiles: "
+ ^ String.concatWith ", " files)
+ else
+ printVersion ()
+ in
+ case (!css, !demo, !tutorial) of
+ (true, _, _) =>
+ (case Compiler.run Compiler.toCss job of
+ NONE => OS.Process.failure
+ | SOME {Overall = ov, Classes = cl} =>
+ (app (print o Css.inheritableToString) ov;
+ print "\n";
+ app (fn (x, (ins, ots)) =>
+ (print x;
+ print " ";
+ app (print o Css.inheritableToString) ins;
+ app (print o Css.othersToString) ots;
+ print "\n")) cl;
+ OS.Process.success))
+ | (_, SOME (prefix, guided), _) =>
+ if Demo.make' {prefix = prefix, dirname = job, guided = guided} then
+ OS.Process.success
+ else
+ OS.Process.failure
+ | (_, _, true) => (Tutorial.make job;
+ OS.Process.success)
+ | _ =>
+ if !tc then
+ (Compiler.check Compiler.toElaborate job;
+ if ErrorMsg.anyErrors () then
+ OS.Process.failure
+ else
+ OS.Process.success)
+ else if !timing then
+ (Compiler.time Compiler.toCjrize job;
+ OS.Process.success)
+ else
+ (if Compiler.compile job then
+ OS.Process.success
+ else
+ OS.Process.failure)
+ end handle Code n => n
+
+fun send (sock, s) =
+ let
+ 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
+ ()
+ else
+ send (sock, String.extract (s, n, NONE))
+ end
+
+val () = (Globals.setResetTime ();
+ case CommandLine.arguments () of
+ ["daemon", "start"] =>
+ (case Posix.Process.fork () of
+ SOME _ => ()
+ | NONE =>
+ let
+ val () = Elaborate.incremental := true
+ val listen = UnixSock.Strm.socket ()
+
+ fun loop () =
+ let
+ val (sock, _) = Socket.accept listen
+
+ fun loop' (buf, args) =
+ let
+ val s = if CharVector.exists (fn ch => ch = #"\n") buf then
+ ""
+ else
+ 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
+ if Substring.isEmpty after then
+ loop' (s, args)
+ else
+ let
+ val cmd = Substring.string befor
+ val rest = Substring.string (Substring.slice (after, 1, NONE))
+ in
+ case cmd of
+ "" =>
+ (case args of
+ ["stop", "daemon"] =>
+ (((Socket.close listen;
+ OS.FileSys.remove socket) handle OS.SysErr _ => ());
+ OS.Process.exit OS.Process.success)
+ | _ =>
+ let
+ val success = (oneRun (rev args))
+ handle ex => (print "unhandled exception:\n";
+ print (General.exnMessage ex ^ "\n");
+ OS.Process.failure)
+ in
+ TextIO.flushOut TextIO.stdOut;
+ TextIO.flushOut TextIO.stdErr;
+ send (sock, if OS.Process.isSuccess success then
+ "\001"
+ else
+ "\002")
+ end)
+ | _ => loop' (rest, cmd :: args)
+ end
+ end handle OS.SysErr _ => ()
+
+ fun redirect old =
+ Posix.IO.dup2 {old = valOf (Posix.FileSys.iodToFD (Socket.ioDesc sock)),
+ new = old}
+
+ val oldStdout = Posix.IO.dup Posix.FileSys.stdout
+ val oldStderr = Posix.IO.dup Posix.FileSys.stderr
+ in
+ (* Redirect the daemon's output to the socket. *)
+ redirect Posix.FileSys.stdout;
+ redirect Posix.FileSys.stderr;
+
+ loop' ("", []);
+ Socket.close sock;
+
+ Posix.IO.dup2 {old = oldStdout, new = Posix.FileSys.stdout};
+ Posix.IO.dup2 {old = oldStderr, new = Posix.FileSys.stderr};
+ Posix.IO.close oldStdout;
+ Posix.IO.close oldStderr;
+
+ Settings.reset ();
+ MLton.GC.pack ();
+ loop ()
+ end
+ in
+ OS.Process.atExit (fn () => OS.FileSys.remove socket);
+ Socket.bind (listen, UnixSock.toAddr socket);
+ Socket.listen (listen, 1);
+ loop ()
+ end)
+ | args =>
+ let
+ val sock = UnixSock.Strm.socket ()
+
+ fun wait () =
+ let
+ val v = Socket.recvVec (sock, 1024)
+ in
+ if Word8Vector.length v = 0 then
+ OS.Process.failure
+ else
+ let
+ 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
+ (SOME OS.Process.failure, String.substring (s, 0, size s - 1))
+ else
+ (NONE, s)
+ in
+ print s;
+ case rc of
+ NONE => wait ()
+ | SOME rc => rc
+ end
+ end handle OS.SysErr _ => OS.Process.failure
+ in
+ if Socket.connectNB (sock, UnixSock.toAddr socket)
+ orelse not (List.null (#wrs (Socket.select {rds = [],
+ wrs = [Socket.sockDesc sock],
+ exs = [],
+ timeout = SOME (Time.fromSeconds 1)}))) then
+ (app (fn arg => send (sock, arg ^ "\n")) args;
+ send (sock, "\n");
+ OS.Process.exit (wait ()))
+ else
+ (OS.FileSys.remove socket;
+ raise OS.SysErr ("", NONE))
+ end handle OS.SysErr _ => OS.Process.exit (oneRun args))
diff --git a/src/marshalcheck.sig b/src/marshalcheck.sig
new file mode 100644
index 0000000..fe16345
--- /dev/null
+++ b/src/marshalcheck.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+signature MARSHAL_CHECK = sig
+
+ val check : Core.file -> unit
+
+end
diff --git a/src/marshalcheck.sml b/src/marshalcheck.sml
new file mode 100644
index 0000000..8d7edd1
--- /dev/null
+++ b/src/marshalcheck.sml
@@ -0,0 +1,132 @@
+(* Copyright (c) 2009-2010, 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 MarshalCheck :> MARSHAL_CHECK = struct
+
+open Core
+
+structure U = CoreUtil
+structure E = ErrorMsg
+
+structure PK = struct
+open Order
+type ord_key = string * string
+fun compare ((m1, x1), (m2, x2)) =
+ join (String.compare (m1, m2),
+ fn () => String.compare (x1, x2))
+end
+
+structure PS = BinarySetFn(PK)
+structure PS = struct
+open PS
+fun toString' (m, x) = m ^ "." ^ x
+fun toString set =
+ case PS.listItems set of
+ [] => "{}"
+ | [x] => toString' x
+ | x :: xs => List.foldl (fn (x, s) => s ^ ", " ^ toString' x) (toString' x) xs
+end
+
+structure IM = IntBinaryMap
+
+fun check file =
+ let
+ fun kind (_, st) = st
+
+ fun con cmap (c, st) =
+ case c of
+ CFfi mx =>
+ if Settings.mayClientToServer mx then
+ st
+ else
+ PS.add (st, mx)
+ | CNamed n =>
+ (case IM.find (cmap, n) of
+ NONE => st
+ | SOME st' => PS.union (st, st'))
+ | _ => st
+
+ fun sins cmap = U.Con.fold {kind = kind, con = con cmap} PS.empty
+ in
+ ignore (foldl (fn ((d, _), (cmap, emap)) =>
+ case d of
+ DCon (_, n, _, c) => (IM.insert (cmap, n, sins cmap c), emap)
+ | DDatatype dts =>
+ (foldl (fn ((_, n, _, xncs), cmap) =>
+ IM.insert (cmap, n, foldl (fn ((_, _, co), s) =>
+ case co of
+ NONE => s
+ | SOME c => PS.union (s, sins cmap c))
+ PS.empty xncs)) cmap dts,
+ emap)
+
+ | DVal (_, n, t, _, tag) => (cmap, IM.insert (emap, n, (t, tag)))
+ | DValRec vis => (cmap,
+ foldl (fn ((_, n, t, _, tag), emap) => IM.insert (emap, n, (t, tag)))
+ emap vis)
+
+ | DExport (_, n, _) =>
+ (case IM.find (emap, n) of
+ NONE => raise Fail "MarshalCheck: Unknown export"
+ | SOME (t, tag) =>
+ let
+ fun makeS (t, _) =
+ case t of
+ TFun (dom, ran) =>
+ (case #1 dom of
+ CFfi ("Basis", "postBody") => makeS ran
+ | CApp ((CFfi ("Basis", "option"), _), (CFfi ("Basis", "queryString"), _)) => makeS ran
+ | _ => PS.union (sins cmap dom, makeS ran))
+ | _ => PS.empty
+ val s = makeS t
+ in
+ if PS.isEmpty s then
+ ()
+ else
+ E.error ("Input to exported function '"
+ ^ tag ^ "' involves one or more types that are disallowed for page handler inputs: "
+ ^ PS.toString s);
+ (cmap, emap)
+ end)
+
+ | DCookie (_, _, t, tag) =>
+ let
+ val s = sins cmap t
+ in
+ if PS.isEmpty s then
+ ()
+ else
+ E.error ("Cookie '" ^ tag ^ "' includes one or more types that are disallowed for cookies: "
+ ^ PS.toString s);
+ (cmap, emap)
+ end
+
+ | _ => (cmap, emap))
+ (IM.empty, IM.empty) file)
+ end
+
+end
diff --git a/src/mod_db.sig b/src/mod_db.sig
new file mode 100644
index 0000000..8f78f2c
--- /dev/null
+++ b/src/mod_db.sig
@@ -0,0 +1,42 @@
+(* Copyright (c) 2012, 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.
+ *)
+
+(* Cache of module code, with dependency information *)
+
+signature MOD_DB = sig
+ val reset : unit -> unit
+
+ val insert : Elab.decl * Time.time -> unit
+ (* Here's a declaration, including the modification timestamp of the file it came from.
+ * We might invalidate other declarations that depend on this one, if the timestamp has changed. *)
+
+ val lookup : Source.decl -> Elab.decl option
+
+ (* Allow undoing to snapshots after failed compilations. *)
+ val snapshot : unit -> unit
+ val revert : unit -> unit
+end
diff --git a/src/mod_db.sml b/src/mod_db.sml
new file mode 100644
index 0000000..2d6b285
--- /dev/null
+++ b/src/mod_db.sml
@@ -0,0 +1,153 @@
+(* Copyright (c) 2012, 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.
+ *)
+
+(* Cache of module code, with dependency information *)
+
+structure ModDb :> MOD_DB = struct
+
+open Elab
+
+structure SK = struct
+type ord_key = string
+val compare = String.compare
+end
+
+structure SS = BinarySetFn(SK)
+structure SM = BinaryMapFn(SK)
+structure IM = IntBinaryMap
+
+type oneMod = {Decl : decl,
+ When : Time.time,
+ Deps : SS.set}
+
+val byName = ref (SM.empty : oneMod SM.map)
+val byId = ref (IM.empty : string IM.map)
+
+fun reset () = (byName := SM.empty;
+ byId := IM.empty)
+
+fun insert (d, tm) =
+ let
+ val xn =
+ case #1 d of
+ DStr (x, n, _, _) => SOME (x, n)
+ | DFfiStr (x, n, _) => SOME (x, n)
+ | _ => NONE
+ in
+ case xn of
+ NONE => ()
+ | SOME (x, n) =>
+ let
+ val skipIt =
+ case SM.find (!byName, x) of
+ NONE => false
+ | SOME r => #When r = tm
+ in
+ if skipIt then
+ ()
+ else
+ let
+ fun doMod (n', deps) =
+ case IM.find (!byId, n') of
+ NONE => deps
+ | SOME x' =>
+ SS.union (deps,
+ SS.add (case SM.find (!byName, x') of
+ NONE => SS.empty
+ | SOME {Deps = ds, ...} => ds, x'))
+
+ val deps = ElabUtil.Decl.fold {kind = #2,
+ con = fn (c, deps) =>
+ case c of
+ CModProj (n', _, _) => doMod (n', deps)
+ | _ => deps,
+ exp = fn (e, deps) =>
+ case e of
+ EModProj (n', _, _) => doMod (n', deps)
+ | _ => deps,
+ sgn_item = #2,
+ sgn = fn (sg, deps) =>
+ case sg of
+ SgnProj (n', _, _) => doMod (n', deps)
+ | _ => deps,
+ str = fn (st, deps) =>
+ case st of
+ StrVar n' => doMod (n', deps)
+ | _ => deps,
+ decl = fn (d, deps) =>
+ case d of
+ DDatatypeImp (_, _, n', _, _, _, _) => doMod (n', deps)
+ | _ => deps}
+ SS.empty d
+ in
+ byName := SM.insert (SM.filter (fn r => if SS.member (#Deps r, x) then
+ case #1 (#Decl r) of
+ DStr (_, n', _, _) =>
+ (byId := #1 (IM.remove (!byId, n'));
+ false)
+ | DFfiStr (_, n', _) =>
+ (byId := #1 (IM.remove (!byId, n'));
+ false)
+ | _ => raise Fail "ModDb: Impossible decl"
+ else
+ true) (!byName),
+ x,
+ {Decl = d,
+ When = tm,
+ Deps = deps});
+ byId := IM.insert (!byId, n, x)
+ end
+ end
+ end
+
+fun lookup (d : Source.decl) =
+ case #1 d of
+ Source.DStr (x, _, SOME tm, _, _) =>
+ (case SM.find (!byName, x) of
+ NONE => NONE
+ | SOME r =>
+ if tm = #When r then
+ SOME (#Decl r)
+ else
+ NONE)
+ | Source.DFfiStr (x, _, SOME tm) =>
+ (case SM.find (!byName, x) of
+ NONE => NONE
+ | SOME r =>
+ if tm = #When r then
+ SOME (#Decl r)
+ else
+ NONE)
+ | _ => NONE
+
+val byNameBackup = ref (!byName)
+val byIdBackup = ref (!byId)
+
+fun snapshot () = (byNameBackup := !byName; byIdBackup := !byId)
+fun revert () = (byName := !byNameBackup; byId := !byIdBackup)
+
+end
diff --git a/src/mono.sml b/src/mono.sml
new file mode 100644
index 0000000..cdadded
--- /dev/null
+++ b/src/mono.sml
@@ -0,0 +1,171 @@
+(* Copyright (c) 2008-2010, 2013-2014, 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 Mono = struct
+
+type 'a located = 'a ErrorMsg.located
+
+datatype datatype_kind = datatype DatatypeKind.datatype_kind
+
+datatype typ' =
+ TFun of typ * typ
+ | TRecord of (string * typ) list
+ | TDatatype of int * (datatype_kind * (string * int * typ option) list) ref
+ | TFfi of string * string
+ | TOption of typ
+ | TList of typ
+ | TSource
+ | TSignal of typ
+
+withtype typ = typ' located
+
+datatype patCon =
+ PConVar of int (* constructor identifier *)
+ | PConFfi of {mod : string, datatyp : string, con : string, arg : typ option}
+
+datatype pat' =
+ PVar of string * typ
+ | PPrim of Prim.t
+ | PCon of datatype_kind * patCon * pat option
+ | PRecord of (string * pat * typ) list
+ | PNone of typ
+ | PSome of typ * pat
+
+withtype pat = pat' located
+
+datatype javascript_mode =
+ Attribute
+ | Script
+ | Source of typ
+
+datatype effect = datatype Export.effect
+datatype export_kind = datatype Export.export_kind
+
+datatype failure_mode = datatype Settings.failure_mode
+
+datatype binop_intness = Int | NotInt
+
+datatype exp' =
+ EPrim of Prim.t
+ | ERel of int (* deBruijn index *)
+ | ENamed of int (* named variable *)
+ | ECon of datatype_kind * patCon * exp option
+ | ENone of typ
+ | ESome of typ * exp
+ | EFfi of string * string
+ | EFfiApp of string * string * (exp * typ) list
+ | EApp of exp * exp
+ | EAbs of string * typ * typ * exp
+
+ | EUnop of string * exp
+ | EBinop of binop_intness * string * exp * exp
+
+ | ERecord of (string * exp * typ) list
+ | EField of exp * string
+
+ | ECase of exp * (pat * exp) list * { disc : typ, result : typ }
+
+ | EStrcat of exp * exp
+
+ | EError of exp * typ
+ | EReturnBlob of {blob : exp option, mimeType : exp, t : typ}
+ | ERedirect of exp * typ
+
+ | EWrite of exp
+ | ESeq of exp * exp
+ | ELet of string * typ * exp * exp
+
+ | EClosure of int * exp list
+
+ | EQuery of { exps : (string * typ) list, (* name of computed field, type of field*)
+ tables : (string * (string * typ) list) list,
+ state : typ,
+ query : exp, (* exp of string type containing sql query *)
+ body : exp,
+ initial : exp }
+ | EDml of exp * failure_mode
+ | ENextval of exp
+ | ESetval of exp * exp
+
+ | EUnurlify of exp * typ * bool
+
+ | EJavaScript of javascript_mode * exp
+
+ | ESignalReturn of exp
+ | ESignalBind of exp * exp
+ | ESignalSource of exp
+
+ | EServerCall of exp * typ * effect * failure_mode
+ | ERecv of exp * typ
+ | ESleep of exp
+ | ESpawn of exp
+
+withtype exp = exp' located
+
+datatype policy =
+ PolClient of exp
+ | PolInsert of exp
+ | PolDelete of exp
+ | PolUpdate of exp
+ | PolSequence of exp
+
+datatype decl' =
+ DDatatype of (string * int * (string * int * typ option) list) list
+ | DVal of string * int * typ * exp * string
+ | DValRec of (string * int * typ * exp * string) list
+ | DExport of export_kind * string * int * typ list * typ * bool
+
+ | DTable of string * (string * typ) list * exp * exp
+ | DSequence of string
+ | DView of string * (string * typ) list * exp
+ | DDatabase of {name : string, expunge : int, initialize : int}
+
+ | DJavaScript of string
+
+ | DCookie of string
+ | DStyle of string
+
+ | DTask of exp * exp
+
+ | DPolicy of policy
+ | DOnError of int
+
+withtype decl = decl' located
+
+datatype sidedness =
+ ServerOnly
+ | ServerAndPull
+ | ServerAndPullAndPush
+
+datatype dbmode =
+ NoDb
+ | OneQuery
+ | AnyDb
+
+type file = decl list * (int * sidedness * dbmode) list
+
+end
diff --git a/src/mono_env.sig b/src/mono_env.sig
new file mode 100644
index 0000000..db6fdc9
--- /dev/null
+++ b/src/mono_env.sig
@@ -0,0 +1,55 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature MONO_ENV = sig
+
+ type env
+
+ val empty : env
+
+ exception UnboundRel of int
+ exception UnboundNamed of int
+
+ val pushDatatype : env -> string -> int -> (string * int * Mono.typ option) list -> env
+ val lookupDatatype : env -> int -> string * (string * int * Mono.typ option) list
+
+ val lookupConstructor : env -> int -> string * Mono.typ option * int
+
+ val pushERel : env -> string -> Mono.typ -> Mono.exp option -> env
+ val lookupERel : env -> int -> string * Mono.typ * Mono.exp option
+
+ val pushENamed : env -> string -> int -> Mono.typ -> Mono.exp option -> string -> env
+ val lookupENamed : env -> int -> string * Mono.typ * Mono.exp option * string
+
+ val declBinds : env -> Mono.decl -> env
+ val patBinds : env -> Mono.pat -> env
+ val patBindsN : Mono.pat -> int
+
+ val liftExpInExp : int -> Mono.exp -> Mono.exp
+ val subExpInExp : (int * Mono.exp) -> Mono.exp -> Mono.exp
+
+end
diff --git a/src/mono_env.sml b/src/mono_env.sml
new file mode 100644
index 0000000..0dd668e
--- /dev/null
+++ b/src/mono_env.sml
@@ -0,0 +1,169 @@
+(* Copyright (c) 2008, 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 MonoEnv :> MONO_ENV = struct
+
+open Mono
+
+structure IM = IntBinaryMap
+
+
+exception UnboundRel of int
+exception UnboundNamed of int
+
+type env = {
+ datatypes : (string * (string * int * typ option) list) IM.map,
+ constructors : (string * typ option * int) IM.map,
+
+ relE : (string * typ * exp option) list,
+ namedE : (string * typ * exp option * string) IM.map
+}
+
+val empty = {
+ datatypes = IM.empty,
+ constructors = IM.empty,
+
+ relE = [],
+ namedE = IM.empty
+}
+
+fun pushDatatype (env : env) x n xncs =
+ {datatypes = IM.insert (#datatypes env, n, (x, xncs)),
+ constructors = foldl (fn ((x, n', to), constructors) =>
+ IM.insert (constructors, n', (x, to, n)))
+ (#constructors env) xncs,
+
+ relE = #relE env,
+ namedE = #namedE env}
+
+fun lookupDatatype (env : env) n =
+ case IM.find (#datatypes env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun lookupConstructor (env : env) n =
+ case IM.find (#constructors env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+structure U = MonoUtil
+
+val liftExpInExp =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn bound => fn e =>
+ case e of
+ ERel xn =>
+ if xn < bound then
+ e
+ else
+ ERel (xn + 1)
+ | _ => e,
+ bind = fn (bound, U.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+
+val subExpInExp =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn (xn, rep) => fn e =>
+ case e of
+ ERel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 rep
+ | GREATER=> ERel (xn' - 1)
+ | LESS => e)
+ | _ => e,
+ bind = fn ((xn, rep), U.Exp.RelE _) => (xn+1, liftExpInExp 0 rep)
+ | (ctx, _) => ctx}
+
+fun pushERel (env : env) x t eo =
+ {datatypes = #datatypes env,
+ constructors = #constructors env,
+ relE = (x, t, eo) :: map (fn (x, t, eo) => (x, t, Option.map (liftExpInExp 0) eo)) (#relE env),
+ namedE = #namedE env}
+
+fun lookupERel (env : env) n =
+ (List.nth (#relE env, n))
+ handle Subscript => raise UnboundRel n
+
+fun pushENamed (env : env) x n t eo s =
+ {datatypes = #datatypes env,
+ constructors = #constructors env,
+
+ relE = #relE env,
+ namedE = IM.insert (#namedE env, n, (x, t, eo, s))}
+
+fun lookupENamed (env : env) n =
+ case IM.find (#namedE env, n) of
+ NONE => raise UnboundNamed n
+ | SOME x => x
+
+fun declBinds env (d, loc) =
+ case d of
+ DDatatype dts =>
+ foldl (fn ((x, n, xncs), env) =>
+ let
+ val env = pushDatatype env x n xncs
+ val dt = (TDatatype (n, ref (ElabUtil.classifyDatatype xncs, xncs)), loc)
+ in
+ foldl (fn ((x', n', NONE), env) => pushENamed env x' n' dt NONE ""
+ | ((x', n', SOME t), env) => pushENamed env x' n' (TFun (t, dt), loc) NONE "")
+ env xncs
+ end) env dts
+ | DVal (x, n, t, e, s) => pushENamed env x n t (SOME e) s
+ | DValRec vis => foldl (fn ((x, n, t, e, s), env) => pushENamed env x n t NONE s) env vis
+ | DExport _ => env
+ | DTable _ => env
+ | DSequence _ => env
+ | DView _ => env
+ | DDatabase _ => env
+ | DJavaScript _ => env
+ | DCookie _ => env
+ | DStyle _ => env
+ | DTask _ => env
+ | DPolicy _ => env
+ | DOnError _ => env
+
+fun patBinds env (p, loc) =
+ case p of
+ PVar (x, t) => pushERel env x t NONE
+ | PPrim _ => env
+ | PCon (_, _, NONE) => env
+ | PCon (_, _, SOME p) => patBinds env p
+ | PRecord xps => foldl (fn ((_, p, _), env) => patBinds env p) env xps
+ | PNone _ => env
+ | PSome (_, p) => patBinds env p
+
+fun patBindsN (p, loc) =
+ case p of
+ PVar _ => 1
+ | PPrim _ => 0
+ | PCon (_, _, NONE) => 0
+ | PCon (_, _, SOME p) => patBindsN p
+ | PRecord xps => foldl (fn ((_, p, _), count) => count + patBindsN p) 0 xps
+ | PNone _ => 0
+ | PSome (_, p) => patBindsN p
+
+end
diff --git a/src/mono_fooify.sig b/src/mono_fooify.sig
new file mode 100644
index 0000000..0cc7234
--- /dev/null
+++ b/src/mono_fooify.sig
@@ -0,0 +1,39 @@
+signature MONO_FOOIFY = sig
+
+(* TODO: don't expose raw references if possible. *)
+val nextPvar : int ref
+val pvarDefs : ((string * int * (string * int * Mono.typ option) list) list) ref
+
+datatype foo_kind = Attr | Url
+
+structure Fm : sig
+ type t
+
+ type vr = string * int * Mono.typ * Mono.exp * string
+
+ val empty : int -> t
+
+ val lookup : t -> foo_kind -> int -> (int -> t -> vr * t) -> t * int
+ val lookupList : t -> foo_kind -> Mono.typ -> (int -> t -> vr * t) -> t * int
+ val enter : t -> t
+ (* This list should be reversed before adding to list of file declarations. *)
+ val decls : t -> Mono.decl list
+
+ val freshName : t -> int * t
+end
+
+(* General form used in [Monoize]. *)
+val fooifyExp : foo_kind
+ -> (int -> Mono.typ * string)
+ -> (int -> string * (string * int * Mono.typ option) list)
+ -> Fm.t
+ -> Mono.exp * Mono.typ
+ -> Mono.exp * Fm.t
+
+(* Easy-to-use interface in [Sqlcache]. Uses [Fm.canonical]. *)
+val canonicalFm : Fm.t ref (* Set at the end of [Monoize]. *)
+val urlify : MonoEnv.env -> Mono.exp * Mono.typ -> Mono.exp option
+(* This list should be reversed before adding to list of file declarations. *)
+val getNewFmDecls : unit -> Mono.decl list
+
+end
diff --git a/src/mono_fooify.sml b/src/mono_fooify.sml
new file mode 100644
index 0000000..e64207c
--- /dev/null
+++ b/src/mono_fooify.sml
@@ -0,0 +1,346 @@
+structure MonoFooify :> MONO_FOOIFY = struct
+
+open Mono
+
+datatype foo_kind =
+ Attr
+ | Url
+
+val nextPvar = ref 0
+val pvarDefs = ref ([] : (string * int * (string * int * typ option) list) list)
+
+structure Fm = struct
+
+type vr = string * int * typ * exp * string
+
+structure IM = IntBinaryMap
+
+structure M = BinaryMapFn(struct
+ type ord_key = foo_kind
+ fun compare x =
+ case x of
+ (Attr, Attr) => EQUAL
+ | (Attr, _) => LESS
+ | (_, Attr) => GREATER
+
+ | (Url, Url) => EQUAL
+ end)
+
+structure TM = BinaryMapFn(struct
+ type ord_key = typ
+ val compare = MonoUtil.Typ.compare
+ end)
+
+type t = {
+ count : int,
+ map : int IM.map M.map,
+ listMap : int TM.map M.map,
+ decls : vr list
+}
+
+fun empty count = {
+ count = count,
+ map = M.empty,
+ listMap = M.empty,
+ decls = []
+}
+
+fun chooseNext count =
+ let
+ val n = !nextPvar
+ in
+ if count < n then
+ (count, count+1)
+ else
+ (nextPvar := n + 1;
+ (n, n+1))
+ end
+
+fun enter ({count, map, listMap, ...} : t) = {count = count, map = map, listMap = listMap, decls = []}
+fun freshName {count, map, listMap, decls} =
+ let
+ val (next, count) = chooseNext count
+ in
+ (next, {count = count , map = map, listMap = listMap, decls = decls})
+ end
+fun decls ({decls, ...} : t) =
+ case decls of
+ [] => []
+ | _ => [(DValRec decls, ErrorMsg.dummySpan)]
+
+fun lookup (t as {count, map, listMap, decls}) k n thunk =
+ let
+ val im = Option.getOpt (M.find (map, k), IM.empty)
+ in
+ case IM.find (im, n) of
+ NONE =>
+ let
+ val n' = count
+ val (d, {count, map, listMap, decls}) =
+ thunk count {count = count + 1,
+ map = M.insert (map, k, IM.insert (im, n, n')),
+ listMap = listMap,
+ decls = decls}
+ in
+ ({count = count,
+ map = map,
+ listMap = listMap,
+ decls = d :: decls}, n')
+ end
+ | SOME n' => (t, n')
+ end
+
+fun lookupList (t as {count, map, listMap, decls}) k tp thunk =
+ let
+ val tm = Option.getOpt (M.find (listMap, k), TM.empty)
+ in
+ case TM.find (tm, tp) of
+ NONE =>
+ let
+ val n' = count
+ val (d, {count, map, listMap, decls}) =
+ thunk count {count = count + 1,
+ map = map,
+ listMap = M.insert (listMap, k, TM.insert (tm, tp, n')),
+ decls = decls}
+ in
+ ({count = count,
+ map = map,
+ listMap = listMap,
+ decls = d :: decls}, n')
+ end
+ | SOME n' => (t, n')
+ end
+
+end
+
+fun fk2s fk =
+ case fk of
+ Attr => "attr"
+ | Url => "url"
+
+fun capitalize s =
+ if s = "" then
+ s
+ else
+ str (Char.toUpper (String.sub (s, 0))) ^ String.extract (s, 1, NONE)
+
+structure E = ErrorMsg
+
+exception TypeMismatch of Fm.t * E.span
+exception CantPass of Fm.t * typ
+exception DontKnow of Fm.t * typ
+
+val dummyExp = (EPrim (Prim.Int 0), E.dummySpan)
+
+fun fooifyExpWithExceptions fk lookupENamed lookupDatatype =
+ let
+ fun fooify fm (e, tAll as (t, loc)) =
+ case #1 e of
+ EClosure (fnam, [(ERecord [], _)]) =>
+ let
+ val (_, s) = lookupENamed fnam
+ in
+ ((EPrim (Prim.String (Prim.Normal, Settings.getUrlPrefix () ^ s)), loc), fm)
+ end
+ | EClosure (fnam, args) =>
+ let
+ val (ft, s) = lookupENamed fnam
+ fun attrify (args, ft, e, fm) =
+ case (args, ft) of
+ ([], _) => (e, fm)
+ | (arg :: args, (TFun (t, ft), _)) =>
+ let
+ val (arg', fm) = fooify fm (arg, t)
+ in
+ attrify (args, ft,
+ (EStrcat (e,
+ (EStrcat ((EPrim (Prim.String (Prim.Normal, "/")), loc),
+ arg'), loc)), loc),
+ fm)
+ end
+ | _ => raise TypeMismatch (fm, loc)
+ in
+ attrify (args, ft, (EPrim (Prim.String (Prim.Normal, Settings.getUrlPrefix () ^ s)), loc), fm)
+ end
+ | _ =>
+ case t of
+ TFfi ("Basis", "unit") => ((EPrim (Prim.String (Prim.Normal, "")), loc), fm)
+ | TFfi (m, x) => (if Settings.mayClientToServer (m, x)
+ then ((EFfiApp (m, fk2s fk ^ "ify" ^ capitalize x, [(e, tAll)]), loc), fm)
+ else raise CantPass (fm, tAll))
+
+ | TRecord [] => ((EPrim (Prim.String (Prim.Normal, "")), loc), fm)
+ | TRecord ((x, t) :: xts) =>
+ let
+ val (se, fm) = fooify fm ((EField (e, x), loc), t)
+ in
+ foldl (fn ((x, t), (se, fm)) =>
+ let
+ val (se', fm) = fooify fm ((EField (e, x), loc), t)
+ in
+ ((EStrcat (se,
+ (EStrcat ((EPrim (Prim.String (Prim.Normal, "/")), loc),
+ se'), loc)), loc),
+ fm)
+ end) (se, fm) xts
+ end
+
+ | TDatatype (i, ref (dk, _)) =>
+ let
+ fun makeDecl n fm =
+ let
+ val (x, xncs) =
+ case ListUtil.search (fn (x, i', xncs) =>
+ if i' = i then
+ SOME (x, xncs)
+ else
+ NONE) (!pvarDefs) of
+ NONE => lookupDatatype i
+ | SOME v => v
+
+ val (branches, fm) =
+ ListUtil.foldlMap
+ (fn ((x, n, to), fm) =>
+ case to of
+ NONE =>
+ (((PCon (dk, PConVar n, NONE), loc),
+ (EPrim (Prim.String (Prim.Normal, x)), loc)),
+ fm)
+ | SOME t =>
+ let
+ val (arg, fm) = fooify fm ((ERel 0, loc), t)
+ in
+ (((PCon (dk, PConVar n, SOME (PVar ("a", t), loc)), loc),
+ (EStrcat ((EPrim (Prim.String (Prim.Normal, x ^ "/")), loc),
+ arg), loc)),
+ fm)
+ end)
+ fm xncs
+
+ val dom = tAll
+ val ran = (TFfi ("Basis", "string"), loc)
+ in
+ ((fk2s fk ^ "ify_" ^ x,
+ n,
+ (TFun (dom, ran), loc),
+ (EAbs ("x",
+ dom,
+ ran,
+ (ECase ((ERel 0, loc),
+ branches,
+ {disc = dom,
+ result = ran}), loc)), loc),
+ ""),
+ fm)
+ end
+
+ val (fm, n) = Fm.lookup fm fk i makeDecl
+ in
+ ((EApp ((ENamed n, loc), e), loc), fm)
+ end
+
+ | TOption t =>
+ let
+ val (body, fm) = fooify fm ((ERel 0, loc), t)
+ in
+ ((ECase (e,
+ [((PNone t, loc),
+ (EPrim (Prim.String (Prim.Normal, "None")), loc)),
+
+ ((PSome (t, (PVar ("x", t), loc)), loc),
+ (EStrcat ((EPrim (Prim.String (Prim.Normal, "Some/")), loc),
+ body), loc))],
+ {disc = tAll,
+ result = (TFfi ("Basis", "string"), loc)}), loc),
+ fm)
+ end
+
+ | TList t =>
+ let
+ fun makeDecl n fm =
+ let
+ val rt = (TRecord [("1", t), ("2", (TList t, loc))], loc)
+ val (arg, fm) = fooify fm ((ERel 0, loc), rt)
+
+ val branches = [((PNone rt, loc),
+ (EPrim (Prim.String (Prim.Normal, "Nil")), loc)),
+ ((PSome (rt, (PVar ("a", rt), loc)), loc),
+ (EStrcat ((EPrim (Prim.String (Prim.Normal, "Cons/")), loc),
+ arg), loc))]
+
+ val dom = tAll
+ val ran = (TFfi ("Basis", "string"), loc)
+ in
+ ((fk2s fk ^ "ify_list",
+ n,
+ (TFun (dom, ran), loc),
+ (EAbs ("x",
+ dom,
+ ran,
+ (ECase ((ERel 0, loc),
+ branches,
+ {disc = dom,
+ result = ran}), loc)), loc),
+ ""),
+ fm)
+ end
+
+ val (fm, n) = Fm.lookupList fm fk t makeDecl
+ in
+ ((EApp ((ENamed n, loc), e), loc), fm)
+ end
+
+ | _ => raise DontKnow (fm, tAll)
+ in
+ fooify
+ end
+
+fun fooifyExp fk lookupENamed lookupDatatype fm exp =
+ fooifyExpWithExceptions fk lookupENamed lookupDatatype fm exp
+ handle TypeMismatch (fm, loc) =>
+ (E.errorAt loc "Type mismatch encoding attribute";
+ (dummyExp, fm))
+ | CantPass (fm, typ as (_, loc)) =>
+ (E.errorAt loc "MonoFooify: can't pass type from client to server";
+ Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty typ)];
+ (dummyExp, fm))
+ | DontKnow (fm, typ as (_, loc)) =>
+ (E.errorAt loc "Don't know how to encode attribute/URL type";
+ Print.eprefaces' [("Type", MonoPrint.p_typ MonoEnv.empty typ)];
+ (dummyExp, fm))
+
+(* Has to be set at the end of [Monoize]. *)
+val canonicalFm = ref (Fm.empty 0 : Fm.t)
+
+fun urlify env expTyp =
+ let
+ val (exp, fm) =
+ fooifyExpWithExceptions
+ Url
+ (fn n =>
+ let
+ val (_, t, _, s) = MonoEnv.lookupENamed env n
+ in
+ (t, s)
+ end)
+ (fn n => MonoEnv.lookupDatatype env n)
+ (!canonicalFm)
+ expTyp
+ in
+ canonicalFm := fm;
+ SOME exp
+ end
+ handle TypeMismatch _ => NONE
+ | CantPass _ => NONE
+ | DontKnow _ => NONE
+
+fun getNewFmDecls () =
+ let
+ val fm = !canonicalFm
+ in
+ canonicalFm := Fm.enter fm;
+ Fm.decls fm
+ end
+
+end
diff --git a/src/mono_inline.sml b/src/mono_inline.sml
new file mode 100644
index 0000000..d23419f
--- /dev/null
+++ b/src/mono_inline.sml
@@ -0,0 +1,28 @@
+structure MonoInline = struct
+
+fun inlineFull file =
+ let
+ val oldInline = Settings.getMonoInline ()
+ val oldFull = !MonoReduce.fullMode
+ in
+ (Settings.setMonoInline (case Int.maxInt of
+ NONE => 1000000
+ | SOME n => n);
+ MonoReduce.fullMode := true;
+ let
+ val file = MonoReduce.reduce file
+ val file = MonoOpt.optimize file
+ val file = Fuse.fuse file
+ val file = MonoOpt.optimize file
+ val file = MonoShake.shake file
+ in
+ file
+ end before
+ (MonoReduce.fullMode := oldFull;
+ Settings.setMonoInline oldInline))
+ handle ex => (Settings.setMonoInline oldInline;
+ MonoReduce.fullMode := oldFull;
+ raise ex)
+ end
+
+end
diff --git a/src/mono_opt.sig b/src/mono_opt.sig
new file mode 100644
index 0000000..1d0fec5
--- /dev/null
+++ b/src/mono_opt.sig
@@ -0,0 +1,33 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature MONO_OPT = sig
+
+ val optimize : Mono.file -> Mono.file
+ val optExp : Mono.exp -> Mono.exp
+
+end
diff --git a/src/mono_opt.sml b/src/mono_opt.sml
new file mode 100644
index 0000000..40b865b
--- /dev/null
+++ b/src/mono_opt.sml
@@ -0,0 +1,655 @@
+(* Copyright (c) 2008-2010, 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 MonoOpt :> MONO_OPT = struct
+
+open Mono
+structure U = MonoUtil
+
+fun typ t = t
+fun decl d = d
+
+fun attrifyInt n =
+ if n < 0 then
+ "-" ^ Int64.toString (Int64.~ n)
+ else
+ Int64.toString n
+
+fun attrifyFloat n =
+ if n < 0.0 then
+ "-" ^ Real.toString (Real.~ n)
+ else
+ Real.toString n
+
+fun attrifyChar ch =
+ case ch of
+ #"\"" => "&quot;"
+ | #"&" => "&amp;"
+ | ch => str ch
+
+val attrifyString = String.translate attrifyChar
+
+
+val urlifyInt = attrifyInt
+val urlifyFloat = attrifyFloat
+
+val htmlifyInt = attrifyInt
+val htmlifyFloat = attrifyFloat
+
+val htmlifyString = String.translate (fn #"<" => "&lt;"
+ | #"&" => "&amp;"
+ | ch => str ch)
+
+fun htmlifySpecialChar ch = "&#" ^ Int.toString (ord ch) ^ ";"
+
+fun hexIt ch =
+ let
+ val s = Int.fmt StringCvt.HEX (ord ch)
+ in
+ case size s of
+ 0 => "00"
+ | 1 => "0" ^ s
+ | _ => s
+ end
+
+fun urlifyString s =
+ case s of
+ "" => "_"
+ | _ =>
+ (if String.sub (s, 0) = #"_" then
+ "_"
+ else
+ "")
+ ^ String.translate (fn #" " => "+"
+ | ch => if Char.isAlphaNum ch then
+ str ch
+ else
+ "." ^ hexIt ch) s
+
+
+fun sqlifyInt n = #p_cast (Settings.currentDbms ()) (attrifyInt n, Settings.Int)
+fun sqlifyFloat n = #p_cast (Settings.currentDbms ()) (attrifyFloat n, Settings.Float)
+
+fun sqlifyString s = #sqlifyString (Settings.currentDbms ()) s
+fun sqlifyChar ch = #sqlifyString (Settings.currentDbms ()) (str ch)
+
+fun unAs s =
+ let
+ fun doChars (cs, acc) =
+ case cs of
+ #"T" :: #"_" :: #"T" :: #"." :: cs => doChars (cs, acc)
+ | #"'" :: cs => doString (cs, #"'" :: acc)
+ | ch :: cs => doChars (cs, ch :: acc)
+ | [] => String.implode (rev acc)
+
+ and doString (cs, acc) =
+ case cs of
+ #"\\" :: #"\\" :: cs => doString (cs, #"\\" :: #"\\" :: acc)
+ | #"\\" :: #"'" :: cs => doString (cs, #"'" :: #"\\" :: acc)
+ | #"'" :: cs => doChars (cs, #"'" :: acc)
+ | ch :: cs => doString (cs, ch :: acc)
+ | [] => String.implode (rev acc)
+ in
+ doChars (String.explode s, [])
+ end
+
+fun checkUrl s = CharVector.all Char.isGraph s andalso Settings.checkUrl s
+val checkData = CharVector.all (fn ch => Char.isAlphaNum ch
+ orelse ch = #"_"
+ orelse ch = #"-")
+val checkAtom = CharVector.all (fn ch => Char.isAlphaNum ch
+ orelse ch = #"+"
+ orelse ch = #"-"
+ orelse ch = #"."
+ orelse ch = #"%"
+ orelse ch = #"#")
+val checkCssUrl = CharVector.all (fn ch => Char.isAlphaNum ch
+ orelse ch = #":"
+ orelse ch = #"/"
+ orelse ch = #"."
+ orelse ch = #"_"
+ orelse ch = #"+"
+ orelse ch = #"-"
+ orelse ch = #"%"
+ orelse ch = #"?"
+ orelse ch = #"&"
+ orelse ch = #"="
+ orelse ch = #"#")
+fun checkProperty s = size s > 0
+ andalso (Char.isLower (String.sub (s, 0)) orelse String.sub (s, 0) = #"_")
+ andalso CharVector.all (fn ch => Char.isLower ch orelse Char.isDigit ch orelse ch = #"_" orelse ch = #"-") s
+
+fun exp e =
+ case e of
+ EPrim (Prim.String (Prim.Html, s)) =>
+ if CharVector.exists Char.isSpace s then
+ let
+ val (_, chs) =
+ CharVector.foldl (fn (ch, (lastSpace, chs)) =>
+ let
+ val isSpace = Char.isSpace ch
+ in
+ if isSpace andalso lastSpace then
+ (true, chs)
+ else
+ (isSpace, ch :: chs)
+ end)
+ (false, []) s
+ in
+ EPrim (Prim.String (Prim.Html, String.implode (rev chs)))
+ end
+ else
+ e
+
+ | EFfiApp ("Basis", "strcat", [(e1, _), (e2, _)]) => exp (EStrcat (e1, e2))
+
+ | EStrcat (e1, (EPrim (Prim.String (_, "")), _)) => #1 e1
+ | EStrcat ((EPrim (Prim.String (_, "")), _), e2) => #1 e2
+
+ | EStrcat ((EPrim (Prim.String (Prim.Html, s1)), loc), (EPrim (Prim.String (Prim.Html, s2)), _)) =>
+ let
+ val s =
+ if size s1 > 0 andalso size s2 > 0
+ andalso Char.isSpace (String.sub (s1, size s1 - 1))
+ andalso Char.isSpace (String.sub (s2, 0)) then
+ s1 ^ String.extract (s2, 1, NONE)
+ else
+ s1 ^ s2
+ in
+ EPrim (Prim.String (Prim.Html, s))
+ end
+
+ | EStrcat ((EPrim (Prim.String (_, s1)), loc), (EPrim (Prim.String (_, s2)), _)) =>
+ EPrim (Prim.String (Prim.Normal, s1 ^ s2))
+
+ | EStrcat ((EPrim (Prim.String (Prim.Html, s1)), loc), (EStrcat ((EPrim (Prim.String (Prim.Html, s2)), _), rest), _)) =>
+ let
+ val s =
+ if size s1 > 0 andalso size s2 > 0
+ andalso Char.isSpace (String.sub (s1, size s1 - 1))
+ andalso Char.isSpace (String.sub (s2, 0)) then
+ s1 ^ String.extract (s2, 1, NONE)
+ else
+ s1 ^ s2
+ in
+ EStrcat ((EPrim (Prim.String (Prim.Html, s)), loc), rest)
+ end
+
+ | EStrcat ((EPrim (Prim.String (_, s1)), loc), (EStrcat ((EPrim (Prim.String (_, s2)), _), rest), _)) =>
+ EStrcat ((EPrim (Prim.String (Prim.Normal, s1 ^ s2)), loc), rest)
+
+ | EStrcat ((EStrcat (e1, e2), loc), e3) =>
+ optExp (EStrcat (e1, (EStrcat (e2, e3), loc)), loc)
+
+ | EWrite (EStrcat (e1, e2), loc) =>
+ ESeq ((optExp (EWrite e1, loc), loc),
+ (optExp (EWrite e2, loc), loc))
+
+ | ESeq ((EWrite (EPrim (Prim.String (_, s1)), _), loc),
+ (EWrite (EPrim (Prim.String (_, s2)), _), _)) =>
+ EWrite (EPrim (Prim.String (Prim.Normal, s1 ^ s2)), loc)
+ | ESeq ((EWrite (EPrim (Prim.String (_, s1)), _), loc),
+ (ESeq ((EWrite (EPrim (Prim.String (_, s2)), _), _),
+ e), _)) =>
+ ESeq ((EWrite (EPrim (Prim.String (Prim.Normal, s1 ^ s2)), loc), loc),
+ e)
+
+ | EFfiApp ("Basis", "htmlifySpecialChar", [((EPrim (Prim.Char ch), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, htmlifySpecialChar ch))
+ | EWrite (EFfiApp ("Basis", "htmlifySpecialChar", [e]), _) =>
+ EFfiApp ("Basis", "htmlifySpecialChar_w", [e])
+
+ | EWrite (EFfiApp ("Basis", "intToString", [e]), _) =>
+ EFfiApp ("Basis", "htmlifyInt_w", [e])
+ | EApp ((EFfi ("Basis", "intToString"), loc), e) =>
+ EFfiApp ("Basis", "intToString", [(e, (TFfi ("Basis", "int"), loc))])
+
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "intToString", [((EPrim (Prim.Int n), _), _)]), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, htmlifyInt n))
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "intToString", es), _), _)]) =>
+ EFfiApp ("Basis", "htmlifyInt", es)
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "intToString"), _),
+ (EPrim (Prim.Int n), _)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, htmlifyInt n))
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "intToString"), _),
+ e), loc), _)]) =>
+ EFfiApp ("Basis", "htmlifyInt", [(e, (TFfi ("Basis", "int"), loc))])
+ | EWrite (EFfiApp ("Basis", "htmlifyInt", [e]), _) =>
+ EFfiApp ("Basis", "htmlifyInt_w", [e])
+
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "floatToString", [((EPrim (Prim.Float n), _), _)]), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, htmlifyFloat n))
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "floatToString", es), _), _)]) =>
+ EFfiApp ("Basis", "htmlifyFloat", es)
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "floatToString"), _),
+ (EPrim (Prim.Float n), _)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, htmlifyFloat n))
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "floatToString"), _),
+ e), loc), _)]) =>
+ EFfiApp ("Basis", "htmlifyFloat", [(e, (TFfi ("Basis", "float"), loc))])
+ | EWrite (EFfiApp ("Basis", "htmlifyFloat", [e]), _) =>
+ EFfiApp ("Basis", "htmlifyFloat_w", [e])
+
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "boolToString",
+ [((ECon (Enum, PConFfi {con = "True", ...}, NONE), _), _)]), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, "True"))
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "boolToString",
+ [((ECon (Enum, PConFfi {con = "False", ...}, NONE), _), _)]), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, "False"))
+ | EFfiApp ("Basis", "htmlifyString", [((EFfiApp ("Basis", "boolToString", es), _), _)]) =>
+ EFfiApp ("Basis", "htmlifyBool", es)
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "boolToString"), _),
+ (ECon (Enum, PConFfi {con = "True", ...}, NONE), _)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, "True"))
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "boolToString"), _),
+ (ECon (Enum, PConFfi {con = "False", ...}, NONE), _)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, "False"))
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "boolToString"), _),
+ e), loc), _)]) =>
+ EFfiApp ("Basis", "htmlifyBool", [(e, (TFfi ("Basis", "bool"), loc))])
+ | EWrite (EFfiApp ("Basis", "htmlifyBool", [e]), _) =>
+ EFfiApp ("Basis", "htmlifyBool_w", [e])
+
+ | EFfiApp ("Basis", "htmlifyString", [((EApp ((EFfi ("Basis", "timeToString"), _), e), loc), _)]) =>
+ EFfiApp ("Basis", "htmlifyTime", [(e, (TFfi ("Basis", "time"), loc))])
+ | EFfiApp ("Basis", "htmlifyString_w", [((EApp ((EFfi ("Basis", "timeToString"), loc), e), _), _)]) =>
+ EFfiApp ("Basis", "htmlifyTime_w", [(e, (TFfi ("Basis", "time"), loc))])
+ | EWrite (EFfiApp ("Basis", "htmlifyTime", [e]), _) =>
+ EFfiApp ("Basis", "htmlifyTime_w", [e])
+
+ | EFfiApp ("Basis", "htmlifyString", [((EPrim (Prim.String (_, s)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, htmlifyString s))
+ | EWrite (EFfiApp ("Basis", "htmlifyString", [((EPrim (Prim.String (_, s)), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Html, htmlifyString s)), loc)
+ | EWrite (EFfiApp ("Basis", "htmlifyString", [e]), _) =>
+ EFfiApp ("Basis", "htmlifyString_w", [e])
+ | EFfiApp ("Basis", "htmlifyString_w", [((EPrim (Prim.String (_, s)), loc), _)]) =>
+ EWrite (EPrim (Prim.String (Prim.Html, htmlifyString s)), loc)
+
+ | EWrite (EFfiApp ("Basis", "htmlifySource", [e]), _) =>
+ EFfiApp ("Basis", "htmlifySource_w", [e])
+
+ | EFfiApp ("Basis", "attrifyInt", [((EPrim (Prim.Int n), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, attrifyInt n))
+ | EWrite (EFfiApp ("Basis", "attrifyInt", [((EPrim (Prim.Int n), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Html, attrifyInt n)), loc)
+ | EWrite (EFfiApp ("Basis", "attrifyInt", [e]), _) =>
+ EFfiApp ("Basis", "attrifyInt_w", [e])
+
+ | EFfiApp ("Basis", "attrifyFloat", [((EPrim (Prim.Float n), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, attrifyFloat n))
+ | EWrite (EFfiApp ("Basis", "attrifyFloat", [((EPrim (Prim.Float n), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Html, attrifyFloat n)), loc)
+ | EWrite (EFfiApp ("Basis", "attrifyFloat", [e]), _) =>
+ EFfiApp ("Basis", "attrifyFloat_w", [e])
+
+ | EFfiApp ("Basis", "attrifyString", [((EPrim (Prim.String (_, s)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, attrifyString s))
+ | EWrite (EFfiApp ("Basis", "attrifyString", [((EPrim (Prim.String (_, s)), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Html, attrifyString s)), loc)
+ | EWrite (EFfiApp ("Basis", "attrifyString", [e]), _) =>
+ EFfiApp ("Basis", "attrifyString_w", [e])
+
+ | EFfiApp ("Basis", "attrifyChar", [((EPrim (Prim.Char s), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, attrifyChar s))
+ | EWrite (EFfiApp ("Basis", "attrifyChar", [((EPrim (Prim.Char s), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Html, attrifyChar s)), loc)
+ | EWrite (EFfiApp ("Basis", "attrifyChar", [e]), _) =>
+ EFfiApp ("Basis", "attrifyChar_w", [e])
+
+ | EFfiApp ("Basis", "attrifyCss_class", [((EPrim (Prim.String (_, s)), _), _)]) =>
+ EPrim (Prim.String (Prim.Html, s))
+ | EWrite (EFfiApp ("Basis", "attrifyCss_class", [((EPrim (Prim.String (_, s)), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Html, s)), loc)
+ | EWrite (EFfiApp ("Basis", "attrifyCss_class", [e]), _) =>
+ EFfiApp ("Basis", "attrifyString_w", [e])
+
+ | EFfiApp ("Basis", "urlifyInt", [((EPrim (Prim.Int n), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, urlifyInt n))
+ | EWrite (EFfiApp ("Basis", "urlifyInt", [((EPrim (Prim.Int n), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Normal, urlifyInt n)), loc)
+ | EWrite (EFfiApp ("Basis", "urlifyInt", [e]), _) =>
+ EFfiApp ("Basis", "urlifyInt_w", [e])
+
+ | EFfiApp ("Basis", "urlifyFloat", [((EPrim (Prim.Float n), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, urlifyFloat n))
+ | EWrite (EFfiApp ("Basis", "urlifyFloat", [((EPrim (Prim.Float n), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Normal, urlifyFloat n)), loc)
+ | EWrite (EFfiApp ("Basis", "urlifyFloat", [e]), _) =>
+ EFfiApp ("Basis", "urlifyFloat_w", [e])
+
+ | EFfiApp ("Basis", "urlifyString", [((EPrim (Prim.String (_, s)), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, urlifyString s))
+ | EWrite (EFfiApp ("Basis", "urlifyString", [((EPrim (Prim.String (Prim.Normal, s)), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Normal, urlifyString s)), loc)
+ | EWrite (EFfiApp ("Basis", "urlifyString", [e]), _) =>
+ EFfiApp ("Basis", "urlifyString_w", [e])
+
+ | EFfiApp ("Basis", "urlifyBool", [((ECon (Enum, PConFfi {con = "True", ...}, NONE), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, "1"))
+ | EFfiApp ("Basis", "urlifyBool", [((ECon (Enum, PConFfi {con = "False", ...}, NONE), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, "0"))
+ | EWrite (EFfiApp ("Basis", "urlifyBool", [((ECon (Enum, PConFfi {con = "True", ...}, NONE), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Normal, "1")), loc)
+ | EWrite (EFfiApp ("Basis", "urlifyBool", [((ECon (Enum, PConFfi {con = "False", ...}, NONE), _), _)]), loc) =>
+ EWrite (EPrim (Prim.String (Prim.Normal, "0")), loc)
+ | EWrite (EFfiApp ("Basis", "urlifyBool", [e]), _) =>
+ EFfiApp ("Basis", "urlifyBool_w", [e])
+
+ | EFfiApp ("Basis", "sqlifyInt", [((EPrim (Prim.Int n), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, sqlifyInt n))
+ | EFfiApp ("Basis", "sqlifyIntN", [((ENone _, _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, "NULL"))
+ | EFfiApp ("Basis", "sqlifyIntN", [((ESome (_, (EPrim (Prim.Int n), _)), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, sqlifyInt n))
+
+ | EFfiApp ("Basis", "sqlifyFloat", [((EPrim (Prim.Float n), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, sqlifyFloat n))
+ | EFfiApp ("Basis", "sqlifyBool", [(b as (_, loc), _)]) =>
+ optExp (ECase (b,
+ [((PCon (Enum, PConFfi {mod = "Basis", datatyp = "bool", con = "True", arg = NONE}, NONE), loc),
+ (EPrim (Prim.String (Prim.Normal, #trueString (Settings.currentDbms ()))), loc)),
+ ((PCon (Enum, PConFfi {mod = "Basis", datatyp = "bool", con = "False", arg = NONE}, NONE), loc),
+ (EPrim (Prim.String (Prim.Normal, #falseString (Settings.currentDbms ()))), loc))],
+ {disc = (TFfi ("Basis", "bool"), loc),
+ result = (TFfi ("Basis", "string"), loc)}), loc)
+ | EFfiApp ("Basis", "sqlifyString", [((EPrim (Prim.String (_, n)), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, sqlifyString n))
+ | EFfiApp ("Basis", "sqlifyChar", [((EPrim (Prim.Char n), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, sqlifyChar n))
+
+ | EWrite (ECase (discE, pes, {disc, ...}), loc) =>
+ optExp (ECase (discE,
+ map (fn (p, e) => (p, (EWrite e, loc))) pes,
+ {disc = disc,
+ result = (TRecord [], loc)}), loc)
+
+ | EApp ((ECase (discE, pes, {disc, result = (TFun (_, ran), _)}), loc), arg as (ERecord [], _)) =>
+ let
+ fun doBody e =
+ case #1 e of
+ EAbs (_, _, _, body) => MonoReduce.subExpInExp (0, arg) body
+ | _ => (EApp (e, arg), loc)
+ in
+ optExp (ECase (discE,
+ map (fn (p, e) => (p, doBody e)) pes,
+ {disc = disc,
+ result = ran}), loc)
+ end
+
+ | EWrite (EQuery {exps, tables, state, query,
+ initial = (EPrim (Prim.String (k, "")), _),
+ body = (EStrcat ((EPrim (Prim.String (_, s)), _),
+ (EStrcat ((ERel 0, _),
+ e'), _)), _)}, loc) =>
+ if (case k of Prim.Normal => s = "" | Prim.Html => CharVector.all Char.isSpace s) then
+ EQuery {exps = exps, tables = tables, query = query,
+ state = (TRecord [], loc),
+ initial = (ERecord [], loc),
+ body = (optExp (EWrite e', loc), loc)}
+ else
+ e
+
+ | EWrite (EQuery {exps, tables, state, query,
+ initial = (EPrim (Prim.String (_, "")), _),
+ body}, loc) =>
+ let
+ fun passLets (depth, (e', _), lets) =
+ case e' of
+ EStrcat ((ERel x, _), e'') =>
+ if x = depth then
+ let
+ val body = (optExp (EWrite e'', loc), loc)
+ val body = foldl (fn ((x, t, e'), e) =>
+ (ELet (x, t, e', e), loc))
+ body lets
+ in
+ EQuery {exps = exps, tables = tables, query = query,
+ state = (TRecord [], loc),
+ initial = (ERecord [], loc),
+ body = body}
+ end
+ else
+ e
+ | ELet (x, t, e', e'') =>
+ passLets (depth + 1, e'', (x, t, e') :: lets)
+ | _ => e
+ in
+ passLets (0, body, [])
+ end
+
+ (*| EWrite (EQuery {exps, tables, state, query,
+ initial = (EPrim (Prim.String ""), _),
+ body = (EStrcat ((ERel 0, _), e'), _)}, loc) =>
+ EQuery {exps = exps, tables = tables, query = query,
+ state = (TRecord [], loc),
+ initial = (ERecord [], loc),
+ body = (optExp (EWrite e', loc), loc)}*)
+
+ | EWrite (ELet (x, t, e1, e2), loc) =>
+ optExp (ELet (x, t, e1, (EWrite e2, loc)), loc)
+
+ | EWrite (EPrim (Prim.String (_, "")), loc) =>
+ ERecord []
+
+ | ESignalBind ((ESignalReturn e1, loc), e2) =>
+ optExp (EApp (e2, e1), loc)
+
+ | EFfiApp ("Basis", "blessData", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if checkData s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid HTML5 data-* attribute " ^ s);
+ se)
+
+ | EFfiApp ("Basis", "bless", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if checkUrl s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid URL " ^ s ^ " passed to 'bless'");
+ se)
+ | EFfiApp ("Basis", "checkUrl", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if checkUrl s then
+ ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+ else
+ ENone (TFfi ("Basis", "string"), loc))
+ | EFfiApp ("Basis", "blessMime", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkMime s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessMime'");
+ se)
+ | EFfiApp ("Basis", "checkMime", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkMime s then
+ ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+ else
+ ENone (TFfi ("Basis", "string"), loc))
+ | EFfiApp ("Basis", "atom", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if checkAtom s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'atom'");
+ se)
+ | EFfiApp ("Basis", "css_url", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if checkCssUrl s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid URL " ^ s ^ " passed to 'css_url'");
+ se)
+ | EFfiApp ("Basis", "property", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if checkProperty s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'property'");
+ se)
+ | EFfiApp ("Basis", "blessRequestHeader", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkRequestHeader s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessRequestHeader'");
+ se)
+ | EFfiApp ("Basis", "checkRequestHeader", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkRequestHeader s then
+ ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+ else
+ ENone (TFfi ("Basis", "string"), loc))
+ | EFfiApp ("Basis", "blessResponseHeader", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkResponseHeader s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessResponseHeader'");
+ se)
+ | EFfiApp ("Basis", "checkResponseHeader", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkResponseHeader s then
+ ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+ else
+ ENone (TFfi ("Basis", "string"), loc))
+ | EFfiApp ("Basis", "blessEnvVar", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkEnvVar s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessEnvVar'");
+ se)
+ | EFfiApp ("Basis", "checkEnvVar", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkEnvVar s then
+ ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+ else
+ ENone (TFfi ("Basis", "string"), loc))
+ | EFfiApp ("Basis", "blessMeta", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkMeta s then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Invalid string " ^ s ^ " passed to 'blessMeta'");
+ se)
+ | EFfiApp ("Basis", "checkMeta", [((se as EPrim (Prim.String (_, s)), loc), _)]) =>
+ (if Settings.checkMeta s then
+ ESome ((TFfi ("Basis", "string"), loc), (se, loc))
+ else
+ ENone (TFfi ("Basis", "string"), loc))
+
+ | EFfiApp ("Basis", "checkString", [((EPrim (Prim.String (_, s)), loc), _)]) =>
+ let
+ fun uwify (cs, acc) =
+ case cs of
+ [] => String.concat (rev acc)
+ | #"(" :: #"_" :: cs => uwify (cs, "(uw_" :: acc)
+ | #" " :: #"_" :: cs => uwify (cs, " uw_" :: acc)
+ | #"'" :: cs =>
+ let
+ fun waitItOut (cs, acc) =
+ case cs of
+ [] => raise Fail "MonoOpt: Unterminated SQL string literal"
+ | #"'" :: cs => uwify (cs, "'" :: acc)
+ | #"\\" :: #"'" :: cs => waitItOut (cs, "\\'" :: acc)
+ | #"\\" :: #"\\" :: cs => waitItOut (cs, "\\\\" :: acc)
+ | c :: cs => waitItOut (cs, str c :: acc)
+ in
+ waitItOut (cs, "'" :: acc)
+ end
+ | c :: cs => uwify (cs, str c :: acc)
+
+ val s = case String.explode s of
+ #"_" :: cs => uwify (cs, ["uw_"])
+ | cs => uwify (cs, [])
+ in
+ EPrim (Prim.String (Prim.Normal, s))
+ end
+
+ | EFfiApp ("Basis", "viewify", [((EPrim (Prim.String (_, s)), loc), _)]) =>
+ let
+ fun uwify (cs, acc) =
+ case cs of
+ [] => String.concat (rev acc)
+ | #"A" :: #"S" :: #" " :: #"_" :: cs => uwify (cs, "AS uw_" :: acc)
+ | #"'" :: cs =>
+ let
+ fun waitItOut (cs, acc) =
+ case cs of
+ [] => raise Fail "MonoOpt: Unterminated SQL string literal"
+ | #"'" :: cs => uwify (cs, "'" :: acc)
+ | #"\\" :: #"'" :: cs => waitItOut (cs, "\\'" :: acc)
+ | #"\\" :: #"\\" :: cs => waitItOut (cs, "\\\\" :: acc)
+ | c :: cs => waitItOut (cs, str c :: acc)
+ in
+ waitItOut (cs, "'" :: acc)
+ end
+ | c :: cs => uwify (cs, str c :: acc)
+
+ val s = uwify (String.explode s, [])
+ in
+ EPrim (Prim.String (Prim.Normal, s))
+ end
+
+ | EFfiApp ("Basis", "unAs", [((EPrim (Prim.String (_, s)), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, unAs s))
+ | EFfiApp ("Basis", "unAs", [(e', _)]) =>
+ let
+ fun parts (e as (_, loc)) =
+ case #1 e of
+ EStrcat (s1, s2) =>
+ (case (parts s1, parts s2) of
+ (SOME p1, SOME p2) => SOME (p1 @ p2)
+ | _ => NONE)
+ | EPrim (Prim.String (_, s)) => SOME [(EPrim (Prim.String (Prim.Normal, unAs s)), loc)]
+ | EFfiApp ("Basis", f, [_]) =>
+ if String.isPrefix "sqlify" f then
+ SOME [e]
+ else
+ NONE
+ | _ => NONE
+ in
+ case parts e' of
+ SOME [e] => #1 e
+ | SOME es =>
+ (case rev es of
+ (e as (_, loc)) :: es => #1 (foldl (fn (e, es) => (EStrcat (e, es), loc)) e es)
+ | [] => raise Fail "MonoOpt impossible nil")
+ | NONE => e
+ end
+
+ | EFfiApp ("Basis", "str1", [((EPrim (Prim.Char ch), _), _)]) =>
+ EPrim (Prim.String (Prim.Normal, str ch))
+ | EFfiApp ("Basis", "attrifyString", [((EFfiApp ("Basis", "str1", [e]), _), _)]) =>
+ EFfiApp ("Basis", "attrifyChar", [e])
+ | EFfiApp ("Basis", "attrifyString_w", [((EFfiApp ("Basis", "str1", [e]), _), _)]) =>
+ EFfiApp ("Basis", "attrifyChar_w", [e])
+ | EWrite (EFfiApp ("Basis", "str1", [e]), _) =>
+ 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
+
+and optExp e = #1 (U.Exp.map {typ = typ, exp = exp} e)
+
+val optimize = U.File.map {typ = typ, exp = exp, decl = decl}
+
+val optExp = U.Exp.map {typ = typ, exp = exp}
+
+end
diff --git a/src/mono_print.sig b/src/mono_print.sig
new file mode 100644
index 0000000..405ff41
--- /dev/null
+++ b/src/mono_print.sig
@@ -0,0 +1,38 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing Ur/Web monomorphic language *)
+
+signature MONO_PRINT = sig
+ val p_typ : MonoEnv.env -> Mono.typ Print.printer
+ val p_exp : MonoEnv.env -> Mono.exp Print.printer
+ val p_decl : MonoEnv.env -> Mono.decl Print.printer
+ val p_file : MonoEnv.env -> Mono.file Print.printer
+
+ val debug : bool ref
+end
+
diff --git a/src/mono_print.sml b/src/mono_print.sml
new file mode 100644
index 0000000..a3b55ec
--- /dev/null
+++ b/src/mono_print.sml
@@ -0,0 +1,554 @@
+(* Copyright (c) 2008, 2013, 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.
+ *)
+
+(* Pretty-printing monomorphic Ur/Web *)
+
+structure MonoPrint :> MONO_PRINT = struct
+
+open Print.PD
+open Print
+
+open Mono
+
+structure E = MonoEnv
+
+val debug = ref false
+
+val dummyt = (TRecord [], ErrorMsg.dummySpan)
+
+fun p_typ' par env (t, _) =
+ case t of
+ TFun (t1, t2) => parenIf par (box [p_typ' true env t1,
+ space,
+ string "->",
+ space,
+ p_typ env t2])
+ | TRecord xcs => box [string "{",
+ p_list (fn (x, t) =>
+ box [string x,
+ space,
+ string ":",
+ space,
+ p_typ env t]) xcs,
+ string "}"]
+ | TDatatype (n, ref (dk, _)) =>
+ ((if !debug then
+ string (#1 (E.lookupDatatype env n) ^ "__" ^ Int.toString n ^ "["
+ ^ (case dk of
+ Option => "Option"
+ | Enum => "Enum"
+ | Default => "Default")
+ ^ "]")
+ else
+ string (#1 (E.lookupDatatype env n)))
+ handle E.UnboundNamed _ => string ("UNBOUND_DATATYPE_" ^ Int.toString n))
+ | TFfi (m, x) => box [string "FFI(", string m, string ".", string x, string ")"]
+ | TOption t => box [string "option(",
+ p_typ env t,
+ string ")"]
+ | TList t => box [string "list(",
+ p_typ env t,
+ string ")"]
+ | TSource => string "source"
+ | TSignal t => box [string "signal(",
+ p_typ env t,
+ string ")"]
+
+and p_typ env = p_typ' false env
+
+fun p_enamed env n =
+ (if !debug then
+ string (#1 (E.lookupENamed env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupENamed env n)))
+ handle E.UnboundNamed _ => string ("UNBOUNDN_" ^ Int.toString n)
+
+fun p_con_named env n =
+ (if !debug then
+ string (#1 (E.lookupConstructor env n) ^ "__" ^ Int.toString n)
+ else
+ string (#1 (E.lookupConstructor env n)))
+ handle E.UnboundNamed _ => string ("CONSTRUCTOR_" ^ Int.toString n)
+
+fun p_patCon env pc =
+ case pc of
+ PConVar n => p_con_named env n
+ | PConFfi {mod = m, con, ...} => box [string "FFIC(",
+ string m,
+ string ".",
+ string con,
+ string ")"]
+
+fun p_pat' par env (p, _) =
+ case p of
+ PVar (s, _) => string s
+ | PPrim p => Prim.p_t p
+ | PCon (_, n, NONE) => p_patCon env n
+ | PCon (_, n, SOME p) => parenIf par (box [p_patCon env n,
+ space,
+ p_pat' true env p])
+ | PRecord xps =>
+ box [string "{",
+ p_list_sep (box [string ",", space]) (fn (x, p, _) =>
+ box [string x,
+ space,
+ string "=",
+ space,
+ p_pat env p]) xps,
+ string "}"]
+ | PNone _ => string "None"
+ | PSome (t, p) =>
+ if !debug then
+ box [string "Some[",
+ p_typ env t,
+ string "]",
+ space,
+ p_pat' true env p]
+ else
+ box [string "Some",
+ space,
+ p_pat' true env p]
+
+and p_pat x = p_pat' false x
+
+fun p_mode env m =
+ case m of
+ Attribute => string "Attribute"
+ | Script => string "Script"
+ | Source t => box [string "Source", space, p_typ env t]
+
+fun p_exp' par env (e, _) =
+ case e of
+ EPrim p => Prim.p_t p
+ | ERel n =>
+ ((if !debug then
+ string (#1 (E.lookupERel env n) ^ "_" ^ Int.toString n)
+ else
+ string (#1 (E.lookupERel env n)))
+ handle E.UnboundRel _ => string ("UNBOUND_" ^ Int.toString n))
+ | ENamed n => p_enamed env n
+ | ECon (_, pc, NONE) => p_patCon env pc
+ | ECon (_, pc, SOME e) => parenIf par (box [p_patCon env pc,
+ space,
+ p_exp' true env e])
+ | ENone _ => string "None"
+ | ESome (_, e) => parenIf par (box [string "Some",
+ space,
+ p_exp' true env e])
+
+ | EFfi (m, x) => box [string "FFI(", string m, string ".", string x, string ")"]
+ | EFfiApp (m, x, es) => box [string "FFI(",
+ string m,
+ string ".",
+ string x,
+ string "(",
+ p_list (p_exp env o #1) es,
+ string "))"]
+ | EApp (e1, e2) => parenIf par (box [p_exp env e1,
+ space,
+ p_exp' true env e2])
+ | EAbs (x, t, _, e) => parenIf true (box [string "fn",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ space,
+ string "=>",
+ space,
+ p_exp (E.pushERel env x t NONE) e])
+
+ | EUnop (s, e) => parenIf true (box [string s,
+ space,
+ p_exp' true env e])
+ | EBinop (_, s, e1, e2) => parenIf true (box [p_exp' true env e1,
+ space,
+ string s,
+ space,
+ p_exp' true env e2])
+
+ | ERecord xes => box [string "{",
+ p_list (fn (x, e, _) =>
+ box [string x,
+ space,
+ string "=",
+ space,
+ p_exp env e]) xes,
+ string "}"]
+ | EField (e, x) =>
+ box [p_exp' true env e,
+ string ".",
+ string x]
+
+ | ECase (e, pes, {result, ...}) => parenIf true (box [string "case",
+ space,
+ p_exp env e,
+ space,
+ if !debug then
+ box [string "return",
+ space,
+ p_typ env result,
+ space]
+ else
+ box [],
+ string "of",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (p, e) => box [p_pat env p,
+ space,
+ string "=>",
+ space,
+ p_exp (E.patBinds env p) e])
+ pes])
+
+ | EError (e, t) => box [string "(error",
+ space,
+ p_exp env e,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ string ")"]
+ | EReturnBlob {blob = SOME blob, mimeType, t} => box [string "(blob",
+ space,
+ p_exp env blob,
+ space,
+ string "in",
+ space,
+ p_exp env mimeType,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ string ")"]
+ | EReturnBlob {blob = NONE, mimeType, t} => box [string "(blob",
+ space,
+ string "<page>",
+ space,
+ string "in",
+ space,
+ p_exp env mimeType,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ string ")"]
+ | ERedirect (e, t) => box [string "(redirect",
+ space,
+ p_exp env e,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ string ")"]
+
+ | EStrcat (e1, e2) => parenIf par (box [p_exp' true env e1,
+ space,
+ string "^",
+ space,
+ p_exp env e2])
+
+ | EWrite e => box [string "write(",
+ p_exp env e,
+ string ")"]
+
+ | ESeq (e1, e2) => box [string "(",
+ p_exp env e1,
+ string ";",
+ space,
+ p_exp env e2,
+ string ")"]
+ | ELet (x, t, e1, e2) => box [string "(let",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ space,
+ string "=",
+ space,
+ string "(",
+ p_exp env e1,
+ string ")",
+ space,
+ string "in",
+ space,
+ string "(",
+ p_exp (E.pushERel env x t NONE) e2,
+ string "))"]
+
+ | EClosure (n, es) => box [string "CLOSURE(",
+ p_enamed env n,
+ p_list_sep (string "") (fn e => box [string ", ",
+ p_exp env e]) es,
+ string ")"]
+
+ | EQuery {exps, tables, state, query, body, initial} =>
+ box [string "query[",
+ p_list (fn (x, t) => box [string x, space, string ":", space, p_typ env t]) exps,
+ string "] [",
+ p_list (fn (x, xts) => box [string x,
+ space,
+ string ":",
+ space,
+ string "{",
+ p_list (fn (x, t) => box [string x, space, string ":", space, p_typ env t]) xts,
+ string "}"]) tables,
+ string "] [",
+ p_typ env state,
+ string "]",
+ space,
+ p_exp env query,
+ space,
+ string "initial",
+ space,
+ p_exp env initial,
+ space,
+ string "in",
+ space,
+ p_exp (E.pushERel (E.pushERel env "r" dummyt NONE) "acc" dummyt NONE) body]
+ | EDml (e, _) => box [string "dml(",
+ p_exp env e,
+ string ")"]
+ | ENextval e => box [string "nextval(",
+ p_exp env e,
+ string ")"]
+ | ESetval (e1, e2) => box [string "setval(",
+ p_exp env e1,
+ string ",",
+ space,
+ p_exp env e2,
+ string ")"]
+ | EUnurlify (e, _, _) => box [string "unurlify(",
+ p_exp env e,
+ string ")"]
+ | EJavaScript (m, e) => box [string "JavaScript(",
+ p_mode env m,
+ string ",",
+ space,
+ p_exp env e,
+ string ")"]
+
+ | ESignalReturn e => box [string "Return(",
+ p_exp env e,
+ string ")"]
+ | ESignalBind (e1, e2) => box [string "Bind(",
+ p_exp env e1,
+ string ",",
+ space,
+ p_exp env e2,
+ string ")"]
+ | ESignalSource e => box [string "Source(",
+ p_exp env e,
+ string ")"]
+
+ | EServerCall (n, _, _, _) => box [string "Server(",
+ p_exp env n,
+ string ")"]
+ | ERecv (n, _) => box [string "Recv(",
+ p_exp env n,
+ string ")"]
+ | ESleep n => box [string "Sleep(",
+ p_exp env n,
+ string ")"]
+ | ESpawn n => box [string "Spawn(",
+ p_exp env n,
+ string ")"]
+
+and p_exp env = p_exp' false env
+
+fun p_vali env (x, n, t, e, s) =
+ let
+ val xp = if !debug then
+ box [string x,
+ string "__",
+ string (Int.toString n)]
+ else
+ string x
+ in
+ box [xp,
+ space,
+ string "as",
+ space,
+ string s,
+ space,
+ string ":",
+ space,
+ p_typ env t,
+ space,
+ string "=",
+ space,
+ p_exp env e]
+ end
+
+fun p_datatype env (x, n, cons) =
+ let
+ val env = E.pushDatatype env x n cons
+ in
+ box [if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x,
+ space,
+ string "=",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (x, n, NONE) => if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x
+ | (x, _, SOME t) => box [if !debug then (string (x ^ "__" ^ Int.toString n))
+ else string x, space, string "of", space, p_typ env t])
+ cons]
+ end
+
+fun p_policy env pol =
+ case pol of
+ PolClient e => box [string "sendClient",
+ space,
+ p_exp env e]
+ | PolInsert e => box [string "mayInsert",
+ space,
+ p_exp env e]
+ | PolDelete e => box [string "mayDelete",
+ space,
+ p_exp env e]
+ | PolUpdate e => box [string "mayUpdate",
+ space,
+ p_exp env e]
+ | PolSequence e => box [string "sendOwnIds",
+ space,
+ p_exp env e]
+
+fun p_decl env (dAll as (d, _) : decl) =
+ case d of
+ DDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) (p_datatype (E.declBinds env dAll)) x]
+ | DVal vi => box [string "val",
+ space,
+ p_vali env vi]
+ | DValRec vis =>
+ let
+ val env = E.declBinds env dAll
+ in
+ box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) (p_vali env) vis]
+ end
+
+ | DExport (ek, s, n, ts, t, _) => box [string "export",
+ space,
+ Export.p_export_kind ek,
+ space,
+ p_enamed env n,
+ space,
+ string "as",
+ space,
+ string s,
+ p_list_sep (string "") (fn t => box [space,
+ string "(",
+ p_typ env t,
+ string ")"]) ts,
+ space,
+ string "->",
+ space,
+ p_typ env t]
+
+ | DTable (s, xts, pe, ce) => box [string "(* SQL table ",
+ string s,
+ space,
+ string ":",
+ space,
+ p_list (fn (x, t) => box [string x,
+ space,
+ string ":",
+ space,
+ p_typ env t]) xts,
+ space,
+ string "keys",
+ space,
+ p_exp env pe,
+ space,
+ string "constraints",
+ space,
+ p_exp env ce,
+ space,
+ string "*)"]
+ | DSequence s => box [string "(* SQL sequence ",
+ string s,
+ string "*)"]
+ | DView (s, _, e) => box [string "(* SQL view ",
+ string s,
+ space,
+ string "as",
+ space,
+ p_exp env e,
+ string "*)"]
+ | DDatabase {name, expunge, initialize} => box [string "database",
+ space,
+ string name,
+ space,
+ string "(",
+ p_enamed env expunge,
+ string ",",
+ space,
+ p_enamed env initialize,
+ string ")"]
+ | DJavaScript s => box [string "JavaScript(",
+ string s,
+ string ")"]
+
+ | DCookie s => box [string "cookie",
+ space,
+ string s]
+ | DStyle s => box [string "style",
+ space,
+ string s]
+ | DTask (e1, e2) => box [string "task",
+ space,
+ p_exp env e1,
+ space,
+ string "=",
+ space,
+ p_exp env e2]
+ | DPolicy p => box [string "policy",
+ space,
+ p_policy env p]
+ | DOnError _ => string "ONERROR"
+
+fun p_file env (file, _) =
+ let
+ val (pds, _) = ListUtil.foldlMap (fn (d, env) =>
+ (p_decl env d,
+ E.declBinds env d))
+ env file
+ in
+ p_list_sep newline (fn x => x) pds
+ end
+
+end
diff --git a/src/mono_reduce.sig b/src/mono_reduce.sig
new file mode 100644
index 0000000..8990b21
--- /dev/null
+++ b/src/mono_reduce.sig
@@ -0,0 +1,40 @@
+(* Copyright (c) 2008, 2013, 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.
+ *)
+
+(* Simplify a Mono program algebraically *)
+
+signature MONO_REDUCE = sig
+
+ val reduce : Mono.file -> Mono.file
+
+ val subExpInExp : int * Mono.exp -> Mono.exp -> Mono.exp
+
+ val impure : Mono.exp -> bool
+
+ val fullMode : bool ref
+
+end
diff --git a/src/mono_reduce.sml b/src/mono_reduce.sml
new file mode 100644
index 0000000..5bcb6f5
--- /dev/null
+++ b/src/mono_reduce.sml
@@ -0,0 +1,924 @@
+(* Copyright (c) 2008, 2013-2014, 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.
+ *)
+
+(* Simplify a Mono program algebraically *)
+
+structure MonoReduce :> MONO_REDUCE = struct
+
+open Mono
+
+val fullMode = ref false
+
+structure E = MonoEnv
+structure U = MonoUtil
+
+structure IM = IntBinaryMap
+structure IS = IntBinarySet
+
+structure SS = BinarySetFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+structure SLS = BinarySetFn(struct
+ type ord_key = string list
+ val compare = Order.joinL String.compare
+ end)
+
+
+
+fun simpleTypeImpure tsyms =
+ U.Typ.exists (fn TFun _ => true
+ | TDatatype (n, _) => IS.member (tsyms, n)
+ | _ => false)
+
+fun simpleImpure isGlobal (tsyms, syms) =
+ U.Exp.existsB {typ = fn _ => false,
+ exp = fn (env, e) =>
+ case e of
+ EWrite _ => true
+ | EQuery _ => true
+ | EDml _ => true
+ | ENextval _ => true
+ | ESetval _ => true
+ | EFfiApp (m, x, _) => Settings.isEffectful (m, x) orelse Settings.isBenignEffectful (m, x)
+ | EServerCall _ => true
+ | ERecv _ => true
+ | ESleep _ => true
+ | ENamed n => IS.member (syms, n)
+ | ERel n =>
+ let
+ val (_, t, _) = E.lookupERel env n
+ in
+ simpleTypeImpure tsyms t
+ end
+ | EApp _ => not isGlobal
+ | _ => false,
+ bind = fn (env, b) =>
+ case b of
+ U.Exp.RelE (x, t) => E.pushERel env x t NONE
+ | _ => env}
+
+fun impure (e, _) =
+ case e of
+ EWrite _ => true
+ | EQuery _ => true
+ | EDml _ => true
+ | ENextval _ => true
+ | ESetval _ => true
+ | EUnurlify (e, _, _) => impure e
+ | EAbs _ => false
+
+ | EPrim _ => false
+ | ERel _ => false
+ | ENamed _ => false
+ | ECon (_, _, eo) => (case eo of NONE => false | SOME e => impure e)
+ | ENone _ => false
+ | ESome (_, e) => impure e
+ | EFfi _ => false
+ | EFfiApp (m, x, _) => Settings.isEffectful (m, x) orelse Settings.isBenignEffectful (m, x)
+ | EApp ((EFfi _, _), _) => false
+ | EApp _ => true
+
+ | EUnop (_, e) => impure e
+ | EBinop (_, _, e1, e2) => impure e1 orelse impure e2
+
+ | ERecord xes => List.exists (fn (_, e, _) => impure e) xes
+ | EField (e, _) => impure e
+
+ | ECase (e, pes, _) => impure e orelse List.exists (fn (_, e) => impure e) pes
+
+ | EError _ => true
+ | EReturnBlob {blob = NONE, mimeType = e2, ...} => impure e2
+ | EReturnBlob {blob = SOME e1, mimeType = e2, ...} => impure e1 orelse impure e2
+ | ERedirect (e, _) => impure e
+
+ | EStrcat (e1, e2) => impure e1 orelse impure e2
+
+ | ESeq (e1, e2) => impure e1 orelse impure e2
+ | ELet (_, _, e1, e2) => impure e1 orelse impure e2
+
+ | EClosure (_, es) => List.exists impure es
+ | EJavaScript (_, e) => impure e
+ | ESignalReturn e => impure e
+ | ESignalBind (e1, e2) => impure e1 orelse impure e2
+ | ESignalSource e => impure e
+ | EServerCall _ => true
+ | ERecv _ => true
+ | ESleep _ => true
+ | ESpawn _ => true
+
+val liftExpInExp = Monoize.liftExpInExp
+
+fun multiLift n e =
+ case n of
+ 0 => e
+ | _ => multiLift (n - 1) (liftExpInExp 0 e)
+
+val subExpInExp' =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn (xn, rep) => fn e =>
+ case e of
+ ERel xn' =>
+ (case Int.compare (xn', xn) of
+ EQUAL => #1 rep
+ | GREATER=> ERel (xn' - 1)
+ | LESS => e)
+ | _ => e,
+ bind = fn ((xn, rep), U.Exp.RelE _) => (xn+1, liftExpInExp 0 rep)
+ | (ctx, _) => ctx}
+
+fun subExpInExp (n, e1) e2 =
+ let
+ val r = subExpInExp' (n, e1) e2
+ in
+ (*Print.prefaces "subExpInExp" [("e1", MonoPrint.p_exp MonoEnv.empty e1),
+ ("e2", MonoPrint.p_exp MonoEnv.empty e2),
+ ("r", MonoPrint.p_exp MonoEnv.empty r)];*)
+ r
+ end
+
+fun typ c = c
+
+val swapExpVars =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn lower => fn e =>
+ case e of
+ ERel xn =>
+ if xn = lower then
+ ERel (lower + 1)
+ else if xn = lower + 1 then
+ ERel lower
+ else
+ e
+ | _ => e,
+ bind = fn (lower, U.Exp.RelE _) => lower+1
+ | (lower, _) => lower}
+
+val swapExpVarsPat =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn (lower, len) => fn e =>
+ case e of
+ ERel xn =>
+ if xn = lower then
+ ERel (lower + len)
+ else if xn >= lower + 1 andalso xn < lower + 1 + len then
+ ERel (xn - 1)
+ else
+ e
+ | _ => e,
+ bind = fn ((lower, len), U.Exp.RelE _) => (lower+1, len)
+ | (st, _) => st}
+
+datatype result = Yes of (string * typ * exp) list | No | Maybe
+
+fun match (env, p : pat, e : exp) =
+ case (#1 p, #1 e) of
+ (PVar (x, t), _) => Yes ((x, t, e) :: env)
+
+ | (PPrim (Prim.String (_, s)), EStrcat ((EPrim (Prim.String (_, s')), _), _)) =>
+ if String.isPrefix s' s then
+ Maybe
+ else
+ No
+
+ | (PPrim (Prim.String (_, s)), EStrcat (_, (EPrim (Prim.String (_, s')), _))) =>
+ if String.isSuffix s' s then
+ Maybe
+ else
+ No
+
+ | (PPrim p, EPrim p') =>
+ if Prim.equal (p, p') then
+ Yes env
+ else
+ No
+
+ | (PPrim (Prim.String (_, s)), _) =>
+ let
+ fun lengthLb (e : exp) =
+ case #1 e of
+ EStrcat (e1, e2) => lengthLb e1 + lengthLb e2
+ | EPrim (Prim.String (_, s)) => size s
+ | _ => 0
+ in
+ if lengthLb e > size s then
+ No
+ else
+ Maybe
+ end
+
+ | (PCon (_, PConVar n1, po), ECon (_, PConVar n2, eo)) =>
+ if n1 = n2 then
+ case (po, eo) of
+ (NONE, NONE) => Yes env
+ | (SOME p, SOME e) => match (env, p, e)
+ | _ => Maybe
+ else
+ No
+
+ | (PCon (_, PConFfi {mod = m1, con = con1, ...}, NONE), ECon (_, PConFfi {mod = m2, con = con2, ...}, NONE)) =>
+ if m1 = m2 andalso con1 = con2 then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConFfi {mod = m1, con = con1, ...}, SOME ep), ECon (_, PConFfi {mod = m2, con = con2, ...}, SOME e)) =>
+ if m1 = m2 andalso con1 = con2 then
+ match (env, p, e)
+ else
+ No
+
+ | (PRecord xps, ERecord xes) =>
+ let
+ fun consider (xps, env) =
+ case xps of
+ [] => Yes env
+ | (x, p, _) :: rest =>
+ case List.find (fn (x', _, _) => x' = x) xes of
+ NONE => No
+ | SOME (_, e, _) =>
+ case match (env, p, e) of
+ No => No
+ | Maybe => Maybe
+ | Yes env => consider (rest, env)
+ in
+ consider (xps, env)
+ end
+
+ | (PNone _, ENone _) => Yes env
+ | (PNone _, ESome _) => No
+ | (PSome (_, p), ESome (_, e)) => match (env, p, e)
+ | (PSome _, ENone _) => No
+
+ | _ => Maybe
+
+datatype event =
+ WritePage
+ | ReadDb
+ | WriteDb
+ | ReadCookie
+ | WriteCookie
+ | UseRel
+ | Unsure
+ | Abort
+
+fun p_event e =
+ let
+ open Print.PD
+ in
+ case e of
+ WritePage => string "WritePage"
+ | ReadDb => string "ReadDb"
+ | WriteDb => string "WriteDb"
+ | ReadCookie => string "ReadCookie"
+ | WriteCookie => string "WriteCookie"
+ | UseRel => string "UseRel"
+ | Unsure => string "Unsure"
+ | Abort => string "Abort"
+ end
+
+val p_events = Print.p_list p_event
+
+fun patBinds (p, _) =
+ case p of
+ PVar _ => 1
+ | PPrim _ => 0
+ | PCon (_, _, NONE) => 0
+ | PCon (_, _, SOME p) => patBinds p
+ | PRecord xpts => foldl (fn ((_, p, _), n) => n + patBinds p) 0 xpts
+ | PNone _ => 0
+ | PSome (_, p) => patBinds p
+
+val countFree = U.Exp.foldB {typ = fn (_, n) => n,
+ exp = fn (x, e, n) =>
+ case e of
+ ERel x' => if x = x' then n + 1 else n
+ | _ => n,
+ bind = fn (n, b) =>
+ case b of
+ U.Exp.RelE _ => n + 1
+ | _ => n}
+
+val freeInAbs = U.Exp.existsB {typ = fn _ => false,
+ exp = fn (n, e) =>
+ case e of
+ EAbs (_, _, _, b) => countFree n 0 b > 0
+ | EJavaScript (_, b) => countFree n 0 b > 0
+ | _ => false,
+ bind = fn (n, b) =>
+ case b of
+ U.Exp.RelE _ => n + 1
+ | _ => n} 0
+
+val yankedCase = ref false
+
+fun reduce' (file : file) =
+ let
+ val (timpures, impures, absCounts) =
+ foldl (fn ((d, _), (timpures, impures, absCounts)) =>
+ let
+ fun countAbs env e =
+ case #1 e of
+ EAbs (x, t, _, e) => 1 + countAbs (E.pushERel env x t NONE) e
+ | _ =>
+ let
+ fun remaining e =
+ case #1 e of
+ ENamed n => IM.find (absCounts, n)
+ | EApp (e, arg) =>
+ if simpleImpure true (timpures, impures) env arg then
+ NONE
+ else
+ (case remaining e of
+ NONE => NONE
+ | SOME n => if n > 0 then
+ SOME (n - 1)
+ else
+ NONE)
+ | _ => NONE
+ in
+ getOpt (remaining e, 0)
+ end
+ in
+ case d of
+ DDatatype dts =>
+ (if List.exists (fn (_, _, cs) =>
+ List.exists (fn (_, _, NONE) => false
+ | (_, _, SOME t) => simpleTypeImpure timpures t) cs)
+ dts then
+ IS.addList (timpures, map #2 dts)
+ else
+ timpures,
+ impures,
+ absCounts)
+ | DVal (_, n, _, e, _) =>
+ (timpures,
+ if simpleImpure true (timpures, impures) E.empty e then
+ IS.add (impures, n)
+ else
+ impures,
+ IM.insert (absCounts, n, countAbs E.empty e))
+ | DValRec vis =>
+ (timpures,
+ if List.exists (fn (_, _, _, e, _) => simpleImpure true (timpures, impures) E.empty e) vis then
+ foldl (fn ((_, n, _, _, _), impures) =>
+ IS.add (impures, n)) impures vis
+ else
+ impures,
+ foldl (fn ((x, n, _, e, _), absCounts) =>
+ IM.insert (absCounts, n, countAbs E.empty e))
+ absCounts vis)
+ | _ => (timpures, impures, absCounts)
+ end)
+ (IS.empty, IS.empty, IM.empty) (#1 file)
+
+ val uses = U.File.fold {typ = fn (_, m) => m,
+ exp = fn (e, m) =>
+ case e of
+ ENamed n => IM.insert (m, n, 1 + Option.getOpt (IM.find (m, n), 0))
+ | _ => m,
+ decl = fn (_, m) => m}
+ IM.empty file
+
+ val size = U.Exp.fold {typ = fn (_, n) => n,
+ exp = fn (_, n) => n + 1} 0
+
+ val functionInside' = U.Typ.exists (fn c => case c of
+ TFun _ => true
+ | _ => false)
+
+ fun functionInside t =
+ case #1 t of
+ TFun (t1, t2) => functionInside' t1 orelse functionInside t2
+ | _ => functionInside' t
+
+ fun mayInline (n, e, t, s) =
+ case IM.find (uses, n) of
+ NONE => false
+ | SOME count => not (Settings.checkNeverInline s)
+ andalso (count <= 1
+ orelse size e <= Settings.getMonoInline ()
+ orelse functionInside t
+ orelse Settings.checkAlwaysInline s)
+
+ fun summarize d (e, _) =
+ let
+ val s =
+ case e of
+ EPrim _ => []
+ | ERel n => if n = d then [UseRel] else []
+ | ENamed _ => []
+ | ECon (_, _, NONE) => []
+ | ECon (_, _, SOME e) => summarize d e
+ | ENone _ => []
+ | ESome (_, e) => summarize d e
+ | EFfi _ => []
+ | EFfiApp ("Basis", "get_cookie", [(e, _)]) =>
+ summarize d e @ [ReadCookie]
+ | EFfiApp ("Basis", "set_cookie", es) =>
+ List.concat (map (summarize d o #1) es) @ [WriteCookie]
+ | EFfiApp ("Basis", "clear_cookie", es) =>
+ List.concat (map (summarize d o #1) es) @ [WriteCookie]
+ | EFfiApp (m, x, es) =>
+ if Settings.isEffectful (m, x) orelse Settings.isBenignEffectful (m, x) then
+ List.concat (map (summarize d o #1) es) @ [if m = "Basis" andalso String.isSuffix "_w" x then
+ WritePage
+ else
+ Unsure]
+ else
+ List.concat (map (summarize d o #1) es)
+ | EApp ((EFfi _, _), e) => summarize d e
+ | EApp _ =>
+ let
+ fun unravel (e, passed, ls) =
+ case e of
+ ENamed n =>
+ let
+ val ls = rev ls
+ in
+ if IS.member (impures, n) then
+ case IM.find (absCounts, n) of
+ NONE => [Unsure]
+ | SOME len =>
+ if passed < len then
+ ls
+ else
+ ls @ [Unsure]
+ else
+ ls
+ end
+ | ERel n => List.revAppend (ls,
+ if n = d then
+ [UseRel, Unsure]
+ else
+ [Unsure])
+ | EApp (f, x) =>
+ unravel (#1 f, passed + 1, List.revAppend (summarize d x,
+ ls))
+ | EError _ => [Abort]
+ | _ => [Unsure]
+ in
+ unravel (e, 0, [])
+ end
+
+ | EAbs _ => []
+
+ | EUnop (_, e) => summarize d e
+ | EBinop (_, _, e1, e2) => summarize d e1 @ summarize d e2
+
+ | ERecord xets => List.concat (map (summarize d o #2) xets)
+ | EField (e, _) => summarize d e
+
+ | ECase (e, pes, _) =>
+ let
+ val lss = map (fn (p, e) => summarize (if d = ~1 then ~1 else d + patBinds p) e) pes
+
+ fun splitRel ls acc =
+ case ls of
+ [] => (acc, false, ls)
+ | UseRel :: ls => (acc, true, ls)
+ | v :: ls => splitRel ls (v :: acc)
+
+ val (pre, used, post) = foldl (fn (ls, (pre, used, post)) =>
+ let
+ val (pre', used', post') = splitRel ls []
+ in
+ (pre' @ pre, used' orelse used, post' @ post)
+ end)
+ ([], false, []) lss
+ in
+ summarize d e
+ @ pre
+ @ (if used then [UseRel] else [])
+ @ post
+ end
+ | EStrcat (e1, e2) => summarize d e1 @ summarize d e2
+
+ | EError (e, _) => summarize d e @ [Abort]
+ | EReturnBlob {blob = NONE, mimeType = e2, ...} => summarize d e2 @ [Abort]
+ | EReturnBlob {blob = SOME e1, mimeType = e2, ...} => summarize d e1 @ summarize d e2 @ [Abort]
+ | ERedirect (e, _) => summarize d e @ [Abort]
+
+ | EWrite e => summarize d e @ [WritePage]
+
+ | ESeq (e1, e2) => summarize d e1 @ summarize d e2
+ | ELet (_, _, e1, e2) => summarize d e1 @ summarize (if d = ~1 then ~1 else d + 1) e2
+
+ | EClosure (_, es) => List.concat (map (summarize d) es)
+
+ | EQuery {query, body, initial, ...} =>
+ List.concat [summarize d query,
+ summarize d initial,
+ [ReadDb],
+ summarize (if d = ~1 then ~1 else d + 2) body]
+
+ | EDml (e, _) => summarize d e @ [WriteDb]
+ | ENextval e => summarize d e @ [WriteDb]
+ | ESetval (e1, e2) => summarize d e1 @ summarize d e2 @ [WriteDb]
+ | EUnurlify (e, _, _) => summarize d e
+ | EJavaScript (_, e) => summarize d e
+ | ESignalReturn e => summarize d e
+ | ESignalBind (e1, e2) => summarize d e1 @ summarize d e2
+ | ESignalSource e => summarize d e
+
+ | EServerCall (e, _, _, _) => summarize d e @ [Unsure]
+ | ERecv (e, _) => summarize d e @ [Unsure]
+ | ESleep e => summarize d e @ [Unsure]
+ | ESpawn e => summarize d e @ [Unsure]
+ in
+ (*Print.prefaces "Summarize"
+ [("e", MonoPrint.p_exp MonoEnv.empty (e, ErrorMsg.dummySpan)),
+ ("d", Print.PD.string (Int.toString d)),
+ ("s", p_events s)];*)
+ s
+ end
+
+ val impure = fn env => fn e =>
+ simpleImpure false (timpures, impures) env e andalso impure e
+ andalso not (List.null (summarize ~1 e))
+
+ fun passive (e : exp) =
+ case #1 e of
+ EPrim _ => true
+ | ERel _ => true
+ | ENamed _ => true
+ | ECon (_, _, NONE) => true
+ | ECon (_, _, SOME e) => passive e
+ | ENone _ => true
+ | ESome (_, e) => passive e
+ | EFfi _ => true
+ | EAbs _ => true
+ | ERecord xets => List.all (passive o #2) xets
+ | EField (e, _) => passive e
+ | _ => false
+
+ fun exp env e =
+ let
+ (*val () = Print.prefaces "exp" [("e", MonoPrint.p_exp env (e, ErrorMsg.dummySpan))]*)
+
+ fun doLet (x, t, e', b) =
+ let
+ fun doSub () =
+ let
+ val r = subExpInExp (0, e') b
+ in
+ (*Print.prefaces "doSub" [("e'", MonoPrint.p_exp env e'),
+ ("b", MonoPrint.p_exp (E.pushERel env x t NONE) b),
+ ("r", MonoPrint.p_exp env r)];*)
+ #1 (reduceExp env r)
+ end
+
+ fun trySub () =
+ ((*Print.prefaces "trySub"
+ [("e", MonoPrint.p_exp env (e, ErrorMsg.dummySpan))];*)
+ case t of
+ (TFfi ("Basis", "string"), _) => doSub ()
+ | (TSignal _, _) => e
+ | _ =>
+ case e' of
+ (ECase _, _) => e
+ | _ => doSub ())
+
+ fun isRecord () =
+ case #1 e' of
+ ERecord _ => true
+ | _ => false
+
+ fun prefixFrom i (e : exp) =
+ case #1 e of
+ ERel i' => if i' = i then SOME [] else NONE
+ | EField (e', s) =>
+ (case prefixFrom i e' of
+ NONE => NONE
+ | SOME ss => SOME (ss @ [s]))
+ | _ => NONE
+
+ fun whichProj i (e : exp) =
+ case #1 e of
+ EPrim _ => SOME SLS.empty
+ | ERel i' => if i' = i then NONE else SOME SLS.empty
+ | ENamed _ => SOME SLS.empty
+ | ECon (_, _, NONE) => SOME SLS.empty
+ | ECon (_, _, SOME e') => whichProj i e'
+ | ENone _ => SOME SLS.empty
+ | ESome (_, e') => whichProj i e'
+ | EFfi _ => SOME SLS.empty
+ | EFfiApp (_, _, es) => whichProjs i (map #1 es)
+ | EApp (e1, e2) => whichProjs i [e1, e2]
+ | EAbs (_, _, _, e) => whichProj (i + 1) e
+ | EUnop (_, e1) => whichProj i e1
+ | EBinop (_, _, e1, e2) => whichProjs i [e1, e2]
+ | ERecord xets => whichProjs i (map #2 xets)
+ | EField (e1, s) =>
+ (case prefixFrom i e1 of
+ NONE => SOME SLS.empty
+ | SOME ss => SOME (SLS.singleton (ss @ [s])))
+ | ECase (e1, pes, _) =>
+ whichProjs' i ((0, e1)
+ :: map (fn (p, e) => (patBinds p, e)) pes)
+ | EStrcat (e1, e2) => whichProjs i [e1, e2]
+ | EError (e1, _) => whichProj i e1
+ | EReturnBlob {blob = NONE, mimeType = e2, ...} => whichProj i e2
+ | EReturnBlob {blob = SOME e1, mimeType = e2, ...} => whichProjs i [e1, e2]
+ | ERedirect (e1, _) => whichProj i e1
+ | EWrite e1 => whichProj i e1
+ | ESeq (e1, e2) => whichProjs i [e1, e2]
+ | ELet (_, _, e1, e2) => whichProjs' i [(0, e1), (1, e2)]
+ | EClosure (_, es) => whichProjs i es
+ | EQuery {query = e1, body = e2, initial = e3, ...} =>
+ whichProjs' i [(0, e1), (2, e2), (0, e3)]
+ | EDml (e1, _) => whichProj i e1
+ | ENextval e1 => whichProj i e1
+ | ESetval (e1, e2) => whichProjs i [e1, e2]
+ | EUnurlify (e1, _, _) => whichProj i e1
+ | EJavaScript (_, e1) => whichProj i e1
+ | ESignalReturn e1 => whichProj i e1
+ | ESignalBind (e1, e2) => whichProjs i [e1, e2]
+ | ESignalSource e1 => whichProj i e1
+ | EServerCall (e1, _, _, _) => whichProj i e1
+ | ERecv (e1, _) => whichProj i e1
+ | ESleep e1 => whichProj i e1
+ | ESpawn e1 => whichProj i e1
+
+ and whichProjs i es =
+ whichProjs' i (map (fn e => (0, e)) es)
+
+ and whichProjs' i es =
+ case es of
+ [] => SOME SLS.empty
+ | (n, e) :: es' =>
+ case (whichProj (i + n) e, whichProjs' i es') of
+ (SOME m1, SOME m2) =>
+ if SLS.isEmpty (SLS.intersection (m1, m2)) then
+ SOME (SLS.union (m1, m2))
+ else
+ NONE
+ | _ => NONE
+ in
+ if impure env e' then
+ let
+ val effs_e' = summarize 0 e'
+ val effs_e' = List.filter (fn x => x <> UseRel) effs_e'
+ val effs_b = summarize 0 b
+
+ (*val () = Print.prefaces "Try"
+ [(*("e", MonoPrint.p_exp env (e, ErrorMsg.dummySpan)),*)
+ ("e'", MonoPrint.p_exp env e'),
+ ("b", MonoPrint.p_exp (E.pushERel env x t NONE) b),
+ ("e'_eff", p_events effs_e'),
+ ("b_eff", p_events effs_b)]*)
+
+ fun does eff = List.exists (fn eff' => eff' = eff) effs_e'
+ val writesPage = does WritePage
+ val readsDb = does ReadDb
+ val writesDb = does WriteDb
+ val readsCookie = does ReadCookie
+ val writesCookie = does ReadCookie
+
+ fun verifyUnused eff =
+ case eff of
+ UseRel => false
+ | _ => true
+
+ fun verifyCompatible effs =
+ case effs of
+ [] => false
+ | eff :: effs =>
+ case eff of
+ Unsure => false
+ | UseRel => List.all verifyUnused effs
+ | WritePage => not writesPage andalso verifyCompatible effs
+ | ReadDb => not writesDb andalso verifyCompatible effs
+ | WriteDb => not writesDb andalso not readsDb andalso verifyCompatible effs
+ | ReadCookie => not writesCookie andalso verifyCompatible effs
+ | WriteCookie => not writesCookie andalso not readsCookie
+ andalso verifyCompatible effs
+ | Abort => true
+ in
+ (*Print.prefaces "verifyCompatible"
+ [("e'", MonoPrint.p_exp env e'),
+ ("b", MonoPrint.p_exp (E.pushERel env x t NONE) b),
+ ("effs_e'", Print.p_list p_event effs_e'),
+ ("effs_b", Print.p_list p_event effs_b)];*)
+ if (List.null effs_e'
+ orelse (List.all (fn eff => eff <> Unsure) effs_e'
+ andalso verifyCompatible effs_b)
+ orelse (case effs_b of
+ UseRel :: effs => List.all verifyUnused effs
+ | _ => false))
+ andalso countFree 0 0 b = 1
+ andalso not (freeInAbs b) then
+ trySub ()
+ else
+ e
+ end
+ else if countFree 0 0 b > 1
+ andalso not (!fullMode)
+ andalso not (passive e')
+ andalso not (isRecord () andalso Option.isSome (whichProj 0 b)) then
+ e
+ else
+ trySub ()
+ end
+
+ val r =
+ case e of
+ ERel n =>
+ (case E.lookupERel env n of
+ (_, _, SOME e') => #1 e'
+ | _ => e)
+ | ENamed n =>
+ (case E.lookupENamed env n of
+ (_, _, SOME e', _) => ((*Print.prefaces "Switch" [("n", Print.PD.string (Int.toString n)),
+ ("e'", MonoPrint.p_exp env e')];*)
+ #1 e')
+ | _ => e)
+
+ | EApp ((EAbs (x, t, _, e1), loc), e2) =>
+ ((*Print.prefaces "Considering" [("e1", MonoPrint.p_exp (E.pushERel env x t NONE) e1),
+ ("e2", MonoPrint.p_exp env e2),
+ ("sub", MonoPrint.p_exp env (reduceExp env (subExpInExp (0, e2) e1)))];*)
+ if impure env e2 orelse (not (!fullMode) andalso countFree 0 0 e1 > 1) then
+ #1 (reduceExp env (ELet (x, t, e2, e1), loc))
+ else
+ #1 (reduceExp env (subExpInExp (0, e2) e1)))
+
+ | ECase (e', pes, {disc, result}) =>
+ let
+ fun push () =
+ case result of
+ (TFun (dom, result), loc) =>
+ let
+ fun safe e =
+ List.all (fn UseRel => true
+ | Abort => true
+ | _ => false) (summarize 0 e)
+
+ fun p_events' es = Print.box [Print.PD.string "{",
+ p_events es,
+ Print.PD.string "}"]
+ in
+ if List.all (safe o #2) pes then
+ (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
+ | _ => e
+
+ fun search pes =
+ case pes of
+ [] => push ()
+ | (p, body) :: pes =>
+ case match ([], p, e') of
+ No => search pes
+ | Maybe => push ()
+ | Yes subs =>
+ let
+ val (body, remaining) =
+ foldl (fn ((x, t, e), (body, remaining)) =>
+ (if countFree 0 0 body > 1 then
+ (ELet (x, t, multiLift remaining e, body), #2 e')
+ else
+ subExpInExp (0, multiLift remaining e) body, remaining - 1))
+ (body, length subs - 1) subs
+ val r = reduceExp (E.patBinds env p) body
+ in
+ (*Print.preface ("subs", Print.p_list (MonoPrint.p_exp env) subs);*)
+ (*Print.prefaces "ECase"
+ [("old", MonoPrint.p_exp env body),
+ ("body", MonoPrint.p_exp env body),
+ ("r", MonoPrint.p_exp env r)];*)
+ #1 r
+ end
+ in
+ if impure env e' then
+ e
+ else
+ search pes
+ end
+
+ | 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
+ val e' = (ELet (x2, t2, e1,
+ (ELet (x1, t1, b1,
+ liftExpInExp 1 b2), loc)), loc)
+ in
+ (*Print.prefaces "ELet commute" [("e", MonoPrint.p_exp env (e, loc)),
+ ("e'", MonoPrint.p_exp env e')];*)
+ #1 (reduceExp env e')
+ end
+ | EApp ((ELet (x, t, e, b), loc), e') =>
+ #1 (reduceExp env (ELet (x, t, e,
+ (EApp (b, liftExpInExp 0 e'), loc)), loc))
+
+ | ELet (x, t, e', b as (EAbs (x', t' as (TRecord [], _), ran, e''), loc)) =>
+ if impure env e' then
+ doLet (x, t, e', b)
+ else
+ EAbs (x', t', ran, reduceExp (E.pushERel env x' t' NONE)
+ (ELet (x, t, liftExpInExp 0 e', swapExpVars 0 e''), loc))
+
+ | ELet (x, t, e', b) => doLet (x, t, e', b)
+
+ | EStrcat ((EPrim (Prim.String (k1, s1)), _), (EPrim (Prim.String (k2, s2)), _)) =>
+ EPrim (Prim.String ((case (k1, k2) of
+ (Prim.Html, Prim.Html) => Prim.Html
+ | _ => Prim.Normal), s1 ^ s2))
+
+ | ESignalBind ((ESignalReturn e1, loc), e2) =>
+ #1 (reduceExp env (EApp (e2, e1), loc))
+
+ | _ => e
+ in
+ (*Print.prefaces "exp'" [("e", MonoPrint.p_exp env (e, ErrorMsg.dummySpan)),
+ ("r", MonoPrint.p_exp env (r, ErrorMsg.dummySpan))];*)
+ r
+ end
+
+ and bind (env, b) =
+ case b of
+ U.Decl.Datatype (x, n, xncs) => E.pushDatatype env x n xncs
+ | U.Decl.RelE (x, t) => E.pushERel env x t NONE
+ | U.Decl.NamedE (x, n, t, eo, s) =>
+ let
+ val eo = case eo of
+ NONE => NONE
+ | SOME e => if mayInline (n, e, t, s) then
+ SOME e
+ else
+ NONE
+ in
+ E.pushENamed env x n t (Option.map (reduceExp env) eo) s
+ end
+
+ and reduceExp env = U.Exp.mapB {typ = typ, exp = exp, bind = bind} env
+
+ fun decl env d = ((*Print.preface ("d", MonoPrint.p_decl env (d, ErrorMsg.dummySpan));*)
+ d)
+ in
+ 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
diff --git a/src/mono_shake.sig b/src/mono_shake.sig
new file mode 100644
index 0000000..813bc52
--- /dev/null
+++ b/src/mono_shake.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Remove unused definitions from a file *)
+
+signature MONO_SHAKE = sig
+
+ val shake : Mono.file -> Mono.file
+
+end
diff --git a/src/mono_shake.sml b/src/mono_shake.sml
new file mode 100644
index 0000000..5818fea
--- /dev/null
+++ b/src/mono_shake.sml
@@ -0,0 +1,164 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Remove unused definitions from a file *)
+
+structure MonoShake :> MONO_SHAKE = struct
+
+open Mono
+
+structure U = MonoUtil
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+type free = {
+ con : IS.set,
+ exp : IS.set
+}
+
+fun shake (file : file) =
+ let
+ val (cdef, edef) = foldl (fn ((DDatatype dts, _), (cdef, edef)) =>
+ (foldl (fn ((_, n, xncs), cdef) => IM.insert (cdef, n, xncs)) cdef dts, edef)
+ | ((DVal (_, n, t, e, _), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, (t, e)))
+ | ((DValRec vis, _), (cdef, edef)) =>
+ (cdef, foldl (fn ((_, n, t, e, _), edef) => IM.insert (edef, n, (t, e))) edef vis)
+ | ((DExport _, _), acc) => acc
+ | ((DTable _, _), acc) => acc
+ | ((DSequence _, _), acc) => acc
+ | ((DView _, _), acc) => acc
+ | ((DDatabase _, _), acc) => acc
+ | ((DJavaScript _, _), acc) => acc
+ | ((DCookie _, _), acc) => acc
+ | ((DStyle _, _), acc) => acc
+ | ((DTask _, _), acc) => acc
+ | ((DPolicy _, _), acc) => acc
+ | ((DOnError _, _), acc) => acc)
+ (IM.empty, IM.empty) (#1 file)
+
+ fun typ (c, s) =
+ case c of
+ TDatatype (n, _) =>
+ if IS.member (#con s, n) then
+ s
+ else
+ let
+ val s' = {exp = #exp s,
+ con = IS.add (#con s, n)}
+ in
+ case IM.find (cdef, n) of
+ NONE => s'
+ | SOME xncs => foldl (fn ((_, _, to), s) =>
+ case to of
+ NONE => s
+ | SOME t => shakeTyp s t)
+ s' xncs
+ end
+ | _ => s
+
+ and shakeTyp s = U.Typ.fold typ s
+
+ fun exp (e, s) =
+ case e of
+ ENamed n =>
+ if IS.member (#exp s, n) then
+ s
+ else
+ let
+ val s' = {exp = IS.add (#exp s, n),
+ con = #con s}
+ in
+ case IM.find (edef, n) of
+ NONE => s'
+ | SOME (t, e) => shakeExp s' e
+ end
+ | _ => s
+
+ and shakeExp s = U.Exp.fold {typ = typ, exp = exp} s
+
+ fun usedVars (cs, es) e =
+ let
+ val {con = cs', exp = es'} = shakeExp {con = cs, exp = es} e
+ in
+ (cs', es')
+ end
+
+ val (page_cs, page_es) =
+ List.foldl
+ (fn ((DExport (_, _, n, _, _, _), _), (page_cs, page_es)) => (page_cs, IS.add (page_es, n))
+ | ((DDatabase {expunge = n1, initialize = n2, ...}, _), (page_cs, page_es)) =>
+ (page_cs, IS.addList (page_es, [n1, n2]))
+ | ((DTask (e1, e2), _), st) => usedVars (usedVars st e2) e1
+ | ((DTable (_, xts, e1, e2), _), st) => usedVars (usedVars (usedVars st e1) e2)
+ (ERecord (map (fn (x, t) => (x, (ERecord [], #2 e1), t)) xts), #2 e1)
+ | ((DView (_, _, e), _), st) => usedVars st e
+ | ((DPolicy pol, _), st) =>
+ let
+ val e1 = case pol of
+ PolClient e1 => e1
+ | PolInsert e1 => e1
+ | PolDelete e1 => e1
+ | PolUpdate e1 => e1
+ | PolSequence e1 => e1
+ in
+ usedVars st e1
+ end
+ | ((DOnError n, _), (page_cs, page_es)) => (page_cs, IS.add (page_es, n))
+ | (_, st) => st) (IS.empty, IS.empty) (#1 file)
+
+ val s = {con = page_cs, exp = page_es}
+
+ val s = IS.foldl (fn (n, s) =>
+ case IM.find (cdef, n) of
+ NONE => raise Fail "MonoShake: Couldn't find 'datatype'"
+ | SOME xncs => foldl (fn ((_, _, SOME c), s) => shakeTyp s c
+ | _ => s) s xncs) s page_cs
+
+ val s = IS.foldl (fn (n, s) =>
+ case IM.find (edef, n) of
+ NONE => raise Fail "MonoShake: Couldn't find 'val'"
+ | SOME (t, e) => shakeExp s e) s page_es
+ in
+ (List.filter (fn (DDatatype dts, _) => List.exists (fn (_, n, _) => IS.member (#con s, n)) dts
+ | (DVal (_, n, _, _, _), _) => IS.member (#exp s, n)
+ | (DValRec vis, _) => List.exists (fn (_, n, _, _, _) => IS.member (#exp s, n)) vis
+ | (DExport _, _) => true
+ | (DTable _, _) => true
+ | (DSequence _, _) => true
+ | (DView _, _) => true
+ | (DDatabase _, _) => true
+ | (DJavaScript _, _) => true
+ | (DCookie _, _) => true
+ | (DStyle _, _) => true
+ | (DTask _, _) => true
+ | (DPolicy _, _) => true
+ | (DOnError _, _) => true) (#1 file), #2 file)
+ end
+
+end
diff --git a/src/mono_util.sig b/src/mono_util.sig
new file mode 100644
index 0000000..5c078a7
--- /dev/null
+++ b/src/mono_util.sig
@@ -0,0 +1,161 @@
+(* Copyright (c) 2008, 2013, 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.
+ *)
+
+signature MONO_UTIL = sig
+
+structure Typ : sig
+ val compare : Mono.typ * Mono.typ -> order
+ val sortFields : (string * Mono.typ) list -> (string * Mono.typ) list
+
+ val mapfold : (Mono.typ', 'state, 'abort) Search.mapfolder
+ -> (Mono.typ, 'state, 'abort) Search.mapfolder
+
+ val map : (Mono.typ' -> Mono.typ')
+ -> Mono.typ -> Mono.typ
+
+ val fold : (Mono.typ' * 'state -> 'state)
+ -> 'state -> Mono.typ -> 'state
+
+ val exists : (Mono.typ' -> bool) -> Mono.typ -> bool
+end
+
+structure Exp : sig
+ datatype binder =
+ Datatype of string * int * (string * int * Mono.typ option) list
+ | RelE of string * Mono.typ
+ | NamedE of string * int * Mono.typ * Mono.exp option * string
+
+ val mapfoldB : {typ : (Mono.typ', 'state, 'abort) Search.mapfolder,
+ exp : ('typtext, Mono.exp', 'state, 'abort) Search.mapfolderB,
+ bind : 'typtext * binder -> 'typtext}
+ -> ('typtext, Mono.exp, 'state, 'abort) Search.mapfolderB
+ val mapfold : {typ : (Mono.typ', 'state, 'abort) Search.mapfolder,
+ exp : (Mono.exp', 'state, 'abort) Search.mapfolder}
+ -> (Mono.exp, 'state, 'abort) Search.mapfolder
+
+ val map : {typ : Mono.typ' -> Mono.typ',
+ exp : Mono.exp' -> Mono.exp'}
+ -> Mono.exp -> Mono.exp
+ val mapB : {typ : Mono.typ' -> Mono.typ',
+ exp : 'typtext -> Mono.exp' -> Mono.exp',
+ bind : 'typtext * binder -> 'typtext}
+ -> 'typtext -> (Mono.exp -> Mono.exp)
+
+ val fold : {typ : Mono.typ' * 'state -> 'state,
+ exp : Mono.exp' * 'state -> 'state}
+ -> 'state -> Mono.exp -> 'state
+
+ val exists : {typ : Mono.typ' -> bool,
+ exp : Mono.exp' -> bool} -> Mono.exp -> bool
+
+ val existsB : {typ : Mono.typ' -> bool,
+ exp : 'context * Mono.exp' -> bool,
+ bind : 'context * binder -> 'context} -> 'context -> Mono.exp -> bool
+
+ val foldB : {typ : Mono.typ' * 'state -> 'state,
+ exp : 'context * Mono.exp' * 'state -> 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Mono.exp -> 'state
+
+ val appLoc : (Mono.exp -> unit) -> Mono.exp -> unit
+end
+
+structure Decl : sig
+ datatype binder = datatype Exp.binder
+
+ val mapfoldB : {typ : (Mono.typ', 'state, 'abort) Search.mapfolder,
+ exp : ('typtext, Mono.exp', 'state, 'abort) Search.mapfolderB,
+ decl : ('typtext, Mono.decl', 'state, 'abort) Search.mapfolderB,
+ bind : 'typtext * binder -> 'typtext}
+ -> ('typtext, Mono.decl, 'state, 'abort) Search.mapfolderB
+ val mapfold : {typ : (Mono.typ', 'state, 'abort) Search.mapfolder,
+ exp : (Mono.exp', 'state, 'abort) Search.mapfolder,
+ decl : (Mono.decl', 'state, 'abort) Search.mapfolder}
+ -> (Mono.decl, 'state, 'abort) Search.mapfolder
+
+ val fold : {typ : Mono.typ' * 'state -> 'state,
+ exp : Mono.exp' * 'state -> 'state,
+ decl : Mono.decl' * 'state -> 'state}
+ -> 'state -> Mono.decl -> 'state
+
+ val map : {typ : Mono.typ' -> Mono.typ',
+ exp : Mono.exp' -> Mono.exp',
+ decl : Mono.decl' -> Mono.decl'}
+ -> Mono.decl -> Mono.decl
+
+ val foldMap : {typ : Mono.typ' * 'state -> Mono.typ' * 'state,
+ exp : Mono.exp' * 'state -> Mono.exp' * 'state,
+ decl : Mono.decl' * 'state -> Mono.decl' * 'state}
+ -> 'state -> Mono.decl -> Mono.decl * 'state
+
+ val foldMapB : {typ : Mono.typ' * 'state -> Mono.typ' * 'state,
+ exp : 'context * Mono.exp' * 'state -> Mono.exp' * 'state,
+ decl : 'context * Mono.decl' * 'state -> Mono.decl' * 'state,
+ bind : 'context * binder -> 'context}
+ -> 'context -> 'state -> Mono.decl -> Mono.decl * 'state
+
+ val exists : {typ : Mono.typ' -> bool,
+ exp : Mono.exp' -> bool,
+ decl : Mono.decl' -> bool} -> Mono.decl -> bool
+end
+
+structure File : sig
+ datatype binder = datatype Exp.binder
+
+ val mapfoldB : {typ : (Mono.typ', 'state, 'abort) Search.mapfolder,
+ exp : ('typtext, Mono.exp', 'state, 'abort) Search.mapfolderB,
+ decl : ('typtext, Mono.decl', 'state, 'abort) Search.mapfolderB,
+ bind : 'typtext * binder -> 'typtext}
+ -> ('typtext, Mono.file, 'state, 'abort) Search.mapfolderB
+
+ val mapfold : {typ : (Mono.typ', 'state, 'abort) Search.mapfolder,
+ exp : (Mono.exp', 'state, 'abort) Search.mapfolder,
+ decl : (Mono.decl', 'state, 'abort) Search.mapfolder}
+ -> (Mono.file, 'state, 'abort) Search.mapfolder
+
+ val mapB : {typ : Mono.typ' -> Mono.typ',
+ exp : 'typtext -> Mono.exp' -> Mono.exp',
+ decl : 'typtext -> Mono.decl' -> Mono.decl',
+ bind : 'typtext * binder -> 'typtext}
+ -> 'typtext -> Mono.file -> Mono.file
+
+ val map : {typ : Mono.typ' -> Mono.typ',
+ exp : Mono.exp' -> Mono.exp',
+ decl : Mono.decl' -> Mono.decl'}
+ -> Mono.file -> Mono.file
+
+ val fold : {typ : Mono.typ' * 'state -> 'state,
+ exp : Mono.exp' * 'state -> 'state,
+ decl : Mono.decl' * 'state -> 'state}
+ -> 'state -> Mono.file -> 'state
+
+ val maxName : Mono.file -> int
+
+ val appLoc : (Mono.exp -> unit) -> Mono.file -> unit
+end
+
+end
diff --git a/src/mono_util.sml b/src/mono_util.sml
new file mode 100644
index 0000000..fc1a2bc
--- /dev/null
+++ b/src/mono_util.sml
@@ -0,0 +1,825 @@
+(* Copyright (c) 2008, 2013, 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 MonoUtil :> MONO_UTIL = struct
+
+open Mono
+
+structure S = Search
+
+val dummyt = (TRecord [], ErrorMsg.dummySpan)
+
+structure Typ = struct
+
+open Order
+
+fun compare ((t1, _), (t2, _)) =
+ case (t1, t2) of
+ (TFun (d1, r1), TFun (d2, r2)) =>
+ join (compare (d1, d2), fn () => compare (r1, r2))
+ | (TRecord xts1, TRecord xts2) =>
+ let
+ val xts1 = sortFields xts1
+ val xts2 = sortFields xts2
+ in
+ joinL compareFields (xts1, xts2)
+ end
+ | (TDatatype (n1, _), TDatatype (n2, _)) => Int.compare (n1, n2)
+ | (TFfi (m1, x1), TFfi (m2, x2)) => join (String.compare (m1, m2), fn () => String.compare (x1, x2))
+ | (TOption t1, TOption t2) => compare (t1, t2)
+ | (TList t1, TList t2) => compare (t1, t2)
+ | (TSource, TSource) => EQUAL
+ | (TSignal t1, TSignal t2) => compare (t1, t2)
+
+ | (TFun _, _) => LESS
+ | (_, TFun _) => GREATER
+
+ | (TRecord _, _) => LESS
+ | (_, TRecord _) => GREATER
+
+ | (TDatatype _, _) => LESS
+ | (_, TDatatype _) => GREATER
+
+ | (TFfi _, _) => LESS
+ | (_, TFfi _) => GREATER
+
+ | (TOption _, _) => LESS
+ | (_, TOption _) => GREATER
+
+ | (TList _, _) => LESS
+ | (_, TList _) => GREATER
+
+ | (TSource, _) => LESS
+ | (_, TSource) => GREATER
+
+and compareFields ((x1, t1), (x2, t2)) =
+ join (String.compare (x1, x2),
+ fn () => compare (t1, t2))
+
+and sortFields xts = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) xts
+
+fun mapfold fc =
+ let
+ fun mft c acc =
+ S.bindP (mft' c acc, fc)
+
+ and mft' (cAll as (c, loc)) =
+ case c of
+ TFun (t1, t2) =>
+ S.bind2 (mft t1,
+ fn t1' =>
+ S.map2 (mft t2,
+ fn t2' =>
+ (TFun (t1', t2'), loc)))
+ | TRecord xts =>
+ S.map2 (ListUtil.mapfold (fn (x, t) =>
+ S.map2 (mft t,
+ fn t' =>
+ (x, t')))
+ xts,
+ fn xts' => (TRecord xts', loc))
+ | TDatatype _ => S.return2 cAll
+ | TFfi _ => S.return2 cAll
+ | TOption t =>
+ S.map2 (mft t,
+ fn t' =>
+ (TOption t, loc))
+ | TList t =>
+ S.map2 (mft t,
+ fn t' =>
+ (TList t, loc))
+ | TSource => S.return2 cAll
+ | TSignal t =>
+ S.map2 (mft t,
+ fn t' =>
+ (TSignal t, loc))
+ in
+ mft
+ end
+
+fun map typ c =
+ case mapfold (fn c => fn () => S.Continue (typ c, ())) c () of
+ S.Return () => raise Fail "Mono_util.Typ.map"
+ | S.Continue (c, ()) => c
+
+fun fold typ s c =
+ case mapfold (fn c => fn s => S.Continue (c, typ (c, s))) c s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "MonoUtil.Typ.fold: Impossible"
+
+fun exists typ k =
+ case mapfold (fn c => fn () =>
+ if typ c then
+ S.Return ()
+ else
+ S.Continue (c, ())) k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure Exp = struct
+
+datatype binder =
+ Datatype of string * int * (string * int * typ option) list
+ | RelE of string * typ
+ | NamedE of string * int * typ * exp option * string
+
+fun mapfoldB {typ = fc, exp = fe, bind} =
+ let
+ val mft = Typ.mapfold fc
+
+ fun mfe ctx e acc =
+ S.bindP (mfe' ctx e acc, fe ctx)
+
+ and mfet ctx (e, t) =
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mft t,
+ fn t' => (e', t')))
+
+ and mfe' ctx (eAll as (e, loc)) =
+ case e of
+ EPrim _ => S.return2 eAll
+ | ERel _ => S.return2 eAll
+ | ENamed _ => S.return2 eAll
+ | ECon (_, _, NONE) => S.return2 eAll
+ | ECon (dk, n, SOME e) =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (ECon (dk, n, SOME e'), loc))
+ | ENone t =>
+ S.map2 (mft t,
+ fn t' =>
+ (ENone t', loc))
+ | ESome (t, e) =>
+ S.bind2 (mft t,
+ fn t' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (ESome (t', e'), loc)))
+ | EFfi _ => S.return2 eAll
+ | EFfiApp (m, x, es) =>
+ S.map2 (ListUtil.mapfold (fn e => mfet ctx e) es,
+ fn es' =>
+ (EFfiApp (m, x, es'), loc))
+ | EApp (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (EApp (e1', e2'), loc)))
+ | EAbs (x, dom, ran, e) =>
+ S.bind2 (mft dom,
+ fn dom' =>
+ S.bind2 (mft ran,
+ fn ran' =>
+ S.map2 (mfe (bind (ctx, RelE (x, dom'))) e,
+ fn e' =>
+ (EAbs (x, dom', ran', e'), loc))))
+
+ | EUnop (s, e) =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EUnop (s, e'), loc))
+ | EBinop (bi, s, e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (EBinop (bi, s, e1', e2'), loc)))
+
+ | ERecord xes =>
+ S.map2 (ListUtil.mapfold (fn (x, e, t) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mft t,
+ fn t' =>
+ (x, e', t'))))
+ xes,
+ fn xes' =>
+ (ERecord xes', loc))
+ | EField (e, x) =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EField (e', x), loc))
+
+ | ECase (e, pes, {disc, result}) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.bind2 (ListUtil.mapfold (fn (p, e) =>
+ let
+ fun pb ((p, _), ctx) =
+ case p of
+ PVar (x, t) => bind (ctx, RelE (x, t))
+ | PPrim _ => ctx
+ | PCon (_, _, NONE) => ctx
+ | PCon (_, _, SOME p) => pb (p, ctx)
+ | PRecord xps => foldl (fn ((_, p, _), ctx) =>
+ pb (p, ctx)) ctx xps
+ | PNone _ => ctx
+ | PSome (_, p) => pb (p, ctx)
+ in
+ S.map2 (mfe (pb (p, ctx)) e,
+ fn e' => (p, e'))
+ end) pes,
+ fn pes' =>
+ S.bind2 (mft disc,
+ fn disc' =>
+ S.map2 (mft result,
+ fn result' =>
+ (ECase (e', pes', {disc = disc', result = result'}), loc)))))
+
+ | EError (e, t) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mft t,
+ fn t' =>
+ (EError (e', t'), loc)))
+ | EReturnBlob {blob = NONE, mimeType, t} =>
+ S.bind2 (mfe ctx mimeType,
+ fn mimeType' =>
+ S.map2 (mft t,
+ fn t' =>
+ (EReturnBlob {blob = NONE, mimeType = mimeType', t = t'}, loc)))
+ | EReturnBlob {blob = SOME blob, mimeType, t} =>
+ S.bind2 (mfe ctx blob,
+ fn blob' =>
+ S.bind2 (mfe ctx mimeType,
+ fn mimeType' =>
+ S.map2 (mft t,
+ fn t' =>
+ (EReturnBlob {blob = SOME blob', mimeType = mimeType', t = t'}, loc))))
+ | ERedirect (e, t) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mft t,
+ fn t' =>
+ (ERedirect (e', t'), loc)))
+
+ | EStrcat (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (EStrcat (e1', e2'), loc)))
+
+ | EWrite e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EWrite e', loc))
+
+ | ESeq (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (ESeq (e1', e2'), loc)))
+ | ELet (x, t, e1, e2) =>
+ S.bind2 (mft t,
+ fn t' =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe (bind (ctx, RelE (x, t'))) e2,
+ fn e2' =>
+ (ELet (x, t', e1', e2'), loc))))
+
+ | EClosure (n, es) =>
+ S.map2 (ListUtil.mapfold (mfe ctx) es,
+ fn es' =>
+ (EClosure (n, es'), loc))
+
+ | EQuery {exps, tables, state, query, body, initial} =>
+ S.bind2 (ListUtil.mapfold (fn (x, t) =>
+ S.map2 (mft t,
+ fn t' => (x, t'))) exps,
+ fn exps' =>
+ S.bind2 (ListUtil.mapfold (fn (x, xts) =>
+ S.map2 (ListUtil.mapfold
+ (fn (x, t) =>
+ S.map2 (mft t,
+ fn t' => (x, t'))) xts,
+ fn xts' => (x, xts'))) tables,
+ fn tables' =>
+ S.bind2 (mft state,
+ fn state' =>
+ S.bind2 (mfe ctx query,
+ fn query' =>
+ S.bind2 (mfe (bind (bind (ctx, RelE ("r", dummyt)),
+ RelE ("acc", dummyt)))
+ body,
+ fn body' =>
+ (* ASK: is this the right thing to do? *)
+ S.map2 (mfe ctx initial,
+ fn initial' =>
+ (EQuery {exps = exps',
+ tables = tables',
+ state = state',
+ query = query',
+ body = body',
+ initial = initial'},
+ loc)))))))
+
+ | EDml (e, fm) =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EDml (e', fm), loc))
+ | ENextval e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (ENextval e', loc))
+ | ESetval (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (ESetval (e1', e2'), loc)))
+ | EUnurlify (e, t, b) =>
+ S.bind2 (mfe ctx e,
+ fn e' =>
+ S.map2 (mft t,
+ fn t' =>
+ (EUnurlify (e', t', b), loc)))
+ | EJavaScript (m, e) =>
+ S.bind2 (mfmode ctx m,
+ fn m' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (EJavaScript (m', e'), loc)))
+
+ | ESignalReturn e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (ESignalReturn e', loc))
+ | ESignalBind (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (ESignalBind (e1', e2'), loc)))
+ | ESignalSource e =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (ESignalSource e', loc))
+
+ | EServerCall (s, t, eff, fm) =>
+ S.bind2 (mfe ctx s,
+ fn s' =>
+ S.map2 (mft t,
+ fn t' =>
+ (EServerCall (s', t', eff, fm), loc)))
+ | ERecv (s, t) =>
+ S.bind2 (mfe ctx s,
+ fn s' =>
+ S.map2 (mft t,
+ fn t' =>
+ (ERecv (s', t'), loc)))
+ | ESleep s =>
+ S.map2 (mfe ctx s,
+ fn s' =>
+ (ESleep s', loc))
+
+ | ESpawn s =>
+ S.map2 (mfe ctx s,
+ fn s' =>
+ (ESpawn s', loc))
+
+ and mfmode ctx mode =
+ case mode of
+ Attribute => S.return2 mode
+ | Script => S.return2 mode
+ | Source t =>
+ S.map2 (mft t,
+ fn t' => Source t')
+ in
+ mfe
+ end
+
+fun mapfold {typ = fc, exp = fe} =
+ mapfoldB {typ = fc,
+ exp = fn () => fe,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {typ, exp, bind} ctx e =
+ case mapfoldB {typ = fn c => fn () => S.Continue (typ c, ()),
+ exp = fn ctx => fn e => fn () => S.Continue (exp ctx e, ()),
+ bind = bind} ctx e () of
+ S.Continue (e, ()) => e
+ | S.Return _ => raise Fail "MonoUtil.Exp.mapB: Impossible"
+
+fun map {typ, exp} e =
+ case mapfold {typ = fn c => fn () => S.Continue (typ c, ()),
+ exp = fn e => fn () => S.Continue (exp e, ())} e () of
+ S.Return () => raise Fail "Mono_util.Exp.map"
+ | S.Continue (e, ()) => e
+
+fun fold {typ, exp} s e =
+ case mapfold {typ = fn c => fn s => S.Continue (c, typ (c, s)),
+ exp = fn e => fn s => S.Continue (e, exp (e, s))} e s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "MonoUtil.Exp.fold: Impossible"
+
+fun exists {typ, exp} k =
+ case mapfold {typ = fn c => fn () =>
+ if typ c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun existsB {typ, exp, bind} ctx e =
+ case mapfoldB {typ = fn t => fn () =>
+ if typ t then
+ S.Return ()
+ else
+ S.Continue (t, ()),
+ exp = fn ctx => fn e => fn () =>
+ if exp (ctx, e) then
+ S.Return ()
+ else
+ S.Continue (e, ()),
+ bind = bind} ctx e () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+fun foldB {typ, exp, bind} ctx s e =
+ case mapfoldB {typ = fn t => fn s => S.Continue (t, typ (t, s)),
+ exp = fn ctx => fn e => fn s => S.Continue (e, exp (ctx, e, s)),
+ bind = bind} ctx e s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "MonoUtil.Exp.foldB: Impossible"
+
+fun appLoc f =
+ let
+ fun appl e =
+ (f e;
+ case #1 e of
+ EPrim _ => ()
+ | ERel _ => ()
+ | ENamed _ => ()
+ | ECon (_, _, eo) => Option.app appl eo
+ | ENone _ => ()
+ | ESome (_, e) => appl e
+ | EFfi _ => ()
+ | EFfiApp (_, _, es) => app (appl o #1) es
+ | EApp (e1, e2) => (appl e1; appl e2)
+ | EAbs (_, _, _, e1) => appl e1
+ | EUnop (_, e1) => appl e1
+ | EBinop (_, _, e1, e2) => (appl e1; appl e2)
+ | ERecord xets => app (appl o #2) xets
+ | EField (e1, _) => appl e1
+ | ECase (e1, pes, _) => (appl e1; app (appl o #2) pes)
+ | EStrcat (e1, e2) => (appl e1; appl e2)
+ | EError (e1, _) => appl e1
+ | EReturnBlob {blob = NONE, mimeType = e2, ...} => appl e2
+ | EReturnBlob {blob = SOME e1, mimeType = e2, ...} => (appl e1; appl e2)
+ | ERedirect (e1, _) => appl e1
+ | EWrite e1 => appl e1
+ | ESeq (e1, e2) => (appl e1; appl e2)
+ | ELet (_, _, e1, e2) => (appl e1; appl e2)
+ | EClosure (_, es) => app appl es
+ | EQuery {query = e1, body = e2, initial = e3, ...} => (appl e1; appl e2; appl e3)
+ | EDml (e1, _) => appl e1
+ | ENextval e1 => appl e1
+ | ESetval (e1, e2) => (appl e1; appl e2)
+ | EUnurlify (e1, _, _) => appl e1
+ | EJavaScript (_, e1) => appl e1
+ | ESignalReturn e1 => appl e1
+ | ESignalBind (e1, e2) => (appl e1; appl e2)
+ | ESignalSource e1 => appl e1
+ | EServerCall (e1, _, _, _) => appl e1
+ | ERecv (e1, _) => appl e1
+ | ESleep e1 => appl e1
+ | ESpawn e1 => appl e1)
+ in
+ appl
+ end
+
+end
+
+structure Decl = struct
+
+datatype binder = datatype Exp.binder
+
+fun mapfoldB {typ = fc, exp = fe, decl = fd, bind} =
+ let
+ val mft = Typ.mapfold fc
+
+ val mfe = Exp.mapfoldB {typ = fc, exp = fe, bind = bind}
+
+ fun mfd ctx d acc =
+ S.bindP (mfd' ctx d acc, fd ctx)
+
+ and mfd' ctx (dAll as (d, loc)) =
+ case d of
+ DDatatype dts =>
+ S.map2 (ListUtil.mapfold (fn (x, n, xncs) =>
+ S.map2 (ListUtil.mapfold (fn (x, n, c) =>
+ case c of
+ NONE => S.return2 (x, n, c)
+ | SOME c =>
+ S.map2 (mft c,
+ fn c' => (x, n, SOME c'))) xncs,
+ fn xncs' => (x, n, xncs'))) dts,
+ fn dts' =>
+ (DDatatype dts', loc))
+ | DVal vi =>
+ S.map2 (mfvi ctx vi,
+ fn vi' =>
+ (DVal vi', loc))
+ | DValRec vis =>
+ let
+ val ctx' = foldl (fn ((x, n, t, _, s), ctx') => bind (ctx', NamedE (x, n, t, NONE, s))) ctx vis
+ in
+ S.map2 (ListUtil.mapfold (mfvi ctx') vis,
+ fn vis' =>
+ (DValRec vis', loc))
+ end
+ | DExport (ek, s, n, ts, t, b) =>
+ S.bind2 (ListUtil.mapfold mft ts,
+ fn ts' =>
+ S.map2 (mft t,
+ fn t' =>
+ (DExport (ek, s, n, ts', t', b), loc)))
+ | DTable (s, xts, pe, ce) =>
+ S.bind2 (mfe ctx pe,
+ fn pe' =>
+ S.map2 (mfe ctx ce,
+ fn ce' =>
+ (DTable (s, xts, pe', ce'), loc)))
+ | DSequence _ => S.return2 dAll
+ | DView (s, xts, e) =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (DView (s, xts, e'), loc))
+ | DDatabase _ => S.return2 dAll
+ | DJavaScript _ => S.return2 dAll
+ | DCookie _ => S.return2 dAll
+ | DStyle _ => S.return2 dAll
+ | DTask (e1, e2) =>
+ S.bind2 (mfe ctx e1,
+ fn e1' =>
+ S.map2 (mfe ctx e2,
+ fn e2' =>
+ (DTask (e1', e2'), loc)))
+ | DPolicy pol =>
+ S.map2 (mfpol ctx pol,
+ fn p' =>
+ (DPolicy p', loc))
+ | DOnError _ => S.return2 dAll
+
+ and mfpol ctx pol =
+ case pol of
+ PolClient e =>
+ S.map2 (mfe ctx e,
+ PolClient)
+ | PolInsert e =>
+ S.map2 (mfe ctx e,
+ PolInsert)
+ | PolDelete e =>
+ S.map2 (mfe ctx e,
+ PolDelete)
+ | PolUpdate e =>
+ S.map2 (mfe ctx e,
+ PolUpdate)
+ | PolSequence e =>
+ S.map2 (mfe ctx e,
+ PolSequence)
+
+ and mfvi ctx (x, n, t, e, s) =
+ S.bind2 (mft t,
+ fn t' =>
+ S.map2 (mfe ctx e,
+ fn e' =>
+ (x, n, t', e', s)))
+ in
+ mfd
+ end
+
+fun mapfold {typ = fc, exp = fe, decl = fd} =
+ mapfoldB {typ = fc,
+ exp = fn () => fe,
+ decl = fn () => fd,
+ bind = fn ((), _) => ()} ()
+
+fun fold {typ, exp, decl} s d =
+ case mapfold {typ = fn c => fn s => S.Continue (c, typ (c, s)),
+ exp = fn e => fn s => S.Continue (e, exp (e, s)),
+ decl = fn d => fn s => S.Continue (d, decl (d, s))} d s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "MonoUtil.Decl.fold: Impossible"
+
+fun map {typ, exp, decl} e =
+ case mapfold {typ = fn c => fn () => S.Continue (typ c, ()),
+ exp = fn e => fn () => S.Continue (exp e, ()),
+ decl = fn d => fn () => S.Continue (decl d, ())} e () of
+ S.Return () => raise Fail "MonoUtil.Decl.map: Impossible"
+ | S.Continue (e, ()) => e
+
+fun foldMap {typ, exp, decl} s d =
+ case mapfold {typ = fn c => fn s => S.Continue (typ (c, s)),
+ exp = fn e => fn s => S.Continue (exp (e, s)),
+ decl = fn d => fn s => S.Continue (decl (d, s))} d s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "MonoUtil.Decl.foldMap: Impossible"
+
+fun foldMapB {typ, exp, decl, bind} ctx s d =
+ case mapfoldB {typ = fn c => fn s => S.Continue (typ (c, s)),
+ exp = fn ctx => fn e => fn s => S.Continue (exp (ctx, e, s)),
+ decl = fn ctx => fn d => fn s => S.Continue (decl (ctx, d, s)),
+ bind = bind} ctx d s of
+ S.Continue v => v
+ | S.Return _ => raise Fail "MonoUtil.Decl.foldMapB: Impossible"
+
+fun exists {typ, exp, decl} k =
+ case mapfold {typ = fn c => fn () =>
+ if typ c then
+ S.Return ()
+ else
+ S.Continue (c, ()),
+ exp = fn e => fn () =>
+ if exp e then
+ S.Return ()
+ else
+ S.Continue (e, ()),
+ decl = fn d => fn () =>
+ if decl d then
+ S.Return ()
+ else
+ S.Continue (d, ())} k () of
+ S.Return _ => true
+ | S.Continue _ => false
+
+end
+
+structure File = struct
+
+datatype binder = datatype Exp.binder
+
+fun mapfoldB (all as {bind, ...}) =
+ let
+ val mfd = Decl.mapfoldB all
+
+ fun mff ctx (ds, ps) =
+ case ds of
+ nil => S.return2 (nil, ps)
+ | d :: ds' =>
+ S.bind2 (mfd ctx d,
+ fn d' =>
+ let
+ val ctx' =
+ case #1 d' of
+ DDatatype dts =>
+ foldl (fn ((x, n, xncs), ctx) =>
+ let
+ val ctx = bind (ctx, Datatype (x, n, xncs))
+ val t = (TDatatype (n, ref (ElabUtil.classifyDatatype xncs, xncs)),
+ #2 d')
+ in
+ foldl (fn ((x, n, to), ctx) =>
+ let
+ val t = case to of
+ NONE => t
+ | SOME t' => (TFun (t', t), #2 d')
+ in
+ bind (ctx, NamedE (x, n, t, NONE, ""))
+ end)
+ ctx xncs
+ end) ctx dts
+ | DVal (x, n, t, e, s) => bind (ctx, NamedE (x, n, t, SOME e, s))
+ | DValRec vis => foldl (fn ((x, n, t, e, s), ctx) =>
+ bind (ctx, NamedE (x, n, t, NONE, s))) ctx vis
+ | DExport _ => ctx
+ | DTable _ => ctx
+ | DSequence _ => ctx
+ | DView _ => ctx
+ | DDatabase _ => ctx
+ | DJavaScript _ => ctx
+ | DCookie _ => ctx
+ | DStyle _ => ctx
+ | DTask _ => ctx
+ | DPolicy _ => ctx
+ | DOnError _ => ctx
+ in
+ S.map2 (mff ctx' (ds', ps),
+ fn (ds', _) =>
+ (d' :: ds', ps))
+ end)
+ in
+ mff
+ end
+
+fun mapfold {typ = fc, exp = fe, decl = fd} =
+ mapfoldB {typ = fc,
+ exp = fn () => fe,
+ decl = fn () => fd,
+ bind = fn ((), _) => ()} ()
+
+fun mapB {typ, exp, decl, bind} ctx ds =
+ case mapfoldB {typ = fn c => fn () => S.Continue (typ c, ()),
+ exp = fn ctx => fn e => fn () => S.Continue (exp ctx e, ()),
+ decl = fn ctx => fn d => fn () => S.Continue (decl ctx d, ()),
+ bind = bind} ctx ds () of
+ S.Continue (ds, ()) => ds
+ | S.Return _ => raise Fail "MonoUtil.File.mapB: Impossible"
+
+fun map {typ, exp, decl} e =
+ case mapfold {typ = fn c => fn () => S.Continue (typ c, ()),
+ exp = fn e => fn () => S.Continue (exp e, ()),
+ decl = fn d => fn () => S.Continue (decl d, ())} e () of
+ S.Return () => raise Fail "MonoUtil.File.map: Impossible"
+ | S.Continue (e, ()) => e
+
+fun fold {typ, exp, decl} s d =
+ case mapfold {typ = fn c => fn s => S.Continue (c, typ (c, s)),
+ exp = fn e => fn s => S.Continue (e, exp (e, s)),
+ decl = fn d => fn s => S.Continue (d, decl (d, s))} d s of
+ S.Continue (_, s) => s
+ | S.Return _ => raise Fail "MonoUtil.File.fold: Impossible"
+
+fun maxName (f : file) =
+ foldl (fn ((d, _) : decl, count) =>
+ case d of
+ DDatatype dts =>
+ foldl (fn ((_, n, ns), count) =>
+ foldl (fn ((_, n', _), m) => Int.max (n', m))
+ (Int.max (n, count)) ns) count dts
+ | DVal (_, n, _, _, _) => Int.max (n, count)
+ | DValRec vis => foldl (fn ((_, n, _, _, _), count) => Int.max (n, count)) count vis
+ | DExport _ => count
+ | DTable _ => count
+ | DSequence _ => count
+ | DView _ => count
+ | DDatabase _ => count
+ | DJavaScript _ => count
+ | DCookie _ => count
+ | DStyle _ => count
+ | DTask _ => count
+ | DPolicy _ => count
+ | DOnError _ => count) 0 (#1 f)
+
+fun appLoc f (fl : file) =
+ let
+ val eal = Exp.appLoc f
+
+ fun appl (d : decl) =
+ case #1 d of
+ DDatatype _ => ()
+ | DVal (_, _, _, e1, _) => eal e1
+ | DValRec vis => app (eal o #4) vis
+ | DExport _ => ()
+ | DTable (_, _, e1, e2) => (eal e1; eal e2)
+ | DSequence _ => ()
+ | DView (_, _, e1) => eal e1
+ | DDatabase _ => ()
+ | DJavaScript _ => ()
+ | DCookie _ => ()
+ | DStyle _ => ()
+ | DTask (e1, e2) => (eal e1; eal e2)
+ | DPolicy pol => applPolicy pol
+ | DOnError _ => ()
+
+ and applPolicy p =
+ case p of
+ PolClient e1 => eal e1
+ | PolInsert e1 => eal e1
+ | PolDelete e1 => eal e1
+ | PolUpdate e1 => eal e1
+ | PolSequence e1 => eal e1
+ in
+ app appl (#1 fl)
+ end
+
+end
+
+end
diff --git a/src/monoize.sig b/src/monoize.sig
new file mode 100644
index 0000000..951db01
--- /dev/null
+++ b/src/monoize.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature MONOIZE = sig
+
+ val monoize : CoreEnv.env -> Core.file -> Mono.file
+
+ val liftExpInExp : int -> Mono.exp -> Mono.exp
+
+end
diff --git a/src/monoize.sml b/src/monoize.sml
new file mode 100644
index 0000000..ddf6cd4
--- /dev/null
+++ b/src/monoize.sml
@@ -0,0 +1,4549 @@
+(* Copyright (c) 2008-2014, 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 Monoize :> MONOIZE = struct
+
+structure E = ErrorMsg
+structure Env = CoreEnv
+
+structure L = Core
+structure L' = Mono
+
+structure IM = IntBinaryMap
+structure IS = IntBinarySet
+
+structure SK = struct
+type ord_key = string
+val compare = String.compare
+end
+
+structure SS = BinarySetFn(SK)
+structure SM = BinaryMapFn(SK)
+
+structure RM = BinaryMapFn(struct
+ type ord_key = (string * L'.typ) list
+ fun compare (r1, r2) = MonoUtil.Typ.compare ((L'.TRecord r1, E.dummySpan),
+ (L'.TRecord r2, E.dummySpan))
+ end)
+
+val nextPvar = MonoFooify.nextPvar
+val pvars = ref (RM.empty : (int * (string * int * L'.typ) list) RM.map)
+val pvarDefs = MonoFooify.pvarDefs
+val pvarOldDefs = ref ([] : (int * (string * int * L.con option) list) list)
+
+fun choosePvar () =
+ let
+ val n = !nextPvar
+ in
+ nextPvar := n + 1;
+ n
+ end
+
+fun pvar (r, r', loc) =
+ case RM.find (!pvars, r') of
+ NONE =>
+ let
+ val n = choosePvar ()
+ val fs = map (fn (x, t) => (x, choosePvar (), t)) r'
+ val r = ListMergeSort.sort (fn (((L.CName x, _), _), ((L.CName y, _), _)) => String.compare (x, y) = GREATER
+ | _ => raise Fail "Monoize: pvar, not CName") r
+ val (r, fs') = ListPair.foldr (fn ((_, t), (x, n, _), (r, fs')) =>
+ ((x, n, SOME t) :: r,
+ SM.insert (fs', x, n))) ([], SM.empty) (r, fs)
+ in
+ pvars := RM.insert (!pvars, r', (n, fs));
+ pvarDefs := ("$poly" ^ Int.toString n, n, map (fn (x, n, t) => (x, n, SOME t)) fs)
+ :: !pvarDefs;
+ pvarOldDefs := (n, r) :: !pvarOldDefs;
+ (n, fs)
+ end
+ | SOME v => v
+
+val singletons = SS.addList (SS.empty,
+ ["link",
+ "br",
+ "p",
+ "hr",
+ "input",
+ "img",
+ "base",
+ "meta",
+ "param",
+ "area",
+ "col"])
+
+val dummyTyp = (L'.TDatatype (0, ref (L'.Enum, [])), E.dummySpan)
+
+structure U = MonoUtil
+
+val liftExpInExp =
+ U.Exp.mapB {typ = fn t => t,
+ exp = fn bound => fn e =>
+ case e of
+ L'.ERel xn =>
+ if xn < bound then
+ e
+ else
+ L'.ERel (xn + 1)
+ | _ => e,
+ bind = fn (bound, U.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+
+fun monoName env (all as (c, loc)) =
+ let
+ fun poly () =
+ (E.errorAt loc "Unsupported name constructor";
+ Print.eprefaces' [("Constructor", CorePrint.p_con env all)];
+ "")
+ in
+ case c of
+ L.CName s => s
+ | _ => poly ()
+ end
+
+fun lowercaseFirst "" = ""
+ | lowercaseFirst s = String.str (Char.toLower (String.sub (s, 0)))
+ ^ String.extract (s, 1, NONE)
+
+fun monoNameLc env c = lowercaseFirst (monoName env c)
+
+fun readType' (t, loc) = (L'.TFun ((L'.TFfi ("Basis", "string"), loc),
+ (L'.TOption t, loc)), loc)
+fun readErrType (t, loc) = (L'.TFun ((L'.TFfi ("Basis", "string"), loc),
+ t), loc)
+fun readType (t, loc) =
+ (L'.TRecord [("Read", readType' (t, loc)),
+ ("ReadError", readErrType (t, loc))],
+ loc)
+
+fun monoType env =
+ let
+ fun mt env dtmap (all as (c, loc)) =
+ let
+ fun poly () =
+ (E.errorAt loc "Unsupported type constructor";
+ Print.eprefaces' [("Constructor", CorePrint.p_con env all)];
+ dummyTyp)
+ in
+ case c of
+ L.TFun (c1, c2) => (L'.TFun (mt env dtmap c1, mt env dtmap c2), loc)
+ | L.TCFun _ => poly ()
+ | L.TRecord (L.CRecord ((L.KType, _), xcs), _) =>
+ let
+ val xcs = map (fn (x, t) => (monoName env x, mt env dtmap t)) xcs
+ val xcs = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) xcs
+ in
+ (L'.TRecord xcs, loc)
+ end
+ | L.TRecord _ => poly ()
+
+ | L.CApp ((L.CFfi ("Basis", "option"), _), t) =>
+ (L'.TOption (mt env dtmap t), loc)
+ | L.CApp ((L.CFfi ("Basis", "list"), _), t) =>
+ (L'.TList (mt env dtmap t), loc)
+
+ | L.CApp ((L.CFfi ("Basis", "variant"), _), (L.CRecord ((L.KType, _), xts), _)) =>
+ let
+ val xts' = map (fn (x, t) => (monoName env x, mt env dtmap t)) xts
+ val xts' = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) xts'
+ val (n, cs) = pvar (xts, xts', loc)
+ val cs = map (fn (x, n, t) => (x, n, SOME t)) cs
+ in
+ (L'.TDatatype (n, ref (ElabUtil.classifyDatatype cs, cs)), loc)
+ end
+
+ | L.CApp ((L.CFfi ("Basis", "monad"), _), _) =>
+ (L'.TRecord [], loc)
+
+ | L.CApp ((L.CFfi ("Basis", "eq"), _), t) =>
+ let
+ val t = mt env dtmap t
+ in
+ (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc)
+ end
+ | L.CApp ((L.CFfi ("Basis", "num"), _), t) =>
+ let
+ val t = mt env dtmap t
+ in
+ (L'.TRecord [("Zero", t),
+ ("Neg", (L'.TFun (t, t), loc)),
+ ("Plus", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Minus", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Times", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Div", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Mod", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Pow", (L'.TFun (t, (L'.TFun (t, t), loc)), loc))],
+ loc)
+ end
+ | L.CApp ((L.CFfi ("Basis", "ord"), _), t) =>
+ let
+ val t = mt env dtmap t
+ in
+ (L'.TRecord [("Lt", (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc)),
+ ("Le", (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc))],
+ loc)
+ end
+ | L.CApp ((L.CFfi ("Basis", "show"), _), t) =>
+ (L'.TFun (mt env dtmap t, (L'.TFfi ("Basis", "string"), loc)), loc)
+ | L.CApp ((L.CFfi ("Basis", "read"), _), t) =>
+ readType (mt env dtmap t, loc)
+
+ | L.CFfi ("Basis", "unit") => (L'.TRecord [], loc)
+ | L.CFfi ("Basis", "page") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "xhead") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "xbody") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "xtable") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "xtr") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "xform") => (L'.TFfi ("Basis", "string"), loc)
+
+ | L.CFfi ("Basis", "url") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "mimeType") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "xml"), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "xhtml"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "css_class") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "css_value") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "css_property") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "css_style") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "id") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "requestHeader") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "responseHeader") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "envVar") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "meta") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "data_attr_kind") => (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "data_attr") => (L'.TFfi ("Basis", "string"), loc)
+
+ | L.CApp ((L.CFfi ("Basis", "serialized"), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+
+ | L.CApp ((L.CFfi ("Basis", "transaction"), _), t) =>
+ (L'.TFun ((L'.TRecord [], loc), mt env dtmap t), loc)
+ | L.CApp ((L.CFfi ("Basis", "source"), _), t) =>
+ (L'.TSource, loc)
+ | L.CApp ((L.CFfi ("Basis", "signal"), _), t) =>
+ (L'.TSignal (mt env dtmap t), loc)
+ | L.CApp ((L.CFfi ("Basis", "http_cookie"), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_table"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_view"), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "sql_sequence") =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_query"), _), _), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_query1"), _), _), _), _), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_from_items"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_exp"), _), _), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_expw"), _), _), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_window"), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_window_function"), _), _), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "primary_key"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_constraints"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "sql_constraints"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_constraint"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "linkable"), _), _), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "matching"), _), _), _), _) =>
+ let
+ val string = (L'.TFfi ("Basis", "string"), loc)
+ in
+ (L'.TRecord [("1", string), ("2", string)], loc)
+ end
+ | L.CApp ((L.CFfi ("Basis", "propagation_mode"), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "dml") =>
+ (L'.TFfi ("Basis", "string"), loc)
+
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_subset"), _), _), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CFfi ("Basis", "sql_relop") =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "sql_direction") =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_order_by"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "sql_limit") =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CFfi ("Basis", "sql_offset") =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "fieldsOf"), _), _), _), _) =>
+ (L'.TRecord [], loc)
+
+ | L.CApp ((L.CFfi ("Basis", "sql_injectable_prim"), _), t) =>
+ (L'.TFun (mt env dtmap t, (L'.TFfi ("Basis", "string"), loc)), loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_injectable"), _), t) =>
+ (L'.TFun (mt env dtmap t, (L'.TFfi ("Basis", "string"), loc)), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "nullify"), _), _), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_unary"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_binary"), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_aggregate"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_summable"), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_maxable"), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_arith"), _), _) =>
+ (L'.TRecord [], loc)
+ | L.CApp ((L.CFfi ("Basis", "sql_nfunc"), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_ufunc"), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_partition"), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+ | L.CApp ((L.CApp ((L.CApp ((L.CApp ((L.CFfi ("Basis", "sql_window"), _), _), _), _), _), _), _), _) =>
+ (L'.TFfi ("Basis", "string"), loc)
+
+ | L.CApp ((L.CFfi ("Basis", "channel"), _), _) =>
+ (L'.TFfi ("Basis", "channel"), loc)
+
+ | L.CRel _ => poly ()
+ | L.CNamed n =>
+ (case IM.find (dtmap, n) of
+ SOME r => (L'.TDatatype (n, r), loc)
+ | NONE =>
+ let
+ val r = ref (L'.Default, [])
+ val (_, xs, xncs) = Env.lookupDatatype env n
+
+ val dtmap' = IM.insert (dtmap, n, r)
+
+ val xncs = map (fn (x, n, to) => (x, n, Option.map (mt env dtmap') to)) xncs
+ in
+ case xs of
+ [] =>(r := (ElabUtil.classifyDatatype xncs, xncs);
+ (L'.TDatatype (n, r), loc))
+ | _ => poly ()
+ end)
+ | L.CFfi mx => (L'.TFfi mx, loc)
+ | L.CApp _ => poly ()
+ | L.CAbs _ => poly ()
+
+ | L.CName _ => poly ()
+
+ | L.CRecord _ => poly ()
+ | L.CConcat _ => poly ()
+ | L.CMap _ => poly ()
+ | L.CUnit => poly ()
+
+ | L.CTuple _ => poly ()
+ | L.CProj _ => poly ()
+
+ | L.CKAbs _ => poly ()
+ | L.CKApp _ => poly ()
+ | L.TKFun _ => poly ()
+ end
+ in
+ mt env IM.empty
+ end
+
+val dummyExp = (L'.EPrim (Prim.Int 0), E.dummySpan)
+
+structure Fm = MonoFooify.Fm
+
+fun fooifyExp fk env =
+ MonoFooify.fooifyExp
+ fk
+ (fn n =>
+ let
+ val (_, t, _, s) = Env.lookupENamed env n
+ in
+ (monoType env t, s)
+ end)
+ (fn n =>
+ let
+ val (x, _, xncs) = Env.lookupDatatype env n
+ in
+ (x, map (fn (x, n, c) => (x, n, Option.map (monoType env) c)) xncs)
+ end)
+
+val attrifyExp = fooifyExp MonoFooify.Attr
+val urlifyExp = fooifyExp MonoFooify.Url
+
+datatype 'a failable_search =
+ Found of 'a
+ | NotFound
+ | Error
+
+structure St :> sig
+ type t
+
+ val empty : t
+
+ val radioGroup : t -> string option
+ val setRadioGroup : t * string -> t
+end = struct
+
+type t = {
+ radioGroup : string option
+}
+
+val empty = {radioGroup = NONE}
+
+fun radioGroup (t : t) = #radioGroup t
+
+fun setRadioGroup (t : t, x) = {radioGroup = SOME x}
+
+end
+
+fun monoPatCon env pc =
+ case pc of
+ L.PConVar n => L'.PConVar n
+ | L.PConFfi {mod = m, datatyp, con, arg, ...} => L'.PConFfi {mod = m, datatyp = datatyp, con = con,
+ arg = Option.map (monoType env) arg}
+
+val dummyPat = (L'.PPrim (Prim.Int 0), ErrorMsg.dummySpan)
+
+
+fun listify t = (L'.TRecord [("1", t), ("2", (L'.TList t, #2 t))], #2 t)
+
+fun monoPat env (all as (p, loc)) =
+ let
+ fun poly () =
+ (E.errorAt loc "Unsupported pattern";
+ Print.eprefaces' [("Pattern", CorePrint.p_pat env all)];
+ dummyPat)
+ in
+ case p of
+ L.PVar (x, t) => (L'.PVar (x, monoType env t), loc)
+ | L.PPrim p => (L'.PPrim p, loc)
+ | L.PCon (dk, pc, [], po) => (L'.PCon (dk, monoPatCon env pc, Option.map (monoPat env) po), loc)
+ | L.PCon (L.Option, L.PConFfi {mod = "Basis", datatyp = "list", ...}, [t], NONE) =>
+ (L'.PNone (listify (monoType env t)), loc)
+ | L.PCon (L.Option, L.PConFfi {mod = "Basis", datatyp = "list", ...}, [t], SOME p) =>
+ (L'.PSome (listify (monoType env t), monoPat env p), loc)
+ | L.PCon (L.Option, _, [t], NONE) => (L'.PNone (monoType env t), loc)
+ | L.PCon (L.Option, pc, [t], SOME p) => (L'.PSome (monoType env t, monoPat env p), loc)
+ | L.PCon _ => poly ()
+ | L.PRecord xps => (L'.PRecord (map (fn (x, p, t) => (x, monoPat env p, monoType env t)) xps), loc)
+ end
+
+fun strcat loc es =
+ case es of
+ [] => (L'.EPrim (Prim.String (Prim.Normal, "")), loc)
+ | [e] => e
+ | _ =>
+ let
+ val e2 = List.last es
+ val es = List.take (es, length es - 1)
+ val e1 = List.last es
+ val es = List.take (es, length es - 1)
+ in
+ foldr (fn (e, e') => (L'.EStrcat (e, e'), loc))
+ (L'.EStrcat (e1, e2), loc) es
+ end
+
+fun strcatComma loc es =
+ case es of
+ [] => (L'.EPrim (Prim.String (Prim.Normal, "")), loc)
+ | [e] => e
+ | _ =>
+ let
+ val e1 = List.last es
+ val es = List.take (es, length es - 1)
+ in
+ foldr (fn (e, e') =>
+ case (e, e') of
+ ((L'.EPrim (Prim.String (_, "")), _), _) => e'
+ | (_, (L'.EPrim (Prim.String (_, "")), _)) => e
+ | _ =>
+ (L'.EStrcat (e,
+ (L'.EStrcat ((L'.EPrim (Prim.String (Prim.Normal, ", ")), loc), e'), loc)), loc))
+ e1 es
+ end
+
+fun strcatR loc e xs = strcatComma loc (map (fn (x, _) => (L'.EField (e, x), loc)) xs)
+
+val readCookie = ref IS.empty
+
+fun isBlobby (t : L.con) =
+ case #1 t of
+ L.CFfi ("Basis", "string") => true
+ | L.CFfi ("Basis", "blob") => true
+ | _ => false
+
+fun monoExp (env, st, fm) (all as (e, loc)) =
+ let
+ val strcat = strcat loc
+ val strcatComma = strcatComma loc
+ fun str s = (L'.EPrim (Prim.String (Prim.Normal, s)), loc)
+ fun strH s = (L'.EPrim (Prim.String (Prim.Html, s)), loc)
+
+ fun poly () =
+ (E.errorAt loc "Unsupported expression";
+ Print.eprefaces' [("Expression", CorePrint.p_exp env all)];
+ (dummyExp, fm))
+
+ fun numTy t =
+ (L'.TRecord [("Zero", t),
+ ("Neg", (L'.TFun (t, t), loc)),
+ ("Plus", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Minus", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Times", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Div", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Mod", (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Pow", (L'.TFun (t, (L'.TFun (t, t), loc)), loc))], loc)
+ fun numEx (t, zero, neg, plus, minus, times, dv, md, ex) =
+ ((L'.ERecord [("Zero", (L'.EPrim zero, loc), t),
+ ("Neg", neg, (L'.TFun (t, t), loc)),
+ ("Plus", plus, (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Minus", minus, (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Times", times, (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Div", dv, (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Mod", md, (L'.TFun (t, (L'.TFun (t, t), loc)), loc)),
+ ("Pow", ex, (L'.TFun (t, (L'.TFun (t, t), loc)), loc))], loc), fm)
+
+ fun ordTy t =
+ (L'.TRecord [("Lt", (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc)),
+ ("Le", (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc))], loc)
+ fun ordEx (t, lt, le) =
+ ((L'.ERecord [("Lt", lt, (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc)),
+ ("Le", le, (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc))],
+ loc), fm)
+
+ fun outerRec xts =
+ (L'.TRecord (map (fn ((L.CName x, _), (L.CRecord (_, xts), _)) =>
+ (x, (L'.TRecord (map (fn (x', _) => (x, (L'.TRecord [], loc))) xts), loc))
+ | (x, all as (_, loc)) =>
+ (E.errorAt loc "Unsupported record field constructor";
+ Print.eprefaces' [("Name", CorePrint.p_con env x),
+ ("Constructor", CorePrint.p_con env all)];
+ ("", dummyTyp))) xts), loc)
+ in
+ case e of
+ L.EPrim p => ((L'.EPrim p, loc), fm)
+ | L.ERel n => ((L'.ERel n, loc), fm)
+ | L.ENamed n => ((L'.ENamed n, loc), fm)
+ | L.ECon (dk, pc, [], eo) =>
+ let
+ val (eo, fm) =
+ case eo of
+ NONE => (NONE, fm)
+ | SOME e =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ (SOME e, fm)
+ end
+ in
+ ((L'.ECon (dk, monoPatCon env pc, eo), loc), fm)
+ end
+ | L.ECon (L.Option, L.PConFfi {mod = "Basis", datatyp = "list", ...}, [t], NONE) =>
+ ((L'.ENone (listify (monoType env t)), loc), fm)
+ | L.ECon (L.Option, L.PConFfi {mod = "Basis", datatyp = "list", ...}, [t], SOME e) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.ESome (listify (monoType env t), e), loc), fm)
+ end
+ | L.ECon (L.Option, _, [t], NONE) =>
+ ((L'.ENone (monoType env t), loc), fm)
+ | L.ECon (L.Option, _, [t], SOME e) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.ESome (monoType env t, e), loc), fm)
+ end
+ | L.ECon _ => poly ()
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "make"), _), nmC as (L.CName nm, _)), _),
+ t), _),
+ (L.CRecord (_, xts), _)) =>
+ let
+ val t' = monoType env t
+ val xts' = map (fn (x, t) => (monoName env x, monoType env t)) xts
+ val xts' = (nm, t') :: xts'
+ val xts' = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) xts'
+ val (n, cs) = pvar ((nmC, t) :: xts, xts', loc)
+ val cs' = map (fn (x, n, t) => (x, n, SOME t)) cs
+ val cl = ElabUtil.classifyDatatype cs'
+ in
+ case List.find (fn (nm', _, _) => nm' = nm) cs of
+ NONE => raise Fail "Monoize: Polymorphic variant tag mismatch for 'make'"
+ | SOME (_, n', _) => ((L'.EAbs ("x", t', (L'.TDatatype (n, ref (cl, cs')), loc),
+ (L'.ECon (cl, L'.PConVar n', SOME (L'.ERel 0, loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "match"), _), (L.CRecord (_, xts), _)), _),
+ t) =>
+ let
+ val t = monoType env t
+ val xts' = map (fn (x, t) => (monoName env x, monoType env t)) xts
+ val xts' = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) xts'
+ val (n, cs) = pvar (xts, xts', loc)
+ val cs' = map (fn (x, n, t) => (x, n, SOME t)) cs
+ val cl = ElabUtil.classifyDatatype cs'
+ val fs = (L'.TRecord (map (fn (x, t') => (x, (L'.TFun (t', t), loc))) xts'), loc)
+ val dt = (L'.TDatatype (n, ref (cl, cs')), loc)
+ in
+ ((L'.EAbs ("v",
+ dt,
+ (L'.TFun (fs, t), loc),
+ (L'.EAbs ("fs", fs, t,
+ (L'.ECase ((L'.ERel 1, loc),
+ map (fn (x, n', t') =>
+ ((L'.PCon (cl, L'.PConVar n', SOME (L'.PVar ("x", t'), loc)), loc),
+ (L'.EApp ((L'.EField ((L'.ERel 1, loc), x), loc),
+ (L'.ERel 0, loc)), loc))) cs,
+ {disc = dt, result = t}), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "eq"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ val dom = (L'.TFun (t, (L'.TFun (t, b), loc)), loc)
+ in
+ ((L'.EAbs ("f", dom, dom,
+ (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "ne"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ val dom = (L'.TFun (t, (L'.TFun (t, b), loc)), loc)
+ in
+ ((L'.EAbs ("f", dom, dom,
+ (L'.EAbs ("x", t, (L'.TFun (t, b), loc),
+ (L'.EAbs ("y", t, b,
+ (L'.EUnop ("!", (L'.EApp ((L'.EApp ((L'.ERel 2, loc),
+ (L'.ERel 1, loc)), loc),
+ (L'.ERel 0, loc)), loc)), loc)),
+ loc)),
+ loc)),
+ loc), fm)
+ end
+ | L.EFfi ("Basis", "eq_int") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "int"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.Int, "==", (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "eq_float") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "float"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, "==", (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "eq_bool") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "bool"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "bool"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "bool"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, "==", (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "eq_string") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "string"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "string"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "string"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, "!strcmp", (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "eq_char") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "char"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "char"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "char"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, "==", (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "eq_time") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "time"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "time"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "time"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EFfiApp ("Basis", "eq_time", [((L'.ERel 1, loc), (L'.TFfi ("Basis", "time"), loc)),
+ ((L'.ERel 0, loc), (L'.TFfi ("Basis", "time"), loc))]), loc)), loc)), loc),
+ fm)
+
+ | L.ECApp ((L.EFfi ("Basis", "mkEq"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ val dom = (L'.TFun (t, (L'.TFun (t, b), loc)), loc)
+ in
+ ((L'.EAbs ("f", dom, dom,
+ (L'.ERel 0, loc)), loc), fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "zero"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, t,
+ (L'.EField ((L'.ERel 0, loc), "Zero"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "neg"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, t), loc),
+ (L'.EField ((L'.ERel 0, loc), "Neg"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "plus"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, (L'.TFun (t, t), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Plus"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "minus"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, (L'.TFun (t, t), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Minus"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "times"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, (L'.TFun (t, t), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Times"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "divide"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, (L'.TFun (t, t), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Div"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "mod"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, (L'.TFun (t, t), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Mod"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "pow"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", numTy t, (L'.TFun (t, (L'.TFun (t, t), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Pow"), loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "num_int") =>
+ let
+ fun intBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "int"), loc), (L'.TFfi ("Basis", "int"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFfi ("Basis", "int"), loc),
+ (L'.EBinop (L'.Int, s, (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ numEx ((L'.TFfi ("Basis", "int"), loc),
+ Prim.Int (Int64.fromInt 0),
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFfi ("Basis", "int"), loc),
+ (L'.EUnop ("-", (L'.ERel 0, loc)), loc)), loc),
+ intBin "+",
+ intBin "-",
+ intBin "*",
+ intBin "/",
+ intBin "%",
+ intBin "powl"
+ )
+ end
+ | L.EFfi ("Basis", "num_float") =>
+ let
+ fun floatBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "float"), loc), (L'.TFfi ("Basis", "float"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFfi ("Basis", "float"), loc),
+ (L'.EBinop (L'.NotInt, s, (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ numEx ((L'.TFfi ("Basis", "float"), loc),
+ Prim.Float 0.0,
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFfi ("Basis", "float"), loc),
+ (L'.EUnop ("-", (L'.ERel 0, loc)), loc)), loc),
+ floatBin "+",
+ floatBin "-",
+ floatBin "*",
+ floatBin "fdiv",
+ floatBin "fmod",
+ floatBin "powf"
+ )
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "lt"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", ordTy t, (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Lt"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "le"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("r", ordTy t, (L'.TFun (t, (L'.TFun (t, (L'.TFfi ("Basis", "bool"), loc)), loc)), loc),
+ (L'.EField ((L'.ERel 0, loc), "Le"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "gt"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ in
+ ((L'.EAbs ("f", ordTy t, (L'.TFun (t, (L'.TFun (t, b), loc)), loc),
+ (L'.EAbs ("x", t, (L'.TFun (t, b), loc),
+ (L'.EAbs ("y", t, b,
+ (L'.EUnop ("!",
+ (L'.EApp ((L'.EApp ((L'.EField ((L'.ERel 2, loc),
+ "Le"), loc),
+ (L'.ERel 1, loc)), loc),
+ (L'.ERel 0, loc)), loc)), loc)), loc)),
+ loc)),
+ loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "ge"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ in
+ ((L'.EAbs ("f", ordTy t, (L'.TFun (t, (L'.TFun (t, b), loc)), loc),
+ (L'.EAbs ("x", t, (L'.TFun (t, b), loc),
+ (L'.EAbs ("y", t, b,
+ (L'.EUnop ("!",
+ (L'.EApp ((L'.EApp ((L'.EField ((L'.ERel 2, loc),
+ "Lt"), loc),
+ (L'.ERel 1, loc)), loc),
+ (L'.ERel 0, loc)), loc)), loc)), loc)),
+ loc)),
+ loc), fm)
+ end
+ | L.EFfi ("Basis", "ord_int") =>
+ let
+ fun intBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "int"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "int"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.Int, s, (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ ordEx ((L'.TFfi ("Basis", "int"), loc),
+ intBin "<",
+ intBin "<=")
+ end
+ | L.EFfi ("Basis", "ord_float") =>
+ let
+ fun floatBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "float"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "float"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, s, (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ ordEx ((L'.TFfi ("Basis", "float"), loc),
+ floatBin "<",
+ floatBin "<=")
+ end
+ | L.EFfi ("Basis", "ord_bool") =>
+ let
+ fun boolBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "bool"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "bool"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "bool"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, s, (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ ordEx ((L'.TFfi ("Basis", "bool"), loc),
+ boolBin "<",
+ boolBin "<=")
+ end
+ | L.EFfi ("Basis", "ord_string") =>
+ let
+ fun boolBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "string"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "string"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "string"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, s,
+ (L'.EBinop (L'.NotInt, "strcmp",
+ (L'.ERel 1, loc),
+ (L'.ERel 0, loc)), loc),
+ (L'.EPrim (Prim.Int (Int64.fromInt 0)), loc)), loc)), loc)), loc)
+ in
+ ordEx ((L'.TFfi ("Basis", "string"), loc),
+ boolBin "<",
+ boolBin "<=")
+ end
+ | L.EFfi ("Basis", "ord_char") =>
+ let
+ fun charBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "char"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "char"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "char"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EBinop (L'.NotInt, s, (L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ ordEx ((L'.TFfi ("Basis", "char"), loc),
+ charBin "<",
+ charBin "<=")
+ end
+ | L.EFfi ("Basis", "ord_time") =>
+ let
+ fun boolBin s =
+ (L'.EAbs ("x", (L'.TFfi ("Basis", "time"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "time"), loc), (L'.TFfi ("Basis", "bool"), loc)), loc),
+ (L'.EAbs ("y", (L'.TFfi ("Basis", "time"), loc),
+ (L'.TFfi ("Basis", "bool"), loc),
+ (L'.EFfiApp ("Basis", s, [((L'.ERel 1, loc), (L'.TFfi ("Basis", "time"), loc)),
+ ((L'.ERel 0, loc), (L'.TFfi ("Basis", "time"), loc))]), loc)), loc)), loc)
+ in
+ ordEx ((L'.TFfi ("Basis", "time"), loc),
+ boolBin "lt_time",
+ boolBin "le_time")
+ end
+ | L.ECApp ((L.EFfi ("Basis", "mkOrd"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ val dom = ordTy t
+ in
+ ((L'.EAbs ("f", dom, dom,
+ (L'.ERel 0, loc)), loc), fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "show"), _), t) =>
+ let
+ val t = monoType env t
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("f", (L'.TFun (t, s), loc), (L'.TFun (t, s), loc),
+ (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "show_int") =>
+ ((L'.EFfi ("Basis", "intToString"), loc), fm)
+ | L.EFfi ("Basis", "show_float") =>
+ ((L'.EFfi ("Basis", "floatToString"), loc), fm)
+ | L.EFfi ("Basis", "show_string") =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "show_queryString") =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "show_url") =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "show_css_class") =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "show_id") =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.EFfi ("Basis", "show_char") =>
+ ((L'.EFfi ("Basis", "charToString"), loc), fm)
+ | L.EFfi ("Basis", "show_bool") =>
+ ((L'.EFfi ("Basis", "boolToString"), loc), fm)
+ | L.EFfi ("Basis", "show_time") =>
+ ((L'.EFfi ("Basis", "timeToString"), loc), fm)
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "show_xml"), _), _),_), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "show_sql_query"), _), _), _), _), _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "mkShow"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "string"), loc)
+ val dom = (L'.TFun (t, b), loc)
+ in
+ ((L'.EAbs ("f", dom, dom,
+ (L'.ERel 0, loc)), loc), fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "read"), _), t) =>
+ let
+ val t = monoType env t
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("f", readType (t, loc), readType' (t, loc),
+ (L'.EField ((L'.ERel 0, loc), "Read"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "readError"), _), t) =>
+ let
+ val t = monoType env t
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("f", readType (t, loc), readErrType (t, loc),
+ (L'.EField ((L'.ERel 0, loc), "ReadError"), loc)), loc), fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "mkRead"), _), t) =>
+ let
+ val t = monoType env t
+ val b = (L'.TFfi ("Basis", "string"), loc)
+ val b' = (L'.TOption b, loc)
+ val dom = (L'.TFun (t, b), loc)
+ val dom' = (L'.TFun (t, b'), loc)
+ in
+ ((L'.EAbs ("f", dom, (L'.TFun (dom', readType (t, loc)), loc),
+ (L'.EAbs ("f'", dom', readType (t, loc),
+ (L'.ERecord [("Read", (L'.ERel 0, loc), dom),
+ ("ReadError", (L'.ERel 1, loc), dom')], loc)), loc)), loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "read_int") =>
+ let
+ val t = (L'.TFfi ("Basis", "int"), loc)
+ in
+ ((L'.ERecord [("Read", (L'.EFfi ("Basis", "stringToInt"), loc), readType' (t, loc)),
+ ("ReadError", (L'.EFfi ("Basis", "stringToInt_error"), loc), readErrType (t, loc))],
+ loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "read_float") =>
+ let
+ val t = (L'.TFfi ("Basis", "float"), loc)
+ in
+ ((L'.ERecord [("Read", (L'.EFfi ("Basis", "stringToFloat"), loc), readType' (t, loc)),
+ ("ReadError", (L'.EFfi ("Basis", "stringToFloat_error"), loc), readErrType (t, loc))],
+ loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "read_string") =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.ERecord [("Read", (L'.EAbs ("s", s, (L'.TOption s, loc),
+ (L'.ESome (s, (L'.ERel 0, loc)), loc)), loc), readType' (s, loc)),
+ ("ReadError", (L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc), readErrType (s, loc))], loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "read_char") =>
+ let
+ val t = (L'.TFfi ("Basis", "char"), loc)
+ in
+ ((L'.ERecord [("Read", (L'.EFfi ("Basis", "stringToChar"), loc), readType' (t, loc)),
+ ("ReadError", (L'.EFfi ("Basis", "stringToChar_error"), loc), readErrType (t, loc))],
+ loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "read_bool") =>
+ let
+ val t = (L'.TFfi ("Basis", "bool"), loc)
+ in
+ ((L'.ERecord [("Read", (L'.EFfi ("Basis", "stringToBool"), loc), readType' (t, loc)),
+ ("ReadError", (L'.EFfi ("Basis", "stringToBool_error"), loc), readErrType (t, loc))],
+ loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "read_time") =>
+ let
+ val t = (L'.TFfi ("Basis", "time"), loc)
+ in
+ ((L'.ERecord [("Read", (L'.EFfi ("Basis", "stringToTime"), loc), readType' (t, loc)),
+ ("ReadError", (L'.EFfi ("Basis", "stringToTime_error"), loc), readErrType (t, loc))],
+ loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "transaction_return"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("x", t,
+ (L'.TFun ((L'.TRecord [], loc), t), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), t,
+ (L'.ERel 1, loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "transaction_bind"), _), t1), _), t2) =>
+ let
+ val t1 = monoType env t1
+ val t2 = monoType env t2
+ val un = (L'.TRecord [], loc)
+ val mt1 = (L'.TFun (un, t1), loc)
+ val mt2 = (L'.TFun (un, t2), loc)
+ in
+ ((L'.EAbs ("m1", mt1, (L'.TFun ((L'.TFun (t1, mt2), loc), (L'.TFun (un, un), loc)), loc),
+ (L'.EAbs ("m2", (L'.TFun (t1, mt2), loc), (L'.TFun (un, un), loc),
+ (L'.EAbs ("_", un, un,
+ (L'.ELet ("r", t1, (L'.EApp ((L'.ERel 2, loc),
+ (L'.ERecord [], loc)), loc),
+ (L'.EApp (
+ (L'.EApp ((L'.ERel 2, loc), (L'.ERel 0, loc)), loc),
+ (L'.ERecord [], loc)),
+ loc)), loc)), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.EApp ((L.ECApp ((L.EFfi ("Basis", "recv"), _), t1), _), ch) =>
+ let
+ val un = (L'.TRecord [], loc)
+ val t1 = monoType env t1
+ val (ch, fm) = monoExp (env, st, fm) ch
+ in
+ ((L'.EAbs ("_", un, un, (L'.ERecv (liftExpInExp 0 ch, t1), loc)), loc), fm)
+ end
+ | L.EFfiApp ("Basis", "recv", _) => poly ()
+
+ | L.EFfiApp ("Basis", "float", [(e, t)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.EFfiApp ("Basis", "floatFromInt", [(e, monoType env t)]), loc), fm)
+ end
+
+ | L.EFfiApp ("Basis", "sleep", [(n, _)]) =>
+ let
+ val (n, fm) = monoExp (env, st, fm) n
+ in
+ ((L'.ESleep n, loc), fm)
+ end
+ | L.EFfiApp ("Basis", "sleep", _) => poly ()
+
+ | L.ECApp ((L.EFfi ("Basis", "source"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("x", t, (L'.TFun ((L'.TRecord [], loc), (L'.TSource, loc)), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TSource, loc),
+ (L'.EFfiApp ("Basis", "new_client_source",
+ [((L'.EJavaScript (L'.Source t, (L'.ERel 1, loc)), loc),
+ (L'.TSource, loc))]),
+ loc)), loc)),
+ loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "set"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("src", (L'.TSource, loc),
+ (L'.TFun (t, (L'.TFun ((L'.TRecord [], loc), (L'.TRecord [], loc)), loc)), loc),
+ (L'.EAbs ("v", t, (L'.TFun ((L'.TRecord [], loc), (L'.TRecord [], loc)), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.EFfiApp ("Basis", "set_client_source",
+ [((L'.ERel 2, loc), (L'.TSource, loc)),
+ ((L'.EJavaScript (L'.Source t,
+ (L'.ERel 1, loc)), loc),
+ (L'.TFfi ("Basis", "string"), loc))]),
+ loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "get"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("src", (L'.TSource, loc),
+ (L'.TFun ((L'.TRecord [], loc), t), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), t,
+ (L'.EFfiApp ("Basis", "get_client_source",
+ [((L'.ERel 1, loc), (L'.TSource, loc))]),
+ loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "current"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("src", (L'.TSource, loc),
+ (L'.TFun ((L'.TRecord [], loc), t), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), t,
+ (L'.EFfiApp ("Basis", "current",
+ [((L'.ERel 1, loc), (L'.TSource, loc))]),
+ loc)), loc)), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "spawn", [(e, _)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.ESpawn e, loc), fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "signal_return"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("x", t, (L'.TSignal t, loc),
+ (L'.ESignalReturn (L'.ERel 0, loc), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "signal_bind"), _), t1), _), t2) =>
+ let
+ val t1 = monoType env t1
+ val t2 = monoType env t2
+ val un = (L'.TRecord [], loc)
+ val mt1 = (L'.TSignal t1, loc)
+ val mt2 = (L'.TSignal t2, loc)
+ in
+ ((L'.EAbs ("m1", mt1, (L'.TFun ((L'.TFun (t1, mt2), loc), mt2), loc),
+ (L'.EAbs ("m2", (L'.TFun (t1, mt2), loc), mt2,
+ (L'.ESignalBind ((L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "signal"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "int"), loc), (L'.TSignal t, loc),
+ (L'.ESignalSource (L'.ERel 0, loc), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "getCookie"), _), t) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val un = (L'.TRecord [], loc)
+ val t = monoType env t
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (un, s), loc),
+ (L'.EAbs ("_", un, s,
+ (L'.EUnurlify ((L'.EFfiApp ("Basis", "get_cookie", [((L'.ERel 1, loc), s)]), loc),
+ t, true),
+ loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "setCookie"), _), t) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val un = (L'.TRecord [], loc)
+ val t = monoType env t
+ val rt = (L'.TRecord [("Value", t),
+ ("Expires", (L'.TOption (L'.TFfi ("Basis", "time"),
+ loc), loc)),
+ ("Secure", (L'.TFfi ("Basis", "bool"), loc))], loc)
+
+ fun fd x = (L'.EField ((L'.ERel 1, loc), x), loc)
+ val (e, fm) = urlifyExp env fm (fd "Value", t)
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (rt, (L'.TFun (un, un), loc)), loc),
+ (L'.EAbs ("r", rt, (L'.TFun (un, un), loc),
+ (L'.EAbs ("_", un, un,
+ (L'.EFfiApp ("Basis", "set_cookie", [(str (Settings.getUrlPrefix ()), s),
+ ((L'.ERel 2, loc), s),
+ (e, s),
+ (fd "Expires", (L'.TOption (L'.TFfi ("Basis", "time"), loc), loc)),
+ (fd "Secure", (L'.TFfi ("Basis", "bool"), loc))])
+ , loc)), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "clearCookie"), _), t) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val un = (L'.TRecord [], loc)
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (un, un), loc),
+ (L'.EAbs ("_", un, un,
+ (L'.EFfiApp ("Basis", "clear_cookie",
+ [(str (Settings.getUrlPrefix ()), s),
+ ((L'.ERel 1, loc), s)]),
+ loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "channel"), _), t) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "channel"), loc),
+ (L'.EFfiApp ("Basis", "new_channel", [((L'.ERecord [], loc), (L'.TRecord [], loc))]), loc)), loc),
+ fm)
+ | L.ECApp ((L.EFfi ("Basis", "send"), _), t) =>
+ let
+ val t = monoType env t
+ val (e, fm) = urlifyExp env fm ((L'.ERel 1, loc), t)
+ in
+ ((L'.EAbs ("ch", (L'.TFfi ("Basis", "channel"), loc),
+ (L'.TFun (t, (L'.TFun ((L'.TRecord [], loc), (L'.TRecord [], loc)), loc)), loc),
+ (L'.EAbs ("v", t, (L'.TFun ((L'.TRecord [], loc), (L'.TRecord [], loc)), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.EFfiApp ("Basis", "send",
+ [((L'.ERel 2, loc), (L'.TFfi ("Basis", "channel"), loc)),
+ (e, (L'.TFfi ("Basis", "string"), loc))]),
+ loc)), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "no_primary_key"), _), _) =>
+ (str "", fm)
+ | L.ECApp (
+ (L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "primary_key"), _), _), _), t), _),
+ nm), _),
+ (L.CRecord (_, unique), _)) =>
+ let
+ val unique = (nm, t) :: unique
+ val witnesses = (L'.TRecord (map (fn (nm, _) => (monoName env nm, (L'.TRecord [], loc))) unique), loc)
+ in
+ ((L'.EAbs ("_", witnesses, (L'.TFfi ("Basis", "string"), loc),
+ (str
+ (String.concatWith ", "
+ (map (fn (x, _) =>
+ Settings.mangleSql (monoNameLc env x)
+ ^ (if #textKeysNeedLengths (Settings.currentDbms ())
+ andalso isBlobby t then
+ "(767)"
+ else
+ "")) unique)))),
+ loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "no_constraint"), _), _) =>
+ ((L'.ERecord [], loc),
+ fm)
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "one_constraint"), _), _), _), _), _), (L.CName name, _)) =>
+ ((L'.EAbs ("c",
+ (L'.TFfi ("Basis", "string"), loc),
+ (L'.TFfi ("Basis", "sql_constraints"), loc),
+ (L'.ERecord [(name, (L'.ERel 0, loc), (L'.TFfi ("Basis", "string"), loc))], loc)), loc),
+ fm)
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "join_constraints"), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val constraints = (L'.TFfi ("Basis", "sql_constraints"), loc)
+ in
+ ((L'.EAbs ("cs1", constraints, (L'.TFun (constraints, constraints), loc),
+ (L'.EAbs ("cs2", constraints, constraints,
+ (L'.EStrcat ((L'.ERel 1, loc), (L'.ERel 0, loc)), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "unique"), _), _), _), t), _),
+ nm), _),
+ (L.CRecord (_, unique), _)) =>
+ let
+ val unique = (nm, t) :: unique
+ in
+ (str ("UNIQUE ("
+ ^ String.concatWith ", "
+ (map (fn (x, t) => Settings.mangleSql (monoNameLc env x)
+ ^ (if #textKeysNeedLengths (Settings.currentDbms ())
+ andalso isBlobby t then
+ "(767)"
+ else
+ "")) unique)
+ ^ ")"),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "linkable_same"), loc), _) =>
+ ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "linkable_from_nullable"), loc), _) =>
+ ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "linkable_to_nullable"), loc), _) =>
+ ((L'.ERecord [], loc), fm)
+
+ | L.EFfi ("Basis", "mat_nil") =>
+ let
+ val string = (L'.TFfi ("Basis", "string"), loc)
+ val stringE = str ""
+ in
+ ((L'.ERecord [("1", stringE, string),
+ ("2", stringE, string)], loc), fm)
+ end
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "mat_cons"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ (L.CName nm1, _)), _),
+ (L.CName nm2, _)) =>
+ let
+ val string = (L'.TFfi ("Basis", "string"), loc)
+ val mat = (L'.TRecord [("1", string), ("2", string)], loc)
+ in
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun (mat, mat), loc),
+ (L'.EAbs ("m", mat, mat,
+ (L'.ECase ((L'.EField ((L'.ERel 0, loc), "1"), loc),
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ (L'.ERecord [("1", str (Settings.mangleSql (lowercaseFirst nm1)),
+ string),
+ ("2", str (Settings.mangleSql (lowercaseFirst nm2)),
+ string)], loc)),
+ ((L'.PVar ("_", string), loc),
+ (L'.ERecord [("1", (L'.EStrcat (
+ str (Settings.mangleSql (lowercaseFirst nm1)
+ ^ ", "),
+ (L'.EField ((L'.ERel 1, loc), "1"), loc)),
+ loc), string),
+ ("2", (L'.EStrcat (
+ str (Settings.mangleSql (lowercaseFirst nm2)
+ ^ ", "),
+ (L'.EField ((L'.ERel 1, loc), "2"), loc)),
+ loc), string)],
+ loc))],
+ {disc = string,
+ result = mat}), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "restrict"), _), _) => (str "RESTRICT", fm)
+ | L.ECApp ((L.EFfi ("Basis", "cascade"), _), _) => (str "CASCADE", fm)
+ | L.ECApp ((L.EFfi ("Basis", "no_action"), _), _) => (str "NO ACTION", fm)
+ | L.ECApp ((L.EFfi ("Basis", "set_null"), _), _) => (str "SET NULL", fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "foreign_key"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val unit = (L'.TRecord [], loc)
+ val string = (L'.TFfi ("Basis", "string"), loc)
+ val mat = (L'.TRecord [("1", string), ("2", string)], loc)
+ val recd = (L'.TRecord [("OnDelete", string),
+ ("OnUpdate", string)], loc)
+
+ fun strcat [] = raise Fail "Monoize.strcat"
+ | strcat [e] = e
+ | strcat (e1 :: es) = (L'.EStrcat (e1, strcat es), loc)
+
+ fun prop (fd, kw) =
+ (L'.ECase ((L'.EField ((L'.ERel 0, loc), fd), loc),
+ [((L'.PPrim (Prim.String (Prim.Normal, "NO ACTION")), loc),
+ str ""),
+ ((L'.PVar ("_", string), loc),
+ strcat [str (" ON " ^ kw ^ " "),
+ (L'.EField ((L'.ERel 1, loc), fd), loc)])],
+ {disc = string,
+ result = string}), loc)
+ in
+ ((L'.EAbs ("m", mat, (L'.TFun (string, (L'.TFun (recd, string), loc)), loc),
+ (L'.EAbs ("tab", string, (L'.TFun (recd, string), loc),
+ (L'.EAbs ("pr", recd, string,
+ strcat [str "FOREIGN KEY (",
+ (L'.EField ((L'.ERel 2, loc), "1"), loc),
+ str ") REFERENCES ",
+ (L'.ERel 1, loc),
+ str " (",
+ (L'.EField ((L'.ERel 2, loc), "2"), loc),
+ str ")",
+ prop ("OnDelete", "DELETE"),
+ prop ("OnUpdate", "UPDATE")]), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_exp_weaken"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val string = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("e", string, string, (L'.ERel 0, loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "check"), _), _) =>
+ let
+ val string = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("e", string, string,
+ (L'.EStrcat (str "CHECK ",
+ (L'.EFfiApp ("Basis", "checkString",
+ [((L'.ERel 0, loc), string)]), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "dml", [(e, _)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.EDml (e, L'.Error), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "tryDml", [(e, _)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.EDml (e, L'.None), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "insert"), _), fields), _), _) =>
+ (case monoType env (L.TRecord fields, loc) of
+ (L'.TRecord fields, _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val fields = map (fn (x, _) => (x, s)) fields
+ val rt = (L'.TRecord fields, loc)
+ in
+ ((L'.EAbs ("tab", s, (L'.TFun (rt, s), loc),
+ (L'.EAbs ("fs", rt, s,
+ strcat [str "INSERT INTO ",
+ (L'.ERel 1, loc),
+ str " (",
+ strcatComma (map (fn (x, _) => str (Settings.mangleSql x)) fields),
+ str ") VALUES (",
+ strcatComma (map (fn (x, _) =>
+ (L'.EField ((L'.ERel 0, loc),
+ x), loc)) fields),
+ str ")"]), loc)), loc),
+ fm)
+ end
+ | _ => poly ())
+
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "update"), _), _), _), _), _), changed) =>
+ (case monoType env (L.TRecord changed, loc) of
+ (L'.TRecord changed, _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val changed = map (fn (x, _) => (x, s)) changed
+ val rt = (L'.TRecord changed, loc)
+ in
+ ((L'.EAbs ("fs", rt, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("tab", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("e", s, s,
+ if #supportsUpdateAs (Settings.currentDbms ()) then
+ strcat [str "UPDATE ",
+ (L'.ERel 1, loc),
+ str " AS T_T SET ",
+ strcatComma (map (fn (x, _) =>
+ strcat [str (Settings.mangleSql x
+ ^ " = "),
+ (L'.EField
+ ((L'.ERel 2,
+ loc),
+ x), loc)])
+ changed),
+ str " WHERE ",
+ (L'.ERel 0, loc)]
+ else
+ strcat [str "UPDATE ",
+ (L'.ERel 1, loc),
+ str " SET ",
+ strcatComma (map (fn (x, _) =>
+ strcat [str (Settings.mangleSql x
+ ^ " = "),
+ (L'.EFfiApp ("Basis", "unAs",
+ [((L'.EField
+ ((L'.ERel 2,
+ loc),
+ x), loc),
+ s)]), loc)])
+ changed),
+ str " WHERE ",
+ (L'.EFfiApp ("Basis", "unAs", [((L'.ERel 0, loc), s)]), loc)]),
+ loc)), loc)), loc),
+ fm)
+ end
+ | _ => poly ())
+
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "delete"), _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("tab", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("e", s, s,
+ if #supportsDeleteAs (Settings.currentDbms ()) then
+ strcat [str "DELETE FROM ",
+ (L'.ERel 1, loc),
+ str " AS T_T WHERE ",
+ (L'.ERel 0, loc)]
+ else
+ strcat [str "DELETE FROM ",
+ (L'.ERel 1, loc),
+ str " WHERE ",
+ (L'.EFfiApp ("Basis", "unAs", [((L'.ERel 0, loc), s)]), loc)]), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "query"), _), (L.CRecord (_, tables), _)), _),
+ exps), _),
+ state) =>
+ (case monoType env (L.TRecord exps, loc) of
+ (L'.TRecord exps, _) =>
+ let
+ val tables = map (fn ((L.CName x, _), xts) =>
+ (case monoType env (L.TRecord xts, loc) of
+ (L'.TRecord xts, _) => SOME (x, xts)
+ | _ => NONE)
+ | _ => NONE) tables
+ in
+ if List.exists (fn x => x = NONE) tables then
+ poly ()
+ else
+ let
+ val tables = List.mapPartial (fn x => x) tables
+ val state = monoType env state
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val un = (L'.TRecord [], loc)
+
+ val rt = exps @ map (fn (x, xts) => (x, (L'.TRecord xts, loc))) tables
+ val ft = (L'.TFun ((L'.TRecord rt, loc),
+ (L'.TFun (state,
+ (L'.TFun (un, state), loc)),
+ loc)), loc)
+
+ val body' = (L'.EApp (
+ (L'.EApp (
+ (L'.EApp ((L'.ERel 4, loc),
+ (L'.ERel 1, loc)), loc),
+ (L'.ERel 0, loc)), loc),
+ (L'.ERecord [], loc)), loc)
+ val body = (L'.EQuery {exps = exps,
+ tables = tables,
+ state = state,
+ query = (L'.ERel 3, loc),
+ body = body',
+ initial = (L'.ERel 1, loc)},
+ loc)
+ in
+ ((L'.EAbs ("q", s, (L'.TFun (ft, (L'.TFun (state, (L'.TFun (un, state), loc)), loc)), loc),
+ (L'.EAbs ("f", ft, (L'.TFun (state, (L'.TFun (un, state), loc)), loc),
+ (L'.EAbs ("i", state, (L'.TFun (un, state), loc),
+ (L'.EAbs ("_", un, state,
+ body), loc)), loc)), loc)), loc), fm)
+ end
+ end
+ | _ => poly ())
+
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_query"), _), _), _), _), _), _), _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ fun gf s = (L'.EField ((L'.ERel 0, loc), s), loc)
+ in
+ ((L'.EAbs ("r",
+ (L'.TRecord [("Rows", s), ("OrderBy", s), ("Limit", s), ("Offset", s)], loc),
+ s,
+ strcat [gf "Rows",
+ (L'.ECase (gf "OrderBy",
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc), str ""),
+ ((L'.PVar ("orderby", s), loc),
+ strcat [str " ORDER BY ",
+ (L'.ERel 0, loc)])],
+ {disc = s, result = s}), loc),
+ gf "Limit",
+ gf "Offset"]), loc), fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_query1"), _),
+ _), _),
+ _), _),
+ (L.CRecord (_, tables), _)), _),
+ (L.CRecord (_, grouped), _)), _),
+ (L.CRecord (_, stables), _)), _),
+ sexps), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val b = (L'.TFfi ("Basis", "bool"), loc)
+ val un = (L'.TRecord [], loc)
+ fun gf s = (L'.EField ((L'.ERel 0, loc), s), loc)
+
+ fun doTables tables =
+ let
+ val tables = map (fn ((L.CName x, _), xts) =>
+ (case monoType env (L.TRecord xts, loc) of
+ (L'.TRecord xts, _) => SOME (x, xts)
+ | _ => NONE)
+ | _ => NONE) tables
+ in
+ if List.exists (fn x => x = NONE) tables then
+ NONE
+ else
+ let
+ val tables = List.mapPartial (fn x => x) tables
+ val tables = ListMergeSort.sort
+ (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER)
+ tables
+ val tables = map (fn (x, xts) =>
+ (x, ListMergeSort.sort
+ (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER)
+ xts)) tables
+ in
+ SOME tables
+ end
+ end
+ in
+ case (doTables tables, doTables grouped, doTables stables, monoType env (L.TRecord sexps, loc)) of
+ (SOME tables, SOME grouped, SOME stables, (L'.TRecord sexps, _)) =>
+ let
+ val sexps = ListMergeSort.sort
+ (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) sexps
+ in
+ ((L'.EAbs ("r",
+ (L'.TRecord [("Distinct", b),
+ ("From", s),
+ ("Where", s),
+ ("GroupBy", un),
+ ("Having", s),
+ ("SelectFields", un),
+ ("SelectExps", (L'.TRecord (map (fn (x, _) => (x, s)) sexps), loc))],
+ loc),
+ s,
+ strcat [str "SELECT ",
+ (L'.ECase (gf "Distinct",
+ [((L'.PCon (L'.Enum,
+ L'.PConFfi {mod = "Basis",
+ datatyp = "bool",
+ con = "True",
+ arg = NONE},
+ NONE), loc),
+ str "DISTINCT "),
+ ((L'.PCon (L'.Enum,
+ L'.PConFfi {mod = "Basis",
+ datatyp = "bool",
+ con = "False",
+ arg = NONE},
+ NONE), loc),
+ str "")],
+ {disc = b, result = s}), loc),
+ strcatComma (map (fn (x, t) =>
+ strcat [
+ (L'.EField (gf "SelectExps", x), loc),
+ str (" AS " ^ Settings.mangleSql x)
+ ]) sexps
+ @ map (fn (x, xts) =>
+ strcatComma
+ (map (fn (x', _) =>
+ str ("T_" ^ x
+ ^ "."
+ ^ Settings.mangleSql x'))
+ xts)) stables),
+ (L'.ECase (gf "From",
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ str ""),
+ ((L'.PVar ("x", s), loc),
+ strcat [str " FROM ",
+ (L'.ERel 0, loc)])],
+ {disc = s,
+ result = s}), loc),
+ (L'.ECase (gf "Where",
+ [((L'.PPrim (Prim.String (Prim.Normal, #trueString (Settings.currentDbms ()))),
+ loc),
+ str ""),
+ ((L'.PVar ("where", s), loc),
+ strcat [str " WHERE ", (L'.ERel 0, loc)])],
+ {disc = s,
+ result = s}), loc),
+
+ if List.all (fn (x, xts) =>
+ case List.find (fn (x', _) => x' = x) grouped of
+ NONE => List.null xts
+ | SOME (_, xts') =>
+ List.all (fn (x, _) =>
+ List.exists (fn (x', _) => x' = x)
+ xts') xts) tables then
+ str ""
+ else
+ strcat [
+ str " GROUP BY ",
+ strcatComma (map (fn (x, xts) =>
+ strcatComma
+ (map (fn (x', _) =>
+ str ("T_" ^ x
+ ^ "."
+ ^ Settings.mangleSql x'))
+ xts)) grouped)
+ ],
+
+ (L'.ECase (gf "Having",
+ [((L'.PPrim (Prim.String
+ (Prim.Normal, #trueString (Settings.currentDbms ()))), loc),
+ str ""),
+ ((L'.PVar ("having", s), loc),
+ strcat [str " HAVING ", (L'.ERel 0, loc)])],
+ {disc = s,
+ result = s}), loc)
+ ]), loc),
+ fm)
+ end
+ | _ => poly ()
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_inject"), _),
+ _), _),
+ _), _),
+ _), _),
+ t) =>
+ let
+ val t = monoType env t
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("f", (L'.TFun (t, s), loc), (L'.TFun (t, s), loc),
+ (L'.ERel 0, loc)), loc), fm)
+ end
+
+ | L.EFfi ("Basis", "sql_int") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "int"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyInt", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "int"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_float") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "float"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyFloat", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "float"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_bool") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "bool"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyBool", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "bool"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_string") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "string"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyString", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "string"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_char") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "char"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyChar", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "char"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_time") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "time"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyTime", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "time"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_blob") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "blob"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyBlob", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "blob"), loc))]), loc)), loc),
+ fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_channel"), _), _) =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "channel"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyChannel", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "channel"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_client") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "client"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyClient", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "client"), loc))]), loc)), loc),
+ fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_serialized"), _), _) =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "string"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyString", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "string"), loc))]), loc)), loc),
+ fm)
+ | L.EFfi ("Basis", "sql_url") =>
+ ((L'.EAbs ("x", (L'.TFfi ("Basis", "string"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "sqlifyString", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "string"), loc))]), loc)), loc),
+ fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_prim"), _), t) =>
+ let
+ val t = monoType env t
+ val tf = (L'.TFun (t, (L'.TFfi ("Basis", "string"), loc)), loc)
+ in
+ ((L'.EAbs ("f", tf, tf, (L'.ERel 0, loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "sql_option_prim"), _), t) =>
+ let
+ val t = monoType env t
+ val s = (L'.TFfi ("Basis", "string"), loc)
+
+ fun toSqlType (t : L'.typ) =
+ case #1 t of
+ L'.TFfi ("Basis", "int") => Settings.Int
+ | L'.TFfi ("Basis", "float") => Settings.Float
+ | L'.TFfi ("Basis", "string") => Settings.String
+ | L'.TFfi ("Basis", "char") => Settings.Char
+ | L'.TFfi ("Basis", "bool") => Settings.Bool
+ | L'.TFfi ("Basis", "time") => Settings.Time
+ | L'.TFfi ("Basis", "blob") => Settings.Blob
+ | L'.TFfi ("Basis", "channel") => Settings.Channel
+ | L'.TFfi ("Basis", "client") => Settings.Client
+ | _ => raise Fail "Monoize/sql_option_prim: invalid SQL type"
+ in
+ ((L'.EAbs ("f",
+ (L'.TFun (t, s), loc),
+ (L'.TFun ((L'.TOption t, loc), s), loc),
+ (L'.EAbs ("x",
+ (L'.TOption t, loc),
+ s,
+ (L'.ECase ((L'.ERel 0, loc),
+ [((L'.PNone t, loc),
+ str (#p_cast (Settings.currentDbms ()) ("NULL", toSqlType t))),
+ ((L'.PSome (t, (L'.PVar ("y", t), loc)), loc),
+ (L'.EApp ((L'.ERel 2, loc), (L'.ERel 0, loc)), loc))],
+ {disc = (L'.TOption t, loc),
+ result = s}), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "nullify_option"), _), _) =>
+ ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "nullify_prim"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.ERecord [], loc)), loc),
+ fm)
+
+ | L.ECApp ((L.EFfi ("Basis", "sql_subset"), _), _) =>
+ ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_subset_all"), _), _) =>
+ ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_subset_concat"),
+ _), _), _), _), _), _), _), _) =>
+ let
+ val un = (L'.TRecord [], loc)
+ in
+ ((L'.EAbs ("_", un, (L'.TFun (un, un), loc),
+ (L'.EAbs ("_", un, un,
+ (L'.ERecord [], loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "fieldsOf_table"), _), _), _), _) =>
+ ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "fieldsOf_view"), _), _) =>
+ ((L'.ERecord [], loc), fm)
+
+ | L.ECApp ((L.EFfi ("Basis", "sql_from_nil"), _), _) =>
+ (str "", fm)
+ | L.ECApp ((L.EApp ((L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_from_table"), _), _),
+ _), _), _), _), _), _), _),
+ (L.CName name, _)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("tab", s, s,
+ strcat [(L'.ERel 0, loc),
+ str (" AS T_" ^ name)]), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_from_query"), _), _),
+ _), _), _),
+ (L.CName name, _)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("q", s, s,
+ strcat [str "(",
+ (L'.ERel 0, loc),
+ str (") AS T_" ^ name)]), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_from_comma"), _), _), _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val disc = (L'.TRecord [("1", s), ("2", s)], loc)
+ in
+ ((L'.EAbs ("tab1", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("tab2", s, s,
+ (L'.ECase ((L'.ERecord [("1", (L'.ERel 1, loc), s),
+ ("2", (L'.ERel 0, loc), s)], loc),
+ [((L'.PRecord [("1", (L'.PPrim (Prim.String (Prim.Normal, "")), loc), s)], loc),
+ (L'.ERel 0, loc)),
+ ((L'.PRecord [("2", (L'.PPrim (Prim.String (Prim.Normal, "")), loc), s)], loc),
+ (L'.ERel 1, loc)),
+ ((L'.PVar ("_", disc), loc),
+ strcat [(L'.ERel 2, loc),
+ str ", ",
+ (L'.ERel 1, loc)])],
+ {disc = disc,
+ result = s}), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_inner_join"), _), _), _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val disc = (L'.TRecord [("1", s), ("2", s)], loc)
+ in
+ ((L'.EAbs ("tab1", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("tab2", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("on", s, s,
+ (L'.ECase ((L'.ERecord [("1", (L'.ERel 2, loc), s),
+ ("2", (L'.ERel 1, loc), s)], loc),
+ [((L'.PRecord [("1", (L'.PPrim (Prim.String (Prim.Normal, "")), loc), s)], loc),
+ (L'.ERel 1, loc)),
+ ((L'.PRecord [("2", (L'.PPrim (Prim.String (Prim.Normal, "")), loc), s)], loc),
+ (L'.ERel 2, loc)),
+ ((L'.PVar ("_", disc), loc),
+ strcat ((if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str "("]
+ else
+ [])
+ @ [(L'.ERel 3, loc),
+ str " JOIN ",
+ (L'.ERel 2, loc),
+ str " ON ",
+ (L'.ERel 1, loc)]
+ @ (if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str ")"]
+ else
+ [])))],
+ {disc = disc,
+ result = s}), loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_left_join"), _), _), _), _), _),
+ (L.CRecord (_, right), _)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val disc = (L'.TRecord [("1", s), ("2", s)], loc)
+ in
+ ((L'.EAbs ("_", outerRec right,
+ (L'.TFun (s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc)), loc),
+ (L'.EAbs ("tab1", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("tab2", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("on", s, s,
+ (L'.ECase ((L'.ERecord [("1", (L'.ERel 2, loc), s),
+ ("2", (L'.ERel 1, loc), s)], loc),
+ [((L'.PRecord [("1", (L'.PPrim (Prim.String (Prim.Normal, "")),
+ loc), s)], loc),
+ (L'.ERel 1, loc)),
+ ((L'.PRecord [("2", (L'.PPrim (Prim.String (Prim.Normal, "")),
+ loc), s)], loc),
+ (L'.ERel 2, loc)),
+ ((L'.PVar ("_", disc), loc),
+ strcat ((if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str "("]
+ else
+ [])
+ @ [(L'.ERel 3, loc),
+ str " LEFT JOIN ",
+ (L'.ERel 2, loc),
+ str " ON ",
+ (L'.ERel 1, loc)]
+ @ (if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str ")"]
+ else
+ [])))],
+ {disc = disc,
+ result = s}), loc)), loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_right_join"), _), (L.CRecord (_, left), _)),
+ _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val disc = (L'.TRecord [("1", s), ("2", s)], loc)
+ in
+ ((L'.EAbs ("_", outerRec left,
+ (L'.TFun (s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc)), loc),
+ (L'.EAbs ("tab1", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("tab2", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("on", s, s,
+ (L'.ECase ((L'.ERecord [("1", (L'.ERel 2, loc), s),
+ ("2", (L'.ERel 1, loc), s)], loc),
+ [((L'.PRecord [("1", (L'.PPrim (Prim.String (Prim.Normal, "")),
+ loc), s)], loc),
+ (L'.ERel 1, loc)),
+ ((L'.PRecord [("2", (L'.PPrim (Prim.String (Prim.Normal, "")),
+ loc), s)], loc),
+ (L'.ERel 2, loc)),
+ ((L'.PVar ("_", disc), loc),
+ strcat ((if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str "("]
+ else
+ [])
+ @ [(L'.ERel 3, loc),
+ str " RIGHT JOIN ",
+ (L'.ERel 2, loc),
+ str " ON ",
+ (L'.ERel 1, loc)]
+ @ (if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str ")"]
+ else
+ [])))],
+ {disc = disc,
+ result = s}), loc)), loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_full_join"), _), (L.CRecord (_, left), _)), _),
+ (L.CRecord (_, right), _)), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val disc = (L'.TRecord [("1", s), ("2", s)], loc)
+ in
+ ((L'.EAbs ("_", outerRec (left @ right),
+ (L'.TFun (s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc)), loc),
+ (L'.EAbs ("tab1", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("tab2", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("on", s, s,
+ (L'.ECase ((L'.ERecord [("1", (L'.ERel 2, loc), s),
+ ("2", (L'.ERel 1, loc), s)], loc),
+ [((L'.PRecord [("1", (L'.PPrim (Prim.String (Prim.Normal, "")),
+ loc), s)], loc),
+ (L'.ERel 1, loc)),
+ ((L'.PRecord [("2", (L'.PPrim (Prim.String (Prim.Normal, "")),
+ loc), s)], loc),
+ (L'.ERel 2, loc)),
+ ((L'.PVar ("_", disc), loc),
+ strcat ((if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str "("]
+ else
+ [])
+ @ [(L'.ERel 3, loc),
+ str " FULL JOIN ",
+ (L'.ERel 2, loc),
+ str " ON ",
+ (L'.ERel 1, loc)]
+ @ (if #nestedRelops
+ (Settings.currentDbms ()) then
+ [str ")"]
+ else
+ [])))],
+ {disc = disc,
+ result = s}), loc)), loc)), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_order_by_Nil"), _), _), _), _) =>
+ (str "", fm)
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_order_by_random"), _), _), _), _) =>
+ (str (#randomFunction (Settings.currentDbms ()) ^ "()"), fm)
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_order_by_Cons"), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun (s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc)), loc),
+ (L'.EAbs ("e1", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("d", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("e2", s, s,
+ (L'.ECase ((L'.ERel 0, loc),
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ strcat [(L'.ERel 2, loc),
+ (L'.ERel 1, loc)]),
+ ((L'.PVar ("_", s), loc),
+ strcat [(L'.ERel 3, loc),
+ (L'.ERel 2, loc),
+ str ", ",
+ (L'.ERel 1, loc)])],
+ {disc = s, result = s}), loc)), loc)), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.EFfi ("Basis", "sql_no_limit") =>
+ (str "", fm)
+ | L.EFfiApp ("Basis", "sql_limit", [(e, t)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ (strcat [
+ str " LIMIT ",
+ (L'.EFfiApp ("Basis", "sqlifyInt", [(e, monoType env t)]), loc)
+ ],
+ fm)
+ end
+
+ | L.EFfi ("Basis", "sql_no_offset") =>
+ (str "", fm)
+ | L.EFfiApp ("Basis", "sql_offset", [(e, t)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ (strcat [
+ str " OFFSET ",
+ (L'.EFfiApp ("Basis", "sqlifyInt", [(e, monoType env t)]), loc)
+ ],
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "sql_eq"), _), _) =>
+ (str "=", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_ne"), _), _) =>
+ (str "<>", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_lt"), _), _) =>
+ (str "<", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_le"), _), _) =>
+ (str "<=", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_gt"), _), _) =>
+ (str ">", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_ge"), _), _) =>
+ (str ">=", fm)
+
+ | L.ECApp ((L.EFfi ("Basis", "sql_plus"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "+"), loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_minus"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "-"), loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_times"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "*"), loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_div"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "/"), loc), fm)
+ | L.EFfi ("Basis", "sql_mod") =>
+ (str "%", fm)
+
+ | L.EFfi ("Basis", "sql_like") =>
+ (str "LIKE", fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_unary"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("e1", s, (L'.TFun (s, s), loc),
+ strcat [str "(",
+ (L'.ERel 1, loc),
+ str " ",
+ (L'.ERel 0, loc),
+ str ")"]), loc)), loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "sql_not") => (str "NOT", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_neg"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "-"), loc), fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_binary"), _),
+ _), _),
+ _), _),
+ _), _),
+ arg1), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+
+ fun default n = strcat [str "(",
+ (L'.ERel (n + 1), loc),
+ str " ",
+ (L'.ERel (n + 2), loc),
+ str " ",
+ (L'.ERel n, loc),
+ str ")"]
+
+ val body = case #1 arg1 of
+ L.CApp ((L.CFfi ("Basis", "option"), _), _) =>
+ (L'.ECase ((L'.ERel 2, loc),
+ [((L'.PPrim (Prim.String (Prim.Normal, "=")), loc),
+ if #supportsIsDistinctFrom (Settings.currentDbms ()) then
+ strcat [str "((",
+ (L'.ERel 1, loc),
+ str " IS NOT DISTINCT FROM ",
+ (L'.ERel 0, loc),
+ str "))"]
+ else
+ strcat [str "((",
+ (L'.ERel 1, loc),
+ str " ",
+ (L'.ERel 2, loc),
+ str " ",
+ (L'.ERel 0, loc),
+ str ") OR ((",
+ (L'.ERel 1, loc),
+ str ") IS NULL AND (",
+ (L'.ERel 0, loc),
+ str ") IS NULL))"]),
+ ((L'.PVar ("_", s), loc),
+ default 1)],
+ {disc = s,
+ result = s}), loc)
+ | _ => default 0
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("e1", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("e2", s, s,
+ body), loc)), loc)), loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "sql_and") => (str "AND", fm)
+ | L.EFfi ("Basis", "sql_or") => (str "OR", fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_field"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ (L.CName tab, _)), _),
+ (L.CName field, _)) => (str ("T_" ^ tab ^ "." ^ Settings.mangleSql (lowercaseFirst field)), fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_exp"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ (L.CName nm, _)) => (str (Settings.mangleSql (lowercaseFirst nm)), fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_relop"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ val disc = (L'.TFfi ("Basis", "bool"), loc)
+ in
+ (if #nestedRelops (Settings.currentDbms ()) then
+ (L'.EAbs ("c", s, (L'.TFun ((L'.TFfi ("Basis", "bool"), loc), (L'.TFun (s, (L'.TFun (s, s), loc)), loc)), loc),
+ (L'.EAbs ("all", (L'.TFfi ("Basis", "bool"), loc), (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("e1", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("e2", s, s,
+ strcat [str "((",
+ (L'.ERel 1, loc),
+ str ") ",
+ (L'.ERel 3, loc),
+ (L'.ECase ((L'.ERel 2, loc),
+ [((L'.PCon (L'.Enum, L'.PConFfi {mod = "Basis",
+ datatyp = "bool",
+ con = "True",
+ arg = NONE}, NONE), loc),
+ str " ALL"),
+ ((L'.PVar ("_", disc), loc),
+ str "")],
+ {disc = disc,
+ result = s}), loc),
+ str " (",
+ (L'.ERel 0, loc),
+ str "))"]), loc)), loc)), loc)), loc)
+ else
+ (L'.EAbs ("c", s, (L'.TFun ((L'.TFfi ("Basis", "bool"), loc), (L'.TFun (s, (L'.TFun (s, s), loc)), loc)), loc),
+ (L'.EAbs ("all", (L'.TFfi ("Basis", "bool"), loc), (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("e1", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("e2", s, s,
+ strcat [(L'.ERel 1, loc),
+ str " ",
+ (L'.ERel 3, loc),
+ (L'.ECase ((L'.ERel 2, loc),
+ [((L'.PCon (L'.Enum, L'.PConFfi {mod = "Basis",
+ datatyp = "bool",
+ con = "True",
+ arg = NONE}, NONE), loc),
+ str " ALL"),
+ ((L'.PVar ("_", disc), loc),
+ str "")],
+ {disc = disc,
+ result = s}), loc),
+ str " ",
+ (L'.ERel 0, loc)]), loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_forget_tables"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("x", s, s, (L'.ERel 0, loc)), loc),
+ fm)
+ end
+
+ | L.EFfi ("Basis", "sql_union") => (str "UNION", fm)
+ | L.EFfi ("Basis", "sql_intersect") =>
+ (if #onlyUnion (Settings.currentDbms ()) then
+ ErrorMsg.errorAt loc "The DBMS you've selected doesn't support INTERSECT."
+ else
+ ();
+ (str "INTERSECT", fm))
+ | L.EFfi ("Basis", "sql_except") =>
+ (if #onlyUnion (Settings.currentDbms ()) then
+ ErrorMsg.errorAt loc "The DBMS you've selected doesn't support EXCEPT."
+ else
+ ();
+ (str "EXCEPT", fm))
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_count"), _),
+ _), _),
+ _), _),
+ _) => (str "COUNT(*)", fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_aggregate"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ t) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+
+ val main = strcat [(L'.ERel 1, loc),
+ str "(",
+ (L'.ERel 0, loc),
+ str ")"]
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("e1", s, (L'.TFun (s, s), loc), main), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "sql_count_col"), _), _) =>
+ (str "COUNT", fm)
+
+ | L.EFfi ("Basis", "sql_summable_int") => ((L'.ERecord [], loc), fm)
+ | L.EFfi ("Basis", "sql_summable_float") => ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_summable_option"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.ERecord [], loc)), loc),
+ fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_avg"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "AVG"), loc),
+ fm)
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_sum"), _), _), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun ((L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc)), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "SUM"), loc)), loc),
+ fm)
+
+ | L.EFfi ("Basis", "sql_arith_int") => ((L'.ERecord [], loc), fm)
+ | L.EFfi ("Basis", "sql_arith_float") => ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_arith_option"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.ERecord [], loc)), loc),
+ fm)
+
+ | L.EFfi ("Basis", "sql_maxable_int") => ((L'.ERecord [], loc), fm)
+ | L.EFfi ("Basis", "sql_maxable_float") => ((L'.ERecord [], loc), fm)
+ | L.EFfi ("Basis", "sql_maxable_string") => ((L'.ERecord [], loc), fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_maxable_option"), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.ERecord [], loc)), loc),
+ fm)
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_max"), _), _), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun ((L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc)), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "MAX"), loc)), loc),
+ fm)
+ | L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_min"), _), _), _), _) =>
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun ((L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc)), loc),
+ (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFfi ("Basis", "string"), loc),
+ str "MIN"), loc)), loc),
+ fm)
+
+ | L.EFfi ("Basis", "sql_asc") => (str "", fm)
+ | L.EFfi ("Basis", "sql_desc") => (str " DESC", fm)
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_nfunc"), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s, (L'.ERel 0, loc)), loc),
+ fm)
+ end
+
+ | L.EFfi ("Basis", "sql_window_normal") => ((L'.ERecord [], loc), fm)
+ | L.EFfi ("Basis", "sql_window_fancy") => ((L'.ERecord [], loc), fm)
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_window"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun (s, s), loc),
+ (L'.EAbs ("e", s, s,
+ (L'.ERel 0, loc)), loc)), loc),
+ fm)
+ end
+
+ | L.EFfi ("Basis", "sql_current_timestamp") => (str "CURRENT_TIMESTAMP", fm)
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_ufunc"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("f", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("x", s, s,
+ strcat [(L'.ERel 1, loc),
+ str "(",
+ (L'.ERel 0, loc),
+ str ")"]), loc)), loc),
+ fm)
+ end
+ | L.EFfi ("Basis", "sql_octet_length") =>
+ (str (if #supportsOctetLength (Settings.currentDbms ()) then
+ "octet_length"
+ else
+ "length"), fm)
+ | L.EFfi ("Basis", "sql_lower") =>
+ (str "lower", fm)
+ | L.EFfi ("Basis", "sql_upper") =>
+ (str "upper", fm)
+ | L.ECApp ((L.EFfi ("Basis", "sql_known"), _), _) =>
+ ((L'.EFfi ("Basis", "sql_known"), loc), fm)
+
+ | (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_is_null"), _), _),
+ _), _),
+ _), _),
+ _), _)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("s", s, s,
+ strcat [str "(",
+ (L'.ERel 0, loc),
+ str " IS NULL)"]), loc),
+ fm)
+ end
+
+ | (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_coalesce"), _), _),
+ _), _),
+ _), _),
+ _), _)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("x1", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("x1", s, s,
+ strcat [str "COALESCE(",
+ (L'.ERel 1, loc),
+ str ",",
+ (L'.ERel 0, loc),
+ str ")"]), loc)), loc),
+ fm)
+ end
+
+ | (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_if_then_else"), _), _),
+ _), _),
+ _), _),
+ _), _)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("if", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("then", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("else", s, s,
+ strcat [str "(CASE WHEN (",
+ (L'.ERel 2, loc),
+ str ") THEN (",
+ (L'.ERel 1, loc),
+ str ") ELSE (",
+ (L'.ERel 0, loc),
+ str ") END)"]), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_nullable"), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("u", (L'.TRecord [], loc), (L'.TFun (s, s), loc),
+ (L'.EAbs ("x", s, s,
+ (L'.ERel 0, loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_subquery"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TFun (s, s), loc),
+ (L'.EAbs ("x", s, s,
+ strcat [str "(",
+ (L'.ERel 0, loc),
+ str ")"]), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_no_partition"), _),
+ _), _),
+ _), _),
+ _) => (str "", fm)
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_partition"), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("e", s, s, strcat [str "PARTITION BY ", (L'.ERel 0, loc)]), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_window_function"), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val () = if #windowFunctions (Settings.currentDbms ()) then
+ ()
+ else
+ ErrorMsg.errorAt loc "The DBMS you've selected doesn't support window functions."
+
+ val s = (L'.TFfi ("Basis", "string"), loc)
+
+ val main = strcat [(L'.ERel 2, loc),
+ str " OVER (",
+ (L'.ERel 1, loc),
+ (L'.ECase ((L'.ERel 0, loc),
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ str ""),
+ ((L'.PVar ("_", s), loc),
+ strcat [str " ORDER BY ",
+ (L'.ERel 1, loc)])],
+ {disc = s,
+ result = s}), loc),
+ str ")"]
+ in
+ ((L'.EAbs ("w", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("p", s, (L'.TFun (s, s), loc),
+ (L'.EAbs ("o", s, s,
+ main), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "sql_window_aggregate"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+
+ val main = strcat [(L'.ERel 1, loc),
+ str "(",
+ (L'.ERel 0, loc),
+ str ")"]
+ in
+ ((L'.EAbs ("c", s, (L'.TFun (s, (L'.TFun (s, s), loc)), loc),
+ (L'.EAbs ("e1", s, s, main), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_window_count"), _), _), _), _), _), _) =>
+ (str "COUNT(*)", fm)
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sql_rank"), _), _), _), _), _), _) =>
+ (str "RANK()", fm)
+
+ | L.EFfiApp ("Basis", "nextval", [(e, _)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.ENextval e, loc), fm)
+ end
+ | L.EFfiApp ("Basis", "setval", [(e1, _), (e2, _)]) =>
+ let
+ val (e1, fm) = monoExp (env, st, fm) e1
+ val (e2, fm) = monoExp (env, st, fm) e2
+ in
+ ((L'.ESetval (e1, e2), loc), fm)
+ end
+
+ | L.EFfi ("Basis", "null") => (str "", fm)
+
+ | L.EFfiApp ("Basis", "classes", [(s1, _), (s2, _)]) =>
+ let
+ val (s1, fm) = monoExp (env, st, fm) s1
+ val (s2, fm) = monoExp (env, st, fm) s2
+ in
+ ((L'.EStrcat (s1, (L'.EStrcat (str " ", s2), loc)), loc),
+ fm)
+ end
+
+ | L.EFfi ("Basis", "data_kind") => (str "data-", fm)
+ | L.EFfi ("Basis", "aria_kind") => (str "aria-", fm)
+
+ | L.EFfiApp ("Basis", "data_attr", [(sk, _), (s1, _), (s2, _)]) =>
+ let
+ val (sk, fm) = monoExp (env, st, fm) sk
+ val (s1, fm) = monoExp (env, st, fm) s1
+ val (s2, fm) = monoExp (env, st, fm) s2
+ in
+ ((L'.EStrcat (sk,
+ (L'.EStrcat ((L'.EFfiApp ("Basis", "blessData", [(s1, (L'.TFfi ("Basis", "string"), loc))]), loc),
+ (L'.EStrcat (str "=\"",
+ (L'.EStrcat ((L'.EFfiApp ("Basis", "attrifyString", [(s2, (L'.TFfi ("Basis", "string"), loc))]), loc),
+ str "\""), loc)),
+ loc)), loc)), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "data_attrs", [(s1, _), (s2, _)]) =>
+ let
+ val (s1, fm) = monoExp (env, st, fm) s1
+ val (s2, fm) = monoExp (env, st, fm) s2
+ in
+ ((L'.EStrcat (s1, (L'.EStrcat (str " ", s2), loc)), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "css_url", [(s, _)]) =>
+ let
+ val (s, fm) = monoExp (env, st, fm) s
+ in
+ ((L'.EStrcat (str "url(",
+ (L'.EStrcat ((L'.EFfiApp ("Basis", "css_url", [(s, (L'.TFfi ("Basis", "string"), loc))]), loc),
+ str ")"), loc)), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "property", [(s, _)]) =>
+ let
+ val (s, fm) = monoExp (env, st, fm) s
+ in
+ ((L'.EStrcat ((L'.EFfiApp ("Basis", "property", [(s, (L'.TFfi ("Basis", "string"), loc))]), loc),
+ str ":"), loc),
+ fm)
+ end
+ | L.EFfiApp ("Basis", "value", [(s1, _), (s2, _)]) =>
+ let
+ val (s1, fm) = monoExp (env, st, fm) s1
+ val (s2, fm) = monoExp (env, st, fm) s2
+ in
+ ((L'.EStrcat (s1, (L'.EStrcat (str " ", s2), loc)), loc),
+ fm)
+ end
+
+ | L.EFfi ("Basis", "noStyle") => (str "", fm)
+ | L.EFfiApp ("Basis", "oneProperty", [(s1, _), (s2, _)]) =>
+ let
+ val (s1, fm) = monoExp (env, st, fm) s1
+ val (s2, fm) = monoExp (env, st, fm) s2
+ in
+ ((L'.EStrcat (s1, (L'.EStrcat (s2, str ";"), loc)), loc),
+ fm)
+ end
+
+ | L.EApp (
+ (L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "cdata"), _), _), _),
+ _), _),
+ se) =>
+ let
+ val (se, fm) = monoExp (env, st, fm) se
+ in
+ ((L'.EFfiApp ("Basis", "htmlifyString", [(se, (L'.TFfi ("Basis", "string"), loc))]), loc), fm)
+ end
+ | L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "cdataChar"), _), _), _),
+ _) =>
+ ((L'.EAbs ("ch", (L'.TFfi ("Basis", "char"), loc), (L'.TFfi ("Basis", "string"), loc),
+ (L'.EFfiApp ("Basis", "htmlifySpecialChar", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "char"), loc))]), loc)), loc), fm)
+
+ | L.EApp (
+ (L.EApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "join"),
+ _), _), _),
+ _), _),
+ _), _),
+ _), _),
+ xml1), _),
+ xml2) =>
+ let
+ val (xml1, fm) = monoExp (env, st, fm) xml1
+ val (xml2, fm) = monoExp (env, st, fm) xml2
+ in
+ ((L'.EStrcat (xml1, xml2), loc), fm)
+ end
+
+ | L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "tag"),
+ _), (L.CRecord (_, attrsGiven), _)), _), _), _), ctxOuter), _), _), _), _), _), _), _), _), _), _), _),
+ class), _),
+ dynClass), _),
+ style), _),
+ dynStyle), _),
+ attrs), _),
+ tag), _),
+ xml) =>
+ let
+ fun getTag' (e, _) =
+ case e of
+ L.EFfi (_, tag) => (tag, [])
+ | L.ECApp (e, t) => let
+ val (tag, ts) = getTag' e
+ in
+ (tag, ts @ [t])
+ end
+ | _ => (E.errorAt loc "Non-constant XML tag";
+ Print.eprefaces' [("Expression", CorePrint.p_exp env tag)];
+ ("", []))
+
+ fun getTag (e, _) =
+ case e of
+ L.EFfiApp (_, tag, [((L.ERecord [], _), _)]) => (tag, [])
+ | L.EApp (e, (L.ERecord [], _)) => getTag' e
+ | _ => (E.errorAt loc "Non-constant XML tag";
+ Print.eprefaces' [("Expression", CorePrint.p_exp env tag)];
+ ("", []))
+
+ val (tag, targs) = getTag tag
+
+ val (attrs, fm) = monoExp (env, st, fm) attrs
+ val attrs = case #1 attrs of
+ L'.ERecord xes => xes
+ | _ => map (fn ((L.CName x, _), t) => (x, (L'.EField (attrs, x), loc), monoType env t)
+ | (c, t) => (E.errorAt loc "Non-constant field name for HTML tag attribute";
+ Print.eprefaces' [("Name", CorePrint.p_con env c)];
+ ("", (L'.EField (attrs, ""), loc), monoType env t))) attrsGiven
+
+ val attrs =
+ if List.exists (fn ("Link", _, _) => true
+ | _ => false) attrs then
+ List.filter (fn ("Href", _, _) => false
+ | _ => true) attrs
+ else
+ attrs
+
+ fun findOnload (attrs, onload, onunload, acc) =
+ case attrs of
+ [] => (onload, onunload, acc)
+ | ("Onload", e, _) :: rest => findOnload (rest, SOME e, onunload, acc)
+ | ("Onunload", e, _) :: rest => findOnload (rest, onload, SOME e, acc)
+ | x :: rest => findOnload (rest, onload, onunload, x :: acc)
+
+ val (onload, onunload, attrs) =
+ if tag = "body" then
+ findOnload (attrs, NONE, NONE, [])
+ 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" =>
+ (case List.partition (fn (x, _, _) => x = "Value") attrs of
+ ([(_, 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 dynamics = ["dyn", "ctextbox", "cpassword", "ccheckbox", "cselect", "coption", "ctextarea", "active", "script", "cemail", "csearch", "curl", "ctel", "ccolor"]
+
+ fun isSome (e, _) =
+ case e of
+ L'.ESome _ => true
+ | _ => false
+
+ val () = if isSome dynClass orelse isSome dynStyle then
+ if List.exists (fn x => x = tag) dynamics then
+ E.errorAt loc ("Dynamic tag <" ^ tag ^ "> cannot be combined with 'dynClass' or 'dynStyle' attribute; an additional <span> may be useful")
+ else
+ ()
+ else
+ ()
+
+ fun tagStart tag' =
+ let
+ val t = (L'.TFfi ("Basis", "string"), loc)
+ val s = strH (String.concat ["<", tag'])
+
+ val s = (L'.EStrcat (s,
+ (L'.ECase (class,
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ strH ""),
+ ((L'.PVar ("x", t), loc),
+ (L'.EStrcat (strH " class=\"",
+ (L'.EStrcat ((L'.ERel 0, loc),
+ strH "\""),
+ loc)), loc))],
+ {disc = t,
+ result = t}), loc)), loc)
+
+ val s = (L'.EStrcat (s,
+ (L'.ECase (style,
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ strH ""),
+ ((L'.PVar ("x", t), loc),
+ (L'.EStrcat (strH " style=\"",
+ (L'.EStrcat ((L'.ERel 0, loc),
+ strH "\""),
+ loc)), loc))],
+ {disc = t,
+ result = t}), loc)), loc)
+
+ val (s, fm) = foldl (fn (("Action", _, _), acc) => acc
+ | (("Source", _, _), acc) => acc
+ | (("Data", e, _), (s, fm)) =>
+ ((L'.EStrcat (s,
+ (L'.EStrcat (
+ strH " ",
+ e), loc)), loc),
+ fm)
+ | ((x, e, t), (s, fm)) =>
+ case t of
+ (L'.TFfi ("Basis", "bool"), _) =>
+ let
+ val s' = " " ^ lowercaseFirst x
+ in
+ ((L'.ECase (e,
+ [((L'.PCon (L'.Enum,
+ L'.PConFfi {mod = "Basis",
+ datatyp = "bool",
+ con = "True",
+ arg = NONE},
+ NONE), loc),
+ (L'.EStrcat (s,
+ strH s'), loc)),
+ ((L'.PCon (L'.Enum,
+ L'.PConFfi {mod = "Basis",
+ datatyp = "bool",
+ con = "False",
+ arg = NONE},
+ NONE), loc),
+ s)],
+ {disc = (L'.TFfi ("Basis", "bool"), loc),
+ result = (L'.TFfi ("Basis", "string"), loc)}), loc),
+ fm)
+ end
+ | (L'.TFun (dom, _), _) =>
+ let
+ val e =
+ case #1 dom of
+ L'.TRecord [] => (L'.EApp (e, (L'.ERecord [], loc)), loc)
+ | _ =>
+ if String.isPrefix "Onkey" x then
+ (L'.EApp ((L'.EApp (e, (L'.EFfiApp ("Basis", "keyEvent", []), loc)),
+ loc), (L'.ERecord [], loc)), loc)
+ else
+ (L'.EApp ((L'.EApp (e, (L'.EFfiApp ("Basis", "mouseEvent", []), loc)),
+ loc), (L'.ERecord [], loc)), loc)
+
+ val s' = " " ^ lowercaseFirst x ^ "='uw_event=event;exec("
+ in
+ ((L'.EStrcat (s,
+ (L'.EStrcat (
+ strH s',
+ (L'.EStrcat (
+ (L'.EJavaScript (L'.Attribute, e), loc),
+ strH ")'"), loc)),
+ loc)), loc),
+ fm)
+ end
+ | _ =>
+ let
+ val fooify =
+ case x of
+ "Link" => urlifyExp
+ | "Action" => urlifyExp
+ | _ => attrifyExp
+
+ val x =
+ case x of
+ "Typ" => "Type"
+ | "Nam" => "Name"
+ | "Link" => "Href"
+ | _ => x
+
+ val x = String.translate (fn #"_" => "-"
+ | ch => String.str ch) x
+
+ val xp = " " ^ lowercaseFirst x ^ "=\""
+
+ val (e, fm) = fooify env fm (e, t)
+ val e = case (tag, x) of
+ ("coption", "Value") => (L'.EStrcat (strH "x", e), loc)
+ | _ => e
+ in
+ ((L'.EStrcat (s,
+ (L'.EStrcat (strH xp,
+ (L'.EStrcat (e,
+ strH "\""),
+ loc)),
+ loc)), loc),
+ fm)
+ end)
+ (s, fm) attrs
+ in
+ (if tag = "coption" andalso List.all (fn ("Value", _, _) => false | _ => true) attrs then
+ (L'.EStrcat (s,
+ strH " value=\"\""), loc)
+ else
+ s,
+ fm)
+ end
+
+ fun input typ =
+ case targs of
+ [_, (L.CName name, _)] =>
+ let
+ val (ts, fm) = tagStart "input"
+ in
+ ((L'.EStrcat (ts,
+ strH (" type=\"" ^ typ ^ "\" name=\"" ^ name ^ "\" />")), loc), fm)
+ end
+ | _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
+ raise Fail "No name passed to input tag")
+
+ fun normal (tag, extra) =
+ let
+ val (tagStart, fm) = tagStart tag
+ val tagStart = case extra of
+ 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
+
+ val xml = case extraString of
+ NONE => xml
+ | SOME extra => (L'.EStrcat (extra, xml), loc)
+ in
+ ((L'.EStrcat ((L'.EStrcat (tagStart, strH ">"), loc),
+ (L'.EStrcat (xml,
+ strH (String.concat ["</", firstWord tag, ">"])), loc)),
+ loc),
+ fm)
+ end
+
+ fun isSingleton () =
+ let
+ val (bef, aft) = Substring.splitl (not o Char.isSpace) (Substring.full tag)
+ in
+ SS.member (singletons, if Substring.isEmpty aft then
+ tag
+ else
+ Substring.string bef)
+ end
+ in
+ case (xml, extraString) of
+ ((L.EApp ((L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "cdata"), _),
+ _), _),
+ _), _),
+ (L.EPrim (Prim.String (_, s)), _)), _), NONE) =>
+ if CharVector.all Char.isSpace s andalso isSingleton () then
+ ((L'.EStrcat (tagStart, strH " />"), loc), fm)
+ else
+ normal ()
+ | _ => normal ()
+ end
+
+ fun setAttrs jexp =
+ let
+ val s = strH (String.concat ["<", tag])
+
+ val assgns = List.mapPartial
+ (fn ("Source", _, _) => NONE
+ | ("Onchange", e, _) =>
+ SOME (strcat [str "addOnChange(d,exec(",
+ (L'.EJavaScript (L'.Script, e), loc),
+ str "));"])
+ | (x, e, (L'.TFun ((L'.TRecord [], _), _), _)) =>
+ SOME (strcat [str ("d." ^ lowercaseFirst x ^ "=exec("),
+ (L'.EJavaScript (L'.Script, e), loc),
+ str ");"])
+ | (x, e, _) =>
+ if String.isPrefix "On" x then
+ let
+ val arg = if String.isPrefix "Onkey" x then
+ SOME (L'.EFfiApp ("Basis", "keyEvent", []), loc)
+ else if String.isSuffix "click" x orelse String.isPrefix "Onmouse" x then
+ SOME (L'.EFfiApp ("Basis", "mouseEvent", []), loc)
+ else
+ NONE
+
+ val e = liftExpInExp 0 e
+
+ val e = case arg of
+ NONE => e
+ | SOME arg => (L'.EApp (e, arg), loc)
+
+ val e = (L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.EApp (e, (L'.ERecord [], loc)), loc)), loc)
+ in
+ case x of
+ "Onkeyup" =>
+ SOME (strcat [str ("((function(c){addOnKeyUp(d,function(ev){window.uw_event=ev?ev:window.event;return c();});})(exec("),
+ (L'.EJavaScript (L'.Script, e), loc),
+ str ")));"])
+ | _ =>
+ SOME (strcat [str ("((function(c){d." ^ lowercaseFirst x ^ "=function(ev){window.uw_event=ev?ev:window.event;return c();};})(exec("),
+ (L'.EJavaScript (L'.Script, e), loc),
+ str ")));"])
+ end
+ else
+ SOME (strcat [str ("d." ^ lowercaseFirst x ^ "=exec("),
+ (L'.EJavaScript (L'.Script, e), loc),
+ str ");"]))
+ attrs
+
+ val t = (L'.TFfi ("Basis", "string"), loc)
+ val setClass = (L'.ECase (class,
+ [((L'.PPrim (Prim.String (Prim.Normal, "")), loc),
+ str ""),
+ ((L'.PVar ("x", t), loc),
+ (L'.EStrcat (strH "d.className=\"",
+ (L'.EStrcat ((L'.ERel 0, loc),
+ strH "\";"), loc)),
+ loc))],
+ {disc = (L'.TOption t, loc),
+ result = t}), loc)
+ in
+ case assgns of
+ [] => strcat [str "var d=",
+ jexp,
+ str ";",
+ setClass]
+ | _ => strcat (str "var d="
+ :: jexp
+ :: str ";"
+ :: setClass
+ :: assgns)
+ end
+
+ fun execify e =
+ case e of
+ NONE => strH ""
+ | SOME e =>
+ let
+ val e = (L'.EApp (e, (L'.ERecord [], loc)), loc)
+ in
+ (L'.EStrcat (strH "exec(",
+ (L'.EStrcat ((L'.EJavaScript (L'.Attribute, e), loc),
+ strH ")"), loc)), loc)
+ end
+
+ fun inTag tag' = case ctxOuter of
+ (L.CRecord (_, ctx), _) =>
+ List.exists (fn ((L.CName tag'', _), _) => tag'' = tag'
+ | _ => false) ctx
+ | _ => false
+
+ fun pnode () = if inTag "Tr" then
+ "tr"
+ else if inTag "Table" then
+ "table"
+ else
+ "span"
+
+ fun cinput (fallback, dynamic) =
+ case List.find (fn ("Source", _, _) => true | _ => false) attrs of
+ NONE =>
+ let
+ val (ts, fm) = tagStart "input"
+ in
+ ((L'.EStrcat (ts,
+ strH (" type=\"" ^ fallback ^ "\" />")),
+ loc), fm)
+ end
+ | SOME (_, src, _) =>
+ let
+ val sc = strcat [str (dynamic ^ "(exec("),
+ (L'.EJavaScript (L'.Script, src), loc),
+ str "))"]
+ val sc = setAttrs sc
+ in
+ (strcat [str "<script type=\"text/javascript\">",
+ sc,
+ str "</script>"],
+ fm)
+ end
+
+ val baseAll as (base, fm) =
+ case tag of
+ "body" => let
+ val onload = execify onload
+ val onunload = execify onunload
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ normal ("body",
+ SOME (L'.EStrcat ((L'.EFfiApp ("Basis", "maybe_onload",
+ [((L'.EStrcat ((L'.EFfiApp ("Basis", "get_settings",
+ [((L'.ERecord [], loc),
+ (L'.TRecord [], loc))]), loc),
+ onload), loc),
+ s)]),
+ loc),
+ (L'.EFfiApp ("Basis", "maybe_onunload",
+ [(onunload, s)]),
+ loc)), loc))
+ end
+
+ | "dyn" =>
+ let
+ in
+ case attrs of
+ [("Signal", e, _)] =>
+ ((L'.EStrcat
+ (strH ("<script type=\"text/javascript\">dyn(\""
+ ^ pnode () ^ "\", execD("),
+ (L'.EStrcat ((L'.EJavaScript (L'.Script, e), loc),
+ strH ("))</script>")), loc)), loc),
+ fm)
+ | _ => raise Fail "Monoize: Bad <dyn> attributes"
+ end
+
+ | "active" =>
+ (case attrs of
+ [("Code", e, _)] =>
+ ((L'.EStrcat
+ (strH "<script type=\"text/javascript\">active(execD(",
+ (L'.EStrcat ((L'.EJavaScript (L'.Script, e), loc),
+ strH "))</script>"), loc)), loc),
+ fm)
+ | _ => raise Fail "Monoize: Bad <active> attributes")
+
+ | "script" =>
+ (case attrs of
+ [("Code", e, _)] =>
+ ((L'.EStrcat
+ (strH "<script type=\"text/javascript\">execF(execD(",
+ (L'.EStrcat ((L'.EJavaScript (L'.Script, e), loc),
+ strH "))</script>"), loc)), loc),
+ fm)
+ | _ => raise Fail "Monoize: Bad <script> attributes")
+
+ | "submit" => normal ("input type=\"submit\"", NONE)
+ | "image" => normal ("input type=\"image\"", NONE)
+ | "hidden" => input "hidden"
+
+ | "textbox" =>
+ (case targs of
+ [_, (L.CName name, _)] =>
+ (case List.find (fn ("Source", _, _) => true | _ => false) attrs of
+ NONE =>
+ let
+ val (ts, fm) = tagStart "input"
+ in
+ ((L'.EStrcat (ts,
+ strH (" type=\"text\" name=\"" ^ name ^ "\" />")),
+ loc), fm)
+ end
+ | SOME (_, src, _) =>
+ (strcat [str "<script type=\"text/javascript\">inp(exec(",
+ (L'.EJavaScript (L'.Script, src), loc),
+ str "), \"",
+ str name,
+ str "\")</script>"],
+ fm))
+ | _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
+ raise Fail "No name passed to textbox tag"))
+ | "password" => input "password"
+ | "email" => input "email"
+ | "search" => input "search"
+ | "url_" => input "url"
+ | "tel" => input "tel"
+ | "color" => input "color"
+ | "number" => input "number"
+ | "range" => input "range"
+ | "date" => input "date"
+ | "datetime" => input "datetime"
+ | "datetime_local" => input "datetime-local"
+ | "month" => input "month"
+ | "week" => input "week"
+ | "timeInput" => input "time"
+ | "textarea" =>
+ (case targs of
+ [_, (L.CName name, _)] =>
+ let
+ val (ts, fm) = tagStart "textarea"
+ val (xml, fm) = monoExp (env, st, fm) xml
+ in
+ ((L'.EStrcat ((L'.EStrcat (ts,
+ strH (" name=\"" ^ name ^ "\">")), loc),
+ (L'.EStrcat (xml,
+ strH "</textarea>"), loc)),
+ loc), fm)
+ end
+ | _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
+ raise Fail "No name passed to ltextarea tag"))
+
+ | "checkbox" => input "checkbox"
+ | "upload" => input "file"
+
+ | "radio" =>
+ (case targs of
+ [_, (L.CName name, _)] =>
+ monoExp (env, St.setRadioGroup (st, name), fm) xml
+ | _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
+ raise Fail "No name passed to radio tag"))
+ | "radioOption" =>
+ (case St.radioGroup st of
+ NONE => raise Fail "No name for radioGroup"
+ | SOME name =>
+ normal ("input",
+ SOME (strH (" type=\"radio\" name=\"" ^ name ^ "\""))))
+
+ | "select" =>
+ (case targs of
+ [_, (L.CName name, _)] =>
+ let
+ val (ts, fm) = tagStart "select"
+ val (xml, fm) = monoExp (env, st, fm) xml
+ in
+ ((L'.EStrcat ((L'.EStrcat (ts,
+ strH (" name=\"" ^ name ^ "\">")), loc),
+ (L'.EStrcat (xml,
+ strH "</select>"),
+ loc)),
+ loc),
+ fm)
+ end
+ | _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
+ raise Fail "No name passed to lselect tag"))
+
+ | "ctextbox" => cinput ("text", "inp")
+ | "cpassword" => cinput ("password", "password")
+ | "cemail" => cinput ("email", "email")
+ | "csearch" => cinput ("search", "search")
+ | "curl" => cinput ("url", "url")
+ | "ctel" => cinput ("tel", "tel")
+ | "ccolor" => cinput ("color", "color")
+
+ | "cnumber" => cinput ("number", "number")
+ | "crange" => cinput ("range", "range")
+ | "cdate" => cinput ("date", "date")
+ | "cdatetime" => cinput ("datetime", "datetime")
+ | "cdatetime_local" => cinput ("datetime-local", "datetime_local")
+ | "cmonth" => cinput ("month", "month")
+ | "cweek" => cinput ("week", "week")
+ | "ctime" => cinput ("time", "time")
+
+ | "ccheckbox" => cinput ("checkbox", "chk")
+ | "cselect" =>
+ (case List.find (fn ("Source", _, _) => true | _ => false) attrs of
+ NONE =>
+ let
+ val (xml, fm) = monoExp (env, st, fm) xml
+ val (ts, fm) = tagStart "select"
+ in
+ (strcat [ts,
+ str ">",
+ xml,
+ str "</select>"],
+ fm)
+ end
+ | SOME (_, src, _) =>
+ let
+ val (xml, fm) = monoExp (env, st, fm) xml
+
+ val sc = strcat [str "sel(exec(",
+ (L'.EJavaScript (L'.Script, src), loc),
+ str "),exec(",
+ (L'.EJavaScript (L'.Script, xml), loc),
+ str "))"]
+ val sc = setAttrs sc
+ in
+ (strcat [str "<script type=\"text/javascript\">",
+ sc,
+ str "</script>"],
+ fm)
+ end)
+
+ | "coption" => normal ("option", NONE)
+
+ | "ctextarea" =>
+ (case List.find (fn ("Source", _, _) => true | _ => false) attrs of
+ NONE =>
+ let
+ val (ts, fm) = tagStart "textarea"
+ in
+ ((L'.EStrcat (ts,
+ strH " />"),
+ loc), fm)
+ end
+ | SOME (_, src, _) =>
+ let
+ val sc = strcat [str "tbx(exec(",
+ (L'.EJavaScript (L'.Script, src), loc),
+ str "))"]
+ val sc = setAttrs sc
+ in
+ (strcat [str "<script type=\"text/javascript\">",
+ sc,
+ str "</script>"],
+ fm)
+ end)
+
+ | "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
+ L'.ENone _ =>
+ (case #1 dynStyle' of
+ L'.ENone _ => baseAll
+ | L'.ESome (_, ds) => (strcat [str "<script type=\"text/javascript\">dynClass(\"",
+ str (pnode ()),
+ str "\",execD(",
+ (L'.EJavaScript (L'.Script, base), loc),
+ str "),null,execD(",
+ (L'.EJavaScript (L'.Script, ds), loc),
+ str "))</script>"],
+ fm)
+ | _ => (E.errorAt loc "Absence/presence of 'dynStyle' unknown";
+ baseAll))
+ | L'.ESome (_, dc) =>
+ let
+ val e = case #1 dynStyle' of
+ L'.ENone _ => str "null"
+ | L'.ESome (_, ds) => strcat [str "execD(",
+ (L'.EJavaScript (L'.Script, ds), loc),
+ str ")"]
+ | _ => (E.errorAt loc "Absence/presence of 'dynStyle' unknown";
+ str "null")
+ in
+ (strcat [str "<script type=\"text/javascript\">dynClass(\"",
+ str (pnode ()),
+ str "\",execD(",
+ (L'.EJavaScript (L'.Script, base), loc),
+ str "),execD(",
+ (L'.EJavaScript (L'.Script, dc), loc),
+ str "),",
+ e,
+ str ")</script>"],
+ fm)
+ end
+ | _ => (E.errorAt loc "Absence/presence of 'dynClass' unknown";
+ baseAll)
+ end
+
+ | L.EApp (
+ (L.EApp ((L.EApp ((L.ECApp (
+ (L.ECApp ((L.EFfi ("Basis", "form"), _), _), _),
+ (L.CRecord (_, fields), _)), _),
+ id), _),
+ class), _),
+ xml) =>
+ let
+ fun findSubmit (e, _) =
+ case e of
+ L.EApp (
+ (L.EApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "join"),
+ _), _), _),
+ _), _),
+ _), _),
+ _), _),
+ xml1), _),
+ xml2) => (case findSubmit xml1 of
+ Error => Error
+ | NotFound => findSubmit xml2
+ | Found e =>
+ case findSubmit xml2 of
+ NotFound => Found e
+ | _ => Error)
+ | L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.EApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "tag"),
+ _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ attrs), _),
+ _), _),
+ xml) =>
+ (case #1 attrs of
+ L.ERecord xes =>
+ (case ListUtil.search (fn ((L.CName "Action", _), e, t) => SOME (e, t)
+ | _ => NONE) xes of
+ NONE => findSubmit xml
+ | SOME et =>
+ case findSubmit xml of
+ NotFound => Found et
+ | _ => Error)
+ | _ => findSubmit xml)
+ | _ => NotFound
+
+ val (func, action, fm) = case findSubmit xml of
+ NotFound => (0, strH "", fm)
+ | Error => raise Fail "Not ready for multi-submit lforms yet"
+ | Found (action, actionT) =>
+ let
+ val func = case #1 action of
+ L.EClosure (n, _) => n
+ | _ => raise Fail "Monoize: Action is not a closure"
+ val actionT = monoType env actionT
+ val (action, fm) = monoExp (env, st, fm) action
+ val (action, fm) = urlifyExp env fm (action, actionT)
+ in
+ (func,
+ (L'.EStrcat (strH " action=\"",
+ (L'.EStrcat (action,
+ strH "\""), loc)), loc),
+ fm)
+ end
+
+ val hasUpload = CoreUtil.Exp.exists {kind = fn _ => false,
+ con = fn _ => false,
+ exp = fn e =>
+ case e of
+ L.EFfi ("Basis", "upload") => true
+ | _ => false} xml
+
+ val (xml, fm) = monoExp (env, st, fm) xml
+
+ val xml =
+ if IS.member (!readCookie, func) then
+ let
+ fun inFields s = List.exists (fn ((L.CName s', _), _) => s' = s
+ | _ => true) fields
+
+ fun getSigName () =
+ let
+ fun getSigName' n =
+ let
+ val s = "Sig" ^ Int.toString n
+ in
+ if inFields s then
+ getSigName' (n + 1)
+ else
+ s
+ end
+ in
+ if inFields "Sig" then
+ getSigName' 0
+ else
+ "Sig"
+ end
+
+ val sigName = getSigName ()
+ val sigSet = (L'.EFfiApp ("Basis", "sigString", [((L'.ERecord [], loc), (L'.TRecord [], loc))]), loc)
+ val sigSet = (L'.EStrcat (strH ("<input type=\"hidden\" name=\""
+ ^ sigName
+ ^ "\" value=\""),
+ sigSet), loc)
+ val sigSet = (L'.EStrcat (sigSet,
+ strH "\" />"), loc)
+ in
+ (L'.EStrcat (sigSet, xml), loc)
+ end
+ else
+ xml
+
+ val action = if hasUpload then
+ (L'.EStrcat (action,
+ strH " enctype=\"multipart/form-data\""), loc)
+ else
+ action
+
+ val stt = (L'.TFfi ("Basis", "string"), loc)
+ val (id, fm) = monoExp (env, st, fm) id
+ val (class, fm) = monoExp (env, st, fm) class
+ val action = (L'.EStrcat (action,
+ (L'.ECase (class,
+ [((L'.PNone stt, loc),
+ strH ""),
+ ((L'.PSome (stt, (L'.PVar ("x", stt), loc)), loc),
+ (L'.EStrcat (strH " class=\"",
+ (L'.EStrcat ((L'.ERel 0, loc),
+ strH "\""), loc)), loc))],
+ {disc = (L'.TOption stt, loc),
+ result = stt}), loc)), loc)
+ in
+ ((L'.EStrcat ((L'.EStrcat (strH "<form method=\"post\"",
+ (L'.EStrcat ((L'.ECase (id,
+ [((L'.PNone stt, loc),
+ strH ""),
+ ((L'.PSome (stt, (L'.PVar ("id", stt), loc)), loc),
+ (L'.EStrcat (strH " id=\"",
+ (L'.EStrcat ((L'.ERel 0, loc),
+ strH "\""), loc)), loc))],
+ {disc = (L'.TOption stt, loc),
+ result = stt}), loc),
+ (L'.EStrcat (action,
+ strH ">"), loc)), loc)), loc),
+ (L'.EStrcat (xml,
+ strH "</form>"), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.ECApp (
+ (L.EFfi ("Basis", "subform"), _), _), _), _),
+ _), _), _), (L.CName nm, loc)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("xml", s, s,
+ strcat [strH ("<input type=\"hidden\" name=\".b\" value=\""
+ ^ nm ^ "\" />"),
+ (L'.ERel 0, loc),
+ strH ("<input type=\"hidden\" name=\".e\" value=\"1\" />")]),
+ loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp ((L.ECApp ((L.ECApp (
+ (L.EFfi ("Basis", "subforms"), _), _), _), _),
+ _), _), _), (L.CName nm, loc)) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("xml", s, s,
+ strcat [strH ("<input type=\"hidden\" name=\".s\" value=\""
+ ^ nm ^ "\" />"),
+ (L'.ERel 0, loc),
+ strH ("<input type=\"hidden\" name=\".e\" value=\"1\" />")]),
+ loc),
+ fm)
+ end
+
+ | L.ECApp ((L.ECApp (
+ (L.EFfi ("Basis", "entry"), _), _), _), _) =>
+ let
+ val s = (L'.TFfi ("Basis", "string"), loc)
+ in
+ ((L'.EAbs ("xml", s, s,
+ strcat [strH ("<input type=\"hidden\" name=\".i\" value=\"1\" />"),
+ (L'.ERel 0, loc),
+ strH ("<input type=\"hidden\" name=\".e\" value=\"1\" />")]),
+ loc),
+ fm)
+ end
+
+ | L.EApp ((L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.ECApp (
+ (L.EFfi ("Basis", "useMore"), _), _), _),
+ _), _),
+ _), _),
+ _), _),
+ xml) => monoExp (env, st, fm) xml
+
+ | L.ECApp ((L.EFfi ("Basis", "error"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("s", (L'.TFfi ("Basis", "string"), loc), t,
+ (L'.EError ((L'.ERel 0, loc), t), loc)), loc),
+ fm)
+ end
+ | L.EApp (
+ (L.ECApp ((L.EFfi ("Basis", "returnBlob"), _), t), _),
+ (L.EFfiApp ("Basis", "textBlob", [(e, _)]), _)) =>
+ let
+ val t = monoType env t
+ val un = (L'.TRecord [], loc)
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.EAbs ("mt", (L'.TFfi ("Basis", "string"), loc), (L'.TFun (un, t), loc),
+ (L'.EAbs ("_", un, t,
+ (L'.ESeq ((L'.EFfiApp ("Basis", "clear_page", []), loc),
+ (L'.ESeq ((L'.EWrite (liftExpInExp 0 (liftExpInExp 0 e)), loc),
+ (L'.EReturnBlob {blob = NONE,
+ mimeType = (L'.ERel 1, loc),
+ t = t}, loc)), loc)), loc)), loc)),
+ loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "returnBlob"), _), t) =>
+ let
+ val t = monoType env t
+ val un = (L'.TRecord [], loc)
+ in
+ ((L'.EAbs ("b", (L'.TFfi ("Basis", "blob"), loc),
+ (L'.TFun ((L'.TFfi ("Basis", "string"), loc), (L'.TFun (un, t), loc)), loc),
+ (L'.EAbs ("mt", (L'.TFfi ("Basis", "string"), loc), (L'.TFun (un, t), loc),
+ (L'.EAbs ("_", un, t,
+ (L'.EReturnBlob {blob = SOME (L'.ERel 2, loc),
+ mimeType = (L'.ERel 1, loc),
+ t = t}, loc)), loc)), loc)), loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "redirect"), _), t) =>
+ let
+ val t = monoType env t
+ val un = (L'.TRecord [], loc)
+ in
+ ((L'.EAbs ("url", (L'.TFfi ("Basis", "string"), loc), (L'.TFun (un, t), loc),
+ (L'.EAbs ("_", un, t,
+ (L'.ERedirect ((L'.ERel 1, loc), t), loc)), loc)), loc),
+ fm)
+ end
+
+ | L.ECApp ((L.EFfi ("Basis", "serialize"), _), t) =>
+ let
+ val t = monoType env t
+ val (e, fm) = urlifyExp env fm ((L'.ERel 0, loc), t)
+ in
+ ((L'.EAbs ("v", t, (L'.TFfi ("Basis", "string"), loc), e), loc),
+ fm)
+ end
+ | L.ECApp ((L.EFfi ("Basis", "deserialize"), _), t) =>
+ let
+ val t = monoType env t
+ in
+ ((L'.EAbs ("v", (L'.TFfi ("Basis", "string"), loc), t, (L'.EUnurlify ((L'.ERel 0, loc), t, false),
+ loc)), loc),
+ fm)
+ end
+
+ | L.EFfiApp ("Basis", "url", [(e, _)]) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ val (e, fm) = urlifyExp env fm (e, dummyTyp)
+ in
+ ((L'.EStrcat (str (Settings.getUrlPrePrefix ()), e), loc), fm)
+ end
+
+ | L.EApp (e1, e2) =>
+ let
+ val (e1, fm) = monoExp (env, st, fm) e1
+ val (e2, fm) = monoExp (env, st, fm) e2
+ in
+ ((L'.EApp (e1, e2), loc), fm)
+ end
+ | L.EAbs (x, dom, ran, e) =>
+ let
+ val (e, fm) = monoExp (Env.pushERel env x dom, st, fm) e
+ in
+ ((L'.EAbs (x, monoType env dom, monoType env ran, e), loc), fm)
+ end
+
+ | L.ECApp (e, _) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ case #1 e of
+ L'.EFfi _ => (e, fm)
+ | _ => poly ()
+ end
+ | L.ECAbs _ => poly ()
+
+ | L.EFfi mx => ((L'.EFfi mx, loc), fm)
+ | L.EFfiApp (m, x, es) =>
+ let
+ val (es, fm) = ListUtil.foldlMap (fn ((e, t), fm) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((e, monoType env t), fm)
+ end) fm es
+ in
+ ((L'.EFfiApp (m, x, es), loc), fm)
+ end
+
+ | L.ERecord xes =>
+ let
+ val (xes, fm) = ListUtil.foldlMap
+ (fn ((x, e, t), fm) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((monoName env x,
+ e,
+ monoType env t), fm)
+ end) fm xes
+
+ val xes = ListMergeSort.sort (fn ((x, _, _), (y, _, _)) => String.compare (x, y) = GREATER) xes
+ in
+ ((L'.ERecord xes, loc), fm)
+ end
+ | L.EField (e, x, _) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.EField (e, monoName env x), loc), fm)
+ end
+ | L.EConcat _ => poly ()
+ | L.ECut _ => poly ()
+ | L.ECutMulti _ => poly ()
+
+ | L.ECase (e, pes, {disc, result}) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ val (pes, fm) = ListUtil.foldlMap
+ (fn ((p, e), fm) =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((monoPat env p, e), fm)
+ end) fm pes
+ in
+ ((L'.ECase (e, pes, {disc = monoType env disc, result = monoType env result}), loc), fm)
+ end
+
+ | L.EWrite e =>
+ let
+ val (e, fm) = monoExp (env, st, fm) e
+ in
+ ((L'.EAbs ("_", (L'.TRecord [], loc), (L'.TRecord [], loc),
+ (L'.EWrite (liftExpInExp 0 e), loc)), loc), fm)
+ end
+
+ | L.EClosure (n, es) =>
+ let
+ val (es, fm) = ListUtil.foldlMap (fn (e, fm) =>
+ monoExp (env, st, fm) e)
+ fm es
+ val e = (L'.EClosure (n, es), loc)
+ in
+ (e, fm)
+ end
+
+ | L.ELet (x, t, e1, e2) =>
+ let
+ val t' = monoType env t
+ val (e1, fm) = monoExp (env, st, fm) e1
+ val (e2, fm) = monoExp (Env.pushERel env x t, st, fm) e2
+ in
+ ((L'.ELet (x, t', e1, e2), loc), fm)
+ end
+
+ | L.EServerCall (n, es, t, fmode) =>
+ let
+ val t = monoType env t
+ val (_, ft, _, name) = Env.lookupENamed env n
+ val (es, fm) = ListUtil.foldlMap (fn (e, fm) => monoExp (env, st, fm) e) fm es
+
+ fun encodeArgs (es, ft, acc, fm) =
+ case (es, ft) of
+ ([], _) => (rev acc, fm)
+ | (e :: es, (L.TFun (dom, ran), _)) =>
+ let
+ val (e, fm) = urlifyExp env fm (e, monoType env dom)
+ in
+ encodeArgs (es, ran, e
+ :: str "/"
+ :: acc, fm)
+ end
+ | _ => raise Fail "Monoize: Not enough arguments visible in RPC function type"
+
+ val (call, fm) = encodeArgs (es, ft, [], fm)
+ val call = foldl (fn (e, call) => (L'.EStrcat (call, e), loc))
+ (str name) call
+
+ val unit = (L'.TRecord [], loc)
+
+ val eff = if IS.member (!readCookie, n) then
+ L'.ReadCookieWrite
+ else
+ L'.ReadOnly
+
+ val e = (L'.EServerCall (call, t, eff, fmode), loc)
+ val e = liftExpInExp 0 e
+ val e = (L'.EAbs ("_", unit, unit, e), loc)
+ in
+ (e, fm)
+ end
+
+ | L.EKAbs _ => poly ()
+ | L.EKApp _ => poly ()
+ end
+
+fun monoDecl (env, fm) (all as (d, loc)) =
+ let
+ fun poly () =
+ (E.errorAt loc "Unsupported declaration";
+ Print.eprefaces' [("Declaration", CorePrint.p_decl env all)];
+ NONE)
+
+ fun str s = (L'.EPrim (Prim.String (Prim.Normal, s)), loc)
+ fun strH s = (L'.EPrim (Prim.String (Prim.Html, s)), loc)
+ in
+ case d of
+ L.DCon _ => NONE
+ | L.DDatatype [("list", n, [_], [("Nil", _, NONE),
+ ("Cons", _, SOME (L.TRecord (L.CRecord (_,
+ [((L.CName "1", _),
+ (L.CRel 0, _)),
+ ((L.CName "2", _),
+ (L.CApp ((L.CNamed n', _),
+ (L.CRel 0, _)),
+ _))]), _), _))])] =>
+ if n = n' then
+ NONE
+ else
+ poly ()
+ | L.DDatatype dts =>
+ let
+ val env' = Env.declBinds env all
+ val dts = map (fn (x, n, [], xncs) =>
+ (x, n, map (fn (x, n, to) => (x, n, Option.map (monoType env') to)) xncs)
+ | _ => (E.errorAt loc "Polymorphic datatype needed too late";
+ Print.eprefaces' [("Declaration", CorePrint.p_decl env all)];
+ ("", 0, []))) dts
+ val d = (L'.DDatatype dts, loc)
+ in
+ SOME (env', fm, [d])
+ end
+ | L.DVal (x, n, t, e, s) =>
+ let
+ val (e, fm) = monoExp (env, St.empty, fm) e
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DVal (x, n, monoType env t, e, s), loc)])
+ end
+ | L.DValRec vis =>
+ let
+ val vis = map (fn (x, n, t, e, s) =>
+ let
+ fun maybeTransaction (t, e) =
+ case (#1 t, #1 e) of
+ (L.CApp ((L.CFfi ("Basis", "transaction"), _), _), _) =>
+ SOME (L.EAbs ("_",
+ (L.TRecord (L.CRecord ((L.KType, loc), []), loc), loc),
+ t,
+ (L.EApp (CoreEnv.liftExpInExp 0 e,
+ (L.ERecord [], loc)), loc)), loc)
+ | (L.TFun (dom, ran), L.EAbs (x, _, _, e)) =>
+ (case maybeTransaction (ran, e) of
+ NONE => NONE
+ | SOME e => SOME (L.EAbs (x, dom, ran, e), loc))
+ | _ => NONE
+ in
+ (x, n, t,
+ case maybeTransaction (t, e) of
+ NONE => e
+ | SOME e => e,
+ s)
+ end) vis
+
+ val env = foldl (fn ((x, n, t, e, s), env) => Env.pushENamed env x n t NONE s) env vis
+
+ val (vis, fm) = ListUtil.foldlMap
+ (fn ((x, n, t, e, s), fm) =>
+ let
+ val (e, fm) = monoExp (env, St.empty, fm) e
+ in
+ ((x, n, monoType env t, e, s), fm)
+ end)
+ fm vis
+ in
+ SOME (env,
+ fm,
+ [(L'.DValRec vis, loc)])
+ end
+ | L.DExport (ek, n, b) =>
+ let
+ val (_, t, _, s) = Env.lookupENamed env n
+
+ fun unwind (t, args) =
+ case #1 t of
+ L.TFun (dom, ran) => unwind (ran, dom :: args)
+ | L.CApp ((L.CFfi ("Basis", "transaction"), _), t) =>
+ unwind (t, (L.TRecord (L.CRecord ((L.KType, loc), []), loc), loc) :: args)
+ | _ => (rev args, t)
+
+ val (ts, ran) = unwind (t, [])
+ val ts = map (monoType env) ts
+ val ran = monoType env ran
+ in
+ SOME (env, fm, [(L'.DExport (ek, s, n, ts, ran, b), loc)])
+ end
+ | L.DTable (x, n, (L.CRecord (_, xts), _), s, pe, _, ce, _) =>
+ let
+ val t = (L.CFfi ("Basis", "string"), loc)
+ val t' = (L'.TFfi ("Basis", "string"), loc)
+ val s = Settings.mangleSqlTable s
+ val e_name = str s
+
+ val xts = map (fn (x, t) => (monoName env x, monoType env t)) xts
+
+ val (pe, fm) = monoExp (env, St.empty, fm) pe
+ val (ce, fm) = monoExp (env, St.empty, fm) ce
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DTable (s, xts, pe, ce), loc),
+ (L'.DVal (x, n, t', e_name, s), loc)])
+ end
+ | L.DTable _ => poly ()
+ | L.DView (x, n, s, e, (L.CRecord (_, xts), _)) =>
+ let
+ val t = (L.CFfi ("Basis", "string"), loc)
+ val t' = (L'.TFfi ("Basis", "string"), loc)
+ val s = Settings.mangleSqlTable s
+ val e_name = str s
+
+ val xts = map (fn (x, t) => (monoName env x, monoType env t)) xts
+
+ val (e, fm) = monoExp (env, St.empty, fm) e
+ val e = (L'.EFfiApp ("Basis", "viewify", [(e, t')]), loc)
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DView (s, xts, e), loc),
+ (L'.DVal (x, n, t', e_name, s), loc)])
+ end
+ | L.DView _ => poly ()
+ | L.DSequence (x, n, s) =>
+ let
+ val t = (L.CFfi ("Basis", "string"), loc)
+ val t' = (L'.TFfi ("Basis", "string"), loc)
+ val s = Settings.mangleSql s
+ val e = str s
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DSequence s, loc),
+ (L'.DVal (x, n, t', e, s), loc)])
+ end
+ | L.DDatabase _ => NONE
+ | L.DCookie (x, n, t, s) =>
+ let
+ val t = (L.CFfi ("Basis", "string"), loc)
+ val t' = (L'.TFfi ("Basis", "string"), loc)
+ val e = str s
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DCookie s, loc),
+ (L'.DVal (x, n, t', e, s), loc)])
+ end
+ | L.DStyle (x, n, s) =>
+ let
+ val t = (L.CFfi ("Basis", "string"), loc)
+ val t' = (L'.TFfi ("Basis", "string"), loc)
+ val e = strH s
+ in
+ SOME (Env.pushENamed env x n t NONE s,
+ fm,
+ [(L'.DStyle s, loc),
+ (L'.DVal (x, n, t', e, s), loc)])
+ end
+ | L.DTask (e1, e2) =>
+ let
+ val (e1, fm) = monoExp (env, St.empty, fm) e1
+ val (e2, fm) = monoExp (env, St.empty, fm) e2
+
+ val un = (L'.TRecord [], loc)
+ val t = if MonoUtil.Exp.exists {typ = fn _ => false,
+ exp = fn L'.EFfiApp ("Basis", "periodic", _) =>
+ (if #persistent (Settings.currentProtocol ()) then
+ ()
+ else
+ E.errorAt (#2 e1)
+ ("Periodic tasks aren't allowed in the selected protocol (" ^ #name (Settings.currentProtocol ()) ^ ").");
+ true)
+ | _ => false} e1 then
+ (L'.TFfi ("Basis", "int"), loc)
+ else
+ un
+
+ val e2 = (L'.EAbs ("$x", t, (L'.TFun (un, un), loc),
+ (L'.EAbs ("$y", un, un,
+ (L'.EApp (
+ (L'.EApp (e2, (L'.ERel 1, loc)), loc),
+ (L'.ERel 0, loc)), loc)), loc)), loc)
+ in
+ SOME (env,
+ fm,
+ [(L'.DTask (e1, e2), loc)])
+ end
+ | L.DPolicy e =>
+ let
+ fun policies (e, fm) =
+ case #1 e of
+ L.EFfiApp ("Basis", "also", [(e1, _), (e2, _)]) =>
+ let
+ val (ps1, fm) = policies (e1, fm)
+ val (ps2, fm) = policies (e2, fm)
+ in
+ (ps1 @ ps2, fm)
+ end
+ | _ =>
+ let
+ val (e, make) =
+ case #1 e of
+ L.EApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "sendClient"), _), _), _), _), _), e) =>
+ (e, L'.PolClient)
+ | L.EApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "mayInsert"), _), _), _), _), _), e) =>
+ (e, L'.PolInsert)
+ | L.EApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "mayDelete"), _), _), _), _), _), e) =>
+ (e, L'.PolDelete)
+ | L.EApp ((L.ECApp ((L.ECApp ((L.EFfi ("Basis", "mayUpdate"), _), _), _), _), _), e) =>
+ (e, L'.PolUpdate)
+ | L.EFfiApp ("Basis", "sendOwnIds", [(e, _)]) =>
+ (e, L'.PolSequence)
+ | _ => (poly (); (e, L'.PolClient))
+
+ val (e, fm) = monoExp (env, St.empty, fm) e
+ in
+ ([(L'.DPolicy (make e), loc)], fm)
+ end
+
+ val (ps, fm) = policies (e, fm)
+ in
+ SOME (env, fm, ps)
+ end
+ | L.DOnError n => SOME (env,
+ fm,
+ [(L'.DOnError n, loc)])
+ end
+
+datatype expungable = Client | Channel
+
+fun monoize env file =
+ let
+ val () = pvars := RM.empty
+
+ (* Calculate which exported functions need cookie signature protection *)
+ val rcook = foldl (fn ((d, _), rcook) =>
+ case d of
+ L.DExport (L.Action L.ReadCookieWrite, n, _) => IS.add (rcook, n)
+ | L.DExport (L.Rpc L.ReadCookieWrite, n, _) => IS.add (rcook, n)
+ | _ => rcook)
+ IS.empty file
+ val () = readCookie := rcook
+
+ val loc = E.dummySpan
+ val client = (L'.TFfi ("Basis", "client"), loc)
+ val unit = (L'.TRecord [], loc)
+
+ fun str s = (L'.EPrim (Prim.String (Prim.Normal, s)), loc)
+ fun strH s = (L'.EPrim (Prim.String (Prim.Html, s)), loc)
+
+ fun calcClientish xts =
+ foldl (fn ((x : L.con, t : L.con), st as (nullable, notNullable)) =>
+ case #1 x of
+ L.CName x =>
+ (case #1 t of
+ L.CFfi ("Basis", "client") =>
+ (nullable, (x, Client) :: notNullable)
+ | L.CApp ((L.CFfi ("Basis", "option"), _),
+ (L.CFfi ("Basis", "client"), _)) =>
+ ((x, Client) :: nullable, notNullable)
+ | L.CApp ((L.CFfi ("Basis", "channel"), _), _) =>
+ (nullable, (x, Channel) :: notNullable)
+ | L.CApp ((L.CFfi ("Basis", "option"), _),
+ (L.CApp ((L.CFfi ("Basis", "channel"), _), _), _)) =>
+ ((x, Channel) :: nullable, notNullable)
+ | _ => st)
+ | _ => st) ([], []) xts
+
+ fun expunger () =
+ let
+ val target = (L'.EFfiApp ("Basis", "sqlifyClient", [((L'.ERel 0, loc), (L'.TFfi ("Basis", "client"), loc))]), loc)
+
+ fun doTable (tab, xts, e) =
+ case xts of
+ L.CRecord (_, xts) =>
+ let
+ val (nullable, notNullable) = calcClientish xts
+
+ fun cond (x, v) =
+ (L'.EStrcat ((L'.EStrcat (str ("(("
+ ^ Settings.mangleSql x
+ ^ (case v of
+ Client => ""
+ | Channel => " >> 32")
+ ^ ") = "),
+ target), loc),
+ str ")"), loc)
+
+ val e =
+ foldl (fn ((x, v), e) =>
+ (L'.ESeq (
+ (L'.EDml ((L'.EStrcat (
+ str ("UPDATE "
+ ^ Settings.mangleSql tab
+ ^ " SET "
+ ^ Settings.mangleSql x
+ ^ " = NULL WHERE "),
+ cond (x, v)), loc), L'.Error), loc),
+ e), loc))
+ e nullable
+
+ val e =
+ case notNullable of
+ [] => e
+ | eb :: ebs =>
+ (L'.ESeq (
+ (L'.EDml ((L'.EStrcat (str ("DELETE FROM "
+ ^ Settings.mangleSql tab
+ ^ " WHERE "),
+ foldl (fn (eb, s) =>
+ (L'.EStrcat (str "(",
+ (L'.EStrcat (s,
+ (L'.EStrcat (str " OR ",
+ (L'.EStrcat (cond eb,
+ str ")"),
+ loc)), loc)), loc)), loc))
+ (cond eb)
+ ebs), loc),
+ L'.Error), loc),
+ e), loc)
+ in
+ e
+ end
+ | _ => e
+
+ val e = (L'.ERecord [], loc)
+ in
+ foldl (fn ((d, _), e) =>
+ case d of
+ L.DTable (_, _, xts, tab, _, _, _, _) => doTable (tab, #1 xts, e)
+ | _ => e) e file
+ end
+
+ fun initializer () =
+ let
+ fun doTable (tab, xts, e) =
+ case xts of
+ L.CRecord (_, xts) =>
+ let
+ val (nullable, notNullable) = calcClientish xts
+
+ val e =
+ case nullable of
+ [] => e
+ | (x, _) :: ebs =>
+ (L'.ESeq (
+ (L'.EDml (str
+ (foldl (fn ((x, _), s) =>
+ s ^ ", " ^ Settings.mangleSql x ^ " = NULL")
+ ("UPDATE "
+ ^ Settings.mangleSql tab
+ ^ " SET "
+ ^ Settings.mangleSql x
+ ^ " = NULL")
+ ebs), L'.Error), loc),
+ e), loc)
+
+ val e =
+ case notNullable of
+ [] => e
+ | eb :: ebs =>
+ (L'.ESeq (
+ (L'.EDml (str ("DELETE FROM "
+ ^ Settings.mangleSql tab), L'.Error), loc),
+ e), loc)
+ in
+ e
+ end
+ | _ => e
+
+ val e = (L'.ERecord [], loc)
+ in
+ foldl (fn ((d, _), e) =>
+ case d of
+ L.DTable (_, _, xts, tab, _, _, _, _) => doTable (tab, #1 xts, e)
+ | _ => e) e file
+ end
+
+ val mname = CoreUtil.File.maxName file + 1
+ val () = nextPvar := mname
+
+ val (_, fm, ds) = List.foldl (fn (d, (env, fm, ds)) =>
+ case #1 d of
+ L.DDatabase s =>
+ let
+ val (nExp, fm) = Fm.freshName fm
+ val (nIni, fm) = Fm.freshName fm
+
+ val dExp = L'.DVal ("expunger",
+ nExp,
+ (L'.TFun (client, unit), loc),
+ (L'.EAbs ("cli", client, unit, expunger ()), loc),
+ "expunger")
+ val dIni = L'.DVal ("initializer",
+ nIni,
+ (L'.TFun (unit, unit), loc),
+ (L'.EAbs ("_", unit, unit, initializer ()), loc),
+ "initializer")
+ in
+ (env, Fm.enter fm, (L'.DDatabase {name = s,
+ expunge = nExp,
+ initialize = nIni}, loc)
+ :: (dExp, loc)
+ :: (dIni, loc)
+ :: ds)
+ end
+ | _ =>
+ (pvarDefs := [];
+ pvarOldDefs := [];
+ case monoDecl (env, fm) d of
+ NONE => (env, fm, ds)
+ | SOME (env, fm, ds') =>
+ (foldr (fn ((n, cs), env) =>
+ Env.declBinds env (L.DDatatype [("$poly" ^ Int.toString n,
+ n,
+ [],
+ cs)], loc))
+ env (!pvarOldDefs),
+ Fm.enter fm,
+ case ds' of
+ [(L'.DDatatype dts, loc)] =>
+ (L'.DDatatype (dts @ !pvarDefs), loc) :: Fm.decls fm @ ds
+ | _ =>
+ ds' @ Fm.decls fm @ (L'.DDatatype (!pvarDefs), loc) :: ds)))
+ (env, Fm.empty mname, []) file
+ val monoFile = (rev ds, [])
+ in
+ pvars := RM.empty;
+ pvarDefs := [];
+ pvarOldDefs := [];
+ MonoFooify.canonicalFm := Fm.empty (MonoUtil.File.maxName monoFile + 1);
+ monoFile
+ end
+
+end
diff --git a/src/multimap_fn.sml b/src/multimap_fn.sml
new file mode 100644
index 0000000..3dab68a
--- /dev/null
+++ b/src/multimap_fn.sml
@@ -0,0 +1,16 @@
+functor MultimapFn (structure KeyMap : ORD_MAP structure ValSet : ORD_SET) = struct
+ type key = KeyMap.Key.ord_key
+ type item = ValSet.item
+ type itemSet = ValSet.set
+ type multimap = ValSet.set KeyMap.map
+ val empty : multimap = KeyMap.empty
+ fun insertSet (kToVs : multimap, k : key, vs : itemSet) : multimap =
+ KeyMap.unionWith ValSet.union (kToVs, KeyMap.singleton (k, vs))
+ fun insert (kToVs : multimap, k : key, v : item) : multimap =
+ insertSet (kToVs, k, ValSet.singleton v)
+ fun findSet (kToVs : multimap, k : key) =
+ case KeyMap.find (kToVs, k) of
+ SOME vs => vs
+ | NONE => ValSet.empty
+ val findList : multimap * key -> item list = ValSet.listItems o findSet
+end
diff --git a/src/mysql.sig b/src/mysql.sig
new file mode 100644
index 0000000..fa254ae
--- /dev/null
+++ b/src/mysql.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+signature MYSQL = sig
+
+end
diff --git a/src/mysql.sml b/src/mysql.sml
new file mode 100644
index 0000000..52e4921
--- /dev/null
+++ b/src/mysql.sml
@@ -0,0 +1,1614 @@
+(* Copyright (c) 2009-2010, 2015, 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 MySQL :> MYSQL = struct
+
+open Settings
+open Print.PD
+open Print
+
+fun p_sql_type t =
+ case t of
+ Int => "bigint"
+ | Float => "double"
+ | String => "longtext"
+ | Char => "char"
+ | Bool => "bool"
+ | Time => "timestamp"
+ | Blob => "longblob"
+ | Channel => "bigint"
+ | Client => "int"
+ | Nullable t => p_sql_type t
+
+fun p_buffer_type t =
+ case t of
+ Int => "MYSQL_TYPE_LONGLONG"
+ | Float => "MYSQL_TYPE_DOUBLE"
+ | String => "MYSQL_TYPE_STRING"
+ | Char => "MYSQL_TYPE_STRING"
+ | Bool => "MYSQL_TYPE_LONG"
+ | Time => "MYSQL_TYPE_TIMESTAMP"
+ | Blob => "MYSQL_TYPE_BLOB"
+ | Channel => "MYSQL_TYPE_LONGLONG"
+ | Client => "MYSQL_TYPE_LONG"
+ | Nullable t => p_buffer_type t
+
+fun p_sql_type_base t =
+ case t of
+ Int => "bigint"
+ | Float => "double"
+ | String => "longtext"
+ | Char => "char"
+ | Bool => "tinyint"
+ | Time => "timestamp"
+ | Blob => "longblob"
+ | Channel => "bigint"
+ | Client => "int"
+ | Nullable t => p_sql_type_base t
+
+val ident = String.translate (fn #"'" => "PRIME"
+ | ch => str ch)
+
+fun checkRel (table, checkNullable) (s, xts) =
+ let
+ val sl = CharVector.map Char.toLower s
+ val sl = if size sl > 1 andalso String.sub (sl, 0) = #"\"" then
+ String.substring (sl, 1, size sl - 2)
+ else
+ sl
+ val both = "table_name = '" ^ sl ^ "'"
+
+ val q = "SELECT COUNT(*) FROM information_schema." ^ table ^ " WHERE " ^ both
+
+ val q' = String.concat ["SELECT COUNT(*) FROM information_schema.columns WHERE ",
+ both,
+ " AND (",
+ case String.concatWith " OR "
+ (map (fn (x, t) =>
+ String.concat ["(LOWER(column_name) = '",
+ Settings.mangleSqlCatalog
+ (CharVector.map
+ Char.toLower (ident x)),
+ "' AND data_type ",
+ case p_sql_type_base t of
+ "bigint" =>
+ "IN ('bigint', 'int')"
+ | "longtext" =>
+ "IN ('longtext', 'varchar')"
+ | s => "= '" ^ s ^ "'",
+ if checkNullable then
+ (" AND is_nullable = '"
+ ^ (if isNotNull t then
+ "NO"
+ else
+ "YES")
+ ^ "'")
+ else
+ "",
+ ")"]) xts) of
+ "" => "FALSE"
+ | s => s,
+ ")"]
+
+ val q'' = String.concat ["SELECT COUNT(*) FROM information_schema.columns WHERE ",
+ both,
+ " AND LOWER(column_name) LIKE '", Settings.mangleSqlCatalog "%'"]
+ in
+ box [string "if (mysql_query(conn->conn, \"",
+ string q,
+ string "\")) {",
+ newline,
+ box [string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if ((res = mysql_store_result(conn->conn)) == NULL) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Result store failed:\\n",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (mysql_num_fields(res) != 1) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Bad column count:\\n",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if ((row = mysql_fetch_row(res)) == NULL) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Row fetch failed:\\n",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (strcmp(row[0], \"1\")) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string sl,
+ string "' does not exist.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "mysql_free_result(res);",
+ newline,
+ newline,
+
+ string "if (mysql_query(conn->conn, \"",
+ string q',
+ string "\")) {",
+ newline,
+ box [string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if ((res = mysql_store_result(conn->conn)) == NULL) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Result store failed:\\n",
+ string q',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (mysql_num_fields(res) != 1) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Bad column count:\\n",
+ string q',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if ((row = mysql_fetch_row(res)) == NULL) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Row fetch failed:\\n",
+ string q',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (strcmp(row[0], \"",
+ string (Int.toString (length xts)),
+ string "\")) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string sl,
+ string "' has the wrong column types.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "mysql_free_result(res);",
+ newline,
+ newline,
+
+ string "if (mysql_query(conn->conn, \"",
+ string q'',
+ string "\")) {",
+ newline,
+ box [string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q'',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if ((res = mysql_store_result(conn->conn)) == NULL) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Result store failed:\\n",
+ string q'',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (mysql_num_fields(res) != 1) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Bad column count:\\n",
+ string q'',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if ((row = mysql_fetch_row(res)) == NULL) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Row fetch failed:\\n",
+ string q'',
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (strcmp(row[0], \"",
+ string (Int.toString (length xts)),
+ string "\")) {",
+ newline,
+ box [string "mysql_free_result(res);",
+ newline,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string sl,
+ string "' has extra columns.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "mysql_free_result(res);",
+ newline]
+ end
+
+fun init {dbstring, prepared = ss, tables, views, sequences} =
+ let
+ val host = ref NONE
+ val user = ref NONE
+ val passwd = ref NONE
+ val db = ref NONE
+ val port = ref NONE
+ val unix_socket = ref NONE
+
+ fun stringOf r = case !r of
+ NONE => string "NULL"
+ | SOME s => box [string "\"",
+ string (Prim.toCString s),
+ string "\""]
+ in
+ app (fn s =>
+ case String.fields (fn ch => ch = #"=") s of
+ [name, value] =>
+ (case name of
+ "host" =>
+ if size value > 0 andalso String.sub (value, 0) = #"/" then
+ unix_socket := SOME value
+ else
+ host := SOME value
+ | "hostaddr" => host := SOME value
+ | "port" => port := Int.fromString value
+ | "dbname" => db := SOME value
+ | "user" => user := SOME value
+ | "password" => passwd := SOME value
+ | _ => ())
+ | _ => ()) (String.tokens Char.isSpace dbstring);
+
+ box [string "typedef struct {",
+ newline,
+ box [string "MYSQL *conn;",
+ newline,
+ p_list_sepi (box [])
+ (fn i => fn _ =>
+ box [string "MYSQL_STMT *p",
+ string (Int.toString i),
+ string ";",
+ newline])
+ ss],
+ string "} uw_conn;",
+ newline,
+ newline,
+
+ string "static void uw_client_init(void) {",
+ newline,
+ box [string "uw_sqlfmtInt = \"%lld%n\";",
+ newline,
+ string "uw_sqlfmtFloat = \"%.16g%n\";",
+ newline,
+ string "uw_Estrings = 0;",
+ newline,
+ string "uw_sql_type_annotations = 0;",
+ newline,
+ string "uw_sqlsuffixString = \"\";",
+ newline,
+ string "uw_sqlsuffixChar = \"\";",
+ newline,
+ string "uw_sqlsuffixBlob = \"\";",
+ newline,
+ string "uw_sqlfmtUint4 = \"%u%n\";",
+ newline,
+ newline,
+
+ string "if (mysql_library_init(0, NULL, NULL)) {",
+ newline,
+ box [string "fprintf(stderr, \"Could not initialize MySQL library\\n\");",
+ newline,
+ string "exit(1);",
+ newline],
+ string "}",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ if #persistent (currentProtocol ()) then
+ box [string "static void uw_db_validate(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "MYSQL_RES *res;",
+ newline,
+ string "MYSQL_ROW row;",
+ newline,
+ newline,
+ p_list_sep newline (checkRel ("tables", true)) tables,
+ p_list_sep newline (fn name => checkRel ("tables", true)
+ (name, [("id", Settings.Client)])) sequences,
+ p_list_sep newline (checkRel ("views", false)) views,
+ string "}",
+ newline,
+ newline,
+
+ string "static void uw_db_prepare(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "MYSQL_STMT *stmt;",
+ newline,
+ newline,
+
+ p_list_sepi newline (fn i => fn (s, _) =>
+ let
+ fun uhoh this s args =
+ box [p_list_sepi (box [])
+ (fn j => fn () =>
+ box [string
+ "mysql_stmt_close(conn->p",
+ string (Int.toString j),
+ string ");",
+ newline])
+ (List.tabulate (i, fn _ => ())),
+ box (if this then
+ [string
+ "mysql_stmt_close(conn->p",
+ string (Int.toString i),
+ string ");",
+ newline]
+ else
+ []),
+ string "mysql_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string s,
+ string "\"",
+ p_list_sep (box []) (fn s => box [string ", ",
+ string s]) args,
+ string ");",
+ newline]
+ in
+ box [string "stmt = mysql_stmt_init(conn->conn);",
+ newline,
+ string "if (stmt == NULL) {",
+ newline,
+ uhoh false "Out of memory allocating prepared statement" [],
+ string "}",
+ newline,
+ string "conn->p",
+ string (Int.toString i),
+ string " = stmt;",
+ newline,
+
+ string "if (mysql_stmt_prepare(stmt, \"",
+ string (Prim.toCString s),
+ string "\", ",
+ string (Int.toString (size s)),
+ string ")) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, mysql_stmt_error(stmt), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ uhoh true "Error preparing statement: %s" ["msg"]],
+ string "}",
+ newline]
+ end)
+ ss,
+
+ string "}"]
+ else
+ box [string "static void uw_db_prepare(uw_context ctx) { }",
+ newline,
+ string "static void uw_db_validate(uw_context ctx) { }"],
+ newline,
+ newline,
+
+ string "static void uw_db_init(uw_context ctx) {",
+ newline,
+ string "MYSQL *mysql = mysql_init(NULL);",
+ newline,
+ string "uw_conn *conn;",
+ newline,
+ string "if (mysql == NULL) uw_error(ctx, FATAL, ",
+ string "\"libmysqlclient can't allocate a connection.\");",
+ newline,
+ string "if (mysql_real_connect(mysql, ",
+ stringOf host,
+ string ", ",
+ stringOf user,
+ string ", ",
+ stringOf passwd,
+ string ", ",
+ stringOf db,
+ string ", ",
+ case !port of
+ NONE => string "0"
+ | SOME n => string (Int.toString n),
+ string ", ",
+ stringOf unix_socket,
+ string ", CLIENT_MULTI_STATEMENTS) == NULL) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, mysql_error(mysql), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "mysql_close(mysql);",
+ newline,
+ string "uw_error(ctx, FATAL, ",
+ string "\"Connection to MySQL server failed: %s\", msg);"],
+ newline,
+ string "}",
+ newline,
+ newline,
+ string "if (mysql_set_character_set(mysql, \"utf8\")) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, mysql_error(mysql), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "mysql_close(mysql);",
+ newline,
+ string "uw_error(ctx, FATAL, ",
+ string "\"Error setting UTF-8 character set for MySQL connection: %s\", msg);"],
+ newline,
+ string "}",
+ newline,
+ newline,
+ string "conn = calloc(1, sizeof(uw_conn));",
+ newline,
+ string "conn->conn = mysql;",
+ newline,
+ string "uw_set_db(ctx, conn);",
+ newline,
+ string "uw_db_validate(ctx);",
+ newline,
+ string "uw_db_prepare(ctx);",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static void uw_db_close(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ p_list_sepi (box [])
+ (fn i => fn _ =>
+ box [string "if (conn->p",
+ string (Int.toString i),
+ string ") mysql_stmt_close(conn->p",
+ string (Int.toString i),
+ string ");",
+ newline])
+ ss,
+ string "mysql_close(conn->conn);",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_begin(uw_context ctx, int could_write) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ newline,
+ string "return mysql_query(conn->conn, \"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN\") ? 1 : (mysql_next_result(conn->conn), 0);",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_commit(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "return mysql_commit(conn->conn);",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_rollback(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "return mysql_rollback(conn->conn);",
+ newline,
+ string "}",
+ newline,
+ newline]
+ end
+
+fun p_getcol {loc, wontLeakStrings = _, col = i, typ = t} =
+ let
+ fun getter t =
+ case t of
+ String => box [string "({",
+ newline,
+ string "uw_Basis_string s = uw_malloc(ctx, length",
+ string (Int.toString i),
+ string " + 1);",
+ newline,
+ string "out[",
+ string (Int.toString i),
+ string "].buffer = s;",
+ newline,
+ string "out[",
+ string (Int.toString i),
+ string "].buffer_length = length",
+ string (Int.toString i),
+ string " + 1;",
+ newline,
+ string "mysql_stmt_fetch_column(stmt, &out[",
+ string (Int.toString i),
+ string "], ",
+ string (Int.toString i),
+ string ", 0);",
+ newline,
+ string "s[length",
+ string (Int.toString i),
+ string "] = 0;",
+ newline,
+ string "s;",
+ newline,
+ string "})"]
+ | Blob => box [string "({",
+ newline,
+ string "uw_Basis_blob b = {length",
+ string (Int.toString i),
+ string ", uw_malloc(ctx, length",
+ string (Int.toString i),
+ string ")};",
+ newline,
+ string "out[",
+ string (Int.toString i),
+ string "].buffer = b.data;",
+ newline,
+ string "out[",
+ string (Int.toString i),
+ string "].buffer_length = length",
+ string (Int.toString i),
+ string ";",
+ newline,
+ string "mysql_stmt_fetch_column(stmt, &out[",
+ string (Int.toString i),
+ string "], ",
+ string (Int.toString i),
+ string ", 0);",
+ newline,
+ string "b;",
+ newline,
+ string "})"]
+ | Time => box [string "({",
+ string "MYSQL_TIME *mt = &buffer",
+ string (Int.toString i),
+ string ";",
+ newline,
+ newline,
+ string "struct tm t = {mt->second, mt->minute, mt->hour, mt->day, mt->month-1, mt->year - 1900, 0, 0, -1};",
+ newline,
+ string "uw_Basis_time res = {mktime(&t), 0};",
+ newline,
+ string "res;",
+ newline,
+ string "})"]
+ | Channel => box [string "({",
+ string "uw_Basis_channel ch = {buffer",
+ string (Int.toString i),
+ string " >> 32, buffer",
+ string (Int.toString i),
+ string " & 0xFFFFFFFF};",
+ newline,
+ string "ch;",
+ newline,
+ string "})"]
+ | _ => box [string "buffer",
+ string (Int.toString i)]
+ in
+ case t of
+ Nullable t => box [string "(is_null",
+ string (Int.toString i),
+ string " ? NULL : ",
+ case t of
+ String => getter t
+ | _ => box [string "({",
+ newline,
+ string (p_sql_ctype t),
+ space,
+ string "*tmp = uw_malloc(ctx, sizeof(",
+ string (p_sql_ctype t),
+ string "));",
+ newline,
+ string "*tmp = ",
+ getter t,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string ")"]
+ | _ => box [string "(is_null",
+ string (Int.toString i),
+ string " ? ",
+ box [string "({",
+ string (p_sql_ctype t),
+ space,
+ string "tmp;",
+ newline,
+ string "uw_error(ctx, FATAL, \"Unexpectedly NULL field #",
+ string (Int.toString i),
+ string "\");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string " : ",
+ getter t,
+ string ")"]
+ end
+
+fun queryCommon {loc, query, cols, doCols} =
+ box [string "int n, r;",
+ newline,
+ string "MYSQL_BIND out[",
+ string (Int.toString (length cols)),
+ string "];",
+ newline,
+ p_list_sepi (box []) (fn i => fn t =>
+ let
+ fun buffers t =
+ case t of
+ String => box [string "unsigned long length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Blob => box [string "unsigned long length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Time => box [string "MYSQL_TIME buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Channel => box [string "unsigned long long buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | _ => box [string (p_sql_ctype t),
+ space,
+ string "buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ in
+ box [string "my_bool is_null",
+ string (Int.toString i),
+ string ";",
+ newline,
+ case t of
+ Nullable t => buffers t
+ | _ => buffers t,
+ newline]
+ end) cols,
+ newline,
+
+ string "memset(out, 0, sizeof out);",
+ newline,
+ p_list_sepi (box []) (fn i => fn t =>
+ let
+ fun buffers t =
+ case t of
+ String => box [string "out[",
+ string (Int.toString i),
+ string "].length = &length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Char => box [string "out[",
+ string (Int.toString i),
+ string "].buffer_length = 1;",
+ newline,
+ string "out[",
+ string (Int.toString i),
+ string "].buffer = &buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Blob => box [string "out[",
+ string (Int.toString i),
+ string "].length = &length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | _ => box [string "out[",
+ string (Int.toString i),
+ string "].buffer = &buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ in
+ box [string "out[",
+ string (Int.toString i),
+ string "].buffer_type = ",
+ string (p_buffer_type t),
+ string ";",
+ newline,
+ string "out[",
+ string (Int.toString i),
+ string "].is_null = &is_null",
+ string (Int.toString i),
+ string ";",
+ newline,
+
+ case t of
+ Nullable t => buffers t
+ | _ => buffers t,
+ newline]
+ end) cols,
+ newline,
+
+ string "if (mysql_stmt_reset(stmt)) {",
+ box [newline,
+ string "if (mysql_errno(conn->conn) == 2006) uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error resetting statement: %s\\n%s\", ",
+ query,
+ string ", mysql_error(conn->conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (mysql_stmt_execute(stmt)) {",
+ newline,
+ box [string "if (mysql_errno(conn->conn) == 1213)",
+ newline,
+ box [string "uw_error(ctx, UNLIMITED_RETRY, \"Deadlock detected\");",
+ newline],
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error executing query: %s\\n%s\", ",
+ query,
+ string ", mysql_error(conn->conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (mysql_stmt_bind_result(stmt, out)) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error binding query result: %s\\n%s\", ",
+ query,
+ string ", mysql_error(conn->conn));",
+ newline,
+ newline,
+
+ string "if (mysql_stmt_store_result(stmt)) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error storing query result: %s\\n%s\", ",
+ query,
+ string ", mysql_error(conn->conn));",
+ newline,
+ newline,
+
+ string "uw_end_region(ctx);",
+ newline,
+ string "while (1) {",
+ newline,
+ string "r = mysql_stmt_fetch(stmt);",
+ newline,
+ string "if (r != 0 && r != MYSQL_DATA_TRUNCATED) break;",
+ newline,
+ doCols p_getcol,
+ string "}",
+ newline,
+ newline,
+
+ string "if (r == 1) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": query result fetching failed: %s\\n%s\", ",
+ query,
+ string ", mysql_error(conn->conn));",
+ newline,
+ newline,
+
+ string "if (mysql_stmt_reset(stmt)) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error resetting statement: %s\\n%s\", ",
+ query,
+ string ", mysql_error(conn->conn));",
+ newline,
+ newline]
+
+fun query {loc, cols, doCols} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "MYSQL_STMT *stmt = mysql_stmt_init(conn->conn);",
+ newline,
+ string "if (stmt == NULL) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": can't allocate temporary prepared statement\");",
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))mysql_stmt_close, stmt);",
+ newline,
+ string "if (mysql_stmt_prepare(stmt, query, strlen(query))) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": error preparing statement: %s\\n%s\", query, mysql_error(conn->conn));",
+ newline,
+ newline,
+
+ queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"},
+
+ string "uw_pop_cleanup(ctx);",
+ newline]
+
+fun queryPrepared {loc, id, query, inputs, cols, doCols, nested} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "MYSQL_BIND in[",
+ string (Int.toString (length inputs)),
+ string "];",
+ newline,
+ p_list_sepi (box []) (fn i => fn t =>
+ let
+ fun buffers t =
+ case t of
+ String => box [string "unsigned long in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Blob => box [string "unsigned long in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Time => box [string "MYSQL_TIME in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | _ => box []
+ in
+ box [case t of
+ Nullable t => box [string "my_bool in_is_null",
+ string (Int.toString i),
+ string ";",
+ newline,
+ buffers t]
+ | _ => buffers t,
+ newline]
+ end) inputs,
+
+ if nested then
+ box [string "MYSQL_STMT *stmt;",
+ newline]
+ else
+ box [string "MYSQL_STMT *stmt = conn->p",
+ string (Int.toString id),
+ string ";",
+ newline,
+ newline,
+
+ string "if (stmt == NULL) {",
+ newline],
+
+ box [string "stmt = mysql_stmt_init(conn->conn);",
+ newline,
+ string "if (stmt == NULL) uw_error(ctx, FATAL, \"Out of memory allocating prepared statement\");",
+ newline,
+ if nested then
+ box [string "uw_push_cleanup(ctx, (void (*)(void *))mysql_stmt_close, stmt);",
+ newline]
+ else
+ box [],
+ string "if (mysql_stmt_prepare(stmt, \"",
+ string (Prim.toCString query),
+ string "\", ",
+ string (Int.toString (size query)),
+ string ")) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, mysql_stmt_error(stmt), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ if nested then
+ box []
+ else
+ box [string "mysql_stmt_close(stmt);",
+ newline],
+ string "uw_error(ctx, FATAL, \"Error preparing statement: %s\", msg);",
+ newline],
+ string "}",
+ newline,
+ if nested then
+ box []
+ else
+ box [string "conn->p",
+ string (Int.toString id),
+ string " = stmt;",
+ newline]],
+ if nested then
+ box []
+ else
+ box [string "}",
+ newline],
+ newline,
+
+ string "memset(in, 0, sizeof in);",
+ newline,
+ p_list_sepi (box []) (fn i => fn t =>
+ let
+ fun buffers t =
+ case t of
+ String => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline,
+ string "in_length",
+ string (Int.toString i),
+ string "= in[",
+ string (Int.toString i),
+ string "].buffer_length = strlen(arg",
+ string (Int.toString (i + 1)),
+ string ");",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].length = &in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Char => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = &arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].buffer_length = 1;",
+ newline]
+ | Blob => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = arg",
+ string (Int.toString (i + 1)),
+ string ".data;",
+ newline,
+ string "in_length",
+ string (Int.toString i),
+ string "= in[",
+ string (Int.toString i),
+ string "].buffer_length = arg",
+ string (Int.toString (i + 1)),
+ string ".size;",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].length = &in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Time =>
+ let
+ fun oneField dst src =
+ box [string "in_buffer",
+ string (Int.toString i),
+ string ".",
+ string dst,
+ string " = tms.tm_",
+ string src,
+ string ";",
+ newline]
+ in
+ box [string "({",
+ newline,
+ string "struct tm tms;",
+ newline,
+ string "if (localtime_r(&arg",
+ string (Int.toString (i + 1)),
+ string ".seconds, &tms) == NULL) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": error converting to MySQL time\");",
+ newline,
+ oneField "year" "year + 1900",
+ box [string "in_buffer",
+ string (Int.toString i),
+ string ".month = tms.tm_mon + 1;",
+ newline],
+ oneField "day" "mday",
+ oneField "hour" "hour",
+ oneField "minute" "min",
+ oneField "second" "sec",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].buffer = &in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline,
+ string "});",
+ newline]
+ end
+ | Channel => box [string "in_buffer",
+ string (Int.toString i),
+ string " = ((unsigned long long)arg",
+ string (Int.toString (i + 1)),
+ string ".cli << 32) | arg",
+ string (Int.toString (i + 1)),
+ string ".chn;",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].buffer = &in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+
+ | _ => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = &arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline]
+ in
+ box [string "in[",
+ string (Int.toString i),
+ string "].buffer_type = ",
+ string (p_buffer_type t),
+ string ";",
+ newline,
+
+ case t of
+ Nullable t => box [string "in[",
+ string (Int.toString i),
+ string "].is_null = &in_is_null",
+ string (Int.toString i),
+ string ";",
+ newline,
+ string "if (arg",
+ string (Int.toString (i + 1)),
+ string " == NULL) {",
+ newline,
+ box [string "in_is_null",
+ string (Int.toString i),
+ string " = 1;",
+ newline],
+ string "} else {",
+ box [case t of
+ String => box []
+ | _ =>
+ box [string (p_sql_ctype t),
+ space,
+ string "tmp = *arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline,
+ string (p_sql_ctype t),
+ space,
+ string "arg",
+ string (Int.toString (i + 1)),
+ string " = tmp;",
+ newline],
+ string "in_is_null",
+ string (Int.toString i),
+ string " = 0;",
+ newline,
+ buffers t,
+ newline],
+ string "}",
+ newline]
+
+ | _ => buffers t,
+ newline]
+ end) inputs,
+ newline,
+
+ string "if (mysql_stmt_bind_param(stmt, in)) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": error binding parameters\");",
+ newline,
+
+ queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"",
+ string (Prim.toCString query),
+ string "\""]},
+
+ if nested then
+ box [string "uw_pop_cleanup(ctx);",
+ newline]
+ else
+ box []]
+
+fun dmlCommon {loc, dml, mode} =
+ box [string "if (mysql_stmt_execute(stmt)) {",
+ box [string "if (mysql_errno(conn->conn) == 2006) uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "if (mysql_errno(conn->conn) == 1213)",
+ newline,
+ box [string "uw_error(ctx, UNLIMITED_RETRY, \"Deadlock detected\");",
+ newline],
+ newline,
+ case mode of
+ Settings.Error => box [string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error executing DML: %s\\n%s\", ",
+ dml,
+ string ", mysql_error(conn->conn));"]
+ | Settings.None => string "uw_set_error_message(ctx, mysql_error(conn->conn));",
+ newline],
+ string "}",
+ newline]
+
+fun dml (loc, mode) =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "MYSQL_STMT *stmt = mysql_stmt_init(conn->conn);",
+ newline,
+ string "if (stmt == NULL) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": can't allocate temporary prepared statement\");",
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))mysql_stmt_close, stmt);",
+ newline,
+ string "if (mysql_stmt_prepare(stmt, dml, strlen(dml))) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": error preparing statement: %s\\n%s\", dml, mysql_error(conn->conn));",
+ newline,
+ newline,
+
+ dmlCommon {loc = loc, dml = string "dml", mode = mode},
+
+ string "uw_pop_cleanup(ctx);",
+ newline]
+
+fun dmlPrepared {loc, id, dml, inputs, mode} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "MYSQL_BIND in[",
+ string (Int.toString (length inputs)),
+ string "];",
+ newline,
+ p_list_sepi (box []) (fn i => fn t =>
+ let
+ fun buffers t =
+ case t of
+ String => box [string "unsigned long in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Blob => box [string "unsigned long in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Time => box [string "MYSQL_TIME in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Channel => box [string "unsigned long long in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | _ => box []
+ in
+ box [case t of
+ Nullable t => box [string "my_bool in_is_null",
+ string (Int.toString i),
+ string ";",
+ newline,
+ buffers t]
+ | _ => buffers t,
+ newline]
+ end) inputs,
+ string "MYSQL_STMT *stmt = conn->p",
+ string (Int.toString id),
+ string ";",
+ newline,
+ newline,
+
+ string "if (stmt == NULL) {",
+ newline,
+ box [string "stmt = mysql_stmt_init(conn->conn);",
+ newline,
+ string "if (stmt == NULL) uw_error(ctx, FATAL, \"Out of memory allocating prepared statement\");",
+ newline,
+ string "if (mysql_stmt_prepare(stmt, \"",
+ string (Prim.toCString dml),
+ string "\", ",
+ string (Int.toString (size dml)),
+ string ")) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, mysql_stmt_error(stmt), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "uw_error(ctx, FATAL, \"Error preparing statement: %s\", msg);",
+ newline],
+ string "}",
+ newline,
+ string "conn->p",
+ string (Int.toString id),
+ string " = stmt;",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "memset(in, 0, sizeof in);",
+ newline,
+ p_list_sepi (box []) (fn i => fn t =>
+ let
+ fun buffers t =
+ case t of
+ String => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline,
+ string "in_length",
+ string (Int.toString i),
+ string "= in[",
+ string (Int.toString i),
+ string "].buffer_length = strlen(arg",
+ string (Int.toString (i + 1)),
+ string ");",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].length = &in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Blob => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = arg",
+ string (Int.toString (i + 1)),
+ string ".data;",
+ newline,
+ string "in_length",
+ string (Int.toString i),
+ string "= in[",
+ string (Int.toString i),
+ string "].buffer_length = arg",
+ string (Int.toString (i + 1)),
+ string ".size;",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].length = &in_length",
+ string (Int.toString i),
+ string ";",
+ newline]
+ | Time =>
+ let
+ fun oneField dst src =
+ box [string "in_buffer",
+ string (Int.toString i),
+ string ".",
+ string dst,
+ string " = tms.tm_",
+ string src,
+ string ";",
+ newline]
+ in
+ box [string "({",
+ newline,
+ string "struct tm tms;",
+ newline,
+ string "if (localtime_r(&arg",
+ string (Int.toString (i + 1)),
+ string ".seconds, &tms) == NULL) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": error converting to MySQL time\");",
+ newline,
+ oneField "year" "year + 1900",
+ oneField "month" "mon + 1",
+ oneField "day" "mday",
+ oneField "hour" "hour",
+ oneField "minute" "min",
+ oneField "second" "sec",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].buffer = &in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline,
+ string "});",
+ newline]
+ end
+ | Channel => box [string "in_buffer",
+ string (Int.toString i),
+ string " = ((unsigned long long)arg",
+ string (Int.toString (i + 1)),
+ string ".cli << 32) | arg",
+ string (Int.toString (i + 1)),
+ string ".chn;",
+ newline,
+ string "in[",
+ string (Int.toString i),
+ string "].buffer = &in_buffer",
+ string (Int.toString i),
+ string ";",
+ newline]
+
+ | _ => box [string "in[",
+ string (Int.toString i),
+ string "].buffer = &arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline]
+ in
+ box [string "in[",
+ string (Int.toString i),
+ string "].buffer_type = ",
+ string (p_buffer_type t),
+ string ";",
+ newline,
+
+ case t of
+ Channel => box [string "in[",
+ string (Int.toString i),
+ string "].is_unsigned = 1;",
+ newline]
+ | _ => box [],
+
+ case t of
+ Nullable t => box [string "in[",
+ string (Int.toString i),
+ string "].is_null = &in_is_null",
+ string (Int.toString i),
+ string ";",
+ newline,
+ string "if (arg",
+ string (Int.toString (i + 1)),
+ string " == NULL) {",
+ newline,
+ box [string "in_is_null",
+ string (Int.toString i),
+ string " = 1;",
+ newline],
+ string "} else {",
+ box [case t of
+ String => box []
+ | _ =>
+ box [string (p_sql_ctype t),
+ space,
+ string "tmp = *arg",
+ string (Int.toString (i + 1)),
+ string ";",
+ newline,
+ string (p_sql_ctype t),
+ space,
+ string "arg",
+ string (Int.toString (i + 1)),
+ string " = tmp;",
+ newline],
+ string "in_is_null",
+ string (Int.toString i),
+ string " = 0;",
+ newline,
+ buffers t,
+ newline],
+ string "}",
+ newline]
+
+ | _ => buffers t,
+ newline]
+ end) inputs,
+ newline,
+
+ string "if (mysql_stmt_bind_param(stmt, in)) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": error binding parameters\");",
+ newline,
+
+ dmlCommon {loc = loc, dml = box [string "\"",
+ string (Prim.toCString dml),
+ string "\""], mode = mode}]
+
+fun nextval {loc, seqE, seqName} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "char *insert = ",
+ case seqName of
+ SOME s => string ("\"INSERT INTO " ^ s ^ " VALUES ()\"")
+ | NONE => box [string "uw_Basis_strcat(ctx, \"INSERT INTO \", uw_Basis_strcat(ctx, ",
+ seqE,
+ string ", \" VALUES ()\"))"],
+ string ";",
+ newline,
+ string "char *delete = ",
+ case seqName of
+ SOME s => string ("\"DELETE FROM " ^ s ^ "\"")
+ | NONE => box [string "uw_Basis_strcat(ctx, \"DELETE FROM \", ",
+ seqE,
+ string ")"],
+ string ";",
+ newline,
+ newline,
+
+ string "if (mysql_query(conn->conn, insert)) {",
+ box [newline,
+ string "if (mysql_errno(conn->conn) == 2006) uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"'nextval' INSERT failed\");",
+ newline],
+ string "}",
+ newline,
+ string "n = mysql_insert_id(conn->conn);",
+ newline,
+ string "if (mysql_query(conn->conn, delete)) uw_error(ctx, FATAL, \"'nextval' DELETE failed\");",
+ newline]
+
+fun nextvalPrepared _ = raise Fail "MySQL.nextvalPrepared called"
+
+fun setval _ = raise Fail "MySQL.setval called"
+
+fun sqlifyString s = "'" ^ String.translate (fn #"'" => "\\'"
+ | #"\\" => "\\\\"
+ | ch =>
+ if Char.isPrint ch then
+ str ch
+ else
+ (ErrorMsg.error
+ "Non-printing character found in SQL string literal";
+ ""))
+ (Prim.toCString s) ^ "'"
+
+fun p_cast (s, _) = s
+
+fun p_blank _ = "?"
+
+val () = addDbms {name = "mysql",
+ header = Config.msheader,
+ randomFunction = "RAND",
+ link = "-lmysqlclient",
+ init = init,
+ p_sql_type = p_sql_type,
+ query = query,
+ queryPrepared = queryPrepared,
+ dml = dml,
+ dmlPrepared = dmlPrepared,
+ nextval = nextval,
+ nextvalPrepared = nextvalPrepared,
+ setval = setval,
+ sqlifyString = sqlifyString,
+ p_cast = p_cast,
+ p_blank = p_blank,
+ supportsDeleteAs = false,
+ supportsUpdateAs = false,
+ createSequence = fn s => "CREATE TABLE " ^ s ^ " (uw_id INTEGER PRIMARY KEY AUTO_INCREMENT)",
+ textKeysNeedLengths = true,
+ supportsNextval = false,
+ supportsNestedPrepared = false,
+ sqlPrefix = "SET storage_engine=InnoDB;\n\n",
+ supportsOctetLength = true,
+ trueString = "TRUE",
+ falseString = "FALSE",
+ onlyUnion = true,
+ nestedRelops = false,
+ windowFunctions = false,
+ supportsIsDistinctFrom = true}
+
+end
diff --git a/src/name_js.sig b/src/name_js.sig
new file mode 100644
index 0000000..6750b7a
--- /dev/null
+++ b/src/name_js.sig
@@ -0,0 +1,35 @@
+(* Copyright (c) 2012, 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.
+ *)
+
+(* Phase that introduces names for fragments of JavaScript code, so that they
+ * may be moved to app.js and not repeated in each generated page *)
+
+signature NAME_JS = sig
+
+ val rewrite : Mono.file -> Mono.file
+
+end
diff --git a/src/name_js.sml b/src/name_js.sml
new file mode 100644
index 0000000..f10e593
--- /dev/null
+++ b/src/name_js.sml
@@ -0,0 +1,173 @@
+(* Copyright (c) 2012-2013, 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.
+ *)
+
+(* Phase that introduces names for fragments of JavaScript code, so that they
+ * may be moved to app.js and not repeated in each generated page *)
+
+structure NameJS :> NAME_JS = struct
+
+open Mono
+
+structure U = MonoUtil
+structure IS = IntBinarySet
+
+val freeVars = U.Exp.foldB {typ = #2,
+ exp = fn (free, e, vs) =>
+ case e of
+ ERel n =>
+ if n < free then
+ vs
+ else
+ IS.add (vs, n - free)
+ | _ => vs,
+ bind = fn (free, b) =>
+ case b of
+ U.Exp.RelE _ => free+1
+ | _ => free}
+ 0 IS.empty
+
+fun index (ls, v) =
+ case ls of
+ [] => raise Fail "NameJs.index"
+ | v' :: ls' => if v = v' then 0 else 1 + index (ls', v)
+
+fun squish vs = U.Exp.mapB {typ = fn x => x,
+ exp = fn free => fn e =>
+ case e of
+ ERel n =>
+ if n < free then
+ e
+ else
+ ERel (free + index (vs, n - free) + 1)
+ | _ => e,
+ bind = fn (free, b) =>
+ case b of
+ U.Exp.RelE _ => free+1
+ | _ => free}
+ 0
+
+fun rewrite file =
+ let
+ fun isTricky' dontName e =
+ case e of
+ ENamed n => IS.member (dontName, n)
+ | EFfiApp ("Basis", "sigString", _) => true
+ | _ => false
+
+ fun isTricky dontName = U.Decl.exists {typ = fn _ => false,
+ exp = isTricky' dontName,
+ decl = fn _ => false}
+
+ fun isTrickyE dontName = U.Exp.exists {typ = fn _ => false,
+ exp = isTricky' dontName}
+
+ val dontName = foldl (fn (d, dontName) =>
+ if isTricky dontName d then
+ case #1 d of
+ DVal (_, n, _, _, _) => IS.add (dontName, n)
+ | DValRec vis => foldl (fn ((_, n, _, _, _), dontName) => IS.add (dontName, n)) dontName vis
+ | _ => dontName
+ else
+ dontName) IS.empty (#1 file)
+
+ val (ds, _) = ListUtil.foldlMapConcat (fn (d, nextName) =>
+ let
+ val (d, (nextName, newDs)) =
+ U.Decl.foldMapB {typ = fn x => x,
+ decl = fn (_, e, s) => (e, s),
+ exp = fn (env, e, st as (nextName, newDs)) =>
+ case e of
+ EJavaScript (mode, e') =>
+ (case mode of
+ Source _ => (e, st)
+ | _ =>
+ let
+ fun isTrulySimple (e, _) =
+ case e of
+ ERel _ => true
+ | ENamed _ => true
+ | ERecord [] => true
+ | _ => false
+
+ fun isAlreadySimple e =
+ case #1 e of
+ EApp (e, arg) => isTrulySimple arg andalso isAlreadySimple e
+ | _ => isTrulySimple e
+ in
+ if isAlreadySimple e' orelse isTrickyE dontName e' then
+ (e, st)
+ else
+ let
+ val loc = #2 e'
+
+ val vs = freeVars e'
+ val vs = IS.listItems vs
+
+ val x = "script" ^ Int.toString nextName
+
+ val un = (TRecord [], loc)
+ val s = (TFfi ("Basis", "string"), loc)
+ val base = (TFun (un, s), loc)
+ val t = foldl (fn (n, t) => (TFun (#2 (List.nth (env, n)), t), loc)) base vs
+ val e' = squish vs e'
+ val e' = (EAbs ("_", un, s, e'), loc)
+ val (e', _) = foldl (fn (n, (e', t)) =>
+ let
+ val (x, this) = List.nth (env, n)
+ in
+ ((EAbs (x, this, t, e'), loc),
+ (TFun (this, t), loc))
+ end) (e', base) vs
+ val d = (x, nextName, t, e', "<script>")
+
+ val e = (ENamed nextName, loc)
+ val e = foldr (fn (n, e) => (EApp (e, (ERel n, loc)), loc)) e vs
+ val e = (EApp (e, (ERecord [], loc)), loc)
+ val e = EJavaScript (Script, e)
+ in
+ (e, (nextName+1, d :: newDs))
+ end
+ end)
+ | _ => (e, st),
+ bind = fn (env, b) =>
+ case b of
+ U.Decl.RelE x => x :: env
+ | _ => env}
+ [] (nextName, []) d
+ in
+ (case newDs of
+ [] => [d]
+ | _ => case #1 d of
+ DValRec vis => [(DValRec (vis @ newDs), #2 d)]
+ | _ => List.revAppend (map (fn vi => (DVal vi, #2 d)) newDs, [d]),
+ nextName)
+ end) (U.File.maxName file + 1) (#1 file)
+ in
+ (ds, #2 file)
+ end
+
+end
diff --git a/src/option_key_fn.sml b/src/option_key_fn.sml
new file mode 100644
index 0000000..27ba913
--- /dev/null
+++ b/src/option_key_fn.sml
@@ -0,0 +1,12 @@
+functor OptionKeyFn(K : ORD_KEY)
+ : ORD_KEY where type ord_key = K.ord_key option = struct
+
+type ord_key = K.ord_key option
+
+val compare =
+ fn (NONE, NONE) => EQUAL
+ | (NONE, _) => LESS
+ | (_, NONE) => GREATER
+ | (SOME x, SOME y) => K.compare (x, y)
+
+end
diff --git a/src/order.sig b/src/order.sig
new file mode 100644
index 0000000..fcee69e
--- /dev/null
+++ b/src/order.sig
@@ -0,0 +1,36 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Utility code for implementing comparisons *)
+
+signature ORDER = sig
+
+ val join : order * (unit -> order) -> order
+ val joinL : ('a * 'b -> order) -> 'a list * 'b list -> order
+ val joinO : ('a * 'b -> order) -> 'a option * 'b option -> order
+
+end
diff --git a/src/order.sml b/src/order.sml
new file mode 100644
index 0000000..3f5bce6
--- /dev/null
+++ b/src/order.sml
@@ -0,0 +1,53 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Utility code for implementing comparisons *)
+
+structure Order :> ORDER = struct
+
+fun join (o1, o2) =
+ case o1 of
+ EQUAL => o2 ()
+ | v => v
+
+fun joinL f (os1, os2) =
+ case (os1, os2) of
+ (nil, nil) => EQUAL
+ | (nil, _) => LESS
+ | (h1 :: t1, h2 :: t2) =>
+ join (f (h1, h2), fn () => joinL f (t1, t2))
+ | (_ :: _, nil) => GREATER
+
+fun joinO f (v1, v2) =
+ case (v1, v2) of
+ (NONE, NONE) => EQUAL
+ | (NONE, _) => LESS
+ | (_, NONE) => GREATER
+
+ | (SOME v1, SOME v2) => f (v1, v2)
+
+end
diff --git a/src/pair_key_fn.sml b/src/pair_key_fn.sml
new file mode 100644
index 0000000..cd33950
--- /dev/null
+++ b/src/pair_key_fn.sml
@@ -0,0 +1,12 @@
+functor PairKeyFn (structure I : ORD_KEY
+ structure J : ORD_KEY)
+ : ORD_KEY where type ord_key = I.ord_key * J.ord_key = struct
+
+type ord_key = I.ord_key * J.ord_key
+
+fun compare ((i1, j1), (i2, j2)) =
+ case I.compare (i1, i2) of
+ EQUAL => J.compare (j1, j2)
+ | ord => ord
+
+end
diff --git a/src/pathcheck.sig b/src/pathcheck.sig
new file mode 100644
index 0000000..e4b9c7a
--- /dev/null
+++ b/src/pathcheck.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature PATH_CHECK = sig
+
+ val check : Mono.file -> unit
+
+end
diff --git a/src/pathcheck.sml b/src/pathcheck.sml
new file mode 100644
index 0000000..3533032
--- /dev/null
+++ b/src/pathcheck.sml
@@ -0,0 +1,115 @@
+(* Copyright (c) 2008, 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 PathCheck :> PATH_CHECK = struct
+
+open Mono
+
+structure E = ErrorMsg
+
+structure SS = BinarySetFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+fun checkDecl ((d, loc), (funcs, rels, cookies, styles)) =
+ let
+ fun doFunc s =
+ (if SS.member (funcs, s) then
+ E.errorAt loc ("Duplicate function path " ^ s)
+ else
+ ();
+ (SS.add (funcs, s), rels, cookies, styles))
+
+ fun doRel s =
+ (if SS.member (rels, s) then
+ E.errorAt loc ("Duplicate table/sequence path " ^ s)
+ else
+ ();
+ (funcs, SS.add (rels, s), cookies, styles))
+
+ fun doCookie s =
+ (if SS.member (cookies, s) then
+ E.errorAt loc ("Duplicate cookie path " ^ s)
+ else
+ ();
+ (funcs, rels, SS.add (cookies, s), styles))
+
+ fun doStyle s =
+ (if SS.member (styles, s) then
+ E.errorAt loc ("Duplicate style path " ^ s)
+ else
+ ();
+ (funcs, rels, cookies, SS.add (styles, s)))
+ in
+ case d of
+ DExport (_, s, _, _, _, _) => doFunc s
+
+ | DTable (s, _, pe, ce) =>
+ let
+ fun constraints (e, rels) =
+ case #1 e of
+ ERecord [(s', _, _)] =>
+ let
+ val s' = s ^ "_" ^ s'
+ in
+ if SS.member (rels, s') then
+ E.errorAt loc ("Duplicate constraint path " ^ s')
+ else
+ ();
+ SS.add (rels, s')
+ end
+ | EStrcat (e1, e2) => constraints (e2, constraints (e1, rels))
+ | _ => rels
+
+ val rels = #2 (doRel s)
+ val rels = case #1 pe of
+ EPrim (Prim.String (_, "")) => rels
+ | _ =>
+ let
+ val s' = s ^ "_Pkey"
+ in
+ if SS.member (rels, s') then
+ E.errorAt loc ("Duplicate primary key constraint path " ^ s')
+ else
+ ();
+ SS.add (rels, s')
+ end
+ in
+ (funcs, constraints (ce, rels), cookies, styles)
+ end
+ | DSequence s => doRel s
+
+ | DCookie s => doCookie s
+ | DStyle s => doStyle s
+
+ | _ => (funcs, rels, cookies, styles)
+ end
+
+fun check (ds, _) = ignore (foldl checkDecl (SS.empty, SS.empty, SS.empty, SS.empty) ds)
+
+end
diff --git a/src/postgres.sig b/src/postgres.sig
new file mode 100644
index 0000000..54117f0
--- /dev/null
+++ b/src/postgres.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+signature POSTGRES = sig
+
+end
diff --git a/src/postgres.sml b/src/postgres.sml
new file mode 100644
index 0000000..404384d
--- /dev/null
+++ b/src/postgres.sml
@@ -0,0 +1,1153 @@
+(* Copyright (c) 2008-2010, 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 Postgres :> POSTGRES = struct
+
+open Settings
+open Print.PD
+open Print
+
+val ident = String.translate (fn #"'" => "PRIME"
+ | ch => str ch)
+
+fun p_sql_type t =
+ case t of
+ Int => "int8"
+ | Float => "float8"
+ | String => "text"
+ | Char => "char"
+ | Bool => "bool"
+ | Time => "timestamp"
+ | Blob => "bytea"
+ | Channel => "int8"
+ | Client => "int4"
+ | Nullable t => p_sql_type t
+
+fun p_sql_type_base t =
+ case t of
+ Int => "bigint"
+ | Float => "double precision"
+ | String => "text"
+ | Char => "character"
+ | Bool => "boolean"
+ | Time => "timestamp without time zone"
+ | Blob => "bytea"
+ | Channel => "bigint"
+ | Client => "integer"
+ | Nullable t => p_sql_type_base t
+
+fun checkRel (table, checkNullable) (s, xts) =
+ let
+ val sl = CharVector.map Char.toLower s
+ val sl = if size sl > 1 andalso String.sub (sl, 0) = #"\"" then
+ String.substring (sl, 1, size sl - 2)
+ else
+ sl
+
+ val q = "SELECT COUNT(*) FROM information_schema." ^ table ^ " WHERE table_name = '"
+ ^ sl ^ "'"
+
+ val q' = String.concat ["SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '",
+ sl,
+ "' AND (",
+ case String.concatWith " OR "
+ (map (fn (x, t) =>
+ String.concat ["(LOWER(column_name) = '",
+ Settings.mangleSqlCatalog
+ (CharVector.map
+ Char.toLower (ident x)),
+ (case p_sql_type_base t of
+ "bigint" =>
+ "' AND data_type IN ('bigint', 'numeric', 'integer')"
+ | "text" =>
+ "' AND data_type IN ('text', 'character varying')"
+ | t =>
+ String.concat ["' AND data_type = '",
+ t,
+ "'"]),
+ if checkNullable then
+ (" AND is_nullable = '"
+ ^ (if isNotNull t then
+ "NO"
+ else
+ "YES")
+ ^ "'")
+ else
+ "",
+ ")"]) xts) of
+ "" => "FALSE"
+ | s => s,
+ ")"]
+
+ val q'' = String.concat ["SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '",
+ sl,
+ "' AND LOWER(column_name) LIKE '", Settings.mangleSqlCatalog "%'"]
+ in
+ box [string "res = PQexec(conn, \"",
+ string q,
+ string "\");",
+ newline,
+ newline,
+ string "if (res == NULL) {",
+ newline,
+ box [string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Out of memory allocating query result.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, PQerrorMessage(conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q,
+ string "\\n%s\", msg);",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (strcmp(PQgetvalue(res, 0, 0), \"1\")) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string sl,
+ string "' does not exist.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "PQclear(res);",
+ newline,
+
+ string "res = PQexec(conn, \"",
+ string q',
+ string "\");",
+ newline,
+ newline,
+ string "if (res == NULL) {",
+ newline,
+ box [string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Out of memory allocating query result.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, PQerrorMessage(conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q',
+ string "\\n%s\", msg);",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (strcmp(PQgetvalue(res, 0, 0), \"",
+ string (Int.toString (length xts)),
+ string "\")) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string sl,
+ string "' has the wrong column types.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "PQclear(res);",
+ newline,
+ newline,
+
+ string "res = PQexec(conn, \"",
+ string q'',
+ string "\");",
+ newline,
+ newline,
+ string "if (res == NULL) {",
+ newline,
+ box [string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Out of memory allocating query result.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, PQerrorMessage(conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q'',
+ string "\\n%s\", msg);",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (strcmp(PQgetvalue(res, 0, 0), \"",
+ string (Int.toString (length xts)),
+ string "\")) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string sl,
+ string "' has extra columns.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "PQclear(res);",
+ newline]
+ end
+
+fun init {dbstring, prepared = ss, tables, views, sequences} =
+ box [if #persistent (currentProtocol ()) then
+ box [string "static void uw_db_validate(uw_context ctx) {",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res;",
+ newline,
+ newline,
+ p_list_sep newline (checkRel ("tables", true)) tables,
+ p_list_sep newline (checkRel ("views", false)) views,
+
+ p_list_sep newline
+ (fn s =>
+ let
+ val sl = CharVector.map Char.toLower s
+
+ val q = "SELECT COUNT(*) FROM pg_class WHERE relname = '"
+ ^ sl ^ "' AND relkind = 'S' AND pg_catalog.pg_table_is_visible(oid)"
+ in
+ box [string "res = PQexec(conn, \"",
+ string q,
+ string "\");",
+ newline,
+ newline,
+ string "if (res == NULL) {",
+ newline,
+ box [string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Out of memory allocating query result.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, PQerrorMessage(conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query failed:\\n",
+ string q,
+ string "\\n%s\", msg);",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (strcmp(PQgetvalue(res, 0, 0), \"1\")) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Sequence '",
+ string s,
+ string "' does not exist.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "PQclear(res);",
+ newline]
+ end) sequences,
+
+ string "}",
+
+ string "static void uw_db_prepare(uw_context ctx) {",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res;",
+ newline,
+ newline,
+
+ p_list_sepi newline (fn i => fn (s, _) =>
+ box [string "res = PQprepare(conn, \"uw",
+ string (Int.toString i),
+ string "\", \"",
+ string (Prim.toCString s),
+ string "\", 0, NULL);",
+ newline,
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, PQerrorMessage(conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Unable to create prepared statement:\\n",
+ string (Prim.toCString s),
+ string "\\n%s\", msg);",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline])
+ ss,
+
+ string "}",
+ newline,
+ newline]
+ else
+ box [string "static void uw_db_validate(uw_context ctx) { }",
+ newline,
+ string "static void uw_db_prepare(uw_context ctx) { }"],
+
+ string "static void uw_client_init(void) {",
+ newline,
+ box [string "uw_sqlfmtInt = \"%lld::int8%n\";",
+ newline,
+ string "uw_sqlfmtFloat = \"%.16g::float8%n\";",
+ newline,
+ string "uw_Estrings = 1;",
+ newline,
+ string "uw_sql_type_annotations = 1;",
+ newline,
+ string "uw_sqlsuffixString = \"::text\";",
+ newline,
+ string "uw_sqlsuffixChar = \"::char\";",
+ newline,
+ string "uw_sqlsuffixBlob = \"::bytea\";",
+ newline,
+ string "uw_sqlfmtUint4 = \"%u::int4%n\";",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "static void uw_db_close(uw_context ctx) {",
+ newline,
+ string "PQfinish(uw_get_db(ctx));",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_begin(uw_context ctx, int could_write) {",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res = PQexec(conn, could_write ? \"BEGIN ISOLATION LEVEL SERIALIZABLE\" : \"BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY\");",
+ newline,
+ newline,
+ string "if (res == NULL) return 1;",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ box [string "PQclear(res);",
+ newline,
+ string "return 1;",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "return 0;",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_commit(uw_context ctx) {",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res = PQexec(conn, \"COMMIT\");",
+ newline,
+ newline,
+ string "if (res == NULL) return 1;",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ box [string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "return -1;",
+ newline],
+ string "}",
+ newline,
+ string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "return -1;",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "return 1;",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "return 0;",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_rollback(uw_context ctx) {",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res = PQexec(conn, \"ROLLBACK\");",
+ newline,
+ newline,
+ string "if (res == NULL) return 1;",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ box [string "PQclear(res);",
+ newline,
+ string "return 1;",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "return 0;",
+ newline,
+ string "}",
+
+ newline,
+ newline,
+
+ string "static void uw_db_init(uw_context ctx) {",
+ newline,
+ string "char *env_db_str = getenv(\"URWEB_PQ_CON\");",
+ newline,
+ string "PGconn *conn = PQconnectdb(env_db_str == NULL ? \"",
+ string (Prim.toCString dbstring),
+ string "\" : env_db_str);",
+ newline,
+ string "if (conn == NULL) uw_error(ctx, FATAL, ",
+ string "\"libpq can't allocate a connection.\");",
+ newline,
+ string "if (PQstatus(conn) != CONNECTION_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, PQerrorMessage(conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ string "PQfinish(conn);",
+ newline,
+ string "uw_error(ctx, BOUNDED_RETRY, ",
+ string "\"Connection to Postgres server failed: %s\", msg);"],
+ newline,
+ string "}",
+ newline,
+ string "uw_set_db(ctx, conn);",
+ newline,
+ string "uw_db_validate(ctx);",
+ newline,
+ string "uw_db_prepare(ctx);",
+ newline,
+ string "}"]
+
+fun p_getcol {loc, wontLeakStrings, col = i, typ = t} =
+ let
+ fun p_unsql t e eLen =
+ case t of
+ Int => box [string "uw_Basis_stringToInt_error(ctx, ", e, string ")"]
+ | Float => box [string "uw_Basis_stringToFloat_error(ctx, ", e, string ")"]
+ | String =>
+ if wontLeakStrings then
+ e
+ else
+ box [string "uw_strdup(ctx, ", e, string ")"]
+ | Char => box [e, string "[0]"]
+ | Bool => box [string "uw_Basis_stringToBool_error(ctx, ", e, string ")"]
+ | Time => box [string "uw_Basis_unsqlTime(ctx, ", e, string ")"]
+ | Blob => box [string "uw_Basis_stringToBlob_error(ctx, ",
+ e,
+ string ", ",
+ eLen,
+ string ")"]
+ | Channel => box [string "uw_Basis_stringToChannel_error(ctx, ", e, string ")"]
+ | Client => box [string "uw_Basis_stringToClient_error(ctx, ", e, string ")"]
+
+ | Nullable _ => raise Fail "Postgres: Recursive Nullable"
+
+ fun getter t =
+ case t of
+ Nullable t =>
+ box [string "(PQgetisnull(res, i, ",
+ string (Int.toString i),
+ string ") ? NULL : ",
+ case t of
+ String => getter t
+ | _ => box [string "({",
+ newline,
+ string (p_sql_ctype t),
+ space,
+ string "*tmp = uw_malloc(ctx, sizeof(",
+ string (p_sql_ctype t),
+ string "));",
+ newline,
+ string "*tmp = ",
+ getter t,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string ")"]
+ | _ =>
+ box [string "(PQgetisnull(res, i, ",
+ string (Int.toString i),
+ string ") ? ",
+ box [string "({",
+ string (p_sql_ctype t),
+ space,
+ string "tmp;",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Unexpectedly NULL field #",
+ string (Int.toString i),
+ string "\");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string " : ",
+ p_unsql t
+ (box [string "PQgetvalue(res, i, ",
+ string (Int.toString i),
+ string ")"])
+ (box [string "PQgetlength(res, i, ",
+ string (Int.toString i),
+ string ")"]),
+ string ")"]
+ in
+ getter t
+ end
+
+fun queryCommon {loc, query, cols, doCols} =
+ box [string "int n, i;",
+ newline,
+ newline,
+
+ string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate query result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, UNLIMITED_RETRY, \"Serialization failure\");",
+ newline],
+ string "}",
+ newline,
+ string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, UNLIMITED_RETRY, \"Deadlock detected\");",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Query failed:\\n%s\\n%s\", ",
+ query,
+ string ", PQerrorMessage(conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQnfields(res) != ",
+ string (Int.toString (length cols)),
+ string ") {",
+ newline,
+ box [string "int nf = PQnfields(res);",
+ newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Query returned %d columns instead of ",
+ string (Int.toString (length cols)),
+ string ":\\n%s\\n%s\", nf, ",
+ query,
+ string ", PQerrorMessage(conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "uw_end_region(ctx);",
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))PQclear, res);",
+ newline,
+ string "n = PQntuples(res);",
+ newline,
+ string "for (i = 0; i < n; ++i) {",
+ newline,
+ doCols p_getcol,
+ string "}",
+ newline,
+ newline,
+ string "uw_pop_cleanup(ctx);",
+ newline]
+
+fun query {loc, cols, doCols} =
+ box [string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);",
+ newline,
+ newline,
+ queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"}]
+
+fun p_ensql t e =
+ case t of
+ Int => box [string "uw_Basis_attrifyInt(ctx, ", e, string ")"]
+ | Float => box [string "uw_Basis_attrifyFloat(ctx, ", e, string ")"]
+ | String => e
+ | Char => box [string "uw_Basis_attrifyChar(ctx, ", e, string ")"]
+ | Bool => box [string "(", e, string " ? \"TRUE\" : \"FALSE\")"]
+ | Time => box [string "uw_Basis_ensqlTime(ctx, ", e, string ")"]
+ | Blob => box [e, string ".data"]
+ | Channel => box [string "uw_Basis_attrifyChannel(ctx, ", e, string ")"]
+ | Client => box [string "uw_Basis_attrifyClient(ctx, ", e, string ")"]
+ | Nullable String => e
+ | Nullable t => box [string "(",
+ e,
+ string " == NULL ? NULL : ",
+ p_ensql t (box [string "(*", e, string ")"]),
+ string ")"]
+
+fun makeParams inputs =
+ box [string "static const int paramFormats[] = { ",
+ p_list_sep (box [string ",", space])
+ (fn t => if isBlob t then string "1" else string "0") inputs,
+ string " };",
+ newline,
+ if List.exists isBlob inputs then
+ box [string "int *paramLengths = uw_malloc(ctx, ",
+ string (Int.toString (length inputs)),
+ string " * sizeof(int));",
+ newline,
+ p_list_sepi (box [])
+ (fn i => fn t =>
+ box [string "paramLengths[",
+ string (Int.toString i),
+ string "] = ",
+ case t of
+ Blob => string ("arg" ^ Int.toString (i + 1) ^ ".size")
+ | Nullable Blob => string ("arg" ^ Int.toString (i + 1)
+ ^ "?arg" ^ Int.toString (i + 1) ^ "->size:0")
+ | _ => string "0",
+ string ";",
+ newline]) inputs,
+ newline]
+ else
+ box [string "const int *paramLengths = paramFormats;",
+ newline],
+
+ string "const char **paramValues = uw_malloc(ctx, ",
+ string (Int.toString (length inputs)),
+ string " * sizeof(char*));",
+ newline,
+ p_list_sepi (box [])
+ (fn i => fn t => box [string "paramValues[",
+ string (Int.toString i),
+ string "] = ",
+ p_ensql t (box [string "arg",
+ string (Int.toString (i + 1))]),
+ string ";",
+ newline])
+ inputs,
+ newline]
+
+fun queryPrepared {loc, id, query, inputs, cols, doCols, nested = _} =
+ box [string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+
+ makeParams inputs,
+
+ newline,
+ string "PGresult *res = ",
+ if #persistent (Settings.currentProtocol ()) then
+ box [string "PQexecPrepared(conn, \"uw",
+ string (Int.toString id),
+ string "\", ",
+ string (Int.toString (length inputs)),
+ string ", paramValues, paramLengths, paramFormats, 0);"]
+ else
+ box [string "PQexecParams(conn, \"",
+ string (Prim.toCString query),
+ string "\", ",
+ string (Int.toString (length inputs)),
+ string ", NULL, paramValues, paramLengths, paramFormats, 0);"],
+ newline,
+ newline,
+ queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"",
+ string (Prim.toCString query),
+ string "\""]}]
+
+fun dmlCommon {loc, dml, mode} =
+ box [string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate DML result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ newline,
+ box [string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40001\")) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, UNLIMITED_RETRY, \"Serialization failure\");",
+ newline],
+ string "}",
+ newline,
+ string "if (!strcmp_nullsafe(PQresultErrorField(res, PG_DIAG_SQLSTATE), \"40P01\")) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, UNLIMITED_RETRY, \"Deadlock detected\");",
+ newline],
+ string "}",
+ newline,
+ case mode of
+ Settings.Error => box [string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": DML failed:\\n%s\\n%s\", ",
+ dml,
+ string ", PQerrorMessage(conn));"]
+ | Settings.None => box [string "uw_set_error_message(ctx, PQerrorMessage(conn));",
+ newline,
+ newline,
+
+ string "res = PQexec(conn, \"ROLLBACK TO s\");",
+ newline,
+ string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate DML ROLLBACK result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": ROLLBACK TO failed:\\n%s\\n%s\", ",
+ dml,
+ string ", PQerrorMessage(conn));",
+ newline,
+ string "}"],
+ newline,
+
+ string "PQclear(res);",
+ newline],
+ newline],
+ string "}",
+
+ case mode of
+ Error => box [newline,
+ newline,
+ string "PQclear(res);",
+ newline]
+ | None => box[string " else {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "res = PQexec(conn, \"RELEASE s\");",
+ newline,
+ string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate DML RELEASE result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": RELEASE failed:\\n%s\\n%s\", ",
+ dml,
+ string ", PQerrorMessage(conn));",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline],
+ string "}",
+ newline]]
+
+fun makeSavepoint mode =
+ case mode of
+ Error => box []
+ | None => box [string "res = PQexec(conn, \"SAVEPOINT s\");",
+ newline,
+ string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate DML SAVEPOINT result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
+ box [newline,
+ string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Error creating SAVEPOINT\");",
+ newline],
+ string "}",
+ newline,
+ string "PQclear(res);",
+ newline,
+ newline]
+
+fun dml (loc, mode) =
+ box [string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res;",
+ newline,
+
+ makeSavepoint mode,
+
+ string "res = PQexecParams(conn, dml, 0, NULL, NULL, NULL, NULL, 0);",
+ newline,
+ newline,
+ dmlCommon {loc = loc, dml = string "dml", mode = mode}]
+
+fun dmlPrepared {loc, id, dml, inputs, mode} =
+ box [string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+
+ makeParams inputs,
+
+ newline,
+ string "PGresult *res;",
+ newline,
+ newline,
+
+ makeSavepoint mode,
+
+ string "res = ",
+ if #persistent (Settings.currentProtocol ()) then
+ box [string "PQexecPrepared(conn, \"uw",
+ string (Int.toString id),
+ string "\", ",
+ string (Int.toString (length inputs)),
+ string ", paramValues, paramLengths, paramFormats, 0);"]
+ else
+ box [string "PQexecParams(conn, \"",
+ string (Prim.toCString dml),
+ string "\", ",
+ string (Int.toString (length inputs)),
+ string ", NULL, paramValues, paramLengths, paramFormats, 0);"],
+ newline,
+ newline,
+ dmlCommon {loc = loc, dml = box [string "\"",
+ string (Prim.toCString dml),
+ string "\""], mode = mode}]
+
+fun nextvalCommon {loc, query} =
+ box [string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate NEXTVAL result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Query failed:\\n%s\\n%s\", ",
+ query,
+ string ", PQerrorMessage(conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "n = PQntuples(res);",
+ newline,
+ string "if (n != 1) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Wrong number of result rows:\\n%s\\n%s\", ",
+ query,
+ string ", PQerrorMessage(conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "n = uw_Basis_stringToInt_error(ctx, PQgetvalue(res, 0, 0));",
+ newline,
+ string "PQclear(res);",
+ newline]
+
+open Cjr
+
+fun nextval {loc, seqE, seqName} =
+ let
+ val query = case seqName of
+ SOME s =>
+ string ("\"SELECT NEXTVAL('" ^ s ^ "')\"")
+ | _ => box [string "uw_Basis_strcat(ctx, \"SELECT NEXTVAL('\", uw_Basis_strcat(ctx, ",
+ seqE,
+ string ", \"')\"))"]
+ in
+ box [string "char *query = ",
+ query,
+ string ";",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);",
+ newline,
+ newline,
+ nextvalCommon {loc = loc, query = string "query"}]
+ end
+
+fun nextvalPrepared {loc, id, query} =
+ box [string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ newline,
+ string "PGresult *res = ",
+ if #persistent (Settings.currentProtocol ()) then
+ box [string "PQexecPrepared(conn, \"uw",
+ string (Int.toString id),
+ string "\", 0, NULL, NULL, NULL, 0);"]
+ else
+ box [string "PQexecParams(conn, \"",
+ string (Prim.toCString query),
+ string "\", 0, NULL, NULL, NULL, NULL, 0);"],
+ newline,
+ newline,
+ nextvalCommon {loc = loc, query = box [string "\"",
+ string (Prim.toCString query),
+ string "\""]}]
+
+fun setvalCommon {loc, query} =
+ box [string "if (res == NULL) {",
+ box [newline,
+ string "uw_try_reconnecting_and_restarting(ctx);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't allocate SETVAL result; database server may be down.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (PQresultStatus(res) != PGRES_TUPLES_OK) {",
+ newline,
+ box [string "PQclear(res);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Query failed:\\n%s\\n%s\", ",
+ query,
+ string ", PQerrorMessage(conn));",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "PQclear(res);",
+ newline]
+
+fun setval {loc, seqE, count} =
+ let
+ val query = box [string "uw_Basis_strcat(ctx, \"SELECT SETVAL('\", uw_Basis_strcat(ctx, ",
+ seqE,
+ string ", uw_Basis_strcat(ctx, \"', \", uw_Basis_strcat(ctx, uw_Basis_sqlifyInt(ctx, ",
+ count,
+ string "), \")\"))))"]
+ in
+ box [string "char *query = ",
+ query,
+ string ";",
+ newline,
+ string "PGconn *conn = uw_get_db(ctx);",
+ newline,
+ string "PGresult *res = PQexecParams(conn, query, 0, NULL, NULL, NULL, NULL, 0);",
+ newline,
+ newline,
+ setvalCommon {loc = loc, query = string "query"}]
+ end
+
+fun sqlifyString s = "E'" ^ String.translate (fn #"'" => "\\'"
+ | #"\\" => "\\\\"
+ | ch =>
+ if Char.isPrint ch then
+ str ch
+ else
+ "\\" ^ StringCvt.padLeft #"0" 3
+ (Int.fmt StringCvt.OCT (ord ch)))
+ (Prim.toCString s) ^ "'::text"
+
+fun p_cast (s, t) = s ^ "::" ^ p_sql_type t
+
+fun p_blank (n, t) = p_cast ("$" ^ Int.toString n, t)
+
+val () = addDbms {name = "postgres",
+ randomFunction = "RANDOM",
+ header = Config.pgheader,
+ link = "-lpq",
+ p_sql_type = p_sql_type,
+ init = init,
+ query = query,
+ queryPrepared = queryPrepared,
+ dml = dml,
+ dmlPrepared = dmlPrepared,
+ nextval = nextval,
+ nextvalPrepared = nextvalPrepared,
+ setval = setval,
+ sqlifyString = sqlifyString,
+ p_cast = p_cast,
+ p_blank = p_blank,
+ supportsDeleteAs = true,
+ supportsUpdateAs = true,
+ createSequence = fn s => "CREATE SEQUENCE " ^ s,
+ textKeysNeedLengths = false,
+ supportsNextval = true,
+ supportsNestedPrepared = true,
+ sqlPrefix = "",
+ supportsOctetLength = true,
+ trueString = "TRUE",
+ falseString = "FALSE",
+ onlyUnion = false,
+ nestedRelops = true,
+ windowFunctions = true,
+ supportsIsDistinctFrom = true}
+
+val () = setDbms "postgres"
+
+end
diff --git a/src/prefix.cm b/src/prefix.cm
new file mode 100644
index 0000000..2e71d07
--- /dev/null
+++ b/src/prefix.cm
@@ -0,0 +1,7 @@
+Group is
+
+$/basis.cm
+$/smlnj-lib.cm
+$smlnj/ml-yacc/ml-yacc-lib.cm
+$/pp-lib.cm
+
diff --git a/src/prefix.mlb b/src/prefix.mlb
new file mode 100644
index 0000000..6a51048
--- /dev/null
+++ b/src/prefix.mlb
@@ -0,0 +1,7 @@
+local
+ $(SML_LIB)/basis/basis.mlb
+ $(SML_LIB)/smlnj-lib/Util/smlnj-lib.mlb
+ $(SML_LIB)/mlyacc-lib/mlyacc-lib.mlb
+ $(SML_LIB)/smlnj-lib/PP/pp-lib.mlb
+in
+
diff --git a/src/prepare.sig b/src/prepare.sig
new file mode 100644
index 0000000..0977100
--- /dev/null
+++ b/src/prepare.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature PREPARE = sig
+
+ val prepare : Cjr.file -> Cjr.file
+
+end
diff --git a/src/prepare.sml b/src/prepare.sml
new file mode 100644
index 0000000..660173f
--- /dev/null
+++ b/src/prepare.sml
@@ -0,0 +1,356 @@
+(* Copyright (c) 2008, 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 Prepare :> PREPARE = struct
+
+open Cjr
+open Settings
+
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+structure St :> sig
+ type t
+ val empty : t
+ val nameOf : t * string -> t * int
+ val list : t -> (string * int) list
+ val count : t -> int
+end = struct
+
+type t = {map : int SM.map, list : (string * int) list, count : int}
+
+val empty = {map = SM.empty, list = [], count = 0}
+
+fun nameOf (t as {map, list, count}, s) =
+ case SM.find (map, s) of
+ NONE => ({map = SM.insert (map, s, count), list = (s, count) :: list, count = count + 1}, count)
+ | SOME n => (t, n)
+
+fun list (t : t) = rev (#list t)
+fun count (t : t) = #count t
+
+end
+
+fun prepString (e, st) =
+ let
+ fun prepString' (e, ss, n) =
+ let
+ fun doOne t =
+ SOME (#p_blank (Settings.currentDbms ()) (n + 1, t) :: ss, n + 1)
+ in
+ case #1 e of
+ EPrim (Prim.String (_, s)) =>
+ SOME (s :: ss, n)
+ | EFfiApp ("Basis", "strcat", [(e1, _), (e2, _)]) =>
+ (case prepString' (e1, ss, n) of
+ NONE => NONE
+ | SOME (ss, n) => prepString' (e2, ss, n))
+ | EFfiApp ("Basis", "sqlifyInt", [_]) => doOne Int
+ | EFfiApp ("Basis", "sqlifyFloat", [_]) => doOne Float
+ | EFfiApp ("Basis", "sqlifyString", [_]) => doOne String
+ | EFfiApp ("Basis", "sqlifyBool", [_]) => doOne Bool
+ | EFfiApp ("Basis", "sqlifyTime", [_]) => doOne Time
+ | EFfiApp ("Basis", "sqlifyBlob", [_]) => doOne Blob
+ | EFfiApp ("Basis", "sqlifyChannel", [_]) => doOne Channel
+ | EFfiApp ("Basis", "sqlifyClient", [_]) => doOne Client
+
+ | ECase (e,
+ [((PNone _, _),
+ (EPrim (Prim.String (_, "NULL")), _)),
+ ((PSome (_, (PVar _, _)), _),
+ (EFfiApp (m, x, [((ERel 0, _), _)]), _))],
+ {disc = t, ...}) => prepString' ((EFfiApp (m, x, [(e, t)]), #2 e), ss, n)
+
+ | ECase (e,
+ [((PCon (_, PConFfi {mod = "Basis", con = "True", ...}, _), _),
+ (EPrim (Prim.String (_, "TRUE")), _)),
+ ((PCon (_, PConFfi {mod = "Basis", con = "False", ...}, _), _),
+ (EPrim (Prim.String (_, "FALSE")), _))],
+ _) => doOne Bool
+
+ | _ => NONE
+ end
+ in
+ case prepString' (e, [], 0) of
+ NONE => NONE
+ | SOME (ss, n) =>
+ let
+ val s = String.concat (rev ss)
+ val (st, id) = St.nameOf (st, s)
+ in
+ SOME (id, s, st)
+ end
+ end
+
+fun prepExp (e as (_, loc), st) =
+ case #1 e of
+ EPrim _ => (e, st)
+ | ERel _ => (e, st)
+ | ENamed _ => (e, st)
+ | ECon (_, _, NONE) => (e, st)
+ | ECon (dk, pc, SOME e) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((ECon (dk, pc, SOME e), loc), st)
+ end
+ | ENone t => (e, st)
+ | ESome (t, e) =>
+ let
+ val (e, st) = prepExp (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) = prepExp (e, st)
+ in
+ ((e, t), st)
+ end) st es
+ in
+ ((EFfiApp (m, x, es), loc), st)
+ end
+ | EApp (e1, es) =>
+ let
+ val (e1, st) = prepExp (e1, st)
+ val (es, st) = ListUtil.foldlMap prepExp st es
+ in
+ ((EApp (e1, es), loc), st)
+ end
+
+ | EUnop (s, e1) =>
+ let
+ val (e1, st) = prepExp (e1, st)
+ in
+ ((EUnop (s, e1), loc), st)
+ end
+ | EBinop (s, e1, e2) =>
+ let
+ val (e1, st) = prepExp (e1, st)
+ val (e2, st) = prepExp (e2, st)
+ in
+ ((EBinop (s, e1, e2), loc), st)
+ end
+
+ | ERecord (rn, xes) =>
+ let
+ val (xes, st) = ListUtil.foldlMap (fn ((x, e), st) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((x, e), st)
+ end) st xes
+ in
+ ((ERecord (rn, xes), loc), st)
+ end
+ | EField (e, s) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((EField (e, s), loc), st)
+ end
+
+ | ECase (e, pes, ts) =>
+ let
+ val (e, st) = prepExp (e, st)
+ val (pes, st) = ListUtil.foldlMap (fn ((p, e), st) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((p, e), st)
+ end) st pes
+ in
+ ((ECase (e, pes, ts), loc), st)
+ end
+
+ | EError (e, t) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((EError (e, t), loc), st)
+ end
+
+ | EReturnBlob {blob, mimeType, t} =>
+ let
+ val (blob, st) = case blob of
+ NONE => (blob, st)
+ | SOME blob =>
+ let
+ val (b, st) = prepExp (blob, st)
+ in
+ (SOME b, st)
+ end
+ val (mimeType, st) = prepExp (mimeType, st)
+ in
+ ((EReturnBlob {blob = blob, mimeType = mimeType, t = t}, loc), st)
+ end
+
+ | ERedirect (e, t) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((ERedirect (e, t), loc), st)
+ end
+
+ | EWrite e =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((EWrite e, loc), st)
+ end
+ | ESeq (e1, e2) =>
+ let
+ val (e1, st) = prepExp (e1, st)
+ val (e2, st) = prepExp (e2, st)
+ in
+ ((ESeq (e1, e2), loc), st)
+ end
+ | ELet (x, t, e1, e2) =>
+ let
+ val (e1, st) = prepExp (e1, st)
+ val (e2, st) = prepExp (e2, st)
+ in
+ ((ELet (x, t, e1, e2), loc), st)
+ end
+
+ | EQuery {exps, tables, rnum, state, query, body, initial, ...} =>
+ let
+ val (body, st) = prepExp (body, st)
+ in
+ case prepString (query, st) of
+ NONE =>
+ ((EQuery {exps = exps, tables = tables, rnum = rnum,
+ state = state, query = query, body = body,
+ initial = initial, prepared = NONE}, loc),
+ st)
+ | SOME (id, s, st) =>
+ ((EQuery {exps = exps, tables = tables, rnum = rnum,
+ state = state, query = query, body = body,
+ initial = initial, prepared = SOME {id = id, query = s, nested = true}}, loc), st)
+ end
+
+ | EDml {dml, mode, ...} =>
+ (case prepString (dml, st) of
+ NONE => (e, st)
+ | SOME (id, s, st) =>
+ ((EDml {dml = dml, prepared = SOME {id = id, dml = s}, mode = mode}, loc), st))
+
+ | ENextval {seq, ...} =>
+ if #supportsNextval (Settings.currentDbms ()) then
+ let
+ val s = case seq of
+ (EPrim (Prim.String (_, s)), loc) =>
+ (EPrim (Prim.String (Prim.Normal, "SELECT NEXTVAL('" ^ s ^ "')")), loc)
+ | _ =>
+ let
+ val t = (TFfi ("Basis", "string"), loc)
+ val s' = (EFfiApp ("Basis", "strcat", [(seq, t), ((EPrim (Prim.String (Prim.Normal, "')")), loc), t)]), loc)
+ in
+ (EFfiApp ("Basis", "strcat", [((EPrim (Prim.String (Prim.Normal, "SELECT NEXTVAL('")), loc), t), (s', t)]), loc)
+ end
+ in
+ case prepString (s, st) of
+ NONE => (e, st)
+ | SOME (id, s, st) =>
+ ((ENextval {seq = seq, prepared = SOME {id = id, query = s}}, loc), st)
+ end
+ else
+ (e, st)
+
+ | ESetval {seq = e1, count = e2} =>
+ let
+ val (e1, st) = prepExp (e1, st)
+ val (e2, st) = prepExp (e2, st)
+ in
+ ((ESetval {seq = e1, count = e2}, loc), st)
+ end
+
+ | EUnurlify (e, t, b) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((EUnurlify (e, t, b), loc), st)
+ end
+
+fun prepDecl (d as (_, loc), st) =
+ case #1 d of
+ DStruct _ => (d, st)
+ | DDatatype _ => (d, st)
+ | DDatatypeForward _ => (d, st)
+ | DVal (x, n, t, e) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((DVal (x, n, t, e), loc), st)
+ end
+ | DFun (x, n, xts, t, e) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((DFun (x, n, xts, t, e), loc), st)
+ end
+ | DFunRec fs =>
+ let
+ val (fs, st) = ListUtil.foldlMap (fn ((x, n, xts, t, e), st) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((x, n, xts, t, e), st)
+ end) st fs
+ in
+ ((DFunRec fs, loc), st)
+ end
+
+ | DTable _ => (d, st)
+ | DSequence _ => (d, st)
+ | DView _ => (d, st)
+ | DDatabase _ => (d, st)
+ | DPreparedStatements _ => (d, st)
+ | DJavaScript _ => (d, st)
+ | DCookie _ => (d, st)
+ | DStyle _ => (d, st)
+ | DTask (tk, x1, x2, e) =>
+ let
+ val (e, st) = prepExp (e, st)
+ in
+ ((DTask (tk, x1, x2, e), loc), st)
+ end
+ | DOnError _ => (d, st)
+
+fun prepare (ds, ps) =
+ let
+ val (ds, st) = ListUtil.foldlMap prepDecl St.empty ds
+ in
+ ((DPreparedStatements (St.list st), ErrorMsg.dummySpan) :: ds, ps)
+ end
+
+end
diff --git a/src/prim.sig b/src/prim.sig
new file mode 100644
index 0000000..1da53d3
--- /dev/null
+++ b/src/prim.sig
@@ -0,0 +1,49 @@
+(* Copyright (c) 2008, 2014, 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.
+ *)
+
+signature PRIM = sig
+
+ datatype string_mode = Normal | Html
+
+ datatype t =
+ Int of Int64.int
+ | Float of Real64.real
+ | String of string_mode * string
+ | Char of char
+
+ val p_t : t Print.printer
+ val p_t_GCC : t Print.printer
+
+ val equal : t * t -> bool
+ val compare : t * t -> order
+
+ val toString : t -> string
+
+ val toCString : string -> string
+ (* SML's built-in [String.toCString] gets confused by single quotes! *)
+
+end
diff --git a/src/prim.sml b/src/prim.sml
new file mode 100644
index 0000000..1de4fc7
--- /dev/null
+++ b/src/prim.sml
@@ -0,0 +1,119 @@
+(* Copyright (c) 2008, 2014, 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 Prim :> PRIM = struct
+
+datatype string_mode = Normal | Html
+
+datatype t =
+ Int of Int64.int
+ | Float of Real64.real
+ | String of string_mode * string
+ | Char of char
+
+open Print.PD
+open Print
+
+fun p_t t =
+ case t of
+ Int n => string (Int64.toString n)
+ | Float n => string (Real64.toString n)
+ | String (_, s) => box [string "\"", string (String.toString s), string "\""]
+ | Char ch => box [string "#\"", string (String.toString (String.str ch)), string "\""]
+
+fun int2s n =
+ if Int64.compare (n, Int64.fromInt 0) = LESS then
+ "-" ^ Int64.toString (Int64.~ n) ^ "LL"
+ else
+ Int64.toString n ^ "LL"
+
+fun int2s' n =
+ if Int64.compare (n, Int64.fromInt 0) = LESS then
+ "-" ^ Int64.toString (Int64.~ n)
+ else
+ Int64.toString n
+
+val float2s = String.translate (fn #"~" => "-" | ch => str ch) o Real64.toString
+
+fun toString t =
+ case t of
+ Int n => int2s' n
+ | Float n => float2s n
+ | String (_, s) => s
+ | Char ch => str ch
+
+fun pad (n, ch, s) =
+ if size s >= n then
+ s
+ else
+ str ch ^ pad (n-1, ch, s)
+
+fun quoteDouble ch =
+ case ch of
+ #"'" => str ch
+ | _ => Char.toCString ch
+
+fun toCChar ch =
+ case ch of
+ #"\"" => str ch
+ | _ => Char.toCString ch
+
+val toCString = String.translate quoteDouble
+
+fun p_t_GCC t =
+ case t of
+ Int n => string (int2s n)
+ | Float n => string (float2s n)
+ | String (_, s) => box [string "\"", string (toCString s), string "\""]
+ | Char ch => box [string "'", string (toCChar ch), string "'"]
+
+fun equal x =
+ case x of
+ (Int n1, Int n2) => n1 = n2
+ | (Float n1, Float n2) => Real64.== (n1, n2)
+ | (String (_, s1), String (_, s2)) => s1 = s2
+ | (Char ch1, Char ch2) => ch1 = ch2
+
+ | _ => false
+
+fun compare (p1, p2) =
+ case (p1, p2) of
+ (Int n1, Int n2) => Int64.compare (n1, n2)
+ | (Int _, _) => LESS
+ | (_, Int _) => GREATER
+
+ | (Float n1, Float n2) => Real64.compare (n1, n2)
+ | (Float _, _) => LESS
+ | (_, Float _) => GREATER
+
+ | (String (_, n1), String (_, n2)) => String.compare (n1, n2)
+ | (String _, _) => LESS
+ | (_, String _) => GREATER
+
+ | (Char ch1, Char ch2) => Char.compare (ch1, ch2)
+
+end
diff --git a/src/print.sig b/src/print.sig
new file mode 100644
index 0000000..7467e04
--- /dev/null
+++ b/src/print.sig
@@ -0,0 +1,64 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing *)
+
+signature PRINT = sig
+ structure PD : PP_DESC
+ where type PPS.token = string
+ and type PPS.device = TextIOPP.device
+ and type PPS.stream = TextIOPP.stream
+
+ type 'a printer = 'a -> PD.pp_desc
+
+ val box : PD.pp_desc list -> PD.pp_desc
+ val parenIf : bool -> PD.pp_desc -> PD.pp_desc
+ val space : PD.pp_desc
+
+ val p_list_sep : PD.pp_desc -> 'a printer -> 'a list printer
+ val p_list : 'a printer -> 'a list printer
+
+ val p_list_sepi : PD.pp_desc -> (int -> 'a printer) -> 'a list printer
+
+ val fprint : PD.PPS.stream -> PD.pp_desc -> unit
+ val print : PD.pp_desc -> unit
+ val eprint : PD.pp_desc -> unit
+
+ val fpreface : PD.PPS.stream -> string * PD.pp_desc -> unit
+ val preface : string * PD.pp_desc -> unit
+ val epreface : string * PD.pp_desc -> unit
+
+ val fprefaces : PD.PPS.stream -> string -> (string * PD.pp_desc) list -> unit
+ val prefaces : string -> (string * PD.pp_desc) list -> unit
+ val eprefaces : string -> (string * PD.pp_desc) list -> unit
+
+ val fprefaces' : PD.PPS.stream -> (string * PD.pp_desc) list -> unit
+ val prefaces' : (string * PD.pp_desc) list -> unit
+ val eprefaces' : (string * PD.pp_desc) list -> unit
+
+ val openOut : {dst : TextIO.outstream, wid : int} -> PD.PPS.stream
+end
diff --git a/src/print.sml b/src/print.sml
new file mode 100644
index 0000000..d4059ed
--- /dev/null
+++ b/src/print.sml
@@ -0,0 +1,127 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Generic printing support code *)
+
+structure Print :> PRINT = struct
+
+structure SM = TextIOPP
+structure PD = PPDescFn(SM)
+
+val openOut = SM.openOut
+
+type 'a printer = 'a -> PD.pp_desc
+
+fun box ds = PD.hovBox (PD.PPS.Rel 1, ds)
+fun parenIf b d =
+ if b then
+ box [PD.string "(", d, PD.string ")"]
+ else
+ d
+val space = PD.space 1
+
+val out = SM.openOut {dst = TextIO.stdOut, wid = 70}
+val err = SM.openOut {dst = TextIO.stdErr, wid = 70}
+
+fun p_list_sep sep f ls =
+ case ls of
+ [] => PD.string ""
+ | [x] => f x
+ | x :: rest =>
+ let
+ val tokens = foldr (fn (x, tokens) =>
+ sep :: PD.cut :: f x :: tokens)
+ [] rest
+ in
+ box (f x :: tokens)
+ end
+fun p_list f = p_list_sep (box [PD.string ",", space]) f
+
+fun p_list_sepi sep f ls =
+ case ls of
+ [] => PD.string ""
+ | [x] => f 0 x
+ | x :: rest =>
+ let
+ val tokens = ListUtil.foldri (fn (n, x, tokens) =>
+ sep :: PD.cut :: f (n + 1) x :: tokens)
+ [] rest
+ in
+ box (f 0 x :: tokens)
+ end
+
+fun fprint f d = (PD.description (f, d);
+ PD.PPS.flushStream f)
+val print = fprint out
+val eprint = fprint err
+
+fun fpreface f (s, d) =
+ fprint f (PD.hovBox (PD.PPS.Rel 0,
+ [PD.string s, PD.space 1, d, PD.newline]))
+
+val preface = fpreface out
+val epreface = fpreface err
+
+fun fprefaces f s ls =
+ let
+ val len = foldl (fn ((s, _), best) =>
+ Int.max (size s, best)) 0 ls
+ in
+ fprint f (PD.string s);
+ fprint f PD.newline;
+ app (fn (s, d) =>
+ let
+ val s = CharVector.tabulate (len - size s,
+ fn _ => #" ")
+ ^ s ^ ": "
+ in
+ fpreface f (s, d)
+ end) ls
+ end
+
+val prefaces = fprefaces out
+val eprefaces = fprefaces err
+
+fun fprefaces' f ls =
+ let
+ val len = foldl (fn ((s, _), best) =>
+ Int.max (size s, best)) 0 ls
+ in
+ app (fn (s, d) =>
+ let
+ val s = CharVector.tabulate (len - size s,
+ fn _ => #" ")
+ ^ s ^ ": "
+ in
+ fpreface f (s, d)
+ end) ls
+ end
+
+val prefaces' = fprefaces' out
+val eprefaces' = fprefaces' err
+
+end
diff --git a/src/reduce.sig b/src/reduce.sig
new file mode 100644
index 0000000..0a28a59
--- /dev/null
+++ b/src/reduce.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Simplify a Core program algebraically *)
+
+signature REDUCE = sig
+
+ val reduce : Core.file -> Core.file
+
+end
diff --git a/src/reduce.sml b/src/reduce.sml
new file mode 100644
index 0000000..04cec16
--- /dev/null
+++ b/src/reduce.sml
@@ -0,0 +1,953 @@
+(* Copyright (c) 2008-2011, 2013, 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.
+ *)
+
+(* Simplify a Core program algebraically *)
+
+structure Reduce :> REDUCE = struct
+
+open Core
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+structure E = CoreEnv
+
+fun multiLiftConInCon n c =
+ if n = 0 then
+ c
+ else
+ multiLiftConInCon (n - 1) (E.liftConInCon 0 c)
+
+fun multiLiftExpInExp n e =
+ if n = 0 then
+ e
+ else
+ multiLiftExpInExp (n - 1) (E.liftExpInExp 0 e)
+
+val count = CoreUtil.Exp.foldB {kind = fn (_, _, c) => c,
+ con = fn (_, _, c) => c,
+ exp = fn (x, e, c) =>
+ case e of
+ ERel x' => if x = x' then c + 1 else c
+ | _ => c,
+ bind = fn (x, b) =>
+ case b of
+ CoreUtil.Exp.RelE _ => x+1
+ | _ => x} 0 0
+
+val dangling =
+ CoreUtil.Exp.existsB {kind = fn _ => false,
+ con = fn _ => false,
+ exp = fn (n, e) =>
+ case e of
+ ERel n' => n' >= n
+ | _ => false,
+ bind = fn (n, b) =>
+ case b of
+ CoreUtil.Exp.RelE _ => n + 1
+ | _ => n}
+
+val cdangling =
+ CoreUtil.Exp.existsB {kind = fn _ => false,
+ con = fn (n, c) =>
+ case c of
+ CRel n' => n' >= n
+ | _ => false,
+ exp = fn _ => false,
+ bind = fn (n, b) =>
+ case b of
+ CoreUtil.Exp.RelC _ => n + 1
+ | _ => n}
+
+datatype env_item =
+ UnknownK
+ | KnownK of kind
+
+ | UnknownC
+ | KnownC of con
+
+ | UnknownE
+ | KnownE of exp
+
+ | Lift of int * int * int
+
+val edepth = foldl (fn (UnknownE, n) => n + 1
+ | (KnownE _, n) => n + 1
+ | (_, n) => n) 0
+
+val edepth' = foldl (fn (UnknownE, n) => n + 1
+ | (KnownE _, n) => n + 1
+ | (Lift (_, _, n'), n) => n + n'
+ | (_, n) => n) 0
+
+val cdepth = foldl (fn (UnknownC, n) => n + 1
+ | (KnownC _, n) => n + 1
+ | (_, n) => n) 0
+
+val cdepth' = foldl (fn (UnknownC, n) => n + 1
+ | (KnownC _, n) => n + 1
+ | (Lift (_, n', _), n) => n + n'
+ | (_, n) => n) 0
+
+type env = env_item list
+
+fun ei2s ei =
+ case ei of
+ UnknownK => "UK"
+ | KnownK _ => "KK"
+ | UnknownC => "UC"
+ | KnownC _ => "KC"
+ | UnknownE => "UE"
+ | KnownE _ => "KE"
+ | Lift (_, n1, n2) => "(" ^ Int.toString n1 ^ ", " ^ Int.toString n2 ^ ")"
+
+fun e2s env = String.concatWith " " (map ei2s env)
+
+(*val deKnown = List.filter (fn KnownC _ => false
+ | KnownE _ => false
+ | KnownK _ => false
+ | _ => true)*)
+
+val deKnown = ListUtil.mapConcat (fn KnownC _ => []
+ | KnownE _ => []
+ | KnownK _ => []
+ | Lift (nk, nc, ne) => List.tabulate (nk, fn _ => UnknownK)
+ @ List.tabulate (nc, fn _ => UnknownC)
+ @ List.tabulate (ne, fn _ => UnknownE)
+ | x => [x])
+
+datatype result = Yes of env | No | Maybe
+
+fun match (env, p : pat, e : exp) =
+ let
+ val baseline = length env
+
+ fun match (env, p, e) =
+ case (#1 p, #1 e) of
+ (PVar (x, t), _) => Yes (KnownE (multiLiftExpInExp (length env - baseline) e) :: env)
+
+ | (PPrim p, EPrim p') =>
+ if Prim.equal (p, p') then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConVar n1, _, NONE), ECon (_, PConVar n2, _, NONE)) =>
+ if n1 = n2 then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConVar n1, _, SOME p), ECon (_, PConVar n2, _, SOME e)) =>
+ if n1 = n2 then
+ match (env, p, e)
+ else
+ No
+
+ | (PCon (_, PConFfi {mod = m1, con = con1, ...}, _, NONE),
+ ECon (_, PConFfi {mod = m2, con = con2, ...}, _, NONE)) =>
+ if m1 = m2 andalso con1 = con2 then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConFfi {mod = m1, con = con1, ...}, _, SOME ep),
+ ECon (_, PConFfi {mod = m2, con = con2, ...}, _, SOME e)) =>
+ if m1 = m2 andalso con1 = con2 then
+ match (env, p, e)
+ else
+ No
+
+ | (PRecord xps, ERecord xes) =>
+ if List.exists (fn ((CName _, _), _, _) => false
+ | _ => true) xes then
+ Maybe
+ else
+ let
+ fun consider (xps, env) =
+ case xps of
+ [] => Yes env
+ | (x, p, _) :: rest =>
+ case List.find (fn ((CName x', _), _, _) => x' = x
+ | _ => false) xes of
+ NONE => No
+ | SOME (_, e, _) =>
+ case match (env, p, e) of
+ No => No
+ | Maybe => Maybe
+ | Yes env => consider (rest, env)
+ in
+ consider (xps, env)
+ end
+
+ | _ => Maybe
+ in
+ match (env, p, e)
+ end
+
+fun returnType m loc =
+ (TCFun ("a", (KType, loc),
+ (TFun ((CRel 0, loc),
+ (CApp (multiLiftConInCon 1 m, (CRel 0, loc)), loc)), loc)), loc)
+
+fun bindType m loc =
+ (TCFun ("a", (KType, loc),
+ (TCFun ("b", (KType, loc),
+ (TFun ((CApp (multiLiftConInCon 2 m, (CRel 1, loc)), loc),
+ (TFun ((TFun ((CRel 1, loc),
+ (CApp (multiLiftConInCon 2 m, (CRel 0, loc)), loc)),
+ loc),
+ (CApp (multiLiftConInCon 2 m, (CRel 0, loc)), loc)), loc)),
+ loc)), loc)), loc)
+
+fun monadRecord m loc =
+ (TRecord (CRecord ((KType, loc),
+ [((CName "Return", loc),
+ returnType m loc),
+ ((CName "Bind", loc),
+ bindType m loc)]), loc), loc)
+
+fun passive (e : exp) =
+ case #1 e of
+ EPrim _ => true
+ | ERel _ => true
+ | ENamed _ => true
+ | ECon (_, _, _, NONE) => true
+ | ECon (_, _, _, SOME e) => passive e
+ | EFfi _ => true
+ | EAbs _ => true
+ | ECAbs _ => true
+ | EKAbs _ => true
+ | ERecord xes => List.all (passive o #2) xes
+ | EField (e, _, _) => passive e
+ | _ => false
+
+fun notFfi (t : con) =
+ case #1 t of
+ CFfi _ => false
+ | _ => true
+
+fun kindConAndExp (namedC, namedE) =
+ let
+ fun kind env (all as (k, loc)) =
+ case k of
+ KType => all
+ | KArrow (k1, k2) => (KArrow (kind env k1, kind env k2), loc)
+ | KName => all
+ | KRecord k => (KRecord (kind env k), loc)
+ | KUnit => all
+ | KTuple ks => (KTuple (map (kind env) ks), loc)
+
+ | KRel n =>
+ let
+ fun find (n', env, nudge, lift) =
+ case env of
+ [] => raise Fail "Reduce.kind: KRel"
+ | UnknownC :: rest => find (n', rest, nudge, lift)
+ | KnownC _ :: rest => find (n', rest, nudge, lift)
+ | UnknownE :: rest => find (n', rest, nudge, lift)
+ | KnownE _ :: rest => find (n', rest, nudge, lift)
+ | Lift (lift', _, _) :: rest => find (n', rest, nudge + lift', lift + lift')
+ | UnknownK :: rest =>
+ if n' = 0 then
+ (KRel (n + nudge), loc)
+ else
+ find (n' - 1, rest, nudge, lift + 1)
+ | KnownK k :: rest =>
+ if n' = 0 then
+ kind (Lift (lift, 0, 0) :: rest) k
+ else
+ find (n' - 1, rest, nudge - 1, lift)
+ in
+ find (n, env, 0, 0)
+ end
+ | KFun (x, k) => (KFun (x, kind (UnknownK :: env) k), loc)
+
+ fun con env (all as (c, loc)) =
+ ((*Print.prefaces "con" [("c", CorePrint.p_con CoreEnv.empty all)];*)
+ case c of
+ TFun (c1, c2) => (TFun (con env c1, con env c2), loc)
+ | TCFun (x, k, c2) => (TCFun (x, kind env k, con (UnknownC :: env) c2), loc)
+ | TKFun (x, c2) => (TKFun (x, con (UnknownK :: env) c2), loc)
+ | TRecord c => (TRecord (con env c), loc)
+
+ | CRel n =>
+ let
+ fun find (n', env, nudge, liftK, liftC) =
+ case env of
+ [] => raise Fail "Reduce.con: CRel"
+ | UnknownK :: rest => find (n', rest, nudge, liftK + 1, liftC)
+ | KnownK _ :: rest => find (n', rest, nudge, liftK, liftC)
+ | UnknownE :: rest => find (n', rest, nudge, liftK, liftC)
+ | KnownE _ :: rest => find (n', rest, nudge, liftK, liftC)
+ | Lift (liftK', liftC', _) :: rest => find (n', rest, nudge + liftC',
+ liftK + liftK', liftC + liftC')
+ | UnknownC :: rest =>
+ if n' = 0 then
+ (CRel (n + nudge), loc)
+ else
+ find (n' - 1, rest, nudge, liftK, liftC + 1)
+ | KnownC c :: rest =>
+ if n' = 0 then
+ con (Lift (liftK, liftC, 0) :: rest) c
+ else
+ find (n' - 1, rest, nudge - 1, liftK, liftC)
+ in
+ (*print (Int.toString n ^ ": " ^ e2s env ^ "\n");*)
+ find (n, env, 0, 0, 0)
+ end
+
+ | CNamed n =>
+ (case IM.find (namedC, n) of
+ NONE => all
+ | SOME c => c)
+
+ | CFfi ("Basis", "monad") => (CAbs ("m", (KArrow ((KType, loc), (KType, loc)), loc), monadRecord (CRel 0, loc) loc), loc)
+
+ | CFfi _ => all
+ | CApp (c1, c2) =>
+ let
+ val c1 = con env c1
+ val c2 = con env c2
+ in
+ case #1 c1 of
+ CAbs (_, _, b) =>
+ con (KnownC c2 :: deKnown env) b
+
+ | CApp ((CMap (dom, ran), _), f) =>
+ (case #1 c2 of
+ CRecord (_, []) => (CRecord (kind env ran, []), loc)
+ | CRecord (_, (x, c) :: rest) =>
+ con (deKnown env)
+ (CConcat ((CRecord (ran, [(x, (CApp (f, c), loc))]), loc),
+ (CApp (c1, (CRecord (kind env dom, rest), loc)), loc)), loc)
+ | _ => (CApp (c1, c2), loc))
+
+ | _ => (CApp (c1, c2), loc)
+ end
+ | CAbs (x, k, b) => (CAbs (x, kind env k, con (UnknownC :: env) b), loc)
+
+ | CKApp (c1, k) =>
+ let
+ val c1 = con env c1
+ in
+ case #1 c1 of
+ CKAbs (_, b) =>
+ con (KnownK k :: deKnown env) b
+
+ | _ => (CKApp (c1, kind env k), loc)
+ end
+ | CKAbs (x, b) => (CKAbs (x, con (UnknownK :: env) b), loc)
+
+ | CName _ => all
+
+ | CRecord (k, xcs) => (CRecord (kind env k, map (fn (x, c) => (con env x, con env c)) xcs), loc)
+ | CConcat (c1, c2) =>
+ let
+ val c1 = con env c1
+ val c2 = con env c2
+ in
+ case (#1 c1, #1 c2) of
+ (CRecord (k, xcs1), CRecord (_, xcs2)) =>
+ (CRecord (kind env k, xcs1 @ xcs2), loc)
+ | (CRecord (_, []), _) => c2
+ | (_, CRecord (_, [])) => c1
+ | _ => (CConcat (c1, c2), loc)
+ end
+ | CMap (dom, ran) => (CMap (kind env dom, kind env ran), loc)
+
+ | CUnit => all
+
+ | CTuple cs => (CTuple (map (con env) cs), loc)
+ | CProj (c, n) =>
+ let
+ val c = con env c
+ in
+ case #1 c of
+ CTuple cs => List.nth (cs, n - 1)
+ | _ => (CProj (c, n), loc)
+ end)
+
+ fun patCon pc =
+ case pc of
+ PConVar _ => pc
+ | PConFfi {mod = m, datatyp, params, con = c, arg, kind} =>
+ PConFfi {mod = m, datatyp = datatyp, params = params, con = c,
+ arg = Option.map (con (map (fn _ => UnknownC) params)) arg,
+ kind = kind}
+
+
+ val k = (KType, ErrorMsg.dummySpan)
+ fun doPart e (this as (x, t), rest) =
+ ((x, (EField (e, x, {field = t, rest = (CRecord (k, rest), #2 t)}), #2 t), t),
+ this :: rest)
+
+ fun exp env (all as (e, loc)) =
+ let
+ (*val () = Print.prefaces "exp" [("e", CorePrint.p_exp CoreEnv.empty all),
+ ("env", Print.PD.string (e2s env))]*)
+ (*val () = if dangling (edepth env) all then
+ (Print.prefaces "exp" [("e", CorePrint.p_exp CoreEnv.empty all),
+ ("env", Print.PD.string (e2s env))];
+ raise Fail "!")
+ else
+ ()*)
+ (*val () = if cdangling (cdepth env) all then
+ Print.prefaces "Bad exp" [("e", CorePrint.p_exp CoreEnv.empty all),
+ ("env", Print.PD.string (e2s env))]
+ else
+ ()*)
+
+ fun patBinds (p, _) =
+ case p of
+ PVar _ => 1
+ | PPrim _ => 0
+ | PCon (_, _, _, NONE) => 0
+ | PCon (_, _, _, SOME p) => patBinds p
+ | PRecord xpts => foldl (fn ((_, p, _), n) => n + patBinds p) 0 xpts
+
+ val r = case e of
+ EPrim _ => all
+ | ERel n =>
+ let
+ fun find (n', env, nudge, liftK, liftC, liftE) =
+ case env of
+ [] => raise Fail ("Reduce.exp: ERel (" ^ ErrorMsg.spanToString loc ^ ")")
+ | UnknownK :: rest => find (n', rest, nudge, liftK + 1, liftC, liftE)
+ | KnownK _ :: rest => find (n', rest, nudge, liftK, liftC, liftE)
+ | UnknownC :: rest => find (n', rest, nudge, liftK, liftC + 1, liftE)
+ | KnownC _ :: rest => find (n', rest, nudge, liftK, liftC, liftE)
+ | Lift (liftK', liftC', liftE') :: rest =>
+ find (n', rest, nudge + liftE',
+ liftK + liftK', liftC + liftC', liftE + liftE')
+ | UnknownE :: rest =>
+ if n' = 0 then
+ (ERel (n + nudge), loc)
+ else
+ find (n' - 1, rest, nudge, liftK, liftC, liftE + 1)
+ | KnownE e :: rest =>
+ if n' = 0 then
+ ((*print "SUBSTITUTING\n";*)
+ exp (Lift (liftK, liftC, liftE) :: rest) e)
+ else
+ find (n' - 1, rest, nudge - 1, liftK, liftC, liftE)
+ in
+ (*print (Int.toString n ^ ": " ^ e2s env ^ "\n");*)
+ find (n, env, 0, 0, 0, 0)
+ end
+ | ENamed n =>
+ (case IM.find (namedE, n) of
+ NONE => all
+ | SOME e => e)
+ | ECon (dk, pc, cs, eo) => (ECon (dk, patCon pc,
+ map (con env) cs, Option.map (exp env) eo), loc)
+
+ | EFfi ("Basis", "return") =>
+ (ECAbs ("m", (KArrow ((KType, loc), (KType, loc)), loc),
+ (ECAbs ("a", (KType, loc),
+ (EAbs ("m", monadRecord (CRel 1, loc) loc, returnType (CRel 1, loc) loc,
+ (ECApp ((EField ((ERel 0, loc), (CName "Return", loc),
+ {field = returnType (CRel 1, loc) loc,
+ rest = (CRecord ((KType, loc),
+ [((CName "Bind", loc), bindType (CRel 1, loc) loc)]),
+ loc)}), loc), (CRel 0, loc)), loc)), loc)), loc)), loc)
+
+ | EFfi ("Basis", "bind") =>
+ (ECAbs ("m", (KArrow ((KType, loc), (KType, loc)), loc),
+ (ECAbs ("a", (KType, loc),
+ (ECAbs ("b", (KType, loc),
+ (EAbs ("m", monadRecord (CRel 2, loc) loc, bindType (CRel 2, loc) loc,
+ (ECApp ((ECApp ((EField ((ERel 0, loc), (CName "Bind", loc),
+ {field = bindType (CRel 2, loc) loc,
+ rest = (CRecord ((KType, loc),
+ [((CName "Return", loc),
+ returnType (CRel 2, loc) loc)]),
+ loc)}), loc), (CRel 1, loc)), loc),
+ (CRel 0, loc)), loc)), loc)), loc)), loc)), loc)
+
+ | EFfi ("Basis", "mkMonad") =>
+ (ECAbs ("m", (KArrow ((KType, loc), (KType, loc)), loc),
+ (EAbs ("m", monadRecord (CRel 0, loc) loc, monadRecord (CRel 0, loc) loc,
+ (ERel 0, loc)), loc)), loc)
+
+ | EFfi ("Basis", "transaction_monad") =>
+ (ERecord [((CName "Return", loc),
+ (EFfi ("Basis", "transaction_return"), loc),
+ returnType (CFfi ("Basis", "transaction"), loc) loc),
+ ((CName "Bind", loc),
+ (EFfi ("Basis", "transaction_bind"), loc),
+ bindType (CFfi ("Basis", "transaction"), loc) loc)], loc)
+
+ | EFfi ("Basis", "signal_monad") =>
+ (ERecord [((CName "Return", loc),
+ (EFfi ("Basis", "signal_return"), loc),
+ returnType (CFfi ("Basis", "signal"), loc) loc),
+ ((CName "Bind", loc),
+ (EFfi ("Basis", "signal_bind"), loc),
+ bindType (CFfi ("Basis", "signal"), loc) loc)], loc)
+
+ | EFfi _ => all
+ | EFfiApp (m, f, es) => (EFfiApp (m, f, map (fn (e, t) => (exp env e, con env t)) es), loc)
+
+ (*| EApp (
+ (EApp
+ ((EApp ((ECApp ((ECApp ((ECApp ((EFfi ("Basis", "bind"), loc), _), _), t1), _), t2), _),
+ (EFfi ("Basis", "transaction_monad"), _)), _),
+ (ECase (ed, pes, {disc, ...}), _)), _),
+ trans2) =>
+ let
+ val e' = (EFfi ("Basis", "bind"), loc)
+ val e' = (ECApp (e', (CFfi ("Basis", "transaction"), loc)), loc)
+ val e' = (ECApp (e', t1), loc)
+ val e' = (ECApp (e', t2), loc)
+ val e' = (EApp (e', (EFfi ("Basis", "transaction_monad"), loc)), loc)
+
+ val pes = map (fn (p, e) =>
+ let
+ val e' = (EApp (e', e), loc)
+ val e' = (EApp (e',
+ multiLiftExpInExp (E.patBindsN p)
+ trans2), loc)
+ val e' = exp env e'
+ in
+ (p, e')
+ end) pes
+ in
+ (ECase (exp env ed,
+ pes,
+ {disc = con env disc,
+ result = (CApp ((CFfi ("Basis", "transaction"), loc), con env t2), loc)}),
+ loc)
+ end*)
+
+ | EApp (e1, e2) =>
+ let
+ val env' = deKnown env
+
+ val e1 = exp env e1
+ val e2 = exp env e2
+ in
+ case #1 e1 of
+ ELet (x, t, e1', e2') =>
+ (ELet (x, t, e1', exp (UnknownE :: env') (EApp (e2', E.liftExpInExp 0 e2), loc)), loc)
+
+ | EAbs (x, dom, _, b) =>
+ if count b <= 1 orelse passive e2 orelse ESpecialize.functionInside IS.empty dom then
+ let
+ val r = exp (KnownE e2 :: env') b
+ in
+ (*Print.prefaces "eapp" [("b", CorePrint.p_exp CoreEnv.empty b),
+ ("env", Print.PD.string (e2s env')),
+ ("e2", CorePrint.p_exp CoreEnv.empty e2),
+ ("r", CorePrint.p_exp CoreEnv.empty r)];*)
+ r
+ end
+ else
+ let
+ val dom = con env' dom
+ val r = exp (UnknownE :: env') b
+ in
+ (*Print.prefaces "El skippo" [("x", Print.PD.string x),
+ ("e2", CorePrint.p_exp CoreEnv.empty e2)];*)
+ (ELet (x, dom, e2, r), loc)
+ end
+
+ | ECase (e, pes, cc as {disc, result = res as (TFun (_, c2), _)}) =>
+ let
+ val pes' = map (fn (p, body) =>
+ let
+ val env' = List.tabulate (patBinds p, fn _ => UnknownE) @ deKnown env
+ val body' = exp env' (EApp (body, multiLiftExpInExp (patBinds p) e2), #2 body)
+ in
+ (p, body')
+ end) pes
+
+ val cc' = {disc = con env' disc, result = con env' c2}
+ in
+ (ECase (e, pes', cc'), loc)
+ end
+ | _ => (EApp (e1, e2), loc)
+ end
+
+ | EAbs (x, dom, ran, e) => (EAbs (x, con env dom, con env ran, exp (UnknownE :: env) e), loc)
+
+ | ECApp (e, c) =>
+ let
+ val e = exp env e
+ val c = con env c
+ in
+ case #1 e of
+ ECAbs (_, _, b) =>
+ let
+ val r = exp (KnownC c :: deKnown env) b
+ in
+ (*Print.prefaces "csub" [("l", Print.PD.string (ErrorMsg.spanToString loc)),
+ ("env", Print.PD.string (e2s (deKnown env))),
+ ("b", CorePrint.p_exp CoreEnv.empty b),
+ ("c", CorePrint.p_con CoreEnv.empty c),
+ ("r", CorePrint.p_exp CoreEnv.empty r)];*)
+ r
+ end
+ | ECase (e, pes, cc as {disc, result = res as (TCFun (_, _, c'), _)}) =>
+ let
+ val pes' = map (fn (p, body) =>
+ let
+ val env' = List.tabulate (patBinds p, fn _ => UnknownE) @ deKnown env
+
+ val body' = exp env' (ECApp (body, c), #2 body)
+ in
+ (p, body')
+ end) pes
+
+ val c' = E.subConInCon (0, c) c'
+ val cc' = {disc = con env disc, result = con env c'}
+ in
+ (ECase (e, pes', cc'), loc)
+ end
+ | _ => (ECApp (e, c), loc)
+ end
+
+ | ECAbs (x, k, e) => (ECAbs (x, kind env k, exp (UnknownC :: env) e), loc)
+
+ | EKApp (e, k) =>
+ let
+ val e = exp env e
+ in
+ case #1 e of
+ EKAbs (_, b) =>
+ let
+ val r = exp (KnownK k :: deKnown env) b
+ in
+ (*Print.prefaces "ksub" [("l", Print.PD.string (ErrorMsg.spanToString loc)),
+ ("b", CorePrint.p_exp CoreEnv.empty b),
+ ("k", CorePrint.p_kind CoreEnv.empty k),
+ ("r", CorePrint.p_exp CoreEnv.empty r)];*)
+ r
+ end
+ | _ => (EKApp (e, kind env k), loc)
+ end
+
+ | EKAbs (x, e) => (EKAbs (x, exp (UnknownK :: env) e), loc)
+
+ | ERecord xcs => (ERecord (map (fn (x, e, t) => (con env x, exp env e, con env t)) xcs), loc)
+ | EField (e, c, {field, rest}) =>
+ let
+ val e = exp env e
+ val c = con env c
+
+ fun default () = (EField (e, c, {field = con env field, rest = con env rest}), loc)
+ in
+ case (#1 e, #1 c) of
+ (ERecord xcs, CName x) =>
+ (case List.find (fn ((CName x', _), _, _) => x' = x | _ => false) xcs of
+ NONE => default ()
+ | SOME (_, e, _) => e)
+ | _ => default ()
+ end
+
+ | EConcat (e1, c1, e2, c2) =>
+ let
+ val e1 = exp env e1
+ val e2 = exp env e2
+ in
+ case (#1 e1, #1 e2) of
+ (ERecord xes1, ERecord xes2) => (ERecord (xes1 @ xes2), loc)
+ | _ =>
+ let
+ val c1 = con env c1
+ val c2 = con env c2
+ in
+ case (#1 c1, #1 c2) of
+ (CRecord (k, xcs1), CRecord (_, xcs2)) =>
+ let
+ val (xes1, rest) = ListUtil.foldlMap (doPart e1) [] xcs1
+ val (xes2, _) = ListUtil.foldlMap (doPart e2) rest xcs2
+ in
+ exp (deKnown env) (ERecord (xes1 @ xes2), loc)
+ end
+ | _ => (EConcat (e1, c1, e2, c2), loc)
+ end
+ end
+
+ | ECut (e, c, {field, rest}) =>
+ let
+ val e = exp env e
+ val c = con env c
+
+ fun default () =
+ let
+ val rest = con env rest
+ in
+ case #1 rest of
+ CRecord (k, xcs) =>
+ let
+ val (xes, _) = ListUtil.foldlMap (doPart e) [] xcs
+ in
+ exp (deKnown env) (ERecord xes, loc)
+ end
+ | _ => (ECut (e, c, {field = con env field, rest = rest}), loc)
+ end
+ in
+ case (#1 e, #1 c) of
+ (ERecord xes, CName x) =>
+ if List.all (fn ((CName _, _), _, _) => true | _ => false) xes then
+ (ERecord (List.filter (fn ((CName x', _), _, _) => x' <> x
+ | _ => raise Fail "Reduce: ECut") xes), loc)
+ else
+ default ()
+ | _ => default ()
+ end
+
+ | ECutMulti (e, c, {rest}) =>
+ let
+ val e = exp env e
+ val c = con env c
+
+ fun default () =
+ let
+ val rest = con env rest
+ in
+ case #1 rest of
+ CRecord (k, xcs) =>
+ let
+ val (xes, _) = ListUtil.foldlMap (doPart e) [] xcs
+ in
+ exp (deKnown env) (ERecord xes, loc)
+ end
+ | _ => (ECutMulti (e, c, {rest = rest}), loc)
+ end
+ in
+ case (#1 e, #1 c) of
+ (ERecord xes, CRecord (_, xcs)) =>
+ if List.all (fn ((CName _, _), _, _) => true | _ => false) xes
+ andalso List.all (fn ((CName _, _), _) => true | _ => false) xcs then
+ (ERecord (List.filter (fn ((CName x', _), _, _) =>
+ List.all (fn ((CName x, _), _) => x' <> x
+ | _ => raise Fail "Reduce: ECutMulti [1]") xcs
+ | _ => raise Fail "Reduce: ECutMulti [2]") xes), loc)
+ else
+ default ()
+ | _ => default ()
+ end
+
+ | ECase (_, [((PRecord [], _), e)], _) => exp env e
+
+ | ECase (e, pes, {disc, result}) =>
+ let
+ fun pat (all as (p, loc)) =
+ case p of
+ PVar (x, t) => (PVar (x, con env t), loc)
+ | PPrim _ => all
+ | PCon (dk, pc, cs, po) =>
+ (PCon (dk, patCon pc, map (con env) cs, Option.map pat po), loc)
+ | PRecord xpts => (PRecord (map (fn (x, p, t) => (x, pat p, con env t)) xpts), loc)
+
+ fun push () =
+ (ECase (exp env e,
+ map (fn (p, e) => (pat p,
+ exp (List.tabulate (patBinds p,
+ fn _ => UnknownE) @ env) e))
+ pes, {disc = con env disc, result = con env result}), loc)
+
+ fun search pes =
+ case pes of
+ [] => push ()
+ | (p, body) :: pes =>
+ case match (env, p, e) of
+ No => search pes
+ | Maybe => push ()
+ | Yes env' => exp env' body
+ in
+ search pes
+ end
+
+ | EWrite e => (EWrite (exp env e), loc)
+ | EClosure (n, es) => (EClosure (n, map (exp env) es), loc)
+
+ | ELet (x, t, e1, e2) =>
+ let
+ val e1' = exp env e1
+
+ val t = con env t
+ in
+ if notFfi t andalso (passive e1' orelse count e2 <= 1 orelse ESpecialize.functionInside IS.empty t) then
+ exp (KnownE e1 :: env) e2
+ else
+ (ELet (x, t, e1', exp (UnknownE :: env) e2), loc)
+ end
+
+ | EServerCall (n, es, t, fm) => (EServerCall (n, map (exp env) es, con env t, fm), loc)
+ in
+ (*if dangling (edepth' (deKnown env)) r then
+ (Print.prefaces "exp" [("e", CorePrint.p_exp CoreEnv.empty all),
+ ("r", CorePrint.p_exp CoreEnv.empty r)];
+ raise Fail "!!")
+ else
+ ();*)
+ (*if cdangling (cdepth' (deKnown env)) r then
+ (Print.prefaces "exp" [("e", CorePrint.p_exp CoreEnv.empty all),
+ ("r", CorePrint.p_exp CoreEnv.empty r)];
+ raise Fail "!!")
+ else
+ ();*)
+ r
+ end
+ in
+ {kind = kind, con = con, exp = exp}
+ end
+
+fun kind namedC env k = #kind (kindConAndExp (namedC, IM.empty)) env k
+fun con namedC env c = #con (kindConAndExp (namedC, IM.empty)) env c
+fun exp (namedC, namedE) env e = #exp (kindConAndExp (namedC, namedE)) env e
+
+fun reduce file =
+ let
+ val uses = CoreUtil.File.fold {kind = fn (_, m) => m,
+ con = fn (_, m) => m,
+ exp = fn (e, m) =>
+ case e of
+ ENamed n => IM.insert (m, n, 1 + Option.getOpt (IM.find (m, n), 0))
+ | _ => m,
+ decl = fn (_, m) => m}
+ IM.empty file
+
+ fun isPoly names = CoreUtil.Con.exists {kind = fn _ => false,
+ con = fn TCFun _ => true
+ | TKFun _ => true
+ | CNamed n => IS.member (names, n)
+ | _ => false}
+
+ val size = CoreUtil.Exp.fold {kind = fn (_, n) => n,
+ con = fn (_, n) => n,
+ exp = fn (_, n) => n + 1} 0
+
+ fun mayInline (polyC, n, t, e, s) =
+ let
+ fun isPolicy t =
+ case #1 t of
+ CFfi ("Basis", "sql_policy") => true
+ | TFun (_, t) => isPolicy t
+ | _ => false
+ in
+ not (Settings.checkNeverInline s) andalso
+ case IM.find (uses, n) of
+ NONE => false
+ | SOME count => count <= 1
+ orelse (case #1 e of
+ ERecord _ => true
+ | _ => false)
+ orelse isPolicy t
+ orelse isPoly polyC t
+ orelse size e <= Settings.getCoreInline ()
+ end
+
+ fun doDecl (d as (_, loc), st as (polyC, namedC, namedE)) =
+ case #1 d of
+ DCon (x, n, k, c) =>
+ let
+ val k = kind namedC [] k
+ val c = con namedC [] c
+ in
+ ((DCon (x, n, k, c), loc),
+ (if isPoly polyC c then
+ IS.add (polyC, n)
+ else
+ polyC,
+ IM.insert (namedC, n, c),
+ namedE))
+ end
+ | DDatatype dts =>
+ ((DDatatype (map (fn (x, n, ps, cs) =>
+ let
+ val env = map (fn _ => UnknownC) ps
+ in
+ (x, n, ps, map (fn (x, n, co) => (x, n, Option.map (con namedC env) co)) cs)
+ end) dts), loc),
+ (if List.exists (fn (_, _, _, cs) => List.exists (fn (_, _, co) => case co of
+ NONE => false
+ | SOME c => isPoly polyC c) cs)
+ dts then
+ foldl (fn ((_, n, _, _), polyC) => IS.add (polyC, n)) polyC dts
+ else
+ polyC,
+ namedC,
+ namedE))
+ | DVal (x, n, t, e, s) =>
+ let
+ val t = con namedC [] t
+ val e = exp (namedC, namedE) [] e
+ in
+ ((DVal (x, n, t, e, s), loc),
+ (polyC,
+ namedC,
+ if mayInline (polyC, n, t, e, s) then
+ IM.insert (namedE, n, e)
+ else
+ namedE))
+ end
+ | DValRec vis =>
+ ((DValRec (map (fn (x, n, t, e, s) => (x, n, con namedC [] t,
+ exp (namedC, namedE) [] e, s)) vis), loc),
+ st)
+ | DExport _ => (d, st)
+ | DTable (s, n, c, s', pe, pc, ce, cc) => ((DTable (s, n, con namedC [] c, s',
+ exp (namedC, namedE) [] pe,
+ con namedC [] pc,
+ exp (namedC, namedE) [] ce,
+ con namedC [] cc), loc), st)
+ | DSequence _ => (d, st)
+ | DView (s, n, s', e, c) => ((DView (s, n, s', exp (namedC, namedE) [] e, con namedC [] c), loc), st)
+ | DDatabase _ => (d, st)
+ | DCookie (s, n, c, s') => ((DCookie (s, n, con namedC [] c, s'), loc), st)
+ | DStyle (s, n, s') => ((DStyle (s, n, s'), loc), st)
+ | DTask (e1, e2) =>
+ let
+ val e1 = exp (namedC, namedE) [] e1
+ val e2 = exp (namedC, namedE) [] e2
+ in
+ ((DTask (e1, e2), loc),
+ (polyC,
+ namedC,
+ namedE))
+ end
+ | DPolicy e1 =>
+ let
+ val e1 = exp (namedC, namedE) [] e1
+ in
+ ((DPolicy e1, loc),
+ (polyC,
+ namedC,
+ namedE))
+ end
+ | DOnError _ => (d, st)
+
+ val (file, _) = ListUtil.foldlMap doDecl (IS.empty, IM.empty, IM.empty) file
+ in
+ file
+ end
+
+end
diff --git a/src/reduce_local.sig b/src/reduce_local.sig
new file mode 100644
index 0000000..ebc22c5
--- /dev/null
+++ b/src/reduce_local.sig
@@ -0,0 +1,36 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+(* Simplify a Core program algebraically, without unfolding definitions *)
+
+signature REDUCE_LOCAL = sig
+
+ val reduce : Core.file -> Core.file
+ val reduceExp : Core.exp -> Core.exp
+ val reduceCon : Core.con -> Core.con
+
+end
diff --git a/src/reduce_local.sml b/src/reduce_local.sml
new file mode 100644
index 0000000..06f49fe
--- /dev/null
+++ b/src/reduce_local.sml
@@ -0,0 +1,386 @@
+(* Copyright (c) 2008-2010, 2013, 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.
+ *)
+
+(* Simplify a Core program algebraically, without unfolding definitions *)
+
+structure ReduceLocal :> REDUCE_LOCAL = struct
+
+open Core
+
+structure IM = IntBinaryMap
+
+fun multiLiftExpInExp n e =
+ if n = 0 then
+ e
+ else
+ multiLiftExpInExp (n - 1) (CoreEnv.liftExpInExp 0 e)
+
+datatype env_item =
+ Unknown
+ | Known of exp
+
+ | UnknownC
+ | KnownC of con
+
+ | Lift of int * int
+
+type env = env_item list
+
+val deKnown = List.filter (fn Known _ => false
+ | KnownC _ => false
+ | _ => true)
+
+datatype result = Yes of env | No | Maybe
+
+fun match (env, p : pat, e : exp) =
+ let
+ val baseline = length env
+
+ fun match (env, p, e) =
+ case (#1 p, #1 e) of
+ (PVar (x, t), _) => Yes (Known (multiLiftExpInExp (length env - baseline) e) :: env)
+
+ | (PPrim p, EPrim p') =>
+ if Prim.equal (p, p') then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConVar n1, _, NONE), ECon (_, PConVar n2, _, NONE)) =>
+ if n1 = n2 then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConVar n1, _, SOME p), ECon (_, PConVar n2, _, SOME e)) =>
+ if n1 = n2 then
+ match (env, p, e)
+ else
+ No
+
+ | (PCon (_, PConFfi {mod = m1, con = con1, ...}, _, NONE),
+ ECon (_, PConFfi {mod = m2, con = con2, ...}, _, NONE)) =>
+ if m1 = m2 andalso con1 = con2 then
+ Yes env
+ else
+ No
+
+ | (PCon (_, PConFfi {mod = m1, con = con1, ...}, _, SOME ep),
+ ECon (_, PConFfi {mod = m2, con = con2, ...}, _, SOME e)) =>
+ if m1 = m2 andalso con1 = con2 then
+ match (env, p, e)
+ else
+ No
+
+ | (PRecord xps, ERecord xes) =>
+ if List.exists (fn ((CName _, _), _, _) => false
+ | _ => true) xes then
+ Maybe
+ else
+ let
+ fun consider (xps, env) =
+ case xps of
+ [] => Yes env
+ | (x, p, _) :: rest =>
+ case List.find (fn ((CName x', _), _, _) => x' = x
+ | _ => false) xes of
+ NONE => No
+ | SOME (_, e, _) =>
+ case match (env, p, e) of
+ No => No
+ | Maybe => Maybe
+ | Yes env => consider (rest, env)
+ in
+ consider (xps, env)
+ end
+
+ | _ => Maybe
+ in
+ match (env, p, e)
+ end
+
+fun con env (all as (c, loc)) =
+ ((*Print.prefaces "con" [("c", CorePrint.p_con CoreEnv.empty all)];*)
+ case c of
+ TFun (c1, c2) => (TFun (con env c1, con env c2), loc)
+ | TCFun (x, k, c2) => (TCFun (x, k, con (UnknownC :: env) c2), loc)
+ | TKFun (x, c2) => (TKFun (x, con env c2), loc)
+ | TRecord c => (TRecord (con env c), loc)
+
+ | CRel n =>
+ let
+ fun find (n', env, nudge, liftC) =
+ case env of
+ [] => raise Fail "ReduceLocal.con: CRel"
+ | Unknown :: rest => find (n', rest, nudge, liftC)
+ | Known _ :: rest => find (n', rest, nudge, liftC)
+ | Lift (liftC', _) :: rest => find (n', rest, nudge + liftC',
+ liftC + liftC')
+ | UnknownC :: rest =>
+ if n' = 0 then
+ (CRel (n + nudge), loc)
+ else
+ find (n' - 1, rest, nudge, liftC + 1)
+ | KnownC c :: rest =>
+ if n' = 0 then
+ con (Lift (liftC, 0) :: rest) c
+ else
+ find (n' - 1, rest, nudge - 1, liftC)
+ in
+ (*print (Int.toString n ^ ": " ^ e2s env ^ "\n");*)
+ find (n, env, 0, 0)
+ end
+ | CNamed _ => all
+ | CFfi _ => all
+ | CApp (c1, c2) =>
+ let
+ val c1 = con env c1
+ val c2 = con env c2
+ in
+ case #1 c1 of
+ CAbs (_, _, b) =>
+ con (KnownC c2 :: deKnown env) b
+
+ | CApp ((CMap (dom, ran), _), f) =>
+ (case #1 c2 of
+ CRecord (_, []) => (CRecord (ran, []), loc)
+ | CRecord (_, (x, c) :: rest) =>
+ con (deKnown env)
+ (CConcat ((CRecord (ran, [(x, (CApp (f, c), loc))]), loc),
+ (CApp (c1, (CRecord (dom, rest), loc)), loc)), loc)
+ | _ => (CApp (c1, c2), loc))
+
+ | _ => (CApp (c1, c2), loc)
+ end
+ | CAbs (x, k, b) => (CAbs (x, k, con (UnknownC :: env) b), loc)
+
+ | CKApp (c1, k) =>
+ let
+ val c1 = con env c1
+ in
+ case #1 c1 of
+ CKAbs (_, b) =>
+ con (deKnown env) b
+
+ | _ => (CKApp (c1, k), loc)
+ end
+ | CKAbs (x, b) => (CKAbs (x, con env b), loc)
+
+ | CName _ => all
+
+ | CRecord (k, xcs) => (CRecord (k, map (fn (x, c) => (con env x, con env c)) xcs), loc)
+ | CConcat (c1, c2) =>
+ let
+ val c1 = con env c1
+ val c2 = con env c2
+ in
+ case (#1 c1, #1 c2) of
+ (CRecord (k, xcs1), CRecord (_, xcs2)) =>
+ (CRecord (k, xcs1 @ xcs2), loc)
+ | (CRecord (_, []), _) => c2
+ | (_, CRecord (_, [])) => c1
+ | _ => (CConcat (c1, c2), loc)
+ end
+ | CMap _ => all
+
+ | CUnit => all
+
+ | CTuple cs => (CTuple (map (con env) cs), loc)
+ | CProj (c, n) =>
+ let
+ val c = con env c
+ in
+ case #1 c of
+ CTuple cs => List.nth (cs, n - 1)
+ | _ => (CProj (c, n), loc)
+ end)
+
+fun patCon pc =
+ case pc of
+ PConVar _ => pc
+ | PConFfi {mod = m, datatyp, params, con = c, arg, kind} =>
+ PConFfi {mod = m, datatyp = datatyp, params = params, con = c,
+ arg = Option.map (con (map (fn _ => UnknownC) params)) arg,
+ kind = kind}
+
+fun exp env (all as (e, loc)) =
+ case e of
+ EPrim _ => all
+ | ERel n =>
+ let
+ fun find (n', env, nudge, liftC, liftE) =
+ case env of
+ [] => (ERel (n + nudge), loc)
+ | Lift (liftC', liftE') :: rest => find (n', rest, nudge + liftE', liftC + liftC', liftE + liftE')
+ | UnknownC :: rest => find (n', rest, nudge, liftC + 1, liftE)
+ | KnownC _ :: rest => find (n', rest, nudge, liftC, liftE)
+ | Unknown :: rest =>
+ if n' = 0 then
+ (ERel (n + nudge), loc)
+ else
+ find (n' - 1, rest, nudge, liftC, liftE + 1)
+ | Known e :: rest =>
+ if n' = 0 then
+ ((*print "SUBSTITUTING\n";*)
+ exp (Lift (liftC, liftE) :: rest) e)
+ else
+ find (n' - 1, rest, nudge - 1, liftC, liftE)
+ in
+ find (n, env, 0, 0, 0)
+ end
+ | ENamed _ => all
+ | ECon (dk, pc, cs, eo) => (ECon (dk, patCon pc, map (con env) cs, Option.map (exp env) eo), loc)
+ | EFfi _ => all
+ | EFfiApp (m, f, es) => (EFfiApp (m, f, map (fn (e, t) => (exp env e, con env t)) es), loc)
+
+ | EApp (e1, e2) =>
+ let
+ val e1 = exp env e1
+ val e2 = exp env e2
+ in
+ case #1 e1 of
+ EAbs (_, _, _, b) => exp (Known e2 :: deKnown env) b
+ | _ => (EApp (e1, e2), loc)
+ end
+
+ | EAbs (x, dom, ran, e) => (EAbs (x, con env dom, con env ran, exp (Unknown :: env) e), loc)
+
+ | ECApp (e, c) =>
+ let
+ val e = exp env e
+ val c = con env c
+ in
+ case #1 e of
+ ECAbs (_, _, b) => exp (KnownC c :: deKnown env) b
+ | _ => (ECApp (e, c), loc)
+ end
+
+ | ECAbs (x, k, e) => (ECAbs (x, k, exp (UnknownC :: env) e), loc)
+
+ | EKApp (e, k) => (EKApp (exp env e, k), loc)
+ | EKAbs (x, e) => (EKAbs (x, exp env e), loc)
+
+ | ERecord xcs => (ERecord (map (fn (x, e, t) => (con env x, exp env e, con env t)) xcs), loc)
+ | EField (e, c, {field = f, rest = r}) =>
+ let
+ val e = exp env e
+ val c = con env c
+
+ fun default () = (EField (e, c, {field = con env f, rest = con env r}), loc)
+ in
+ case (#1 e, #1 c) of
+ (ERecord xcs, CName x) =>
+ (case List.find (fn ((CName x', _), _, _) => x' = x | _ => false) xcs of
+ NONE => default ()
+ | SOME (_, e, _) => e)
+ | _ => default ()
+ end
+
+ | EConcat (e1, c1, e2, c2) => (EConcat (exp env e1, con env c1, exp env e2, con env c2), loc)
+ | ECut (e, c, {field = f, rest = r}) => (ECut (exp env e,
+ con env c,
+ {field = con env f, rest = con env r}), loc)
+ | ECutMulti (e, c, {rest = r}) => (ECutMulti (exp env e, con env c, {rest = con env r}), loc)
+
+ | ECase (e, pes, {disc = d, result = r}) =>
+ let
+ val others = {disc = con env d, result = con env r}
+
+ fun patBinds (p, _) =
+ case p of
+ PVar _ => 1
+ | PPrim _ => 0
+ | PCon (_, _, _, NONE) => 0
+ | PCon (_, _, _, SOME p) => patBinds p
+ | PRecord xpts => foldl (fn ((_, p, _), n) => n + patBinds p) 0 xpts
+
+ fun pat (all as (p, loc)) =
+ case p of
+ PVar (x, t) => (PVar (x, con env t), loc)
+ | PPrim _ => all
+ | PCon (dk, pc, cs, po) =>
+ (PCon (dk, patCon pc, map (con env) cs, Option.map pat po), loc)
+ | PRecord xpts => (PRecord (map (fn (x, p, t) => (x, pat p, con env t)) xpts), loc)
+
+ fun push () =
+ (ECase (exp env e,
+ map (fn (p, e) => (pat p,
+ exp (List.tabulate (patBinds p,
+ fn _ => Unknown) @ env) e))
+ pes, others), loc)
+
+ fun search pes =
+ case pes of
+ [] => push ()
+ | (p, body) :: pes =>
+ case match (env, p, e) of
+ No => search pes
+ | Maybe => push ()
+ | Yes env' => exp env' body
+ in
+ search pes
+ end
+
+ | EWrite e => (EWrite (exp env e), loc)
+ | EClosure (n, es) => (EClosure (n, map (exp env) es), loc)
+
+ | ELet (x, t, e1, e2) => (ELet (x, con env t, exp env e1, exp (Unknown :: env) e2), loc)
+
+ | EServerCall (n, es, t, fm) => (EServerCall (n, map (exp env) es, con env t, fm), loc)
+
+fun reduce file =
+ let
+ fun doDecl (d as (_, loc)) =
+ case #1 d of
+ DCon _ => d
+ | DDatatype _ => d
+ | DVal (x, n, t, e, s) =>
+ let
+ val e = exp [] e
+ in
+ (DVal (x, n, t, e, s), loc)
+ end
+ | DValRec vis =>
+ (DValRec (map (fn (x, n, t, e, s) => (x, n, t, exp [] e, s)) vis), loc)
+ | DExport _ => d
+ | DTable _ => d
+ | DSequence _ => d
+ | DView _ => d
+ | DDatabase _ => d
+ | DCookie _ => d
+ | DStyle _ => d
+ | DTask (e1, e2) => (DTask (exp [] e1, exp [] e2), loc)
+ | DPolicy e1 => (DPolicy (exp [] e1), loc)
+ | DOnError _ => d
+ in
+ map doDecl file
+ end
+
+val reduceExp = exp []
+val reduceCon = con []
+
+end
diff --git a/src/rpcify.sig b/src/rpcify.sig
new file mode 100644
index 0000000..7da53b7
--- /dev/null
+++ b/src/rpcify.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+signature RPCIFY = sig
+
+ val frob : Core.file -> Core.file
+
+end
diff --git a/src/rpcify.sml b/src/rpcify.sml
new file mode 100644
index 0000000..551a151
--- /dev/null
+++ b/src/rpcify.sml
@@ -0,0 +1,168 @@
+(* Copyright (c) 2009, 2012-2013, 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 Rpcify :> RPCIFY = struct
+
+open Core
+
+structure U = CoreUtil
+structure E = CoreEnv
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+type state = {
+ exported : IS.set,
+ export_decls : decl list
+}
+
+fun frob file =
+ let
+ val (rpcBaseIds, trpcBaseIds) =
+ foldl (fn ((d, _), (rpcIds, trpcIds)) =>
+ case d of
+ DVal (_, n, _, (EFfi ("Basis", "rpc"), _), _) =>
+ (IS.add (rpcIds, n), trpcIds)
+ | DVal (_, n, _, (EFfi ("Basis", "tryRpc"), _), _) =>
+ (rpcIds, IS.add (trpcIds, n))
+ | DVal (_, n, _, (ENamed n', _), _) =>
+ if IS.member (rpcIds, n') then
+ (IS.add (rpcIds, n), trpcIds)
+ else if IS.member (trpcIds, n') then
+ (rpcIds, IS.add (trpcIds, n))
+ else
+ (rpcIds, trpcIds)
+ | _ => (rpcIds, trpcIds))
+ (IS.empty, IS.empty) file
+
+ val tfuncs = foldl
+ (fn ((d, _), tfuncs) =>
+ let
+ fun doOne ((x, n, t, e, _), tfuncs) =
+ let
+ val loc = #2 e
+
+ fun crawl (t, e, args) =
+ case (#1 t, #1 e) of
+ (CApp (_, ran), _) =>
+ SOME (x, rev args, ran, e)
+ | (TFun (arg, rest), EAbs (x, _, _, e)) =>
+ crawl (rest, e, (x, arg) :: args)
+ | (TFun (arg, rest), _) =>
+ crawl (rest, (EApp (e, (ERel (length args), loc)), loc), ("x", arg) :: args)
+ | _ => NONE
+ in
+ case crawl (t, e, []) of
+ NONE => tfuncs
+ | SOME sg => IM.insert (tfuncs, n, sg)
+ end
+ in
+ case d of
+ DVal vi => doOne (vi, tfuncs)
+ | DValRec vis => foldl doOne tfuncs vis
+ | _ => tfuncs
+ end)
+ IM.empty file
+
+ fun exp (e, st) =
+ let
+ fun getApp (e', args) =
+ case e' of
+ ENamed n => SOME (n, args)
+ | EApp (e1, e2) => getApp (#1 e1, e2 :: args)
+ | _ => NONE
+
+ fun newRpc (trans : exp, st : state, fm) =
+ case getApp (#1 trans, []) of
+ NONE => (ErrorMsg.errorAt (#2 trans)
+ "RPC code doesn't use a named function or transaction";
+ (*Print.preface ("Expression",
+ CorePrint.p_exp CoreEnv.empty trans);*)
+ (#1 trans, st))
+ | SOME (n, args) =>
+ case IM.find (tfuncs, n) of
+ NONE => ((*Print.prefaces "BAD" [("e", CorePrint.p_exp CoreEnv.empty (e, loc))];*)
+ raise Fail ("Rpcify: Undetected transaction function " ^ Int.toString n))
+ | SOME (_, _, ran, _) =>
+ let
+ val loc = #2 trans
+
+ val (exported, export_decls) =
+ if IS.member (#exported st, n) then
+ (#exported st, #export_decls st)
+ else
+ (IS.add (#exported st, n),
+ (DExport (Rpc ReadWrite, n, false), loc) :: #export_decls st)
+
+ val st = {exported = exported,
+ export_decls = export_decls}
+
+ val e' = EServerCall (n, args, ran, fm)
+ in
+ (e', st)
+ end
+ in
+ case e of
+ EApp ((ECApp ((EFfi ("Basis", "rpc"), _), ran), _), trans) => newRpc (trans, st, None)
+ | EApp ((ECApp ((EFfi ("Basis", "tryRpc"), _), ran), _), trans) => newRpc (trans, st, Error)
+ | EApp ((ECApp ((ENamed n, _), ran), _), trans) =>
+ if IS.member (rpcBaseIds, n) then
+ newRpc (trans, st, None)
+ else if IS.member (trpcBaseIds, n) then
+ newRpc (trans, st, Error)
+ else
+ (e, st)
+
+ | _ => (e, st)
+ end
+
+ and doExp (e, st) = U.Exp.foldMap {kind = fn x => x,
+ con = fn x => x,
+ exp = exp} st (ReduceLocal.reduceExp e)
+
+ fun decl (d, st : state) =
+ let
+ val (d, st) = U.Decl.foldMap {kind = fn x => x,
+ con = fn x => x,
+ exp = exp,
+ decl = fn x => x}
+ st d
+ in
+ (d :: #export_decls st,
+ {exported = #exported st,
+ export_decls = []})
+ end
+
+ val (file, _) = ListUtil.foldlMapConcat decl
+ {exported = IS.empty,
+ export_decls = []}
+ file
+ in
+ file
+ end
+
+end
diff --git a/src/scriptcheck.sig b/src/scriptcheck.sig
new file mode 100644
index 0000000..afb557b
--- /dev/null
+++ b/src/scriptcheck.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2009, 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.
+ *)
+
+signature SCRIPT_CHECK = sig
+
+ val classify : Mono.file -> Mono.file
+
+end
diff --git a/src/scriptcheck.sml b/src/scriptcheck.sml
new file mode 100644
index 0000000..0d30ebc
--- /dev/null
+++ b/src/scriptcheck.sml
@@ -0,0 +1,182 @@
+(* Copyright (c) 2009, 2014, 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 ScriptCheck :> SCRIPT_CHECK = struct
+
+open Mono
+
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+structure SS = BinarySetFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+structure IS = IntBinarySet
+
+val pushBasis = SS.addList (SS.empty,
+ ["new_channel",
+ "self"])
+
+datatype rpcmap =
+ Rpc of int (* ID of function definition *)
+ | Module of rpcmap SM.map
+
+fun lookup (r : rpcmap, k : string) =
+ let
+ fun lookup' (r, ks) =
+ case r of
+ Rpc x => SOME x
+ | Module m =>
+ case ks of
+ [] => NONE
+ | k :: ks' =>
+ case SM.find (m, k) of
+ NONE => NONE
+ | SOME r' => lookup' (r', ks')
+ in
+ lookup' (r, String.tokens (fn ch => ch = #"/") k)
+ end
+
+fun insert (r : rpcmap, k : string, v) =
+ let
+ fun insert' (r, ks) =
+ case r of
+ Rpc _ => Rpc v
+ | Module m =>
+ case ks of
+ [] => Rpc v
+ | k :: ks' =>
+ let
+ val r' = case SM.find (m, k) of
+ NONE => Module SM.empty
+ | SOME r' => r'
+ in
+ Module (SM.insert (m, k, insert' (r', ks')))
+ end
+ in
+ insert' (r, String.tokens (fn ch => ch = #"/") k)
+ end
+
+fun dump (r : rpcmap) =
+ case r of
+ Rpc _ => print "ROOT\n"
+ | Module m => (print "<Module>\n";
+ SM.appi (fn (k, r') => (print (k ^ ":\n");
+ dump r')) m;
+ print "</Module>\n")
+
+fun classify (ds, ps) =
+ let
+ val proto = Settings.currentProtocol ()
+
+ fun inString {needle, haystack} = String.isSubstring needle haystack
+
+ fun hasClient {basis, rpcs, funcs, push} =
+ MonoUtil.Exp.exists {typ = fn _ => false,
+ exp = fn ERecv _ => push
+ | EFfiApp ("Basis", x, _) => SS.member (basis, x)
+ | EJavaScript _ => not push
+ | ENamed n => IS.member (funcs, n)
+ | EServerCall (e, _, _, _) =>
+ let
+ fun head (e : exp) =
+ case #1 e of
+ EStrcat (e1, _) => head e1
+ | EPrim (Prim.String (_, s)) => SOME s
+ | _ => NONE
+ in
+ case head e of
+ NONE => true
+ | SOME fcall =>
+ case lookup (rpcs, fcall) of
+ NONE => true
+ | SOME n => IS.member (funcs, n)
+ end
+ | _ => false}
+
+ fun decl ((d, _), rpcs) =
+ case d of
+ DExport (Mono.Rpc _, fcall, n, _, _, _) =>
+ insert (rpcs, fcall, n)
+ | _ => rpcs
+
+ val rpcs = foldl decl (Module SM.empty) ds
+
+ fun decl ((d, _), (pull_ids, push_ids)) =
+ let
+ val hasClientPull = hasClient {basis = SS.empty, rpcs = rpcs, funcs = pull_ids, push = false}
+ val hasClientPush = hasClient {basis = pushBasis, rpcs = rpcs, funcs = push_ids, push = true}
+ in
+ case d of
+ DVal (_, n, _, e, _) => (if hasClientPull e then
+ IS.add (pull_ids, n)
+ else
+ pull_ids,
+ if hasClientPush e then
+ IS.add (push_ids, n)
+ else
+ push_ids)
+ | DValRec xes => (if List.exists (fn (_, _, _, e, _) => hasClientPull e) xes then
+ foldl (fn ((_, n, _, _, _), pull_ids) => IS.add (pull_ids, n))
+ pull_ids xes
+ else
+ pull_ids,
+ if List.exists (fn (_, _, _, e, _) => hasClientPush e) xes then
+ foldl (fn ((_, n, _, _, _), push_ids) => IS.add (push_ids, n))
+ push_ids xes
+ else
+ push_ids)
+ | _ => (pull_ids, push_ids)
+ end
+
+ val (pull_ids, push_ids) = foldl decl (IS.empty, IS.empty) ds
+
+ val foundBad = ref false
+
+ val all_ids = IS.union (pull_ids, push_ids)
+
+ val ps = map (fn n =>
+ (n, if IS.member (push_ids, n) then
+ (if not (#persistent proto) andalso not (!foundBad) then
+ (foundBad := true;
+ ErrorMsg.error ("This program needs server push, but the current protocol ("
+ ^ #name proto ^ ") doesn't support that."))
+ else
+ ();
+ ServerAndPullAndPush)
+ else if IS.member (pull_ids, n) then
+ ServerAndPull
+ else
+ ServerOnly, AnyDb)) (IS.listItems all_ids)
+ in
+ (ds, ps)
+ end
+
+end
+
diff --git a/src/search.sig b/src/search.sig
new file mode 100644
index 0000000..ac86714
--- /dev/null
+++ b/src/search.sig
@@ -0,0 +1,62 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature SEARCH = sig
+
+ datatype ('state, 'abort) result =
+ Return of 'abort
+ | Continue of 'state
+
+ type ('data, 'state, 'abort) mapfolder =
+ 'data -> 'state -> ('data * 'state, 'abort) result
+
+ type ('context, 'data, 'state, 'abort) mapfolderB =
+ 'context -> 'data -> 'state -> ('data * 'state, 'abort) result
+
+ val return2 : 'data -> 'state -> ('data * 'state, 'abort) result
+
+ val map : ('state1, 'abort) result
+ * ('state1 -> 'state2)
+ -> ('state2, 'abort) result
+
+ val map2 : ('state2 -> ('state1 * 'state2, 'abort) result)
+ * ('state1 -> 'state1')
+ -> ('state2 -> ('state1' * 'state2, 'abort) result)
+
+ val bind : ('state1, 'abort) result
+ * ('state1 -> ('state2, 'abort) result)
+ -> ('state2, 'abort) result
+
+ val bind2 : ('state2 -> ('state1 * 'state2, 'abort) result)
+ * ('state1 -> 'state2 -> ('state1' * 'state2, 'abort) result)
+ -> ('state2 -> ('state1' * 'state2, 'abort) result)
+
+ val bindP : (('state11 * 'state12) * 'state2, 'abort) result
+ * ('state11 -> 'state2 -> ('state11 * 'state2, 'abort) result)
+ -> (('state11 * 'state12) * 'state2, 'abort) result
+
+end
diff --git a/src/search.sml b/src/search.sml
new file mode 100644
index 0000000..563496f
--- /dev/null
+++ b/src/search.sml
@@ -0,0 +1,73 @@
+(* Copyright (c) 2008, 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 Search :> SEARCH = struct
+
+datatype ('state, 'abort) result =
+ Return of 'abort
+ | Continue of 'state
+
+type ('data, 'state, 'abort) mapfold_arg =
+ 'data * 'state -> ('data * 'state, 'abort) result
+
+type ('data, 'state, 'abort) mapfolder =
+ 'data -> 'state -> ('data * 'state, 'abort) result
+
+type ('context, 'data, 'state, 'abort) mapfolderB =
+ 'context -> 'data -> 'state -> ('data * 'state, 'abort) result
+
+fun return2 v acc = Continue (v, acc)
+
+fun map (r, f) =
+ case r of
+ Continue acc => Continue (f acc)
+ | Return x => Return x
+
+fun map2 (r, f) acc =
+ case r acc of
+ Continue (x, acc) => Continue (f x, acc)
+ | Return x => Return x
+
+fun bind (r, f) =
+ case r of
+ Continue acc => f acc
+ | Return x => Return x
+
+fun bind2 (r, f) acc =
+ case r acc of
+ Continue (x, acc) => f x acc
+ | Return x => Return x
+
+fun bindP (r, f) =
+ case r of
+ Continue ((x, pos), acc) =>
+ map (f x acc,
+ fn (x', acc') =>
+ ((x', pos), acc'))
+ | Return x => Return x
+
+end
diff --git a/src/settings.sig b/src/settings.sig
new file mode 100644
index 0000000..256a12b
--- /dev/null
+++ b/src/settings.sig
@@ -0,0 +1,309 @@
+(* Copyright (c) 2008-2011, 2013, 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.
+ *)
+
+signature SETTINGS = sig
+
+ (* Call this when compiling a new project, e.g. with the Ur/Web daemon or from the SML/NJ REPL.
+ * Some settings stay, but most are reset, especially files cached for the app to serve. *)
+ val reset : unit -> unit
+
+ (* XXX these should be unit -> string too *)
+ val configBin : string ref
+ val configLib : string ref
+ val configSrcLib : string ref
+ val configInclude : string ref
+ val configSitelisp : string ref
+
+ val libUr : unit -> string
+ val libC : unit -> string
+ val libJs : unit -> string
+
+ val setDebug : bool -> unit
+ val getDebug : unit -> bool
+
+ val libFile : string -> string
+ val clibFile : string -> string
+
+ (* How do all application URLs begin? *)
+ val setUrlPrefix : string -> unit
+ val getUrlPrefix : unit -> string
+ val getUrlPrePrefix : unit -> string
+ val getUrlPrefixFull : unit -> string
+ (* The full prefix is the value that was set explicitly, while the "pre"
+ * prefix gets the protocol/host/port part and the unqualified prefix gets
+ * the URI. *)
+
+ (* How many seconds should the server wait before assuming a Comet client has left? *)
+ val setTimeout : int -> unit
+ val getTimeout : unit -> int
+
+ (* Which C header files are needed? *)
+ val setHeaders : string list -> unit
+ val getHeaders : unit -> string list
+
+ (* Which extra JavaScript URLs should be included? *)
+ val setScripts : string list -> unit
+ val getScripts : unit -> string list
+
+ type ffi = string * string
+
+ (* Which FFI types may be sent from clients to servers? *)
+ val setClientToServer : ffi list -> unit
+ val mayClientToServer : ffi -> bool
+
+ (* Which FFI functions have side effects? *)
+ val setEffectful : ffi list -> unit
+ val addEffectful : ffi -> unit
+ val isEffectful : ffi -> bool
+
+ (* Which FFI functions should not have their calls removed or reordered, but cause no lasting effects? *)
+ val setBenignEffectful : ffi list -> unit
+ val addBenignEffectful : ffi -> unit
+ val isBenignEffectful : ffi -> bool
+
+ (* Which FFI functions may only be run in clients? *)
+ val setClientOnly : ffi list -> unit
+ val addClientOnly : ffi -> unit
+ val isClientOnly : ffi -> bool
+
+ (* Which FFI functions may only be run on servers? *)
+ val setServerOnly : ffi list -> unit
+ val addServerOnly : ffi -> unit
+ val isServerOnly : ffi -> bool
+
+ (* Which FFI functions may be run in JavaScript? (JavaScript function names included) *)
+ val setJsModule : string option -> unit
+ val setJsFuncs : (ffi * string) list -> unit
+ val addJsFunc : ffi * string -> unit
+ val jsFunc : ffi -> string option
+ val allJsFuncs : unit -> (ffi * string) list
+
+ datatype pattern_kind = Exact | Prefix
+ datatype action = Allow | Deny
+ type rule = { action : action, kind : pattern_kind, pattern : string }
+
+ 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 }
+
+ (* Rules for rewriting URLs from canonical forms *)
+ val setRewriteRules : rewrite list -> unit
+ val rewrite : path_kind -> string -> string
+
+ (* Validating URLs and MIME types *)
+ val setUrlRules : rule list -> unit
+ val getUrlRules : unit -> rule list
+ val checkUrl : string -> bool
+
+ val setMimeRules : rule list -> unit
+ val getMimeRules : unit -> rule list
+ val checkMime : string -> bool
+
+ val setRequestHeaderRules : rule list -> unit
+ val getRequestHeaderRules : unit -> rule list
+ val checkRequestHeader : string -> bool
+
+ val setResponseHeaderRules : rule list -> unit
+ val getResponseHeaderRules : unit -> rule list
+ val checkResponseHeader : string -> bool
+
+ val setEnvVarRules : rule list -> unit
+ val getEnvVarRules : unit -> rule list
+ val checkEnvVar : string -> bool
+
+ val setMetaRules : rule list -> unit
+ val getMetaRules : unit -> rule list
+ val checkMeta : string -> bool
+
+ (* Web protocols that generated programs may speak *)
+ type protocol = {
+ name : string, (* Call it this on the command line *)
+ compile : string, (* Pass these `gcc -c' arguments *)
+ linkStatic : string, (* Pass these static linker arguments *)
+ linkDynamic : string,(* Pass these dynamic linker arguments *)
+ persistent : bool, (* Multiple requests per process? *)
+ code : unit -> Print.PD.pp_desc (* Extra code to include in C files *)
+ }
+ val addProtocol : protocol -> unit
+ val setProtocol : string -> unit
+ val currentProtocol : unit -> protocol
+
+ (* Different DBMSes *)
+ datatype sql_type =
+ Int
+ | Float
+ | String
+ | Char
+ | Bool
+ | Time
+ | Blob
+ | Channel
+ | Client
+ | Nullable of sql_type
+
+ val p_sql_ctype : sql_type -> string
+ val isBlob : sql_type -> bool
+ val isNotNull : sql_type -> bool
+
+ datatype failure_mode = Error | None
+
+ type dbms = {
+ name : string,
+ (* Call it this on the command line *)
+ randomFunction : string,
+ (* DBMS's name for random number-generating function *)
+ header : string,
+ (* Include this C header file *)
+ link : string,
+ (* Pass these linker arguments *)
+ p_sql_type : sql_type -> string,
+ init : {dbstring : string,
+ prepared : (string * int) list,
+ tables : (string * (string * sql_type) list) list,
+ views : (string * (string * sql_type) list) list,
+ sequences : string list} -> Print.PD.pp_desc,
+ (* Define uw_client_init(), uw_db_init(), uw_db_close(), uw_db_begin(), uw_db_commit(), and uw_db_rollback() *)
+ query : {loc : ErrorMsg.span, cols : sql_type list,
+ doCols : ({loc : ErrorMsg.span, wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc)
+ -> Print.PD.pp_desc}
+ -> Print.PD.pp_desc,
+ queryPrepared : {loc : ErrorMsg.span, id : int, query : string,
+ inputs : sql_type list, cols : sql_type list,
+ doCols : ({loc : ErrorMsg.span, wontLeakStrings : bool, col : int,
+ typ : sql_type} -> Print.PD.pp_desc)
+ -> Print.PD.pp_desc,
+ nested : bool}
+ -> Print.PD.pp_desc,
+ dml : ErrorMsg.span * failure_mode -> Print.PD.pp_desc,
+ dmlPrepared : {loc : ErrorMsg.span, id : int, dml : string,
+ inputs : sql_type list, mode : failure_mode} -> Print.PD.pp_desc,
+ nextval : {loc : ErrorMsg.span, seqE : Print.PD.pp_desc, seqName : string option} -> Print.PD.pp_desc,
+ nextvalPrepared : {loc : ErrorMsg.span, id : int, query : string} -> Print.PD.pp_desc,
+ setval : {loc : ErrorMsg.span, seqE : Print.PD.pp_desc, count : Print.PD.pp_desc} -> Print.PD.pp_desc,
+ sqlifyString : string -> string,
+ p_cast : string * sql_type -> string,
+ p_blank : int * sql_type -> string (* Prepared statement input *),
+ supportsDeleteAs : bool,
+ supportsUpdateAs : bool,
+ createSequence : string -> string,
+ textKeysNeedLengths : bool,
+ supportsNextval : bool,
+ supportsNestedPrepared : bool,
+ sqlPrefix : string,
+ supportsOctetLength : bool,
+ trueString : string,
+ falseString : string,
+ onlyUnion : bool,
+ nestedRelops : bool,
+ windowFunctions : bool,
+ supportsIsDistinctFrom : bool
+ }
+
+ val addDbms : dbms -> unit
+ val setDbms : string -> unit
+ val currentDbms : unit -> dbms
+
+ val setDbstring : string option -> unit
+ val getDbstring : unit -> string option
+
+ val setExe : string option -> unit
+ val getExe : unit -> string option
+
+ val setSql : string option -> unit
+ val getSql : unit -> string option
+
+ val setCoreInline : int -> unit
+ val getCoreInline : unit -> int
+
+ val setMonoInline : int -> unit
+ val getMonoInline : unit -> int
+
+ val setStaticLinking : bool -> unit
+ val getStaticLinking : unit -> bool
+
+ val setBootLinking : bool -> unit
+ val getBootLinking : unit -> bool
+
+ val setDeadlines : bool -> unit
+ val getDeadlines : unit -> bool
+
+ val setSigFile : string option -> unit
+ val getSigFile : unit -> string option
+
+ (* Which GET-able functions should be allowed to have side effects? *)
+ val setSafeGets : string list -> unit
+ val isSafeGet : string -> bool
+
+ val setOnError : (string * string list * string) option -> unit
+ val getOnError : unit -> (string * string list * string) option
+
+ val addLimit : string * int -> unit
+ val limits : unit -> (string * int) list
+
+ val setMinHeap : int -> unit
+ val getMinHeap : unit -> int
+
+ val addAlwaysInline : string -> unit
+ val checkAlwaysInline : string -> bool
+
+ val addNeverInline : string -> unit
+ val checkNeverInline : string -> bool
+
+ val addNoXsrfProtection : string -> unit
+ val checkNoXsrfProtection : string -> bool
+
+ val setTimeFormat : string -> unit
+ val getTimeFormat : unit -> string
+
+ val getCCompiler : unit -> string
+ val setCCompiler : string -> unit
+
+ val setMangleSql : bool -> unit
+ val mangleSql : string -> string
+ val mangleSqlCatalog : string -> string
+ val mangleSqlTable : string -> string
+
+ val setIsHtml5 : bool -> unit
+ val getIsHtml5 : unit -> bool
+
+ val setLessSafeFfi : bool -> unit
+ val getLessSafeFfi : unit -> bool
+
+ val setSqlcache : bool -> unit
+ val getSqlcache : unit -> bool
+
+ val setFilePath : string -> unit
+ (* Sets the directory where we look for files being added below. *)
+
+ val addFile : {Uri : string, LoadFromFilename : string} -> unit
+ val listFiles : unit -> {Uri : string, ContentType : string option, LastModified : Time.time, Bytes : Word8Vector.vector} list
+
+ val addJsFile : string (* filename *) -> unit
+ val listJsFiles : unit -> {Filename : string, Content : string} list
+
+ val setOutputJsFile : string option (* filename *) -> unit
+ val getOutputJsFile : unit -> string option
+end
diff --git a/src/settings.sml b/src/settings.sml
new file mode 100644
index 0000000..a3263c0
--- /dev/null
+++ b/src/settings.sml
@@ -0,0 +1,1012 @@
+(* 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 Settings :> SETTINGS = struct
+
+val configBin = ref Config.bin
+val configLib = ref Config.lib
+val configSrcLib = ref Config.srclib
+val configInclude = ref Config.includ
+val configSitelisp = ref Config.sitelisp
+
+val configCCompiler = ref Config.ccompiler
+
+fun getCCompiler () = !configCCompiler
+fun setCCompiler cc = configCCompiler := cc
+
+fun libUr () = OS.Path.joinDirFile {dir = !configSrcLib,
+ file = "ur"}
+fun libC () = OS.Path.joinDirFile {dir = !configSrcLib,
+ file = "c"}
+fun libJs () = OS.Path.joinDirFile {dir = !configSrcLib,
+ file = "js"}
+
+fun libFile s = OS.Path.joinDirFile {dir = libUr (),
+ file = s}
+
+val urlPrefixFull = ref "/"
+val urlPrefix = ref "/"
+val urlPrePrefix = ref ""
+val timeout = ref 0
+val headers = ref ([] : string list)
+val scripts = ref ([] : string list)
+
+fun getUrlPrefixFull () = !urlPrefixFull
+fun getUrlPrefix () = !urlPrefix
+fun getUrlPrePrefix () = !urlPrePrefix
+fun setUrlPrefix p =
+ let
+ val prefix = if p = "" then
+ "/"
+ else if String.sub (p, size p - 1) <> #"/" then
+ p ^ "/"
+ else
+ p
+
+ fun findPrefix n =
+ let
+ val (befor, after) = Substring.splitl (fn ch => ch <> #"/") (Substring.extract (prefix, n, NONE))
+ in
+ if Substring.isEmpty after then
+ ("", prefix)
+ else
+ (String.substring (prefix, 0, n) ^ Substring.string befor, Substring.string after)
+ end
+
+ val (prepre, prefix) =
+ if String.isPrefix "http://" prefix then
+ findPrefix 7
+ else if String.isPrefix "https://" prefix then
+ findPrefix 8
+ else
+ ("", prefix)
+ in
+ urlPrefixFull := p;
+ urlPrePrefix := prepre;
+ urlPrefix := prefix
+ end
+
+fun getTimeout () = !timeout
+fun setTimeout n = timeout := n
+
+fun getHeaders () = !headers
+fun setHeaders ls = headers := ls
+
+fun getScripts () = !scripts
+fun setScripts ls = scripts := ls
+
+type ffi = string * string
+
+structure K = struct
+type ord_key = ffi
+fun compare ((m1, x1), (m2, x2)) =
+ Order.join (String.compare (m1, m2),
+ fn () => String.compare (x1, x2))
+end
+
+structure S = BinarySetFn(K)
+structure M = BinaryMapFn(K)
+
+fun basis x = S.addList (S.empty, map (fn x : string => ("Basis", x)) x)
+
+val clientToServerBase = basis ["int",
+ "float",
+ "string",
+ "time",
+ "file",
+ "unit",
+ "option",
+ "list",
+ "bool",
+ "variant"]
+val clientToServer = ref clientToServerBase
+fun setClientToServer ls = clientToServer := S.addList (clientToServerBase, ls)
+fun mayClientToServer x = S.member (!clientToServer, x)
+
+val effectfulBase = basis ["dml",
+ "nextval",
+ "setval",
+ "set_cookie",
+ "clear_cookie",
+ "new_channel",
+ "send",
+ "htmlifyInt_w",
+ "htmlifyFloat_w",
+ "htmlifyString_w",
+ "htmlifyBool_w",
+ "htmlifyTime_w",
+ "attrifyInt_w",
+ "attrifyFloat_w",
+ "attrifyString_w",
+ "attrifyChar_w",
+ "urlifyInt_w",
+ "urlifyFloat_w",
+ "urlifyString_w",
+ "urlifyBool_w",
+ "urlifyChannel_w"]
+
+val effectful = ref effectfulBase
+fun setEffectful ls = effectful := S.addList (effectfulBase, ls)
+fun isEffectful ("Sqlcache", _) = true
+ | isEffectful x = S.member (!effectful, x)
+fun addEffectful x = effectful := S.add (!effectful, x)
+
+val benignBase = basis ["get_cookie",
+ "new_client_source",
+ "get_client_source",
+ "set_client_source",
+ "current",
+ "alert",
+ "confirm",
+ "onError",
+ "onFail",
+ "onConnectFail",
+ "onDisconnect",
+ "onServerError",
+ "mouseEvent",
+ "keyEvent",
+ "debug",
+ "rand",
+ "now",
+ "getHeader",
+ "setHeader",
+ "spawn",
+ "onClick",
+ "onDblclick",
+ "onContextmenu",
+ "onKeydown",
+ "onKeypress",
+ "onKeyup",
+ "onMousedown",
+ "onMouseenter",
+ "onMouseleave",
+ "onMousemove",
+ "onMouseout",
+ "onMouseover",
+ "onMouseup",
+ "preventDefault",
+ "stopPropagation",
+ "fresh",
+ "giveFocus",
+ "currentUrlHasPost",
+ "currentUrlHasQueryString",
+ "currentUrl"]
+
+val benign = ref benignBase
+fun setBenignEffectful ls = benign := S.addList (benignBase, ls)
+fun addBenignEffectful x = benign := S.add (!benign, x)
+fun isBenignEffectful x = S.member (!benign, x)
+
+val clientBase = basis ["get_client_source",
+ "current",
+ "alert",
+ "confirm",
+ "recv",
+ "sleep",
+ "spawn",
+ "onError",
+ "onFail",
+ "onConnectFail",
+ "onDisconnect",
+ "onServerError",
+ "mouseEvent",
+ "keyEvent",
+ "onClick",
+ "onContextmenu",
+ "onDblclick",
+ "onKeydown",
+ "onKeypress",
+ "onKeyup",
+ "onMousedown",
+ "onMouseenter",
+ "onMouseleave",
+ "onMousemove",
+ "onMouseout",
+ "onMouseover",
+ "onMouseup",
+ "preventDefault",
+ "stopPropagation",
+ "giveFocus"]
+val client = ref clientBase
+fun setClientOnly ls = client := S.addList (clientBase, ls)
+fun addClientOnly x = client := S.add (!client, x)
+fun isClientOnly x = S.member (!client, x)
+
+val serverBase = basis ["requestHeader",
+ "query",
+ "dml",
+ "nextval",
+ "setval",
+ "channel",
+ "send",
+ "fieldName",
+ "fieldValue",
+ "remainingFields",
+ "firstFormField"]
+val server = ref serverBase
+fun setServerOnly ls = server := S.addList (serverBase, ls)
+fun addServerOnly x = server := S.add (!server, x)
+fun isServerOnly x = S.member (!server, x)
+
+val basisM = foldl (fn ((k, v : string), m) => M.insert (m, ("Basis", k), v)) M.empty
+
+val jsFuncsBase = basisM [("alert", "alert"),
+ ("stringToTime", "stringToTime"),
+ ("stringToTime_error", "stringToTime_error"),
+ ("timef", "strftime"),
+ ("confirm", "confrm"),
+ ("get_client_source", "sg"),
+ ("current", "scur"),
+ ("htmlifyBool", "bs"),
+ ("htmlifyFloat", "ts"),
+ ("htmlifyInt", "ts"),
+ ("htmlifyString", "eh"),
+ ("new_client_source", "sc"),
+ ("set_client_source", "sv"),
+ ("stringToFloat", "pflo"),
+ ("stringToInt", "pio"),
+ ("stringToFloat_error", "pfl"),
+ ("stringToInt_error", "pi"),
+ ("urlifyInt", "ts"),
+ ("urlifyFloat", "ts"),
+ ("urlifyTime", "ts"),
+ ("urlifyString", "uf"),
+ ("urlifyBool", "ub"),
+ ("recv", "rv"),
+ ("strcat", "cat"),
+ ("intToString", "ts"),
+ ("floatToString", "ts"),
+ ("charToString", "ts"),
+ ("onError", "onError"),
+ ("onFail", "onFail"),
+ ("onConnectFail", "onConnectFail"),
+ ("onDisconnect", "onDisconnect"),
+ ("onServerError", "onServerError"),
+ ("attrifyString", "atr"),
+ ("attrifyInt", "ts"),
+ ("attrifyFloat", "ts"),
+ ("attrifyBool", "bs"),
+ ("boolToString", "bs"),
+ ("str1", "id"),
+ ("strsub", "sub"),
+ ("strsuffix", "suf"),
+ ("strlen", "slen"),
+ ("strindex", "sidx"),
+ ("strsindex", "ssidx"),
+ ("strchr", "schr"),
+ ("substring", "ssub"),
+ ("strcspn", "sspn"),
+ ("strlenGe", "strlenGe"),
+ ("mouseEvent", "uw_mouseEvent"),
+ ("keyEvent", "uw_keyEvent"),
+ ("minTime", "0"),
+ ("stringToBool_error", "s2be"),
+ ("stringToBool", "s2b"),
+
+ ("islower", "isLower"),
+ ("isupper", "isUpper"),
+ ("isalpha", "isAlpha"),
+ ("isdigit", "isDigit"),
+ ("isalnum", "isAlnum"),
+ ("isblank", "isBlank"),
+ ("isspace", "isSpace"),
+ ("isxdigit", "isXdigit"),
+ ("isprint", "isPrint"),
+ ("tolower", "toLower"),
+ ("toupper", "toUpper"),
+ ("ord", "ord"),
+
+ ("checkUrl", "checkUrl"),
+ ("bless", "bless"),
+ ("blessData", "blessData"),
+
+ ("eq_time", "eq"),
+ ("lt_time", "lt"),
+ ("le_time", "le"),
+
+ ("debug", "uw_debug"),
+ ("naughtyDebug", "uw_debug"),
+
+ ("floatFromInt", "float"),
+ ("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"),
+ ("htmlifyTime", "showTimeHtml"),
+ ("toSeconds", "toSeconds"),
+ ("addSeconds", "addSeconds"),
+ ("diffInSeconds", "diffInSeconds"),
+ ("toMilliseconds", "toMilliseconds"),
+ ("fromMilliseconds", "fromMilliseconds"),
+ ("diffInMilliseconds", "diffInMilliseconds"),
+
+ ("fromDatetime", "fromDatetime"),
+ ("datetimeYear", "datetimeYear"),
+ ("datetimeMonth", "datetimeMonth"),
+ ("datetimeDay", "datetimeDay"),
+ ("datetimeHour", "datetimeHour"),
+ ("datetimeMinute", "datetimeMinute"),
+ ("datetimeSecond", "datetimeSecond"),
+ ("datetimeDayOfWeek", "datetimeDayOfWeek"),
+
+
+ ("onClick", "uw_onClick"),
+ ("onContextmenu", "uw_onContextmenu"),
+ ("onDblclick", "uw_onDblclick"),
+ ("onKeydown", "uw_onKeydown"),
+ ("onKeypress", "uw_onKeypress"),
+ ("onKeyup", "uw_onKeyup"),
+ ("onMousedown", "uw_onMousedown"),
+ ("onMouseenter", "uw_onMouseenter"),
+ ("onMouseleave", "uw_onMouseleave"),
+ ("onMousemove", "uw_onMousemove"),
+ ("onMouseout", "uw_onMouseout"),
+ ("onMouseover", "uw_onMouseover"),
+ ("onMouseup", "uw_onMouseup"),
+ ("preventDefault", "uw_preventDefault"),
+ ("stopPropagation", "uw_stopPropagation"),
+
+ ("fresh", "fresh"),
+
+ ("atom", "atom"),
+ ("css_url", "css_url"),
+ ("property", "property"),
+ ("giveFocus", "giveFocus"),
+
+ ("htmlifySpecialChar", "htmlifySpecialChar"),
+ ("chr", "chr")]
+val jsFuncs = ref jsFuncsBase
+val jsModule = ref (NONE : string option)
+fun setJsModule m = jsModule := m
+fun jsFuncName f =
+ case !jsModule of
+ SOME m => m ^ "." ^ f
+ | NONE => f
+fun setJsFuncs ls = jsFuncs := foldl (fn ((k, v), m) => M.insert (m, k, jsFuncName v)) jsFuncsBase ls
+fun jsFunc x = M.find (!jsFuncs, x)
+fun addJsFunc (k, v) = jsFuncs := M.insert (!jsFuncs, k, jsFuncName v)
+fun allJsFuncs () = M.listItemsi (!jsFuncs)
+
+datatype pattern_kind = Exact | Prefix
+datatype action = Allow | Deny
+type rule = { action : action, kind : pattern_kind, pattern : string }
+
+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) =
+ pk1 = pk2
+ orelse pk2 = Any
+ orelse pk2 = Relation andalso (pk1 = Table orelse pk1 = Sequence orelse pk1 = View)
+
+fun setRewriteRules ls = rewrites := ls
+fun rewrite pk s =
+ let
+ fun rew (ls : rewrite list) =
+ case ls of
+ [] => s
+ | rewr :: ls =>
+ let
+ fun match () =
+ case #kind rewr of
+ Exact => if #from rewr = s then
+ SOME (size s)
+ else
+ NONE
+ | Prefix => if String.isPrefix (#from rewr) s then
+ SOME (size (#from rewr))
+ else
+ NONE
+ in
+ if subsume (pk, #pkind rewr) then
+ case match () of
+ NONE => rew ls
+ | SOME suffixStart =>
+ let
+ val s = #to rewr ^ String.extract (s, suffixStart, NONE)
+ in
+ if #hyphenate rewr then
+ String.translate (fn #"_" => "-" | ch => str ch) s
+ else
+ s
+ end
+ else
+ rew ls
+ end
+ in
+ rew (!rewrites)
+ end
+
+val url = ref ([] : rule list)
+val mime = ref ([] : rule list)
+val request = ref ([] : rule list)
+val response = ref ([] : rule list)
+val env = ref ([] : rule list)
+val meta = ref ([] : rule list)
+
+fun setUrlRules ls = url := ls
+fun setMimeRules ls = mime := ls
+fun setRequestHeaderRules ls = request := ls
+fun setResponseHeaderRules ls = response := ls
+fun setEnvVarRules ls = env := ls
+fun setMetaRules ls = meta := ls
+
+fun getUrlRules () = !url
+fun getMimeRules () = !mime
+fun getRequestHeaderRules () = !request
+fun getResponseHeaderRules () = !response
+fun getEnvVarRules () = !env
+fun getMetaRules () = !meta
+
+fun check f rules s =
+ let
+ fun chk (ls : rule list) =
+ case ls of
+ [] => false
+ | rule :: ls =>
+ let
+ val matches =
+ case #kind rule of
+ Exact => #pattern rule = s
+ | Prefix => String.isPrefix (#pattern rule) s
+ in
+ if matches then
+ case #action rule of
+ Allow => true
+ | Deny => false
+ else
+ chk ls
+ end
+ in
+ f s andalso chk (!rules)
+ end
+
+val checkUrl = check (fn _ => true) url
+
+val validMime = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"/" orelse ch = #"-" orelse ch = #"." orelse ch = #"+")
+val validEnv = CharVector.all (fn ch => Char.isAlphaNum ch orelse ch = #"_" orelse ch = #".")
+val validMeta = CharVector.all (fn ch => Char.isAlpha ch orelse ch = #"-")
+
+val checkMime = check validMime mime
+val checkRequestHeader = check validMime request
+val checkResponseHeader = check validMime response
+val checkEnvVar = check validEnv env
+val checkMeta = check validMeta meta
+
+
+type protocol = {
+ name : string,
+ compile : string,
+ linkStatic : string,
+ linkDynamic : string,
+ persistent : bool,
+ code : unit -> Print.PD.pp_desc
+}
+val protocols = ref ([] : protocol list)
+fun addProtocol p = protocols := p :: !protocols
+fun getProtocol s = List.find (fn p => #name p = s) (!protocols)
+
+fun clibFile s = OS.Path.joinDirFile {dir = libC (),
+ file = s}
+
+val curProto = ref {name = "",
+ compile = "",
+ linkStatic = "",
+ linkDynamic = "",
+ persistent = false,
+ code = fn () => Print.box []}
+fun setProtocol name =
+ case getProtocol name of
+ NONE => raise Fail ("Unknown protocol " ^ name)
+ | SOME p => curProto := p
+fun currentProtocol () = !curProto
+
+val debug = ref false
+fun setDebug b = debug := b
+fun getDebug () = !debug
+
+datatype sql_type =
+ Int
+ | Float
+ | String
+ | Char
+ | Bool
+ | Time
+ | Blob
+ | Channel
+ | Client
+ | Nullable of sql_type
+
+fun p_sql_ctype t =
+ let
+ open Print.PD
+ open Print
+ in
+ case t of
+ Int => "uw_Basis_int"
+ | Float => "uw_Basis_float"
+ | String => "uw_Basis_string"
+ | Char => "uw_Basis_char"
+ | Bool => "uw_Basis_bool"
+ | Time => "uw_Basis_time"
+ | Blob => "uw_Basis_blob"
+ | Channel => "uw_Basis_channel"
+ | Client => "uw_Basis_client"
+ | Nullable String => "uw_Basis_string"
+ | Nullable t => p_sql_ctype t ^ "*"
+ end
+
+fun isBlob Blob = true
+ | isBlob (Nullable t) = isBlob t
+ | isBlob _ = false
+
+fun isNotNull (Nullable _) = false
+ | isNotNull _ = true
+
+datatype failure_mode = Error | None
+
+type dbms = {
+ name : string,
+ randomFunction : string,
+ header : string,
+ link : string,
+ p_sql_type : sql_type -> string,
+ init : {dbstring : string,
+ prepared : (string * int) list,
+ tables : (string * (string * sql_type) list) list,
+ views : (string * (string * sql_type) list) list,
+ sequences : string list} -> Print.PD.pp_desc,
+ query : {loc : ErrorMsg.span, cols : sql_type list,
+ doCols : ({loc : ErrorMsg.span, wontLeakStrings : bool, col : int, typ : sql_type} -> Print.PD.pp_desc)
+ -> Print.PD.pp_desc}
+ -> Print.PD.pp_desc,
+ queryPrepared : {loc : ErrorMsg.span, id : int, query : string,
+ inputs : sql_type list, cols : sql_type list,
+ doCols : ({loc : ErrorMsg.span, wontLeakStrings : bool, col : int,
+ typ : sql_type} -> Print.PD.pp_desc)
+ -> Print.PD.pp_desc,
+ nested : bool}
+ -> Print.PD.pp_desc,
+ dml : ErrorMsg.span * failure_mode -> Print.PD.pp_desc,
+ dmlPrepared : {loc : ErrorMsg.span, id : int, dml : string,
+ inputs : sql_type list, mode : failure_mode} -> Print.PD.pp_desc,
+ nextval : {loc : ErrorMsg.span, seqName : string option, seqE : Print.PD.pp_desc} -> Print.PD.pp_desc,
+ nextvalPrepared : {loc : ErrorMsg.span, id : int, query : string} -> Print.PD.pp_desc,
+ setval : {loc : ErrorMsg.span, seqE : Print.PD.pp_desc, count : Print.PD.pp_desc} -> Print.PD.pp_desc,
+ sqlifyString : string -> string,
+ p_cast : string * sql_type -> string,
+ p_blank : int * sql_type -> string,
+ supportsDeleteAs : bool,
+ supportsUpdateAs : bool,
+ createSequence : string -> string,
+ textKeysNeedLengths : bool,
+ supportsNextval : bool,
+ supportsNestedPrepared : bool,
+ sqlPrefix : string,
+ supportsOctetLength : bool,
+ trueString : string,
+ falseString : string,
+ onlyUnion : bool,
+ nestedRelops : bool,
+ windowFunctions: bool,
+ supportsIsDistinctFrom : bool
+}
+
+val dbmses = ref ([] : dbms list)
+val curDb = ref ({name = "",
+ randomFunction = "",
+ header = "",
+ link = "",
+ p_sql_type = fn _ => "",
+ init = fn _ => Print.box [],
+ query = fn _ => Print.box [],
+ queryPrepared = fn _ => Print.box [],
+ dml = fn _ => Print.box [],
+ dmlPrepared = fn _ => Print.box [],
+ nextval = fn _ => Print.box [],
+ nextvalPrepared = fn _ => Print.box [],
+ setval = fn _ => Print.box [],
+ sqlifyString = fn s => s,
+ p_cast = fn _ => "",
+ p_blank = fn _ => "",
+ supportsDeleteAs = false,
+ supportsUpdateAs = false,
+ createSequence = fn _ => "",
+ textKeysNeedLengths = false,
+ supportsNextval = false,
+ supportsNestedPrepared = false,
+ sqlPrefix = "",
+ supportsOctetLength = false,
+ trueString = "",
+ falseString = "",
+ onlyUnion = false,
+ nestedRelops = false,
+ windowFunctions = false,
+ supportsIsDistinctFrom = false} : dbms)
+
+fun addDbms v = dbmses := v :: !dbmses
+fun setDbms s =
+ case List.find (fn db => #name db = s) (!dbmses) of
+ NONE => raise Fail ("Unknown DBMS " ^ s)
+ | SOME db => curDb := db
+fun currentDbms () = !curDb
+
+val dbstring = ref (NONE : string option)
+fun setDbstring so = dbstring := so
+fun getDbstring () = !dbstring
+
+val exe = ref (NONE : string option)
+fun setExe so = exe := so
+fun getExe () = !exe
+
+val sql = ref (NONE : string option)
+fun setSql so = sql := so
+fun getSql () = !sql
+
+val coreInline = ref 5
+fun setCoreInline n = coreInline := n
+fun getCoreInline () = !coreInline
+
+val monoInline = ref 5
+fun setMonoInline n = monoInline := n
+fun getMonoInline () = !monoInline
+
+val staticLinking = ref false
+fun setStaticLinking b = staticLinking := b
+fun getStaticLinking () = !staticLinking
+
+val bootLinking = ref false
+fun setBootLinking b = bootLinking := b
+fun getBootLinking () = !bootLinking
+
+val deadlines = ref false
+fun setDeadlines b = deadlines := b
+fun getDeadlines () = !deadlines
+
+val sigFile = ref (NONE : string option)
+fun setSigFile v = sigFile := v
+fun getSigFile () = !sigFile
+
+structure SS = BinarySetFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+val safeGet = ref SS.empty
+fun setSafeGets ls = safeGet := SS.addList (SS.empty, ls)
+fun isSafeGet x = SS.member (!safeGet, x)
+
+val onError = ref (NONE : (string * string list * string) option)
+fun setOnError x = onError := x
+fun getOnError () = !onError
+
+val limits = ["messages", "clients", "headers", "page", "heap", "script",
+ "inputs", "subinputs", "cleanup", "deltas", "transactionals",
+ "globals", "database", "time"]
+
+val limitsList = ref ([] : (string * int) list)
+fun addLimit (v as (name, _)) =
+ if List.exists (fn name' => name' = name) limits then
+ (limitsList := v :: !limitsList;
+ if name = "time" then
+ setDeadlines true
+ else
+ ())
+ else
+ raise Fail ("Unknown limit category '" ^ name ^ "'")
+fun limits () = !limitsList
+
+val minHeap = ref 0
+fun setMinHeap n = if n >= 0 then minHeap := n else raise Fail "Trying to set negative minHeap"
+fun getMinHeap () = !minHeap
+
+val alwaysInline = ref SS.empty
+fun addAlwaysInline s = alwaysInline := SS.add (!alwaysInline, s)
+fun checkAlwaysInline s = SS.member (!alwaysInline, s)
+
+val neverInline = ref SS.empty
+fun addNeverInline s = neverInline := SS.add (!neverInline, s)
+fun checkNeverInline s = SS.member (!neverInline, s)
+
+val noXsrfProtection = ref SS.empty
+fun addNoXsrfProtection s = noXsrfProtection := SS.add (!noXsrfProtection, s)
+fun checkNoXsrfProtection s = SS.member (!noXsrfProtection, s)
+
+val timeFormat = ref "%c"
+fun setTimeFormat v = timeFormat := v
+fun getTimeFormat () = !timeFormat
+
+fun lowercase s =
+ case s of
+ "" => ""
+ | _ => str (Char.toLower (String.sub (s, 0))) ^ String.extract (s, 1, NONE)
+
+fun capitalize s =
+ case s of
+ "" => ""
+ | _ => 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 #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 true
+fun setIsHtml5 b = html5 := b
+fun getIsHtml5 () = !html5
+
+val less = ref false
+fun setLessSafeFfi b = less := b
+fun getLessSafeFfi () = !less
+
+val sqlcache = ref false
+fun setSqlcache b = sqlcache := b
+fun getSqlcache () = !sqlcache
+
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+val noMimeFile = ref false
+
+fun noMime () =
+ (TextIO.output (TextIO.stdErr, "WARNING: Error opening /etc/mime.types. Static files will be served with no suggested MIME types.\n");
+ noMimeFile := true;
+ SM.empty)
+
+fun readMimeTypes () =
+ let
+ val inf = FileIO.txtOpenIn "/etc/mime.types"
+
+ fun loop m =
+ case TextIO.inputLine inf of
+ NONE => m
+ | SOME line =>
+ if size line > 0 andalso String.sub (line, 0) = #"#" then
+ loop m
+ else
+ case String.tokens Char.isSpace line of
+ typ :: exts =>
+ loop (foldl (fn (ext, m) => SM.insert (m, ext, typ)) m exts)
+ | _ => loop m
+ in
+ loop SM.empty
+ before TextIO.closeIn inf
+ end handle IO.Io _ => noMime ()
+ | OS.SysErr _ => noMime ()
+
+val mimeTypes = ref (NONE : string SM.map option)
+
+fun getMimeTypes () =
+ case !mimeTypes of
+ SOME m => m
+ | NONE =>
+ let
+ val m = readMimeTypes ()
+ in
+ mimeTypes := SOME m;
+ m
+ end
+
+fun mimeTypeOf filename =
+ case OS.Path.ext filename of
+ NONE => (if !noMimeFile then
+ ()
+ else
+ TextIO.output (TextIO.stdErr, "WARNING: No extension found in filename '" ^ filename ^ "'. Header 'Content-Type' will be omitted in HTTP responses.\n");
+ NONE)
+ | SOME ext =>
+ let
+ val to = SM.find (getMimeTypes (), ext)
+ in
+ case to of
+ NONE => if !noMimeFile then
+ ()
+ else
+ TextIO.output (TextIO.stdErr, "WARNING: No MIME type known for extension '" ^ ext ^ "'. Header 'Content-Type' will be omitted in HTTP responses.\n")
+ | _ => ();
+ to
+ end
+
+val files = ref (SM.empty : (string * {Uri : string, ContentType : string option, LastModified : Time.time, Bytes : Word8Vector.vector}) SM.map)
+
+val filePath = ref "."
+
+fun setFilePath path = filePath := path
+
+fun addFile {Uri, LoadFromFilename} =
+ let
+ val path = OS.Path.concat (!filePath, LoadFromFilename)
+ in
+ case SM.find (!files, Uri) of
+ SOME (path', _) =>
+ if OS.Path.mkCanonical path' = OS.Path.mkCanonical path then
+ ()
+ else
+ ErrorMsg.error ("Two different files requested for URI " ^ Uri ^ " ( " ^ path' ^ " vs. " ^ path ^ ")")
+ | NONE =>
+ let
+ val inf = FileIO.binOpenIn path
+ in
+ files := SM.insert (!files,
+ Uri,
+ (path,
+ {Uri = Uri,
+ ContentType = mimeTypeOf path,
+ LastModified = OS.FileSys.modTime path,
+ Bytes = BinIO.inputAll inf}));
+ BinIO.closeIn inf
+ end
+ end handle IO.Io _ =>
+ ErrorMsg.error ("Error loading file " ^ LoadFromFilename)
+ | OS.SysErr (s, _) =>
+ ErrorMsg.error ("Error loading file " ^ LoadFromFilename ^ " (" ^ s ^ ")")
+
+fun listFiles () = map #2 (SM.listItems (!files))
+
+val jsFiles = ref (SM.empty : {Filename : string, Content : string} SM.map)
+
+fun addJsFile LoadFromFilename =
+ let
+ val path = OS.Path.concat (!filePath, LoadFromFilename)
+ val inf = FileIO.txtOpenIn path
+ in
+ jsFiles := SM.insert (!jsFiles,
+ path,
+ {Filename = LoadFromFilename,
+ Content = TextIO.inputAll inf});
+ TextIO.closeIn inf
+ end handle IO.Io _ =>
+ ErrorMsg.error ("Error loading file " ^ LoadFromFilename)
+ | OS.SysErr (s, _) =>
+ ErrorMsg.error ("Error loading file " ^ LoadFromFilename ^ " (" ^ s ^ ")")
+
+fun listJsFiles () = SM.listItems (!jsFiles)
+
+val jsOutput = ref (NONE : string option)
+fun setOutputJsFile so = jsOutput := so
+fun getOutputJsFile () = !jsOutput
+
+fun reset () =
+ (Globals.setResetTime ();
+ urlPrefixFull := "/";
+ urlPrefix := "/";
+ urlPrePrefix := "";
+ timeout := 0;
+ headers := [];
+ scripts := [];
+ clientToServer := clientToServerBase;
+ effectful := effectfulBase;
+ benign := benignBase;
+ client := clientBase;
+ server := serverBase;
+ jsFuncs := jsFuncsBase;
+ rewrites := [];
+ url := [];
+ mime := [];
+ request := [];
+ response := [];
+ env := [];
+ meta := [];
+ debug := false;
+ dbstring := NONE;
+ exe := NONE;
+ sql := NONE;
+ coreInline := 5;
+ monoInline := 5;
+ staticLinking := false;
+ deadlines := false;
+ sigFile := NONE;
+ safeGet := SS.empty;
+ onError := NONE;
+ limitsList := [];
+ minHeap := 0;
+ alwaysInline := SS.empty;
+ neverInline := SS.empty;
+ noXsrfProtection := SS.empty;
+ timeFormat := "%c";
+ mangle := true;
+ html5 := false;
+ less := false;
+ noMimeFile := false;
+ mimeTypes := NONE;
+ files := SM.empty;
+ jsFiles := SM.empty;
+ filePath := ".";
+ jsOutput := NONE)
+
+end
diff --git a/src/sha1.sig b/src/sha1.sig
new file mode 100644
index 0000000..7fda97f
--- /dev/null
+++ b/src/sha1.sig
@@ -0,0 +1,31 @@
+
+(* Implementation the SHA-1 hash function.
+ Written by Tom 7 in 2004; code in the public domain. *)
+
+signature SHA1 =
+sig
+
+ (* Perform the SHA-1 hash function on a message.
+ Returns the 160 bit (20 byte) hash.
+
+ recall that string = CharVector.vector.
+ The input string may contain non-ascii data;
+ the output certainly will. *)
+
+ val hash : string -> string
+
+ (* pass in a stream as stateful function that returns
+ SOME s for some non-empty prefix of the remainder of
+ the stream, or NONE when the stream has ended. *)
+ val hash_stream : (unit -> string option) -> string
+
+ (* XXX move to hashutil *)
+ (* convert a binary string to one built of hex digits *)
+ val bintohex : string -> string
+
+ (* Parse a hexadecimal SHA-1 string. Uppercase and lowercase
+ are permitted. If the string is not the right length or
+ contains invalid characters, returns NONE. *)
+ val parse_hex : string -> string option
+
+end
diff --git a/src/sha1.sml b/src/sha1.sml
new file mode 100644
index 0000000..d962c4e
--- /dev/null
+++ b/src/sha1.sml
@@ -0,0 +1,264 @@
+
+(* RFC-3174 (SHA-1) hashing function.
+ By Tom 7, 2004: Code placed in the public domain.
+*)
+
+structure SHA1 :> SHA1 =
+struct
+ exception Unimplemented
+
+ val xorb = Word32.xorb
+ val andb = Word32.andb
+ val orb = Word32.orb
+ val << = Word32.<<
+ val >> = Word32.>>
+ val notb = Word32.notb
+ val ++ = Word32.+
+
+ type w32 = word
+ infix xorb andb orb << >> ++
+
+ (* workaround for andb bug in MLton 20010706 *)
+ fun mkbyte w = Word32.mod (w, 0w256)
+
+ fun ROL(X, N : Word.word) = (X << N) orb (X >> (0w32-N))
+
+ fun wc hi lo = (hi << 0w16) orb lo
+
+ fun w2b w = map chr
+ [Word32.toInt (mkbyte (w >> 0w24)),
+ Word32.toInt (mkbyte (w >> 0w16)),
+ Word32.toInt (mkbyte (w >> 0w8)),
+ Word32.toInt (mkbyte w)]
+
+ (* the length (arg in bytes, output in bits)
+ as a 64-bit quantity, big-endian *)
+ fun lenbits l =
+ implode (List.tabulate (4, fn _ => chr 0)) ^
+ implode (w2b (Word32.fromInt (l * 8)))
+
+
+ (* executes f for each index lo..hi-1 inclusive *)
+ fun for lo hi f =
+ if lo >= hi then ()
+ else (ignore (f lo); for (lo + 1) hi f)
+
+ fun ford lo hi b f =
+ if lo >= hi then b
+ else
+ let
+ val b = f (lo, b)
+ in
+ (ford (lo + 1) hi b f)
+ end
+
+ fun doblock (aa, bb, cc, dd, ee) msg =
+ let
+ val K0 = wc 0wx5A82 0wx7999
+ val K1 = wc 0wx6ED9 0wxEBA1
+ val K2 = wc 0wx8F1B 0wxBCDC
+ val K3 = wc 0wxCA62 0wxC1D6
+
+ fun mb n = Word32.fromInt (ord (CharVector.sub(msg, n)))
+
+ val W = Array.array(80, 0w0)
+ fun Ws x = Array.sub(W, x)
+
+ val _ =
+ for 0 16
+ (fn t =>
+ let in
+ Array.update(W, t,
+ (mb (t * 4 ) << 0w24) orb
+ (mb (t * 4 + 1) << 0w16) orb
+ (mb (t * 4 + 2) << 0w8) orb
+ (mb (t * 4 + 3)))
+ end)
+
+ val _ =
+ for 16 80
+ (fn t =>
+ let
+ val n =
+ Ws (t-3) xorb
+ Ws (t-8) xorb
+ Ws (t-14) xorb
+ Ws (t-16)
+ val zz = ROL(n, 0w1)
+ in
+ Array.update(W, t, zz)
+ end)
+
+
+ val (A, B, C, D, E) = (aa, bb, cc, dd, ee)
+
+
+ fun round lo hi f k ctxt =
+ ford lo hi ctxt
+ (fn (t, ctxt as (A, B, C, D, E)) =>
+ let
+ val temp = ROL(A, 0w5) ++ (f ctxt) ++ E ++ Ws t ++ k
+ val E = D;
+ val D = C;
+ val C = ROL(B, 0w30)
+ val B = A
+ val A = temp
+ in
+ (A, B, C, D, E)
+ end)
+
+ val (A, B, C, D, E) =
+ round 0 20 (fn (A, B, C, D, E) =>
+ ((B andb C) orb ((notb B) andb D)))
+ K0 (A, B, C, D, E)
+
+ val (A, B, C, D, E) =
+ round 20 40 (fn (A, B, C, D, E) =>
+ (B xorb C xorb D))
+ K1 (A, B, C, D, E)
+
+ val (A, B, C, D, E) =
+ round 40 60 (fn (A, B, C, D, E) =>
+ ((B andb C) orb (B andb D) orb (C andb D)))
+ K2 (A, B, C, D, E)
+
+ val (A, B, C, D, E) =
+ round 60 80 (fn (A, B, C, D, E) =>
+ (B xorb C xorb D))
+ K3 (A, B, C, D, E)
+
+ in
+ (aa ++ A, bb ++ B, cc ++ C, dd ++ D, ee ++ E)
+ end
+
+ datatype 'a stream =
+ Cons of ('a * (unit -> 'a stream))
+ | Nil
+
+ (* turn a stream of oddly chunked strings into
+ one with 512-bit blocks *)
+ fun chunk_512 s =
+ let
+
+ (* the padding required to make a message of length l (bytes)
+ a proper SHA-1 input. Returns either one or two Cons cells.
+ tail is the end of the input (63 bytes or less)
+ l is the total length of the input, *including* the length of the
+ tail end *)
+ fun padding tail l =
+ let val v = l mod 64 in
+ if v < 56 then
+ let val p = 56 - v
+ val padding = implode (List.tabulate (p - 1, fn _ => chr 0))
+ in Cons (tail ^ str (chr 0x80) ^ padding ^ lenbits l,
+ fn _ => Nil)
+ end
+ else if v < 64 then
+ let val p = 64 - v
+ val padding1 = implode (List.tabulate (p - 1, fn _ => chr 0))
+ val padding2 = implode (List.tabulate (56, fn _ => chr 0))
+ in Cons (tail ^ str (chr 0x80) ^ padding1,
+ fn _ => Cons (padding2 ^ lenbits l, fn _ => Nil))
+ end
+ else raise Unimplemented (* Impossible? *)
+ end
+
+ (* n is the bytes we've already output.
+ cur is a string (of 64 bytes or less) that will
+ be our next chunk.
+ rest,sofar is a string and index indicating the
+ next bit of data. *)
+ (* PERF Could be more efficient by using an
+ accumulating array instead of a string for cur *)
+ fun ch n cur sofar startat () =
+ (* if we already have 64 bytes, return it *)
+ if size cur = 64
+ then
+ let in
+ Cons(cur, ch (n + 64) "" sofar startat)
+ end
+ else
+ (* do we have any in 'sofar'? *)
+ if startat < size sofar
+ then let
+ val get = Int.min(size sofar - startat,
+ 64 - size cur)
+ in
+ (* be eager, since we need to return something now *)
+ ch n (cur ^ String.substring(sofar, startat, get))
+ sofar (startat + get) ()
+ end
+ else
+ (* sofar has been exhausted,
+ so get some from input stream *)
+ (case s () of
+ (* eager, again *)
+ SOME ss => ch n cur ss 0 ()
+ | NONE =>
+ (* no more data. *)
+ padding cur (n + size cur))
+ in
+ ch 0 "" "" 0
+ end
+
+ fun hash_stream orig_stream =
+ let
+
+ val stream512 = chunk_512 orig_stream
+
+ (* gets hash context, length of string so far (bytes),
+ and tail of stream *)
+ fun hash_rest stream ctxt =
+ (case stream() of
+ Cons (s, stream) =>
+ let val ctxt = doblock ctxt s
+ in hash_rest stream ctxt
+ end
+ | Nil => ctxt)
+
+ val init =
+ (wc 0wx6745 0wx2301,
+ wc 0wxefcd 0wxab89,
+ wc 0wx98ba 0wxdcfe,
+ wc 0wx1032 0wx5476,
+ wc 0wxc3d2 0wxe1f0)
+
+ val (a, b, c, d, e) = hash_rest stream512 init
+ in
+ implode (w2b a @ w2b b @ w2b c @ w2b d @ w2b e)
+ end
+
+ fun hash m =
+ hash_stream
+ (let val r = ref true
+ in (fn () =>
+ if !r
+ then (r := false; SOME m)
+ else NONE)
+ end)
+
+ val digits = "0123456789ABCDEF"
+ fun bintohex s =
+ String.translate (fn c =>
+ implode [CharVector.sub (digits, ord c div 16),
+ CharVector.sub (digits, ord c mod 16)]) s
+
+ (* ASCII trick: (ch | 4400) % 55 *)
+ fun hexvalue ch =
+ SysWord.toInt (SysWord.orb(SysWord.fromInt(ord ch), SysWord.fromInt 4400)) mod 55
+
+ fun parse_hex s =
+ if size s <> 40
+ orelse not (CharVector.all (fn c => (ord c >= ord #"0" andalso
+ ord c <= ord #"9") orelse
+ (ord c >= ord #"a" andalso
+ ord c <= ord #"f") orelse
+ (ord c >= ord #"A" andalso
+ ord c <= ord #"F")) s)
+ then NONE
+ else SOME (CharVector.tabulate(20,
+ (fn i =>
+ chr(hexvalue (String.sub(s, i * 2)) * 16 +
+ hexvalue (String.sub(s, i * 2 + 1))))))
+
+end
diff --git a/src/shake.sig b/src/shake.sig
new file mode 100644
index 0000000..2b805de
--- /dev/null
+++ b/src/shake.sig
@@ -0,0 +1,37 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Remove unused definitions from a file *)
+
+signature SHAKE = sig
+
+ val shake : Core.file -> Core.file
+
+ val sliceDb : bool ref
+ (* Set this to try to delete anything not needed to determine the database schema. *)
+
+end
diff --git a/src/shake.sml b/src/shake.sml
new file mode 100644
index 0000000..051507d
--- /dev/null
+++ b/src/shake.sml
@@ -0,0 +1,229 @@
+(* Copyright (c) 2008-2010, 2013, 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.
+ *)
+
+(* Remove unused definitions from a file *)
+
+structure Shake :> SHAKE = struct
+
+val sliceDb = ref false
+
+open Core
+
+structure U = CoreUtil
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+type free = {
+ con : IS.set,
+ exp : IS.set
+}
+
+val dummyt = (TRecord (CRecord ((KType, ErrorMsg.dummySpan), []), ErrorMsg.dummySpan), ErrorMsg.dummySpan)
+val dummye = (EPrim (Prim.String (Prim.Normal, "")), ErrorMsg.dummySpan)
+
+fun tupleC cs = (CTuple cs, ErrorMsg.dummySpan)
+fun tupleE es = (ERecord (map (fn e => (dummyt, e, dummyt)) es), ErrorMsg.dummySpan)
+
+fun shake file =
+ let
+ val usedVarsC = U.Con.fold {kind = fn (_, st) => st,
+ con = fn (c, cs) =>
+ case c of
+ CNamed n => IS.add (cs, n)
+ | _ => cs}
+
+ val usedVars = U.Exp.fold {kind = fn (_, st) => st,
+ con = fn (c, st as (es, cs)) =>
+ case c of
+ CNamed n => (es, IS.add (cs, n))
+ | _ => st,
+ exp = fn (e, st as (es, cs)) =>
+ case e of
+ ENamed n => (IS.add (es, n), cs)
+ | _ => st}
+
+ val (usedE, usedC) =
+ List.foldl
+ (fn ((DExport (_, n, _), _), st as (usedE, usedC)) =>
+ if !sliceDb then
+ st
+ else
+ (IS.add (usedE, n), usedC)
+ | ((DTable (_, _, c, _, pe, pc, ce, cc), _), (usedE, usedC)) =>
+ let
+ val usedC = usedVarsC usedC c
+ val usedC = usedVarsC usedC pc
+ val usedC = usedVarsC usedC cc
+
+ val (usedE, usedC) = usedVars (usedE, usedC) pe
+ val (usedE, usedC) = usedVars (usedE, usedC) ce
+ in
+ (usedE, usedC)
+ end
+ | ((DView (_, _, _, e, c), _), (usedE, usedC)) =>
+ let
+ val usedC = usedVarsC usedC c
+ in
+ usedVars (usedE, usedC) e
+ end
+ | ((DTask (e1, e2), _), st) =>
+ if !sliceDb then
+ st
+ else
+ usedVars (usedVars st e1) e2
+ | ((DPolicy e1, _), st) =>
+ if !sliceDb then
+ st
+ else
+ usedVars st e1
+ | ((DOnError n, _), st as (usedE, usedC)) =>
+ if !sliceDb then
+ st
+ else
+ (IS.add (usedE, n), usedC)
+ | (_, acc) => acc) (IS.empty, IS.empty) file
+
+ val (cdef, edef) = foldl (fn ((DCon (_, n, _, c), _), (cdef, edef)) => (IM.insert (cdef, n, [c]), edef)
+ | ((DDatatype dts, _), (cdef, edef)) =>
+ (foldl (fn ((_, n, _, xncs), cdef) =>
+ IM.insert (cdef, n, List.mapPartial #3 xncs)) cdef dts, edef)
+ | ((DVal (_, n, t, e, _), _), (cdef, edef)) => (cdef, IM.insert (edef, n, ([], t, e)))
+ | ((DValRec vis, _), (cdef, edef)) =>
+ let
+ val all_ns = map (fn (_, n, _, _, _) => n) vis
+ in
+ (cdef, foldl (fn ((_, n, t, e, _), edef) =>
+ IM.insert (edef, n, (all_ns, t, e))) edef vis)
+ end
+ | ((DExport _, _), acc) => acc
+ | ((DTable (_, n, c, _, e1, c1, e2, c2), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, ([], tupleC [c, c1, c2], tupleE [e1, e2])))
+ | ((DSequence (_, n, _), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, ([], dummyt, dummye)))
+ | ((DView (_, n, _, _, c), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, ([], c, dummye)))
+ | ((DDatabase _, _), acc) => acc
+ | ((DCookie (_, n, c, _), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, ([], c, dummye)))
+ | ((DStyle (_, n, _), _), (cdef, edef)) =>
+ (cdef, IM.insert (edef, n, ([], dummyt, dummye)))
+ | ((DTask _, _), acc) => acc
+ | ((DPolicy _, _), acc) => acc
+ | ((DOnError _, _), acc) => acc)
+ (IM.empty, IM.empty) file
+
+ fun kind (_, s) = s
+
+ fun con (c, s) =
+ case c of
+ CNamed n =>
+ if IS.member (#con s, n) then
+ s
+ else
+ let
+ val s' = {con = IS.add (#con s, n),
+ exp = #exp s}
+ in
+ case IM.find (cdef, n) of
+ NONE => s'
+ | SOME cs => foldl (fn (c, s') => shakeCon s' c) s' cs
+ end
+ | _ => s
+
+ and shakeCon s = U.Con.fold {kind = kind, con = con} s
+
+ (*val () = print "=====\nSHAKE\n=====\n"
+ val current = ref 0*)
+
+ fun exp (e, s) =
+ let
+ fun check n =
+ if IS.member (#exp s, n) then
+ s
+ else
+ let
+ val s' = {exp = IS.add (#exp s, n),
+ con = #con s}
+ in
+ (*print ("Need " ^ Int.toString n ^ " <-- " ^ Int.toString (!current) ^ "\n");*)
+ case IM.find (edef, n) of
+ NONE => s'
+ | SOME (ns, t, e) =>
+ let
+ (*val old = !current
+ val () = current := n*)
+ val s' = shakeExp (shakeCon s' t) e
+ in
+ (*current := old;*)
+ foldl (fn (n, s') => exp (ENamed n, s')) s' ns
+ end
+ end
+ in
+ case e of
+ ENamed n => check n
+ | EServerCall (n, _, _, _) => check n
+ | _ => s
+ end
+
+ and shakeExp s = U.Exp.fold {kind = kind, con = con, exp = exp} s
+
+ val s = {con = usedC, exp = usedE}
+
+ val s = IS.foldl (fn (n, s) =>
+ case IM.find (edef, n) of
+ NONE => raise Fail "Shake: Couldn't find 'val'"
+ | SOME (ns, t, e) =>
+ let
+ (*val () = current := n*)
+ val s = shakeExp (shakeCon s t) e
+ in
+ foldl (fn (n, s) => exp (ENamed n, s)) s ns
+ end) s usedE
+
+ val s = IS.foldl (fn (n, s) =>
+ case IM.find (cdef, n) of
+ NONE => raise Fail ("Shake: Couldn't find 'con' " ^ Int.toString n)
+ | SOME cs => foldl (fn (c, s) => shakeCon s c) s cs) s usedC
+ in
+ List.filter (fn (DCon (_, n, _, _), _) => IS.member (#con s, n)
+ | (DDatatype dts, _) => List.exists (fn (_, n, _, _) => IS.member (#con s, n)) dts
+ | (DVal (_, n, _, _, _), _) => IS.member (#exp s, n)
+ | (DValRec vis, _) => List.exists (fn (_, n, _, _, _) => IS.member (#exp s, n)) vis
+ | (DExport _, _) => not (!sliceDb)
+ | (DView _, _) => true
+ | (DSequence _, _) => true
+ | (DTable _, _) => true
+ | (DDatabase _, _) => not (!sliceDb)
+ | (DCookie _, _) => not (!sliceDb)
+ | (DStyle _, _) => not (!sliceDb)
+ | (DTask _, _) => not (!sliceDb)
+ | (DPolicy _, _) => not (!sliceDb)
+ | (DOnError _, _) => not (!sliceDb)) file
+ end
+
+end
diff --git a/src/sidecheck.sig b/src/sidecheck.sig
new file mode 100644
index 0000000..1e3e227
--- /dev/null
+++ b/src/sidecheck.sig
@@ -0,0 +1,37 @@
+(* Copyright (c) 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.
+ *)
+
+signature SIDE_CHECK = sig
+
+ 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
diff --git a/src/sidecheck.sml b/src/sidecheck.sml
new file mode 100644
index 0000000..bd11223
--- /dev/null
+++ b/src/sidecheck.sml
@@ -0,0 +1,84 @@
+(* Copyright (c) 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 SideCheck :> SIDE_CHECK = struct
+
+open Mono
+
+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 =
+ 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
diff --git a/src/sigcheck.sig b/src/sigcheck.sig
new file mode 100644
index 0000000..565621c
--- /dev/null
+++ b/src/sigcheck.sig
@@ -0,0 +1,36 @@
+(* Copyright (c) 2013, 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.
+ *)
+
+(* Make sure that no global variable initializers mention signature generation,
+ * because said generation only makes sense in the context of a page view.
+ * Replace such global variables with functions. *)
+
+signature SIG_CHECK = sig
+
+ val check : Mono.file -> Mono.file
+
+end
diff --git a/src/sigcheck.sml b/src/sigcheck.sml
new file mode 100644
index 0000000..a6ed765
--- /dev/null
+++ b/src/sigcheck.sml
@@ -0,0 +1,97 @@
+(* Copyright (c) 2013, 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 SigCheck :> SIG_CHECK = struct
+
+open Mono
+
+structure IS = IntBinarySet
+structure E = ErrorMsg
+
+fun check (ds, sl) =
+ let
+ fun isSiggy siggers =
+ MonoUtil.Decl.exists {typ = fn _ => false,
+ decl = fn _ => false,
+ exp = fn e =>
+ case e of
+ ERel n => IS.member (siggers, n)
+ | EFfiApp ("Basis", "sigString", _) => true
+ | _ => false}
+
+ fun sigify' sigdecs e =
+ case e of
+ ENamed n => if IS.member (sigdecs, n) then
+ (EApp ((e, E.dummySpan),
+ (ERecord [], E.dummySpan)))
+ else
+ e
+ | _ => e
+
+ fun sigify sigdecs =
+ MonoUtil.Decl.map {typ = fn x => x,
+ decl = fn d => d,
+ exp = sigify' sigdecs}
+
+ fun sigifyE sigdecs =
+ MonoUtil.Exp.map {typ = fn x => x,
+ exp = sigify' sigdecs}
+
+ fun isFun (e, _) =
+ case e of
+ EAbs _ => true
+ | _ => false
+
+ fun doDecl (d : decl, (siggers, sigdecs)) =
+ case #1 d of
+ DVal (x, n, t, e, s) =>
+ if isSiggy siggers d then
+ if isFun e then
+ (sigify sigdecs d, (IS.add (siggers, n), sigdecs))
+ else
+ ((DVal (x, n, (TFun ((TRecord [], #2 d), t), #2 d),
+ (EAbs ("_", (TRecord [], #2 d), t, sigifyE sigdecs e), #2 d),
+ s), #2 d),
+ (IS.add (siggers, n),
+ IS.add (sigdecs, n)))
+ else
+ (sigify sigdecs d, (siggers, sigdecs))
+ | DValRec vis =>
+ if isSiggy siggers d then
+ (sigify sigdecs d,
+ (foldl IS.add' siggers (map #2 vis),
+ sigdecs))
+ else
+ (sigify sigdecs d, (siggers, sigdecs))
+ | _ => (sigify sigdecs d, (siggers, sigdecs))
+
+ val (ds, _) = ListUtil.foldlMap doDecl (IS.empty, IS.empty) ds
+ in
+ (ds, sl)
+ end
+
+end
diff --git a/src/source.sml b/src/source.sml
new file mode 100644
index 0000000..2d8c1ed
--- /dev/null
+++ b/src/source.sml
@@ -0,0 +1,192 @@
+(* Copyright (c) 2008, 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 Source = struct
+
+type 'a located = 'a ErrorMsg.located
+
+datatype kind' =
+ KType
+ | KArrow of kind * kind
+ | KName
+ | KRecord of kind
+ | KUnit
+ | KTuple of kind list
+ | KWild
+
+ | KFun of string * kind
+ | KVar of string
+
+withtype kind = kind' located
+
+datatype explicitness =
+ Explicit
+ | Implicit
+
+datatype con' =
+ CAnnot of con * kind
+
+ | TFun of con * con
+ | TCFun of explicitness * string * kind * con
+ | TRecord of con
+ | TDisjoint of con * con * con
+
+ | CVar of string list * string
+ | CApp of con * con
+ | CAbs of string * kind option * con
+
+ | CKAbs of string * con
+ | TKFun of string * con
+
+ | CName of string
+
+ | CRecord of (con * con) list
+ | CConcat of con * con
+ | CMap
+
+ | CUnit
+
+ | CTuple of con list
+ | CProj of con * int
+
+ | CWild of kind
+
+withtype con = con' located
+
+datatype inference =
+ Infer
+ | DontInfer
+ | TypesOnly
+
+datatype sgn_item' =
+ SgiConAbs of string * kind
+ | SgiCon of string * kind option * con
+ | SgiDatatype of (string * string list * (string * con option) list) list
+ | SgiDatatypeImp of string * string list * string
+ | SgiVal of string * con
+ | SgiTable of string * con * exp * exp
+ | SgiStr of string * sgn
+ | SgiSgn of string * sgn
+ | SgiInclude of sgn
+ | SgiConstraint of con * con
+ | SgiClassAbs of string * kind
+ | SgiClass of string * kind * con
+
+and sgn' =
+ SgnConst of sgn_item list
+ | SgnVar of string
+ | SgnFun of string * sgn * sgn
+ | SgnWhere of sgn * string list * string * con
+ | SgnProj of string * string list * string
+
+and pat' =
+ PVar of string
+ | PPrim of Prim.t
+ | PCon of string list * string * pat option
+ | PRecord of (string * pat) list * bool
+ | PAnnot of pat * con
+
+and exp' =
+ EAnnot of exp * con
+
+ | EPrim of Prim.t
+ | EVar of string list * string * inference
+ | EApp of exp * exp
+ | EAbs of string * con option * exp
+ | ECApp of exp * con
+ | ECAbs of explicitness * string * kind * exp
+ | EDisjoint of con * con * exp
+ | EDisjointApp of exp
+
+ | EKAbs of string * exp
+
+ | ERecord of (con * exp) list * bool
+ | EField of exp * con
+ | EConcat of exp * exp
+ | ECut of exp * con
+ | ECutMulti of exp * con
+
+ | EWild
+
+ | ECase of exp * (pat * exp) list
+
+ | ELet of edecl list * exp
+
+and edecl' =
+ EDVal of pat * exp
+ | EDValRec of (string * con option * exp) list
+
+withtype sgn_item = sgn_item' located
+and sgn = sgn' located
+and pat = pat' located
+and exp = exp' located
+and edecl = edecl' located
+
+datatype ffi_mode =
+ Effectful
+ | BenignEffectful
+ | ClientOnly
+ | ServerOnly
+ | JsFunc of string
+
+datatype decl' =
+ DCon of string * kind option * con
+ | DDatatype of (string * string list * (string * con option) list) list
+ | DDatatypeImp of string * string list * string
+ | DVal of pat * exp
+ | DValRec of (string * con option * exp) list
+ | DSgn of string * sgn
+ | DStr of string * sgn option * Time.time option * str * bool (* did this module come from the '-root' directive? *)
+ | DFfiStr of string * sgn * Time.time option
+ | DOpen of string * string list
+ | DConstraint of con * con
+ | DOpenConstraints of string * string list
+ | DExport of str
+ | DTable of string * con * exp * exp
+ | DSequence of string
+ | DView of string * exp
+ | DDatabase of string
+ | DCookie of string * con
+ | DStyle of string
+ | DTask of exp * exp
+ | DPolicy of exp
+ | DOnError of string * string list * string
+ | DFfi of string * ffi_mode list * con
+
+ and str' =
+ StrConst of decl list
+ | StrVar of string
+ | StrProj of str * string
+ | StrFun of string * sgn * sgn option * str
+ | StrApp of str * str
+
+withtype decl = decl' located
+ and str = str' located
+
+type file = decl list
+
+end
diff --git a/src/source_print.sig b/src/source_print.sig
new file mode 100644
index 0000000..f5b0df2
--- /dev/null
+++ b/src/source_print.sig
@@ -0,0 +1,40 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing Ur/Web *)
+
+signature SOURCE_PRINT = sig
+ val p_kind : Source.kind Print.printer
+ val p_explicitness : Source.explicitness Print.printer
+ val p_con : Source.con Print.printer
+ val p_exp : Source.exp Print.printer
+ val p_decl : Source.decl Print.printer
+ val p_edecl : Source.edecl Print.printer
+ val p_sgn_item : Source.sgn_item Print.printer
+ val p_str : Source.str Print.printer
+ val p_file : Source.file Print.printer
+end
diff --git a/src/source_print.sml b/src/source_print.sml
new file mode 100644
index 0000000..e18a82f
--- /dev/null
+++ b/src/source_print.sml
@@ -0,0 +1,728 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Pretty-printing Ur/Web *)
+
+structure SourcePrint :> SOURCE_PRINT = struct
+
+open Print.PD
+open Print
+
+open Source
+
+fun p_kind' par (k, _) =
+ case k of
+ KType => string "Type"
+ | KArrow (k1, k2) => parenIf par (box [p_kind' true k1,
+ space,
+ string "->",
+ space,
+ p_kind k2])
+ | KName => string "Name"
+ | KRecord k => box [string "{", p_kind k, string "}"]
+ | KUnit => string "Unit"
+ | KWild => string "_"
+ | KTuple ks => box [string "(",
+ p_list_sep (box [space, string "*", space]) p_kind ks,
+ string ")"]
+
+ | KVar x => string x
+ | KFun (x, k) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_kind k]
+
+and p_kind k = p_kind' false k
+
+fun p_explicitness e =
+ case e of
+ Explicit => string "::"
+ | Implicit => string ":::"
+
+fun p_con' par (c, _) =
+ case c of
+ CAnnot (c, k) => box [string "(",
+ p_con c,
+ space,
+ string "::",
+ space,
+ p_kind k,
+ string ")"]
+
+ | TFun (t1, t2) => parenIf par (box [p_con' true t1,
+ space,
+ string "->",
+ space,
+ p_con t2])
+ | TCFun (e, x, k, c) => parenIf par (box [string x,
+ space,
+ p_explicitness e,
+ space,
+ p_kind k,
+ space,
+ string "->",
+ space,
+ p_con c])
+ | TRecord (CRecord xcs, _) => box [string "{",
+ p_list (fn (x, c) =>
+ box [p_name x,
+ space,
+ string ":",
+ space,
+ p_con c]) xcs,
+ string "}"]
+ | TRecord c => box [string "$",
+ p_con' true c]
+ | TDisjoint (c1, c2, c3) => parenIf par (box [string "[",
+ p_con c1,
+ space,
+ string "~",
+ space,
+ p_con c2,
+ string "]",
+ space,
+ string "=>",
+ space,
+ p_con c3])
+
+ | CVar (ss, s) => p_list_sep (string ".") string (ss @ [s])
+ | CApp (c1, c2) => parenIf par (box [p_con c1,
+ space,
+ p_con' true c2])
+ | CAbs (x, NONE, c) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "=>",
+ space,
+ p_con c])
+ | CAbs (x, SOME k, c) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind k,
+ space,
+ string "=>",
+ space,
+ p_con c])
+
+
+ | CName s => box [string "#", string s]
+
+ | CRecord xcs => box [string "[",
+ p_list (fn (x, c) =>
+ box [p_con x,
+ space,
+ string "=",
+ space,
+ p_con c]) xcs,
+ string "]"]
+ | CConcat (c1, c2) => parenIf par (box [p_con' true c1,
+ space,
+ string "++",
+ space,
+ p_con c2])
+ | CMap => string "map"
+
+ | CUnit => string "()"
+
+ | CWild k => box [string "(_",
+ space,
+ string "::",
+ space,
+ p_kind k,
+ string ")"]
+
+ | CTuple cs => box [string "(",
+ p_list p_con cs,
+ string ")"]
+ | CProj (c, n) => box [p_con c,
+ string ".",
+ string (Int.toString n)]
+
+ | CKAbs (x, c) => box [string x,
+ space,
+ string "==>",
+ space,
+ p_con c]
+ | TKFun (x, c) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_con c]
+
+and p_con c = p_con' false c
+
+and p_name (all as (c, _)) =
+ case c of
+ CName s => string s
+ | _ => p_con all
+
+fun p_pat' par (p, _) =
+ case p of
+ PVar s => string s
+ | PPrim p => Prim.p_t p
+ | PCon (ms, x, NONE) => p_list_sep (string ".") string (ms @ [x])
+ | PCon (ms, x, SOME p) => parenIf par (box [p_list_sep (string ".") string (ms @ [x]),
+ space,
+ p_pat' true p])
+ | PRecord (xps, flex) =>
+ let
+ val pps = map (fn (x, p) => box [string x, space, string "=", space, p_pat p]) xps
+ in
+ box [string "{",
+ p_list_sep (box [string ",", space]) (fn x => x)
+ (if flex then
+ pps @ [string "..."]
+ else
+ pps),
+ string "}"]
+ end
+
+ | PAnnot (p, t) => box [p_pat p,
+ space,
+ string ":",
+ space,
+ p_con t]
+
+and p_pat x = p_pat' false x
+
+fun p_exp' par (e, _) =
+ case e of
+ EAnnot (e, t) => box [string "(",
+ p_exp e,
+ space,
+ string ":",
+ space,
+ p_con t,
+ string ")"]
+
+ | EPrim p => Prim.p_t p
+ | EVar (ss, s, _) => p_list_sep (string ".") string (ss @ [s])
+ | EApp (e1, e2) => parenIf par (box [p_exp e1,
+ space,
+ p_exp' true e2])
+ | EAbs (x, NONE, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string "=>",
+ space,
+ p_exp e])
+ | EAbs (x, SOME t, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con t,
+ space,
+ string "=>",
+ space,
+ p_exp e])
+ | ECApp (e, c) => parenIf par (box [p_exp e,
+ space,
+ string "[",
+ p_con c,
+ string "]"])
+ | ECAbs (exp, x, k, e) => parenIf par (box [string "fn",
+ space,
+ string x,
+ space,
+ p_explicitness exp,
+ space,
+ p_kind k,
+ space,
+ string "=>",
+ space,
+ p_exp e])
+ | EDisjoint (c1, c2, e) => parenIf par (box [p_con c1,
+ space,
+ string "~",
+ space,
+ p_con c2,
+ space,
+ string "=>",
+ space,
+ p_exp e])
+ | EDisjointApp e => parenIf par (box [p_exp e,
+ space,
+ string "!"])
+
+ | ERecord (xes, flex) => box [string "{",
+ p_list (fn (x, e) =>
+ box [p_name x,
+ space,
+ string "=",
+ space,
+ p_exp e]) xes,
+ if flex then
+ box [string ",",
+ space,
+ string "..."]
+ else
+ box [],
+ string "}"]
+ | EField (e, c) => box [p_exp' true e,
+ string ".",
+ p_con' true c]
+ | EConcat (e1, e2) => parenIf par (box [p_exp' true e1,
+ space,
+ string "++",
+ space,
+ p_exp' true e2])
+ | ECut (e, c) => parenIf par (box [p_exp' true e,
+ space,
+ string "--",
+ space,
+ p_con' true c])
+ | ECutMulti (e, c) => parenIf par (box [p_exp' true e,
+ space,
+ string "---",
+ space,
+ p_con' true c])
+ | ECase (e, pes) => parenIf par (box [string "case",
+ space,
+ p_exp e,
+ space,
+ string "of",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (p, e) => box [p_pat p,
+ space,
+ string "=>",
+ space,
+ p_exp e]) pes])
+
+ | EWild => string "_"
+
+ | ELet (ds, e) => box [string "let",
+ newline,
+ box [p_list_sep newline p_edecl ds],
+ newline,
+ string "in",
+ newline,
+ box [p_exp e],
+ newline,
+ string "end"]
+
+ | EKAbs (x, e) => box [string x,
+ space,
+ string "-->",
+ space,
+ p_exp e]
+
+and p_exp e = p_exp' false e
+
+and p_edecl (d, _) =
+ case d of
+ EDVal (p, e) => box [string "val",
+ space,
+ p_pat p,
+ space,
+ string "=",
+ space,
+ p_exp e]
+ | EDValRec vis => box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) p_vali vis]
+
+and p_vali (x, co, e) =
+ case co of
+ NONE => box [string x,
+ space,
+ string "=",
+ space,
+ p_exp e]
+ | SOME t => box [string x,
+ space,
+ string ":",
+ space,
+ p_con t,
+ space,
+ string "=",
+ space,
+ p_exp e]
+
+
+fun p_datatype (x, xs, cons) =
+ box [string x,
+ p_list_sep (box []) (fn x => box [space, string x]) xs,
+ space,
+ string "=",
+ space,
+ p_list_sep (box [space, string "|", space])
+ (fn (x, NONE) => string x
+ | (x, SOME t) => box [string x, space, string "of", space, p_con t])
+ cons]
+
+fun p_sgn_item (sgi, _) =
+ case sgi of
+ SgiConAbs (x, k) => box [string "con",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind k]
+ | SgiCon (x, NONE, c) => box [string "con",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_con c]
+ | SgiCon (x, SOME k, c) => box [string "con",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind k,
+ space,
+ string "=",
+ space,
+ p_con c]
+ | SgiDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) p_datatype x]
+ | SgiDatatypeImp (x, ms, x') =>
+ box [string "datatype",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ string "datatype",
+ space,
+ p_list_sep (string ".") string (ms @ [x'])]
+ | SgiVal (x, c) => box [string "val",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con c]
+ | SgiTable (x, c, pe, ce) => box [string "table",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con c,
+ space,
+ string "keys",
+ space,
+ p_exp pe,
+ space,
+ string "constraints",
+ space,
+ p_exp ce]
+ | SgiStr (x, sgn) => box [string "structure",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_sgn sgn]
+ | SgiSgn (x, sgn) => box [string "signature",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_sgn sgn]
+ | SgiInclude sgn => box [string "include",
+ space,
+ p_sgn sgn]
+ | SgiConstraint (c1, c2) => box [string "constraint",
+ space,
+ p_con c1,
+ space,
+ string "~",
+ space,
+ p_con c2]
+ | SgiClassAbs (x, k) => box [string "class",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind k]
+ | SgiClass (x, k, c) => box [string "class",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind k,
+ space,
+ string "=",
+ space,
+ p_con c]
+
+and p_sgn (sgn, _) =
+ case sgn of
+ SgnConst sgis => box [string "sig",
+ newline,
+ p_list_sep newline p_sgn_item sgis,
+ newline,
+ string "end"]
+ | SgnVar x => string x
+ | SgnFun (x, sgn, sgn') => box [string "functor",
+ space,
+ string "(",
+ string x,
+ space,
+ string ":",
+ p_sgn sgn,
+ string ")",
+ space,
+ string ":",
+ space,
+ p_sgn sgn']
+ | SgnWhere (sgn, ms, x, c) => box [p_sgn sgn,
+ space,
+ string "where",
+ space,
+ string "con",
+ space,
+ p_list_sep (string ".")
+ string (ms @ [x]),
+ string x,
+ space,
+ string "=",
+ space,
+ p_con c]
+ | SgnProj (m, ms, x) => p_list_sep (string ".") string (m :: ms @ [x])
+
+
+
+fun p_decl ((d, _) : decl) =
+ case d of
+ DCon (x, NONE, c) => box [string "con",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_con c]
+ | DCon (x, SOME k, c) => box [string "con",
+ space,
+ string x,
+ space,
+ string "::",
+ space,
+ p_kind k,
+ space,
+ string "=",
+ space,
+ p_con c]
+ | DDatatype x => box [string "datatype",
+ space,
+ p_list_sep (box [space, string "and", space]) p_datatype x]
+ | DDatatypeImp (x, ms, x') =>
+ box [string "datatype",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ string "datatype",
+ space,
+ p_list_sep (string ".") string (ms @ [x'])]
+ | DVal (p, e) => box [string "val",
+ space,
+ p_pat p,
+ space,
+ string "=",
+ space,
+ p_exp e]
+ | DValRec vis => box [string "val",
+ space,
+ string "rec",
+ space,
+ p_list_sep (box [newline, string "and", space]) p_vali vis]
+
+ | DSgn (x, sgn) => box [string "signature",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_sgn sgn]
+ | DStr (x, NONE, _, str, _) => box [string "structure",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_str str]
+ | DStr (x, SOME sgn, _, str, _) => box [string "structure",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_sgn sgn,
+ space,
+ string "=",
+ space,
+ p_str str]
+ | DFfiStr (x, sgn, _) => box [string "extern",
+ space,
+ string "structure",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_sgn sgn]
+ | DOpen (m, ms) => box [string "open",
+ space,
+ p_list_sep (string ".") string (m :: ms)]
+ | DConstraint (c1, c2) => box [string "constraint",
+ space,
+ p_con c1,
+ space,
+ string "~",
+ space,
+ p_con c2]
+ | DOpenConstraints (m, ms) => box [string "open",
+ space,
+ string "constraints",
+ space,
+ p_list_sep (string ".") string (m :: ms)]
+
+ | DExport str => box [string "export",
+ space,
+ p_str str]
+ | DTable (x, c, pe, ce) => box [string "table",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con c,
+ space,
+ string "keys",
+ space,
+ p_exp pe,
+ space,
+ string "constraints",
+ space,
+ p_exp ce]
+ | DSequence x => box [string "sequence",
+ space,
+ string x]
+ | DView (x, e) => box [string "view",
+ space,
+ string x,
+ space,
+ string "=",
+ space,
+ p_exp e]
+
+ | DDatabase s => box [string "database",
+ space,
+ string s]
+
+ | DCookie (x, c) => box [string "cookie",
+ space,
+ string x,
+ space,
+ string ":",
+ space,
+ p_con c]
+ | DStyle x => box [string "style",
+ space,
+ string x]
+ | DTask (e1, e2) => box [string "task",
+ space,
+ p_exp e1,
+ space,
+ string "=",
+ space,
+ p_exp e2]
+ | DPolicy e1 => box [string "policy",
+ space,
+ p_exp e1]
+ | DOnError _ => string "ONERROR"
+ | DFfi _ => string "FFI"
+
+and p_str (str, _) =
+ case str of
+ StrConst ds => box [string "struct",
+ newline,
+ p_list_sep newline p_decl ds,
+ newline,
+ string "end"]
+ | StrVar x => string x
+ | StrProj (str, x) => box [p_str str,
+ string ".",
+ string x]
+ | StrFun (x, sgn, NONE, str) => box [string "functor",
+ space,
+ string "(",
+ string x,
+ space,
+ string ":",
+ p_sgn sgn,
+ string ")",
+ space,
+ string "=>",
+ space,
+ p_str str]
+ | StrFun (x, sgn, SOME sgn', str) => box [string "functor",
+ space,
+ string "(",
+ string x,
+ space,
+ string ":",
+ p_sgn sgn,
+ string ")",
+ space,
+ string ":",
+ space,
+ p_sgn sgn',
+ space,
+ string "=>",
+ space,
+ p_str str]
+ | StrApp (str1, str2) => box [p_str str1,
+ string "(",
+ p_str str2,
+ string ")"]
+
+val p_file = p_list_sep newline p_decl
+
+end
diff --git a/src/sources b/src/sources
new file mode 100644
index 0000000..52b1bdd
--- /dev/null
+++ b/src/sources
@@ -0,0 +1,272 @@
+$(SRC)/config.sig
+config.sml
+
+$(SRC)/globals.sig
+$(SRC)/globals.sml
+
+$(SRC)/search.sig
+$(SRC)/search.sml
+
+$(SRC)/list_util.sig
+$(SRC)/list_util.sml
+
+$(SRC)/order.sig
+$(SRC)/order.sml
+
+$(SRC)/errormsg.sig
+$(SRC)/errormsg.sml
+
+$(SRC)/print.sig
+$(SRC)/print.sml
+
+$(SRC)/fileio.sig
+$(SRC)/fileio.sml
+
+$(SRC)/settings.sig
+$(SRC)/settings.sml
+
+$(SRC)/http.sig
+$(SRC)/http.sml
+
+$(SRC)/cgi.sig
+$(SRC)/cgi.sml
+
+$(SRC)/fastcgi.sig
+$(SRC)/fastcgi.sml
+
+$(SRC)/static.sig
+$(SRC)/static.sml
+
+$(SRC)/prim.sig
+$(SRC)/prim.sml
+
+$(SRC)/mysql.sig
+$(SRC)/mysql.sml
+
+$(SRC)/sqlite.sig
+$(SRC)/sqlite.sml
+
+$(SRC)/datatype_kind.sml
+
+$(SRC)/export.sig
+$(SRC)/export.sml
+
+$(SRC)/source.sml
+
+$(SRC)/utf8.sig
+$(SRC)/utf8.sml
+
+../xml/entities.sml
+
+urweb.grm
+urweb.lex
+
+$(SRC)/source_print.sig
+$(SRC)/source_print.sml
+
+$(SRC)/elab.sml
+
+$(SRC)/elab_util.sig
+$(SRC)/elab_util.sml
+
+$(SRC)/elab_env.sig
+$(SRC)/elab_env.sml
+
+$(SRC)/elab_print.sig
+$(SRC)/elab_print.sml
+
+$(SRC)/elab_ops.sig
+$(SRC)/elab_ops.sml
+
+$(SRC)/disjoint.sig
+$(SRC)/disjoint.sml
+
+$(SRC)/elab_err.sig
+$(SRC)/elab_err.sml
+
+$(SRC)/mod_db.sig
+$(SRC)/mod_db.sml
+
+$(SRC)/elaborate.sig
+$(SRC)/elaborate.sml
+
+$(SRC)/unnest.sig
+$(SRC)/unnest.sml
+
+$(SRC)/termination.sig
+$(SRC)/termination.sml
+
+$(SRC)/expl.sml
+
+$(SRC)/expl_util.sig
+$(SRC)/expl_util.sml
+
+$(SRC)/expl_env.sig
+$(SRC)/expl_env.sml
+
+$(SRC)/expl_print.sig
+$(SRC)/expl_print.sml
+
+$(SRC)/explify.sig
+$(SRC)/explify.sml
+
+$(SRC)/core.sml
+
+$(SRC)/core_util.sig
+$(SRC)/core_util.sml
+
+$(SRC)/core_env.sig
+$(SRC)/core_env.sml
+
+$(SRC)/core_print.sig
+$(SRC)/core_print.sml
+
+$(SRC)/expl_rename.sig
+$(SRC)/expl_rename.sml
+
+$(SRC)/corify.sig
+$(SRC)/corify.sml
+
+$(SRC)/reduce_local.sig
+$(SRC)/reduce_local.sml
+
+$(SRC)/shake.sig
+$(SRC)/shake.sml
+
+$(SRC)/core_untangle.sig
+$(SRC)/core_untangle.sml
+
+$(SRC)/especialize.sig
+$(SRC)/especialize.sml
+
+$(SRC)/reduce.sig
+$(SRC)/reduce.sml
+
+$(SRC)/unpoly.sig
+$(SRC)/unpoly.sml
+
+$(SRC)/specialize.sig
+$(SRC)/specialize.sml
+
+$(SRC)/rpcify.sig
+$(SRC)/rpcify.sml
+
+$(SRC)/tag.sig
+$(SRC)/tag.sml
+
+$(SRC)/effectize.sig
+$(SRC)/effectize.sml
+
+$(SRC)/marshalcheck.sig
+$(SRC)/marshalcheck.sml
+
+$(SRC)/css.sig
+$(SRC)/css.sml
+
+$(SRC)/mono.sml
+
+$(SRC)/mono_util.sig
+$(SRC)/mono_util.sml
+
+$(SRC)/mono_env.sig
+$(SRC)/mono_env.sml
+
+$(SRC)/mono_print.sig
+$(SRC)/mono_print.sml
+
+$(SRC)/mono_fooify.sig
+$(SRC)/mono_fooify.sml
+
+$(SRC)/sql.sig
+$(SRC)/sql.sml
+
+$(SRC)/union_find_fn.sml
+$(SRC)/multimap_fn.sml
+
+$(SRC)/list_key_fn.sml
+$(SRC)/option_key_fn.sml
+$(SRC)/pair_key_fn.sml
+$(SRC)/triple_key_fn.sml
+
+$(SRC)/cache.sml
+$(SRC)/toy_cache.sml
+$(SRC)/lru_cache.sml
+
+$(SRC)/monoize.sig
+$(SRC)/monoize.sml
+
+$(SRC)/mono_reduce.sig
+$(SRC)/mono_reduce.sml
+
+$(SRC)/mono_opt.sig
+$(SRC)/mono_opt.sml
+
+$(SRC)/untangle.sig
+$(SRC)/untangle.sml
+
+$(SRC)/mono_shake.sig
+$(SRC)/mono_shake.sml
+
+$(SRC)/fuse.sig
+$(SRC)/fuse.sml
+
+$(SRC)/iflow.sig
+$(SRC)/iflow.sml
+
+$(SRC)/sqlcache.sig
+$(SRC)/sqlcache.sml
+
+$(SRC)/name_js.sig
+$(SRC)/name_js.sml
+
+$(SRC)/jscomp.sig
+$(SRC)/jscomp.sml
+
+$(SRC)/pathcheck.sig
+$(SRC)/pathcheck.sml
+
+$(SRC)/sidecheck.sig
+$(SRC)/sidecheck.sml
+
+$(SRC)/sigcheck.sig
+$(SRC)/sigcheck.sml
+
+$(SRC)/mono_inline.sml
+
+$(SRC)/sha1.sig
+$(SRC)/sha1.sml
+
+$(SRC)/cjr.sml
+
+$(SRC)/postgres.sig
+$(SRC)/postgres.sml
+
+$(SRC)/cjr_env.sig
+$(SRC)/cjr_env.sml
+
+$(SRC)/cjr_print.sig
+$(SRC)/cjr_print.sml
+
+$(SRC)/cjrize.sig
+$(SRC)/cjrize.sml
+
+$(SRC)/scriptcheck.sig
+$(SRC)/scriptcheck.sml
+
+$(SRC)/dbmodecheck.sig
+$(SRC)/dbmodecheck.sml
+
+$(SRC)/prepare.sig
+$(SRC)/prepare.sml
+
+$(SRC)/checknest.sig
+$(SRC)/checknest.sml
+
+$(SRC)/compiler.sig
+$(SRC)/compiler.sml
+
+$(SRC)/demo.sig
+$(SRC)/demo.sml
+
+$(SRC)/tutorial.sig
+$(SRC)/tutorial.sml
diff --git a/src/specialize.sig b/src/specialize.sig
new file mode 100644
index 0000000..9b0d1e8
--- /dev/null
+++ b/src/specialize.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Simplify a Core program by repeating polymorphic definitions of datatypes *)
+
+signature SPECIALIZE = sig
+
+ val specialize : Core.file -> Core.file
+
+end
diff --git a/src/specialize.sml b/src/specialize.sml
new file mode 100644
index 0000000..3354525
--- /dev/null
+++ b/src/specialize.sml
@@ -0,0 +1,298 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+(* Simplify a Core program by repeating polymorphic definitions of datatypes *)
+
+structure Specialize :> SPECIALIZE = struct
+
+open Core
+
+structure E = CoreEnv
+structure U = CoreUtil
+
+val liftConInCon = E.liftConInCon
+val subConInCon = E.subConInCon
+
+structure CK = struct
+type ord_key = con list
+val compare = Order.joinL U.Con.compare
+end
+
+structure CM = BinaryMapFn(CK)
+structure IM = IntBinaryMap
+
+type datatyp' = {
+ name : int,
+ constructors : int IM.map
+}
+
+type datatyp = {
+ name : string,
+ params : int,
+ constructors : (string * int * con option) list,
+ specializations : datatyp' CM.map
+}
+
+type state = {
+ count : int,
+ datatypes : datatyp IM.map,
+ constructors : int IM.map,
+ decls : (string * int * string list * (string * int * con option) list) list
+}
+
+fun kind (k, st) = (k, st)
+
+val isOpen = U.Con.exists {kind = fn _ => false,
+ con = fn c =>
+ case c of
+ CRel _ => true
+ | _ => false}
+
+fun considerSpecialization (st : state, n, args, dt : datatyp) =
+ let
+ val args = map ReduceLocal.reduceCon args
+ in
+ case CM.find (#specializations dt, args) of
+ SOME dt' => (#name dt', #constructors dt', st)
+ | NONE =>
+ let
+ (*val () = Print.prefaces "Args" [("n", Print.PD.string (Int.toString n)),
+ ("args", Print.p_list (CorePrint.p_con CoreEnv.empty) args)]*)
+
+ val n' = #count st
+
+ val nxs = length args - 1
+ fun sub t = ListUtil.foldli (fn (i, arg, t) =>
+ subConInCon (nxs - i, arg) t) t args
+
+ val (cons, (count, cmap)) =
+ ListUtil.foldlMap (fn ((x, n, to), (count, cmap)) =>
+ let
+ val to = Option.map sub to
+ in
+ ((x, count, to),
+ (count + 1,
+ IM.insert (cmap, n, count)))
+ end) (n' + 1, IM.empty) (#constructors dt)
+
+ val st = {count = count,
+ datatypes = IM.insert (#datatypes st, n,
+ {name = #name dt,
+ params = #params dt,
+ constructors = #constructors dt,
+ specializations = CM.insert (#specializations dt,
+ args,
+ {name = n',
+ constructors = cmap})}),
+ constructors = #constructors st,
+ decls = #decls st}
+
+ val (cons, st) = ListUtil.foldlMap (fn ((x, n, NONE), st) => ((x, n, NONE), st)
+ | ((x, n, SOME t), st) =>
+ let
+ val (t, st) = specCon st t
+ in
+ ((x, n, SOME t), st)
+ end) st cons
+
+ val dt = (#name dt ^ "_s",
+ n',
+ [],
+ cons)
+ in
+ (n', cmap, {count = #count st,
+ datatypes = #datatypes st,
+ constructors = #constructors st,
+ decls = dt :: #decls st})
+ end
+ end
+
+and con (c, st : state) =
+ let
+ fun findApp (c, args) =
+ case c of
+ CApp ((c', _), arg) => findApp (c', arg :: args)
+ | CNamed n => SOME (n, args)
+ | _ => NONE
+ in
+ case findApp (c, []) of
+ SOME (n, args as (_ :: _)) =>
+ if List.exists isOpen args then
+ (c, st)
+ else
+ (case IM.find (#datatypes st, n) of
+ NONE => (c, st)
+ | SOME dt =>
+ if length args <> #params dt then
+ (c, st)
+ else
+ let
+ val (n, _, st) = considerSpecialization (st, n, args, dt)
+ in
+ (CNamed n, st)
+ end)
+ | _ => (c, st)
+ end
+
+and specCon st = U.Con.foldMap {kind = kind, con = con} st
+
+fun pat (p, st) =
+ case #1 p of
+ PVar _ => (p, st)
+ | PPrim _ => (p, st)
+ | PCon (dk, PConVar pn, args as (_ :: _), po) =>
+ let
+ val (po, st) =
+ case po of
+ NONE => (NONE, st)
+ | SOME p =>
+ let
+ val (p, st) = pat (p, st)
+ in
+ (SOME p, st)
+ end
+ val p = (PCon (dk, PConVar pn, args, po), #2 p)
+ in
+ if List.exists isOpen args then
+ (p, st)
+ else
+ case IM.find (#constructors st, pn) of
+ NONE => (p, st)
+ | SOME n =>
+ case IM.find (#datatypes st, n) of
+ NONE => (p, st)
+ | SOME dt =>
+ let
+ val (n, cmap, st) = considerSpecialization (st, n, args, dt)
+ in
+ case IM.find (cmap, pn) of
+ NONE => raise Fail "Specialize: Missing datatype constructor (pat)"
+ | SOME pn' => ((PCon (dk, PConVar pn', [], po), #2 p), st)
+ end
+ end
+ | PCon (dk, pc, args, SOME p') =>
+ let
+ val (p', st) = pat (p', st)
+ in
+ ((PCon (dk, pc, args, SOME p'), #2 p), st)
+ end
+ | PCon _ => (p, st)
+ | PRecord xps =>
+ let
+ val (xps, st) = ListUtil.foldlMap (fn ((x, p, t), st) =>
+ let
+ val (p, st) = pat (p, st)
+ in
+ ((x, p, t), st)
+ end)
+ st xps
+ in
+ ((PRecord xps, #2 p), st)
+ end
+
+fun exp (e, st) =
+ case e of
+ ECon (dk, PConVar pn, args as (_ :: _), eo) =>
+ if List.exists isOpen args then
+ (e, st)
+ else
+ (case IM.find (#constructors st, pn) of
+ NONE => (e, st)
+ | SOME n =>
+ case IM.find (#datatypes st, n) of
+ NONE => (e, st)
+ | SOME dt =>
+ let
+ val (n, cmap, st) = considerSpecialization (st, n, args, dt)
+ in
+ case IM.find (cmap, pn) of
+ NONE => raise Fail "Specialize: Missing datatype constructor"
+ | SOME pn' => (ECon (dk, PConVar pn', [], eo), st)
+ end)
+ | ECase (e, pes, r) =>
+ let
+ val (pes, st) = ListUtil.foldlMap (fn ((p, e), st) =>
+ let
+ val (p, st) = pat (p, st)
+ in
+ ((p, e), st)
+ end) st pes
+ in
+ (ECase (e, pes, r), st)
+ end
+ | _ => (e, st)
+
+fun decl (d, st) = (d, st)
+
+val specDecl = U.Decl.foldMap {kind = kind, con = con, exp = exp, decl = decl}
+
+fun specialize file =
+ let
+ fun doDecl (d, st) =
+ let
+ (*val () = Print.preface ("decl:", CorePrint.p_decl CoreEnv.empty all)*)
+ val (d, st) = specDecl st d
+ in
+ case #1 d of
+ DDatatype dts =>
+ ((case #decls st of
+ [] => [d]
+ | dts' => [(DDatatype (dts' @ dts), #2 d)]),
+ {count = #count st,
+ datatypes = foldl (fn ((x, n, xs, xnts), dts) =>
+ IM.insert (dts, n,
+ {name = x,
+ params = length xs,
+ constructors = xnts,
+ specializations = CM.empty}))
+ (#datatypes st) dts,
+ constructors = foldl (fn ((x, n, xs, xnts), cs) =>
+ foldl (fn ((_, n', _), constructors) =>
+ IM.insert (constructors, n', n))
+ cs xnts)
+ (#constructors st) dts,
+ decls = []})
+ | _ =>
+ (case #decls st of
+ [] => [d]
+ | dts => [(DDatatype dts, #2 d), d],
+ {count = #count st,
+ datatypes = #datatypes st,
+ constructors = #constructors st,
+ decls = []})
+ end
+
+ val (ds, _) = ListUtil.foldlMapConcat doDecl
+ {count = U.File.maxName file + 1,
+ datatypes = IM.empty,
+ constructors = IM.empty,
+ decls = []} file
+ in
+ ds
+ end
+
+end
diff --git a/src/sql.sig b/src/sql.sig
new file mode 100644
index 0000000..317c157
--- /dev/null
+++ b/src/sql.sig
@@ -0,0 +1,104 @@
+signature SQL = sig
+
+val debug : bool ref
+
+val sqlcacheMode : bool ref
+
+datatype chunk =
+ String of string
+ | Exp of Mono.exp
+
+val chunkify : Mono.exp -> chunk list
+
+type lvar = int
+
+datatype func =
+ DtCon0 of string
+ | DtCon1 of string
+ | UnCon of string
+ | Other of string
+
+datatype exp =
+ Const of Prim.t
+ | Var of int
+ | Lvar of lvar
+ | Func of func * exp list
+ | Recd of (string * exp) list
+ | Proj of exp * string
+
+datatype cmp =
+ Eq
+ | Ne
+ | Lt
+ | Le
+ | Gt
+ | Ge
+
+datatype reln =
+ Known
+ | Sql of string
+ | PCon0 of string
+ | PCon1 of string
+ | Cmp of cmp
+
+datatype lop =
+ And
+ | Or
+
+datatype prop =
+ True
+ | False
+ | Unknown
+ | Lop of lop * prop * prop
+ | Reln of reln * exp list
+ | Cond of exp * prop
+
+type 'a parser
+
+val parse : 'a parser -> Mono.exp -> 'a option
+
+datatype Rel =
+ RCmp of cmp
+ | RLop of lop
+
+datatype sqexp =
+ SqConst of Prim.t
+ | SqTrue
+ | SqFalse
+ | SqNot of sqexp
+ | Field of string * string
+ | Computed of string
+ | Binop of Rel * sqexp * sqexp
+ | SqKnown of sqexp
+ | Inj of Mono.exp
+ | SqFunc of string * sqexp
+ | Unmodeled
+ | Null
+
+datatype ('a,'b) sum = inl of 'a | inr of 'b
+
+datatype sitem =
+ SqField of string * string
+ | SqExp of sqexp * string
+
+datatype jtype = Inner | Left | Right | Full
+
+datatype fitem =
+ Table of string * string (* table AS name *)
+ | Join of jtype * fitem * fitem * sqexp
+ | Nested of query * string (* query AS name *)
+
+ and query =
+ Query1 of {Select : sitem list, From : fitem list, Where : sqexp option}
+ | Union of query * query
+
+val query : query parser
+
+datatype dml =
+ Insert of string * (string * sqexp) list
+ | Delete of string * sqexp
+ | Update of string * (string * sqexp) list * sqexp
+
+val dml : dml parser
+
+end
diff --git a/src/sql.sml b/src/sql.sml
new file mode 100644
index 0000000..409e205
--- /dev/null
+++ b/src/sql.sml
@@ -0,0 +1,509 @@
+structure Sql :> SQL = struct
+
+open Mono
+
+val debug = ref false
+
+type lvar = int
+
+datatype func =
+ DtCon0 of string
+ | DtCon1 of string
+ | UnCon of string
+ | Other of string
+
+datatype exp =
+ Const of Prim.t
+ | Var of int
+ | Lvar of lvar
+ | Func of func * exp list
+ | Recd of (string * exp) list
+ | Proj of exp * string
+
+datatype cmp =
+ Eq
+ | Ne
+ | Lt
+ | Le
+ | Gt
+ | Ge
+
+datatype reln =
+ Known
+ | Sql of string
+ | PCon0 of string
+ | PCon1 of string
+ | Cmp of cmp
+
+datatype lop =
+ And
+ | Or
+
+datatype prop =
+ True
+ | False
+ | Unknown
+ | Lop of lop * prop * prop
+ | Reln of reln * exp list
+ | Cond of exp * prop
+
+datatype chunk =
+ String of string
+ | Exp of Mono.exp
+
+fun chunkify e =
+ case #1 e of
+ EPrim (Prim.String (_, s)) => [String s]
+ | EStrcat (e1, e2) =>
+ let
+ val chs1 = chunkify e1
+ val chs2 = chunkify e2
+ in
+ case chs2 of
+ String s2 :: chs2' =>
+ (case List.last chs1 of
+ String s1 => List.take (chs1, length chs1 - 1) @ String (s1 ^ s2) :: chs2'
+ | _ => chs1 @ chs2)
+ | _ => chs1 @ chs2
+ end
+ | _ => [Exp e]
+
+type 'a parser = chunk list -> ('a * chunk list) option
+
+fun always v chs = SOME (v, chs)
+
+fun parse p s =
+ case p (chunkify s) of
+ SOME (v, []) => SOME v
+ | _ => NONE
+
+fun const s chs =
+ case chs of
+ String s' :: chs => if String.isPrefix s s' then
+ SOME ((), if size s = size s' then
+ chs
+ else
+ String (String.extract (s', size s, NONE)) :: chs)
+ else
+ NONE
+ | _ => NONE
+
+fun follow p1 p2 chs =
+ case p1 chs of
+ NONE => NONE
+ | SOME (v1, chs) =>
+ case p2 chs of
+ NONE => NONE
+ | SOME (v2, chs) => SOME ((v1, v2), chs)
+
+fun wrap p f chs =
+ case p chs of
+ NONE => NONE
+ | SOME (v, chs) => SOME (f v, chs)
+
+fun wrapP p f chs =
+ case p chs of
+ NONE => NONE
+ | SOME (v, chs) =>
+ case f v of
+ NONE => NONE
+ | SOME r => SOME (r, chs)
+
+fun alt p1 p2 chs =
+ case p1 chs of
+ NONE => p2 chs
+ | v => v
+
+fun altL ps =
+ case rev ps of
+ [] => (fn _ => NONE)
+ | p :: ps =>
+ foldl (fn (p1, p2) => alt p1 p2) p ps
+
+fun opt p chs =
+ case p chs of
+ NONE => SOME (NONE, chs)
+ | SOME (v, chs) => SOME (SOME v, chs)
+
+fun skip cp chs =
+ case chs of
+ String "" :: chs => skip cp chs
+ | String s :: chs' => if cp (String.sub (s, 0)) then
+ skip cp (String (String.extract (s, 1, NONE)) :: chs')
+ else
+ SOME ((), chs)
+ | _ => SOME ((), chs)
+
+fun keep cp chs =
+ case chs of
+ String "" :: chs => keep cp chs
+ | String s :: chs' =>
+ let
+ val (befor, after) = Substring.splitl cp (Substring.full s)
+ in
+ if Substring.isEmpty befor then
+ NONE
+ else
+ SOME (Substring.string befor,
+ if Substring.isEmpty after then
+ chs'
+ else
+ String (Substring.string after) :: chs')
+ end
+ | _ => NONE
+
+(* Used by primSqlcache. *)
+fun optConst s chs =
+ case chs of
+ String s' :: chs => if String.isPrefix s s' then
+ SOME (s, if size s = size s' then
+ chs
+ else
+ String (String.extract (s', size s, NONE)) :: chs)
+ else
+ SOME ("", String s' :: chs)
+ | _ => NONE
+
+fun ws p = wrap (follow (skip (fn ch => ch = #" "))
+ (follow p (skip (fn ch => ch = #" ")))) (#1 o #2)
+
+fun log name p chs =
+ (if !debug then
+ (print (name ^ ": ");
+ app (fn String s => print s
+ | _ => print "???") chs;
+ print "\n")
+ else
+ ();
+ p chs)
+
+fun list p chs =
+ altL [wrap (follow p (follow (ws (const ",")) (list p)))
+ (fn (v, ((), ls)) => v :: ls),
+ wrap (ws p) (fn v => [v]),
+ always []] chs
+
+val ident = keep (fn ch => Char.isAlphaNum ch orelse ch = #"_")
+
+val t_ident = wrapP ident (fn s => if String.isPrefix "T_" s then
+ SOME (String.extract (s, 2, NONE))
+ else
+ NONE)
+val uw_ident = wrapP ident (fn s => if String.isPrefix "uw_" s andalso size s >= 4 then
+ SOME (str (Char.toUpper (String.sub (s, 3)))
+ ^ String.extract (s, 4, NONE))
+ else
+ SOME s)
+
+val field = wrap (follow (opt (follow t_ident (const ".")))
+ uw_ident)
+ (fn (SOME (t, ()), f) => (t, f)
+ | (NONE, f) => ("T", f)) (* Should probably deal with this MySQL/SQLite case better some day. *)
+
+datatype Rel =
+ RCmp of cmp
+ | RLop of lop
+
+datatype sqexp =
+ SqConst of Prim.t
+ | SqTrue
+ | SqFalse
+ | SqNot of sqexp
+ | Field of string * string
+ | Computed of string
+ | Binop of Rel * sqexp * sqexp
+ | SqKnown of sqexp
+ | Inj of Mono.exp
+ | SqFunc of string * sqexp
+ | Unmodeled
+ | Null
+
+fun cmp s r = wrap (const s) (fn () => RCmp r)
+
+val sqbrel = altL [cmp "=" Eq,
+ cmp "IS NOT DISTINCT FROM" Eq,
+ cmp "<>" Ne,
+ cmp "<=" Le,
+ cmp "<" Lt,
+ cmp ">=" Ge,
+ cmp ">" Gt,
+ wrap (const "AND") (fn () => RLop And),
+ wrap (const "OR") (fn () => RLop Or)]
+
+datatype ('a, 'b) sum = inl of 'a | inr of 'b
+
+fun string chs =
+ case chs of
+ String s :: chs =>
+ if size s >= 2 andalso String.sub (s, 0) = #"'" then
+ let
+ fun loop (cs, acc) =
+ case cs of
+ [] => NONE
+ | c :: cs =>
+ if c = #"'" then
+ SOME (String.implode (rev acc), cs)
+ else if c = #"\\" then
+ case cs of
+ c :: cs => loop (cs, c :: acc)
+ | _ => raise Fail "Iflow.string: Unmatched backslash escape"
+ else
+ loop (cs, c :: acc)
+ in
+ case loop (String.explode (String.extract (s, 1, NONE)), []) of
+ NONE => NONE
+ | SOME (s, []) => SOME (s, chs)
+ | SOME (s, cs) => SOME (s, String (String.implode cs) :: chs)
+ end
+ else
+ NONE
+ | _ => NONE
+
+val prim =
+ altL [wrap (follow (wrapP (follow (keep Char.isDigit) (follow (const ".") (keep Char.isDigit)))
+ (fn (x, ((), y)) => Option.map Prim.Float (Real64.fromString (x ^ "." ^ y))))
+ (opt (const "::float8"))) #1,
+ wrap (follow (wrapP (keep Char.isDigit)
+ (Option.map Prim.Int o Int64.fromString))
+ (opt (const "::int8"))) #1,
+ wrap (follow (opt (const "E")) (follow string (opt (const "::text"))))
+ ((fn s => Prim.String (Prim.Normal, s)) o #1 o #2)]
+
+val primSqlcache =
+ (* Like [prim], but always uses [Prim.String]s. *)
+ let
+ fun wrapS p f = wrap p ((fn s => Prim.String (Prim.Normal, s)) o f)
+ in
+ altL [wrapS (follow (wrap (follow (keep Char.isDigit)
+ (follow (const ".") (keep Char.isDigit)))
+ (fn (x, ((), y)) => x ^ "." ^ y))
+ (optConst "::float8"))
+ op^,
+ wrapS (follow (keep Char.isDigit)
+ (optConst "::int8"))
+ op^,
+ wrapS (follow (optConst "E") (follow string (optConst "::text")))
+ (fn (c1, (s, c2)) => c1 ^ s ^ c2)]
+end
+
+fun known' chs =
+ case chs of
+ Exp (EFfi ("Basis", "sql_known"), _) :: chs => SOME ((), chs)
+ | _ => NONE
+
+fun sqlify chs =
+ case chs of
+ Exp (EFfiApp ("Basis", f, [(e, _)]), _) :: chs =>
+ if String.isPrefix "sqlify" f then
+ SOME (e, chs)
+ else
+ NONE
+ | Exp (ECase (e, [((PCon (_, PConFfi {mod = "Basis", con = "True", ...}, NONE), _),
+ (EPrim (Prim.String (Prim.Normal, "TRUE")), _)),
+ ((PCon (_, PConFfi {mod = "Basis", con = "False", ...}, NONE), _),
+ (EPrim (Prim.String (Prim.Normal, "FALSE")), _))], _), _) :: chs =>
+ SOME (e, chs)
+
+ | _ => NONE
+
+(* For sqlcache, we only care that we can do string equality on injected Mono
+ expressions, so accept any expression without modifying it. *)
+val sqlifySqlcache =
+ fn Exp e :: chs => SOME (e, chs)
+ | _ => NONE
+
+fun constK s = wrap (const s) (fn () => s)
+
+val funcName = altL [constK "COUNT",
+ constK "MIN",
+ constK "MAX",
+ constK "SUM",
+ constK "AVG"]
+
+fun arithmetic pExp = follow (const "(")
+ (follow pExp
+ (follow (altL (map const [" + ", " - ", " * ", " / ", " >> ", " << "]))
+ (follow pExp (const ")"))))
+
+val unmodeled = altL [const "COUNT(*)",
+ const "CURRENT_TIMESTAMP"]
+
+val sqlcacheMode = ref false;
+
+fun sqexp chs =
+ log "sqexp"
+ (altL [wrap (if !sqlcacheMode then primSqlcache else prim) SqConst,
+ wrap (const "TRUE") (fn () => SqTrue),
+ wrap (const "FALSE") (fn () => SqFalse),
+ wrap (follow (const "NULL::") ident) (fn ((), _) => Null),
+ wrap (const "NULL") (fn () => Null),
+ wrap known SqKnown,
+ wrap func SqFunc,
+ wrap field Field,
+ wrap uw_ident Computed,
+ wrap (arithmetic sqexp) (fn _ => Unmodeled),
+ wrap unmodeled (fn () => Unmodeled),
+ wrap (if !sqlcacheMode then sqlifySqlcache else sqlify) Inj,
+ wrap (follow (const "COALESCE(") (follow sqexp (follow (const ",")
+ (follow (keep (fn ch => ch <> #")")) (const ")")))))
+ (fn ((), (e, _)) => e),
+ wrap (follow (const "(NOT ") (follow sqexp (const ")")))
+ (fn ((), (e, _)) => SqNot e),
+ wrap (follow (ws (const "("))
+ (follow (wrap
+ (follow sqexp
+ (alt
+ (wrap
+ (follow (ws sqbrel)
+ (ws sqexp))
+ inl)
+ (always (inr ()))))
+ (fn (e1, sm) =>
+ case sm of
+ inl (bo, e2) => Binop (bo, e1, e2)
+ | inr () => e1))
+ (const ")")))
+ (fn ((), (e, ())) => e)])
+ chs
+
+and known chs = wrap (follow known' (follow (const "(") (follow sqexp (const ")"))))
+ (fn ((), ((), (e, ()))) => e) chs
+
+and func chs = wrap (follow funcName (follow (const "(") (follow sqexp (const ")"))))
+ (fn (f, ((), (e, ()))) => (f, e)) chs
+
+datatype sitem =
+ SqField of string * string
+ | SqExp of sqexp * string
+
+val sitem = alt (wrap (follow sqexp (follow (const " AS ") uw_ident))
+ (fn (e, ((), s)) => SqExp (e, s)))
+ (wrap field SqField)
+
+val select = log "select"
+ (wrap (follow (const "SELECT ") (list sitem))
+ (fn ((), ls) => ls))
+
+datatype jtype = Inner | Left | Right | Full
+
+datatype fitem =
+ Table of string * string (* table AS name *)
+ | Join of jtype * fitem * fitem * sqexp
+ | Nested of query * string (* query AS name *)
+
+ and query =
+ Query1 of {Select : sitem list, From : fitem list, Where : sqexp option}
+ | Union of query * query
+
+val wher = wrap (follow (ws (const "WHERE ")) sqexp)
+ (fn ((), ls) => ls)
+
+val orderby = log "orderby"
+ (wrap (follow (ws (const "ORDER BY "))
+ (list (follow sqexp
+ (opt (ws (const "DESC"))))))
+ ignore)
+
+val groupby = log "groupby"
+ (wrap (follow (ws (const "GROUP BY "))
+ (list sqexp))
+ ignore)
+
+val jtype = altL [wrap (const "JOIN") (fn () => Inner),
+ wrap (const "LEFT JOIN") (fn () => Left),
+ wrap (const "RIGHT JOIN") (fn () => Right),
+ wrap (const "FULL JOIN") (fn () => Full)]
+
+fun fitem chs = altL [wrap (follow uw_ident
+ (follow (const " AS ")
+ t_ident))
+ (fn (t, ((), f)) => Table (t, f)),
+ wrap (follow (const "(")
+ (follow fitem
+ (follow (ws jtype)
+ (follow fitem
+ (follow (const " ON ")
+ (follow sqexp
+ (const ")")))))))
+ (fn ((), (fi1, (jt, (fi2, ((), (se, ())))))) =>
+ Join (jt, fi1, fi2, se)),
+ wrap (follow (const "(")
+ (follow query
+ (follow (const ") AS ") t_ident)))
+ (fn ((), (q, ((), f))) => Nested (q, f))]
+ chs
+
+and query1 chs = log "query1"
+ (wrap (follow (follow select from) (opt wher))
+ (fn ((fs, ts), wher) => {Select = fs, From = ts, Where = wher}))
+ chs
+
+and from chs = log "from"
+ (wrap (follow (const "FROM ") (list fitem))
+ (fn ((), ls) => ls))
+ chs
+
+and query chs = log "query"
+ (wrap (follow
+ (alt (wrap (follow (const "((")
+ (follow query
+ (follow (const ") UNION (")
+ (follow query (const "))")))))
+ (fn ((), (q1, ((), (q2, ())))) => Union (q1, q2)))
+ (wrap query1 Query1))
+ (follow (opt groupby) (opt orderby)))
+ #1)
+ chs
+
+datatype dml =
+ Insert of string * (string * sqexp) list
+ | Delete of string * sqexp
+ | Update of string * (string * sqexp) list * sqexp
+
+val insert = log "insert"
+ (wrapP (follow (const "INSERT INTO ")
+ (follow uw_ident
+ (follow (const " (")
+ (follow (list uw_ident)
+ (follow (const ") VALUES (")
+ (follow (list sqexp)
+ (const ")")))))))
+ (fn ((), (tab, ((), (fs, ((), (es, ())))))) =>
+ (SOME (tab, ListPair.zipEq (fs, es)))
+ handle ListPair.UnequalLengths => NONE))
+
+val delete = log "delete"
+ (wrap (follow (const "DELETE FROM ")
+ (follow uw_ident
+ (follow (opt (const " AS T_T"))
+ (opt (follow (const " WHERE ") sqexp)))))
+ (fn ((), (tab, (_, wher))) => (tab, case wher of
+ SOME (_, es) => es
+ | NONE => SqTrue)))
+
+val setting = log "setting"
+ (wrap (follow uw_ident (follow (const " = ") sqexp))
+ (fn (f, ((), e)) => (f, e)))
+
+val update = log "update"
+ (wrap (follow (const "UPDATE ")
+ (follow uw_ident
+ (follow (follow (opt (const " AS T_T")) (const " SET "))
+ (follow (list setting)
+ (follow (ws (const "WHERE "))
+ sqexp)))))
+ (fn ((), (tab, (_, (fs, ((), e))))) =>
+ (tab, fs, e)))
+
+val dml = log "dml"
+ (altL [wrap insert Insert,
+ wrap delete Delete,
+ wrap update Update])
+
+datatype querydml =
+ Query of query
+ | Dml of dml
+
+val querydml = log "querydml" (altL [wrap dml Dml, wrap query Query])
+
+end
diff --git a/src/sqlcache.sig b/src/sqlcache.sig
new file mode 100644
index 0000000..e264c1f
--- /dev/null
+++ b/src/sqlcache.sig
@@ -0,0 +1,11 @@
+signature SQLCACHE = sig
+
+val setCache : Cache.cache -> unit
+val getCache : unit -> Cache.cache
+
+val setHeuristic : string -> unit
+
+val getFfiInfo : unit -> {index : int, params : int} list
+val go : Mono.file -> Mono.file
+
+end
diff --git a/src/sqlcache.sml b/src/sqlcache.sml
new file mode 100644
index 0000000..83a264f
--- /dev/null
+++ b/src/sqlcache.sml
@@ -0,0 +1,1732 @@
+structure Sqlcache :> SQLCACHE = struct
+
+
+(*********************)
+(* General Utilities *)
+(*********************)
+
+structure IK = struct type ord_key = int val compare = Int.compare end
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+structure SK = struct type ord_key = string val compare = String.compare end
+structure SS = BinarySetFn(SK)
+structure SM = BinaryMapFn(SK)
+structure IIMM = MultimapFn(structure KeyMap = IM structure ValSet = IS)
+structure SIMM = MultimapFn(structure KeyMap = SM structure ValSet = IS)
+
+fun id x = x
+
+fun iterate f n x = if n < 0
+ then raise Fail "Can't iterate function negative number of times."
+ else if n = 0
+ then x
+ else iterate f (n-1) (f x)
+
+(* From the MLton wiki. *)
+infix 3 <\ fun x <\ f = fn y => f (x, y) (* Left section *)
+infix 3 \> fun f \> y = f y (* Left application *)
+
+fun mapFst f (x, y) = (f x, y)
+
+(* Option monad. *)
+fun obind (x, f) = Option.mapPartial f x
+fun oguard (b, x) = if b then x () else NONE
+fun omap f = fn SOME x => SOME (f x) | _ => NONE
+fun omap2 f = fn (SOME x, SOME y) => SOME (f (x,y)) | _ => NONE
+fun osequence ys = List.foldr (omap2 op::) (SOME []) ys
+
+fun concatMap f xs = List.concat (map f xs)
+
+val rec cartesianProduct : 'a list list -> 'a list list =
+ fn [] => [[]]
+ | (xs :: xss) => concatMap (fn ys => concatMap (fn x => [x :: ys]) xs)
+ (cartesianProduct xss)
+
+fun indexOf test =
+ let
+ fun f n =
+ fn [] => NONE
+ | (x::xs) => if test x then SOME n else f (n+1) xs
+ in
+ f 0
+ end
+
+
+(************)
+(* Settings *)
+(************)
+
+open Mono
+
+(* Filled in by [addFlushing]. *)
+val ffiInfoRef : {index : int, params : int} list ref = ref []
+
+fun resetFfiInfo () = ffiInfoRef := []
+
+fun getFfiInfo () = !ffiInfoRef
+
+(* Some FFIs have writing as their only effect, which the caching records. *)
+val ffiEffectful =
+ (* ASK: how can this be less hard-coded? *)
+ let
+ val okayWrites = SS.fromList ["htmlifyInt_w",
+ "htmlifyFloat_w",
+ "htmlifyString_w",
+ "htmlifyBool_w",
+ "htmlifyTime_w",
+ "attrifyInt_w",
+ "attrifyFloat_w",
+ "attrifyString_w",
+ "attrifyChar_w",
+ "urlifyInt_w",
+ "urlifyFloat_w",
+ "urlifyString_w",
+ "urlifyBool_w",
+ "urlifyChannel_w"]
+ in
+ (* ASK: is it okay to hardcode Sqlcache functions as effectful? *)
+ fn (m, f) => Settings.isEffectful (m, f)
+ andalso not (m = "Basis" andalso SS.member (okayWrites, f))
+ end
+
+val cacheRef = ref LruCache.cache
+fun setCache c = cacheRef := c
+fun getCache () = !cacheRef
+
+datatype heuristic = Smart | Always | Never | NoPureAll | NoPureOne | NoCombo
+
+val heuristicRef = ref NoPureOne
+fun setHeuristic h = heuristicRef := (case h of
+ "smart" => Smart
+ | "always" => Always
+ | "never" => Never
+ | "nopureall" => NoPureAll
+ | "nopureone" => NoPureOne
+ | "nocombo" => NoCombo
+ | _ => raise Fail "Sqlcache: setHeuristic")
+fun getHeuristic () = !heuristicRef
+
+
+(************************)
+(* Really Useful Things *)
+(************************)
+
+(* Used to have type context for local variables in MonoUtil functions. *)
+val doBind =
+ fn (env, MonoUtil.Exp.RelE (x, t)) => MonoEnv.pushERel env x t NONE
+ | (env, MonoUtil.Exp.NamedE (x, n, t, eo, s)) => MonoEnv.pushENamed env x n t eo s
+ | (env, MonoUtil.Exp.Datatype (x, n, cs)) => MonoEnv.pushDatatype env x n cs
+
+val dummyLoc = ErrorMsg.dummySpan
+
+(* DEBUG *)
+fun printExp msg exp =
+ (Print.preface ("SQLCACHE: " ^ msg ^ ":", MonoPrint.p_exp MonoEnv.empty exp); exp)
+fun printExp' msg exp' = (printExp msg (exp', dummyLoc); exp')
+fun printTyp msg typ =
+ (Print.preface ("SQLCACHE: " ^ msg ^ ":", MonoPrint.p_typ MonoEnv.empty typ); typ)
+fun printTyp' msg typ' = (printTyp msg (typ', dummyLoc); typ')
+fun obindDebug printer (x, f) =
+ case x of
+ NONE => NONE
+ | SOME x' => case f x' of
+ NONE => (printer (); NONE)
+ | y => y
+
+
+(*******************)
+(* Effect Analysis *)
+(*******************)
+
+(* TODO: test this. *)
+fun transitiveAnalysis doVal state (decls, _) =
+ let
+ val doDecl =
+ fn ((DVal v, _), state) => doVal (v, state)
+ (* Pass over the list of values a number of times equal to its size,
+ making sure whatever property we're testing propagates everywhere
+ it should. This is analagous to the Bellman-Ford algorithm. *)
+ | ((DValRec vs, _), state) =>
+ iterate (fn state => List.foldl doVal state vs) (length vs) state
+ | (_, state) => state
+ in
+ List.foldl doDecl state decls
+ end
+
+(* Makes an exception for [EWrite] (which is recorded when caching). *)
+fun effectful (effs : IS.set) =
+ let
+ val isFunction =
+ fn (TFun _, _) => true
+ | _ => false
+ fun doExp (env, e) =
+ case e of
+ EPrim _ => false
+ (* For now: variables of function type might be effectful, but
+ others are fully evaluated and are therefore not effectful. *)
+ | ERel n => isFunction (#2 (MonoEnv.lookupERel env n))
+ | ENamed n => IS.member (effs, n)
+ | EFfi (m, f) => ffiEffectful (m, f)
+ | EFfiApp (m, f, _) => ffiEffectful (m, f)
+ (* These aren't effectful unless a subexpression is. *)
+ | ECon _ => false
+ | ENone _ => false
+ | ESome _ => false
+ | EApp _ => false
+ | EAbs _ => false
+ | EUnop _ => false
+ | EBinop _ => false
+ | ERecord _ => false
+ | EField _ => false
+ | ECase _ => false
+ | EStrcat _ => false
+ (* EWrite is a special exception because we record writes when caching. *)
+ | EWrite _ => false
+ | ESeq _ => false
+ | ELet _ => false
+ | EUnurlify _ => false
+ (* ASK: what should we do about closures? *)
+ (* Everything else is some sort of effect. We could flip this and
+ explicitly list bits of Mono that are effectful, but this is
+ conservatively robust to future changes (however unlikely). *)
+ | _ => true
+ in
+ MonoUtil.Exp.existsB {typ = fn _ => false, exp = doExp, bind = doBind}
+ end
+
+(* TODO: test this. *)
+fun effectfulDecls file =
+ transitiveAnalysis (fn ((_, name, _, e, _), effs) =>
+ if effectful effs MonoEnv.empty e
+ then IS.add (effs, name)
+ else effs)
+ IS.empty
+ file
+
+
+(*********************************)
+(* Boolean Formula Normalization *)
+(*********************************)
+
+datatype junctionType = Conj | Disj
+
+datatype 'atom formula =
+ Atom of 'atom
+ | Negate of 'atom formula
+ | Combo of junctionType * 'atom formula list
+
+(* Guaranteed to have all negation pushed to the atoms. *)
+datatype 'atom formula' =
+ Atom' of 'atom
+ | Combo' of junctionType * 'atom formula' list
+
+val flipJt = fn Conj => Disj | Disj => Conj
+
+(* Pushes all negation to the atoms.*)
+fun pushNegate (normalizeAtom : bool * 'atom -> 'atom) (negating : bool) =
+ fn Atom x => Atom' (normalizeAtom (negating, x))
+ | Negate f => pushNegate normalizeAtom (not negating) f
+ | Combo (j, fs) => Combo' (if negating then flipJt j else j,
+ map (pushNegate normalizeAtom negating) fs)
+
+val rec flatten =
+ fn Combo' (_, [f]) => flatten f
+ | Combo' (j, fs) =>
+ Combo' (j, List.foldr (fn (f, acc) =>
+ case f of
+ Combo' (j', fs') =>
+ if j = j' orelse length fs' = 1
+ then fs' @ acc
+ else f :: acc
+ | _ => f :: acc)
+ []
+ (map flatten fs))
+ | f => f
+
+(* [simplify] operates on the desired normal form. E.g., if [junc] is [Disj],
+ consider the list of lists to be a disjunction of conjunctions. *)
+fun normalize' (simplify : 'a list list -> 'a list list)
+ (junc : junctionType) =
+ let
+ fun norm junc =
+ simplify
+ o (fn Atom' x => [[x]]
+ | Combo' (j, fs) =>
+ let
+ val fss = map (norm junc) fs
+ in
+ if j = junc
+ then List.concat fss
+ else map List.concat (cartesianProduct fss)
+ end)
+ in
+ norm junc
+ end
+
+fun normalize simplify normalizeAtom junc =
+ normalize' simplify junc
+ o flatten
+ o pushNegate normalizeAtom false
+
+fun mapFormula mf =
+ fn Atom x => Atom (mf x)
+ | Negate f => Negate (mapFormula mf f)
+ | Combo (j, fs) => Combo (j, map (mapFormula mf) fs)
+
+fun mapFormulaExps mf = mapFormula (fn (cmp, e1, e2) => (cmp, mf e1, mf e2))
+
+
+(****************)
+(* SQL Analysis *)
+(****************)
+
+structure CmpKey = struct
+
+ type ord_key = Sql.cmp
+
+ val compare =
+ fn (Sql.Eq, Sql.Eq) => EQUAL
+ | (Sql.Eq, _) => LESS
+ | (_, Sql.Eq) => GREATER
+ | (Sql.Ne, Sql.Ne) => EQUAL
+ | (Sql.Ne, _) => LESS
+ | (_, Sql.Ne) => GREATER
+ | (Sql.Lt, Sql.Lt) => EQUAL
+ | (Sql.Lt, _) => LESS
+ | (_, Sql.Lt) => GREATER
+ | (Sql.Le, Sql.Le) => EQUAL
+ | (Sql.Le, _) => LESS
+ | (_, Sql.Le) => GREATER
+ | (Sql.Gt, Sql.Gt) => EQUAL
+ | (Sql.Gt, _) => LESS
+ | (_, Sql.Gt) => GREATER
+ | (Sql.Ge, Sql.Ge) => EQUAL
+
+end
+
+val rec chooseTwos : 'a list -> ('a * 'a) list =
+ fn [] => []
+ | x :: ys => map (fn y => (x, y)) ys @ chooseTwos ys
+
+fun removeRedundant madeRedundantBy zs =
+ let
+ fun removeRedundant' (xs, ys) =
+ case xs of
+ [] => ys
+ | x :: xs' =>
+ removeRedundant' (xs',
+ if List.exists (fn y => madeRedundantBy (x, y)) (xs' @ ys)
+ then ys
+ else x :: ys)
+ in
+ removeRedundant' (zs, [])
+ end
+
+datatype atomExp =
+ True
+ | False
+ | QueryArg of int
+ | DmlRel of int
+ | Prim of Prim.t
+ | Field of string * string
+
+structure AtomExpKey : ORD_KEY = struct
+
+ type ord_key = atomExp
+
+ val compare =
+ fn (True, True) => EQUAL
+ | (True, _) => LESS
+ | (_, True) => GREATER
+ | (False, False) => EQUAL
+ | (False, _) => LESS
+ | (_, False) => GREATER
+ | (QueryArg n1, QueryArg n2) => Int.compare (n1, n2)
+ | (QueryArg _, _) => LESS
+ | (_, QueryArg _) => GREATER
+ | (DmlRel n1, DmlRel n2) => Int.compare (n1, n2)
+ | (DmlRel _, _) => LESS
+ | (_, DmlRel _) => GREATER
+ | (Prim p1, Prim p2) => Prim.compare (p1, p2)
+ | (Prim _, _) => LESS
+ | (_, Prim _) => GREATER
+ | (Field (t1, f1), Field (t2, f2)) =>
+ case String.compare (t1, t2) of
+ EQUAL => String.compare (f1, f2)
+ | ord => ord
+
+end
+
+structure AtomOptionKey = OptionKeyFn(AtomExpKey)
+
+val rec tablesOfQuery =
+ fn Sql.Query1 {From = fitems, ...} => List.foldl SS.union SS.empty (map tableOfFitem fitems)
+ | Sql.Union (q1, q2) => SS.union (tablesOfQuery q1, tablesOfQuery q2)
+and tableOfFitem =
+ fn Sql.Table (t, _) => SS.singleton t
+ | Sql.Nested (q, _) => tablesOfQuery q
+ | Sql.Join (_, f1, f2, _) => SS.union (tableOfFitem f1, tableOfFitem f2)
+
+val tableOfDml =
+ fn Sql.Insert (tab, _) => tab
+ | Sql.Delete (tab, _) => tab
+ | Sql.Update (tab, _, _) => tab
+
+val freeVars =
+ MonoUtil.Exp.foldB
+ {typ = #2,
+ exp = fn (bound, ERel n, vars) => if n < bound
+ then vars
+ else IS.add (vars, n - bound)
+ | (_, _, vars) => vars,
+ bind = fn (bound, MonoUtil.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+ 0
+ IS.empty
+
+(* A path is a number of field projections of a variable. *)
+type path = int * string list
+structure PK = PairKeyFn(structure I = IK structure J = ListKeyFn(SK))
+structure PS = BinarySetFn(PK)
+
+val pathOfExp =
+ let
+ fun readFields acc exp =
+ acc
+ <\obind\>
+ (fn fs =>
+ case #1 exp of
+ ERel n => SOME (n, fs)
+ | EField (exp, f) => readFields (SOME (f::fs)) exp
+ | _ => NONE)
+ in
+ readFields (SOME [])
+ end
+
+fun expOfPath (n, fs) =
+ List.foldl (fn (f, exp) => (EField (exp, f), dummyLoc)) (ERel n, dummyLoc) fs
+
+fun freePaths'' bound exp paths =
+ case pathOfExp (exp, dummyLoc) of
+ NONE => paths
+ | SOME (n, fs) => if n < bound then paths else PS.add (paths, (n - bound, fs))
+
+(* ASK: nicer way? :( *)
+fun freePaths' bound exp =
+ case #1 exp of
+ EPrim _ => id
+ | e as ERel _ => freePaths'' bound e
+ | ENamed _ => id
+ | ECon (_, _, data) => (case data of NONE => id | SOME e => freePaths' bound e)
+ | ENone _ => id
+ | ESome (_, e) => freePaths' bound e
+ | EFfi _ => id
+ | EFfiApp (_, _, args) =>
+ List.foldl (fn ((e, _), acc) => freePaths' bound e o acc) id args
+ | EApp (e1, e2) => freePaths' bound e1 o freePaths' bound e2
+ | EAbs (_, _, _, e) => freePaths' (bound + 1) e
+ | EUnop (_, e) => freePaths' bound e
+ | EBinop (_, _, e1, e2) => freePaths' bound e1 o freePaths' bound e2
+ | ERecord fields => List.foldl (fn ((_, e, _), acc) => freePaths' bound e o acc) id fields
+ | e as EField _ => freePaths'' bound e
+ | ECase (e, cases, _) =>
+ List.foldl (fn ((p, e), acc) => freePaths' (MonoEnv.patBindsN p + bound) e o acc)
+ (freePaths' bound e)
+ cases
+ | EStrcat (e1, e2) => freePaths' bound e1 o freePaths' bound e2
+ | EError (e, _) => freePaths' bound e
+ | EReturnBlob {blob, mimeType = e, ...} =>
+ freePaths' bound e o (case blob of NONE => id | SOME e => freePaths' bound e)
+ | ERedirect (e, _) => freePaths' bound e
+ | EWrite e => freePaths' bound e
+ | ESeq (e1, e2) => freePaths' bound e1 o freePaths' bound e2
+ | ELet (_, _, e1, e2) => freePaths' bound e1 o freePaths' (bound + 1) e2
+ | EClosure (_, es) => List.foldl (fn (e, acc) => freePaths' bound e o acc) id es
+ | EQuery {query = e1, body = e2, initial = e3, ...} =>
+ freePaths' bound e1 o freePaths' (bound + 2) e2 o freePaths' bound e3
+ | EDml (e, _) => freePaths' bound e
+ | ENextval e => freePaths' bound e
+ | ESetval (e1, e2) => freePaths' bound e1 o freePaths' bound e2
+ | EUnurlify (e, _, _) => freePaths' bound e
+ | EJavaScript (_, e) => freePaths' bound e
+ | ESignalReturn e => freePaths' bound e
+ | ESignalBind (e1, e2) => freePaths' bound e1 o freePaths' bound e2
+ | ESignalSource e => freePaths' bound e
+ | EServerCall (e, _, _, _) => freePaths' bound e
+ | ERecv (e, _) => freePaths' bound e
+ | ESleep e => freePaths' bound e
+ | ESpawn e => freePaths' bound e
+
+fun freePaths exp = freePaths' 0 exp PS.empty
+
+datatype unbind = Known of exp | Unknowns of int
+
+datatype cacheArg = AsIs of exp | Urlify of exp
+
+structure InvalInfo :> sig
+ type t
+ type state = {tableToIndices : SIMM.multimap,
+ indexToInvalInfo : (t * int) IntBinaryMap.map,
+ ffiInfo : {index : int, params : int} list,
+ index : int}
+ val empty : t
+ val singleton : Sql.query -> t
+ val query : t -> Sql.query
+ val orderArgs : t * Mono.exp -> cacheArg list option
+ val unbind : t * unbind -> t option
+ val union : t * t -> t
+ val updateState : t * int * state -> state
+end = struct
+
+ (* Variable, field projections, possible wrapped sqlification FFI call. *)
+ type sqlArg = path * (string * string * typ) option
+
+ type subst = sqlArg IM.map
+
+ (* TODO: store free variables as well? *)
+ type t = (Sql.query * subst) list
+
+ type state = {tableToIndices : SIMM.multimap,
+ indexToInvalInfo : (t * int) IntBinaryMap.map,
+ ffiInfo : {index : int, params : int} list,
+ index : int}
+
+ structure AK = PairKeyFn(
+ structure I = PK
+ structure J = OptionKeyFn(TripleKeyFn(
+ structure I = SK
+ structure J = SK
+ structure K = struct type ord_key = Mono.typ val compare = MonoUtil.Typ.compare end)))
+ structure AS = BinarySetFn(AK)
+ structure AM = BinaryMapFn(AK)
+
+ (* Traversal Utilities *)
+ (* TODO: get rid of unused ones. *)
+
+ (* Need lift', etc. because we don't have rank-2 polymorphism. This should
+ probably use a functor (an ML one, not Haskell) but works for now. *)
+ fun traverseSqexp (pure, _, _, _, lift, lift', _, _, lift2, _, _, _, _, _) f =
+ let
+ val rec tr =
+ fn Sql.SqNot se => lift Sql.SqNot (tr se)
+ | Sql.Binop (r, se1, se2) =>
+ lift2 (fn (trse1, trse2) => Sql.Binop (r, trse1, trse2)) (tr se1, tr se2)
+ | Sql.SqKnown se => lift Sql.SqKnown (tr se)
+ | Sql.Inj (e', loc) => lift' (fn fe' => Sql.Inj (fe', loc)) (f e')
+ | Sql.SqFunc (s, se) => lift (fn trse => Sql.SqFunc (s, trse)) (tr se)
+ | se => pure se
+ in
+ tr
+ end
+
+ fun traverseFitem (ops as (_, _, _, pure''', _, _, _, lift''', _, _, _, _, lift2'''', lift2''''')) f =
+ let
+ val rec tr =
+ fn Sql.Table t => pure''' (Sql.Table t)
+ | Sql.Join (jt, fi1, fi2, se) =>
+ lift2'''' (fn ((trfi1, trfi2), trse) => Sql.Join (jt, trfi1, trfi2, trse))
+ (lift2''''' id (tr fi1, tr fi2), traverseSqexp ops f se)
+ | Sql.Nested (q, s) => lift''' (fn trq => Sql.Nested (trq, s))
+ (traverseQuery ops f q)
+ in
+ tr
+ end
+
+ and traverseQuery (ops as (_, pure', pure'', _, _, _, lift'', _, _, lift2', lift2'', lift2''', _, _)) f =
+ let
+ val rec seqList =
+ fn [] => pure'' []
+ | (x::xs) => lift2''' op:: (x, seqList xs)
+ val rec tr =
+ fn Sql.Query1 q =>
+ (* TODO: make sure we don't need to traverse [#Select q]. *)
+ lift2' (fn (trfrom, trwher) => Sql.Query1 {Select = #Select q,
+ From = trfrom,
+ Where = trwher})
+ (seqList (map (traverseFitem ops f) (#From q)),
+ case #Where q of
+ NONE => pure' NONE
+ | SOME se => lift'' SOME (traverseSqexp ops f se))
+ | Sql.Union (q1, q2) => lift2'' Sql.Union (tr q1, tr q2)
+ in
+ tr
+ end
+
+ (* Include unused tuple elements in argument for convenience of using same
+ argument as [traverseQuery]. *)
+ fun traverseIM (pure, _, _, _, _, _, _, _, _, lift2, _, _, _, _) f =
+ IM.foldli (fn (k, v, acc) => lift2 (fn (acc, w) => IM.insert (acc, k, w)) (acc, f (k,v)))
+ (pure IM.empty)
+
+ fun traverseSubst (ops as (_, pure', _, _, lift, _, _, _, _, lift2', _, _, _, _)) f =
+ let
+ fun mp ((n, fields), sqlify) =
+ lift (fn ((n', fields'), sqlify') =>
+ let
+ fun wrap sq = ((n', fields' @ fields), sq)
+ in
+ case (fields', sqlify', fields, sqlify) of
+ (_, NONE, _, NONE) => wrap NONE
+ | (_, NONE, _, sq as SOME _) => wrap sq
+ (* Last case should suffice because we don't
+ project from a sqlified value (which is a
+ string). *)
+ | (_, sq as SOME _, [], NONE) => wrap sq
+ | _ => raise Fail "Sqlcache: traverseSubst"
+ end)
+ (f n)
+ in
+ traverseIM ops (fn (_, v) => mp v)
+ end
+
+ fun monoidOps plus zero =
+ (fn _ => zero, fn _ => zero, fn _ => zero, fn _ => zero,
+ fn _ => fn x => x, fn _ => fn x => x, fn _ => fn x => x, fn _ => fn x => x,
+ fn _ => plus, fn _ => plus, fn _ => plus, fn _ => plus, fn _ => plus, fn _ => plus)
+
+ val optionOps = (SOME, SOME, SOME, SOME,
+ omap, omap, omap, omap,
+ omap2, omap2, omap2, omap2, omap2, omap2)
+
+ fun foldMapQuery plus zero = traverseQuery (monoidOps plus zero)
+ val omapQuery = traverseQuery optionOps
+ fun foldMapIM plus zero = traverseIM (monoidOps plus zero)
+ fun omapIM f = traverseIM optionOps f
+ fun foldMapSubst plus zero = traverseSubst (monoidOps plus zero)
+ fun omapSubst f = traverseSubst optionOps f
+
+ val varsOfQuery = foldMapQuery IS.union
+ IS.empty
+ (fn e' => freeVars (e', dummyLoc))
+
+ fun varsOfSubst subst = foldMapSubst IS.union IS.empty IS.singleton subst
+
+ val varsOfList =
+ fn [] => IS.empty
+ | (q::qs) => varsOfQuery (List.foldl Sql.Union q qs)
+
+ (* Signature Implementation *)
+
+ val empty = []
+
+ fun singleton q = [(q, IS.foldl (fn (n, acc) => IM.insert (acc, n, ((n, []), NONE)))
+ IM.empty
+ (varsOfQuery q))]
+
+ val union = op@
+
+ fun sqlArgsSet (q, subst) =
+ IM.foldl AS.add' AS.empty subst
+
+ fun sqlArgsMap (qs : t) =
+ let
+ val args =
+ List.foldl (fn ((q, subst), acc) =>
+ IM.foldl (fn (arg, acc) => AM.insert (acc, arg, ())) acc subst)
+ AM.empty
+ qs
+ val countRef = ref (~1)
+ fun count () = (countRef := !countRef + 1; !countRef)
+ in
+ (* Maps each arg to a different consecutive integer, starting from 0. *)
+ AM.map count args
+ end
+
+ fun expOfArg (path, sqlify) =
+ let
+ val exp = expOfPath path
+ in
+ case sqlify of
+ NONE => exp
+ | SOME (m, x, typ) => (EFfiApp (m, x, [(exp, typ)]), dummyLoc)
+ end
+
+ fun orderArgs (qs : t, exp) =
+ let
+ val paths = freePaths exp
+ fun erel n = (ERel n, dummyLoc)
+ val argsMap = sqlArgsMap qs
+ val args = map (expOfArg o #1) (AM.listItemsi argsMap)
+ val invalPaths = List.foldl PS.union PS.empty (map freePaths args)
+ (* TODO: make sure these variables are okay to remove from the argument list. *)
+ val pureArgs = PS.difference (paths, invalPaths)
+ val shouldCache =
+ case getHeuristic () of
+ Smart =>
+ (case (qs, PS.numItems pureArgs) of
+ ((q::qs), 0) =>
+ let
+ val args = sqlArgsSet q
+ val argss = map sqlArgsSet qs
+ fun test (args, acc) =
+ acc
+ <\obind\>
+ (fn args' =>
+ let
+ val both = AS.union (args, args')
+ in
+ (AS.numItems args = AS.numItems both
+ orelse AS.numItems args' = AS.numItems both)
+ <\oguard\>
+ (fn _ => SOME both)
+ end)
+ in
+ case List.foldl test (SOME args) argss of
+ NONE => false
+ | SOME _ => true
+ end
+ | _ => false)
+ | Always => true
+ | Never => (case qs of [_] => PS.numItems pureArgs = 0 | _ => false)
+ | NoPureAll => (case qs of [] => false | _ => true)
+ | NoPureOne => (case qs of [] => false | _ => PS.numItems pureArgs = 0)
+ | NoCombo => PS.numItems pureArgs = 0 orelse AM.numItems argsMap = 0
+ in
+ (* Put arguments we might invalidate by first. *)
+ if shouldCache
+ then SOME (map AsIs args @ map (Urlify o expOfPath) (PS.listItems pureArgs))
+ else NONE
+ end
+
+ (* As a kludge, we rename the variables in the query to correspond to the
+ argument of the cache they're part of. *)
+ fun query (qs : t) =
+ let
+ val argsMap = sqlArgsMap qs
+ fun substitute subst =
+ fn ERel n => IM.find (subst, n)
+ <\obind\>
+ (fn arg =>
+ AM.find (argsMap, arg)
+ <\obind\>
+ (fn n' => SOME (ERel n')))
+ | _ => raise Fail "Sqlcache: query (a)"
+ in
+ case (map #1 qs) of
+ (q :: qs) =>
+ let
+ val q = List.foldl Sql.Union q qs
+ val ns = IS.listItems (varsOfQuery q)
+ val rename =
+ fn ERel n => omap ERel (indexOf (fn n' => n' = n) ns)
+ | _ => raise Fail "Sqlcache: query (b)"
+ in
+ case omapQuery rename q of
+ SOME q => q
+ (* We should never get NONE because indexOf should never fail. *)
+ | NONE => raise Fail "Sqlcache: query (c)"
+ end
+ (* We should never reach this case because [updateState] won't
+ put anything in the state if there are no queries. *)
+ | [] => raise Fail "Sqlcache: query (d)"
+ end
+
+ val argOfExp =
+ let
+ fun doFields acc exp =
+ acc
+ <\obind\>
+ (fn (fs, sqlify) =>
+ case #1 exp of
+ ERel n => SOME (n, fs, sqlify)
+ | EField (exp, f) => doFields (SOME (f::fs, sqlify)) exp
+ | _ => NONE)
+ in
+ fn (EFfiApp ("Basis", x, [(exp, typ)]), _) =>
+ if String.isPrefix "sqlify" x
+ then omap (fn path => (path, SOME ("Basis", x, typ))) (pathOfExp exp)
+ else NONE
+ | exp => omap (fn path => (path, NONE)) (pathOfExp exp)
+ end
+
+ val unbind1 =
+ fn Known e =>
+ let
+ val replacement = argOfExp e
+ in
+ omapSubst (fn 0 => replacement
+ | n => SOME ((n-1, []), NONE))
+ end
+ | Unknowns k => omapSubst (fn n => if n < k then NONE else SOME ((n-k, []), NONE))
+
+ fun unbind (qs, ub) =
+ case ub of
+ (* Shortcut if nothing's changing. *)
+ Unknowns 0 => SOME qs
+ | _ => osequence (map (fn (q, subst) => unbind1 ub subst
+ <\obind\>
+ (fn subst' => SOME (q, subst'))) qs)
+
+ fun updateState (qs, numArgs, state as {index, ...} : state) =
+ {tableToIndices = List.foldr (fn ((q, _), acc) =>
+ SS.foldl (fn (tab, acc) =>
+ SIMM.insert (acc, tab, index))
+ acc
+ (tablesOfQuery q))
+ (#tableToIndices state)
+ qs,
+ indexToInvalInfo = IM.insert (#indexToInvalInfo state, index, (qs, numArgs)),
+ ffiInfo = {index = index, params = numArgs} :: #ffiInfo state,
+ index = index + 1}
+
+end
+
+structure UF = UnionFindFn(AtomExpKey)
+
+val rec sqexpToFormula =
+ fn Sql.SqTrue => Combo (Conj, [])
+ | Sql.SqFalse => Combo (Disj, [])
+ | Sql.SqNot e => Negate (sqexpToFormula e)
+ | Sql.Binop (Sql.RCmp c, e1, e2) => Atom (c, e1, e2)
+ | Sql.Binop (Sql.RLop l, p1, p2) => Combo (case l of Sql.And => Conj | Sql.Or => Disj,
+ [sqexpToFormula p1, sqexpToFormula p2])
+ | e as Sql.Field f => Atom (Sql.Eq, e, Sql.SqTrue)
+ (* ASK: any other sqexps that can be props? *)
+ | Sql.SqConst prim =>
+ (case prim of
+ (Prim.String (Prim.Normal, s)) =>
+ if s = #trueString (Settings.currentDbms ())
+ then Combo (Conj, [])
+ else if s = #falseString (Settings.currentDbms ())
+ then Combo (Disj, [])
+ else raise Fail "Sqlcache: sqexpToFormula (SqConst a)"
+ | _ => raise Fail "Sqlcache: sqexpToFormula (SqConst b)")
+ | Sql.Computed _ => raise Fail "Sqlcache: sqexpToFormula (Computed)"
+ | Sql.SqKnown _ => raise Fail "Sqlcache: sqexpToFormula (SqKnown)"
+ | Sql.Inj _ => raise Fail "Sqlcache: sqexpToFormula (Inj)"
+ | Sql.SqFunc _ => raise Fail "Sqlcache: sqexpToFormula (SqFunc)"
+ | Sql.Unmodeled => raise Fail "Sqlcache: sqexpToFormula (Unmodeled)"
+ | Sql.Null => raise Fail "Sqlcache: sqexpToFormula (Null)"
+
+fun mapSqexpFields f =
+ fn Sql.Field (t, v) => f (t, v)
+ | Sql.SqNot e => Sql.SqNot (mapSqexpFields f e)
+ | Sql.Binop (r, e1, e2) => Sql.Binop (r, mapSqexpFields f e1, mapSqexpFields f e2)
+ | Sql.SqKnown e => Sql.SqKnown (mapSqexpFields f e)
+ | Sql.SqFunc (s, e) => Sql.SqFunc (s, mapSqexpFields f e)
+ | e => e
+
+fun renameTables tablePairs =
+ let
+ fun rename table =
+ case List.find (fn (_, t) => table = t) tablePairs of
+ NONE => table
+ | SOME (realTable, _) => realTable
+ in
+ mapSqexpFields (fn (t, f) => Sql.Field (rename t, f))
+ end
+
+structure FlattenQuery = struct
+
+ datatype substitution = RenameTable of string | SubstituteExp of Sql.sqexp SM.map
+
+ fun applySubst substTable =
+ let
+ fun substitute (table, field) =
+ case SM.find (substTable, table) of
+ NONE => Sql.Field (table, field)
+ | SOME (RenameTable realTable) => Sql.Field (realTable, field)
+ | SOME (SubstituteExp substField) =>
+ case SM.find (substField, field) of
+ NONE => raise Fail "Sqlcache: applySubst"
+ | SOME se => se
+ in
+ mapSqexpFields substitute
+ end
+
+ fun addToSubst (substTable, table, substField) =
+ SM.insert (substTable,
+ table,
+ case substField of
+ RenameTable _ => substField
+ | SubstituteExp subst => SubstituteExp (SM.map (applySubst substTable) subst))
+
+ fun newSubst (t, s) = addToSubst (SM.empty, t, s)
+
+ datatype sitem' = Named of Sql.sqexp * string | Unnamed of Sql.sqexp
+
+ type queryFlat = {Select : sitem' list, Where : Sql.sqexp}
+
+ val sitemsToSubst =
+ List.foldl (fn (Named (se, s), acc) => SM.insert (acc, s, se)
+ | (Unnamed _, _) => raise Fail "Sqlcache: sitemsToSubst")
+ SM.empty
+
+ fun unionSubst (s1, s2) = SM.unionWith (fn _ => raise Fail "Sqlcache: unionSubst") (s1, s2)
+
+ fun sqlAnd (se1, se2) = Sql.Binop (Sql.RLop Sql.And, se1, se2)
+
+ val rec flattenFitem : Sql.fitem -> (Sql.sqexp * substitution SM.map) list =
+ fn Sql.Table (real, alias) => [(Sql.SqTrue, newSubst (alias, RenameTable real))]
+ | Sql.Nested (q, s) =>
+ let
+ val qfs = flattenQuery q
+ in
+ map (fn (qf, subst) =>
+ (#Where qf, addToSubst (subst, s, SubstituteExp (sitemsToSubst (#Select qf)))))
+ qfs
+ end
+ | Sql.Join (jt, fi1, fi2, se) =>
+ concatMap (fn ((wher1, subst1)) =>
+ map (fn (wher2, subst2) =>
+ let
+ val subst = unionSubst (subst1, subst2)
+ in
+ (* ON clause becomes part of the accumulated WHERE. *)
+ (sqlAnd (sqlAnd (wher1, wher2), applySubst subst se), subst)
+ end)
+ (flattenFitem fi2))
+ (flattenFitem fi1)
+
+ and flattenQuery : Sql.query -> (queryFlat * substitution SM.map) list =
+ fn Sql.Query1 q =>
+ let
+ val fifss = cartesianProduct (map flattenFitem (#From q))
+ in
+ map (fn fifs =>
+ let
+ val subst = List.foldl (fn ((_, subst), acc) => unionSubst (acc, subst))
+ SM.empty
+ fifs
+ val wher = List.foldr (fn ((wher, _), acc) => sqlAnd (wher, acc))
+ (case #Where q of
+ NONE => Sql.SqTrue
+ | SOME wher => wher)
+ fifs
+ in
+ (* ASK: do we actually need to pass the substitution through here? *)
+ (* We use the substitution later, but it's not clear we
+ need any of its currently present fields again. *)
+ ({Select = map (fn Sql.SqExp (se, s) => Named (applySubst subst se, s)
+ | Sql.SqField tf =>
+ Unnamed (applySubst subst (Sql.Field tf)))
+ (#Select q),
+ Where = applySubst subst wher},
+ subst)
+ end)
+ fifss
+ end
+ | Sql.Union (q1, q2) => (flattenQuery q1) @ (flattenQuery q2)
+
+end
+
+val flattenQuery = map #1 o FlattenQuery.flattenQuery
+
+fun queryFlatToFormula marker {Select = sitems, Where = wher} =
+ let
+ val fWhere = sqexpToFormula wher
+ in
+ case marker of
+ NONE => fWhere
+ | SOME markFields =>
+ let
+ val fWhereMarked = mapFormulaExps markFields fWhere
+ val toSqexp =
+ fn FlattenQuery.Named (se, _) => se
+ | FlattenQuery.Unnamed se => se
+ fun ineq se = Atom (Sql.Ne, se, markFields se)
+ val fIneqs = Combo (Disj, map (ineq o toSqexp) sitems)
+ in
+ (Combo (Conj,
+ [fWhere,
+ Combo (Disj,
+ [Negate fWhereMarked,
+ Combo (Conj, [fWhereMarked, fIneqs])])]))
+ end
+ end
+
+fun queryToFormula marker q = Combo (Disj, map (queryFlatToFormula marker) (flattenQuery q))
+
+fun valsToFormula (markLeft, markRight) (table, vals) =
+ Combo (Conj,
+ map (fn (field, v) => Atom (Sql.Eq, markLeft (Sql.Field (table, field)), markRight v))
+ vals)
+
+(* TODO: verify logic for insertion and deletion. *)
+val rec dmlToFormulaMarker =
+ fn Sql.Insert (table, vals) => (valsToFormula (id, id) (table, vals), NONE)
+ | Sql.Delete (table, wher) => (sqexpToFormula (renameTables [(table, "T")] wher), NONE)
+ | Sql.Update (table, vals, wher) =>
+ let
+ val fWhere = sqexpToFormula (renameTables [(table, "T")] wher)
+ fun fVals marks = valsToFormula marks (table, vals)
+ val modifiedFields = SS.addList (SS.empty, map #1 vals)
+ (* TODO: don't use field name hack. *)
+ val markFields =
+ mapSqexpFields (fn (t, v) => if t = table andalso SS.member (modifiedFields, v)
+ then Sql.Field (t, v ^ "'")
+ else Sql.Field (t, v))
+ val mark = mapFormulaExps markFields
+ in
+ ((Combo (Disj, [Combo (Conj, [fVals (id, markFields), mark fWhere]),
+ Combo (Conj, [fVals (markFields, id), fWhere])])),
+ SOME markFields)
+ end
+
+fun pairToFormulas (query, dml) =
+ let
+ val (fDml, marker) = dmlToFormulaMarker dml
+ in
+ (queryToFormula marker query, fDml)
+ end
+
+structure ConflictMaps = struct
+
+ structure TK = TripleKeyFn(structure I = CmpKey
+ structure J = AtomOptionKey
+ structure K = AtomOptionKey)
+
+ structure TS : ORD_SET = BinarySetFn(TK)
+
+ val toKnownEquality =
+ (* [NONE] here means unkown. Anything that isn't a comparison between two
+ knowns shouldn't be used, and simply dropping unused terms is okay in
+ disjunctive normal form. *)
+ fn (Sql.Eq, SOME e1, SOME e2) => SOME (e1, e2)
+ | _ => NONE
+
+ fun equivClasses atoms : atomExp list list option =
+ let
+ val uf = List.foldl UF.union' UF.empty (List.mapPartial toKnownEquality atoms)
+ val contradiction =
+ fn (cmp, SOME ae1, SOME ae2) => (cmp = Sql.Ne orelse cmp = Sql.Lt orelse cmp = Sql.Gt)
+ andalso UF.together (uf, ae1, ae2)
+ (* If we don't know one side of the comparision, not a contradiction. *)
+ | _ => false
+ in
+ not (List.exists contradiction atoms) <\oguard\> (fn _ => SOME (UF.classes uf))
+ end
+
+ fun addToEqs (eqs, n, e) =
+ case IM.find (eqs, n) of
+ (* Comparing to a constant is probably better than comparing to a
+ variable? Checking that existing constants match a new ones is
+ handled by [accumulateEqs]. *)
+ SOME (Prim _) => eqs
+ | _ => IM.insert (eqs, n, e)
+
+ val accumulateEqs =
+ (* [NONE] means we have a contradiction. *)
+ fn (_, NONE) => NONE
+ | ((Prim p1, Prim p2), eqso) =>
+ (case Prim.compare (p1, p2) of
+ EQUAL => eqso
+ | _ => NONE)
+ | ((QueryArg n, Prim p), SOME eqs) => SOME (addToEqs (eqs, n, Prim p))
+ | ((QueryArg n, DmlRel r), SOME eqs) => SOME (addToEqs (eqs, n, DmlRel r))
+ | ((Prim p, QueryArg n), SOME eqs) => SOME (addToEqs (eqs, n, Prim p))
+ | ((DmlRel r, QueryArg n), SOME eqs) => SOME (addToEqs (eqs, n, DmlRel r))
+ (* TODO: deal with equalities between [DmlRel]s and [Prim]s.
+ This would involve guarding the invalidation with a check for the
+ relevant comparisons. *)
+ | (_, eqso) => eqso
+
+ val eqsOfClass : atomExp list -> atomExp IM.map option =
+ List.foldl accumulateEqs (SOME IM.empty)
+ o chooseTwos
+
+ fun toAtomExps rel (cmp, e1, e2) =
+ let
+ val qa =
+ (* Here [NONE] means unkown. *)
+ fn Sql.SqConst p => SOME (Prim p)
+ | Sql.Field tf => SOME (Field tf)
+ | Sql.Inj (EPrim p, _) => SOME (Prim p)
+ | Sql.Inj (ERel n, _) => SOME (rel n)
+ (* We can't deal with anything else, e.g., CURRENT_TIMESTAMP
+ becomes Sql.Unmodeled, which becomes NONE here. *)
+ | _ => NONE
+ in
+ (cmp, qa e1, qa e2)
+ end
+
+ val negateCmp =
+ fn Sql.Eq => Sql.Ne
+ | Sql.Ne => Sql.Eq
+ | Sql.Lt => Sql.Ge
+ | Sql.Le => Sql.Gt
+ | Sql.Gt => Sql.Le
+ | Sql.Ge => Sql.Lt
+
+ fun normalizeAtom (negating, (cmp, e1, e2)) =
+ (* Restricting to Le/Lt and sorting the expressions in Eq/Ne helps with
+ simplification, where we put the triples in sets. *)
+ case (if negating then negateCmp cmp else cmp) of
+ Sql.Eq => (case AtomOptionKey.compare (e1, e2) of
+ LESS => (Sql.Eq, e2, e1)
+ | _ => (Sql.Eq, e1, e2))
+ | Sql.Ne => (case AtomOptionKey.compare (e1, e2) of
+ LESS => (Sql.Ne, e2, e1)
+ | _ => (Sql.Ne, e1, e2))
+ | Sql.Lt => (Sql.Lt, e1, e2)
+ | Sql.Le => (Sql.Le, e1, e2)
+ | Sql.Gt => (Sql.Lt, e2, e1)
+ | Sql.Ge => (Sql.Le, e2, e1)
+
+ val markQuery : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula ->
+ (Sql.cmp * atomExp option * atomExp option) formula =
+ mapFormula (toAtomExps QueryArg)
+
+ val markDml : (Sql.cmp * Sql.sqexp * Sql.sqexp) formula ->
+ (Sql.cmp * atomExp option * atomExp option) formula =
+ mapFormula (toAtomExps DmlRel)
+
+ (* No eqs should have key conflicts because no variable is in two
+ equivalence classes. *)
+ val mergeEqs : (atomExp IntBinaryMap.map option list
+ -> atomExp IntBinaryMap.map option) =
+ List.foldr (omap2 (IM.unionWith (fn _ => raise Fail "Sqlcache: ConflictMaps.mergeEqs")))
+ (SOME IM.empty)
+
+ val simplify =
+ map TS.listItems
+ o removeRedundant (fn (x, y) => TS.isSubset (y, x))
+ o map (fn xs => TS.addList (TS.empty, xs))
+
+ fun dnf (fQuery, fDml) =
+ normalize simplify normalizeAtom Disj (Combo (Conj, [markQuery fQuery, markDml fDml]))
+
+ val conflictMaps =
+ List.mapPartial (mergeEqs o map eqsOfClass)
+ o List.mapPartial equivClasses
+ o dnf
+
+end
+
+val conflictMaps = ConflictMaps.conflictMaps
+
+
+(*************************************)
+(* Program Instrumentation Utilities *)
+(*************************************)
+
+val {check, store, flush, lock, ...} = getCache ()
+
+val dummyTyp = (TRecord [], dummyLoc)
+
+fun stringExp s = (EPrim (Prim.String (Prim.Normal, s)), dummyLoc)
+
+val stringTyp = (TFfi ("Basis", "string"), dummyLoc)
+
+val sequence =
+ fn (exp :: exps) =>
+ let
+ val loc = dummyLoc
+ in
+ List.foldl (fn (e', seq) => ESeq ((seq, loc), (e', loc))) exp exps
+ end
+ | _ => raise Fail "Sqlcache: sequence"
+
+(* Always increments negative indices as a hack we use later. *)
+fun incRels inc =
+ MonoUtil.Exp.mapB
+ {typ = fn t' => t',
+ exp = fn bound =>
+ (fn ERel n => ERel (if n >= bound orelse n < 0 then n + inc else n)
+ | e' => e'),
+ bind = fn (bound, MonoUtil.Exp.RelE _) => bound + 1 | (bound, _) => bound}
+ 0
+
+fun fileTopLevelMapfoldB doTopLevelExp (decls, sideInfo) state =
+ let
+ fun doVal env ((x, n, t, exp, s), state) =
+ let
+ val (exp, state) = doTopLevelExp env exp state
+ in
+ ((x, n, t, exp, s), state)
+ end
+ fun doDecl' env (decl', state) =
+ case decl' of
+ DVal v =>
+ let
+ val (v, state) = doVal env (v, state)
+ in
+ (DVal v, state)
+ end
+ | DValRec vs =>
+ let
+ val (vs, state) = ListUtil.foldlMap (doVal env) state vs
+ in
+ (DValRec vs, state)
+ end
+ | _ => (decl', state)
+ fun doDecl (decl as (decl', loc), (env, state)) =
+ let
+ val env = MonoEnv.declBinds env decl
+ val (decl', state) = doDecl' env (decl', state)
+ in
+ ((decl', loc), (env, state))
+ end
+ val (decls, (_, state)) = (ListUtil.foldlMap doDecl (MonoEnv.empty, state) decls)
+ in
+ ((decls, sideInfo), state)
+ end
+
+fun fileAllMapfoldB doExp file start =
+ case MonoUtil.File.mapfoldB
+ {typ = Search.return2,
+ exp = fn env => fn e' => fn s => Search.Continue (doExp env e' s),
+ decl = fn _ => Search.return2,
+ bind = doBind}
+ MonoEnv.empty file start of
+ Search.Continue x => x
+ | Search.Return _ => raise Fail "Sqlcache: fileAllMapfoldB"
+
+fun fileMap doExp file = #1 (fileAllMapfoldB (fn _ => fn e => fn _ => (doExp e, ())) file ())
+
+(* TODO: make this a bit prettier.... *)
+(* TODO: factour out identical subexpressions to the same variable.... *)
+val simplifySql =
+ let
+ fun factorOutNontrivial text =
+ let
+ val loc = dummyLoc
+ val strcat =
+ fn (e1, (EPrim (Prim.String (Prim.Normal, "")), _)) => e1
+ | ((EPrim (Prim.String (Prim.Normal, "")), _), e2) => e2
+ | (e1, e2) => (EStrcat (e1, e2), loc)
+ val chunks = Sql.chunkify text
+ val (newText, newVariables) =
+ (* Important that this is foldr (to oppose foldl below). *)
+ List.foldr
+ (fn (chunk, (qText, newVars)) =>
+ (* Variable bound to the head of newVars will have the lowest index. *)
+ case chunk of
+ (* EPrim should always be a string in this case. *)
+ Sql.Exp (e as (EPrim _, _)) => (strcat (e, qText), newVars)
+ | Sql.Exp e =>
+ let
+ val n = length newVars
+ in
+ (* This is the (n+1)th new variable, so there are
+ already n new variables bound, so we increment
+ indices by n. *)
+ (strcat ((ERel (~(n+1)), loc), qText), incRels n e :: newVars)
+ end
+ | Sql.String s => (strcat (stringExp s, qText), newVars))
+ (stringExp "", [])
+ chunks
+ fun wrapLets e' =
+ (* Important that this is foldl (to oppose foldr above). *)
+ List.foldl (fn (v, e') => ELet ("sqlArg", stringTyp, v, (e', loc)))
+ e'
+ newVariables
+ val numArgs = length newVariables
+ in
+ (newText, wrapLets, numArgs)
+ end
+ fun doExp exp' =
+ let
+ val text = case exp' of
+ EQuery {query = text, ...} => text
+ | EDml (text, _) => text
+ | _ => raise Fail "Sqlcache: simplifySql (a)"
+ val (newText, wrapLets, numArgs) = factorOutNontrivial text
+ val newExp' = case exp' of
+ EQuery q => EQuery {query = newText,
+ exps = #exps q,
+ tables = #tables q,
+ state = #state q,
+ body = #body q,
+ initial = #initial q}
+ | EDml (_, failureMode) => EDml (newText, failureMode)
+ | _ => raise Fail "Sqlcache: simplifySql (b)"
+ in
+ (* Increment once for each new variable just made. This is
+ where we use the negative De Bruijn indices hack. *)
+ (* TODO: please don't use that hack. As anyone could have
+ predicted, it was incomprehensible a year later.... *)
+ wrapLets (#1 (incRels numArgs (newExp', dummyLoc)))
+ end
+ in
+ fileMap (fn exp' => case exp' of
+ EQuery _ => doExp exp'
+ | EDml _ => doExp exp'
+ | _ => exp')
+ end
+
+
+(**********************)
+(* Mono Type Checking *)
+(**********************)
+
+fun typOfExp' (env : MonoEnv.env) : exp' -> typ option =
+ fn EPrim p => SOME (TFfi ("Basis", case p of
+ Prim.Int _ => "int"
+ | Prim.Float _ => "double"
+ | Prim.String _ => "string"
+ | Prim.Char _ => "char"),
+ dummyLoc)
+ | ERel n => SOME (#2 (MonoEnv.lookupERel env n))
+ | ENamed n => SOME (#2 (MonoEnv.lookupENamed env n))
+ (* ASK: okay to make a new [ref] each time? *)
+ | ECon (dk, PConVar nCon, _) =>
+ let
+ val (_, _, nData) = MonoEnv.lookupConstructor env nCon
+ val (_, cs) = MonoEnv.lookupDatatype env nData
+ in
+ SOME (TDatatype (nData, ref (dk, cs)), dummyLoc)
+ end
+ | ECon (_, PConFfi {mod = s, datatyp, ...}, _) => SOME (TFfi (s, datatyp), dummyLoc)
+ | ENone t => SOME (TOption t, dummyLoc)
+ | ESome (t, _) => SOME (TOption t, dummyLoc)
+ | EFfi _ => NONE
+ | EFfiApp _ => NONE
+ | EApp (e1, e2) => (case typOfExp env e1 of
+ SOME (TFun (_, t), _) => SOME t
+ | _ => NONE)
+ | EAbs (_, t1, t2, _) => SOME (TFun (t1, t2), dummyLoc)
+ (* ASK: is this right? *)
+ | EUnop (unop, e) => (case unop of
+ "!" => SOME (TFfi ("Basis", "bool"), dummyLoc)
+ | "-" => typOfExp env e
+ | _ => NONE)
+ (* ASK: how should this (and other "=> NONE" cases) work? *)
+ | EBinop _ => NONE
+ | ERecord fields => SOME (TRecord (map (fn (s, _, t) => (s, t)) fields), dummyLoc)
+ | EField (e, s) => (case typOfExp env e of
+ SOME (TRecord fields, _) =>
+ omap #2 (List.find (fn (s', _) => s = s') fields)
+ | _ => NONE)
+ | ECase (_, _, {result, ...}) => SOME result
+ | EStrcat _ => SOME (TFfi ("Basis", "string"), dummyLoc)
+ | EWrite _ => SOME (TRecord [], dummyLoc)
+ | ESeq (_, e) => typOfExp env e
+ | ELet (s, t, e1, e2) => typOfExp (MonoEnv.pushERel env s t (SOME e1)) e2
+ | EClosure _ => NONE
+ | EUnurlify (_, t, _) => SOME t
+ | EQuery {state, ...} => SOME state
+ | e => NONE
+
+and typOfExp env (e', loc) = typOfExp' env e'
+
+
+(***********)
+(* Caching *)
+(***********)
+
+type state = InvalInfo.state
+
+datatype subexp = Cachable of InvalInfo.t * (state -> exp * state) | Impure of exp
+
+val isImpure =
+ fn Cachable _ => false
+ | Impure _ => true
+
+val runSubexp : subexp * state -> exp * state =
+ fn (Cachable (_, f), state) => f state
+ | (Impure e, state) => (e, state)
+
+val invalInfoOfSubexp =
+ fn Cachable (invalInfo, _) => invalInfo
+ | Impure _ => raise Fail "Sqlcache: invalInfoOfSubexp"
+
+fun cacheWrap (env, exp, typ, args, index) =
+ let
+ val loc = dummyLoc
+ val rel0 = (ERel 0, loc)
+ in
+ case MonoFooify.urlify env (rel0, typ) of
+ NONE => NONE
+ | SOME urlified =>
+ let
+ (* We ensure before this step that all arguments aren't effectful.
+ by turning them into local variables as needed. *)
+ val argsInc = map (incRels 1) args
+ val check = (check (index, args), loc)
+ val store = (store (index, argsInc, urlified), loc)
+ in
+ SOME (ECase (check,
+ [((PNone stringTyp, loc),
+ (ELet ("q", typ, exp, (ESeq (store, rel0), loc)), loc)),
+ ((PSome (stringTyp, (PVar ("hit", stringTyp), loc)), loc),
+ (* Boolean is false because we're not unurlifying from a cookie. *)
+ (EUnurlify (rel0, typ, false), loc))],
+ {disc = (TOption stringTyp, loc), result = typ}))
+ end
+ end
+
+val expSize = MonoUtil.Exp.fold {typ = #2, exp = fn (_, n) => n+1} 0
+
+(* TODO: pick a number. *)
+val sizeWorthCaching = 5
+
+val worthCaching =
+ fn EQuery _ => true
+ | exp' => expSize (exp', dummyLoc) > sizeWorthCaching
+
+fun cacheExp (env, exp', invalInfo, state : state) =
+ case worthCaching exp' <\oguard\> (fn _ => typOfExp' env exp') of
+ NONE => NONE
+ | SOME (TFun _, _) => NONE
+ | SOME typ =>
+ InvalInfo.orderArgs (invalInfo, (exp', dummyLoc))
+ <\obind\>
+ (fn args =>
+ List.foldr (fn (arg, acc) =>
+ acc
+ <\obind\>
+ (fn args' =>
+ (case arg of
+ AsIs exp => SOME exp
+ | Urlify exp =>
+ (typOfExp env exp)
+ <\obind\>
+ (fn typ => MonoFooify.urlify env (exp, typ)))
+ <\obind\>
+ (fn arg' => SOME (arg' :: args'))))
+ (SOME [])
+ args
+ <\obind\>
+ (fn args' =>
+ cacheWrap (env, (exp', dummyLoc), typ, args', #index state)
+ <\obind\>
+ (fn cachedExp =>
+ SOME (cachedExp,
+ InvalInfo.updateState (invalInfo, length args', state)))))
+
+fun cacheQuery (effs, env, q) : subexp =
+ let
+ (* We use dummyTyp here. I think this is okay because databases don't
+ store (effectful) functions, but perhaps there's some pathalogical
+ corner case missing.... *)
+ fun safe bound =
+ not
+ o effectful effs
+ (iterate (fn env => MonoEnv.pushERel env "_" dummyTyp NONE)
+ bound
+ env)
+ val {query = queryText, initial, body, ...} = q
+ val attempt =
+ (* Ziv misses Haskell's do notation.... *)
+ (safe 0 queryText andalso safe 0 initial andalso safe 2 body)
+ <\oguard\>
+ (fn _ =>
+ Sql.parse Sql.query queryText
+ <\obind\>
+ (fn queryParsed =>
+ let
+ val invalInfo = InvalInfo.singleton queryParsed
+ fun mkExp state =
+ case cacheExp (env, EQuery q, invalInfo, state) of
+ NONE => ((EQuery q, dummyLoc), state)
+ | SOME (cachedExp, state) => ((cachedExp, dummyLoc), state)
+ in
+ SOME (Cachable (invalInfo, mkExp))
+ end))
+ in
+ case attempt of
+ NONE => Impure (EQuery q, dummyLoc)
+ | SOME subexp => subexp
+ end
+
+fun cacheTree (effs : IS.set) ((env, exp as (exp', loc)), state) =
+ let
+ fun wrapBindN (f : exp list -> exp')
+ (args : ((MonoEnv.env * exp) * unbind) list) =
+ let
+ val (subexps, state) =
+ ListUtil.foldlMap (cacheTree effs)
+ state
+ (map #1 args)
+ fun mkExp state = mapFst (fn exps => (f exps, loc))
+ (ListUtil.foldlMap runSubexp state subexps)
+ val attempt =
+ if List.exists isImpure subexps
+ then NONE
+ else (List.foldl (omap2 InvalInfo.union)
+ (SOME InvalInfo.empty)
+ (ListPair.map
+ (fn (subexp, (_, unbinds)) =>
+ InvalInfo.unbind (invalInfoOfSubexp subexp, unbinds))
+ (subexps, args)))
+ <\obind\>
+ (fn invalInfo =>
+ SOME (Cachable (invalInfo,
+ fn state =>
+ case cacheExp (env,
+ f (map (#2 o #1) args),
+ invalInfo,
+ state) of
+ NONE => mkExp state
+ | SOME (e', state) => ((e', loc), state)),
+ state))
+ in
+ case attempt of
+ SOME (subexp, state) => (subexp, state)
+ | NONE => mapFst Impure (mkExp state)
+ end
+ fun wrapBind1 f arg =
+ wrapBindN (fn [arg] => f arg
+ | _ => raise Fail "Sqlcache: cacheTree (a)") [arg]
+ fun wrapBind2 f (arg1, arg2) =
+ wrapBindN (fn [arg1, arg2] => f (arg1, arg2)
+ | _ => raise Fail "Sqlcache: cacheTree (b)") [arg1, arg2]
+ fun wrapN f es = wrapBindN f (map (fn e => ((env, e), Unknowns 0)) es)
+ fun wrap1 f e = wrapBind1 f ((env, e), Unknowns 0)
+ fun wrap2 f (e1, e2) = wrapBind2 f (((env, e1), Unknowns 0), ((env, e2), Unknowns 0))
+ in
+ case exp' of
+ ECon (dk, pc, SOME e) => wrap1 (fn e => ECon (dk, pc, SOME e)) e
+ | ESome (t, e) => wrap1 (fn e => ESome (t, e)) e
+ | EFfiApp (s1, s2, args) =>
+ if ffiEffectful (s1, s2)
+ then (Impure exp, state)
+ else wrapN (fn es =>
+ EFfiApp (s1, s2, ListPair.map (fn (e, (_, t)) => (e, t)) (es, args)))
+ (map #1 args)
+ | EApp (e1, e2) => wrap2 EApp (e1, e2)
+ | EAbs (s, t1, t2, e) =>
+ wrapBind1 (fn e => EAbs (s, t1, t2, e))
+ ((MonoEnv.pushERel env s t1 NONE, e), Unknowns 1)
+ | EUnop (s, e) => wrap1 (fn e => EUnop (s, e)) e
+ | EBinop (bi, s, e1, e2) => wrap2 (fn (e1, e2) => EBinop (bi, s, e1, e2)) (e1, e2)
+ | ERecord fields =>
+ wrapN (fn es => ERecord (ListPair.map (fn (e, (s, _, t)) => (s, e, t)) (es, fields)))
+ (map #2 fields)
+ | EField (e, s) => wrap1 (fn e => EField (e, s)) e
+ | ECase (e, cases, {disc, result}) =>
+ wrapBindN (fn (e::es) =>
+ ECase (e,
+ (ListPair.map (fn (e, (p, _)) => (p, e)) (es, cases)),
+ {disc = disc, result = result})
+ | _ => raise Fail "Sqlcache: cacheTree (c)")
+ (((env, e), Unknowns 0)
+ :: map (fn (p, e) =>
+ ((MonoEnv.patBinds env p, e), Unknowns (MonoEnv.patBindsN p)))
+ cases)
+ | EStrcat (e1, e2) => wrap2 EStrcat (e1, e2)
+ (* We record page writes, so they're cachable. *)
+ | EWrite e => wrap1 EWrite e
+ | ESeq (e1, e2) => wrap2 ESeq (e1, e2)
+ | ELet (s, t, e1, e2) =>
+ wrapBind2 (fn (e1, e2) => ELet (s, t, e1, e2))
+ (((env, e1), Unknowns 0),
+ ((MonoEnv.pushERel env s t (SOME e1), e2), Known e1))
+ (* ASK: | EClosure (n, es) => ? *)
+ | EUnurlify (e, t, b) => wrap1 (fn e => EUnurlify (e, t, b)) e
+ | EQuery q => (cacheQuery (effs, env, q), state)
+ | _ => (if effectful effs env exp
+ then Impure exp
+ else Cachable (InvalInfo.empty,
+ fn state =>
+ case cacheExp (env, exp', InvalInfo.empty, state) of
+ NONE => ((exp', loc), state)
+ | SOME (exp', state) => ((exp', loc), state)),
+ state)
+ end
+
+fun addCaching file =
+ let
+ val effs = effectfulDecls file
+ fun doTopLevelExp env exp state = runSubexp (cacheTree effs ((env, exp), state))
+ in
+ (fileTopLevelMapfoldB doTopLevelExp
+ file
+ {tableToIndices = SIMM.empty,
+ indexToInvalInfo = IM.empty,
+ ffiInfo = [],
+ index = 0},
+ effs)
+ end
+
+
+(************)
+(* Flushing *)
+(************)
+
+structure Invalidations = struct
+
+ val loc = dummyLoc
+
+ val optionAtomExpToExp =
+ fn NONE => (ENone stringTyp, loc)
+ | SOME e => (ESome (stringTyp,
+ (case e of
+ DmlRel n => ERel n
+ | Prim p => EPrim p
+ (* TODO: make new type containing only these two. *)
+ | _ => raise Fail "Sqlcache: Invalidations.optionAtomExpToExp",
+ loc)),
+ loc)
+
+ fun eqsToInvalidation numArgs eqs =
+ List.tabulate (numArgs, (fn n => IM.find (eqs, n)))
+
+ (* Tests if [ys] makes [xs] a redundant cache invalidation. [NONE] here
+ represents unknown, which means a wider invalidation. *)
+ val rec madeRedundantBy : atomExp option list * atomExp option list -> bool =
+ fn ([], []) => true
+ | (_ :: xs, NONE :: ys) => madeRedundantBy (xs, ys)
+ | (SOME x :: xs, SOME y :: ys) => (case AtomExpKey.compare (x, y) of
+ EQUAL => madeRedundantBy (xs, ys)
+ | _ => false)
+ | _ => false
+
+ fun invalidations ((invalInfo, numArgs), dml) =
+ let
+ val query = InvalInfo.query invalInfo
+ in
+ (map (map optionAtomExpToExp)
+ o removeRedundant madeRedundantBy
+ o map (eqsToInvalidation numArgs)
+ o conflictMaps)
+ (pairToFormulas (query, dml))
+ end
+
+end
+
+val invalidations = Invalidations.invalidations
+
+fun addFlushing ((file, {tableToIndices, indexToInvalInfo, ffiInfo, ...} : state), effs) =
+ let
+ val flushes = List.concat
+ o map (fn (i, argss) => map (fn args => flush (i, args)) argss)
+ val doExp =
+ fn dmlExp as EDml (dmlText, failureMode) =>
+ let
+ val inval =
+ case Sql.parse Sql.dml dmlText of
+ SOME dmlParsed =>
+ SOME (map (fn i => case IM.find (indexToInvalInfo, i) of
+ SOME invalInfo =>
+ (i, invalidations (invalInfo, dmlParsed))
+ (* TODO: fail more gracefully. *)
+ (* This probably means invalidating everything.... *)
+ | NONE => raise Fail "Sqlcache: addFlushing (a)")
+ (SIMM.findList (tableToIndices, tableOfDml dmlParsed)))
+ | NONE => NONE
+ in
+ case inval of
+ (* TODO: fail more gracefully. *)
+ NONE => (Print.preface ("DML", MonoPrint.p_exp MonoEnv.empty dmlText);
+ raise Fail "Sqlcache: addFlushing (b)")
+ | SOME invs => sequence (flushes invs @ [dmlExp])
+ end
+ | e' => e'
+ val file = fileMap doExp file
+
+ in
+ ffiInfoRef := ffiInfo;
+ file
+ end
+
+
+(***********)
+(* Locking *)
+(***********)
+
+(* TODO: do this less evilly by not relying on specific FFI names, please? *)
+fun locksNeeded (lockMap : {store : IIMM.multimap, flush : IIMM.multimap}) =
+ MonoUtil.Exp.fold
+ {typ = #2,
+ exp = fn (EFfiApp ("Sqlcache", x, _), state as {store, flush}) =>
+ (case Int.fromString (String.extract (x, 5, NONE)) of
+ NONE => state
+ | SOME index =>
+ if String.isPrefix "flush" x
+ then {store = store, flush = IS.add (flush, index)}
+ else if String.isPrefix "store" x
+ then {store = IS.add (store, index), flush = flush}
+ else state)
+ | (ENamed n, {store, flush}) =>
+ {store = IS.union (store, IIMM.findSet (#store lockMap, n)),
+ flush = IS.union (flush, IIMM.findSet (#flush lockMap, n))}
+ | (_, state) => state}
+ {store = IS.empty, flush = IS.empty}
+
+fun lockMapOfFile file =
+ transitiveAnalysis
+ (fn ((_, name, _, e, _), state) =>
+ let
+ val locks = locksNeeded state e
+ in
+ {store = IIMM.insertSet (#store state, name, #store locks),
+ flush = IIMM.insertSet (#flush state, name, #flush locks)}
+ end)
+ {store = IIMM.empty, flush = IIMM.empty}
+ file
+
+fun exports (decls, _) =
+ List.foldl (fn ((DExport (_, _, n, _, _, _), _), ns) => IS.add (ns, n)
+ | (_, ns) => ns)
+ IS.empty
+ decls
+
+fun wrapLocks (locks, (exp', loc)) =
+ case exp' of
+ EAbs (s, t1, t2, exp) => (EAbs (s, t1, t2, wrapLocks (locks, exp)), loc)
+ | _ => (List.foldr (fn (l, e') => sequence [lock l, e']) exp' locks, loc)
+
+fun addLocking file =
+ let
+ val lockMap = lockMapOfFile file
+ fun lockList {store, flush} =
+ let
+ val ls = map (fn i => (i, true)) (IS.listItems flush)
+ @ map (fn i => (i, false)) (IS.listItems (IS.difference (store, flush)))
+ in
+ ListMergeSort.sort (fn ((i, _), (j, _)) => i > j) ls
+ end
+ fun locksOfName n =
+ lockList {flush = IIMM.findSet (#flush lockMap, n),
+ store = IIMM.findSet (#store lockMap, n)}
+ val locksOfExp = lockList o locksNeeded lockMap
+ val expts = exports file
+ fun doVal (v as (x, n, t, exp, s)) =
+ if IS.member (expts, n)
+ then (x, n, t, wrapLocks ((locksOfName n), exp), s)
+ else v
+ val doDecl =
+ fn (DVal v, loc) => (DVal (doVal v), loc)
+ | (DValRec vs, loc) => (DValRec (map doVal vs), loc)
+ | (DTask (exp1, exp2), loc) => (DTask (exp1, wrapLocks (locksOfExp exp2, exp2)), loc)
+ | decl => decl
+ in
+ mapFst (map doDecl) file
+ end
+
+
+(************************)
+(* Compiler Entry Point *)
+(************************)
+
+val inlineSql =
+ let
+ val doExp =
+ (* TODO: EQuery, too? *)
+ (* ASK: should this live in [MonoOpt]? *)
+ fn EDml ((ECase (disc, cases, {disc = dTyp, ...}), loc), failureMode) =>
+ let
+ val newCases = map (fn (p, e) => (p, (EDml (e, failureMode), loc))) cases
+ in
+ ECase (disc, newCases, {disc = dTyp, result = (TRecord [], loc)})
+ end
+ | e => e
+ in
+ fileMap doExp
+ end
+
+fun insertAfterDatatypes ((decls, sideInfo), newDecls) =
+ let
+ val (datatypes, others) = List.partition (fn (DDatatype _, _) => true | _ => false) decls
+ in
+ (datatypes @ newDecls @ others, sideInfo)
+ end
+
+val go' = addLocking o addFlushing o addCaching o simplifySql o inlineSql
+
+fun go file =
+ let
+ (* TODO: do something nicer than [Sql] being in one of two modes. *)
+ val () = (resetFfiInfo (); Sql.sqlcacheMode := true)
+ val file = go' file
+ (* Important that this happens after [MonoFooify.urlify] calls! *)
+ val fmDecls = MonoFooify.getNewFmDecls ()
+ val () = Sql.sqlcacheMode := false
+ val file = insertAfterDatatypes (file, rev fmDecls)
+ in
+ MonoReduce.reduce file
+ end
+
+end
diff --git a/src/sqlite.sig b/src/sqlite.sig
new file mode 100644
index 0000000..97475a0
--- /dev/null
+++ b/src/sqlite.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+signature SQLITE = sig
+
+end
diff --git a/src/sqlite.sml b/src/sqlite.sml
new file mode 100644
index 0000000..a9b6389
--- /dev/null
+++ b/src/sqlite.sml
@@ -0,0 +1,855 @@
+ (* Copyright (c) 2009-2010, 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 SQLite :> SQLITE = struct
+
+open Settings
+open Print.PD
+open Print
+
+fun p_sql_type t =
+ case t of
+ Int => "integer"
+ | Float => "real"
+ | String => "text"
+ | Char => "text"
+ | Bool => "integer"
+ | Time => "text"
+ | Blob => "blob"
+ | Channel => "integer"
+ | Client => "integer"
+ | Nullable t => p_sql_type t
+
+val ident = String.translate (fn #"'" => "PRIME"
+ | ch => str ch)
+
+fun checkRel (table, checkNullable) (s, xts) =
+ let
+ val q = "SELECT COUNT(*) FROM sqlite_master WHERE type = '" ^ table ^ "' AND name = '"
+ ^ s ^ "'"
+ in
+ box [string "if (sqlite3_prepare_v2(conn->conn, \"",
+ string q,
+ string "\", -1, &stmt, NULL) != SQLITE_OK) {",
+ newline,
+ box [string "sqlite3_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Query preparation failed:<br />",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "while ((res = sqlite3_step(stmt)) == SQLITE_BUSY)",
+ newline,
+ box [string "sleep(1);",
+ newline],
+ newline,
+ string "if (res == SQLITE_DONE) {",
+ newline,
+ box [string "sqlite3_finalize(stmt);",
+ newline,
+ string "sqlite3_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"No row returned:<br />",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "if (res != SQLITE_ROW) {",
+ newline,
+ box [string "sqlite3_finalize(stmt);",
+ newline,
+ string "sqlite3_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Error getting row:<br />",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (sqlite3_column_count(stmt) != 1) {",
+ newline,
+ box [string "sqlite3_finalize(stmt);",
+ newline,
+ string "sqlite3_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Bad column count:<br />",
+ string q,
+ string "\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (sqlite3_column_int(stmt, 0) != 1) {",
+ newline,
+ box [string "sqlite3_finalize(stmt);",
+ newline,
+ string "sqlite3_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Table '",
+ string s,
+ string "' does not exist.\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+ string "sqlite3_finalize(stmt);",
+ newline]
+ end
+
+fun init {dbstring, prepared = ss, tables, views, sequences} =
+ let
+ val db = ref dbstring
+ in
+ app (fn s =>
+ case String.fields (fn ch => ch = #"=") s of
+ [name, value] =>
+ (case name of
+ "dbname" => db := value
+ | _ => ())
+ | _ => ()) (String.tokens Char.isSpace dbstring);
+
+ box [string "typedef struct {",
+ newline,
+ box [string "sqlite3 *conn;",
+ newline,
+ p_list_sepi (box [])
+ (fn i => fn _ =>
+ box [string "sqlite3_stmt *p",
+ string (Int.toString i),
+ string ";",
+ newline])
+ ss],
+ string "} uw_conn;",
+ newline,
+ newline,
+
+ string "static void uw_client_init(void) {",
+ newline,
+ box [string "uw_sqlfmtInt = \"%lld%n\";",
+ newline,
+ string "uw_sqlfmtFloat = \"%.16g%n\";",
+ newline,
+ string "uw_Estrings = 0;",
+ newline,
+ string "uw_sql_type_annotations = 0;",
+ newline,
+ string "uw_sqlsuffixString = \"\";",
+ newline,
+ string "uw_sqlsuffixChar = \"\";",
+ newline,
+ string "uw_sqlsuffixBlob = \"\";",
+ newline,
+ string "uw_sqlfmtUint4 = \"%u%n\";",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ if #persistent (currentProtocol ()) then
+ box [string "static void uw_db_validate(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "sqlite3_stmt *stmt;",
+ newline,
+ string "int res;",
+ newline,
+ newline,
+ p_list_sep newline (checkRel ("table", true)) tables,
+ p_list_sep newline (fn name => checkRel ("table", true)
+ (name, [("id", Settings.Client)])) sequences,
+ p_list_sep newline (checkRel ("view", false)) views,
+ string "}",
+ newline,
+ newline,
+
+ string "static void uw_db_prepare(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ newline,
+
+ p_list_sepi newline (fn i => fn (s, _) =>
+ let
+ fun uhoh this s args =
+ box [p_list_sepi (box [])
+ (fn j => fn () =>
+ box [string
+ "sqlite3_finalize(conn->p",
+ string (Int.toString j),
+ string ");",
+ newline])
+ (List.tabulate (i, fn _ => ())),
+ box (if this then
+ [string
+ "sqlite3_finalize(conn->p",
+ string (Int.toString i),
+ string ");",
+ newline]
+ else
+ []),
+ string "sqlite3_close(conn->conn);",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string s,
+ string "\"",
+ p_list_sep (box []) (fn s => box [string ", ",
+ string s]) args,
+ string ");",
+ newline]
+ in
+ box [string "if (sqlite3_prepare_v2(conn->conn, \"",
+ string (Prim.toCString s),
+ string "\", -1, &conn->p",
+ string (Int.toString i),
+ string ", NULL) != SQLITE_OK) {",
+ newline,
+ box [string "char msg[1024];",
+ newline,
+ string "strncpy(msg, sqlite3_errmsg(conn->conn), 1024);",
+ newline,
+ string "msg[1023] = 0;",
+ newline,
+ uhoh false ("Error preparing statement: "
+ ^ Prim.toCString s ^ "<br />%s") ["msg"]],
+ string "}",
+ newline]
+ end)
+ ss,
+
+ string "}"]
+ else
+ box [string "static void uw_db_prepare(uw_context ctx) { }",
+ newline,
+ string "static void uw_db_validate(uw_context ctx) { }"],
+ newline,
+ newline,
+
+ string "static void uw_db_init(uw_context ctx) {",
+ newline,
+ string "sqlite3 *sqlite;",
+ newline,
+ string "sqlite3_stmt *stmt;",
+ newline,
+ string "uw_conn *conn;",
+ newline,
+ newline,
+ string "if (sqlite3_open(\"",
+ string (!db),
+ string "\", &sqlite) != SQLITE_OK) uw_error(ctx, FATAL, ",
+ string "\"Can't open SQLite database.\");",
+ newline,
+ newline,
+ string "if (uw_database_max < SIZE_MAX) {",
+ newline,
+ box [string "char buf[100];",
+ newline,
+ newline,
+
+ string "sprintf(buf, \"PRAGMA max_page_count = %llu\", (unsigned long long)(uw_database_max / 1024));",
+ newline,
+ newline,
+
+ string "if (sqlite3_prepare_v2(sqlite, buf, -1, &stmt, NULL) != SQLITE_OK) {",
+ newline,
+ box [string "sqlite3_close(sqlite);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't prepare max_page_count query for SQLite database\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (sqlite3_step(stmt) != SQLITE_ROW) {",
+ newline,
+ box [string "sqlite3_finalize(stmt);",
+ newline,
+ string "sqlite3_close(sqlite);",
+ newline,
+ string "uw_error(ctx, FATAL, \"Can't set max_page_count parameter for SQLite database\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "sqlite3_finalize(stmt);",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "conn = calloc(1, sizeof(uw_conn));",
+ newline,
+ string "conn->conn = sqlite;",
+ newline,
+ string "uw_set_db(ctx, conn);",
+ newline,
+ string "uw_db_validate(ctx);",
+ newline,
+ string "uw_db_prepare(ctx);",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static void uw_db_close(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ p_list_sepi (box [])
+ (fn i => fn _ =>
+ box [string "if (conn->p",
+ string (Int.toString i),
+ string ") sqlite3_finalize(conn->p",
+ string (Int.toString i),
+ string ");",
+ newline])
+ ss,
+ string "sqlite3_close(conn->conn);",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_begin(uw_context ctx, int could_write) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ newline,
+ string "if (sqlite3_exec(conn->conn, \"BEGIN\", NULL, NULL, NULL) == SQLITE_OK)",
+ newline,
+ box [string "return 0;",
+ newline],
+ string "else {",
+ newline,
+ box [string "fprintf(stderr, \"Begin error: %s<br />\", sqlite3_errmsg(conn->conn));",
+ newline,
+ string "return 1;",
+ newline],
+ string "}",
+ newline,
+ string "}",
+ newline,
+ string "static int uw_db_commit(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "if (sqlite3_exec(conn->conn, \"COMMIT\", NULL, NULL, NULL) == SQLITE_OK)",
+ newline,
+ box [string "return 0;",
+ newline],
+ string "else {",
+ newline,
+ box [string "fprintf(stderr, \"Commit error: %s<br />\", sqlite3_errmsg(conn->conn));",
+ newline,
+ string "return 1;",
+ newline],
+ string "}",
+ newline,
+ string "}",
+ newline,
+ newline,
+
+ string "static int uw_db_rollback(uw_context ctx) {",
+ newline,
+ string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "if (sqlite3_exec(conn->conn, \"ROLLBACK\", NULL, NULL, NULL) == SQLITE_OK)",
+ newline,
+ box [string "return 0;",
+ newline],
+ string "else {",
+ newline,
+ box [string "fprintf(stderr, \"Rollback error: %s<br />\", sqlite3_errmsg(conn->conn));",
+ newline,
+ string "return 1;",
+ newline],
+ string "}",
+ newline,
+ string "}",
+ newline,
+ newline]
+ end
+
+val fmt = "\"%Y-%m-%d %H:%M:%S\""
+
+fun p_getcol {loc, wontLeakStrings, col = i, typ = t} =
+ let
+ fun p_unsql t =
+ case t of
+ Int => box [string "sqlite3_column_int64(stmt, ", string (Int.toString i), string ")"]
+ | Float => box [string "sqlite3_column_double(stmt, ", string (Int.toString i), string ")"]
+ | String =>
+ if wontLeakStrings then
+ box [string "(uw_Basis_string)sqlite3_column_text(stmt, ", string (Int.toString i), string ")"]
+ else
+ box [string "uw_strdup(ctx, (uw_Basis_string)sqlite3_column_text(stmt, ", string (Int.toString i), string "))"]
+ | Char => box [string "sqlite3_column_text(stmt, ", string (Int.toString i), string ")[0]"]
+ | Bool => box [string "(uw_Basis_bool)sqlite3_column_int(stmt, ", string (Int.toString i), string ")"]
+ | Time => box [string "uw_Basis_stringToTimef_error(ctx, ",
+ string fmt,
+ string ", (uw_Basis_string)sqlite3_column_text(stmt, ",
+ string (Int.toString i),
+ string "))"]
+ | Blob => box [string "({",
+ newline,
+ string "char *data = (char *)sqlite3_column_blob(stmt, ",
+ string (Int.toString i),
+ string ");",
+ newline,
+ string "int len = sqlite3_column_bytes(stmt, ",
+ string (Int.toString i),
+ string ");",
+ newline,
+ string "uw_Basis_blob b = {len, uw_memdup(ctx, data, len)};",
+ newline,
+ string "b;",
+ newline,
+ string "})"]
+ | Channel => box [string "({",
+ newline,
+ string "sqlite3_int64 n = sqlite3_column_int64(stmt, ",
+ string (Int.toString i),
+ string ");",
+ newline,
+ string "uw_Basis_channel ch = {n >> 32, n & 0xFFFFFFFF};",
+ newline,
+ string "ch;",
+ newline,
+ string "})"]
+ | Client => box [string "sqlite3_column_int(stmt, ", string (Int.toString i), string ")"]
+
+ | Nullable _ => raise Fail "Postgres: Recursive Nullable"
+
+ fun getter t =
+ case t of
+ Nullable t =>
+ box [string "(sqlite3_column_type(stmt, ",
+ string (Int.toString i),
+ string ") == SQLITE_NULL ? NULL : ",
+ case t of
+ String => getter t
+ | _ => box [string "({",
+ newline,
+ string (p_sql_ctype t),
+ space,
+ string "*tmp = uw_malloc(ctx, sizeof(",
+ string (p_sql_ctype t),
+ string "));",
+ newline,
+ string "*tmp = ",
+ getter t,
+ string ";",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string ")"]
+ | _ =>
+ box [string "(sqlite3_column_type(stmt, ",
+ string (Int.toString i),
+ string ") == SQLITE_NULL ? ",
+ box [string "({",
+ string (p_sql_ctype t),
+ space,
+ string "tmp;",
+ newline,
+ string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Unexpectedly NULL field #",
+ string (Int.toString i),
+ string "\");",
+ newline,
+ string "tmp;",
+ newline,
+ string "})"],
+ string " : ",
+ p_unsql t,
+ string ")"]
+ in
+ getter t
+ end
+
+fun queryCommon {loc, query, cols, doCols} =
+ box [string "int r;",
+ newline,
+
+ string "sqlite3_reset(stmt);",
+ newline,
+
+ string "uw_end_region(ctx);",
+ newline,
+ string "while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {",
+ newline,
+ doCols p_getcol,
+ string "}",
+ newline,
+ newline,
+
+ string "if (r == SQLITE_BUSY) {",
+ box [string "sleep(1);",
+ newline,
+ string "uw_error(ctx, UNLIMITED_RETRY, \"Database is busy\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (r != SQLITE_DONE) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": query step failed: %s<br />%s\", ",
+ query,
+ string ", sqlite3_errmsg(conn->conn));",
+ newline,
+ newline]
+
+fun query {loc, cols, doCols} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "sqlite3_stmt *stmt;",
+ newline,
+ newline,
+ string "if (sqlite3_prepare_v2(conn->conn, query, -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: %s<br />%s\", sqlite3_errmsg(conn->conn), query);",
+ newline,
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);",
+ newline,
+ newline,
+
+ queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"},
+
+ string "uw_pop_cleanup(ctx);",
+ newline]
+
+val p_pre_inputs =
+ p_list_sepi (box [])
+ (fn i => fn t =>
+ case t of
+ Char => box [string "char arg",
+ string (Int.toString (i + 1)),
+ string "s = {arg",
+ string (Int.toString (i + 1)),
+ string ", 0};",
+ newline]
+ | _ => box [])
+
+fun p_inputs loc =
+ p_list_sepi (box [])
+ (fn i => fn t =>
+ let
+ fun bind (t, arg) =
+ case t of
+ Int => box [string "sqlite3_bind_int64(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string ")"]
+ | Float => box [string "sqlite3_bind_double(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string ")"]
+ | String => box [string "sqlite3_bind_text(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string ", -1, SQLITE_TRANSIENT)"]
+ | Char => box [string "sqlite3_bind_text(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string "s, -1, SQLITE_TRANSIENT)"]
+ | Bool => box [string "sqlite3_bind_int(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string ")"]
+ | Time => box [string "sqlite3_bind_text(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", uw_Basis_timef(ctx, ",
+ string fmt,
+ string ", ",
+ arg,
+ string "), -1, SQLITE_TRANSIENT)"]
+ | Blob => box [string "sqlite3_bind_blob(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string ".data, ",
+ arg,
+ string ".size, SQLITE_TRANSIENT)"]
+ | Channel => box [string "sqlite3_bind_int64(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ((sqlite3_int64)",
+ arg,
+ string ".cli << 32) | ",
+ arg,
+ string ".chn)"]
+ | Client => box [string "sqlite3_bind_int(stmt, ",
+ string (Int.toString (i + 1)),
+ string ", ",
+ arg,
+ string ")"]
+ | Nullable t => box [string "(",
+ arg,
+ string " == NULL ? sqlite3_bind_null(stmt, ",
+ string (Int.toString (i + 1)),
+ string ") : ",
+ bind (t, case t of
+ String => arg
+ | _ => box [string "(*", arg, string ")"]),
+ string ")"]
+ in
+ box [string "if (",
+ bind (t, box [string "arg", string (Int.toString (i + 1))]),
+ string " != SQLITE_OK) uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": Error binding parameter #",
+ string (Int.toString (i + 1)),
+ string ": %s\", sqlite3_errmsg(conn->conn));",
+ newline]
+ end)
+
+fun queryPrepared {loc, id, query, inputs, cols, doCols, nested} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ p_pre_inputs inputs,
+ if nested then
+ box [string "sqlite3_stmt *stmt;",
+ newline]
+ else
+ box [string "sqlite3_stmt *stmt = conn->p",
+ string (Int.toString id),
+ string ";",
+ newline,
+ newline,
+
+ string "if (stmt == NULL) {",
+ newline],
+
+ string "if (sqlite3_prepare_v2(conn->conn, \"",
+ string (Prim.toCString query),
+ string "\", -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: ",
+ string (Prim.toCString query),
+ string "<br />%s\", sqlite3_errmsg(conn->conn));",
+ newline,
+ if nested then
+ box [string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);",
+ newline]
+ else
+ box [string "conn->p",
+ string (Int.toString id),
+ string " = stmt;",
+ newline,
+ string "}",
+ newline,
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_clear_bindings, stmt);",
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_reset, stmt);",
+ newline],
+ newline,
+
+ p_inputs loc inputs,
+ newline,
+
+ queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"",
+ string (Prim.toCString query),
+ string "\""]},
+
+ string "uw_pop_cleanup(ctx);",
+ newline,
+ if nested then
+ box []
+ else
+ box [string "uw_pop_cleanup(ctx);",
+ newline]]
+
+fun dmlCommon {loc, dml, mode} =
+ box [string "int r;",
+ newline,
+
+ string "if ((r = sqlite3_step(stmt)) == SQLITE_BUSY) {",
+ box [string "sleep(1);",
+ newline,
+ string "uw_error(ctx, UNLIMITED_RETRY, \"Database is busy\");",
+ newline],
+ string "}",
+ newline,
+ newline,
+
+ string "if (r != SQLITE_DONE) ",
+ case mode of
+ Settings.Error => box [string "uw_error(ctx, FATAL, \"",
+ string (ErrorMsg.spanToString loc),
+ string ": DML step failed: %s<br />%s\", ",
+ dml,
+ string ", sqlite3_errmsg(conn->conn));"]
+ | Settings.None => string "uw_set_error_message(ctx, sqlite3_errmsg(conn->conn));",
+ newline]
+
+fun dml (loc, mode) =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "sqlite3_stmt *stmt;",
+ newline,
+ newline,
+ string "if (sqlite3_prepare_v2(conn->conn, dml, -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: %s<br />%s\", dml, sqlite3_errmsg(conn->conn));",
+ newline,
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);",
+ newline,
+ newline,
+
+ dmlCommon {loc = loc, dml = string "dml", mode = mode},
+
+ string "uw_pop_cleanup(ctx);",
+ newline]
+
+fun dmlPrepared {loc, id, dml, inputs, mode = mode} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ p_pre_inputs inputs,
+ string "sqlite3_stmt *stmt = conn->p",
+ string (Int.toString id),
+ string ";",
+ newline,
+ newline,
+
+ string "if (stmt == NULL) {",
+ newline,
+ box [string "if (sqlite3_prepare_v2(conn->conn, \"",
+ string (Prim.toCString dml),
+ string "\", -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: ",
+ string (Prim.toCString dml),
+ string "<br />%s\", sqlite3_errmsg(conn->conn));",
+ newline,
+ string "conn->p",
+ string (Int.toString id),
+ string " = stmt;",
+ newline],
+ string "}",
+ newline,
+
+ string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_clear_bindings, stmt);",
+ newline,
+ string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_reset, stmt);",
+ newline,
+
+ p_inputs loc inputs,
+ newline,
+
+ dmlCommon {loc = loc, dml = box [string "\"",
+ string (Prim.toCString dml),
+ string "\""], mode = mode},
+
+ string "uw_pop_cleanup(ctx);",
+ newline,
+ string "uw_pop_cleanup(ctx);",
+ newline]
+
+fun nextval {loc, seqE, seqName} =
+ box [string "uw_conn *conn = uw_get_db(ctx);",
+ newline,
+ string "char *insert = ",
+ case seqName of
+ SOME s => string ("\"INSERT INTO " ^ s ^ " VALUES (NULL)\"")
+ | NONE => box [string "uw_Basis_strcat(ctx, \"INSERT INTO \", uw_Basis_strcat(ctx, ",
+ seqE,
+ string ", \" VALUES ()\"))"],
+ string ";",
+ newline,
+ string "char *delete = ",
+ case seqName of
+ SOME s => string ("\"DELETE FROM " ^ s ^ "\"")
+ | NONE => box [string "uw_Basis_strcat(ctx, \"DELETE FROM \", ",
+ seqE,
+ string ")"],
+ string ";",
+ newline,
+ newline,
+
+ string "if (sqlite3_exec(conn->conn, insert, NULL, NULL, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"'nextval' INSERT failed: %s\", sqlite3_errmsg(conn->conn));",
+ newline,
+ string "n = sqlite3_last_insert_rowid(conn->conn);",
+ newline,
+ string "if (sqlite3_exec(conn->conn, delete, NULL, NULL, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"'nextval' DELETE failed: %s\", sqlite3_errmsg(conn->conn));",
+ newline]
+
+fun nextvalPrepared _ = raise Fail "SQLite.nextvalPrepared called"
+fun setval _ = raise Fail "SQLite.setval called"
+
+fun sqlifyString s = "'" ^ String.translate (fn #"'" => "''"
+ | #"\000" => ""
+ | ch => str ch)
+ s ^ "'"
+
+fun p_cast (s, _) = s
+
+fun p_blank _ = "?"
+
+val () = addDbms {name = "sqlite",
+ randomFunction = "RANDOM",
+ header = Config.sqheader,
+ link = "-lsqlite3",
+ init = init,
+ p_sql_type = p_sql_type,
+ query = query,
+ queryPrepared = queryPrepared,
+ dml = dml,
+ dmlPrepared = dmlPrepared,
+ nextval = nextval,
+ nextvalPrepared = nextvalPrepared,
+ setval = setval,
+ sqlifyString = sqlifyString,
+ p_cast = p_cast,
+ p_blank = p_blank,
+ supportsDeleteAs = false,
+ supportsUpdateAs = false,
+ createSequence = fn s => "CREATE TABLE " ^ s ^ " (id INTEGER PRIMARY KEY AUTOINCREMENT)",
+ textKeysNeedLengths = false,
+ supportsNextval = false,
+ supportsNestedPrepared = false,
+ sqlPrefix = "",
+ supportsOctetLength = false,
+ trueString = "1",
+ falseString = "0",
+ onlyUnion = false,
+ nestedRelops = false,
+ windowFunctions = false,
+ supportsIsDistinctFrom = false}
+
+end
diff --git a/src/static.sig b/src/static.sig
new file mode 100644
index 0000000..f809a6d
--- /dev/null
+++ b/src/static.sig
@@ -0,0 +1,30 @@
+(* Copyright (c) 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.
+ *)
+
+signature STATIC = sig
+
+end
diff --git a/src/static.sml b/src/static.sml
new file mode 100644
index 0000000..c74d4e3
--- /dev/null
+++ b/src/static.sml
@@ -0,0 +1,41 @@
+(* Copyright (c) 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 Static :> STATIC = struct
+
+open Settings
+open Print.PD Print
+
+val () = addProtocol {name = "static",
+ compile = "",
+ linkStatic = "liburweb_static.a",
+ linkDynamic = "-lurweb_static",
+ persistent = false,
+ code = fn () => box [string "void uw_global_custom() { }",
+ newline]}
+
+end
diff --git a/src/suffix.mlb b/src/suffix.mlb
new file mode 100644
index 0000000..7f2d065
--- /dev/null
+++ b/src/suffix.mlb
@@ -0,0 +1,2 @@
+
+end
diff --git a/src/tag.sig b/src/tag.sig
new file mode 100644
index 0000000..c19a353
--- /dev/null
+++ b/src/tag.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature TAG = sig
+
+ val tag : Core.file -> Core.file
+
+end
diff --git a/src/tag.sml b/src/tag.sml
new file mode 100644
index 0000000..94e5d44
--- /dev/null
+++ b/src/tag.sml
@@ -0,0 +1,356 @@
+(* Copyright (c) 2008, 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 Tag :> TAG = struct
+
+open Core
+
+structure U = CoreUtil
+structure E = CoreEnv
+
+structure IM = IntBinaryMap
+structure SM = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+structure UnionFind :> sig
+ type t
+ val empty : t
+ val equate : t * int * int -> t
+ val equal : t * int * int -> bool
+ val rep : t * int -> int
+ end = struct
+
+type t = int IM.map
+
+val empty = IM.empty
+
+fun rep (t, n) =
+ case IM.find (t, n) of
+ NONE => n
+ | SOME n' => rep (t, n')
+
+fun equate (t, n1, n2) =
+ let
+ val r1 = rep (t, n1)
+ val r2 = rep (t, n2)
+ in
+ if r1 = r2 then
+ t
+ else
+ IM.insert (t, r1, r2)
+ end
+
+fun equal (t, n1, n2) = rep (t, n1) = rep (t, n2)
+
+end
+
+fun kind (k, s) = (k, s)
+fun con (c, s) = (c, s)
+
+fun both (loc, f) = (ErrorMsg.errorAt loc ("Function " ^ f ^ " needed for multiple modes (link, form, RPC handler).");
+ TextIO.output (TextIO.stdErr,
+ "Make sure that the signature of the containing module hides any form/RPC handlers.\n"))
+
+fun exp uf env (e, s) =
+ let
+ fun tagIt (e, ek : export_kind, newAttr, (count, tags, byTag, newTags)) =
+ let
+ val loc = #2 e
+
+ val eOrig = e
+
+ fun unravel (e, _) =
+ case e of
+ ENamed n => (n, [])
+ | EApp (e1, e2) =>
+ let
+ val (n, es) = unravel e1
+ in
+ (n, es @ [e2])
+ end
+ | _ => (ErrorMsg.errorAt loc ("Invalid " ^ newAttr
+ ^ " expression");
+ Print.epreface ("Expression",
+ CorePrint.p_exp env eOrig);
+ (0, []))
+
+ val (f, args) = unravel e
+ in
+ if f = 0 then
+ (e, (count, tags, byTag, newTags))
+ else
+ let
+ val f = UnionFind.rep (uf, f)
+
+ val (cn, count, tags, newTags) =
+ case IM.find (tags, f) of
+ NONE =>
+ (count, count + 1, IM.insert (tags, f, count),
+ (ek, f, count) :: newTags)
+ | SOME cn => (cn, count, tags, newTags)
+
+ val (_, _, _, s) = E.lookupENamed env f
+
+ val byTag = case SM.find (byTag, s) of
+ NONE => SM.insert (byTag, s, (ek, f))
+ | SOME (ek', f') =>
+ (if f = f' then
+ ()
+ else
+ ErrorMsg.errorAt loc
+ ("Duplicate HTTP tag "
+ ^ s);
+ if ek = ek' then
+ ()
+ else
+ both (loc, s);
+ byTag)
+
+ val e = (EClosure (cn, args), loc)
+ in
+ (e, (count, tags, byTag, newTags))
+ end
+ end
+ in
+ case e of
+ EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (EFfi ("Basis", "tag"),
+ loc), given), _), absent), _), outer), _), inner), _),
+ useOuter), _), useInner), _), bindOuter), _), bindInner), _),
+ class), _),
+ dynClass), _),
+ style), _),
+ dynStyle), _),
+ attrs), _),
+ tag), _),
+ xml) =>
+ (case attrs of
+ (ERecord xets, _) =>
+ let
+ val (xets, s) =
+ ListUtil.foldlMap (fn ((x, e, t), s) =>
+ let
+ fun tagIt' (ek, newAttr) =
+ let
+ val (e', s) = tagIt (e, ek, newAttr, s)
+ val t = (CFfi ("Basis", "string"), loc)
+ in
+ (((CName newAttr, loc), e', t), s)
+ end
+ in
+ case x of
+ (CName "Link", _) => tagIt' (Link ReadCookieWrite, "Link")
+ | (CName "Action", _) => tagIt' (Action ReadWrite, "Action")
+ | _ => ((x, e, t), s)
+ end)
+ s xets
+ in
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (EApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (EFfi ("Basis", "tag"),
+ loc), given), loc), absent), loc), outer), loc), inner), loc),
+ useOuter), loc), useInner), loc), bindOuter), loc), bindInner), loc),
+ class), loc), dynClass), loc), style), loc), dynStyle), loc),
+ (ERecord xets, loc)), loc),
+ tag), loc),
+ xml), s)
+ end
+ | _ => (e, s))
+
+ | EFfiApp ("Basis", "url", [((ERel 0, _), _)]) => (e, s)
+
+ | EFfiApp ("Basis", "url", [(e, t)]) =>
+ let
+ val (e, s) = tagIt (e, Link ReadCookieWrite, "Url", s)
+ in
+ (EFfiApp ("Basis", "url", [(e, t)]), s)
+ end
+
+ | EFfiApp ("Basis", "effectfulUrl", [((ERel 0, _), _)]) => (e, s)
+
+ | EFfiApp ("Basis", "effectfulUrl", [(e, t)]) =>
+ let
+ val (e, s) = tagIt (e, Extern ReadCookieWrite, "Url", s)
+ in
+ (EFfiApp ("Basis", "url", [(e, t)]), s)
+ end
+
+ | EApp ((ENamed n, _), e') =>
+ let
+ val (_, _, eo, _) = E.lookupENamed env n
+ in
+ case eo of
+ SOME (EAbs (_, _, _, (EFfiApp ("Basis", "url", [((ERel 0, _), t)]), _)), _) =>
+ let
+ val (e, s) = tagIt (e', Link ReadCookieWrite, "Url", s)
+ in
+ (EFfiApp ("Basis", "url", [(e, t)]), s)
+ end
+ | _ => (e, s)
+ end
+
+ | _ => (e, s)
+ end
+
+fun decl (d, s) = (d, s)
+
+fun tag file =
+ let
+ val count = U.File.maxName file
+
+ fun doDecl (d as (d', loc), (env, count, tags, byTag, uf)) =
+ case d' of
+ DExport (ek, n, _) =>
+ let
+ val (_, _, _, s) = E.lookupENamed env n
+ in
+ case SM.find (byTag, s) of
+ NONE => ([d], (env, count, tags, byTag, uf))
+ | SOME (ek', n') =>
+ (if ek = ek' then
+ ()
+ else
+ both (loc, s);
+ ([], (env, count, tags, byTag, uf)))
+ end
+ | _ =>
+ let
+ val env' = E.declBinds env d
+ val env'' = case d' of
+ DValRec _ => env'
+ | _ => env
+
+ val (d, (count, tags, byTag, newTags)) =
+ U.Decl.foldMap {kind = kind,
+ con = con,
+ exp = exp uf env'',
+ decl = decl}
+ (count, tags, byTag, []) d
+
+ val env = env'
+
+ val newDs = map
+ (fn (ek, f, cn) =>
+ let
+ val unit = (TRecord (CRecord ((KType, loc), []), loc), loc)
+
+ fun unravel (all as (t, _)) =
+ case t of
+ TFun (dom, ran) =>
+ let
+ val (args, result) = unravel ran
+ in
+ (dom :: args, result)
+ end
+ | _ => ([], all)
+
+ val (fnam, t, _, tag) = E.lookupENamed env f
+ val (args, result) = unravel t
+
+ val (abs, t) =
+ case args of
+ [] =>
+ let
+ val app = (EApp ((ENamed f, loc), (ERecord [], loc)), loc)
+ val body = (EWrite app, loc)
+ in
+ (body,
+ (TFun (unit, unit), loc))
+ end
+ | _ =>
+ let
+ val (app, _) = foldl (fn (t, (app, n)) =>
+ ((EApp (app, (ERel n, loc)), loc),
+ n - 1))
+ ((ENamed f, loc), length args - 1) args
+ val app = (EApp (app, (ERecord [], loc)), loc)
+ val body = (EWrite app, loc)
+ val t = (TFun (unit, unit), loc)
+ val (abs, _, t) = foldr (fn (t, (abs, n, rest)) =>
+ ((EAbs ("x" ^ Int.toString n,
+ t,
+ rest,
+ abs), loc),
+ n + 1,
+ (TFun (t, rest), loc)))
+ (body, 0, t) args
+ in
+ (abs, t)
+ end
+ in
+ (("wrap_" ^ fnam, cn, t, abs, tag),
+ (DExport (ek, cn, false), loc))
+ end) newTags
+
+ val (newVals, newExports) = ListPair.unzip newDs
+
+ val ds = case d of
+ (DValRec vis, _) => [(DValRec (vis @ newVals), loc)]
+ | _ => map (fn vi => (DVal vi, loc)) newVals @ [d]
+
+ val uf = case d' of
+ DVal (_, n1, _, (ENamed n2, _), _) => UnionFind.equate (uf, n1, n2)
+ | _ => uf
+ in
+ (ds @ newExports, (env, count, tags, byTag, uf))
+ end
+
+ val (file, _) = ListUtil.foldlMapConcat doDecl (CoreEnv.empty, count+1, IM.empty, SM.empty, UnionFind.empty) file
+ in
+ file
+ end
+
+end
diff --git a/src/termination.sig b/src/termination.sig
new file mode 100644
index 0000000..1de00f1
--- /dev/null
+++ b/src/termination.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature TERMINATION = sig
+
+ val check : Elab.file -> unit
+
+end
diff --git a/src/termination.sml b/src/termination.sml
new file mode 100644
index 0000000..f0ec46d
--- /dev/null
+++ b/src/termination.sml
@@ -0,0 +1,396 @@
+(* Copyright (c) 2008, 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 Termination :> TERMINATION = struct
+
+open Elab
+
+structure E = ElabEnv
+structure IM = IntBinaryMap
+structure IS = IntBinarySet
+
+datatype pedigree =
+ Func of int
+ | Arg of int * int * con
+ | Subarg of int * int * con
+ | Rabble
+
+fun p2s p =
+ case p of
+ Func i => "Func" ^ Int.toString i
+ | Arg (i, j, _) => "Arg" ^ Int.toString i ^ "," ^ Int.toString j
+ | Subarg (i, j, _) => "Subarg" ^ Int.toString i ^ "," ^ Int.toString j
+ | Rabble => "Rabble"
+
+fun declOk' env (d, loc) =
+ case d of
+ DValRec vis =>
+ let
+ val nfns = length vis
+
+ val fenv = ListUtil.foldli (fn (i, (_, j, _, _), fenv) => IM.insert (fenv, j, i)) IM.empty vis
+
+ fun namesEq ((c1, _), (c2, _)) =
+ case (c1, c2) of
+ (CName s1, CName s2) => s1 = s2
+ | (CRel n1, CRel n2) => n1 = n2
+ | (CNamed n1, CNamed n2) => n1 = n2
+ | (CModProj n1, CModProj n2) => n1 = n2
+ | _ => false
+
+ fun patCon pc =
+ let
+ fun unravel (t, _) =
+ case t of
+ TCFun (_, _, _, t) => unravel t
+ | TFun (dom, _) => dom
+ | _ => raise Fail "Termination: Unexpected constructor type"
+ in
+ case pc of
+ PConVar i =>
+ let
+ val (_, t) = E.lookupENamed env i
+ in
+ unravel t
+ end
+ | PConProj (m1, ms, x) =>
+ let
+ val (str, sgn) = E.chaseMpath env (m1, ms)
+ in
+ case E.projectVal env {str = str, sgn = sgn, field = x} of
+ NONE => raise Fail "Termination: Bad constructor projection"
+ | SOME t => unravel t
+ end
+ end
+
+ fun pat penv (p, (pt, _)) =
+ let
+ fun con (i, j, pc, pt') = pat penv (Subarg (i, j, patCon pc), pt')
+
+ fun record (i, j, t, xps) =
+ case t of
+ (TRecord (CRecord (_, xts), _), _) =>
+ foldl (fn ((x, pt', _), penv) =>
+ let
+ val p' =
+ case List.find (fn (x', _) =>
+ namesEq ((CName x, ErrorMsg.dummySpan), x')) xts of
+ NONE => Rabble
+ | SOME (_, t) => Subarg (i, j, t)
+ in
+ pat penv (p', pt')
+ end) penv xps
+ | _ => foldl (fn ((_, pt', _), penv) => pat penv (Rabble, pt')) penv xps
+ in
+ case (p, pt) of
+ (_, PVar _) => p :: penv
+ | (_, PPrim _) => penv
+ | (_, PCon (_, _, _, NONE)) => penv
+ | (Arg (i, j, _), PCon (_, pc, _, SOME pt')) => con (i, j, pc, pt')
+ | (Subarg (i, j, _), PCon (_, pc, _, SOME pt')) => con (i, j, pc, pt')
+ | (_, PCon (_, _, _, SOME pt')) => pat penv (Rabble, pt')
+ | (Arg (i, j, t), PRecord xps) => record (i, j, t, xps)
+ | (Subarg (i, j, t), PRecord xps) => record (i, j, t, xps)
+ | (_, PRecord xps) => foldl (fn ((_, pt', _), penv) => pat penv (Rabble, pt')) penv xps
+ end
+
+ fun exp parent (penv, calls) e =
+ let
+ val default = (Rabble, calls)
+
+ fun apps () =
+ let
+ fun combiner calls e =
+ case #1 e of
+ EApp ((ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (ECApp (
+ (EModProj (m, [], "tag"), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ _), _),
+ (ERecord xets, _)) =>
+ let
+ val checkName =
+ case E.lookupStrNamed env m of
+ ("Basis", _) => (fn x : con => case #1 x of
+ CName s => s = "Link"
+ orelse s = "Action"
+ | _ => false)
+ | _ => (fn _ => false)
+
+ val calls = foldl (fn ((x, e, _), calls) =>
+ if checkName x then
+ calls
+ else
+ #2 (exp parent (penv, calls) e)) calls xets
+ in
+ (Rabble, [Rabble], calls)
+ end
+
+ | EApp (e1, e2) =>
+ let
+ val (p1, ps, calls) = combiner calls e1
+ val (p2, calls) = exp parent (penv, calls) e2
+
+ val p = case p1 of
+ Rabble => Rabble
+ | Arg _ => Rabble
+ | Subarg (i, j, (TFun (_, ran), _)) => Subarg (i, j, ran)
+ | Subarg _ => Rabble
+ | Func _ => Rabble
+ in
+ (p, ps @ [p2], calls)
+ end
+ | ECApp (e, _) =>
+ let
+ val (p, ps, calls) = combiner calls e
+
+ val p = case p of
+ Rabble => Rabble
+ | Arg _ => Rabble
+ | Subarg (i, j, (TCFun (_, _, _, ran), _)) => Subarg (i, j, ran)
+ | Subarg _ => Rabble
+ | Func _ => Rabble
+ in
+ (p, ps, calls)
+ end
+ | EKApp (e, _) => combiner calls e
+ | _ =>
+ let
+ val (p, calls) = exp parent (penv, calls) e
+ in
+ (*Print.prefaces "Head" [("e", ElabPrint.p_exp env e)];
+ print (p2s p ^ "\n");*)
+ (p, [p], calls)
+ end
+
+ val (p, ps, calls) = combiner calls e
+
+ val calls =
+ case ps of
+ [] => raise Fail "Termination: Empty ps"
+ | f :: ps =>
+ case f of
+ Func i => (parent, i, ps) :: calls
+ | _ => calls
+ in
+ (p, calls)
+ end
+ in
+ case #1 e of
+ EPrim _ => default
+ | ERel n => (List.nth (penv, n), calls)
+ | ENamed n =>
+ let
+ val p = case IM.find (fenv, n) of
+ NONE => Rabble
+ | SOME n' => Func n'
+ in
+ (p, calls)
+ end
+ | EModProj _ => default
+
+ | EApp _ => apps ()
+ | EAbs (_, _, _, e) =>
+ let
+ val (_, calls) = exp parent (Rabble :: penv, calls) e
+ in
+ (Rabble, calls)
+ end
+ | ECApp _ => apps ()
+ | ECAbs (_, _, _, e) =>
+ let
+ val (_, calls) = exp parent (penv, calls) e
+ in
+ (Rabble, calls)
+ end
+ | EKApp _ => apps ()
+ | EKAbs (_, e) =>
+ let
+ val (_, calls) = exp parent (penv, calls) e
+ in
+ (Rabble, calls)
+ end
+
+ | ERecord xets =>
+ let
+ val calls = foldl (fn ((_, e, _), calls) => #2 (exp parent (penv, calls) e)) calls xets
+ in
+ (Rabble, calls)
+ end
+ | EField (e, x, _) =>
+ let
+ val (p, calls) = exp parent (penv, calls) e
+ val p =
+ case p of
+ Subarg (i, j, (TRecord (CRecord (_, xts), _), _)) =>
+ (case List.find (fn (x', _) => namesEq (x, x')) xts of
+ NONE => Rabble
+ | SOME (_, t) => Subarg (i, j, t))
+ | _ => Rabble
+ in
+ (p, calls)
+ end
+ | ECut (e, _, _) =>
+ let
+ val (_, calls) = exp parent (penv, calls) e
+ in
+ (Rabble, calls)
+ end
+ | ECutMulti (e, _, _) =>
+ let
+ val (_, calls) = exp parent (penv, calls) e
+ in
+ (Rabble, calls)
+ end
+ | EConcat (e1, _, e2, _) =>
+ let
+ val (_, calls) = exp parent (penv, calls) e1
+ val (_, calls) = exp parent (penv, calls) e2
+ in
+ (Rabble, calls)
+ end
+
+ | ECase (e, pes, _) =>
+ let
+ val (p, calls) = exp parent (penv, calls) e
+
+ val calls = foldl (fn ((pt, e), calls) =>
+ let
+ val penv = pat penv (p, pt)
+ val (_, calls) = exp parent (penv, calls) e
+ in
+ calls
+ end) calls pes
+ in
+ (Rabble, calls)
+ end
+
+ | EError => (Rabble, calls)
+ | EUnif (ref (SOME e)) => exp parent (penv, calls) e
+ | EUnif (ref NONE) => (Rabble, calls)
+
+ | ELet (eds, e, _) =>
+ let
+ fun extPenv ((ed, _), penv) =
+ case ed of
+ EDVal _ => Rabble :: penv
+ | EDValRec vis => foldl (fn (_, penv) => Rabble :: penv) penv vis
+ in
+ exp parent (foldl extPenv penv eds, calls) e
+ end
+ end
+
+ fun doVali (i, (_, f, _, e), calls) =
+ let
+ fun unravel (e, j, penv) =
+ case #1 e of
+ EAbs (_, t, _, e) =>
+ unravel (e, j + 1, Arg (i, j, t) :: penv)
+ | ECAbs (_, _, _, e) =>
+ unravel (e, j, penv)
+ | _ => (j, #2 (exp f (penv, calls) e))
+ in
+ unravel (e, 0, [])
+ end
+
+ val (ns, calls) = ListUtil.foldliMap doVali [] vis
+
+ fun isRecursive (from, to, _) =
+ let
+ fun search (at, soFar) =
+ at = from
+ orelse List.exists (fn (from', to', _) =>
+ from' = at
+ andalso not (IS.member (soFar, to'))
+ andalso search (to', IS.add (soFar, to')))
+ calls
+ in
+ search (to, IS.empty)
+ end
+
+ val calls = List.filter isRecursive calls
+
+ fun search (ns, choices) =
+ case ns of
+ [] =>
+ let
+ val choices = rev choices
+ in
+ List.all (fn (_, f, args) =>
+ let
+ val recArg = List.nth (choices, f)
+
+ fun isDatatype (t, _) =
+ case t of
+ CNamed _ => true
+ | CModProj _ => true
+ | CApp (t, _) => isDatatype t
+ | _ => false
+ in
+ length args > recArg andalso
+ case List.nth (args, recArg) of
+ Subarg (i, j, t) => isDatatype t andalso j = List.nth (choices, i)
+ | _ => false
+ end) calls
+ end
+ | n :: ns' =>
+ let
+ fun search' i =
+ i < n andalso (search (ns', i :: choices) orelse search' (i + 1))
+ in
+ search' 0
+ end
+ in
+ if search (ns, []) then
+ ()
+ else
+ ErrorMsg.errorAt loc "Can't prove termination of recursive function(s)"
+ end
+
+ | DStr (_, _, _, (StrConst ds, _)) => ignore (foldl declOk env ds)
+
+ | _ => ()
+
+and declOk (d, env) =
+ (declOk' env d;
+ E.declBinds env d)
+
+fun check ds = ignore (foldl declOk E.empty ds)
+
+end
diff --git a/src/toy_cache.sml b/src/toy_cache.sml
new file mode 100644
index 0000000..5c5aa45
--- /dev/null
+++ b/src/toy_cache.sml
@@ -0,0 +1,207 @@
+structure ToyCache : sig
+ val cache : Cache.cache
+end = struct
+
+
+(* Mono *)
+
+open Mono
+
+val dummyLoc = ErrorMsg.dummySpan
+val stringTyp = (TFfi ("Basis", "string"), dummyLoc)
+val optionStringTyp = (TOption stringTyp, dummyLoc)
+fun withTyp typ = map (fn exp => (exp, typ))
+
+fun ffiAppCache' (func, index, argTyps) =
+ EFfiApp ("Sqlcache", func ^ Int.toString index, argTyps)
+
+fun check (index, keys) =
+ ffiAppCache' ("check", index, withTyp stringTyp keys)
+
+fun store (index, keys, value) =
+ ffiAppCache' ("store", index, (value, stringTyp) :: withTyp stringTyp keys)
+
+fun flush (index, keys) =
+ ffiAppCache' ("flush", index, withTyp optionStringTyp keys)
+
+fun lock (index, keys) =
+ raise Fail "ToyCache doesn't yet implement lock"
+
+
+(* Cjr *)
+
+open Print
+open Print.PD
+
+fun setupQuery {index, params} =
+ let
+
+ val i = Int.toString index
+
+ fun paramRepeat itemi sep =
+ let
+ fun f n =
+ if n < 0 then ""
+ else if n = 0 then itemi (Int.toString 0)
+ else f (n-1) ^ sep ^ itemi (Int.toString n)
+ in
+ f (params - 1)
+ end
+
+ fun paramRepeatInit itemi sep =
+ if params = 0 then "" else sep ^ paramRepeat itemi sep
+
+ val args = paramRepeatInit (fn p => "uw_Basis_string p" ^ p) ", "
+
+ val decls = paramRepeat (fn p => "uw_Basis_string param" ^ i ^ "_"
+ ^ p ^ " = NULL;")
+ "\n"
+
+ val sets = paramRepeat (fn p => "param" ^ i ^ "_" ^ p
+ ^ " = strdup(p" ^ p ^ ");")
+ "\n"
+
+ val frees = paramRepeat (fn p => "free(param" ^ i ^ "_" ^ p ^ ");")
+ "\n"
+
+ val eqs = paramRepeatInit (fn p => "strcmp(param" ^ i ^ "_" ^ p
+ ^ ", p" ^ p ^ ")")
+ " || "
+
+ (* Using [!=] instead of [==] to mimic [strcmp]. *)
+ val eqsNull = paramRepeatInit (fn p => "(p" ^ p ^ " == NULL || "
+ ^ "!strcmp(param" ^ i ^ "_"
+ ^ p ^ ", p" ^ p ^ "))")
+ " && "
+
+ in
+ Print.box
+ [string "static char *cacheQuery",
+ string i,
+ string " = NULL;",
+ newline,
+ string "static char *cacheWrite",
+ string i,
+ string " = NULL;",
+ newline,
+ string decls,
+ newline,
+ string "static uw_Basis_string uw_Sqlcache_check",
+ string i,
+ string "(uw_context ctx",
+ string args,
+ string ") {",
+ newline,
+ string "if (cacheWrite",
+ string i,
+ (* ASK: is returning the pointer okay? Should we duplicate? *)
+ string " == NULL",
+ string eqs,
+ string ") {",
+ newline,
+ string "puts(\"SQLCACHE: miss ",
+ string i,
+ string ".\");",
+ newline,
+ string "uw_recordingStart(ctx);",
+ newline,
+ string "return NULL;",
+ newline,
+ string "} else {",
+ newline,
+ string "puts(\"SQLCACHE: hit ",
+ string i,
+ string ".\");",
+ newline,
+ string " if (cacheWrite",
+ string i,
+ string " != NULL) { uw_write(ctx, cacheWrite",
+ string i,
+ string "); }",
+ newline,
+ string "return cacheQuery",
+ string i,
+ string ";",
+ newline,
+ string "} };",
+ newline,
+ string "static uw_unit uw_Sqlcache_store",
+ string i,
+ string "(uw_context ctx, uw_Basis_string s",
+ string args,
+ string ") {",
+ newline,
+ string "free(cacheQuery",
+ string i,
+ string "); free(cacheWrite",
+ string i,
+ string ");",
+ newline,
+ string frees,
+ newline,
+ string "cacheQuery",
+ string i,
+ string " = strdup(s); cacheWrite",
+ string i,
+ string " = uw_recordingRead(ctx);",
+ newline,
+ string sets,
+ newline,
+ string "puts(\"SQLCACHE: store ",
+ string i,
+ string ".\");",
+ newline,
+ string "return uw_unit_v;",
+ newline,
+ string "};",
+ newline,
+ string "static uw_unit uw_Sqlcache_flush",
+ string i,
+ string "(uw_context ctx",
+ string args,
+ string ") {",
+ newline,
+ string "if (cacheQuery",
+ string i,
+ string " != NULL",
+ string eqsNull,
+ string ") {",
+ newline,
+ string "free(cacheQuery",
+ string i,
+ string ");",
+ newline,
+ string "cacheQuery",
+ string i,
+ string " = NULL;",
+ newline,
+ string "free(cacheWrite",
+ string i,
+ string ");",
+ newline,
+ string "cacheWrite",
+ string i,
+ string " = NULL;",
+ newline,
+ string "puts(\"SQLCACHE: flush ",
+ string i,
+ string ".\");}",
+ newline,
+ string "else { puts(\"SQLCACHE: keep ",
+ string i,
+ string ".\"); } return uw_unit_v;",
+ newline,
+ string "};",
+ newline,
+ newline]
+ end
+
+val setupGlobal = string "/* No global setup for toy cache. */"
+
+
+(* Bundled up. *)
+
+val cache = {check = check, store = store, flush = flush, lock = lock,
+ setupQuery = setupQuery, setupGlobal = setupGlobal}
+
+end
diff --git a/src/triple_key_fn.sml b/src/triple_key_fn.sml
new file mode 100644
index 0000000..ba77c60
--- /dev/null
+++ b/src/triple_key_fn.sml
@@ -0,0 +1,15 @@
+functor TripleKeyFn (structure I : ORD_KEY
+ structure J : ORD_KEY
+ structure K : ORD_KEY)
+ : ORD_KEY where type ord_key = I.ord_key * J.ord_key * K.ord_key = struct
+
+type ord_key = I.ord_key * J.ord_key * K.ord_key
+
+fun compare ((i1, j1, k1), (i2, j2, k2)) =
+ case I.compare (i1, i2) of
+ EQUAL => (case J.compare (j1, j2) of
+ EQUAL => K.compare (k1, k2)
+ | ord => ord)
+ | ord => ord
+
+end
diff --git a/src/tutorial.sig b/src/tutorial.sig
new file mode 100644
index 0000000..cda9b01
--- /dev/null
+++ b/src/tutorial.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 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.
+ *)
+
+signature TUTORIAL = sig
+
+ val make : string -> unit
+
+end
diff --git a/src/tutorial.sml b/src/tutorial.sml
new file mode 100644
index 0000000..0c2f908
--- /dev/null
+++ b/src/tutorial.sml
@@ -0,0 +1,322 @@
+(* Copyright (c) 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 Tutorial :> TUTORIAL = struct
+
+fun readAll inf =
+ let
+ fun loop acc =
+ case TextIO.inputLine inf of
+ NONE => Substring.full (String.concat (rev acc))
+ | SOME line => loop (line :: acc)
+ in
+ loop []
+ before TextIO.closeIn inf
+ end
+
+val readAllFile = readAll o FileIO.txtOpenIn
+
+fun fixupFile (fname, title) =
+ let
+ val source = readAllFile "/tmp/final.html"
+ val outf = TextIO.openOut (OS.Path.mkAbsolute {relativeTo = OS.FileSys.getDir (),
+ path = OS.Path.joinBaseExt {base = OS.Path.base fname, ext = SOME "html"}})
+
+ val (befor, after) = Substring.position "<title>" source
+
+ fun proseLoop source =
+ let
+ val (befor, after) = Substring.splitl (fn ch => ch <> #"&") source
+ in
+ if Substring.isEmpty after then
+ TextIO.outputSubstr (outf, source)
+ else if Substring.size after >= 4 andalso Substring.string (Substring.slice (after, 1, SOME 3)) = "lt;" then
+ (TextIO.outputSubstr (outf, befor);
+ TextIO.output (outf, "<");
+ proseLoop (Substring.slice (after, 4, NONE)))
+ else if Substring.size after >= 4 andalso Substring.string (Substring.slice (after, 1, SOME 3)) = "gt;" then
+ (TextIO.outputSubstr (outf, befor);
+ TextIO.output (outf, ">");
+ proseLoop (Substring.slice (after, 4, NONE)))
+ else if Substring.size after >= 5 andalso Substring.string (Substring.slice (after, 1, SOME 4)) = "amp;" then
+ (TextIO.outputSubstr (outf, befor);
+ TextIO.output (outf, "&");
+ proseLoop (Substring.slice (after, 5, NONE)))
+ else
+ raise Fail "Unsupported HTML escape"
+ end
+
+ fun loop source =
+ let
+ val (befor, after) = Substring.position "<span class=\"comment-delimiter\">(* </span><span class=\"comment\">" source
+ in
+ if Substring.isEmpty after then
+ TextIO.outputSubstr (outf, source)
+ else
+ let
+ val (befor', after) = Substring.position " </span><span class=\"comment-delimiter\">*)</span>"
+ (Substring.slice (after, 64, NONE))
+ in
+ if Substring.isEmpty after then
+ TextIO.outputSubstr (outf, source)
+ else
+ (TextIO.outputSubstr (outf, befor);
+ TextIO.output (outf, "</pre>");
+ if Substring.size befor' >= 1 andalso Substring.sub (befor', 0) = #"*" then
+ (TextIO.output (outf, "<h2>");
+ proseLoop (Substring.slice (befor', 2, NONE));
+ TextIO.output (outf, "</h2>"))
+ else
+ (TextIO.output (outf, "<div class=\"prose\">");
+ proseLoop befor';
+ TextIO.output (outf, "</div>"));
+ TextIO.output (outf, "<pre>");
+ loop (Substring.slice (after, 49, NONE)))
+ end
+ end
+ in
+ if Substring.isEmpty after then
+ raise Fail ("Missing <title> for " ^ title)
+ else
+ (TextIO.outputSubstr (outf, befor);
+ TextIO.output (outf, "<style type=\"text/css\">\n");
+ TextIO.output (outf, "<!--\n");
+ TextIO.output (outf, "\tdiv.prose {\n");
+ TextIO.output (outf, "\t\tfont-family: Arial;\n");
+ TextIO.output (outf, "\t\tbackground-color: #CCFFCC;\n");
+ TextIO.output (outf, "\t\tborder-style: solid;\n");
+ TextIO.output (outf, "\t\tpadding: 5px;\n");
+ TextIO.output (outf, "\t\tfont-size: larger;\n");
+ TextIO.output (outf, "\t}\n");
+ TextIO.output (outf, "\th2 {\n");
+ TextIO.output (outf, "\t\tfont-family: Arial;\n");
+ TextIO.output (outf, "\t\tfont-size: 20pt;\n");
+ TextIO.output (outf, "\t\tbackground-color: #99FF99;\n");
+ TextIO.output (outf, "\t\tpadding: 5px;\n");
+ TextIO.output (outf, "\t}\n");
+ TextIO.output (outf, "\ta:link {\n");
+ TextIO.output (outf, "\t\ttext-decoration: underline;\n");
+ TextIO.output (outf, "\t\tcolor: blue;\n");
+ TextIO.output (outf, "\t}\n");
+ TextIO.output (outf, "\ta:visited {\n");
+ TextIO.output (outf, "\t\ttext-decoration: underline;\n");
+ TextIO.output (outf, "\t\tcolor: red;\n");
+ TextIO.output (outf, "\t}\n");
+ TextIO.output (outf, "-->\n");
+ TextIO.output (outf, "</style>\n");
+ TextIO.output (outf, "<title>");
+ TextIO.output (outf, title);
+ let
+ val (befor, after) = Substring.position "</title>" after
+ in
+ if Substring.isEmpty after then
+ raise Fail ("Missing </title> for " ^ title)
+ else
+ let
+ val (befor, after) = Substring.position "<body>" after
+ in
+ if Substring.isEmpty after then
+ raise Fail ("Missing <body> for " ^ title)
+ else
+ (TextIO.outputSubstr (outf, befor);
+ TextIO.output (outf, "<body><h1>");
+ TextIO.output (outf, title);
+ TextIO.output (outf, "</h1>");
+ loop (Substring.slice (after, 6, NONE)))
+ end
+ end;
+ TextIO.closeOut outf)
+ end
+
+fun doUr fname =
+ let
+ val inf = FileIO.txtOpenIn fname
+
+ val title = case TextIO.inputLine inf of
+ NONE => raise Fail ("No title comment at start of " ^ fname)
+ | SOME title => title
+
+ val title = String.substring (title, 3, size title - 7)
+
+ val eval = TextIO.openOut "/tmp/eval.ur"
+ val gen = TextIO.openOut "/tmp/gen.ur"
+
+ fun untilEnd source =
+ let
+ val (befor, after) = Substring.position "(* end *)" source
+ in
+ if Substring.isEmpty after then
+ (source, Substring.full "")
+ else
+ (befor, Substring.slice (after, 9, NONE))
+ end
+
+ fun doDirectives (count, source) =
+ let
+ val safe = String.translate (fn #"<" => "&lt;"
+ | #"&" => "&amp;"
+ | #"{" => "&#123;"
+ | #"(" => "&#40;"
+ | #"\n" => "&#40;*NL*)\n"
+ | #" " => "&#40;*NL*) "
+ | ch => str ch) o Substring.string
+
+ val (befor, after) = Substring.position "(* begin " source
+
+ fun default () = (TextIO.outputSubstr (eval, source);
+ TextIO.output (gen, safe source))
+ in
+ if Substring.isEmpty after then
+ default ()
+ else
+ let
+ val (command, after) = Substring.splitl (not o Char.isSpace) (Substring.slice (after, 9, NONE))
+ in
+ if Substring.isEmpty after then
+ default ()
+ else
+ let
+ val (_, rest) = Substring.position "*)" after
+ in
+ if Substring.isEmpty rest then
+ default ()
+ else
+ let
+ val (arg, source) = untilEnd (Substring.slice (rest, 3, NONE))
+ val () = (TextIO.outputSubstr (eval, befor);
+ TextIO.output (gen, safe befor))
+ val (count, skip) =
+ case Substring.string command of
+ "hide" => (TextIO.outputSubstr (eval, arg);
+ (count, true))
+ | "eval" => (TextIO.output (eval, "val _eval");
+ TextIO.output (eval, Int.toString count);
+ TextIO.output (eval, " = ");
+ TextIO.outputSubstr (eval, arg);
+ TextIO.output (eval, "\n\n");
+
+ TextIO.output (gen, safe arg);
+ TextIO.output (gen, "== {[_eval");
+ TextIO.output (gen, Int.toString count);
+ TextIO.output (gen, "]}");
+
+ (count + 1, false))
+ | s => raise Fail ("Unknown tutorial directive: " ^ s)
+ in
+ doDirectives (count, if skip then
+ #2 (Substring.splitl Char.isSpace source)
+ else
+ source)
+ end
+ end
+ end
+ end
+ in
+ doDirectives (0, readAll inf);
+ TextIO.closeOut gen;
+
+ TextIO.output (eval, "\n\nfun main () : transaction page =\nreturn <xml><body>");
+ TextIO.outputSubstr (eval, readAllFile "/tmp/gen.ur");
+ TextIO.output (eval, "</body></xml>");
+ TextIO.closeOut eval;
+
+ if Compiler.compile "/tmp/eval" then
+ let
+ val proc = Unix.execute ("/bin/sh", ["-c", "/tmp/eval.exe /main"])
+ val inf = Unix.textInstreamOf proc
+ val s = readAll inf
+ val _ = Unix.reap proc
+
+ val (befor, after) = Substring.position "<body>" s
+ in
+ if Substring.isEmpty after then
+ print ("Bad output for " ^ fname ^ "! [1]\n")
+ else
+ let
+ val after = Substring.slice (after, 6, NONE)
+ val (befor, after) = Substring.position "</body>" after
+ in
+ if Substring.isEmpty after then
+ print ("Bad output for " ^ fname ^ "! [2]\n")
+ else
+ let
+ val outf = TextIO.openOut "/tmp/final.ur"
+
+ fun eatNls source =
+ let
+ val (befor, after) = Substring.position "(*NL*)" source
+ in
+ if Substring.isEmpty after then
+ TextIO.outputSubstr (outf, source)
+ else
+ (TextIO.outputSubstr (outf, befor);
+ eatNls (Substring.slice (after, 6, NONE)))
+ end
+
+ val cmd = "emacs --eval \"(progn "
+ ^ "(global-font-lock-mode t) "
+ ^ "(add-to-list 'load-path \\\""
+ ^ !Settings.configSitelisp
+ ^ "/\\\") "
+ ^ "(load \\\"urweb-mode-startup\\\") "
+ ^ "(urweb-mode) "
+ ^ "(find-file \\\"/tmp/final2.ur\\\") "
+ ^ "(switch-to-buffer (htmlize-buffer)) "
+ ^ "(write-file \\\"/tmp/final.html\\\") "
+ ^ "(kill-emacs))\""
+ in
+ eatNls befor;
+ TextIO.closeOut outf;
+ ignore (OS.Process.system "sed -e 's/&lt;/</g;s/&amp;/\\&/g' </tmp/final.ur >/tmp/final2.ur");
+ ignore (OS.Process.system cmd);
+ fixupFile (fname, title)
+ end
+ end
+ end
+ else
+ ()
+ end
+
+fun make dirname =
+ let
+ val dir = OS.FileSys.openDir dirname
+
+ fun doDir () =
+ case OS.FileSys.readDir dir of
+ NONE => OS.FileSys.closeDir dir
+ | SOME fname =>
+ (if OS.Path.ext fname = SOME "ur" then
+ doUr (OS.Path.joinDirFile {dir = dirname, file = fname})
+ else
+ ();
+ doDir ())
+ in
+ Settings.setProtocol "static";
+ doDir ()
+ end
+
+end
diff --git a/src/union_find_fn.sml b/src/union_find_fn.sml
new file mode 100644
index 0000000..7880591
--- /dev/null
+++ b/src/union_find_fn.sml
@@ -0,0 +1,58 @@
+functor UnionFindFn(K : ORD_KEY) :> sig
+ type unionFind
+ val empty : unionFind
+ val union : unionFind * K.ord_key * K.ord_key -> unionFind
+ val union' : (K.ord_key * K.ord_key) * unionFind -> unionFind
+ val together : unionFind * K.ord_key * K.ord_key -> bool
+ val classes : unionFind -> K.ord_key list list
+end = struct
+
+structure M = BinaryMapFn(K)
+structure S = BinarySetFn(K)
+
+datatype entry =
+ Set of S.set
+ | Pointer of K.ord_key
+
+(* First map is the union-find tree, second stores equivalence classes. *)
+type unionFind = entry M.map ref * S.set M.map
+
+val empty : unionFind = (ref M.empty, M.empty)
+
+fun findPair (uf, x) =
+ case M.find (!uf, x) of
+ NONE => (S.singleton x, x)
+ | SOME (Set set) => (set, x)
+ | SOME (Pointer parent) =>
+ let
+ val (set, rep) = findPair (uf, parent)
+ in
+ uf := M.insert (!uf, x, Pointer rep);
+ (set, rep)
+ end
+
+fun find ((uf, _), x) = (S.listItems o #1 o findPair) (uf, x)
+
+fun classes (_, cs) = (map S.listItems o M.listItems) cs
+
+fun together ((uf, _), x, y) = case K.compare (#2 (findPair (uf, x)), #2 (findPair (uf, y))) of
+ EQUAL => true
+ | _ => false
+
+fun union ((uf, cs), x, y) =
+ let
+ val (xSet, xRep) = findPair (uf, x)
+ val (ySet, yRep) = findPair (uf, y)
+ val xySet = S.union (xSet, ySet)
+ in
+ (ref (M.insert (M.insert (!uf, yRep, Pointer xRep),
+ xRep, Set xySet)),
+ M.insert (case M.find (cs, yRep) of
+ NONE => cs
+ | SOME _ => #1 (M.remove (cs, yRep)),
+ xRep, xySet))
+ end
+
+fun union' ((x, y), uf) = union (uf, x, y)
+
+end
diff --git a/src/unnest.sig b/src/unnest.sig
new file mode 100644
index 0000000..6508a78
--- /dev/null
+++ b/src/unnest.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Remove nested function definitions *)
+
+signature UNNEST = sig
+
+ val unnest : Elab.file -> Elab.file
+
+end
diff --git a/src/unnest.sml b/src/unnest.sml
new file mode 100644
index 0000000..7469ffd
--- /dev/null
+++ b/src/unnest.sml
@@ -0,0 +1,567 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+(* Remove nested function definitions *)
+
+structure Unnest :> UNNEST = struct
+
+open Elab
+
+structure E = ElabEnv
+structure U = ElabUtil
+
+structure IS = IntBinarySet
+
+fun liftExpInExp by =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn bound => fn e =>
+ case e of
+ ERel xn =>
+ if xn < bound then
+ e
+ else
+ ERel (xn + by)
+ | _ => e,
+ bind = fn (bound, U.Exp.RelE _) => bound + 1
+ | (bound, _) => bound}
+
+val subExpInExp =
+ U.Exp.mapB {kind = fn _ => fn k => k,
+ con = fn _ => fn c => c,
+ exp = fn (xn, rep) => fn e =>
+ case e of
+ ERel xn' =>
+ if xn' = xn then
+ #1 rep
+ else
+ e
+ | _ => e,
+ bind = fn ((xn, rep), U.Exp.RelE _) => (xn+1, E.liftExpInExp 0 rep)
+ | ((xn, rep), U.Exp.RelC _) => (xn, E.liftConInExp 0 rep)
+ | (ctx, _) => ctx}
+
+val fvsKind = U.Kind.foldB {kind = fn (kb, k, kvs) =>
+ case k of
+ KRel n =>
+ if n >= kb then
+ IS.add (kvs, n - kb)
+ else
+ kvs
+ | _ => kvs,
+ bind = fn (kb, b) => kb + 1}
+ 0 IS.empty
+
+val fvsCon = U.Con.foldB {kind = fn ((kb, _), k, st as (kvs, cvs)) =>
+ case k of
+ KRel n =>
+ if n >= kb then
+ (IS.add (kvs, n - kb), cvs)
+ else
+ st
+ | _ => st,
+ con = fn ((_, cb), c, st as (kvs, cvs)) =>
+ case c of
+ CRel n =>
+ if n >= cb then
+ (kvs, IS.add (cvs, n - cb))
+ else
+ st
+ | _ => st,
+ bind = fn (ctx as (kb, cb), b) =>
+ case b of
+ U.Con.RelK _ => (kb + 1, cb + 1)
+ | U.Con.RelC _ => (kb, cb + 1)
+ | _ => ctx}
+ (0, 0) (IS.empty, IS.empty)
+
+fun fvsExp nr = U.Exp.foldB {kind = fn ((kb, _, _), k, st as (kvs, cvs, evs)) =>
+ case k of
+ KRel n =>
+ if n >= kb then
+ (IS.add (kvs, n - kb), cvs, evs)
+ else
+ st
+ | _ => st,
+ con = fn ((kb, cb, eb), c, st as (kvs, cvs, evs)) =>
+ case c of
+ CRel n =>
+ if n >= cb then
+ (kvs, IS.add (cvs, n - cb), evs)
+ else
+ st
+ | _ => st,
+ exp = fn ((kb, cb, eb), e, st as (kvs, cvs, evs)) =>
+ case e of
+ ERel n =>
+ if n >= eb then
+ (kvs, cvs, IS.add (evs, n - eb))
+ else
+ st
+ | _ => st,
+ bind = fn (ctx as (kb, cb, eb), b) =>
+ case b of
+ U.Exp.RelK _ => (kb + 1, cb, eb)
+ | U.Exp.RelC _ => (kb, cb + 1, eb)
+ | U.Exp.RelE _ => (kb, cb, eb + 1)
+ | _ => ctx}
+ (0, 0, nr) (IS.empty, IS.empty, IS.empty)
+
+fun positionOf (x : int) ls =
+ let
+ fun po n ls =
+ case ls of
+ [] => raise Fail "Unnest.positionOf"
+ | x' :: ls' =>
+ if x' = x then
+ n
+ else
+ po (n + 1) ls'
+ in
+ po 0 ls
+ handle Fail _ => raise Fail ("Unnest.positionOf("
+ ^ Int.toString x
+ ^ ", "
+ ^ String.concatWith ";" (map Int.toString ls)
+ ^ ")")
+ end
+
+fun squishCon (kfv, cfv) =
+ U.Con.mapB {kind = fn (kb, _) => fn k =>
+ case k of
+ KRel n =>
+ if n >= kb then
+ KRel (positionOf (n - kb) kfv + kb)
+ else
+ k
+ | _ => k,
+ con = fn (_, cb) => fn c =>
+ case c of
+ CRel n =>
+ if n >= cb then
+ CRel (positionOf (n - cb) cfv + cb)
+ else
+ c
+ | _ => c,
+ bind = fn (ctx as (kb, cb), b) =>
+ case b of
+ U.Con.RelK _ => (kb + 1, cb)
+ | U.Con.RelC _ => (kb, cb + 1)
+ | _ => ctx}
+ (0, 0)
+
+fun squishExp (nr, kfv, cfv, efv) =
+ U.Exp.mapB {kind = fn (kb, _, _) => fn k =>
+ case k of
+ KRel n =>
+ if n >= kb then
+ KRel (positionOf (n - kb) kfv + kb)
+ else
+ k
+ | _ => k,
+ con = fn (_, cb, _) => fn c =>
+ case c of
+ CRel n =>
+ if n >= cb then
+ CRel (positionOf (n - cb) cfv + cb)
+ else
+ c
+ | _ => c,
+ exp = fn (_, _, eb) => fn e =>
+ case e of
+ ERel n =>
+ if n >= eb then
+ ERel (positionOf (n - eb) efv + eb - nr)
+ else
+ e
+ | _ => e,
+ bind = fn (ctx as (kb, cb, eb), b) =>
+ case b of
+ U.Exp.RelK _ => (kb + 1, cb, eb)
+ | U.Exp.RelC _ => (kb, cb + 1, eb)
+ | U.Exp.RelE _ => (kb, cb, eb + 1)
+ | _ => ctx}
+ (0, 0, nr)
+
+type state = {
+ maxName : int,
+ decls : (string * int * con * exp) list
+}
+
+fun kind (_, k, st) = (k, st)
+
+val basis = ref 0
+
+fun exp ((ns, ks, ts), e as old, st : state) =
+ case e of
+ ELet (eds, e, t) =>
+ let
+ (*val () = Print.prefaces "Letto" [("e", ElabPrint.p_exp E.empty (old, ErrorMsg.dummySpan))]*)
+
+ fun doSubst' (e, subs) = foldl (fn (p, e) => subExpInExp p e) e subs
+
+ fun doSubst (e, subs, by) =
+ let
+ val e = doSubst' (e, subs)
+ in
+ liftExpInExp (~by) (length subs) e
+ end
+
+ fun functionInside (t : con) =
+ case #1 t of
+ TFun _ => true
+ | CApp ((CModProj (basis', [], "transaction"), _), _) => basis' = !basis
+ | _ => false
+
+ val eds = map (fn ed =>
+ case #1 ed of
+ EDVal ((PVar (x, _), _), t, e) =>
+ if functionInside t then
+ (EDValRec [(x, t, E.liftExpInExp 0 e)], #2 ed)
+ else
+ ed
+ | _ => ed) eds
+
+ val (eds, (ts, maxName, ds, subs, by)) =
+ ListUtil.foldlMapConcat
+ (fn (ed, (ts, maxName, ds, subs, by)) =>
+ case #1 ed of
+ EDVal (p, t, e) =>
+ let
+ val e = doSubst (e, subs, by)
+
+ fun doVars ((p, _), ts) =
+ case p of
+ PVar xt => xt :: ts
+ | PPrim _ => ts
+ | PCon (_, _, _, NONE) => ts
+ | PCon (_, _, _, SOME p) => doVars (p, ts)
+ | PRecord xpcs =>
+ foldl (fn ((_, p, _), ts) => doVars (p, ts))
+ ts xpcs
+
+ fun bindOne subs = ((0, (ERel 0, #2 ed))
+ :: map (fn (n, e) => (n + 1, E.liftExpInExp 0 e)) subs)
+
+ fun bindMany (n, subs) =
+ case n of
+ 0 => subs
+ | _ => bindMany (n - 1, bindOne subs)
+ in
+ ([(EDVal (p, t, e), #2 ed)],
+ (doVars (p, ts),
+ maxName, ds,
+ bindMany (E.patBindsN p, subs),
+ by))
+ end
+ | EDValRec vis =>
+ let
+ val loc = #2 ed
+
+ val nr = length vis
+ val subsLocal = List.filter (fn (_, (ERel _, _)) => false
+ | _ => true) subs
+ val subsLocal = map (fn (n, e) => (n + nr, liftExpInExp nr 0 e))
+ subsLocal
+
+ val vis = map (fn (x, t, e) =>
+ (x, t, doSubst' (e, subsLocal))) vis
+
+ val (kfv, cfv, efv) =
+ foldl (fn ((_, t, e), (kfv, cfv, efv)) =>
+ let
+ val (kfv', cfv', efv') = fvsExp nr e
+ (*val () = Print.prefaces "fvsExp"
+ [("e", ElabPrint.p_exp E.empty e),
+ ("cfv", Print.PD.string
+ (Int.toString (IS.numItems cfv'))),
+ ("efv", Print.PD.string
+ (Int.toString (IS.numItems efv')))]*)
+ val (kfv'', cfv'') = fvsCon t
+ in
+ (IS.union (kfv, IS.union (kfv', kfv'')),
+ IS.union (cfv, IS.union (cfv', cfv'')),
+ IS.union (efv, efv'))
+ end)
+ (IS.empty, IS.empty, IS.empty) vis
+
+ (*val () = Print.prefaces "Letto" [("e", ElabPrint.p_exp E.empty (old, ErrorMsg.dummySpan))]*)
+ (*val () = print ("A: " ^ Int.toString (length ts) ^ ", " ^ Int.toString (length ks) ^ "\n")*)
+ (*val () = app (fn (x, t) =>
+ Print.prefaces "Var" [("x", Print.PD.string x),
+ ("t", ElabPrint.p_con E.empty t)]) ts
+ val () = IS.app (fn n => print ("Free: " ^ Int.toString n ^ "\n")) efv*)
+
+ val kfv = IS.foldl (fn (x, kfv) =>
+ let
+ (*val () = print (Int.toString x ^ "\n")*)
+ val (_, k) = List.nth (ks, x)
+ in
+ IS.union (kfv, fvsKind k)
+ end)
+ kfv cfv
+
+ val kfv = IS.foldl (fn (x, kfv) =>
+ let
+ (*val () = print (Int.toString x ^ "\n")*)
+ val (_, t) = List.nth (ts, x)
+ in
+ IS.union (kfv, #1 (fvsCon t))
+ end)
+ kfv efv
+
+ val cfv = IS.foldl (fn (x, cfv) =>
+ let
+ (*val () = print (Int.toString x ^ "\n")*)
+ val (_, t) = List.nth (ts, x)
+ in
+ IS.union (cfv, #2 (fvsCon t))
+ end)
+ cfv efv
+ (*val () = print "B\n"*)
+
+ val (vis, maxName) =
+ ListUtil.foldlMap (fn ((x, t, e), maxName) =>
+ ((x, maxName, t, e),
+ maxName + 1))
+ maxName vis
+
+ val subs = map (fn (n, e) => (n + nr,
+ case e of
+ (ERel _, _) => e
+ | _ => liftExpInExp nr 0 e))
+ subs
+
+ val subs' = ListUtil.mapi (fn (i, (_, n, _, _)) =>
+ let
+ val e = (ENamed n, loc)
+
+ val e = IS.foldr (fn (x, e) =>
+ (EKApp (e, (KRel x, loc)), loc))
+ e kfv
+
+ val e = IS.foldr (fn (x, e) =>
+ (ECApp (e, (CRel x, loc)), loc))
+ e cfv
+
+ val e = IS.foldr (fn (x, e) =>
+ (EApp (e, (ERel (nr + x), loc)),
+ loc))
+ e efv
+ in
+ (nr - i - 1, e)
+ end)
+ vis
+
+ val kfv = IS.listItems kfv
+ val cfv = IS.listItems cfv
+ val efv = IS.listItems efv
+
+ val subs = subs' @ subs
+
+ val vis = map (fn (x, n, t, e) =>
+ let
+ (*val () = Print.prefaces "preSubst"
+ [("e", ElabPrint.p_exp E.empty e)]*)
+ val e = doSubst' (e, subs')
+
+ (*val () = Print.prefaces "squishCon"
+ [("t", ElabPrint.p_con E.empty t)]*)
+ val t = squishCon (kfv, cfv) t
+ (*val () = Print.prefaces "squishExp"
+ [("e", ElabPrint.p_exp E.empty e)]*)
+ val e = squishExp (nr, kfv, cfv, efv) e
+
+ (*val () = print ("Avail: " ^ Int.toString (length ts) ^ "\n")*)
+ val (e, t) = foldl (fn (ex, (e, t)) =>
+ let
+ (*val () = print (Int.toString ex ^ "\n")*)
+ val (name, t') = List.nth (ts, ex)
+ val t' = squishCon (kfv, cfv) t'
+ in
+ ((EAbs (name,
+ t',
+ t,
+ e), loc),
+ (TFun (t',
+ t), loc))
+ end)
+ (e, t) efv
+ (*val () = print "Done\n"*)
+
+ val (e, t) = foldl (fn (cx, (e, t)) =>
+ let
+ val (name, k) = List.nth (ks, cx)
+ in
+ ((ECAbs (Explicit,
+ name,
+ k,
+ e), loc),
+ (TCFun (Explicit,
+ name,
+ k,
+ t), loc))
+ end)
+ (e, t) cfv
+
+ val (e, t) = foldl (fn (kx, (e, t)) =>
+ let
+ val name = List.nth (ns, kx)
+ in
+ ((EKAbs (name,
+ e), loc),
+ (TKFun (name,
+ t), loc))
+ end)
+ (e, t) kfv
+ in
+ (*Print.prefaces "Have a vi"
+ [("x", Print.PD.string x),
+ ("e", ElabPrint.p_exp ElabEnv.empty e)];*)
+ ("$" ^ x, n, t, e)
+ end)
+ vis
+
+ val ts = List.revAppend (map (fn (x, _, t, _) => (x, t)) vis, ts)
+ in
+ ([], (ts, maxName, vis @ ds, subs, by + nr))
+ end)
+ (ts, #maxName st, #decls st, [], 0) eds
+
+ val e' = doSubst (e, subs, by)
+ in
+ (*Print.prefaces "Before" [("e", ElabPrint.p_exp ElabEnv.empty e),
+ ("se", ElabPrint.p_exp ElabEnv.empty (doSubst' (e, subs))),
+ ("e'", ElabPrint.p_exp ElabEnv.empty e')];*)
+ (*Print.prefaces "Let" [("Before", ElabPrint.p_exp ElabEnv.empty (old, ErrorMsg.dummySpan)),
+ ("After", ElabPrint.p_exp ElabEnv.empty (ELet (eds, e', t), ErrorMsg.dummySpan))];*)
+ (ELet (eds, e', t),
+ {maxName = maxName,
+ decls = ds})
+ (*(ELet (eds, doSubst (liftExpInExp (~(length subs - numRemaining)) (length subs) e) subs),*)
+ end
+
+ | _ => (e, st)
+
+fun default (ctx, d, st) = (d, st)
+
+fun bind ((ns, ks, ts), b) =
+ case b of
+ U.Decl.RelK x => (x :: ns, ks, ts)
+ | U.Decl.RelC p => (ns, p :: ks, map (fn (name, t) => (name, E.liftConInCon 0 t)) ts)
+ | U.Decl.RelE p => (ns, ks, p :: ts)
+ | _ => (ns, ks, ts)
+
+val unnestDecl = U.Decl.foldMapB {kind = kind,
+ con = default,
+ exp = exp,
+ sgn_item = default,
+ sgn = default,
+ str = default,
+ decl = default,
+ bind = bind}
+ ([], [], [])
+
+fun unnest file =
+ let
+ fun doDecl (all as (d, loc), st : state) =
+ let
+ fun default () = ([all], st)
+ fun explore () =
+ let
+ val (d, st) = unnestDecl st all
+
+ val ds =
+ case #1 d of
+ DValRec vis => [(DValRec (vis @ #decls st), #2 d)]
+ | _ => [(DValRec (#decls st), #2 d), d]
+ in
+ (ds,
+ {maxName = #maxName st,
+ decls = []})
+ end
+ in
+ case d of
+ DCon _ => default ()
+ | DDatatype _ => default ()
+ | DDatatypeImp _ => default ()
+ | DVal _ => explore ()
+ | DValRec _ => explore ()
+ | DSgn _ => default ()
+ | DStr (x, n, sgn, str) =>
+ let
+ val (str, st) = doStr (str, st)
+ in
+ ([(DStr (x, n, sgn, str), loc)], st)
+ end
+ | DFfiStr ("Basis", n, _) => (basis := n; default ())
+ | DFfiStr _ => default ()
+ | DConstraint _ => default ()
+ | DExport _ => default ()
+ | DTable _ => default ()
+ | DSequence _ => default ()
+ | DView _ => default ()
+ | DDatabase _ => default ()
+ | DCookie _ => default ()
+ | DStyle _ => default ()
+ | DTask _ => explore ()
+ | DPolicy _ => explore ()
+ | DOnError _ => default ()
+ | DFfi _ => default ()
+ end
+
+ and doStr (all as (str, loc), st) =
+ let
+ fun default () = (all, st)
+ in
+ case str of
+ StrConst ds =>
+ let
+ val (ds, st) = ListUtil.foldlMapConcat doDecl st ds
+ in
+ ((StrConst ds, loc), st)
+ end
+ | StrVar _ => default ()
+ | StrProj _ => default ()
+ | StrFun (x, n, dom, ran, str) =>
+ let
+ val (str, st) = doStr (str, st)
+ in
+ ((StrFun (x, n, dom, ran, str), loc), st)
+ end
+ | StrApp _ => default ()
+ | StrError => raise Fail "Unnest: StrError"
+ end
+
+ val (ds, _) = ListUtil.foldlMapConcat doDecl
+ {maxName = U.File.maxName file + 1,
+ decls = []} file
+ in
+ ds
+ end
+
+end
diff --git a/src/unpoly.sig b/src/unpoly.sig
new file mode 100644
index 0000000..aba3825
--- /dev/null
+++ b/src/unpoly.sig
@@ -0,0 +1,34 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+(* Simplify a Core program by repeating polymorphic function definitions *)
+
+signature UNPOLY = sig
+
+ val unpoly : Core.file -> Core.file
+
+end
diff --git a/src/unpoly.sml b/src/unpoly.sml
new file mode 100644
index 0000000..549de5d
--- /dev/null
+++ b/src/unpoly.sml
@@ -0,0 +1,336 @@
+(* Copyright (c) 2008-2010, 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.
+ *)
+
+(* Simplify a Core program by repeating polymorphic function definitions *)
+
+structure Unpoly :> UNPOLY = struct
+
+open Core
+
+structure E = CoreEnv
+structure U = CoreUtil
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+
+(** The actual specialization *)
+
+val liftConInCon = E.liftConInCon
+val subConInCon = E.subConInCon
+
+val liftConInExp = E.liftConInExp
+val subConInExp = E.subConInExp
+
+val isOpen = U.Con.existsB {kind = fn _ => false,
+ con = fn (n, c) =>
+ case c of
+ CRel n' => n' >= n
+ | _ => false,
+ bind = fn (n, b) =>
+ case b of
+ U.Con.RelC _ => n + 1
+ | _ => n} 0
+
+fun unpolyNamed (xn, rep) =
+ U.Exp.map {kind = fn k => k,
+ con = fn c => c,
+ exp = fn e =>
+ case e of
+ ECApp (e', _) =>
+ let
+ fun isTheOne (e, _) =
+ case e of
+ ENamed xn' => xn' = xn
+ | ECApp (e, _) => isTheOne e
+ | _ => false
+ in
+ if isTheOne e' then
+ rep
+ else
+ e
+ end
+ | _ => e}
+
+structure M = BinaryMapFn(struct
+ type ord_key = con list
+ val compare = Order.joinL U.Con.compare
+ end)
+
+type func = {
+ kinds : kind list,
+ defs : (string * int * con * exp * string) list,
+ replacements : int M.map
+}
+
+type state = {
+ funcs : func IM.map,
+ decls : decl list,
+ nextName : int
+}
+
+fun kind (k, st) = (k, st)
+
+fun con (c, st) = (c, st)
+
+fun exp (e, st : state) =
+ case e of
+ ECApp _ =>
+ let
+ fun unravel (e, cargs) =
+ case e of
+ ECApp ((e, _), c) => unravel (e, c :: cargs)
+ | ENamed n => SOME (n, rev cargs)
+ | _ => NONE
+ in
+ case unravel (e, []) of
+ NONE => (e, st)
+ | SOME (n, cargs) =>
+ if List.exists isOpen cargs then
+ (e, st)
+ else
+ case IM.find (#funcs st, n) of
+ NONE => (e, st)
+ | SOME {kinds = ks, defs = vis, replacements} =>
+ let
+ val cargs = map ReduceLocal.reduceCon cargs
+ in
+ case M.find (replacements, cargs) of
+ SOME n => (ENamed n, st)
+ | NONE =>
+ let
+ val old_vis = vis
+ val (vis, (thisName, nextName)) =
+ ListUtil.foldlMap
+ (fn ((x, n', t, e, s), (thisName, nextName)) =>
+ ((x, nextName, n', t, e, s),
+ (if n' = n then nextName else thisName,
+ nextName + 1)))
+ (0, #nextName st) vis
+
+ fun specialize (x, n, n_old, t, e, s) =
+ let
+ fun trim (t, e, cargs) =
+ case (t, e, cargs) of
+ ((TCFun (_, _, t), _),
+ (ECAbs (_, _, e), _),
+ carg :: cargs) =>
+ let
+ val t = subConInCon (length cargs, carg) t
+ val e = subConInExp (length cargs, carg) e
+ in
+ trim (t, e, cargs)
+ end
+ | (_, _, []) => SOME (t, e)
+ | _ => NONE
+ in
+ (*Print.prefaces "specialize"
+ [("n", Print.PD.string (Int.toString n)),
+ ("nold", Print.PD.string (Int.toString n_old)),
+ ("t", CorePrint.p_con CoreEnv.empty t),
+ ("e", CorePrint.p_exp CoreEnv.empty e),
+ ("|cargs|", Print.PD.string (Int.toString (length cargs)))];*)
+ Option.map (fn (t, e) => (x, n, n_old, t, e, s))
+ (trim (t, e, cargs))
+ end
+
+ val vis = List.map specialize vis
+ in
+ if List.exists (not o Option.isSome) vis orelse length cargs > length ks then
+ (e, st)
+ else
+ let
+ val vis = List.mapPartial (fn x => x) vis
+
+ val vis = map (fn (x, n, n_old, t, e, s) =>
+ (x ^ "_unpoly", n, n_old, t, e, s)) vis
+ val vis' = map (fn (x, n, _, t, e, s) =>
+ (x, n, t, e, s)) vis
+
+ val funcs = foldl (fn ((_, n, n_old, _, _, _), funcs) =>
+ let
+ val replacements = case IM.find (funcs, n_old) of
+ NONE => M.empty
+ | SOME {replacements = r,
+ ...} => r
+ in
+ IM.insert (funcs, n_old,
+ {kinds = ks,
+ defs = old_vis,
+ replacements = M.insert (replacements,
+ cargs,
+ n)})
+ end) (#funcs st) vis
+
+ val ks' = List.drop (ks, length cargs)
+
+ val st = {funcs = foldl (fn (vi, funcs) =>
+ IM.insert (funcs, #2 vi,
+ {kinds = ks',
+ defs = vis',
+ replacements = M.empty}))
+ funcs vis',
+ decls = #decls st,
+ nextName = nextName}
+
+ val (vis', st) = ListUtil.foldlMap (fn ((x, n, t, e, s), st) =>
+ let
+ val (e, st) = polyExp (e, st)
+ in
+ ((x, n, t, e, s), st)
+ end)
+ st vis'
+ in
+ (ENamed thisName,
+ {funcs = #funcs st,
+ decls = (DValRec vis', ErrorMsg.dummySpan) :: #decls st,
+ nextName = #nextName st})
+ end
+ end
+ end
+ end
+ | _ => (e, st)
+
+and polyExp (x, st) = U.Exp.foldMap {kind = kind, con = con, exp = exp} st x
+
+fun decl (d, st : state) =
+ let
+ fun unravel (e, cargs) =
+ case e of
+ (ECAbs (_, k, e), _) =>
+ unravel (e, k :: cargs)
+ | _ => rev cargs
+ in
+ case d of
+ DVal (vi as (x, n, t, e, s)) =>
+ let
+ val cargs = unravel (e, [])
+
+ val ns = IS.singleton n
+ in
+ (d, {funcs = IM.insert (#funcs st, n, {kinds = cargs,
+ defs = [vi],
+ replacements = M.empty}),
+ decls = #decls st,
+ nextName = #nextName st})
+ end
+ | DValRec (vis as ((x, n, t, e, s) :: rest)) =>
+ let
+ val cargs = unravel (e, [])
+
+ fun unravel (e, cargs) =
+ case (e, cargs) of
+ ((ECAbs (_, k, e), _), k' :: cargs) =>
+ U.Kind.compare (k, k') = EQUAL
+ andalso unravel (e, cargs)
+ | (_, []) => true
+ | _ => false
+
+ fun deAbs (e, cargs) =
+ case (e, cargs) of
+ ((ECAbs (_, _, e), _), _ :: cargs) => deAbs (e, cargs)
+ | (_, []) => e
+ | _ => raise Fail "Unpoly: deAbs"
+
+ in
+ if List.exists (fn vi => not (unravel (#4 vi, cargs))) rest then
+ (d, st)
+ else
+ let
+ val ns = IS.addList (IS.empty, map #2 vis)
+ val nargs = length cargs
+
+ (** Verifying lack of polymorphic recursion *)
+
+ fun kind _ = false
+ fun con _ = false
+
+ fun exp (cn, e) =
+ case e of
+ orig as ECApp (e, c) =>
+ let
+ fun isIrregular (e, pos) =
+ case #1 e of
+ ENamed n =>
+ IS.member (ns, n)
+ andalso
+ (case #1 c of
+ CRel i => i <> nargs - pos + cn
+ | _ => true)
+ | ECApp (e, _) => isIrregular (e, pos + 1)
+ | _ => false
+ in
+ isIrregular (e, 1)
+ end
+ | _ => false
+
+ fun bind (cn, b) =
+ case b of
+ U.Exp.RelC _ => cn+1
+ | _ => cn
+
+ val irregular = U.Exp.existsB {kind = kind, con = con, exp = exp, bind = bind} 0
+ in
+ if List.exists (fn x => irregular (deAbs (#4 x, cargs))) vis then
+ (d, st)
+ else
+ (d, {funcs = foldl (fn (vi, funcs) =>
+ IM.insert (funcs, #2 vi, {kinds = cargs,
+ defs = vis,
+ replacements = M.empty}))
+ (#funcs st) vis,
+ decls = #decls st,
+ nextName = #nextName st})
+ end
+ end
+
+ | _ => (d, st)
+ end
+
+val polyDecl = U.Decl.foldMap {kind = kind, con = con, exp = exp, decl = decl}
+
+fun unpoly file =
+ let
+ fun doDecl (d : decl, st : state) =
+ let
+ val (d, st) = polyDecl st d
+ in
+ (rev (d :: #decls st),
+ {funcs = #funcs st,
+ decls = [],
+ nextName = #nextName st})
+ end
+
+ val (ds, _) = ListUtil.foldlMapConcat doDecl
+ {funcs = IM.empty,
+ decls = [],
+ nextName = U.File.maxName file + 1} file
+ in
+ ds
+ end
+
+end
diff --git a/src/untangle.sig b/src/untangle.sig
new file mode 100644
index 0000000..522cc6d
--- /dev/null
+++ b/src/untangle.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 2008, 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.
+ *)
+
+signature UNTANGLE = sig
+
+ val untangle : Mono.file -> Mono.file
+
+end
diff --git a/src/untangle.sml b/src/untangle.sml
new file mode 100644
index 0000000..bcb90ed
--- /dev/null
+++ b/src/untangle.sml
@@ -0,0 +1,214 @@
+(* Copyright (c) 2008, 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 Untangle :> UNTANGLE = struct
+
+open Mono
+
+structure U = MonoUtil
+structure E = MonoEnv
+
+structure IS = IntBinarySet
+structure IM = IntBinaryMap
+
+fun typ (k, s) = s
+
+fun exp (e, s) =
+ case e of
+ ENamed n => IS.add (s, n)
+
+ | _ => s
+
+fun untangle (file : file) =
+ let
+ fun decl (dAll as (d, loc)) =
+ case d of
+ DValRec vis =>
+ let
+ val thisGroup = foldl (fn ((_, n, _, _, _), thisGroup) =>
+ IS.add (thisGroup, n)) IS.empty vis
+
+ val used = foldl (fn ((_, n, _, e, _), used) =>
+ let
+ val usedHere = U.Exp.fold {typ = typ,
+ exp = exp} IS.empty e
+ in
+ IM.insert (used, n, IS.intersection (usedHere, thisGroup))
+ end)
+ IM.empty vis
+
+ fun p_graph reachable =
+ IM.appi (fn (n, reachableHere) =>
+ (print (Int.toString n);
+ print ":";
+ IS.app (fn n' => (print " ";
+ print (Int.toString n'))) reachableHere;
+ print "\n")) reachable
+
+ (*val () = print "used:\n"
+ val () = p_graph used*)
+
+ fun expand reachable =
+ let
+ val changed = ref false
+
+ val reachable =
+ IM.mapi (fn (n, reachableHere) =>
+ IS.foldl (fn (n', reachableHere) =>
+ let
+ val more = valOf (IM.find (reachable, n'))
+ in
+ if IS.isEmpty (IS.difference (more, reachableHere)) then
+ reachableHere
+ else
+ (changed := true;
+ IS.union (more, reachableHere))
+ end)
+ reachableHere reachableHere) reachable
+ in
+ (reachable, !changed)
+ end
+
+ fun iterate reachable =
+ let
+ val (reachable, changed) = expand reachable
+ in
+ if changed then
+ iterate reachable
+ else
+ reachable
+ end
+
+ val reachable = iterate used
+
+ (*val () = print "reachable:\n"
+ val () = p_graph reachable*)
+
+ fun sccs (nodes, acc) =
+ case IS.find (fn _ => true) nodes of
+ NONE => acc
+ | SOME rep =>
+ let
+ val reachableHere = valOf (IM.find (reachable, rep))
+
+ val (nodes, scc) = IS.foldl (fn (node, (nodes, scc)) =>
+ if node = rep then
+ (nodes, scc)
+ else
+ let
+ val reachableThere =
+ valOf (IM.find (reachable, node))
+ in
+ if IS.member (reachableThere, rep) then
+ (IS.delete (nodes, node),
+ IS.add (scc, node))
+ else
+ (nodes, scc)
+ end)
+ (IS.delete (nodes, rep), IS.singleton rep) reachableHere
+ in
+ sccs (nodes, scc :: acc)
+ end
+
+ val sccs = sccs (thisGroup, [])
+ (*val () = app (fn nodes => (print "SCC:";
+ IS.app (fn i => (print " ";
+ print (Int.toString i))) nodes;
+ print "\n")) sccs*)
+
+ fun depends nodes1 nodes2 =
+ let
+ val node1 = valOf (IS.find (fn _ => true) nodes1)
+ val node2 = valOf (IS.find (fn _ => true) nodes2)
+ val reachable1 = valOf (IM.find (reachable, node1))
+ in
+ IS.member (reachable1, node2)
+ end
+
+ fun findReady (sccs, passed) =
+ case sccs of
+ [] => raise Fail "Untangle: Unable to topologically sort 'val rec'"
+ | nodes :: sccs =>
+ if List.exists (depends nodes) passed
+ orelse List.exists (depends nodes) sccs then
+ findReady (sccs, nodes :: passed)
+ else
+ (nodes, List.revAppend (passed, sccs))
+
+ fun topo (sccs, acc) =
+ case sccs of
+ [] => rev acc
+ | _ =>
+ let
+ val (node, sccs) = findReady (sccs, [])
+ in
+ topo (sccs, node :: acc)
+ end
+
+ val sccs = topo (sccs, [])
+ (*val () = app (fn nodes => (print "SCC':";
+ IS.app (fn i => (print " ";
+ print (Int.toString i))) nodes;
+ print "\n")) sccs*)
+
+ fun isNonrec nodes =
+ case IS.find (fn _ => true) nodes of
+ NONE => NONE
+ | SOME node =>
+ let
+ val nodes = IS.delete (nodes, node)
+ val reachableHere = valOf (IM.find (reachable, node))
+ in
+ if IS.isEmpty nodes then
+ if IS.member (reachableHere, node) then
+ NONE
+ else
+ SOME node
+ else
+ NONE
+ end
+
+ val ds = map (fn nodes =>
+ case isNonrec nodes of
+ SOME node =>
+ let
+ val vi = valOf (List.find (fn (_, n, _, _, _) => n = node) vis)
+ in
+ (DVal vi, loc)
+ end
+ | NONE =>
+ (DValRec (List.filter (fn (_, n, _, _, _) => IS.member (nodes, n)) vis), loc))
+ sccs
+ in
+ ds
+ end
+ | _ => [dAll]
+ in
+ (ListUtil.mapConcat decl (#1 file), #2 file)
+ end
+
+end
diff --git a/src/urweb.grm b/src/urweb.grm
new file mode 100644
index 0000000..afebff0
--- /dev/null
+++ b/src/urweb.grm
@@ -0,0 +1,2394 @@
+(* Copyright (c) 2008-2016, 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 SOFTARE 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.
+ *)
+
+(* Grammar for Ur/Web programs *)
+
+open Source
+
+val s = ErrorMsg.spanOf
+val dummy = ErrorMsg.dummySpan
+
+fun capitalize "" = ""
+ | capitalize s = str (Char.toUpper (String.sub (s, 0))) ^ String.extract (s, 1, NONE)
+
+fun makeAttr s =
+ case s of
+ "type" => "Typ"
+ | "name" => "Nam"
+ | _ => capitalize (String.translate (fn ch => if ch = #"-" then "_" else str ch) s)
+
+fun entable t =
+ case #1 t of
+ TRecord c => c
+ | _ => t
+
+datatype select_item =
+ Field of con * con
+ | Exp of con option * exp
+ | Fields of con * con
+ | StarFields of con
+
+datatype select =
+ Star
+ | Items of select_item list
+
+datatype group_item =
+ GField of con * con
+ | GFields of con * con
+
+fun eqTnames ((c1, _), (c2, _)) =
+ case (c1, c2) of
+ (CVar (ms1, x1), CVar (ms2, x2)) => ms1 = ms2 andalso x1 = x2
+ | (CName x1, CName x2) => x1 = x2
+ | _ => false
+
+fun nameString (c, _) =
+ case c of
+ CName s => s
+ | CVar (_, x) => x
+ | _ => "?"
+
+datatype tableMode =
+ Unknown
+ | Everything
+ | Selective of con
+
+fun amend_select loc (si, (count, tabs, exps)) =
+ case si of
+ Field (tx, fx) =>
+ let
+ val c = (CRecord ([(fx, (CWild (KType, loc), loc))]), loc)
+
+ val (tabs, found) = ListUtil.foldlMap (fn ((tx', c'), found) =>
+ if eqTnames (tx, tx') then
+ case c' of
+ Everything =>
+ (ErrorMsg.errorAt loc
+ "Mixing specific-field and '*' selection of fields from same table";
+ ((tx', c'), found))
+ | Unknown =>
+ ((tx', Selective c), true)
+ | Selective c' =>
+ ((tx', Selective (CConcat (c, c'), loc)), true)
+ else
+ ((tx', c'), found))
+ false tabs
+ in
+ if found then
+ ()
+ else
+ ErrorMsg.errorAt loc ("Select of field " ^ nameString fx ^ " from unbound table " ^ nameString tx);
+
+ (count, tabs, exps)
+ end
+ | Fields (tx, fs) =>
+ let
+ val (tabs, found) = ListUtil.foldlMap (fn ((tx', c'), found) =>
+ if eqTnames (tx, tx') then
+ case c' of
+ Everything =>
+ (ErrorMsg.errorAt loc
+ "Mixing specific-field and '*' selection of fields from same table";
+ ((tx', c'), found))
+ | Selective c' =>
+ ((tx', Selective (CConcat (fs, c'), loc)), true)
+ | Unknown =>
+ ((tx', Selective fs), true)
+ else
+ ((tx', c'), found))
+ false tabs
+ in
+ if found then
+ ()
+ else
+ ErrorMsg.errorAt loc "Select of field from unbound table";
+
+ (count, tabs, exps)
+ end
+ | StarFields tx =>
+ if List.exists (fn (tx', c') => eqTnames (tx, tx') andalso case c' of
+ Unknown => false
+ | _ => true) tabs then
+ (ErrorMsg.errorAt loc "Selection with '*' from table already mentioned in same SELECT clause";
+ (count, tabs, exps))
+ else if List.all (fn (tx', c') => not (eqTnames (tx, tx'))) tabs then
+ (ErrorMsg.errorAt loc "Select of all fields from unbound table";
+ (count, tabs, exps))
+ else
+ (count, map (fn (tx', c') => (tx', if eqTnames (tx, tx') then Everything else c')) tabs, exps)
+ | Exp (SOME c, e) => (count, tabs, (c, e) :: exps)
+ | Exp (NONE, e) => (count+1, tabs, ((CName (Int.toString count), loc), e) :: exps)
+
+fun amend_group loc (gi, tabs) =
+ let
+ val (tx, c) = case gi of
+ GField (tx, fx) => (tx, (CRecord ([(fx, (CWild (KType, loc), loc))]), loc))
+ | GFields (tx, fxs) => (tx, fxs)
+
+ val (tabs, found) = ListUtil.foldlMap (fn ((tx', c'), found) =>
+ if eqTnames (tx, tx') then
+ ((tx', (CConcat (c, c'), loc)), true)
+ else
+ ((tx', c'), found))
+ false tabs
+ in
+ if found then
+ ()
+ else
+ ErrorMsg.errorAt loc "Select of field from unbound table";
+
+ tabs
+ end
+
+fun sql_inject (v, loc) =
+ (EApp ((EVar (["Basis"], "sql_inject", Infer), loc), (v, loc)), loc)
+
+fun sql_binary (oper, sqlexp1, sqlexp2, loc) =
+ let
+ val e = (EVar (["Basis"], "sql_binary", Infer), loc)
+ val e = (EApp (e, (EVar (["Basis"], "sql_" ^ oper, Infer), loc)), loc)
+ val e = (EApp (e, sqlexp1), loc)
+ in
+ (EApp (e, sqlexp2), loc)
+ end
+
+fun sql_unary (oper, sqlexp, loc) =
+ let
+ val e = (EVar (["Basis"], "sql_unary", Infer), loc)
+ val e = (EApp (e, (EVar (["Basis"], "sql_" ^ oper, Infer), loc)), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end
+
+fun sql_relop (oper, all, sqlexp1, sqlexp2, loc) =
+ let
+ val e = (EVar (["Basis"], "sql_relop", Infer), loc)
+ val e = (EApp (e, (EVar (["Basis"], "sql_" ^ oper, Infer), loc)), loc)
+ val e = (EApp (e, (EVar (["Basis"], if all then "True" else "False", Infer), loc)), loc)
+ val e = (EApp (e, sqlexp1), loc)
+ in
+ (EApp (e, sqlexp2), loc)
+ end
+
+fun sql_nfunc (oper, loc) =
+ let
+ val e = (EVar (["Basis"], "sql_nfunc", Infer), loc)
+ in
+ (EApp (e, (EVar (["Basis"], "sql_" ^ oper, Infer), loc)), loc)
+ end
+
+fun native_unop (oper, e1, loc) =
+ let
+ val e = (EVar (["Basis"], oper, Infer), loc)
+ in
+ (EApp (e, e1), loc)
+ end
+
+fun native_op (oper, e1, e2, loc) =
+ let
+ val e = (EVar (["Basis"], oper, Infer), loc)
+ val e = (EApp (e, e1), loc)
+ in
+ (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 =
+ case bt of
+ "table" => "tabl"
+ | "url" => "url_"
+ | "datetime-local" => "datetime_local"
+ | "cdatetime-local" => "cdatetime_local"
+ | _ => bt
+
+datatype prop_kind = Delete | Update
+
+datatype attr = Class of exp | DynClass of exp | Style of exp | DynStyle of exp | Normal of con * exp | Data of string * string * exp
+
+fun patType loc (p : pat) =
+ case #1 p of
+ PAnnot (_, t) => t
+ | _ => (CWild (KType, loc), loc)
+
+fun tnamesOf (e, _) =
+ case e of
+ EApp (e1, e2) => tnamesOf e1 @ tnamesOf e2
+ | ECApp (e, c as (CName _, _)) =>
+ let
+ fun isFt (e, _) =
+ case e of
+ EVar (["Basis"], "sql_from_table", _) => true
+ | EVar ([], "sql_from_table", _) => true
+ | ECApp (e, _) => isFt e
+ | EApp (e, _) => isFt e
+ | EDisjointApp e => isFt e
+ | _ => false
+ in
+ (if isFt e then [c] else []) @ tnamesOf e
+ end
+ | ECApp (e, _) => tnamesOf e
+ | EDisjointApp e => tnamesOf e
+ | _ => []
+
+fun classOut (s, pos) =
+ let
+ val s = case s of
+ "table" => "tabl"
+ | _ => s
+ in
+ (EVar ([], String.translate (fn #"-" => "_" | ch => str ch) s, Infer), pos)
+ end
+
+fun parseClass s pos =
+ case String.tokens Char.isSpace s of
+ [] => (EVar (["Basis"], "null", Infer), pos)
+ | class :: classes =>
+ foldl (fn (s, e) => (EApp ((EApp ((EVar (["Basis"], "classes", Infer), pos), e), pos), classOut (s, pos)), pos))
+ (classOut (class, pos)) classes
+
+fun parseValue s pos =
+ if String.isPrefix "url(" s andalso String.isSuffix ")" s then
+ let
+ val s = String.substring (s, 4, size s - 5)
+
+ val s = if size s >= 2
+ andalso ((String.isPrefix "\"" s andalso String.isSuffix "\"" s)
+ orelse (String.isPrefix "'" s andalso String.isSuffix "'" s)) then
+ String.substring (s, 1, size s - 2)
+ else
+ s
+ in
+ (EApp ((EVar (["Basis"], "css_url", Infer), pos),
+ (EApp ((EVar (["Basis"], "bless", Infer), pos),
+ (EPrim (Prim.String (Prim.Normal, s)), pos)), pos)), pos)
+ end
+ else
+ (EApp ((EVar (["Basis"], "atom", Infer), pos),
+ (EPrim (Prim.String (Prim.Normal, s)), pos)), pos)
+
+fun parseProperty s pos =
+ let
+ val (befor, after) = Substring.splitl (fn ch => ch <> #":") (Substring.full s)
+ in
+ if Substring.isEmpty after then
+ (ErrorMsg.errorAt pos ("Invalid CSS property syntax: " ^ s);
+ (EPrim (Prim.String (Prim.Normal, "")), pos))
+ else
+ foldl (fn (value, e) => (EApp ((EApp ((EVar (["Basis"], "value", Infer), pos), e), pos), parseValue value pos), pos))
+ (EApp ((EVar (["Basis"], "property", Infer), pos),
+ (EPrim (Prim.String (Prim.Normal, Substring.string (#2 (Substring.splitl Char.isSpace befor)))), pos)), pos)
+ (String.tokens Char.isSpace (Substring.string (Substring.slice (after, 1, NONE))))
+ end
+
+fun parseStyle s pos =
+ case String.tokens (fn ch => ch = #";") s of
+ [] => (EVar (["Basis"], "noStyle", Infer), pos)
+ | props =>
+ foldl (fn (s, e) => (EApp ((EApp ((EVar (["Basis"], "oneProperty", Infer), pos), e), pos), parseProperty s pos), pos))
+ (EVar (["Basis"], "noStyle", Infer), pos) props
+
+fun applyWindow loc e window =
+ let
+ val (pb, ob) = getOpt (window, ((EVar (["Basis"], "sql_no_partition", Infer), dummy),
+ (ECApp ((EVar (["Basis"], "sql_order_by_Nil", Infer), dummy),
+ (CWild (KRecord (KType, dummy), dummy), dummy)),
+ dummy)))
+ val e' = (EVar (["Basis"], "sql_window_function", Infer), loc)
+ val e' = (EApp (e', e), loc)
+ val e' = (EApp (e', pb), loc)
+ in
+ (EApp (e', ob), loc)
+ end
+
+fun patternOut (e : exp) =
+ case #1 e of
+ EWild => (PVar "_", #2 e)
+ | EVar ([], x, Infer) =>
+ if Char.isUpper (String.sub (x, 0)) then
+ (PCon ([], x, NONE), #2 e)
+ else
+ (PVar x, #2 e)
+ | EVar (xs, x, Infer) =>
+ if Char.isUpper (String.sub (x, 0)) then
+ (PCon (xs, x, NONE), #2 e)
+ else
+ (ErrorMsg.errorAt (#2 e) "Badly capitalized constructor name in pattern";
+ (PVar "_", #2 e))
+ | EPrim p => (PPrim p, #2 e)
+ | EApp ((EVar (xs, x, Infer), _), e') =>
+ (PCon (xs, x, SOME (patternOut e')), #2 e)
+ | ERecord (xes, flex) =>
+ (PRecord (map (fn (x, e') =>
+ let
+ val x =
+ case #1 x of
+ CName x => x
+ | _ => (ErrorMsg.errorAt (#2 e) "Field name not constant in pattern";
+ "")
+ in
+ (x, patternOut e')
+ end) xes, flex), #2 e)
+ | EAnnot (e', t) =>
+ (PAnnot (patternOut e', t), #2 e)
+ | _ => (ErrorMsg.errorAt (#2 e) "This is an expression but not a pattern.";
+ (PVar "_", #2 e))
+
+%%
+%header (functor UrwebLrValsFn(structure Token : TOKEN))
+
+%term
+ EOF
+ | STRING of string | INT of Int64.int | FLOAT of Real64.real | CHAR of char
+ | SYMBOL of string | CSYMBOL of string
+ | LPAREN | RPAREN | LBRACK | RBRACK | LBRACE | RBRACE
+ | EQ | COMMA | COLON | DCOLON | DCOLONWILD | TCOLON | TCOLONWILD | DOT | HASH | UNDER | UNDERUNDER | BAR
+ | PLUS | MINUS | DIVIDE | DOTDOTDOT | MOD | AT
+ | CON | LTYPE | VAL | REC | AND | FUN | MAP | UNIT | KUNIT | CLASS | FFI
+ | DATATYPE | OF
+ | TYPE | NAME
+ | ARROW | LARROW | DARROW | STAR | SEMI | KARROW | DKARROW | BANG
+ | FN | PLUSPLUS | MINUSMINUS | MINUSMINUSMINUS | DOLLAR | TWIDDLE | CARET
+ | LET | IN
+ | STRUCTURE | SIGNATURE | STRUCT | SIG | END | FUNCTOR | WHERE | SQL | SELECT1
+ | INCLUDE | OPEN | CONSTRAINT | CONSTRAINTS | EXPORT | TABLE | SEQUENCE | VIEW
+ | COOKIE | STYLE | TASK | POLICY
+ | CASE | IF | THEN | ELSE | ANDALSO | ORELSE
+
+ | XML_BEGIN of string | XML_END | XML_BEGIN_END of string
+ | NOTAGS of string
+ | BEGIN_TAG of string | END_TAG of string
+
+ | SELECT | DISTINCT | FROM | AS | CWHERE | GROUP | ORDER | BY | HAVING
+ | UNION | INTERSECT | EXCEPT
+ | LIMIT | OFFSET | ALL
+ | TRUE | FALSE | CAND | OR | NOT
+ | COUNT | AVG | SUM | MIN | MAX | RANK | PARTITION | OVER
+ | ASC | DESC | RANDOM
+ | INSERT | INTO | VALUES | UPDATE | SET | DELETE | NULL | IS | COALESCE | LIKE
+ | CURRENT_TIMESTAMP
+ | NE | LT | LE | GT | GE
+ | 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
+ | decls of decl list
+ | decl of decl list
+ | vali of string * con option * exp
+ | valis of (string * con option * exp) list
+ | copt of con option
+
+ | dargs of string list
+ | barOpt of unit
+ | dcons of (string * con option) list
+ | dtype of string * string list * (string * con option) list
+ | dtypes of (string * string list * (string * con option) list) list
+ | dcon of string * con option
+
+ | pkopt of exp
+ | pk of exp
+ | commaOpt of unit
+
+ | cst of exp
+ | csts of exp
+ | cstopt of exp
+
+ | ckl of (string * kind option) list
+
+ | pmode of prop_kind * exp
+ | pkind of prop_kind
+ | prule of exp
+ | pmodes of (prop_kind * exp) list
+
+ | sgn of sgn
+ | sgntm of sgn
+ | sgi of sgn_item
+ | sgis of sgn_item list
+
+ | str of str
+
+ | kind of kind
+ | ktuple of kind list
+ | kcolon of explicitness
+ | kopt of kind option
+
+ | path of string list * string
+ | cpath of string list * string
+ | spath of str
+ | mpath of string list
+
+ | cexp of con
+ | cexpO of con option
+ | capps of con
+ | cterm of con
+ | ctuple of con list
+ | ctuplev of con list
+ | ident of con
+ | idents of con list
+ | rcon of (con * con) list
+ | rconn of (con * con) list
+ | rcone of (con * con) list
+ | cargs of con * kind -> con * kind
+ | cargl of con * kind -> con * kind
+ | cargl2 of con * kind -> con * kind
+ | carg of con * kind -> con * kind
+ | cargp of con * kind -> con * kind
+
+ | eexp of exp
+ | eapps of exp
+ | eterm of exp
+ | etuple of exp list
+ | rexp of (con * exp) list * bool
+ | rpath of con
+ | xml of exp
+ | xmlOne of exp
+ | xmlOpt of exp
+ | tag of (string * exp) * exp option * exp option * exp option * exp
+ | tagHead of string * exp
+ | bind of pat * con option * exp
+ | edecl of edecl
+ | edecls of edecl list
+
+ | earg of exp * con -> exp * con
+ | eargp of exp * con -> exp * con
+ | earga of exp * con -> exp * con
+ | eargs of exp * con -> exp * con
+ | eargl of exp * con -> exp * con
+ | eargl2 of bool * (exp * con -> exp * con)
+
+ | branch of pat * exp
+ | branchs of (pat * exp) list
+ | pat of pat
+ | patS of pat
+ | pterm of pat
+ | rpat of (string * pat) list * bool
+ | ptuple of pat list
+
+ | attrs of exp option * exp option * exp option * exp option * exp option * (string * string * exp) list * (con * exp) list
+ | attr of attr
+ | attrv of exp
+
+ | query of exp
+ | query1 of exp
+ | dopt of exp
+ | tables of con list * exp
+ | fitem of con list * exp
+ | tname of con
+ | tnameW of con * con
+ | tnames of (con * con) * (con * con) list
+ | tnames' of (con * con) * (con * con) list
+ | table of con * exp
+ | table' of con * exp
+ | tident of con
+ | fident of con
+ | seli of select_item
+ | selis of select_item list
+ | select of select
+ | sqlexp of exp
+ | window of (exp * exp) option
+ | pbopt of exp
+ | wopt of exp
+ | groupi of group_item
+ | groupis of group_item list
+ | gopt of group_item list option
+ | hopt of exp
+ | obopt of exp
+ | obitem of exp * exp
+ | obexps of exp
+ | popt of unit
+ | diropt of exp
+ | lopt of exp
+ | ofopt of exp
+ | sqlint of exp
+ | sqlagg of string
+ | fname of exp
+
+ | texp of exp
+ | fields of con list
+ | sqlexps of exp list
+ | fsets of (con * exp) list
+ | enterDml of unit
+ | leaveDml of unit
+
+ | ffi_mode of ffi_mode
+ | ffi_modes of ffi_mode list
+
+
+%verbose (* print summary of errors *)
+%pos int (* positions *)
+%start file
+%pure
+%eop EOF
+%noshift EOF
+
+%name Urweb
+
+%right KARROW
+%nonassoc DKARROW
+%right SEMI
+%nonassoc LARROW
+%nonassoc IF THEN ELSE
+%nonassoc DARROW
+%left ANDALSO
+%left ORELSE
+%nonassoc COLON
+%nonassoc DCOLON TCOLON DCOLONWILD TCOLONWILD
+%left UNION INTERSECT EXCEPT ALL
+%right COMMA
+%right JOIN INNER CROSS OUTER LEFT RIGHT FULL
+%right OR
+%right CAND
+%nonassoc EQ NE LT LE GT GE IS LIKE
+%right ARROW
+
+%left REVAPP
+%right FWDAPP
+%left BACKTICK_PATH
+%right COMPOSE ANDTHEN
+
+%right CARET PLUSPLUS
+%left MINUSMINUS MINUSMINUSMINUS
+%left PLUS MINUS
+%left STAR DIVIDE MOD
+%left NOT
+%nonassoc TWIDDLE
+%nonassoc DOLLAR
+%left DOT
+%nonassoc LBRACE RBRACE
+
+%%
+
+file : decls (decls)
+ | SIG sgis ([(DSgn ("?", (SgnConst sgis, s (SIGleft, sgisright))),
+ s (SIGleft, sgisright))])
+
+decls : ([])
+ | decl decls (decl @ decls)
+
+decl : CON SYMBOL cargl2 kopt EQ cexp (let
+ val loc = s (CONleft, cexpright)
+
+ val k = Option.getOpt (kopt, (KWild, loc))
+ val (c, k) = cargl2 (cexp, k)
+ in
+ [(DCon (SYMBOL, SOME k, c), loc)]
+ end)
+ | LTYPE SYMBOL cargl2 EQ cexp (let
+ val loc = s (LTYPEleft, cexpright)
+
+ val k = (KWild, loc)
+ val (c, k) = cargl2 (cexp, k)
+ in
+ [(DCon (SYMBOL, SOME k, c), loc)]
+ end)
+ | DATATYPE dtypes ([(DDatatype dtypes, s (DATATYPEleft, dtypesright))])
+ | DATATYPE SYMBOL dargs EQ DATATYPE CSYMBOL DOT path
+ (case dargs of
+ [] => [(DDatatypeImp (SYMBOL, CSYMBOL :: #1 path, #2 path), s (DATATYPEleft, pathright))]
+ | _ => raise Fail "Arguments specified for imported datatype")
+ | VAL pat eargl2 copt EQ eexp (let
+ fun justVar (p : pat) =
+ case #1 p of
+ PVar x => SOME x
+ | PAnnot (p', _) => justVar p'
+ | _ => NONE
+
+ val loc = s (VALleft, eexpright)
+ in
+ case justVar pat of
+ SOME x =>
+ let
+ val t = Option.getOpt (copt, (CWild (KType, loc), loc))
+ val (e, t) = #2 eargl2 (eexp, t)
+ val pat =
+ case #1 t of
+ CWild _ => pat
+ | _ => (PAnnot (pat, t), loc)
+ in
+ [(DVal (pat, e), loc)]
+ end
+ | NONE =>
+ let
+ val pat =
+ case copt of
+ SOME t => (PAnnot (pat, t), loc)
+ | _ => pat
+ in
+ (if #1 eargl2 then
+ ErrorMsg.errorAt loc "Additional arguments not allowed after pattern"
+ else
+ ());
+ [(DVal (pat, eexp), loc)]
+ end
+ end)
+ | VAL REC valis ([(DValRec valis, s (VALleft, valisright))])
+ | FUN valis ([(DValRec valis, s (FUNleft, valisright))])
+
+ | SIGNATURE CSYMBOL EQ sgn ([(DSgn (CSYMBOL, sgn), s (SIGNATUREleft, sgnright))])
+ | STRUCTURE CSYMBOL EQ str ([(DStr (CSYMBOL, NONE, NONE, str, false), s (STRUCTUREleft, strright))])
+ | STRUCTURE CSYMBOL COLON sgn EQ str ([(DStr (CSYMBOL, SOME sgn, NONE, str, false), s (STRUCTUREleft, strright))])
+ | FUNCTOR CSYMBOL LPAREN CSYMBOL COLON sgn RPAREN EQ str
+ ([(DStr (CSYMBOL1, NONE, NONE,
+ (StrFun (CSYMBOL2, sgn1, NONE, str), s (FUNCTORleft, strright)), false),
+ s (FUNCTORleft, strright))])
+ | FUNCTOR CSYMBOL LPAREN CSYMBOL COLON sgn RPAREN COLON sgn EQ str
+ ([(DStr (CSYMBOL1, NONE, NONE,
+ (StrFun (CSYMBOL2, sgn1, SOME sgn2, str), s (FUNCTORleft, strright)), false),
+ s (FUNCTORleft, strright))])
+ | OPEN mpath (case mpath of
+ [] => raise Fail "Impossible mpath parse [1]"
+ | m :: ms => [(DOpen (m, ms), s (OPENleft, mpathright))])
+ | OPEN mpath LPAREN str RPAREN (let
+ val loc = s (OPENleft, RPARENright)
+
+ val m = case mpath of
+ [] => raise Fail "Impossible mpath parse [4]"
+ | m :: ms =>
+ foldl (fn (m, str) => (StrProj (str, m), loc))
+ (StrVar m, loc) ms
+ in
+ [(DStr ("anon", NONE, NONE, (StrApp (m, str), loc), false), loc),
+ (DOpen ("anon", []), loc)]
+ end)
+ | OPEN CONSTRAINTS mpath (case mpath of
+ [] => raise Fail "Impossible mpath parse [3]"
+ | m :: ms => [(DOpenConstraints (m, ms), s (OPENleft, mpathright))])
+ | CONSTRAINT cterm TWIDDLE cterm ([(DConstraint (cterm1, cterm2), s (CONSTRAINTleft, ctermright))])
+ | EXPORT spath ([(DExport spath, s (EXPORTleft, spathright))])
+ | TABLE SYMBOL COLON cterm pkopt commaOpt cstopt([(DTable (SYMBOL, entable cterm, pkopt, cstopt),
+ s (TABLEleft, cstoptright))])
+ | SEQUENCE SYMBOL ([(DSequence SYMBOL, s (SEQUENCEleft, SYMBOLright))])
+ | VIEW SYMBOL EQ query ([(DView (SYMBOL, query),
+ s (VIEWleft, queryright))])
+ | VIEW SYMBOL EQ LBRACE eexp RBRACE ([(DView (SYMBOL, eexp),
+ s (VIEWleft, RBRACEright))])
+ | COOKIE SYMBOL COLON cexp ([(DCookie (SYMBOL, cexp), s (COOKIEleft, cexpright))])
+ | STYLE SYMBOL ([(DStyle SYMBOL, s (STYLEleft, SYMBOLright))])
+ | TASK eapps EQ eexp ([(DTask (eapps, eexp), s (TASKleft, eexpright))])
+ | POLICY eexp ([(DPolicy eexp, s (POLICYleft, eexpright))])
+ | FFI SYMBOL ffi_modes COLON cexp([(DFfi (SYMBOL, ffi_modes, cexp), s (FFIleft, cexpright))])
+
+dtype : SYMBOL dargs EQ barOpt dcons (SYMBOL, dargs, dcons)
+
+dtypes : dtype ([dtype])
+ | dtype AND dtypes (dtype :: dtypes)
+
+kopt : (NONE)
+ | DCOLON kind (SOME kind)
+ | DCOLONWILD (SOME (KWild, s (DCOLONWILDleft, DCOLONWILDright)))
+
+dargs : ([])
+ | SYMBOL dargs (SYMBOL :: dargs)
+
+barOpt : ()
+ | BAR ()
+
+dcons : dcon ([dcon])
+ | dcon BAR dcons (dcon :: dcons)
+
+dcon : CSYMBOL (CSYMBOL, NONE)
+ | CSYMBOL OF cexp (CSYMBOL, SOME cexp)
+
+vali : SYMBOL eargl2 copt EQ eexp (let
+ val loc = s (SYMBOLleft, eexpright)
+ val t = Option.getOpt (copt, (CWild (KType, loc), loc))
+
+ val (e, t) = #2 eargl2 (eexp, t)
+ in
+ (SYMBOL, SOME t, e)
+ end)
+
+copt : (NONE)
+ | COLON cexp (SOME cexp)
+
+cstopt : (EVar (["Basis"], "no_constraint", Infer), dummy)
+ | csts (csts)
+
+csts : CCONSTRAINT tname cst (let
+ val loc = s (CCONSTRAINTleft, cstright)
+
+ val e = (EVar (["Basis"], "one_constraint", Infer), loc)
+ val e = (ECApp (e, tname), loc)
+ in
+ (EApp (e, cst), loc)
+ end)
+ | csts COMMA csts (let
+ val loc = s (csts1left, csts2right)
+
+ val e = (EVar (["Basis"], "join_constraints", Infer), loc)
+ val e = (EApp (e, csts1), loc)
+ in
+ (EApp (e, csts2), loc)
+ end)
+ | LBRACE LBRACE eexp RBRACE RBRACE (eexp)
+
+cst : UNIQUE tnames (let
+ val loc = s (UNIQUEleft, tnamesright)
+
+ val e = (EVar (["Basis"], "unique", Infer), loc)
+ val e = (ECApp (e, #1 (#1 tnames)), loc)
+ val e = (ECApp (e, (CRecord (#2 tnames), loc)), loc)
+ in
+ e
+ end)
+
+ | CHECK sqlexp (let
+ val loc = s (CHECKleft, sqlexpright)
+ in
+ (EApp ((EVar (["Basis"], "check", Infer), loc),
+ sqlexp), loc)
+ end)
+
+ | FOREIGN KEY tnames REFERENCES texp LPAREN tnames' RPAREN pmodes
+ (let
+ val loc = s (FOREIGNleft, pmodesright)
+
+ val mat = ListPair.foldrEq
+ (fn ((nm1, _), (nm2, _), mat) =>
+ let
+ val e = (EVar (["Basis"], "mat_cons", Infer), loc)
+ val e = (ECApp (e, nm1), loc)
+ val e = (ECApp (e, nm2), loc)
+ in
+ (EApp (e, mat), loc)
+ end)
+ (EVar (["Basis"], "mat_nil", Infer), loc)
+ (#1 tnames :: #2 tnames, #1 tnames' :: #2 tnames')
+ handle ListPair.UnequalLengths =>
+ (ErrorMsg.errorAt loc ("Unequal foreign key list lengths ("
+ ^ Int.toString (1 + length (#2 tnames))
+ ^ " vs. "
+ ^ Int.toString (1 + length (#2 tnames'))
+ ^ ")");
+ (EVar (["Basis"], "mat_nil", Infer), loc))
+
+ fun findMode mode =
+ let
+ fun findMode' pmodes =
+ case pmodes of
+ [] => (EVar (["Basis"], "no_action", Infer), loc)
+ | (mode', rule) :: pmodes' =>
+ if mode' = mode then
+ (if List.exists (fn (mode', _) => mode' = mode)
+ pmodes' then
+ ErrorMsg.errorAt loc "Duplicate propagation rule"
+ else
+ ();
+ rule)
+ else
+ findMode' pmodes'
+ in
+ findMode' pmodes
+ end
+
+ val e = (EVar (["Basis"], "foreign_key", Infer), loc)
+ val e = (EApp (e, mat), loc)
+ val e = (EApp (e, texp), loc)
+ in
+ (EApp (e, (ERecord ([((CName "OnDelete", loc),
+ findMode Delete),
+ ((CName "OnUpdate", loc),
+ findMode Update)], false), loc)), loc)
+ end)
+
+ | LBRACE eexp RBRACE (eexp)
+
+tnameW : tname (let
+ val loc = s (tnameleft, tnameright)
+ in
+ (tname, (CWild (KType, loc), loc))
+ end)
+
+tnames : tnameW (tnameW, [])
+ | LPAREN tnames' RPAREN (tnames')
+
+tnames': tnameW (tnameW, [])
+ | tnameW COMMA tnames' (#1 tnames', tnameW :: #2 tnames')
+
+pmode : ON pkind prule (pkind, prule)
+
+pkind : DELETE (Delete)
+ | UPDATE (Update)
+
+prule : NO ACTION (EVar (["Basis"], "no_action", Infer), s (NOleft, ACTIONright))
+ | RESTRICT (EVar (["Basis"], "restrict", Infer), s (RESTRICTleft, RESTRICTright))
+ | CASCADE (EVar (["Basis"], "cascade", Infer), s (CASCADEleft, CASCADEright))
+ | SET NULL (EVar (["Basis"], "set_null", Infer), s (SETleft, NULLright))
+
+pmodes : ([])
+ | pmode pmodes (pmode :: pmodes)
+
+commaOpt: ()
+ | COMMA ()
+
+pk : LBRACE LBRACE eexp RBRACE RBRACE (eexp)
+ | tnames (let
+ val loc = s (tnamesleft, tnamesright)
+
+ val e = (EVar (["Basis"], "primary_key", TypesOnly), loc)
+ val e = (ECApp (e, #1 (#1 tnames)), loc)
+ val e = (ECApp (e, (CRecord (#2 tnames), loc)), loc)
+ val e = (EDisjointApp e, loc)
+ val e = (EDisjointApp e, loc)
+
+ val witness = map (fn (c, _) =>
+ (c, (EWild, loc)))
+ (#1 tnames :: #2 tnames)
+ val witness = (ERecord (witness, false), loc)
+ in
+ (EApp (e, witness), loc)
+ end)
+
+pkopt : (EVar (["Basis"], "no_primary_key", Infer), dummy)
+ | PRIMARY KEY pk (pk)
+
+valis : vali ([vali])
+ | vali AND valis (vali :: valis)
+
+sgn : sgntm (sgntm)
+ | FUNCTOR LPAREN CSYMBOL COLON sgn RPAREN COLON sgn
+ (SgnFun (CSYMBOL, sgn1, sgn2), s (FUNCTORleft, sgn2right))
+
+sgntm : SIG sgis END (SgnConst sgis, s (SIGleft, ENDright))
+ | mpath (case mpath of
+ [] => raise Fail "Impossible mpath parse [2]"
+ | [x] => SgnVar x
+ | m :: ms => SgnProj (m,
+ List.take (ms, length ms - 1),
+ List.nth (ms, length ms - 1)),
+ s (mpathleft, mpathright))
+ | sgntm WHERE CON path EQ cexp (SgnWhere (sgntm, #1 path, #2 path, cexp), s (sgntmleft, cexpright))
+ | sgntm WHERE LTYPE path EQ cexp (SgnWhere (sgntm, #1 path, #2 path, cexp), s (sgntmleft, cexpright))
+ | LPAREN sgn RPAREN (sgn)
+
+cexpO : (NONE)
+ | EQ cexp (SOME cexp)
+
+sgi : LTYPE SYMBOL ((SgiConAbs (SYMBOL, (KType, s (LTYPEleft, SYMBOLright))),
+ s (LTYPEleft, SYMBOLright)))
+ | CON SYMBOL cargl2 kopt cexpO (let
+ val loc = s (CONleft, cexpOright)
+
+ val k = Option.getOpt (kopt, (KWild, loc))
+ in
+ case cexpO of
+ NONE => (SgiConAbs (SYMBOL, k), loc)
+ | SOME cexp =>
+ let
+ val (c, k) = cargl2 (cexp, k)
+ in
+ (SgiCon (SYMBOL, SOME k, c), loc)
+ end
+ end)
+ | LTYPE SYMBOL cargl2 cexpO (let
+ val loc = s (LTYPEleft, cexpOright)
+
+ val k = (KWild, loc)
+ in
+ case cexpO of
+ NONE => (SgiConAbs (SYMBOL, k), loc)
+ | SOME cexp =>
+ let
+ val (c, k) = cargl2 (cexp, k)
+ in
+ (SgiCon (SYMBOL, SOME k, c), loc)
+ end
+ end)
+ | DATATYPE dtypes ((SgiDatatype dtypes, s (DATATYPEleft, dtypesright)))
+ | DATATYPE SYMBOL dargs EQ DATATYPE CSYMBOL DOT path
+ (case dargs of
+ [] => (SgiDatatypeImp (SYMBOL, CSYMBOL :: #1 path, #2 path), s (DATATYPEleft, pathright))
+ | _ => raise Fail "Arguments specified for imported datatype")
+ | VAL SYMBOL COLON cexp ((SgiVal (SYMBOL, cexp), s (VALleft, cexpright)))
+
+ | STRUCTURE CSYMBOL COLON sgn ((SgiStr (CSYMBOL, sgn), s (STRUCTUREleft, sgnright)))
+ | SIGNATURE CSYMBOL EQ sgn ((SgiSgn (CSYMBOL, sgn), s (SIGNATUREleft, sgnright)))
+ | FUNCTOR CSYMBOL LPAREN CSYMBOL COLON sgn RPAREN COLON sgn
+ ((SgiStr (CSYMBOL1,
+ (SgnFun (CSYMBOL2, sgn1, sgn2), s (FUNCTORleft, sgn2right))),
+ s (FUNCTORleft, sgn2right)))
+ | INCLUDE sgn ((SgiInclude sgn, s (INCLUDEleft, sgnright)))
+ | CONSTRAINT cterm TWIDDLE cterm ((SgiConstraint (cterm1, cterm2), s (CONSTRAINTleft, ctermright)))
+ | TABLE SYMBOL COLON cterm pkopt commaOpt cstopt (let
+ val loc = s (TABLEleft, ctermright)
+ in
+ (SgiTable (SYMBOL, entable cterm, pkopt, cstopt), loc)
+ end)
+ | SEQUENCE SYMBOL (let
+ val loc = s (SEQUENCEleft, SYMBOLright)
+ val t = (CVar (["Basis"], "sql_sequence"), loc)
+ in
+ (SgiVal (SYMBOL, t), loc)
+ end)
+ | VIEW SYMBOL COLON cexp (let
+ val loc = s (VIEWleft, cexpright)
+ val t = (CVar (["Basis"], "sql_view"), loc)
+ val t = (CApp (t, entable cexp), loc)
+ in
+ (SgiVal (SYMBOL, t), loc)
+ end)
+ | CLASS SYMBOL (let
+ val loc = s (CLASSleft, SYMBOLright)
+ val k = (KArrow ((KType, loc), (KType, loc)), loc)
+ in
+ (SgiClassAbs (SYMBOL, k), loc)
+ end)
+ | CLASS SYMBOL DCOLON kind (let
+ val loc = s (CLASSleft, kindright)
+ in
+ (SgiClassAbs (SYMBOL, kind), loc)
+ end)
+ | CLASS SYMBOL EQ cexp (let
+ val loc = s (CLASSleft, cexpright)
+ in
+ (SgiClass (SYMBOL, (KWild, loc), cexp), loc)
+ end)
+ | CLASS SYMBOL DCOLON kind EQ cexp (let
+ val loc = s (CLASSleft, cexpright)
+ in
+ (SgiClass (SYMBOL, kind, cexp), loc)
+ end)
+ | CLASS SYMBOL SYMBOL EQ cexp (let
+ val loc = s (CLASSleft, cexpright)
+ val k = (KWild, loc)
+ val c = (CAbs (SYMBOL2, SOME k, cexp), loc)
+ in
+ (SgiClass (SYMBOL1, k, c), s (CLASSleft, cexpright))
+ end)
+ | CLASS SYMBOL LPAREN SYMBOL DCOLON kind RPAREN EQ cexp (let
+ val loc = s (CLASSleft, cexpright)
+ val c = (CAbs (SYMBOL2, SOME kind, cexp), loc)
+ in
+ (SgiClass (SYMBOL1, kind, c), s (CLASSleft, cexpright))
+ end)
+ | COOKIE SYMBOL COLON cexp (let
+ val loc = s (COOKIEleft, cexpright)
+ val t = (CApp ((CVar (["Basis"], "http_cookie"), loc),
+ entable cexp), loc)
+ in
+ (SgiVal (SYMBOL, t), loc)
+ end)
+ | STYLE SYMBOL (let
+ val loc = s (STYLEleft, SYMBOLright)
+ val t = (CVar (["Basis"], "css_class"), loc)
+ in
+ (SgiVal (SYMBOL, t), loc)
+ end)
+
+sgis : ([])
+ | sgi sgis (sgi :: sgis)
+
+str : STRUCT decls END (StrConst decls, s (STRUCTleft, ENDright))
+ | spath (spath)
+ | FUNCTOR LPAREN CSYMBOL COLON sgn RPAREN DARROW str
+ (StrFun (CSYMBOL, sgn, NONE, str), s (FUNCTORleft, strright))
+ | FUNCTOR LPAREN CSYMBOL COLON sgn RPAREN COLON sgn DARROW str
+ (StrFun (CSYMBOL, sgn1, SOME sgn2, str), s (FUNCTORleft, strright))
+ | spath LPAREN str RPAREN (StrApp (spath, str), s (spathleft, RPARENright))
+
+spath : CSYMBOL (StrVar CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+ | spath DOT CSYMBOL (StrProj (spath, CSYMBOL), s (spathleft, CSYMBOLright))
+
+kind : TYPE (KType, s (TYPEleft, TYPEright))
+ | NAME (KName, s (NAMEleft, NAMEright))
+ | LBRACE kind RBRACE (KRecord kind, s (LBRACEleft, RBRACEright))
+ | kind ARROW kind (KArrow (kind1, kind2), s (kind1left, kind2right))
+ | LPAREN kind RPAREN (#1 kind, s (LPARENleft, RPARENright))
+ | KUNIT (KUnit, s (KUNITleft, KUNITright))
+ | UNDERUNDER (KWild, s (UNDERUNDERleft, UNDERUNDERright))
+ | LPAREN ktuple RPAREN (KTuple ktuple, s (LPARENleft, RPARENright))
+ | CSYMBOL (KVar CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+ | CSYMBOL KARROW kind (KFun (CSYMBOL, kind), s (CSYMBOLleft, kindright))
+
+ktuple : kind STAR kind ([kind1, kind2])
+ | kind STAR ktuple (kind :: ktuple)
+
+capps : cterm (cterm)
+ | capps cterm (CApp (capps, cterm), s (cappsleft, ctermright))
+
+cexp : capps (capps)
+ | cexp ARROW cexp (TFun (cexp1, cexp2), s (cexp1left, cexp2right))
+ | SYMBOL kcolon kind ARROW cexp (TCFun (kcolon, SYMBOL, kind, cexp), s (SYMBOLleft, cexpright))
+ | CSYMBOL KARROW cexp (TKFun (CSYMBOL, cexp), s (CSYMBOLleft, cexpright))
+
+ | cexp PLUSPLUS cexp (CConcat (cexp1, cexp2), s (cexp1left, cexp1right))
+
+ | FN cargs DARROW cexp (#1 (cargs (cexp, (KWild, s (FNleft, cexpright)))))
+ | LBRACK cexp TWIDDLE cexp RBRACK DARROW cexp (TDisjoint (cexp1, cexp2, cexp3), s (LBRACKleft, cexp3right))
+ | CSYMBOL DKARROW cexp (CKAbs (CSYMBOL, cexp), s (CSYMBOLleft, cexpright))
+
+ | LPAREN cexp RPAREN DCOLON kind (CAnnot (cexp, kind), s (LPARENleft, kindright))
+
+ | UNDER DCOLON kind (CWild kind, s (UNDERleft, UNDERright))
+ | ctuple (let
+ val loc = s (ctupleleft, ctupleright)
+ in
+ (TRecord (CRecord (ListUtil.mapi (fn (i, c) =>
+ ((CName (Int.toString (i + 1)), loc),
+ c)) ctuple),
+ loc), loc)
+ end)
+
+kcolon : DCOLON (Explicit)
+ | TCOLON (Implicit)
+
+cargs : carg (carg)
+ | cargl (cargl)
+
+cargl : cargp cargp (cargp1 o cargp2)
+ | cargp cargl (cargp o cargl)
+
+cargl2 : (fn x => x)
+ | cargp cargl2 (cargp o cargl2)
+
+carg : SYMBOL DCOLON kind (fn (c, k) =>
+ let
+ val loc = s (SYMBOLleft, kindright)
+ in
+ ((CAbs (SYMBOL, SOME kind, c), loc),
+ (KArrow (kind, k), loc))
+ end)
+ | UNDER DCOLON kind (fn (c, k) =>
+ let
+ val loc = s (UNDERleft, kindright)
+ in
+ ((CAbs ("_", SOME kind, c), loc),
+ (KArrow (kind, k), loc))
+ end)
+ | SYMBOL DCOLONWILD (fn (c, k) =>
+ let
+ val loc = s (SYMBOLleft, DCOLONWILDright)
+ val kind = (KWild, loc)
+ in
+ ((CAbs (SYMBOL, NONE, c), loc),
+ (KArrow (kind, k), loc))
+ end)
+ | UNDER DCOLONWILD (fn (c, k) =>
+ let
+ val loc = s (UNDERleft, DCOLONWILDright)
+ val kind = (KWild, loc)
+ in
+ ((CAbs ("_", NONE, c), loc),
+ (KArrow (kind, k), loc))
+ end)
+ | cargp (cargp)
+
+cargp : SYMBOL (fn (c, k) =>
+ let
+ val loc = s (SYMBOLleft, SYMBOLright)
+ in
+ ((CAbs (SYMBOL, NONE, c), loc),
+ (KArrow ((KWild, loc), k), loc))
+ end)
+ | UNDER (fn (c, k) =>
+ let
+ val loc = s (UNDERleft, UNDERright)
+ in
+ ((CAbs ("_", NONE, c), loc),
+ (KArrow ((KWild, loc), k), loc))
+ end)
+ | LPAREN SYMBOL kopt ckl RPAREN (fn (c, k) =>
+ let
+ val loc = s (LPARENleft, RPARENright)
+ val ckl = (SYMBOL, kopt) :: ckl
+ val ckl = map (fn (x, ko) => (x, case ko of
+ NONE => (KWild, loc)
+ | SOME k => k)) ckl
+ in
+ case ckl of
+ [(x, k')] => ((CAbs (SYMBOL, SOME k', c), loc),
+ (KArrow (k', k), loc))
+ | _ =>
+ let
+ val k' = (KTuple (map #2 ckl), loc)
+
+ val c = foldr (fn ((x, k), c) =>
+ (CAbs (x, SOME k, c), loc)) c ckl
+ val v = (CVar ([], "$x"), loc)
+ val c = ListUtil.foldli (fn (i, _, c) =>
+ (CApp (c, (CProj (v, i + 1), loc)),
+ loc)) c ckl
+ in
+ ((CAbs ("$x", SOME k', c), loc),
+ (KArrow (k', k), loc))
+ end
+ end)
+
+ckl : ([])
+ | COMMA SYMBOL kopt ckl ((SYMBOL, kopt) :: ckl)
+
+path : SYMBOL ([], SYMBOL)
+ | CSYMBOL DOT path (let val (ms, x) = path in (CSYMBOL :: ms, x) end)
+
+cpath : CSYMBOL ([], CSYMBOL)
+ | CSYMBOL DOT cpath (let val (ms, x) = cpath in (CSYMBOL :: ms, x) end)
+
+mpath : CSYMBOL ([CSYMBOL])
+ | CSYMBOL DOT mpath (CSYMBOL :: mpath)
+
+cterm : LPAREN cexp RPAREN (#1 cexp, s (LPARENleft, RPARENright))
+ | LBRACK rcon RBRACK (CRecord rcon, s (LBRACKleft, RBRACKright))
+ | LBRACK rconn RBRACK (CRecord rconn, s (LBRACKleft, RBRACKright))
+ | LBRACE rcone RBRACE (TRecord (CRecord rcone, s (LBRACEleft, RBRACEright)),
+ s (LBRACEleft, RBRACEright))
+ | DOLLAR cterm (TRecord cterm, s (DOLLARleft, ctermright))
+ | HASH CSYMBOL (CName CSYMBOL, s (HASHleft, CSYMBOLright))
+ | HASH INT (CName (Int64.toString INT), s (HASHleft, INTright))
+
+ | path (CVar path, s (pathleft, pathright))
+ | path DOT INT (CProj ((CVar path, s (pathleft, pathright)), Int64.toInt INT),
+ s (pathleft, INTright))
+ | UNDER (CWild (KWild, s (UNDERleft, UNDERright)), s (UNDERleft, UNDERright))
+ | MAP (CMap, s (MAPleft, MAPright))
+ | UNIT (CUnit, s (UNITleft, UNITright))
+ | LPAREN ctuplev RPAREN (CTuple ctuplev, s (LPARENleft, RPARENright))
+
+ctuplev: cexp COMMA cexp ([cexp1, cexp2])
+ | cexp COMMA ctuplev (cexp :: ctuplev)
+
+ctuple : capps STAR capps ([capps1, capps2])
+ | capps STAR ctuple (capps :: ctuple)
+
+rcon : ([])
+ | rpath EQ cexp ([(rpath, cexp)])
+ | rpath EQ cexp COMMA rcon ((rpath, cexp) :: rcon)
+
+rconn : rpath ([(rpath, (CUnit, s (rpathleft, rpathright)))])
+ | rpath COMMA rconn ((rpath, (CUnit, s (rpathleft, rpathright))) :: rconn)
+
+rcone : ([])
+ | rpath COLON cexp ([(rpath, cexp)])
+ | rpath COLON cexp COMMA rcone ((rpath, cexp) :: rcone)
+
+ident : CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+ | INT (CName (Int64.toString INT), s (INTleft, INTright))
+ | SYMBOL (CVar ([], SYMBOL), s (SYMBOLleft, SYMBOLright))
+
+eapps : eterm (eterm)
+ | eapps eterm (EApp (eapps, eterm), s (eappsleft, etermright))
+ | eapps LBRACK cexp RBRACK (ECApp (eapps, cexp), s (eappsleft, RBRACKright))
+ | eapps BANG (EDisjointApp eapps, s (eappsleft, BANGright))
+
+eexp : eapps (case #1 eapps of
+ EApp ((EVar ([], "CLASS", _), _), (EPrim (Prim.String (_, s)), loc)) => parseClass s loc
+ | EApp ((EVar ([], "STYLE", _), _), (EPrim (Prim.String (_, s)), loc)) => parseStyle s loc
+ | _ => eapps)
+ | FN eargs DARROW eexp (let
+ val loc = s (FNleft, eexpright)
+ in
+ #1 (eargs (eexp, (CWild (KType, loc), loc)))
+ end)
+ | CSYMBOL DKARROW eexp (EKAbs (CSYMBOL, eexp), s (CSYMBOLleft, eexpright))
+ | eexp COLON cexp (EAnnot (eexp, cexp), s (eexpleft, cexpright))
+ | eexp MINUSMINUS cexp (ECut (eexp, cexp), s (eexpleft, cexpright))
+ | eexp MINUSMINUSMINUS cexp (ECutMulti (eexp, cexp), s (eexpleft, cexpright))
+ | CASE eexp OF barOpt branch branchs (ECase (eexp, branch :: branchs), s (CASEleft, branchsright))
+ | IF eexp THEN eexp ELSE eexp (let
+ val loc = s (IFleft, eexp3right)
+ in
+ (ECase (eexp1, [((PCon (["Basis"], "True", NONE), loc), eexp2),
+ ((PCon (["Basis"], "False", NONE), loc), eexp3)]), loc)
+ end)
+ | bind SEMI eexp (let
+ val loc = s (bindleft, eexpright)
+ val (p, to, e1) = bind
+ val e = (EVar (["Basis"], "bind", Infer), loc)
+ val e = (EApp (e, e1), loc)
+
+ val f = case #1 p of
+ PVar v => (EAbs (v, to, eexp), loc)
+ | _ => (EAbs ("$x", to,
+ (ECase ((EVar ([], "$x", Infer), loc),
+ [(p, eexp)]), loc)), loc)
+ in
+ (EApp (e, f), loc)
+ end)
+ | eexp EQ eexp (native_op ("eq", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | eexp NE eexp (native_op ("ne", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | MINUS eterm (native_unop ("neg", eterm, s (MINUSleft, etermright)))
+ | eexp PLUS eexp (native_op ("plus", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | eexp MINUS eexp (native_op ("minus", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | eapps STAR eexp (native_op ("times", eapps, eexp, s (eappsleft, eexpright)))
+ | eexp DIVIDE eexp (native_op ("divide", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | eexp MOD eexp (native_op ("mod", eexp1, eexp2, s (eexp1left, eexp2right)))
+
+ | eexp LT eexp (native_op ("lt", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | eexp LE eexp (native_op ("le", eexp1, eexp2, s (eexp1left, eexp2right)))
+ | 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
+ (ECase (eexp1, [((PCon (["Basis"], "True", NONE), loc),
+ eexp2),
+ ((PCon (["Basis"], "False", NONE), loc),
+ (EVar (["Basis"], "False", Infer), loc))]), loc)
+ end)
+ | eexp ORELSE eexp (let
+ val loc = s (eexp1left, eexp2right)
+ in
+ (ECase (eexp1, [((PCon (["Basis"], "True", NONE), loc),
+ (EVar (["Basis"], "True", Infer), loc)),
+ ((PCon (["Basis"], "False", NONE), loc),
+ eexp2)]), loc)
+ end)
+
+ | eexp PLUSPLUS eexp (EConcat (eexp1, eexp2), s (eexp1left, eexp2right))
+
+ | eexp CARET eexp (native_op ("strcat", eexp1, eexp2, s (eexp1left, eexp2right)))
+
+ | eapps DCOLON eexp (let
+ val loc = s (eappsleft, eexpright)
+ in
+ (EApp ((EVar (["Basis"], "Cons", Infer), loc),
+ (ERecord ([((CName "1", loc),
+ eapps),
+ ((CName "2", loc),
+ eexp)], false), loc)), loc)
+ end)
+
+bind : eapps LARROW eapps (patternOut eapps1, NONE, eapps2)
+ | eapps (let
+ val loc = s (eappsleft, eappsright)
+ in
+ ((PVar "_", loc), SOME (TRecord (CRecord [], loc), loc), eapps)
+ end)
+
+eargs : earg (earg)
+ | eargl (eargl)
+
+eargl : eargp eargp (eargp1 o eargp2)
+ | eargp eargl (eargp o eargl)
+
+eargl2 : (false, fn x => x)
+ | eargp eargl2 (true, eargp o #2 eargl2)
+
+earg : patS (fn (e, t) =>
+ let
+ val loc = s (patSleft, patSright)
+ val pt = patType loc patS
+
+ val e' = case #1 patS of
+ PVar x => (EAbs (x, NONE, e), loc)
+ | PAnnot ((PVar x, _), t) => (EAbs (x, SOME t, e), loc)
+ | _ => (EAbs ("$x", SOME pt,
+ (ECase ((EVar ([], "$x", DontInfer),
+ loc),
+ [(patS, e)]), loc)), loc)
+ in
+ (e', (TFun (pt, t), loc))
+ end)
+ | earga (earga)
+
+eargp : pterm (fn (e, t) =>
+ let
+ val loc = s (ptermleft, ptermright)
+ val pt = patType loc pterm
+
+ val e' = case #1 pterm of
+ PVar x => (EAbs (x, NONE, e), loc)
+ | PAnnot ((PVar x, _), t) => (EAbs (x, SOME t, e), loc)
+ | _ => (EAbs ("$x", SOME pt,
+ (ECase ((EVar ([], "$x", DontInfer),
+ loc),
+ [(pterm, e)]), loc)), loc)
+ in
+ (e', (TFun (pt, t), loc))
+ end)
+ | earga (earga)
+
+earga : LBRACK SYMBOL RBRACK (fn (e, t) =>
+ let
+ val loc = s (LBRACKleft, RBRACKright)
+ val kind = (KWild, loc)
+ in
+ ((ECAbs (Implicit, SYMBOL, kind, e), loc),
+ (TCFun (Implicit, SYMBOL, kind, t), loc))
+ end)
+ | LBRACK SYMBOL DCOLONWILD RBRACK (fn (e, t) =>
+ let
+ val loc = s (LBRACKleft, RBRACKright)
+ val kind = (KWild, loc)
+ in
+ ((ECAbs (Explicit, SYMBOL, kind, e), loc),
+ (TCFun (Explicit, SYMBOL, kind, t), loc))
+ end)
+ | LBRACK SYMBOL kcolon kind RBRACK(fn (e, t) =>
+ let
+ val loc = s (LBRACKleft, RBRACKright)
+ in
+ ((ECAbs (kcolon, SYMBOL, kind, e), loc),
+ (TCFun (kcolon, SYMBOL, kind, t), loc))
+ end)
+ | LBRACK SYMBOL TCOLONWILD RBRACK (fn (e, t) =>
+ let
+ val loc = s (LBRACKleft, RBRACKright)
+ val kind = (KWild, loc)
+ in
+ ((ECAbs (Implicit, SYMBOL, kind, e), loc),
+ (TCFun (Implicit, SYMBOL, kind, t), loc))
+ end)
+ | LBRACK cexp TWIDDLE cexp RBRACK(fn (e, t) =>
+ let
+ val loc = s (LBRACKleft, RBRACKright)
+ in
+ ((EDisjoint (cexp1, cexp2, e), loc),
+ (TDisjoint (cexp1, cexp2, t), loc))
+ end)
+ | LBRACK CSYMBOL RBRACK (fn (e, t) =>
+ let
+ val loc = s (CSYMBOLleft, CSYMBOLright)
+ in
+ ((EKAbs (CSYMBOL, e), loc),
+ (TKFun (CSYMBOL, t), loc))
+ end)
+
+eterm : LPAREN eexp RPAREN (#1 eexp, s (LPARENleft, RPARENright))
+ | LPAREN etuple RPAREN (let
+ val loc = s (LPARENleft, RPARENright)
+ in
+ (ERecord (ListUtil.mapi (fn (i, e) =>
+ ((CName (Int.toString (i + 1)), loc),
+ e)) etuple, false), loc)
+ end)
+
+ | path (EVar (#1 path, #2 path, Infer), s (pathleft, pathright))
+ | cpath (EVar (#1 cpath, #2 cpath, Infer), s (cpathleft, cpathright))
+ | AT path (EVar (#1 path, #2 path, TypesOnly), s (ATleft, pathright))
+ | AT AT path (EVar (#1 path, #2 path, DontInfer), s (AT1left, pathright))
+ | AT cpath (EVar (#1 cpath, #2 cpath, TypesOnly), s (ATleft, cpathright))
+ | AT AT cpath (EVar (#1 cpath, #2 cpath, DontInfer), s (AT1left, cpathright))
+ | LBRACE rexp RBRACE (ERecord rexp, s (LBRACEleft, RBRACEright))
+ | LBRACE RBRACE (ERecord ([], false), s (LBRACEleft, RBRACEright))
+ | UNIT (ERecord ([], false), s (UNITleft, UNITright))
+
+ | INT (EPrim (Prim.Int INT), s (INTleft, INTright))
+ | FLOAT (EPrim (Prim.Float FLOAT), s (FLOATleft, FLOATright))
+ | STRING (EPrim (Prim.String (Prim.Normal, STRING)), s (STRINGleft, STRINGright))
+ | CHAR (EPrim (Prim.Char CHAR), s (CHARleft, CHARright))
+
+ | path DOT idents (let
+ val loc = s (pathleft, identsright)
+ in
+ foldl (fn (ident, e) =>
+ (EField (e, ident), loc))
+ (EVar (#1 path, #2 path, Infer), s (pathleft, pathright)) idents
+ end)
+ | LPAREN eexp RPAREN DOT idents (let
+ val loc = s (LPARENleft, identsright)
+ in
+ foldl (fn (ident, e) =>
+ (EField (e, ident), loc))
+ eexp idents
+ end)
+ | AT path DOT idents (let
+ val loc = s (ATleft, identsright)
+ in
+ foldl (fn (ident, e) =>
+ (EField (e, ident), loc))
+ (EVar (#1 path, #2 path, TypesOnly), s (pathleft, pathright)) idents
+ end)
+ | AT AT path DOT idents (let
+ val loc = s (AT1left, identsright)
+ in
+ foldl (fn (ident, e) =>
+ (EField (e, ident), loc))
+ (EVar (#1 path, #2 path, DontInfer), s (pathleft, pathright)) idents
+ end)
+
+ | XML_BEGIN xml XML_END (let
+ val loc = s (XML_BEGINleft, XML_ENDright)
+ in
+ if XML_BEGIN = "xml" then
+ ()
+ else
+ ErrorMsg.errorAt loc "Initial XML tag pair aren't both tagged \"xml\".";
+ xml
+ end)
+ | XML_BEGIN XML_END (let
+ val loc = s (XML_BEGINleft, XML_ENDright)
+ in
+ if XML_BEGIN = "xml" then
+ ()
+ else
+ ErrorMsg.errorAt loc "Initial XML tag pair aren't both tagged \"xml\".";
+ (EApp ((EVar (["Basis"], "cdata", Infer), loc),
+ (EPrim (Prim.String (Prim.Html, "")), loc)),
+ loc)
+ end)
+ | XML_BEGIN_END (let
+ val loc = s (XML_BEGIN_ENDleft, XML_BEGIN_ENDright)
+ in
+ if XML_BEGIN_END = "xml" then
+ ()
+ else
+ ErrorMsg.errorAt loc "Initial XML tag pair aren't both tagged \"xml\".";
+ (EApp ((EVar (["Basis"], "cdata", Infer), loc),
+ (EPrim (Prim.String (Prim.Html, "")), loc)),
+ loc)
+ end)
+
+ | LPAREN query RPAREN (query)
+ | LPAREN CWHERE sqlexp RPAREN (sqlexp)
+ | LPAREN SQL sqlexp RPAREN (sqlexp)
+ | LPAREN FROM tables RPAREN (#2 tables)
+ | LPAREN SELECT1 query1 RPAREN (query1)
+
+ | LPAREN INSERT INTO texp LPAREN fields RPAREN VALUES LPAREN sqlexps RPAREN RPAREN
+ (let
+ val loc = s (LPAREN1left, RPAREN3right)
+
+ val e = (EVar (["Basis"], "insert", Infer), loc)
+ val e = (EApp (e, texp), loc)
+ in
+ if length fields <> length sqlexps then
+ ErrorMsg.errorAt loc ("Length mismatch in INSERT field specification ("
+ ^ Int.toString (length fields)
+ ^ " vs. " ^ Int.toString (length sqlexps) ^ ")")
+ else
+ ();
+ (EApp (e, (ERecord (ListPair.zip (fields, sqlexps), false), loc)), loc)
+ end)
+ | LPAREN enterDml UPDATE texp SET fsets CWHERE sqlexp leaveDml RPAREN
+ (let
+ val loc = s (LPARENleft, RPARENright)
+
+ val e = (EVar (["Basis"], "update", Infer), loc)
+ val e = (ECApp (e, (CWild (KRecord (KType, loc), loc), loc)), loc)
+ val e = (EApp (e, (ERecord (fsets, false), loc)), loc)
+ val e = (EApp (e, texp), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end)
+ | LPAREN enterDml DELETE FROM texp CWHERE sqlexp leaveDml RPAREN
+ (let
+ val loc = s (LPARENleft, RPARENright)
+
+ val e = (EVar (["Basis"], "delete", Infer), loc)
+ val e = (EApp (e, texp), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end)
+
+ | UNDER (EWild, s (UNDERleft, UNDERright))
+
+ | LET edecls IN eexp END (ELet (edecls, eexp), s (LETleft, ENDright))
+ | LET eexp WHERE edecls END (ELet (edecls, eexp), s (LETleft, ENDright))
+
+ | LBRACK RBRACK (EVar (["Basis"], "Nil", Infer), s (LBRACKleft, RBRACKright))
+
+edecls : ([])
+ | edecl edecls (edecl :: edecls)
+
+edecl : VAL pat EQ eexp ((EDVal (pat, eexp), s (VALleft, eexpright)))
+ | VAL REC valis ((EDValRec valis, s (VALleft, valisright)))
+ | FUN valis ((EDValRec valis, s (FUNleft, valisright)))
+
+enterDml : (inDml := true)
+leaveDml : (inDml := false)
+
+texp : SYMBOL (EVar ([], SYMBOL, Infer), s (SYMBOLleft, SYMBOLright))
+ | LBRACE LBRACE eexp RBRACE RBRACE (eexp)
+
+fields : fident ([fident])
+ | fident COMMA fields (fident :: fields)
+
+sqlexps: sqlexp ([sqlexp])
+ | sqlexp COMMA sqlexps (sqlexp :: sqlexps)
+
+fsets : fident EQ sqlexp ([(fident, sqlexp)])
+ | fident EQ sqlexp COMMA fsets ((fident, sqlexp) :: fsets)
+
+idents : ident ([ident])
+ | ident DOT idents (ident :: idents)
+
+etuple : eexp COMMA eexp ([eexp1, eexp2])
+ | eexp COMMA etuple (eexp :: etuple)
+
+branch : pat DARROW eexp (pat, eexp)
+
+branchs: ([])
+ | BAR branch branchs (branch :: branchs)
+
+patS : pterm (pterm)
+ | pterm DCOLON patS (let
+ val loc = s (ptermleft, patSright)
+ in
+ (PCon (["Basis"], "Cons", SOME (PRecord ([("1", pterm),
+ ("2", patS)], false), loc)),
+ loc)
+ end)
+ | patS COLON cexp (PAnnot (patS, cexp), s (patSleft, cexpright))
+
+pat : patS (patS)
+ | cpath pterm (PCon (#1 cpath, #2 cpath, SOME pterm), s (cpathleft, ptermright))
+
+pterm : SYMBOL (PVar SYMBOL, s (SYMBOLleft, SYMBOLright))
+ | cpath (PCon (#1 cpath, #2 cpath, NONE), s (cpathleft, cpathright))
+ | UNDER (PVar "_", s (UNDERleft, UNDERright))
+ | INT (PPrim (Prim.Int INT), s (INTleft, INTright))
+ | MINUS INT (PPrim (Prim.Int (~INT)), s (MINUSleft, INTright))
+ | STRING (PPrim (Prim.String (Prim.Normal, STRING)), s (STRINGleft, STRINGright))
+ | CHAR (PPrim (Prim.Char CHAR), s (CHARleft, CHARright))
+ | LPAREN pat RPAREN (pat)
+ | LBRACE RBRACE (PRecord ([], false), s (LBRACEleft, RBRACEright))
+ | UNIT (PRecord ([], false), s (UNITleft, UNITright))
+ | LBRACE rpat RBRACE (PRecord rpat, s (LBRACEleft, RBRACEright))
+ | LPAREN ptuple RPAREN (PRecord (ListUtil.mapi (fn (i, p) => (Int.toString (i + 1), p)) ptuple,
+ false),
+ s (LPARENleft, RPARENright))
+ | LBRACK RBRACK (PCon (["Basis"], "Nil", NONE), s (LBRACKleft, RBRACKright))
+
+rpat : CSYMBOL EQ pat ([(CSYMBOL, pat)], false)
+ | INT EQ pat ([(Int64.toString INT, pat)], false)
+ | DOTDOTDOT ([], true)
+ | CSYMBOL EQ pat COMMA rpat ((CSYMBOL, pat) :: #1 rpat, #2 rpat)
+ | INT EQ pat COMMA rpat ((Int64.toString INT, pat) :: #1 rpat, #2 rpat)
+
+ptuple : pat COMMA pat ([pat1, pat2])
+ | pat COMMA ptuple (pat :: ptuple)
+
+rexp : DOTDOTDOT ([], true)
+ | rpath EQ eexp ([(rpath, eexp)], false)
+ | rpath EQ eexp COMMA rexp ((rpath, eexp) :: #1 rexp, #2 rexp)
+
+rpath : path (CVar path, s (pathleft, pathright))
+ | CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+
+xml : xmlOne xml (let
+ val pos = s (xmlOneleft, xmlright)
+ in
+ (EApp ((EApp (
+ (EVar (["Basis"], "join", Infer), pos),
+ xmlOne), pos),
+ xml), pos)
+ end)
+ | xmlOne (xmlOne)
+
+xmlOpt : xml (xml)
+ | (EApp ((EVar (["Basis"], "cdata", Infer), dummy),
+ (EPrim (Prim.String (Prim.Html, "")), dummy)),
+ dummy)
+
+xmlOne : NOTAGS (EApp ((EVar (["Basis"], "cdata", Infer), s (NOTAGSleft, NOTAGSright)),
+ (EPrim (Prim.String (Prim.Html, NOTAGS)), s (NOTAGSleft, NOTAGSright))),
+ s (NOTAGSleft, NOTAGSright))
+ | tag DIVIDE GT (let
+ val pos = s (tagleft, GTright)
+
+ val cdata =
+ if #1 (#1 tag) = "submit" orelse #1 (#1 tag) = "dyn" then
+ let
+ val e = (EVar (["Basis"], "cdata", DontInfer), pos)
+ val e = (ECApp (e, (CWild (KWild, pos), pos)), pos)
+ in
+ (ECApp (e, (CRecord [], pos)), pos)
+ end
+ else
+ (EVar (["Basis"], "cdata", Infer), pos)
+
+ val cdata = (EApp (cdata,
+ (EPrim (Prim.String (Prim.Html, "")), pos)),
+ pos)
+ in
+ (EApp (#5 tag, cdata), pos)
+ end)
+
+ | tag GT xmlOpt END_TAG (let
+ fun tagOut s =
+ case s of
+ "tabl" => "table"
+ | _ => s
+
+ val pos = s (tagleft, GTright)
+ val et = tagIn END_TAG
+ in
+ if #1 (#1 tag) = et then
+ if et = "form" then
+ let
+ val e = (EVar (["Basis"], "form", Infer), pos)
+ val e = (EApp (e, case #4 tag of
+ NONE => (EVar (["Basis"], "None", Infer), pos)
+ | SOME c => (EApp ((EVar (["Basis"], "Some", Infer), pos), c), pos)), pos)
+ val e = (EApp (e, case #2 tag of
+ NONE => (EVar (["Basis"], "None", Infer), pos)
+ | SOME (EPrim (Prim.String (_, s)), _) => (EApp ((EVar (["Basis"], "Some", Infer), pos), parseClass s pos), pos)
+ | SOME c => (EApp ((EVar (["Basis"], "Some", Infer), pos), c), pos)), pos)
+ in
+ case #3 tag of
+ NONE => ()
+ | SOME _ => ErrorMsg.errorAt pos "<form> does not support 'dynClass' attribute";
+ (EApp (e, xmlOpt), pos)
+ end
+ else if et = "subform" orelse et = "subforms" then
+ (EApp (#2 (#1 tag),
+ xmlOpt), pos)
+ else if et = "entry" then
+ (EApp ((EVar (["Basis"], "entry", Infer), pos),
+ xmlOpt), pos)
+ else
+ (EApp (#5 tag, xmlOpt), pos)
+ else
+ (if ErrorMsg.anyErrors () then
+ ()
+ else
+ ErrorMsg.errorAt pos ("Begin tag <"
+ ^ tagOut (#1 (#1 tag))
+ ^ "> and end tag </"
+ ^ tagOut et
+ ^ "> don't match.");
+ (EWild, pos))
+ end)
+ | LBRACE eexp RBRACE (eexp)
+ | LBRACE LBRACK eexp RBRACK RBRACE (let
+ val loc = s (LBRACEleft, RBRACEright)
+ val e = (EVar (["Top"], "txt", Infer), loc)
+ in
+ (EApp (e, eexp), loc)
+ end)
+
+tag : tagHead attrs (let
+ val pos = s (tagHeadleft, attrsright)
+
+ val e = (EVar (["Basis"], "tag", Infer), pos)
+ val eo = case #1 attrs of
+ NONE => (EVar (["Basis"], "null", Infer), pos)
+ | SOME (EPrim (Prim.String (_, s)), pos) => parseClass s pos
+ | SOME e => e
+ val e = (EApp (e, eo), pos)
+ val eo = case #2 attrs of
+ NONE => (EVar (["Basis"], "None", Infer), pos)
+ | SOME e => (EApp ((EVar (["Basis"], "Some", Infer), pos),
+ e), pos)
+ val e = (EApp (e, eo), pos)
+ val eo = case #3 attrs of
+ NONE => (EVar (["Basis"], "noStyle", Infer), pos)
+ | SOME (EPrim (Prim.String (_, s)), pos) => parseStyle s pos
+ | SOME e => e
+ val e = (EApp (e, eo), pos)
+ val eo = case #4 attrs of
+ NONE => (EVar (["Basis"], "None", Infer), pos)
+ | SOME e => (EApp ((EVar (["Basis"], "Some", Infer), pos),
+ e), pos)
+ val e = (EApp (e, eo), pos)
+
+ val atts = case #6 attrs of
+ [] => #7 attrs
+ | data :: datas =>
+ let
+ fun doOne (kind, name, value) =
+ let
+ val e = (EVar (["Basis"], "data_attr", Infer), pos)
+ val e = (EApp (e, (EVar (["Basis"], kind ^ "_kind", Infer), pos)), pos)
+ val e = (EApp (e, (EPrim (Prim.String (Prim.Normal, name)), pos)), pos)
+ in
+ (EApp (e, value), pos)
+ end
+
+ val datas' = foldl (fn (nv, acc) =>
+ let
+ val e = (EVar (["Basis"], "data_attrs", Infer), pos)
+ val e = (EApp (e, acc), pos)
+ in
+ (EApp (e, doOne nv), pos)
+ end) (doOne data) datas
+ in
+ ((CName "Data", pos), datas') :: #7 attrs
+ end
+
+ val e = (EApp (e, (ERecord (atts, false), pos)), pos)
+ val e = (EApp (e, (EApp (#2 tagHead,
+ (ERecord ([], false), pos)), pos)), pos)
+ in
+ (tagHead, #1 attrs, #2 attrs, #5 attrs, e)
+ end)
+
+tagHead: BEGIN_TAG (let
+ val bt = tagIn BEGIN_TAG
+ val pos = s (BEGIN_TAGleft, BEGIN_TAGright)
+ in
+ (bt,
+ (EVar ([], bt, Infer), pos))
+ end)
+ | tagHead LBRACE cexp RBRACE (#1 tagHead, (ECApp (#2 tagHead, cexp), s (tagHeadleft, RBRACEright)))
+
+attrs : (NONE, NONE, NONE, NONE, NONE, [], [])
+ | attr attrs (let
+ val loc = s (attrleft, attrsright)
+ in
+ case attr of
+ Class e =>
+ (case #1 attrs of
+ NONE => ()
+ | SOME _ => ErrorMsg.errorAt loc "Multiple classes specified for tag";
+ (SOME e, #2 attrs, #3 attrs, #4 attrs, #5 attrs, #6 attrs, #7 attrs))
+ | DynClass e =>
+ (case #2 attrs of
+ NONE => ()
+ | SOME _ => ErrorMsg.errorAt loc "Multiple dynamic classes specified for tag";
+ (#1 attrs, SOME e, #3 attrs, #4 attrs, #5 attrs, #6 attrs, #7 attrs))
+ | Style e =>
+ (case #3 attrs of
+ NONE => ()
+ | SOME _ => ErrorMsg.errorAt loc "Multiple styles specified for tag";
+ (#1 attrs, #2 attrs, SOME e, #4 attrs, #5 attrs, #6 attrs, #7 attrs))
+ | DynStyle e =>
+ (case #4 attrs of
+ NONE => ()
+ | SOME _ => ErrorMsg.errorAt loc "Multiple dynamic classes specified for tag";
+ (#1 attrs, #2 attrs, #3 attrs, SOME e, #5 attrs, #6 attrs, #7 attrs))
+ | Data xe =>
+ (#1 attrs, #2 attrs, #3 attrs, #4 attrs, #5 attrs, xe :: #6 attrs, #7 attrs)
+ | Normal xe =>
+ (#1 attrs, #2 attrs, #3 attrs, #4 attrs, (case #1 (#1 xe) of
+ CName "Id" => SOME (#2 xe)
+ | _ => #5 attrs), #6 attrs, xe :: #7 attrs)
+ end)
+
+attr : SYMBOL EQ attrv (case SYMBOL of
+ "class" => Class attrv
+ | "dynClass" => DynClass attrv
+ | "style" => Style attrv
+ | "dynStyle" => DynStyle attrv
+ | _ =>
+ if String.isPrefix "data-" SYMBOL then
+ Data ("data", String.extract (SYMBOL, 5, NONE), attrv)
+ else if String.isPrefix "aria-" SYMBOL then
+ Data ("aria", String.extract (SYMBOL, 5, NONE), attrv)
+ else
+ let
+ val sym = makeAttr SYMBOL
+ in
+ Normal ((CName sym, s (SYMBOLleft, SYMBOLright)),
+ if (sym = "Href" orelse sym = "Src")
+ andalso (case #1 attrv of
+ EPrim _ => true
+ | _ => false) then
+ let
+ val loc = s (attrvleft, attrvright)
+ in
+ (EApp ((EVar (["Basis"], "bless", Infer), loc),
+ attrv), loc)
+ end
+ else if sym = "Nam"
+ andalso (case #1 attrv of
+ EPrim _ => true
+ | _ => false) then
+ let
+ val loc = s (attrvleft, attrvright)
+ in
+ (EApp ((EVar (["Basis"], "blessMeta", Infer), loc),
+ attrv), loc)
+ end
+ else
+ attrv)
+ end)
+ | SYMBOL (let
+ val loc = s (SYMBOLleft, SYMBOLright)
+ in
+ Normal ((CName (makeAttr SYMBOL), loc),
+ (EVar (["Basis"], "True", Infer), loc))
+ end)
+
+attrv : INT (EPrim (Prim.Int INT), s (INTleft, INTright))
+ | FLOAT (EPrim (Prim.Float FLOAT), s (FLOATleft, FLOATright))
+ | STRING (EPrim (Prim.String (Prim.Normal, STRING)), s (STRINGleft, STRINGright))
+ | LBRACE eexp RBRACE (eexp)
+
+query : query1 obopt lopt ofopt (let
+ val loc = s (query1left, query1right)
+
+ val re = (ERecord ([((CName "Rows", loc),
+ query1),
+ ((CName "OrderBy", loc),
+ obopt),
+ ((CName "Limit", loc),
+ lopt),
+ ((CName "Offset", loc),
+ ofopt)], false), loc)
+ in
+ (EApp ((EVar (["Basis"], "sql_query", Infer), loc), re), loc)
+ end)
+
+dopt : (EVar (["Basis"], "False", Infer), dummy)
+ | DISTINCT (EVar (["Basis"], "True", Infer),
+ s (DISTINCTleft, DISTINCTright))
+
+query1 : SELECT dopt select FROM tables wopt gopt hopt
+ (let
+ val loc = s (SELECTleft, tablesright)
+
+ val (empties, sel, exps) =
+ case select of
+ Star => ([],
+ map (fn nm =>
+ (nm, (CTuple [(CWild (KRecord (KType, loc), loc),
+ loc),
+ (CRecord [], loc)],
+ loc))) (#1 tables),
+ [])
+ | Items sis =>
+ let
+ val tabs = map (fn nm => (nm, Unknown)) (#1 tables)
+ val (_, tabs, exps) = foldl (amend_select loc)
+ (1, tabs, []) sis
+ val empties = List.mapPartial (fn (nm, c) =>
+ case c of
+ Unknown => SOME nm
+ | Selective (CRecord [], _) => SOME nm
+ | _ => NONE) tabs
+ in
+ (empties,
+ map (fn (nm, c) => (nm,
+ case c of
+ Everything =>
+ (CTuple [(CWild (KRecord (KType, loc), loc), loc),
+ (CRecord [], loc)], loc)
+ | _ =>
+ let
+ val c = case c of
+ Selective c => c
+ | _ => (CRecord [], loc)
+ in
+ (CTuple [c,
+ (CWild (KRecord (KType, loc), loc),
+ loc)], loc)
+ end)) tabs,
+ exps)
+ end
+
+ val exps = map (fn (c, e) => (c, (EApp ((EVar (["Basis"], "sql_window", Infer), loc), e), loc))) exps
+
+ val sel = (CRecord sel, loc)
+
+ val grp = case gopt of
+ NONE => (ECApp ((EVar (["Basis"], "sql_subset_all",
+ Infer), loc),
+ (CWild (KRecord (KRecord (KType, loc), loc),
+ loc), loc)), loc)
+ | SOME gis =>
+ let
+ val tabs = map (fn nm =>
+ (nm, (CRecord [], loc))) (#1 tables)
+ val tabs = foldl (amend_group loc) tabs gis
+
+ val tabs = map (fn (nm, c) =>
+ (nm,
+ (CTuple [c,
+ (CWild (KRecord (KType, loc),
+ loc),
+ loc)], loc))) tabs
+ in
+ (ECApp ((EVar (["Basis"], "sql_subset", Infer), loc),
+ (CRecord tabs, loc)), loc)
+ end
+
+ val e = (EVar (["Basis"], "sql_query1", Infer), loc)
+ val e = (ECApp (e, (CRecord (map (fn nm => (nm, (CUnit, loc))) empties),
+ loc)), loc)
+ val re = (ERecord ([((CName "Distinct", loc),
+ dopt),
+ ((CName "From", loc),
+ #2 tables),
+ ((CName "Where", loc),
+ wopt),
+ ((CName "GroupBy", loc),
+ grp),
+ ((CName "Having", loc),
+ hopt),
+ ((CName "SelectFields", loc),
+ (ECApp ((EVar (["Basis"], "sql_subset", Infer), loc),
+ sel), loc)),
+ ((CName "SelectExps", loc),
+ (ERecord (exps, false), loc))], false), loc)
+
+ val e = (EApp (e, re), loc)
+ in
+ e
+ end)
+ | query1 UNION query1 (sql_relop ("union", false, query11, query12, s (query11left, query12right)))
+ | query1 INTERSECT query1 (sql_relop ("intersect", false, query11, query12, s (query11left, query12right)))
+ | query1 EXCEPT query1 (sql_relop ("except", false, query11, query12, s (query11left, query12right)))
+ | query1 UNION ALL query1 (sql_relop ("union", true, query11, query12, s (query11left, query12right)))
+ | query1 INTERSECT ALL query1 (sql_relop ("intersect", true, query11, query12, s (query11left, query12right)))
+ | query1 EXCEPT ALL query1 (sql_relop ("except", true, query11, query12, s (query11left, query12right)))
+ | LBRACE LBRACE LBRACE eexp RBRACE RBRACE RBRACE (eexp)
+
+tables : fitem (fitem)
+ | fitem COMMA tables (let
+ val loc = s (fitemleft, tablesright)
+
+ val e = (EVar (["Basis"], "sql_from_comma", Infer), loc)
+ val e = (EApp (e, #2 fitem), loc)
+ in
+ (#1 fitem @ #1 tables,
+ (EApp (e, #2 tables), loc))
+ end)
+
+fitem : table' ([#1 table'], #2 table')
+ | LBRACE LBRACE eexp RBRACE RBRACE (tnamesOf eexp, eexp)
+ | fitem JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_inner_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem INNER JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_inner_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem CROSS JOIN fitem (let
+ val loc = s (fitem1left, fitem2right)
+
+ val e = (EVar (["Basis"], "sql_inner_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ val tru = sql_inject (EVar (["Basis"], "True", Infer), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, tru), loc))
+ end)
+ | fitem LEFT JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_left_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem LEFT OUTER JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_left_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem RIGHT JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_right_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem RIGHT OUTER JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_right_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem FULL JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_full_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | fitem FULL OUTER JOIN fitem ON sqlexp (let
+ val loc = s (fitem1left, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_full_join", Infer), loc)
+ val e = (EApp (e, #2 fitem1), loc)
+ val e = (EApp (e, #2 fitem2), loc)
+ in
+ (#1 fitem1 @ #1 fitem2,
+ (EApp (e, sqlexp), loc))
+ end)
+ | LPAREN query RPAREN AS tname (let
+ val loc = s (LPARENleft, RPARENright)
+
+ val e = (EVar (["Basis"], "sql_from_query", Infer), loc)
+ val e = (ECApp (e, tname), loc)
+ in
+ ([tname], (EApp (e, query), loc))
+ end)
+ | LPAREN LBRACE LBRACE eexp RBRACE RBRACE RPAREN AS tname (let
+ val loc = s (LPARENleft, RPARENright)
+
+ val e = (EVar (["Basis"], "sql_from_query", Infer), loc)
+ val e = (ECApp (e, tname), loc)
+ in
+ ([tname], (EApp (e, eexp), loc))
+ end)
+ | LPAREN fitem RPAREN (fitem)
+
+tname : CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+ | LBRACE cexp RBRACE (cexp)
+
+table : SYMBOL ((CName (capitalize SYMBOL), s (SYMBOLleft, SYMBOLright)),
+ (EVar ([], SYMBOL, Infer), s (SYMBOLleft, SYMBOLright)))
+ | SYMBOL AS tname (tname, (EVar ([], SYMBOL, Infer), s (SYMBOLleft, SYMBOLright)))
+ | LBRACE LBRACE eexp RBRACE RBRACE AS tname (tname, eexp)
+
+table' : table (let
+ val loc = s (tableleft, tableright)
+ val e = (EVar (["Basis"], "sql_from_table", Infer), loc)
+ val e = (ECApp (e, #1 table), loc)
+ in
+ (#1 table, (EApp (e, #2 table), loc))
+ end)
+
+tident : SYMBOL (CName (capitalize SYMBOL), s (SYMBOLleft, SYMBOLright))
+ | CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+ | LBRACE LBRACE cexp RBRACE RBRACE (cexp)
+
+fident : CSYMBOL (CName CSYMBOL, s (CSYMBOLleft, CSYMBOLright))
+ | LBRACE cexp RBRACE (cexp)
+
+seli : tident DOT fident (Field (tident, fident))
+ | sqlexp (Exp (NONE, sqlexp))
+ | sqlexp AS fident (Exp (SOME fident, sqlexp))
+ | tident DOT LBRACE LBRACE cexp RBRACE RBRACE (Fields (tident, cexp))
+ | tident DOT STAR (StarFields tident)
+
+selis : seli ([seli])
+ | seli COMMA selis (seli :: selis)
+
+select : STAR (Star)
+ | selis (Items selis)
+
+sqlexp : TRUE (sql_inject (EVar (["Basis"], "True", Infer),
+ s (TRUEleft, TRUEright)))
+ | FALSE (sql_inject (EVar (["Basis"], "False", Infer),
+ s (FALSEleft, FALSEright)))
+
+ | INT (sql_inject (EPrim (Prim.Int INT),
+ s (INTleft, INTright)))
+ | FLOAT (sql_inject (EPrim (Prim.Float FLOAT),
+ s (FLOATleft, FLOATright)))
+ | STRING (sql_inject (EPrim (Prim.String (Prim.Normal, STRING)),
+ s (STRINGleft, STRINGright)))
+ | CURRENT_TIMESTAMP (sql_nfunc ("current_timestamp",
+ s (CURRENT_TIMESTAMPleft, CURRENT_TIMESTAMPright)))
+
+ | tident DOT fident (let
+ val loc = s (tidentleft, fidentright)
+ val e = (EVar (["Basis"], "sql_field", Infer), loc)
+ val e = (ECApp (e, tident), loc)
+ in
+ (ECApp (e, fident), loc)
+ end)
+ | CSYMBOL (let
+ val loc = s (CSYMBOLleft, CSYMBOLright)
+ in
+ if !inDml then
+ let
+ val e = (EVar (["Basis"], "sql_field", Infer), loc)
+ val e = (ECApp (e, (CName "T", loc)), loc)
+ in
+ (ECApp (e, (CName CSYMBOL, loc)), loc)
+ end
+ else
+ let
+ val e = (EVar (["Basis"], "sql_exp", Infer), loc)
+ in
+ (ECApp (e, (CName CSYMBOL, loc)), loc)
+ end
+ end)
+
+ | LBRACE eexp RBRACE (eexp)
+
+ | sqlexp EQ sqlexp (sql_binary ("eq", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp NE sqlexp (sql_binary ("ne", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp LT sqlexp (sql_binary ("lt", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp LE sqlexp (sql_binary ("le", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp GT sqlexp (sql_binary ("gt", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp GE sqlexp (sql_binary ("ge", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+
+ | sqlexp PLUS sqlexp (sql_binary ("plus", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp MINUS sqlexp (sql_binary ("minus", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp STAR sqlexp (sql_binary ("times", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp DIVIDE sqlexp (sql_binary ("div", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp MOD sqlexp (sql_binary ("mod", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+
+ | sqlexp CAND sqlexp (sql_binary ("and", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+ | sqlexp OR sqlexp (sql_binary ("or", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+
+ | sqlexp LIKE sqlexp (sql_binary ("like", sqlexp1, sqlexp2, s (sqlexp1left, sqlexp2right)))
+
+ | NOT sqlexp (sql_unary ("not", sqlexp, s (NOTleft, sqlexpright)))
+ | MINUS sqlexp (sql_unary ("neg", sqlexp, s (MINUSleft, sqlexpright)))
+
+ | sqlexp IS NULL (let
+ val loc = s (sqlexpleft, NULLright)
+ in
+ (EApp ((EVar (["Basis"], "sql_is_null", Infer), loc),
+ sqlexp), loc)
+ end)
+
+ | CIF sqlexp CTHEN sqlexp CELSE sqlexp (let
+ val loc = s (CIFleft, sqlexp3right)
+ val e = (EVar (["Basis"], "sql_if_then_else", Infer), loc)
+ in
+ (EApp ((EApp ((EApp (e, sqlexp1), loc), sqlexp2), loc), sqlexp3), loc)
+ end)
+
+ | LBRACE LBRACK eexp RBRACK RBRACE (sql_inject (#1 eexp,
+ s (LBRACEleft, RBRACEright)))
+ | LPAREN sqlexp RPAREN (sqlexp)
+
+ | NULL (sql_inject ((EVar (["Basis"], "None", Infer),
+ s (NULLleft, NULLright))))
+
+ | COUNT LPAREN STAR RPAREN window(let
+ val loc = s (COUNTleft, windowright)
+ in
+ case window of
+ NONE => (EVar (["Basis"], "sql_count", Infer), loc)
+ | SOME _ => applyWindow loc (EVar (["Basis"], "sql_window_count", Infer), loc) window
+ end)
+ | COUNT LPAREN sqlexp RPAREN window(let
+ val loc = s (COUNTleft, RPARENright)
+ val e = (EVar (["Basis"], "sql_count_col", Infer), loc)
+ in
+ case window of
+ NONE =>
+ let
+ val e = (EApp ((EVar (["Basis"], "sql_aggregate", Infer), loc),
+ e), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end
+ | SOME _ =>
+ let
+ val e = (EVar (["Basis"], "sql_count_col", Infer), loc)
+ val e = (EApp ((EVar (["Basis"], "sql_window_aggregate", Infer), loc),
+ e), loc)
+ in
+ applyWindow loc (EApp (e, sqlexp), loc) window
+ end
+ end)
+ | sqlagg LPAREN sqlexp RPAREN window (let
+ val loc = s (sqlaggleft, RPARENright)
+
+ val e = (EVar (["Basis"], "sql_" ^ sqlagg, Infer), loc)
+ in
+ case window of
+ NONE =>
+ let
+ val e = (EApp ((EVar (["Basis"], "sql_aggregate", Infer), loc),
+ e), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end
+ | SOME _ =>
+ let
+ val e = (EApp ((EVar (["Basis"], "sql_window_aggregate", Infer), loc),
+ e), loc)
+ in
+ applyWindow loc (EApp (e, sqlexp), loc) window
+ end
+ end)
+ | RANK UNIT window (let
+ val loc = s (RANKleft, windowright)
+ in
+ applyWindow loc (EVar (["Basis"], "sql_rank", Infer), loc) window
+ end)
+ | COALESCE LPAREN sqlexp COMMA sqlexp RPAREN
+ (let
+ val loc = s (COALESCEright, sqlexp2right)
+ val e = (EVar (["Basis"], "sql_coalesce", Infer), loc)
+ val e = (EApp (e, sqlexp1), loc)
+ in
+ (EApp (e, sqlexp2), loc)
+ end)
+ | fname LPAREN sqlexp RPAREN (let
+ val loc = s (fnameleft, RPARENright)
+
+ val e = (EVar (["Basis"], "sql_ufunc", Infer), loc)
+ val e = (EApp (e, fname), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end)
+ | LPAREN query RPAREN (let
+ val loc = s (LPARENleft, RPARENright)
+
+ val e = (EVar (["Basis"], "sql_subquery", Infer), loc)
+ in
+ (EApp (e, query), loc)
+ end)
+
+window : (NONE)
+ | OVER LPAREN pbopt obopt RPAREN (SOME (pbopt, obopt))
+
+pbopt : ((EVar (["Basis"], "sql_no_partition", Infer), dummy))
+ | PARTITION BY sqlexp (let
+ val loc = s (PARTITIONleft, sqlexpright)
+
+ val e = (EVar (["Basis"], "sql_partition", Infer), loc)
+ in
+ (EApp (e, sqlexp), loc)
+ end)
+
+fname : SYMBOL (EVar (["Basis"], "sql_" ^ SYMBOL, Infer), s (SYMBOLleft, SYMBOLright))
+ | LBRACE eexp RBRACE (eexp)
+
+wopt : (sql_inject (EVar (["Basis"], "True", Infer),
+ dummy))
+ | CWHERE sqlexp (sqlexp)
+
+groupi : tident DOT fident (GField (tident, fident))
+ | tident DOT LBRACE LBRACE cexp RBRACE RBRACE (GFields (tident, cexp))
+
+groupis: groupi ([groupi])
+ | groupi COMMA groupis (groupi :: groupis)
+
+gopt : (NONE)
+ | GROUP BY groupis (SOME groupis)
+
+hopt : (sql_inject (EVar (["Basis"], "True", Infer),
+ dummy))
+ | HAVING sqlexp (sqlexp)
+
+obopt : (ECApp ((EVar (["Basis"], "sql_order_by_Nil", Infer), dummy),
+ (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)
+
+obexps : obitem (let
+ val loc = s (obitemleft, obitemright)
+
+ val e' = (ECApp ((EVar (["Basis"], "sql_order_by_Nil", Infer), loc),
+ (CWild (KRecord (KType, loc), loc), loc)),
+ loc)
+ val e = (EApp ((EVar (["Basis"], "sql_order_by_Cons", Infer), loc),
+ #1 obitem), loc)
+ val e = (EApp (e, #2 obitem), loc)
+ in
+ (EApp (e, e'), loc)
+ end)
+ | obitem COMMA obexps (let
+ val loc = s (obitemleft, obexpsright)
+
+ val e = (EApp ((EVar (["Basis"], "sql_order_by_Cons", Infer), loc),
+ #1 obitem), loc)
+ val e = (EApp (e, #2 obitem), loc)
+ in
+ (EApp (e, obexps), loc)
+ end)
+ | RANDOM popt (EVar (["Basis"], "sql_order_by_random", Infer), s (RANDOMleft, poptright))
+
+popt : ()
+ | LPAREN RPAREN ()
+ | UNIT ()
+
+diropt : (EVar (["Basis"], "sql_asc", Infer), dummy)
+ | ASC (EVar (["Basis"], "sql_asc", Infer), s (ASCleft, ASCright))
+ | DESC (EVar (["Basis"], "sql_desc", Infer), s (DESCleft, DESCright))
+ | LBRACE eexp RBRACE (eexp)
+
+lopt : (EVar (["Basis"], "sql_no_limit", Infer), dummy)
+ | LIMIT ALL (EVar (["Basis"], "sql_no_limit", Infer), dummy)
+ | LIMIT sqlint (let
+ val loc = s (LIMITleft, sqlintright)
+ in
+ (EApp ((EVar (["Basis"], "sql_limit", Infer), loc), sqlint), loc)
+ end)
+
+ofopt : (EVar (["Basis"], "sql_no_offset", Infer), dummy)
+ | OFFSET sqlint (let
+ val loc = s (OFFSETleft, sqlintright)
+ in
+ (EApp ((EVar (["Basis"], "sql_offset", Infer), loc), sqlint), loc)
+ end)
+
+sqlint : INT (EPrim (Prim.Int INT), s (INTleft, INTright))
+ | LBRACE eexp RBRACE (eexp)
+
+sqlagg : AVG ("avg")
+ | SUM ("sum")
+ | MIN ("min")
+ | MAX ("max")
+
+ffi_mode : SYMBOL (case SYMBOL of
+ "effectful" => Effectful
+ | "benignEffectful" => BenignEffectful
+ | "clientOnly" => ClientOnly
+ | "serverOnly" => ServerOnly
+ | _ => (ErrorMsg.errorAt (s (SYMBOLleft, SYMBOLright)) "Invalid FFI mode"; Effectful))
+ | SYMBOL STRING (case SYMBOL of
+ "jsFunc" => JsFunc STRING
+ | _ => (ErrorMsg.errorAt (s (SYMBOLleft, SYMBOLright)) "Invalid FFI mode"; Effectful))
+
+ffi_modes : ([])
+ | ffi_mode ffi_modes (ffi_mode :: ffi_modes)
diff --git a/src/urweb.lex b/src/urweb.lex
new file mode 100644
index 0000000..368b9f1
--- /dev/null
+++ b/src/urweb.lex
@@ -0,0 +1,579 @@
+(* -*- mode: sml-lex -*- *)
+
+(* Copyright (c) 2008-2009, 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.
+ *)
+
+(* Lexing info for Ur/Web programs *)
+
+type pos = int
+type svalue = Tokens.svalue
+type ('a,'b) token = ('a,'b) Tokens.token
+type lexresult = (svalue,pos) Tokens.token
+
+val commentOut = ref (fn () => ())
+
+local
+ val commentLevel = ref 0
+ val commentPos = ref 0
+in
+ fun reset () =
+ (commentLevel := 0;
+ commentPos := 0)
+
+ fun enterComment pos =
+ (if !commentLevel = 0 then
+ commentPos := pos
+ else
+ ();
+ commentLevel := !commentLevel + 1)
+
+ fun exitComment () =
+ (ignore (commentLevel := !commentLevel - 1);
+ if !commentLevel = 0 then
+ !commentOut ()
+ else
+ ())
+
+ fun eof () =
+ let
+ val pos = ErrorMsg.lastLineStart ()
+ in
+ if !commentLevel > 0 then
+ ErrorMsg.errorAt' (!commentPos, !commentPos) "Unterminated comment"
+ else
+ ();
+ Tokens.EOF (pos, pos)
+ end
+end
+
+val strEnder = ref #"\""
+val str = ref ([] : char list)
+val strStart = ref 0
+
+local
+ val initSig = ref false
+ val offset = ref 0
+in
+
+fun initialSig () = initSig := true
+
+fun pos yypos = yypos - !offset
+
+fun newline yypos =
+ if !initSig then
+ (initSig := false;
+ offset := yypos + 1)
+ else
+ ErrorMsg.newline (pos yypos)
+
+end
+
+val xmlTag = ref ([] : string list)
+val xmlString = ref true
+val braceLevels = ref ([] : ((unit -> unit) * int) list)
+
+fun pushLevel s = braceLevels := (s, 1) :: (!braceLevels)
+
+fun enterBrace () =
+ case !braceLevels of
+ (s, i) :: rest => braceLevels := (s, i+1) :: rest
+ | _ => ()
+
+fun exitBrace () =
+ case !braceLevels of
+ (s, i) :: rest =>
+ if i = 1 then
+ (braceLevels := rest;
+ s ())
+ else
+ braceLevels := (s, i-1) :: rest
+ | _ => ()
+
+fun initialize () = (reset ();
+ xmlTag := [];
+ xmlString := false)
+
+
+structure StringMap = BinaryMapFn(struct
+ type ord_key = string
+ val compare = String.compare
+ end)
+
+val entities = foldl (fn ((key, value), entities) => StringMap.insert (entities, key, value))
+ StringMap.empty Entities.all
+
+fun unescape loc s =
+ let
+ fun process (s, acc) =
+ let
+ val (befor, after) = Substring.splitl (fn ch => ch <> #"&") s
+ in
+ if Substring.size after = 0 then
+ Substring.concat (rev (s :: acc))
+ else
+ let
+ val after = Substring.slice (after, 1, NONE)
+ val (befor', after') = Substring.splitl (fn ch => ch <> #";") after
+ in
+ if Substring.size after' = 0 then
+ (ErrorMsg.errorAt' loc "Missing ';' after '&'";
+ "")
+ else
+ let
+ val pre = befor
+ val code = befor'
+ val s = Substring.slice (after', 1, NONE)
+
+ val special =
+ if Substring.size code > 0 andalso Substring.sub (code, 0) = #"#"
+ andalso CharVectorSlice.all Char.isDigit (Substring.slice (code, 1, NONE)) then
+ let
+ val code = Substring.string (Substring.slice (code, 1, NONE))
+ in
+ Option.map Utf8.encode (Int.fromString code)
+ end
+ else
+ Option.map Utf8.encode (StringMap.find (entities, Substring.string code))
+ in
+ case special of
+ NONE => (ErrorMsg.errorAt' loc ("Unsupported XML character entity "
+ ^ Substring.string code);
+ "")
+ | SOME sp => process (s, Substring.full sp :: pre :: acc)
+ end
+ end
+ end
+ in
+ process (Substring.full s, [])
+ end
+
+%%
+%header (functor UrwebLexFn(structure Tokens : Urweb_TOKENS));
+%full
+%s COMMENT STRING CHAR XML XMLTAG;
+
+id = [a-z_][A-Za-z0-9_']*;
+xmlid = [A-Za-z][A-Za-z0-9_-]*;
+cid = [A-Z][A-Za-z0-9_']*;
+ws = [\ \t\012\r];
+intconst = [0-9]+;
+realconst = [0-9]+\.[0-9]*;
+hexconst = 0x[0-9A-F]+;
+notags = ([^<{\n(]|(\([^\*<{\n]))+;
+xcom = ([^\-]|(-[^\-]))+;
+oint = [0-9][0-9][0-9];
+xint = x[0-9a-fA-F][0-9a-fA-F];
+
+%%
+
+<INITIAL,COMMENT,XMLTAG>
+ \n => (newline yypos;
+ continue ());
+<XML> \n => (newline yypos;
+ Tokens.NOTAGS (yytext, yypos, yypos + size yytext));
+
+<INITIAL> {ws}+ => (lex ());
+
+<INITIAL> "(*" => (YYBEGIN COMMENT;
+ commentOut := (fn () => YYBEGIN INITIAL);
+ enterComment (pos yypos);
+ continue ());
+<XML> "(*" => (YYBEGIN COMMENT;
+ commentOut := (fn () => YYBEGIN XML);
+ enterComment (pos yypos);
+ continue ());
+<XMLTAG> "(*" => (YYBEGIN COMMENT;
+ commentOut := (fn () => YYBEGIN XMLTAG);
+ enterComment (pos yypos);
+ continue ());
+<INITIAL,XML,XMLTAG>
+ "*)" => (ErrorMsg.errorAt' (pos yypos, pos yypos) "Unbalanced comments";
+ continue ());
+
+<COMMENT> "(*" => (enterComment (pos yypos);
+ continue ());
+<COMMENT> "*)" => (exitComment ();
+ continue ());
+
+<XML> "<!--" {xcom} "-->" => (continue ());
+
+<STRING,CHAR> "\\\"" => (str := #"\"" :: !str; continue());
+<STRING,CHAR> "\\'" => (str := #"'" :: !str; continue());
+<STRING,CHAR> "\\n" => (str := #"\n" :: !str; continue());
+<STRING,CHAR> "\\r" => (str := #"\r" :: !str; continue());
+<STRING,CHAR> "\\\\" => (str := #"\\" :: !str; continue());
+<STRING,CHAR> "\\t" => (str := #"\t" :: !str; continue());
+<STRING,CHAR> "\n" => (newline yypos;
+ str := #"\n" :: !str; continue());
+<STRING,CHAR> "\\" {oint} => (case StringCvt.scanString (Int.scan StringCvt.OCT)
+ (String.extract (yytext, 1, NONE)) of
+ NONE => ErrorMsg.errorAt' (pos yypos, pos yypos) "Illegal string escape"
+ | SOME n => str := chr n :: !str;
+ continue());
+<STRING,CHAR> "\\" {xint} => (case StringCvt.scanString (Int.scan StringCvt.HEX)
+ (String.extract (yytext, 2, NONE)) of
+ NONE => ErrorMsg.errorAt' (pos yypos, pos yypos) "Illegal string escape"
+ | SOME n => str := chr n :: !str;
+ continue());
+
+<INITIAL> "#\"" => (YYBEGIN CHAR; strEnder := #"\""; strStart := pos yypos; str := []; continue());
+
+<CHAR> . => (let
+ val ch = String.sub (yytext, 0)
+ in
+ if ch = !strEnder then
+ let
+ val s = String.implode (List.rev (!str))
+ in
+ YYBEGIN INITIAL;
+ if size s = 1 then
+ Tokens.CHAR (String.sub (s, 0), !strStart, pos yypos + 1)
+ else
+ (ErrorMsg.errorAt' (yypos, yypos)
+ "Character constant is zero or multiple characters";
+ continue ())
+ end
+ else
+ (str := ch :: !str;
+ continue ())
+ end);
+
+<INITIAL> "\"" => (YYBEGIN STRING; strEnder := #"\""; strStart := pos yypos; str := []; continue());
+<INITIAL> "'" => (YYBEGIN STRING; strEnder := #"'"; strStart := pos yypos; str := []; continue());
+
+<STRING> . => (let
+ val ch = String.sub (yytext, 0)
+ in
+ if ch = !strEnder then
+ (if !xmlString then
+ (xmlString := false; YYBEGIN XMLTAG)
+ else
+ YYBEGIN INITIAL;
+ Tokens.STRING (String.implode (List.rev (!str)), !strStart, pos yypos + 1))
+ else
+ (str := ch :: !str;
+ continue ())
+ end);
+
+<INITIAL> "<" {xmlid} "/>"=>(let
+ val tag = String.substring (yytext, 1, size yytext - 3)
+ in
+ Tokens.XML_BEGIN_END (tag, yypos, yypos + size yytext)
+ end);
+<INITIAL> "<" {xmlid} ">"=> (let
+ val tag = String.substring (yytext, 1, size yytext - 2)
+ in
+ YYBEGIN XML;
+ xmlTag := tag :: (!xmlTag);
+ Tokens.XML_BEGIN (tag, yypos, yypos + size yytext)
+ end);
+<XML> "</" {xmlid} ">" => (let
+ val id = String.substring (yytext, 2, size yytext - 3)
+ in
+ case !xmlTag of
+ id' :: rest =>
+ if id = id' then
+ (YYBEGIN INITIAL;
+ xmlTag := rest;
+ Tokens.XML_END (yypos, yypos + size yytext))
+ else
+ Tokens.END_TAG (id, yypos, yypos + size yytext)
+ | _ =>
+ Tokens.END_TAG (id, yypos, yypos + size yytext)
+ end);
+
+<XML> "<" {xmlid} => (YYBEGIN XMLTAG;
+ Tokens.BEGIN_TAG (String.extract (yytext, 1, NONE),
+ yypos, yypos + size yytext));
+
+<XMLTAG> "/" => (Tokens.DIVIDE (yypos, yypos + size yytext));
+<XMLTAG> ">" => (YYBEGIN XML;
+ Tokens.GT (yypos, yypos + size yytext));
+
+<XMLTAG> {ws}+ => (lex ());
+
+<XMLTAG> {xmlid} => (Tokens.SYMBOL (yytext, yypos, yypos + size yytext));
+<XMLTAG> "=" => (Tokens.EQ (yypos, yypos + size yytext));
+
+<XMLTAG> {intconst} => (case Int64.fromString yytext of
+ SOME x => Tokens.INT (x, yypos, yypos + size yytext)
+ | NONE => (ErrorMsg.errorAt' (yypos, yypos)
+ ("Expected int, received: " ^ yytext);
+ continue ()));
+<XMLTAG> {realconst} => (case Real.fromString yytext of
+ SOME x => Tokens.FLOAT (x, yypos, yypos + size yytext)
+ | NONE => (ErrorMsg.errorAt' (yypos, yypos)
+ ("Expected float, received: " ^ yytext);
+ continue ()));
+<XMLTAG> "\"" => (YYBEGIN STRING;
+ xmlString := true; strEnder := #"\"";
+ strStart := yypos; str := []; continue ());
+
+<XMLTAG> "{" => (YYBEGIN INITIAL;
+ pushLevel (fn () => YYBEGIN XMLTAG);
+ Tokens.LBRACE (yypos, yypos + 1));
+<XMLTAG> "(" => (YYBEGIN INITIAL;
+ pushLevel (fn () => YYBEGIN XMLTAG);
+ Tokens.LPAREN (yypos, yypos + 1));
+
+<XMLTAG> . => (ErrorMsg.errorAt' (yypos, yypos)
+ ("illegal XML tag character: \"" ^ yytext ^ "\"");
+ continue ());
+
+<XML> "{" => (YYBEGIN INITIAL;
+ pushLevel (fn () => YYBEGIN XML);
+ Tokens.LBRACE (yypos, yypos + 1));
+
+<XML> {notags} => (Tokens.NOTAGS (unescape (yypos, yypos + size yytext) yytext, yypos, yypos + size yytext));
+
+<XML> "(" => (Tokens.NOTAGS ("(", yypos, yypos + size yytext));
+
+<XML> . => (ErrorMsg.errorAt' (yypos, yypos)
+ ("illegal XML character: \"" ^ yytext ^ "\"");
+ continue ());
+
+<INITIAL> "()" => (Tokens.UNIT (pos yypos, pos yypos + size yytext));
+<INITIAL> "(" => (Tokens.LPAREN (pos yypos, pos yypos + size yytext));
+<INITIAL> ")" => (Tokens.RPAREN (pos yypos, pos yypos + size yytext));
+<INITIAL> "[" => (Tokens.LBRACK (pos yypos, pos yypos + size yytext));
+<INITIAL> "]" => (Tokens.RBRACK (pos yypos, pos yypos + size yytext));
+<INITIAL> "{" => (enterBrace ();
+ Tokens.LBRACE (pos yypos, pos yypos + size yytext));
+<INITIAL> "}" => (exitBrace ();
+ Tokens.RBRACE (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "-->" => (Tokens.KARROW (pos yypos, pos yypos + size yytext));
+<INITIAL> "->" => (Tokens.ARROW (pos yypos, pos yypos + size yytext));
+<INITIAL> "==>" => (Tokens.DKARROW (pos yypos, pos yypos + size yytext));
+<INITIAL> "=>" => (Tokens.DARROW (pos yypos, pos yypos + size yytext));
+<INITIAL> "++" => (Tokens.PLUSPLUS (pos yypos, pos yypos + size yytext));
+<INITIAL> "--" => (Tokens.MINUSMINUS (pos yypos, pos yypos + size yytext));
+<INITIAL> "---" => (Tokens.MINUSMINUSMINUS (pos yypos, pos yypos + size yytext));
+<INITIAL> "^" => (Tokens.CARET (pos yypos, pos yypos + size yytext));
+
+<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));
+<INITIAL> ">" => (Tokens.GT (pos yypos, pos yypos + size yytext));
+<INITIAL> "<=" => (Tokens.LE (pos yypos, pos yypos + size yytext));
+<INITIAL> ">=" => (Tokens.GE (pos yypos, pos yypos + size yytext));
+<INITIAL> "," => (Tokens.COMMA (pos yypos, pos yypos + size yytext));
+<INITIAL> ":::_" => (Tokens.TCOLONWILD (pos yypos, pos yypos + size yytext));
+<INITIAL> ":::" => (Tokens.TCOLON (pos yypos, pos yypos + size yytext));
+<INITIAL> "::_" => (Tokens.DCOLONWILD (pos yypos, pos yypos + size yytext));
+<INITIAL> "::" => (Tokens.DCOLON (pos yypos, pos yypos + size yytext));
+<INITIAL> ":" => (Tokens.COLON (pos yypos, pos yypos + size yytext));
+<INITIAL> "..." => (Tokens.DOTDOTDOT (pos yypos, pos yypos + size yytext));
+<INITIAL> "." => (Tokens.DOT (pos yypos, pos yypos + size yytext));
+<INITIAL> "$" => (Tokens.DOLLAR (pos yypos, pos yypos + size yytext));
+<INITIAL> "#" => (Tokens.HASH (pos yypos, pos yypos + size yytext));
+<INITIAL> "__" => (Tokens.UNDERUNDER (pos yypos, pos yypos + size yytext));
+<INITIAL> "_" => (Tokens.UNDER (pos yypos, pos yypos + size yytext));
+<INITIAL> "~" => (Tokens.TWIDDLE (pos yypos, pos yypos + size yytext));
+<INITIAL> "|" => (Tokens.BAR (pos yypos, pos yypos + size yytext));
+<INITIAL> "*" => (Tokens.STAR (pos yypos, pos yypos + size yytext));
+<INITIAL> "<-" => (Tokens.LARROW (pos yypos, pos yypos + size yytext));
+<INITIAL> ";" => (Tokens.SEMI (pos yypos, pos yypos + size yytext));
+<INITIAL> "!" => (Tokens.BANG (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "+" => (Tokens.PLUS (pos yypos, pos yypos + size yytext));
+<INITIAL> "-" => (Tokens.MINUS (pos yypos, pos yypos + size yytext));
+<INITIAL> "/" => (Tokens.DIVIDE (yypos, yypos + size yytext));
+<INITIAL> "%" => (Tokens.MOD (pos yypos, pos yypos + size yytext));
+<INITIAL> "@" => (Tokens.AT (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "con" => (Tokens.CON (pos yypos, pos yypos + size yytext));
+<INITIAL> "type" => (Tokens.LTYPE (pos yypos, pos yypos + size yytext));
+<INITIAL> "datatype" => (Tokens.DATATYPE (pos yypos, pos yypos + size yytext));
+<INITIAL> "of" => (Tokens.OF (pos yypos, pos yypos + size yytext));
+<INITIAL> "val" => (Tokens.VAL (pos yypos, pos yypos + size yytext));
+<INITIAL> "rec" => (Tokens.REC (pos yypos, pos yypos + size yytext));
+<INITIAL> "and" => (Tokens.AND (pos yypos, pos yypos + size yytext));
+<INITIAL> "fun" => (Tokens.FUN (pos yypos, pos yypos + size yytext));
+<INITIAL> "fn" => (Tokens.FN (pos yypos, pos yypos + size yytext));
+<INITIAL> "map" => (Tokens.MAP (pos yypos, pos yypos + size yytext));
+<INITIAL> "case" => (Tokens.CASE (pos yypos, pos yypos + size yytext));
+<INITIAL> "if" => (Tokens.IF (pos yypos, pos yypos + size yytext));
+<INITIAL> "then" => (Tokens.THEN (pos yypos, pos yypos + size yytext));
+<INITIAL> "else" => (Tokens.ELSE (pos yypos, pos yypos + size yytext));
+
+
+<INITIAL> "structure" => (Tokens.STRUCTURE (pos yypos, pos yypos + size yytext));
+<INITIAL> "signature" => (Tokens.SIGNATURE (pos yypos, pos yypos + size yytext));
+<INITIAL> "struct" => (Tokens.STRUCT (pos yypos, pos yypos + size yytext));
+<INITIAL> "sig" => (if yypos <= 2 then initialSig () else (); Tokens.SIG (pos yypos, pos yypos + size yytext));
+<INITIAL> "let" => (Tokens.LET (pos yypos, pos yypos + size yytext));
+<INITIAL> "in" => (Tokens.IN (pos yypos, pos yypos + size yytext));
+<INITIAL> "end" => (Tokens.END (pos yypos, pos yypos + size yytext));
+<INITIAL> "functor" => (Tokens.FUNCTOR (pos yypos, pos yypos + size yytext));
+<INITIAL> "where" => (Tokens.WHERE (pos yypos, pos yypos + size yytext));
+<INITIAL> "include" => (Tokens.INCLUDE (pos yypos, pos yypos + size yytext));
+<INITIAL> "open" => (Tokens.OPEN (pos yypos, pos yypos + size yytext));
+<INITIAL> "constraint"=> (Tokens.CONSTRAINT (pos yypos, pos yypos + size yytext));
+<INITIAL> "constraints"=> (Tokens.CONSTRAINTS (pos yypos, pos yypos + size yytext));
+<INITIAL> "export" => (Tokens.EXPORT (pos yypos, pos yypos + size yytext));
+<INITIAL> "table" => (Tokens.TABLE (pos yypos, pos yypos + size yytext));
+<INITIAL> "sequence" => (Tokens.SEQUENCE (pos yypos, pos yypos + size yytext));
+<INITIAL> "view" => (Tokens.VIEW (pos yypos, pos yypos + size yytext));
+<INITIAL> "class" => (Tokens.CLASS (pos yypos, pos yypos + size yytext));
+<INITIAL> "cookie" => (Tokens.COOKIE (pos yypos, pos yypos + size yytext));
+<INITIAL> "style" => (Tokens.STYLE (pos yypos, pos yypos + size yytext));
+<INITIAL> "task" => (Tokens.TASK (pos yypos, pos yypos + size yytext));
+<INITIAL> "policy" => (Tokens.POLICY (pos yypos, pos yypos + size yytext));
+<INITIAL> "ffi" => (Tokens.FFI (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "Type" => (Tokens.TYPE (pos yypos, pos yypos + size yytext));
+<INITIAL> "Name" => (Tokens.NAME (pos yypos, pos yypos + size yytext));
+<INITIAL> "Unit" => (Tokens.KUNIT (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "SELECT" => (Tokens.SELECT (pos yypos, pos yypos + size yytext));
+<INITIAL> "DISTINCT" => (Tokens.DISTINCT (pos yypos, pos yypos + size yytext));
+<INITIAL> "FROM" => (Tokens.FROM (pos yypos, pos yypos + size yytext));
+<INITIAL> "AS" => (Tokens.AS (pos yypos, pos yypos + size yytext));
+<INITIAL> "WHERE" => (Tokens.CWHERE (pos yypos, pos yypos + size yytext));
+<INITIAL> "SQL" => (Tokens.SQL (pos yypos, pos yypos + size yytext));
+<INITIAL> "GROUP" => (Tokens.GROUP (pos yypos, pos yypos + size yytext));
+<INITIAL> "ORDER" => (Tokens.ORDER (pos yypos, pos yypos + size yytext));
+<INITIAL> "BY" => (Tokens.BY (pos yypos, pos yypos + size yytext));
+<INITIAL> "HAVING" => (Tokens.HAVING (pos yypos, pos yypos + size yytext));
+<INITIAL> "LIMIT" => (Tokens.LIMIT (pos yypos, pos yypos + size yytext));
+<INITIAL> "OFFSET" => (Tokens.OFFSET (pos yypos, pos yypos + size yytext));
+<INITIAL> "ALL" => (Tokens.ALL (pos yypos, pos yypos + size yytext));
+<INITIAL> "SELECT1" => (Tokens.SELECT1 (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "JOIN" => (Tokens.JOIN (pos yypos, pos yypos + size yytext));
+<INITIAL> "INNER" => (Tokens.INNER (pos yypos, pos yypos + size yytext));
+<INITIAL> "CROSS" => (Tokens.CROSS (pos yypos, pos yypos + size yytext));
+<INITIAL> "OUTER" => (Tokens.OUTER (pos yypos, pos yypos + size yytext));
+<INITIAL> "LEFT" => (Tokens.LEFT (pos yypos, pos yypos + size yytext));
+<INITIAL> "RIGHT" => (Tokens.RIGHT (pos yypos, pos yypos + size yytext));
+<INITIAL> "FULL" => (Tokens.FULL (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "UNION" => (Tokens.UNION (pos yypos, pos yypos + size yytext));
+<INITIAL> "INTERSECT" => (Tokens.INTERSECT (pos yypos, pos yypos + size yytext));
+<INITIAL> "EXCEPT" => (Tokens.EXCEPT (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "TRUE" => (Tokens.TRUE (pos yypos, pos yypos + size yytext));
+<INITIAL> "FALSE" => (Tokens.FALSE (pos yypos, pos yypos + size yytext));
+<INITIAL> "AND" => (Tokens.CAND (pos yypos, pos yypos + size yytext));
+<INITIAL> "OR" => (Tokens.OR (pos yypos, pos yypos + size yytext));
+<INITIAL> "NOT" => (Tokens.NOT (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "COUNT" => (Tokens.COUNT (pos yypos, pos yypos + size yytext));
+<INITIAL> "AVG" => (Tokens.AVG (pos yypos, pos yypos + size yytext));
+<INITIAL> "SUM" => (Tokens.SUM (pos yypos, pos yypos + size yytext));
+<INITIAL> "MIN" => (Tokens.MIN (pos yypos, pos yypos + size yytext));
+<INITIAL> "MAX" => (Tokens.MAX (pos yypos, pos yypos + size yytext));
+<INITIAL> "RANK" => (Tokens.RANK (pos yypos, pos yypos + size yytext));
+<INITIAL> "PARTITION" => (Tokens.PARTITION (pos yypos, pos yypos + size yytext));
+<INITIAL> "OVER" => (Tokens.OVER (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "IF" => (Tokens.CIF (pos yypos, pos yypos + size yytext));
+<INITIAL> "THEN" => (Tokens.CTHEN (pos yypos, pos yypos + size yytext));
+<INITIAL> "ELSE" => (Tokens.CELSE (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "ASC" => (Tokens.ASC (pos yypos, pos yypos + size yytext));
+<INITIAL> "DESC" => (Tokens.DESC (pos yypos, pos yypos + size yytext));
+<INITIAL> "RANDOM" => (Tokens.RANDOM (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "INSERT" => (Tokens.INSERT (pos yypos, pos yypos + size yytext));
+<INITIAL> "INTO" => (Tokens.INTO (pos yypos, pos yypos + size yytext));
+<INITIAL> "VALUES" => (Tokens.VALUES (pos yypos, pos yypos + size yytext));
+<INITIAL> "UPDATE" => (Tokens.UPDATE (pos yypos, pos yypos + size yytext));
+<INITIAL> "SET" => (Tokens.SET (pos yypos, pos yypos + size yytext));
+<INITIAL> "DELETE" => (Tokens.DELETE (pos yypos, pos yypos + size yytext));
+<INITIAL> "NULL" => (Tokens.NULL (pos yypos, pos yypos + size yytext));
+<INITIAL> "IS" => (Tokens.IS (pos yypos, pos yypos + size yytext));
+<INITIAL> "COALESCE" => (Tokens.COALESCE (pos yypos, pos yypos + size yytext));
+<INITIAL> "LIKE" => (Tokens.LIKE (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "CONSTRAINT"=> (Tokens.CCONSTRAINT (pos yypos, pos yypos + size yytext));
+<INITIAL> "UNIQUE" => (Tokens.UNIQUE (pos yypos, pos yypos + size yytext));
+<INITIAL> "CHECK" => (Tokens.CHECK (pos yypos, pos yypos + size yytext));
+<INITIAL> "PRIMARY" => (Tokens.PRIMARY (pos yypos, pos yypos + size yytext));
+<INITIAL> "FOREIGN" => (Tokens.FOREIGN (pos yypos, pos yypos + size yytext));
+<INITIAL> "KEY" => (Tokens.KEY (pos yypos, pos yypos + size yytext));
+<INITIAL> "ON" => (Tokens.ON (pos yypos, pos yypos + size yytext));
+<INITIAL> "NO" => (Tokens.NO (pos yypos, pos yypos + size yytext));
+<INITIAL> "ACTION" => (Tokens.ACTION (pos yypos, pos yypos + size yytext));
+<INITIAL> "RESTRICT" => (Tokens.RESTRICT (pos yypos, pos yypos + size yytext));
+<INITIAL> "CASCADE" => (Tokens.CASCADE (pos yypos, pos yypos + size yytext));
+<INITIAL> "REFERENCES"=> (Tokens.REFERENCES (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "CURRENT_TIMESTAMP" => (Tokens.CURRENT_TIMESTAMP (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "_LOC_" => (let val strLoc = ErrorMsg.spanToString (ErrorMsg.spanOf
+ (pos yypos, pos yypos + size yytext))
+ in
+ Tokens.STRING (strLoc, pos yypos, pos yypos + size yytext)
+ end);
+
+<INITIAL> {id} => (Tokens.SYMBOL (yytext, pos yypos, pos yypos + size yytext));
+<INITIAL> {cid} => (Tokens.CSYMBOL (yytext, pos yypos, pos yypos + size yytext));
+
+<INITIAL> {hexconst} => (let val digits = String.extract (yytext, 2, NONE)
+ val v = (StringCvt.scanString (Int64.scan StringCvt.HEX) digits)
+ handle Overflow => NONE
+ in
+ case v of
+ SOME x => Tokens.INT (x, pos yypos, pos yypos + size yytext)
+ | NONE => (ErrorMsg.errorAt' (pos yypos, pos yypos)
+ ("Expected hexInt, received: " ^ yytext);
+ continue ())
+ end);
+
+<INITIAL> {intconst} => (let val v = (Int64.fromString yytext) handle Overflow => NONE
+ in
+ case v of
+ SOME x => Tokens.INT (x, pos yypos, pos yypos + size yytext)
+ | NONE => (ErrorMsg.errorAt' (pos yypos, pos yypos)
+ ("Expected int, received: " ^ yytext);
+ continue ())
+ end);
+<INITIAL> {realconst} => (case Real64.fromString yytext of
+ SOME x => Tokens.FLOAT (x, pos yypos, pos yypos + size yytext)
+ | NONE => (ErrorMsg.errorAt' (pos yypos, pos yypos)
+ ("Expected float, received: " ^ yytext);
+ continue ()));
+
+<COMMENT> . => (continue());
+
+<INITIAL> . => (ErrorMsg.errorAt' (pos yypos, pos yypos)
+ ("illegal character: \"" ^ yytext ^ "\"");
+ continue ());
diff --git a/src/utf8.sig b/src/utf8.sig
new file mode 100644
index 0000000..4198f60
--- /dev/null
+++ b/src/utf8.sig
@@ -0,0 +1,32 @@
+(* Copyright (c) 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.
+ *)
+
+(* UTF-8 conversion *)
+
+signature UTF8 = sig
+ val encode : int -> string
+end
diff --git a/src/utf8.sml b/src/utf8.sml
new file mode 100644
index 0000000..cbd2fa5
--- /dev/null
+++ b/src/utf8.sml
@@ -0,0 +1,59 @@
+(* Copyright (c) 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.
+ *)
+
+(* UTF-8 conversion *)
+
+structure Utf8 :> UTF8 = struct
+
+fun byte n = str (chr (Word.toInt n))
+
+fun encode n =
+ if n <= 0 then
+ raise Fail "Invalid character to UTF-8-encode"
+ else if n <= 0x7F then
+ str (chr n)
+ else if n <= 0x7FF then
+ let
+ val w = Word.fromInt n
+ val b1 = Word.orb (Word.fromInt (128 + 64), Word.>> (w, Word.fromInt 6))
+ val b2 = Word.orb (Word.fromInt 128, Word.andb (w, Word.fromInt 63))
+ in
+ byte b1 ^ byte b2
+ end
+ else if n <= 0xFFFF then
+ let
+ val w = Word.fromInt n
+ val b1 = Word.orb (Word.fromInt (128 + 64 + 32), Word.>> (w, Word.fromInt 12))
+ val b2 = Word.orb (Word.fromInt 128, Word.andb (Word.>> (w, Word.fromInt 6), Word.fromInt 63))
+ val b3 = Word.orb (Word.fromInt 128, Word.andb (w, Word.fromInt 63))
+ in
+ byte b1 ^ byte b2 ^ byte b3
+ end
+ else
+ raise Fail "Exceeded supported range for UTF-8 characters"
+
+end