aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module-list.c142
-rw-r--r--core/module-list.h34
-rw-r--r--core/module-save.c51
-rw-r--r--core/module-save.h10
-rw-r--r--core/module.c418
-rw-r--r--core/module.h74
-rw-r--r--core/test-module.c333
7 files changed, 801 insertions, 261 deletions
diff --git a/core/module-list.c b/core/module-list.c
index 20fed2b..51148eb 100644
--- a/core/module-list.c
+++ b/core/module-list.c
@@ -49,7 +49,7 @@ enum { INNER_SIZE = sizeof(Node) + FANOUT * sizeof(Node *) + sizeof(Guide) };
/* Where a leaf keeps each field's cells, and what an item stores. */
typedef struct {
unsigned fields, texts;
- uint8_t text_fields[16];
+ uint8_t kinds[16]; /* each field's (PURE_CELL_...) */
uint8_t text_field[16]; /* the text fields, in order */
uint32_t column[16]; /* where each field's cells start in a leaf */
size_t leaf_size;
@@ -127,17 +127,20 @@ static unsigned child_at(const Node *node, size_t at) {
return (unsigned)(base - before) + (*base <= at);
}
static double *numbers(const Layout *l, const Node *leaf, unsigned f) { return (double *)((char *)leaf + l->column[f]); }
+static int64_t *integers(const Layout *l, const Node *leaf, unsigned f) { return (int64_t *)((char *)leaf + l->column[f]); }
static PureSpan *texts(const Layout *l, const Node *leaf, unsigned f) { return (PureSpan *)((char *)leaf + l->column[f]); }
static PureCell cell_at(const Layout *l, const Node *leaf, unsigned i, unsigned f) {
- if (!l->text_fields[f]) return (PureCell){.num = numbers(l, leaf, f)[i]};
+ if (l->kinds[f] == PURE_CELL_INTEGER) return (PureCell){.i64 = integers(l, leaf, f)[i]};
+ if (!pure_cell_text(l->kinds[f])) return (PureCell){.num = numbers(l, leaf, f)[i]};
PureSpan text = texts(l, leaf, f)[i];
return (PureCell){.text = (unsigned char *)text.data, .size = text.size};
}
static void put_cells(const Layout *l, Node *leaf, unsigned i, const PureCell *cells) {
for (unsigned f = 0; f < l->fields; ++f)
- if (l->text_fields[f]) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size};
+ if (pure_cell_text(l->kinds[f])) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size};
+ else if (l->kinds[f] == PURE_CELL_INTEGER) integers(l, leaf, f)[i] = cells[f].i64;
else numbers(l, leaf, f)[i] = cells[f].num;
}
@@ -149,7 +152,7 @@ static void move_items(const Layout *l, Node *to, unsigned at, const Node *from,
return;
}
for (unsigned f = 0; f < l->fields; ++f) {
- size_t width = l->text_fields[f] ? sizeof(PureSpan) : sizeof(double);
+ size_t width = pure_cell_text(l->kinds[f]) ? sizeof(PureSpan) : 8;
memmove((char *)to + l->column[f] + at * width, (const char *)from + l->column[f] + start * width, count * width);
}
}
@@ -293,7 +296,7 @@ static void drop_node(PureList *list, Node *node) {
/* ---- The stored form: each field in order, a number as its binary64 bits
* (little-endian), a text as a u32le length and its bytes. */
-static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 4 + cell.size : 8; }
+static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return pure_cell_text(l->kinds[field]) ? 4 + cell.size : 8; }
static size_t cells_bytes(const Layout *l, const PureCell *cells) {
size_t size = l->number_bytes;
@@ -556,7 +559,7 @@ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within
/* ---- The list */
-PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) {
+PureList *pure_list_new(unsigned fields, const uint8_t *kinds) {
if (fields > 16) return NULL;
PureList *list = calloc(1, sizeof(*list));
if (!list) return NULL;
@@ -564,10 +567,10 @@ PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) {
l->fields = fields;
size_t at = sizeof(Node);
for (unsigned f = 0; f < fields; ++f) {
- l->text_fields[f] = text_fields[f]; /* 1 text (UTF-8), 2 bytes */
+ l->kinds[f] = kinds[f];
l->column[f] = (uint32_t)at;
- at += FANOUT * (l->text_fields[f] ? sizeof(PureSpan) : sizeof(double));
- if (l->text_fields[f]) l->text_field[l->texts++] = (uint8_t)f;
+ at += FANOUT * (pure_cell_text(kinds[f]) ? sizeof(PureSpan) : 8);
+ if (pure_cell_text(kinds[f])) l->text_field[l->texts++] = (uint8_t)f;
else l->number_bytes += 8;
}
l->leaf_size = at;
@@ -588,7 +591,7 @@ static bool rekey(PureList *list, Node **place) {
PureResult pure_list_keyed(PureList *list, int field) {
Layout *l = &list->layout;
- int key = field >= 0 && (unsigned)field < l->fields && !l->text_fields[field] ? field : -1;
+ int key = field >= 0 && (unsigned)field < l->fields && l->kinds[field] == PURE_CELL_NUMBER ? field : -1;
if (key == l->key) return PURE_OK;
l->key = key;
if (list->root && !rekey(list, &list->root)) {
@@ -674,37 +677,69 @@ static bool log_change(PureUndo *undo, Entry entry) {
/* A cell the list can keep: a text's bytes copied (NULL when empty). */
static bool own_cell(const PureList *list, unsigned field, PureCell from, PureCell *to) {
- *to = (PureCell){.num = list->layout.text_fields[field] ? 0 : from.num};
- if (!list->layout.text_fields[field] || !from.size) return true;
+ bool text = pure_cell_text(list->layout.kinds[field]);
+ *to = (PureCell){.i64 = text ? 0 : from.i64}; /* a number's or an integer's bits */
+ if (!text || !from.size) return true;
if (!(to->text = new_text(list, from.text, from.size))) return false;
to->size = from.size;
return true;
}
static unsigned char *encode_cell(const Layout *l, unsigned field, PureCell cell, unsigned char *out) {
- uint64_t bits = cell.size;
- unsigned width = 4;
- if (!l->text_fields[field]) { memcpy(&bits, &cell.num, sizeof(bits)); width = 8; }
+ bool text = pure_cell_text(l->kinds[field]);
+ uint64_t bits = text ? cell.size : (uint64_t)cell.i64; /* a number's bits, or an integer's */
+ unsigned width = text ? 4 : 8;
for (unsigned i = 0; i < width; ++i) *out++ = (unsigned char)(bits >> (8 * i));
- if (l->text_fields[field] && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; }
+ if (text && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; }
return out;
}
+bool pure_cell_fits(uint8_t kind, PureCell cell) {
+ return kind == PURE_CELL_INTEGER || (kind == PURE_CELL_BOOL ? cell.num == 0 || cell.num == 1 : isfinite(cell.num));
+}
+
+bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell) {
+ if (from == to) return true;
+ if (pure_cell_text(from) || pure_cell_text(to)) return false;
+ if (to == PURE_CELL_INTEGER) { /* a whole number in range: -2^63 is, 2^63 is not */
+ double v = cell->num;
+ if (!(v >= -9223372036854775808.0 && v < 9223372036854775808.0) || v != floor(v)) return false;
+ cell->i64 = (int64_t)v;
+ return true;
+ }
+ if (from == PURE_CELL_INTEGER) {
+ if (cell->i64 > INT64_C(9007199254740992) || cell->i64 < -INT64_C(9007199254740992)) return false;
+ cell->num = (double)cell->i64;
+ }
+ if (to == PURE_CELL_BOOL) {
+ if (cell->num != 0 && cell->num != 1) return false;
+ cell->num = cell->num == 1; /* -0 is false as 0 is */
+ }
+ return true;
+}
+
+/* Whether a number cell holds what its kind may: a finite number, a bool 0 or 1. */
+static bool fits(const Layout *l, unsigned field, double value) {
+ return pure_cell_fits(l->kinds[field], (PureCell){.num = value});
+}
+
/* A stored cell of field at *at in bytes (a text's bytes borrowed from
- * them): false when it is cut short, not finite or not UTF-8. */
+ * them): false when it is cut short, not finite (not 0 or 1 for a bool) or
+ * not UTF-8. */
static bool decode_cell(const Layout *l, unsigned field, const unsigned char *bytes, size_t size, size_t *at,
PureCell *cell) {
- size_t width = l->text_fields[field] ? 4 : 8;
+ bool text = pure_cell_text(l->kinds[field]);
+ size_t width = text ? 4 : 8;
if (size - *at < width) return false;
uint64_t bits = 0;
for (size_t i = 0; i < width; ++i) bits |= (uint64_t)bytes[*at + i] << (8 * i);
*at += width;
*cell = (PureCell){0};
- if (!l->text_fields[field]) {
- memcpy(&cell->num, &bits, sizeof(bits));
- return isfinite(cell->num);
+ if (!text) {
+ cell->i64 = (int64_t)bits;
+ return l->kinds[field] == PURE_CELL_INTEGER || fits(l, field, cell->num);
}
- if (bits > size - *at || (l->text_fields[field] == 1 && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)bits})))
+ if (bits > size - *at || (l->kinds[field] == PURE_CELL_TEXT && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)bits})))
return false;
*cell = (PureCell){.text = (unsigned char *)bytes + *at, .size = (size_t)bits};
*at += (size_t)bits;
@@ -716,9 +751,10 @@ static bool decode_cell(const Layout *l, unsigned field, const unsigned char *by
* item's fields, for a set the u8 field and its cell. A record is written
* compact (kinds 4-6): the position as a varint (LEB128), a number as the
* varint 2 * zigzag(n) when it is an integer n below 2^53 in size (not -0),
- * else 1 and its binary64 bits (8 bytes, little-endian), a text as a varint
- * length and its bytes. Records stored before (kinds 1-3, a list's tree
- * value "PLS2") had a u32le position and cells in stored form. */
+ * else 1 and its binary64 bits (8 bytes, little-endian), an integer as the
+ * varint zigzag(n), a text as a varint length and its bytes. Records stored
+ * before (kinds 1-3, a list's tree value "PLS2") had a u32le position and
+ * cells in stored form. */
enum { FIXED_INSERT = 1, FIXED_REMOVE = 2, FIXED_SET = 3, RECORD_INSERT = 4, RECORD_REMOVE = 5, RECORD_SET = 6,
FIXED_HEAD = 5, RECORD_HEAD = 6, JOURNAL_KEPT = 1 << 20 };
@@ -740,20 +776,23 @@ static bool get_varint(const unsigned char *bytes, size_t size, size_t *at, uint
}
/* A cell in compact form (the most it takes: compact_bound). */
-static size_t compact_bound(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 5 + cell.size : 9; }
+static size_t compact_bound(const Layout *l, unsigned field, PureCell cell) {
+ return pure_cell_text(l->kinds[field]) ? 5 + cell.size : 10;
+}
+
+static uint64_t zigzag(int64_t n) { return (uint64_t)n << 1 ^ (n < 0 ? UINT64_MAX : 0); }
+static int64_t unzigzag(uint64_t z) { return z & 1 ? -(int64_t)(z >> 1) - 1 : (int64_t)(z >> 1); }
static unsigned char *put_compact(const Layout *l, unsigned field, PureCell cell, unsigned char *out) {
- if (l->text_fields[field]) {
+ if (l->kinds[field] == PURE_CELL_INTEGER) return put_varint(out, zigzag(cell.i64));
+ if (pure_cell_text(l->kinds[field])) {
out = put_varint(out, cell.size);
if (cell.size) memcpy(out, cell.text, cell.size);
return out + cell.size;
}
double v = cell.num;
- if (fabs(v) < 9007199254740992.0 && v == (double)(int64_t)v && !(v == 0 && signbit(v))) {
- int64_t n = (int64_t)v;
- uint64_t zigzag = (uint64_t)n << 1 ^ (n < 0 ? UINT64_MAX : 0);
- return put_varint(out, zigzag << 1);
- }
+ if (fabs(v) < 9007199254740992.0 && v == (double)(int64_t)v && !(v == 0 && signbit(v)))
+ return put_varint(out, zigzag((int64_t)v) << 1);
uint64_t bits;
memcpy(&bits, &v, sizeof(bits));
*out++ = 1;
@@ -766,26 +805,28 @@ static bool get_compact(const Layout *l, unsigned field, const unsigned char *by
uint64_t value;
*cell = (PureCell){0};
if (!get_varint(bytes, size, at, &value)) return false;
- if (l->text_fields[field]) {
- if (value > size - *at || (l->text_fields[field] == 1 && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)value})))
+ if (l->kinds[field] == PURE_CELL_INTEGER) {
+ cell->i64 = unzigzag(value);
+ return true;
+ }
+ if (pure_cell_text(l->kinds[field])) {
+ if (value > size - *at || (l->kinds[field] == PURE_CELL_TEXT && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)value})))
return false;
*cell = (PureCell){.text = (unsigned char *)bytes + *at, .size = (size_t)value};
*at += (size_t)value;
return true;
}
if (!(value & 1)) { /* an integer */
- uint64_t zigzag = value >> 1;
- if (zigzag >= UINT64_C(1) << 54) return false;
- int64_t n = zigzag & 1 ? -(int64_t)(zigzag >> 1) - 1 : (int64_t)(zigzag >> 1);
- cell->num = (double)n;
- return true;
+ if (value >> 1 >= UINT64_C(1) << 54) return false;
+ cell->num = (double)unzigzag(value >> 1);
+ return fits(l, field, cell->num);
}
if (value != 1 || size - *at < 8) return false;
uint64_t bits = 0;
for (unsigned i = 0; i < 8; ++i) bits |= (uint64_t)bytes[*at + i] << (8 * i);
*at += 8;
memcpy(&cell->num, &bits, sizeof(bits));
- return isfinite(cell->num);
+ return fits(l, field, cell->num);
}
/* Room for size more bytes at the journal's end, or NULL without memory. */
@@ -1029,7 +1070,8 @@ PureResult pure_list_set(PureList *list, size_t at, unsigned field, PureCell val
list->journal_size += (size_t)(put_compact(l, field, copy, end) - record);
}
list->bytes += cell_bytes(l, field, copy) - cell_bytes(l, field, old);
- if (l->text_fields[field]) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size};
+ if (pure_cell_text(l->kinds[field])) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size};
+ else if (l->kinds[field] == PURE_CELL_INTEGER) integers(l, leaf, field)[within] = copy.i64;
else numbers(l, leaf, field)[within] = copy.num;
if (!undo) let_go_text(list, old.text);
if (keyed) sum_path(list, path, depth);
@@ -1046,7 +1088,8 @@ PureCell pure_list_get(const PureList *list, size_t at, unsigned field) {
static void open_run(const Layout *l, const Node *leaf, PureRun *run) {
run->count = leaf->count;
for (unsigned f = 0; f < l->fields; ++f)
- if (l->text_fields[f]) run->column[f].texts = texts(l, leaf, f);
+ if (pure_cell_text(l->kinds[f])) run->column[f].texts = texts(l, leaf, f);
+ else if (l->kinds[f] == PURE_CELL_INTEGER) run->column[f].integers = integers(l, leaf, f);
else run->column[f].numbers = numbers(l, leaf, f);
}
@@ -1095,10 +1138,11 @@ bool pure_list_next(const PureList *list, PureRun *run) {
return true;
}
-/* How two cells of a field order: numbers by value (-0 and +0 are one key),
- * texts by their bytes. */
+/* How two cells of a field order: numbers and integers by value (-0 and +0
+ * are one key), texts by their bytes. */
static int order(const Layout *l, unsigned field, PureCell a, PureCell b) {
- if (!l->text_fields[field]) return (a.num > b.num) - (a.num < b.num);
+ if (l->kinds[field] == PURE_CELL_INTEGER) return (a.i64 > b.i64) - (a.i64 < b.i64);
+ if (!pure_cell_text(l->kinds[field])) return (a.num > b.num) - (a.num < b.num);
size_t shorter = a.size < b.size ? a.size : b.size;
int bytes = shorter ? memcmp(a.text, b.text, shorter) : 0;
return bytes ? (bytes > 0) - (bytes < 0) : (a.size > b.size) - (a.size < b.size);
@@ -1202,7 +1246,7 @@ PURE_COLD PureResult pure_list_sorted(const PureList *list, unsigned field, Pure
PureResult result = PURE_OK;
for (size_t i = 1; i < list->count && result == PURE_OK; ++i)
if (!order(l, field, ref_cell(l, items[at[i - 1]], field), ref_cell(l, items[at[i]], field))) result = PURE_CONFLICT;
- PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->text_fields) : NULL;
+ PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->kinds) : NULL;
if (result == PURE_OK && !sorted) result = PURE_ALLOCATION;
for (size_t i = 0; result == PURE_OK && i < list->count; ++i) {
PureCell cells[16];
@@ -1249,11 +1293,13 @@ void pure_undo_rollback(PureUndo *undo, size_t mark) {
continue;
}
list->bytes += cell_bytes(l, e->field, e->old) - cell_bytes(l, e->field, cell_at(l, leaf, within, e->field));
- if (l->text_fields[e->field]) {
+ if (pure_cell_text(l->kinds[e->field])) {
PureSpan *text = &texts(l, leaf, e->field)[within];
free_text(text->data);
*text = (PureSpan){e->old.text, e->old.size};
- } else
+ } else if (l->kinds[e->field] == PURE_CELL_INTEGER)
+ integers(l, leaf, e->field)[within] = e->old.i64;
+ else
numbers(l, leaf, e->field)[within] = e->old.num;
sum_path(list, path, depth);
++list->version;
diff --git a/core/module-list.h b/core/module-list.h
index 603134a..75a871f 100644
--- a/core/module-list.h
+++ b/core/module-list.h
@@ -16,9 +16,9 @@
#endif
/* Runtime-owned lists of records for the module executor. An item is one
- * cell per field: a number, or text bytes the list owns. Every change made
- * during an event is logged; a failed event undoes the log in reverse and a
- * successful one frees what it replaced. */
+ * cell per field: a number, an integer, or text bytes the list owns. Every
+ * change made during an event is logged; a failed event undoes the log in
+ * reverse and a successful one frees what it replaced. */
typedef struct { union { double num; int64_t i64; }; unsigned char *text; size_t size; } PureCell;
typedef struct PureList PureList;
/* An event's list changes; a removed item's cells wait in cells. */
@@ -36,7 +36,7 @@ typedef struct { void *entries; size_t count, capacity; } PureRetired;
typedef struct {
size_t start;
unsigned count;
- union { const double *numbers; const PureSpan *texts; } column[16];
+ union { const double *numbers; const int64_t *integers; const PureSpan *texts; } column[16];
/* Where the run is in the list's tree, for the next one. */
const void *path[16];
uint8_t taken[16];
@@ -46,9 +46,22 @@ typedef struct {
/* A list holds up to PURE_LIST_ITEMS items, a run up to PURE_LIST_RUN. */
enum { PURE_LIST_ITEMS = 1 << 24, PURE_LIST_RUN = 32 };
-/* text_fields: a field's kind, 0 a number, 1 a text (UTF-8, checked when
- * read), 2 bytes (any: a runtime list's). */
-PureList *pure_list_new(unsigned fields, const uint8_t *text_fields);
+/* A field's kind: a number, a text (UTF-8, checked when read), bytes (any: a
+ * runtime list's), a bool (a number 0 or 1, kept as a number is) or an
+ * integer (an int64_t, ordered as one). A stored list's header names its
+ * fields by these kinds. */
+enum { PURE_CELL_NUMBER = 0, PURE_CELL_TEXT = 1, PURE_CELL_BYTES = 2, PURE_CELL_BOOL = 3, PURE_CELL_INTEGER = 4 };
+static inline bool pure_cell_text(uint8_t kind) { return kind == PURE_CELL_TEXT || kind == PURE_CELL_BYTES; }
+/* Whether a number, bool or integer cell holds what its kind may: a finite
+ * number, a bool 0 or 1 (either zero), any integer. */
+bool pure_cell_fits(uint8_t kind, PureCell cell);
+/* A number, a bool or an integer as another of them holds it (a module
+ * replaced, a stored value read by another module, a waiting send recast):
+ * true with *cell made the other when it fits (a bool from 0 or 1, either
+ * zero false; a number from an integer within 2^53; an integer from a
+ * whole number in its range), else false. Texts only as themselves. */
+bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell);
+PureList *pure_list_new(unsigned fields, const uint8_t *kinds);
void pure_list_free(PureList *);
/* Let every item go (retired where a shared copy may read it). */
void pure_list_clear(PureList *);
@@ -97,8 +110,8 @@ PureCell pure_list_get(const PureList *, size_t at, unsigned field);
bool pure_list_run(const PureList *, size_t at, PureRun *);
bool pure_list_next(const PureList *, PureRun *);
/* In a list ordered by field (a keyed list): the first position whose item's
- * field does not order below key (numbers by value, texts by bytes), and
- * whether that item's field is key. */
+ * field does not order below key (numbers and integers by value, texts by
+ * bytes), and whether that item's field is key. */
size_t pure_list_seek(const PureList *, unsigned field, PureCell key, bool *found);
/* The field a keyed list keeps its items in order of: when it is a number
* field, seek by it reads each level's keys from one node (field -1, or a
@@ -110,7 +123,8 @@ PureResult pure_list_keyed(PureList *, int field);
* same key (nothing is made). */
PureResult pure_list_sorted(const PureList *, unsigned field, PureList **out, size_t *moved);
/* The stored form of items: each item's fields in order, a number as its
- * binary64 bits (little-endian), a text as a u32le length and its bytes.
+ * binary64 bits and an integer as its two's complement (8 bytes,
+ * little-endian), a text as a u32le length and its bytes.
* pure_list_size is the bytes of all the items so stored, and
* pure_list_encode writes them to out. */
size_t pure_list_size(const PureList *);
diff --git a/core/module-save.c b/core/module-save.c
index ae32c02..1300832 100644
--- a/core/module-save.c
+++ b/core/module-save.c
@@ -242,7 +242,7 @@ static int read_outputs(const unsigned char *at, size_t size, SentShape *shapes,
for (unsigned f = 0; f < fields; ++f) {
size_t named = size - k >= 2 ? at[k + 1] : 0;
if (size - k < 2 || !named || named >= PURE_MODULE_NAME || size - k < 2 + named || !at[k] ||
- at[k] == PURE_FIELD_ROWS || at[k] > PURE_FIELD_BOOL) /* an output sends no rows */
+ at[k] == PURE_FIELD_ROWS || at[k] > PURE_FIELD_I64) /* an output sends no rows */
return -1;
if (shapes) {
shapes[t].fields[f] = (PureField){.type = at[k]};
@@ -445,9 +445,9 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
if (result == PURE_OK && pure_draft_revision(*legacy) && pure_draft_size(*legacy))
result = read_whole(*legacy, &bytes, &size);
}
- /* The stored record: its fields' kinds and names. */
+ /* The stored record: its fields' kinds (PURE_CELL_...: a record's, no runtime bytes) and names. */
char names[PURE_RECORD_FIELDS][PURE_MODULE_NAME] = {{0}};
- uint8_t text[PURE_RECORD_FIELDS] = {0};
+ uint8_t kinds[PURE_RECORD_FIELDS] = {0};
unsigned count = 0;
size_t at = 0, items = 0;
uint64_t holds = 0;
@@ -458,7 +458,7 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
count = ok ? bytes[4] : 0;
at = 5;
for (unsigned f = 0; ok && f < count; ++f) {
- ok = named(bytes, size, &at, 0, &text[f], names[f]) && (text[f] <= 1 || text[f] == PURE_RECORD_BOOL);
+ ok = named(bytes, size, &at, 0, &kinds[f], names[f]) && kinds[f] <= PURE_CELL_INTEGER && kinds[f] != PURE_CELL_BYTES;
for (unsigned k = 0; k < f; ++k) ok = ok && strcmp(names[k], names[f]);
}
if (ok && image) { /* the save it holds, then its items */
@@ -474,9 +474,7 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
}
PureList *from = NULL;
if (result == PURE_OK && stored) {
- uint8_t layout[PURE_RECORD_FIELDS]; /* a bool is a number as lists go */
- for (unsigned f = 0; f < count; ++f) layout[f] = text[f] == PURE_RECORD_BOOL ? 0 : text[f];
- from = pure_list_new(count, layout);
+ from = pure_list_new(count, kinds);
result = from ? pure_list_read(from, bytes + at, items) : PURE_ALLOCATION;
if (result == PURE_OK && !image) result = pure_list_replay(from, bytes + at + items, size - at - items);
}
@@ -495,7 +493,7 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
}
bool same = stored && count == r->count;
for (unsigned f = 0; same && f < count; ++f)
- same = text[f] == pure_record_kind(m, r->first + f) && !strcmp(names[f], m->record_field_names[r->first + f]);
+ same = kinds[f] == pure_record_kind(m, r->first + f) && !strcmp(names[f], m->record_field_names[r->first + f]);
if (result == PURE_OK && same && image) {
*out = from;
saved->image = holds;
@@ -509,8 +507,12 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
if (same) {
*out = from;
from = NULL;
- } else if (from) result = pure_list_migrate(out, from, field_names, text, count, m, record);
- else if (!(*out = pure_list_new(r->count, m->record_field_text + r->first))) result = PURE_ALLOCATION;
+ } else if (from) result = pure_list_migrate(out, from, field_names, kinds, count, m, record);
+ else {
+ uint8_t own[PURE_RECORD_FIELDS];
+ for (unsigned f = 0; f < r->count; ++f) own[f] = pure_record_kind(m, r->first + f);
+ if (!(*out = pure_list_new(r->count, own))) result = PURE_ALLOCATION;
+ }
}
pure_list_free(from);
if (result != PURE_OK) {
@@ -669,20 +671,14 @@ void pure_app_checkpointed(PureApp *app, PureCheckpoint *c, PureResult result) {
}
static bool scalar(uint8_t type) {
- return type == PURE_STATE_NUM || type == PURE_STATE_BOOL || type == PURE_STATE_CURSOR || type == PURE_STATE_INDEX;
+ return type == PURE_STATE_NUM || type == PURE_STATE_BOOL || type == PURE_STATE_I64 || type == PURE_STATE_CURSOR ||
+ type == PURE_STATE_INDEX;
}
-/* A number's or a bool's (each is stored as the other may be read: 0 and 1 are false and true). */
-static bool valued(uint8_t type) { return type == PURE_STATE_NUM || type == PURE_STATE_BOOL; }
-
-/* A stored value still fits its state: a cursor on a character boundary of
- * its text, an index on an item of its list (or 0), a finite number, a bool
- * 0 or 1. */
+/* A stored position still fits its state: a cursor on a character boundary
+ * of its text, an index on an item of its list (or 0). */
PURE_COLD static bool fits(const PureModule *m, PureDraft *const *texts, PureList *const *lists, uint32_t state, double value) {
- if (!isfinite(value)) return false;
- if (m->state_type[state] == PURE_STATE_NUM) return true;
- if (m->state_type[state] == PURE_STATE_BOOL) return value == 0 || value == 1;
- if (value < 0 || value != floor(value)) return false;
+ if (!isfinite(value) || value < 0 || value != floor(value)) return false;
if (m->state_type[state] == PURE_STATE_INDEX) {
size_t count = pure_list_count(lists[m->state_ref[state]]);
return value <= (count ? (double)(count - 1) : 0);
@@ -737,13 +733,14 @@ PURE_COLD PureResult pure_save_open_values(PureApp *app, const PureModule *m, Pu
uint64_t bits = 0;
for (unsigned b = 0; b < 8; ++b) bits |= (uint64_t)bytes[at + b] << (8 * b);
at += 8;
- double value;
- memcpy(&value, &bits, sizeof(value));
int state = pure_module_state(m, name);
- if (state >= 0 && kept[state] < 0 && scalar(type) &&
- (m->state_type[state] == type || (valued(type) && valued(m->state_type[state]))) &&
- fits(m, texts, lists, (uint32_t)state, value))
- values[state].num = m->state_type[state] == PURE_STATE_BOOL ? pure_as_bool(value, 0) : value;
+ if (state < 0 || kept[state] >= 0 || !scalar(type)) continue;
+ /* A number, a bool or an i64, each as the state's type holds it when it fits (pure_cell_as); a position where it fits. */
+ uint8_t from = pure_value_kind(type), to = pure_value_kind(m->state_type[state]);
+ PureCell cell = {.i64 = (int64_t)bits};
+ if (from != 0xFF && to != 0xFF ? pure_cell_fits(from, cell) && pure_cell_as(from, to, &cell)
+ : m->state_type[state] == type && fits(m, texts, lists, (uint32_t)state, cell.num))
+ values[state].i64 = cell.i64;
}
free(bytes);
return ok ? PURE_OK : PURE_STORE_ERROR;
diff --git a/core/module-save.h b/core/module-save.h
index 991c498..5c81504 100644
--- a/core/module-save.h
+++ b/core/module-save.h
@@ -74,14 +74,12 @@ uint32_t pure_fields_lay_out(PureField *fields, uint32_t count);
PureResult pure_send_recast(const PureField *from, uint32_t from_count, const PureField *to, uint32_t to_count,
PureSpan payload, unsigned char **out, size_t *size);
-/* A record field's kind as a stored list names it: 0 a number, 1 a text, 3
- * a bool (2 is a runtime list's bytes); a list holds a bool as a number. */
-enum { PURE_RECORD_BOOL = 3 };
+/* A record field's kind in a list, as a stored list names it too (PURE_CELL_...). */
uint8_t pure_record_kind(const PureModule *, uint32_t field);
/* A list with record's fields, taking from `from` (its fields' names and
- * kinds) each field of the same name and kind, or a number and a bool from
- * each other (0 and 1 are false and true; a bool's other numbers take the
- * default), and giving the others their defaults (module.c). */
+ * kinds) each field of the same name and kind, or a number, a bool and an
+ * i64 from each other when the value fits (pure_cell_as), and giving the
+ * others their defaults (module.c). */
PureResult pure_list_migrate(PureList **out, const PureList *from, const char *const *names,
const uint8_t *kinds, unsigned count, const PureModule *m, uint32_t record);
#endif
diff --git a/core/module.c b/core/module.c
index 99b3088..7c3a0ac 100644
--- a/core/module.c
+++ b/core/module.c
@@ -2,6 +2,7 @@
#include "module-save.h"
#include "log.h"
#include "module-text.h"
+#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@@ -12,10 +13,10 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES =
HANDLER_BYTES = 12, OP_BYTES = 16 };
/* T_FIELD and T_ITEM stand for the type of the message or record field
* named, T_KEY for the type of the named keyed list's key, T_LOCAL for what
- * the named local holds, T_STATE for the type of the number or bool state
- * named. */
+ * the named local holds, T_STATE for the type of the number, bool or i64
+ * state named. */
enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7, T_KEY = 8, T_LOCAL = 9,
- T_BOOL = 10, T_STATE = 11 };
+ T_BOOL = 10, T_STATE = 11, T_I64 = 12 };
enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS };
/* Where an opcode may appear. */
/* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */
@@ -27,16 +28,16 @@ enum { READ = IN_VIEW | IN_HANDLER };
* the type), a list and one of its record's fields, a message field, the
* collection, a constant, a unit, the column's row, a branch target, a stack
* depth, a fold, a relation, a keyed list, an output, the row of the column
- * a fold runs in, a number or a bool state (A_VALUE), or whether the value
- * is a bool (A_BOOL: 0 or 1). */
-enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_BOOL + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT,
- A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_BOOL };
+ * a fold runs in, a number, bool or i64 state (A_VALUE), or the value's type
+ * (A_TYPE: 0 a number, 1 a bool, 2 an i64). */
+enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT,
+ A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE };
/* A fold's block is verified as a column that leaves one bool (count, first)
- * or a number and whether to include it (a bool), a table's filter as a
- * column that leaves a bool: these stand for a view type. A walk's block
- * (EACH) is verified as its handler's code that may read the item it runs
- * for (WALK). */
-enum { FOLD_ONE = 100, FOLD_TWO, FILTER, WALK };
+ * or a value and whether to include it (a bool): a number (FOLD_TWO) or an
+ * i64 (FOLD_I64). A table's filter is verified as a column that leaves a
+ * bool: these stand for a view type. A walk's block (EACH) is verified as
+ * its handler's code that may read the item it runs for (WALK). */
+enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK };
/* The text or list an opcode works on: its cursor and index operands belong
* to it, and so does a cursor or index it pushes. O_ANY: no rule. O_OPERAND:
* it pushes a position in its operand's text or list. O_ARG: the argument.
@@ -50,9 +51,10 @@ static const unsigned char magic[8] = {'P','U','R','E','M','O','D','6'};
#define NUMS(pops) {pops, {T_NUM, T_NUM, T_NUM}, T_NUM, READ, A_NONE, O_ANY}
#define COMPARE {2, {T_NUM, T_NUM}, T_BOOL, READ, A_NONE, O_ANY}
#define LOGIC(pops) {pops, {T_BOOL, T_BOOL}, T_BOOL, READ, A_NONE, O_ANY}
+#define I64S(pops) {pops, {T_I64, T_I64}, T_I64, READ, A_NONE, O_ANY}
#define STATE(type) A_STATE + PURE_STATE_##type
static const struct { unsigned char pops, in[3], out; unsigned short where; unsigned char arg, of; } signature[PURE_OP_COUNT] = {
- [PURE_OP_CONST] = {0, {0}, T_NUM, READ, A_BOOL, O_ANY}, [PURE_OP_ADD] = NUMS(2), [PURE_OP_SUB] = NUMS(2),
+ [PURE_OP_CONST] = {0, {0}, T_NUM, READ, A_TYPE, O_ANY}, [PURE_OP_ADD] = NUMS(2), [PURE_OP_SUB] = NUMS(2),
[PURE_OP_MUL] = NUMS(2), [PURE_OP_DIV] = NUMS(2), [PURE_OP_NEG] = NUMS(1), [PURE_OP_LT] = COMPARE,
[PURE_OP_LE] = COMPARE, [PURE_OP_GT] = COMPARE, [PURE_OP_GE] = COMPARE, [PURE_OP_EQ] = COMPARE,
[PURE_OP_NE] = COMPARE, [PURE_OP_AND] = LOGIC(2), [PURE_OP_OR] = LOGIC(2), [PURE_OP_NOT] = LOGIC(1),
@@ -72,7 +74,7 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_TEXT_CONST] = {0, {0}, T_TEXT, READ, A_CONSTANT, O_ANY},
[PURE_OP_TEXT_LOAD] = {0, {0}, T_TEXT, READ, STATE(TEXT), O_ANY},
[PURE_OP_TEXT_INPUT] = {0, {0}, T_TEXT, IN_TEXT, A_NONE, O_ANY},
- [PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_BOOL, O_ANY},
+ [PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_TYPE, O_ANY},
[PURE_OP_CONCAT] = {2, {T_TEXT, T_TEXT}, T_TEXT, READ, A_NONE, O_ANY},
[PURE_OP_CHARS] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY},
[PURE_OP_LINES] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY},
@@ -118,10 +120,16 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_LOCAL_LOAD] = {0, {0}, T_LOCAL, IN_LOCALS, A_LOCAL, O_ANY},
[PURE_OP_LOCAL_STORE] = {1, {T_LOCAL}, 0, IN_HANDLER, A_LOCAL, O_ANY},
[PURE_OP_FLOOR] = NUMS(1), [PURE_OP_MOD] = NUMS(2),
+ [PURE_OP_I64_ADD] = I64S(2), [PURE_OP_I64_SUB] = I64S(2), [PURE_OP_I64_MUL] = I64S(2), [PURE_OP_I64_DIV] = I64S(2),
+ [PURE_OP_I64_MOD] = I64S(2), [PURE_OP_I64_NEG] = I64S(1), [PURE_OP_I64_MIN] = I64S(2), [PURE_OP_I64_MAX] = I64S(2),
+ [PURE_OP_I64_COMPARE] = {2, {T_I64, T_I64}, T_BOOL, READ, A_RELATION, O_ANY},
+ [PURE_OP_I64_FROM] = {1, {T_NUM}, T_I64, READ, A_NONE, O_ANY},
+ [PURE_OP_NUM_FROM] = {1, {T_I64}, T_NUM, READ, A_NONE, O_ANY},
};
#undef NUMS
#undef COMPARE
#undef LOGIC
+#undef I64S
#undef STATE
static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; }
@@ -148,6 +156,19 @@ static void write_double(unsigned char *p, double value) {
write32(p + 4, (uint32_t)(bits >> 32));
}
+static int64_t read_i64(const unsigned char *p) { return (int64_t)(read32(p) | (uint64_t)read32(p + 4) << 32); }
+
+static void write_i64(unsigned char *p, int64_t value) {
+ write32(p, (uint32_t)(uint64_t)value);
+ write32(p + 4, (uint32_t)((uint64_t)value >> 32));
+}
+
+const char *pure_fault_name(PureFault fault) {
+ static const char *const names[PURE_FAULT_COUNT] = {"none", "divide", "non_finite", "clamp", "limit", "not_found",
+ "encoding", "exists", "memory", "overflow"};
+ return fault < PURE_FAULT_COUNT ? names[fault] : "none";
+}
+
/* A bool's value: 0 (not -0) or 1. */
static bool canonical_bool(double value) { return value == 1 || (value == 0 && !signbit(value)); }
@@ -166,8 +187,8 @@ uint32_t pure_fields_lay_out(PureField *fields, uint32_t count) {
return prefix;
}
-/* A field's value in a payload: a number, or bytes (of the payload). */
-typedef struct { double num; PureSpan bytes; } FieldValue;
+/* A field's value in a payload: a number (a u32's, a bool's) or an i64, or bytes (of the payload). */
+typedef struct { union { double num; int64_t i64; }; PureSpan bytes; } FieldValue;
/* Each field's value in payload, laid out by fields (their offsets set):
* false when the payload is not so laid out. */
@@ -182,6 +203,7 @@ static bool read_fields(const PureField *fields, uint32_t count, PureSpan payloa
values[f] = (FieldValue){0};
if (field->type == PURE_FIELD_U32) values[f].num = read32(payload.data + field->offset);
else if (field->type == PURE_FIELD_NUMBER) values[f].num = read_double(payload.data + field->offset);
+ else if (field->type == PURE_FIELD_I64) values[f].i64 = read_i64(payload.data + field->offset);
else if (field->type == PURE_FIELD_BOOL) {
if (payload.data[field->offset] > 1) return false;
values[f].num = payload.data[field->offset];
@@ -195,6 +217,11 @@ static bool read_fields(const PureField *fields, uint32_t count, PureSpan payloa
return at == payload.size;
}
+/* A fixed field's value as a list cell's kind (a u32 is a number). */
+static uint8_t field_kind(uint8_t type) {
+ return type == PURE_FIELD_BOOL ? PURE_CELL_BOOL : type == PURE_FIELD_I64 ? PURE_CELL_INTEGER : PURE_CELL_NUMBER;
+}
+
PureResult pure_send_recast(const PureField *from, uint32_t from_count, const PureField *to, uint32_t to_count,
PureSpan payload, unsigned char **out, size_t *size) {
FieldValue was[PURE_MODULE_FIELDS], now[PURE_MODULE_FIELDS];
@@ -210,13 +237,13 @@ PureResult pure_send_recast(const PureField *from, uint32_t from_count, const Pu
while (f < from_count && strcmp(from[f].name, field->name)) ++f;
bool value = pure_field_fixed(field->type);
if (f < from_count && value && pure_field_fixed(from[f].type)) {
- /* A number, a u32 or a bool: the value when the field can hold it
- * (a bool 0 or 1), as a state's is kept. */
- double v = was[f].num;
- bool fits = field->type == PURE_FIELD_NUMBER ? true
- : field->type == PURE_FIELD_U32 ? v >= 0 && v <= UINT32_MAX && v == (double)(uint32_t)v
- : v == 0 || v == 1;
- now[g].num = fits ? v : 0;
+ /* A number, a u32, a bool or an i64: the value when the field can
+ * hold it (pure_cell_as; a u32 a whole number in its range), as a
+ * state's is kept; else 0. */
+ PureCell cell = {.i64 = was[f].i64};
+ bool fits = pure_cell_as(field_kind(from[f].type), field_kind(field->type), &cell) &&
+ (field->type != PURE_FIELD_U32 || (cell.num >= 0 && cell.num <= UINT32_MAX && cell.num == floor(cell.num)));
+ now[g].i64 = fits ? cell.i64 : 0;
} else if (f < from_count && !value && (from[f].type == PURE_FIELD_BYTES || from[f].type == PURE_FIELD_TEXT)) {
bool fits = field->type == PURE_FIELD_BYTES || from[f].type == PURE_FIELD_TEXT || pure_utf8_valid(was[f].bytes);
if (fits) now[g].bytes = was[f].bytes;
@@ -235,6 +262,7 @@ PureResult pure_send_recast(const PureField *from, uint32_t from_count, const Pu
unsigned char *place = field->offset == PURE_FIELD_REST ? made + at : made + field->offset;
if (field->type == PURE_FIELD_U32) write32(place, (uint32_t)now[g].num);
else if (field->type == PURE_FIELD_NUMBER) write_double(place, now[g].num);
+ else if (field->type == PURE_FIELD_I64) write_i64(place, now[g].i64);
else if (field->type == PURE_FIELD_BOOL) *place = (unsigned char)now[g].num;
else {
if (field->offset != PURE_FIELD_REST) write32(place, (uint32_t)now[g].bytes.size);
@@ -303,13 +331,26 @@ static uint32_t list_of(uint32_t arg) { return arg & 0xFFFF; }
static uint32_t item_field(uint32_t arg) { return arg >> 16; }
static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) {
- uint32_t f = m->record[m->state_ref[list]].first + field;
- return m->record_field_text[f] ? T_TEXT : m->record_field_bool[f] ? T_BOOL : T_NUM;
+ static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT,
+ [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64};
+ return types[m->record_field_type[m->record[m->state_ref[list]].first + field]];
}
/* The stack type of a message field's value (a u32 is a number). */
static unsigned char message_type(uint8_t type) {
- return type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : type == PURE_FIELD_BOOL ? T_BOOL : T_NUM;
+ return type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : type == PURE_FIELD_BOOL ? T_BOOL :
+ type == PURE_FIELD_I64 ? T_I64 : T_NUM;
+}
+
+/* The stack type of a number, bool or i64 state's value. */
+static unsigned char state_type(const PureModule *m, uint32_t state) {
+ return m->state_type[state] == PURE_STATE_BOOL ? T_BOOL : m->state_type[state] == PURE_STATE_I64 ? T_I64 : T_NUM;
+}
+
+/* What a local holds (its argument's bits 8-15): a number, an index, a bool or an i64. */
+static unsigned char local_type(uint32_t arg) {
+ static const unsigned char types[] = {T_NUM, T_INDEX, T_BOOL, T_I64};
+ return types[arg >> 8 & 3];
}
PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg, const PureInputShape *shape, int row,
@@ -324,17 +365,20 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
case A_ROW: return (int)arg == row;
case A_OUTER: return outer >= 0 && (int)arg == outer;
case A_FOLD: case A_EACH: { /* a slice's list is keyed by a number */
- uint32_t list = arg >> 8 & 0xFF, flags = arg & 0xFF;
+ uint32_t list = arg >> 8 & 0xFF, flags = arg & 0xFF, how = flags & PURE_FOLD_KIND;
bool sliced = kind == A_FOLD ? flags & PURE_FOLD_SLICED : flags & PURE_EACH_SLICED;
- return (kind == A_FOLD ? (flags & 0x7F) <= PURE_FOLD_DISTINCT : flags <= PURE_EACH_SLICED) &&
- state_of(m, list, PURE_STATE_LIST) && arg >> 16 &&
- (!sliced || (m->state_key[list] && field_type(m, list, m->state_key[list] - 1u) == T_NUM));
- }
- case A_LOCAL: /* a slot, and a number, an index of a list or a bool */
- return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 2 &&
+ /* An i64's fold sums, keeps the most or least or counts the different values. */
+ bool valued = how != PURE_FOLD_COUNT && how != PURE_FOLD_FIRST;
+ unsigned char key = state_of(m, list, PURE_STATE_LIST) && m->state_key[list] ?
+ field_type(m, list, m->state_key[list] - 1u) : 0;
+ return (kind == A_FOLD ? how <= PURE_FOLD_DISTINCT && (valued || !(flags & PURE_FOLD_INTEGER)) : flags <= PURE_EACH_SLICED) &&
+ state_of(m, list, PURE_STATE_LIST) && arg >> 16 && (!sliced || key == T_NUM || key == T_I64);
+ }
+ case A_LOCAL: /* a slot, and a number, an index of a list, a bool or an i64 */
+ return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 3 &&
((arg >> 8 & 0xFF) == 1 ? state_of(m, arg >> 16, PURE_STATE_LIST) : !(arg >> 16));
- case A_VALUE: return state_of(m, arg, PURE_STATE_NUM) || state_of(m, arg, PURE_STATE_BOOL);
- case A_BOOL: return arg <= 1;
+ case A_VALUE: return state_of(m, arg, PURE_STATE_NUM) || state_of(m, arg, PURE_STATE_BOOL) || state_of(m, arg, PURE_STATE_I64);
+ case A_TYPE: return arg <= 2;
case A_RELATION: return arg <= PURE_NOT_EQUAL;
case A_KEYED: return state_of(m, arg, PURE_STATE_LIST) && m->state_key[arg];
case A_OUTPUT: return arg < m->inputs && m->input[arg].kind == PURE_KIND_OUTPUT;
@@ -371,7 +415,7 @@ PURE_COLD static const char *verify_block(const PureModule *, PureBlock, int vie
PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, int view, int row, int outer,
uint32_t event, bool locals, uint32_t *at, unsigned char *target) {
#define REFUSE(why) do { *at = pc; return why; } while (0)
- bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO;
+ bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64;
if (!handler && !block.length) return "empty view";
/* A handler's message or text; a view's arguments, or the message of the handler a fold is in. */
int input = event_input(event);
@@ -422,9 +466,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
REFUSE("message operation outside a message handler");
}
uint32_t arg = op->arg;
- bool truth = op->op == PURE_OP_CONST && arg;
+ /* A constant: a finite number, a bool +0 or 1, any i64 (arg says which). */
+ bool constant = op->op == PURE_OP_CONST, truth = constant && arg == 1, integer = constant && arg == 2;
if ((kind == A_NONE && arg) ||
- (truth ? !canonical_bool(op->value) : op->op == PURE_OP_CONST ? !isfinite(op->value) : op->value != 0))
+ (truth ? !canonical_bool(op->value) : integer ? false : constant ? !isfinite(op->value) : op->integer != 0))
REFUSE("non-canonical operand");
if (!names_arg(m, kind, arg, shape, row, outer)) REFUSE("operand names no such state, field, constant or unit");
/* A keyed list places its items by key, and an item's key stays. */
@@ -454,12 +499,13 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (kind == A_FOLD && (folding || view == FILTER))
REFUSE(view == FILTER ? "fold inside a filter" : "fold inside a fold");
if (skip > block.length - i - 1) REFUSE(kind == A_FOLD ? "fold block outside its block" : "each block outside its block");
- unsigned how = arg & 0x7F;
+ unsigned how = arg & PURE_FOLD_KIND;
/* A fold in a walk reads the walk's item as a fold in a column reads its row. */
const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, WALK, (int)(arg >> 8 & 0xFF), -1,
event, locals, &inner)
: verify_block(m, (PureBlock){pc + 1, skip},
- how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : FOLD_TWO,
+ how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE :
+ arg & PURE_FOLD_INTEGER ? FOLD_I64 : FOLD_TWO,
(int)(arg >> 8 & 0xFF), row, event, locals, &inner);
if (why) { *at = inner; return why; }
for (uint32_t k = i + 1; k <= i + skip; ++k) {
@@ -485,16 +531,17 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
} else if (need[0] == T_KEY) {
need[0] = field_type(m, arg, m->state_key[arg] - 1u);
} else if (need[0] == T_LOCAL) {
- need[0] = (arg >> 8 & 0xFF) == 1 ? T_INDEX : (arg >> 8 & 0xFF) == 2 ? T_BOOL : T_NUM;
+ need[0] = local_type(arg);
} else if (need[0] == T_STATE) {
- need[0] = m->state_type[arg] == PURE_STATE_BOOL ? T_BOOL : T_NUM;
+ need[0] = state_type(m, arg);
} else if (op->op == PURE_OP_NUM_TEXT && arg) {
- need[0] = T_BOOL;
+ need[0] = arg == 1 ? T_BOOL : T_I64;
} else if ((op->op == PURE_OP_EQ || op->op == PURE_OP_NE) && height >= 2 && types[height - 1] == T_BOOL &&
types[height - 2] == T_BOOL) {
need[0] = need[1] = T_BOOL; /* two bools compare as two numbers do */
} else if ((kind == A_FOLD && arg & PURE_FOLD_SLICED) || (kind == A_EACH && arg & PURE_EACH_SLICED)) {
- need[0] = need[1] = T_NUM; /* a slice's low and high keys */
+ uint32_t list = arg >> 8 & 0xFF;
+ need[0] = need[1] = field_type(m, list, m->state_key[list] - 1u); /* a slice's low and high keys */
pops = 2;
}
if (height < (int)pops) REFUSE("stack underflow");
@@ -512,16 +559,18 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
REFUSE(type == T_CURSOR ? "cursor of another text" : "index of another list");
}
unsigned char out = signature[op->op].out;
- if (kind == A_FOLD && (arg & 0x7F) == PURE_FOLD_FIRST) { /* an index of its list */
+ if (kind == A_FOLD && (arg & PURE_FOLD_KIND) == PURE_FOLD_FIRST) { /* an index of its list */
out = T_INDEX;
of = O_ARG;
owner = arg >> 8 & 0xFF;
- }
+ } else if (kind == A_FOLD && arg & PURE_FOLD_INTEGER && (arg & PURE_FOLD_KIND) != PURE_FOLD_DISTINCT)
+ out = T_I64; /* an i64's sum, most or least */
if (out == T_FIELD) out = message_type(m->field[shape->field_start + arg].type);
if (out == T_ITEM) out = field_type(m, list_of(arg), item_field(arg));
- if (out == T_LOCAL) out = (arg >> 8 & 0xFF) == 1 ? T_INDEX : (arg >> 8 & 0xFF) == 2 ? T_BOOL : T_NUM;
- if (out == T_STATE) out = m->state_type[arg] == PURE_STATE_BOOL ? T_BOOL : T_NUM;
+ if (out == T_LOCAL) out = local_type(arg);
+ if (out == T_STATE) out = state_type(m, arg);
if (truth) out = T_BOOL;
+ if (integer) out = T_I64;
if (out) {
if (height == PURE_MODULE_STACK) REFUSE("stack overflow");
owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : of == O_OPERAND ? owners[height] : owner);
@@ -552,7 +601,9 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (view == FOLD_ONE) return height != 1 || types[0] != T_BOOL ? "fold block must leave one bool" : NULL;
if (view == FOLD_TWO)
return height != 2 || types[0] != T_NUM || types[1] != T_BOOL ? "fold block must leave a number and a bool" : NULL;
- unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : view == PURE_VIEW_BOOL ? T_BOOL : T_NUM;
+ if (view == FOLD_I64)
+ return height != 2 || types[0] != T_I64 || types[1] != T_BOOL ? "fold block must leave an i64 and a bool" : NULL;
+ unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : view == PURE_VIEW_BOOL ? T_BOOL : view == PURE_VIEW_I64 ? T_I64 : T_NUM;
if (!handler ? height != 1 || types[0] != result : height != 0)
return !handler ? "view must leave one value of its type" : "handler must leave no value";
return NULL;
@@ -570,21 +621,27 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
}
/* Whether an instruction has a form that works on the items of a run at
- * once: numbers and indexes, with no branch and no text. */
+ * once: numbers, bools and indexes, with no branch, no text and no i64. */
PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op, int input) {
switch (op->op) {
case PURE_OP_FIELD: { /* a number or bool the message or the arguments give, the same for every item */
uint8_t type = input >= 0 ? m->field[m->input[input].field_start + op->arg].type : 0;
return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL;
}
- case PURE_OP_CONST: case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_NEG:
+ case PURE_OP_CONST: return op->arg != 2;
+ case PURE_OP_LOAD: return m->state_type[op->arg] != PURE_STATE_I64;
+ case PURE_OP_LOCAL_LOAD: return local_type(op->arg) != T_I64;
+ case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_NEG:
case PURE_OP_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: case PURE_OP_EQ: case PURE_OP_NE:
case PURE_OP_AND: case PURE_OP_OR: case PURE_OP_NOT: case PURE_OP_MIN: case PURE_OP_MAX: case PURE_OP_CLAMP:
- case PURE_OP_FLOOR: case PURE_OP_MOD: case PURE_OP_LOAD: case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_LOAD:
+ case PURE_OP_FLOOR: case PURE_OP_MOD: case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_LOAD:
case PURE_OP_INDEX_MOVE: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_INDEX_NUM:
- case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_PICK: case PURE_OP_SLIDE: case PURE_OP_LOCAL_LOAD:
- return true;
- case PURE_OP_ITEM_GET: return field_type(m, list_of(op->arg), item_field(op->arg)) != T_TEXT;
+ case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_PICK: case PURE_OP_SLIDE:
+ return true; /* no i64 comes from the others, so PICK and SLIDE move none */
+ case PURE_OP_ITEM_GET: {
+ unsigned char type = field_type(m, list_of(op->arg), item_field(op->arg));
+ return type == T_NUM || type == T_BOOL;
+ }
case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: return field_type(m, op->arg, m->state_key[op->arg] - 1u) == T_NUM;
default: return false;
}
@@ -602,7 +659,7 @@ PURE_COLD static void plan_folds(PureModule *m, PureBlock block, int input) {
for (uint32_t pc = block.start; pc < block.start + block.length; ++pc) {
PureOp *op = &m->code[pc];
if (op->op != PURE_OP_FOLD) continue;
- op->plan = block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16}, input);
+ op->plan = !(op->arg & PURE_FOLD_INTEGER) && block_has_lanes(m, (PureBlock){pc + 1, op->arg >> 16}, input);
pc += op->arg >> 16;
}
}
@@ -620,7 +677,7 @@ PURE_COLD static unsigned arity(uint8_t op) {
case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_FIELD: case PURE_OP_LOCAL_LOAD:
return 0;
case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT:
- case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM:
+ case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: case PURE_OP_I64_NEG: case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM:
return 1;
case PURE_OP_CLAMP: return 3;
default: return 2;
@@ -979,10 +1036,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (constant_bytes) memcpy(out->constant_data, p + size - constant_bytes, constant_bytes);
for (uint32_t i = 0; i < out->states; ++i, at += STATE_BYTES) {
out->state_type[i] = (uint8_t)read32(at + 4);
- if (read32(at + 4) > PURE_STATE_BOOL) REFUSE("state type", i);
- /* A number's or a bool's value is its 8 bytes; others have flags: a
- * mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */
- bool value = out->state_type[i] == PURE_STATE_NUM || out->state_type[i] == PURE_STATE_BOOL;
+ if (read32(at + 4) > PURE_STATE_I64) REFUSE("state type", i);
+ /* A number's, a bool's or an i64's value is its 8 bytes; others have flags:
+ * a mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */
+ bool value = out->state_type[i] == PURE_STATE_NUM || out->state_type[i] == PURE_STATE_BOOL ||
+ out->state_type[i] == PURE_STATE_I64;
uint32_t flags = value ? 0 : read32(at + 12);
bool cursor = out->state_type[i] == PURE_STATE_CURSOR, list = out->state_type[i] == PURE_STATE_LIST;
bool rows = list && flags == PURE_LIST_ROWS;
@@ -990,7 +1048,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (!name_at(text, strings, read32(at), rows ? NAME_PATH : NAME_IDENTIFIER, out->state_names[i]) ||
(rows && !strchr(out->state_names[i], '.')))
REFUSE("state name", i);
- if (value) {
+ if (out->state_type[i] == PURE_STATE_I64) {
+ out->initial[i].i64 = (int64_t)(read32(at + 8) | (uint64_t)read32(at + 12) << 32);
+ } else if (value) {
out->initial[i].num = read_double(at + 8);
if (!isfinite(out->initial[i].num)) REFUSE("non-finite initial value", i);
if (out->state_type[i] == PURE_STATE_BOOL && !canonical_bool(out->initial[i].num)) REFUSE("bool initial value", i);
@@ -1050,7 +1110,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
field->type = (uint8_t)type;
field->rows = (uint16_t)(type >> 8); /* a rows field's list state */
if (!name_at(text, strings, read32(entry), NAME_PATH, field->name) || field->type < PURE_FIELD_U32 ||
- field->type > PURE_FIELD_BOOL || (field->type != PURE_FIELD_ROWS && type >> 8) || type >> 24)
+ field->type > PURE_FIELD_I64 || (field->type != PURE_FIELD_ROWS && type >> 8) || type >> 24)
REFUSE("message field", next_field + f);
/* Rows fill the list named by the input's name and the field's path; an output sends none. */
if (field->type == PURE_FIELD_ROWS) {
@@ -1094,17 +1154,19 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
REFUSE("record", i);
for (uint32_t f = record->first; f < record->first + record->count; ++f) {
const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES;
- uint32_t type = read32(entry + 4); /* 0 a number, 1 a text, 2 a bool */
- out->record_field_text[f] = type == 1;
- out->record_field_bool[f] = type == 2;
- if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > 2)
+ uint32_t type = read32(entry + 4); /* PureRecordType */
+ out->record_field_type[f] = (uint8_t)type;
+ if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > PURE_RECORD_I64)
REFUSE("record field", f);
for (uint32_t k = record->first; k < f; ++k)
if (!strcmp(out->record_field_names[k], out->record_field_names[f])) REFUSE("duplicate record field", f);
- out->record_field_default[f].num = type == 1 ? read32(entry + 8) : read_double(entry + 8);
+ /* A default: a text's constant, an i64's 8 bytes, a number (a bool's +0 or 1). */
+ if (type == PURE_RECORD_TEXT) out->record_field_default[f].num = read32(entry + 8);
+ else if (type == PURE_RECORD_I64) out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32);
+ else out->record_field_default[f].num = read_double(entry + 8);
double given = out->record_field_default[f].num;
- if (type == 1 ? read32(entry + 12) || read32(entry + 8) >= out->constants
- : !isfinite(given) || (type == 2 && !canonical_bool(given)))
+ if (type == PURE_RECORD_TEXT ? read32(entry + 12) || read32(entry + 8) >= out->constants
+ : type != PURE_RECORD_I64 && (!isfinite(given) || (type == PURE_RECORD_BOOL && !canonical_bool(given))))
REFUSE("record field default", f);
}
next_record_field += record->count;
@@ -1116,7 +1178,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
(out->state_type[i] == PURE_STATE_CURSOR && !state_of(out, out->state_ref[i], PURE_STATE_TEXT)) ||
(out->state_type[i] == PURE_STATE_LIST &&
(out->state_ref[i] >= out->records || out->state_key[i] > out->record[out->state_ref[i]].count ||
- (out->state_key[i] && out->record_field_bool[out->record[out->state_ref[i]].first + out->state_key[i] - 1u]))) ||
+ (out->state_key[i] &&
+ out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_BOOL))) ||
(out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST)))
REFUSE("state reference", i);
/* A table names its list in place of code; its columns follow it. */
@@ -1126,7 +1189,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->view_table[i] = (uint16_t)read16(at + 6);
out->view_code[i] = (PureBlock){read32(at + 8), read32(at + 12)};
uint32_t table = out->view_table[i];
- if (at[4] > PURE_VIEW_BOOL || (at[5] && at[4] != PURE_VIEW_TABLE) ||
+ if (at[4] > PURE_VIEW_I64 || (at[5] && at[4] != PURE_VIEW_TABLE) ||
(table && (table > i || out->view_type[table - 1] != PURE_VIEW_TABLE || at[4] == PURE_VIEW_TABLE)))
REFUSE("view type", i);
/* A table names its list in start, or, filtered, in the byte after its
@@ -1273,7 +1336,7 @@ static int named_fields(const unsigned char *bytes, size_t size, PureField *fiel
for (unsigned f = 0; f < count; ++f) {
size_t length = size - at >= 2 ? bytes[at + 1] : 0;
if (size - at < 2 || !length || length >= PURE_MODULE_NAME || size - at < 2 + length || !bytes[at] ||
- bytes[at] == PURE_FIELD_ROWS || bytes[at] > PURE_FIELD_BOOL)
+ bytes[at] == PURE_FIELD_ROWS || bytes[at] > PURE_FIELD_I64)
return -1;
fields[f] = (PureField){.type = bytes[at]};
memcpy(fields[f].name, bytes + at + 2, length);
@@ -1296,7 +1359,7 @@ bool pure_module_takes(const PureModule *m, uint32_t input, const unsigned char
while (f < s->field_count && strcmp(m->field[s->field_start + f].name, sent[k].name)) ++f;
if (f == s->field_count) return false; /* its value would be dropped */
uint8_t type = m->field[s->field_start + f].type, from = sent[k].type;
- if (type != from && !(type == PURE_FIELD_NUMBER && from == PURE_FIELD_U32) &&
+ if (type != from && !((type == PURE_FIELD_NUMBER || type == PURE_FIELD_I64) && from == PURE_FIELD_U32) &&
!(type == PURE_FIELD_BYTES && from == PURE_FIELD_TEXT))
return false;
}
@@ -1346,6 +1409,12 @@ static double minimum(double a, double b) { return a < b || (a == b && signbit(a
static double maximum(double a, double b) { return a > b || (a == b && !signbit(a)) ? a : b; }
typedef struct { union { double num; int64_t i64; }; PureBytes bytes; } Slot;
+
+/* Whether two values that order as order (below zero: the first below) are related as a relation says (PureRelation). */
+static bool related(uint32_t relation, int order) {
+ return relation == PURE_LESS ? order < 0 : relation == PURE_LESS_EQUAL ? order <= 0 : relation == PURE_GREATER ? order > 0 :
+ relation == PURE_GREATER_EQUAL ? order >= 0 : relation == PURE_EQUAL ? order == 0 : order != 0;
+}
/* A walk (EACH): the list, the item it runs for, where it ends (items the
* walk removes move it), and whether that item left. */
typedef struct { uint32_t list; size_t at, end; bool removed; } Walk;
@@ -1359,7 +1428,7 @@ typedef struct {
size_t row; /* the row a column is evaluated for (in a fold, the fold's item) */
size_t outer; /* in a fold, the row of the column it runs in */
Walk *walk; /* the walk the code runs in, or NULL */
- double *locals; /* the event's locals (a handler's, and what runs in it), or NULL */
+ PureValue *locals; /* the event's locals (a handler's, and what runs in it), or NULL */
bool arguments; /* FIELD reads the arguments given for a view, not the message */
bool modified, exists;
uint32_t (*spans)[2]; /* a message's variable fields: start and size in the payload */
@@ -1485,7 +1554,7 @@ static void move_indices(const PureModule *m, PureValue *values, uint32_t list,
}
static bool text_field(const PureModule *m, uint32_t list, uint32_t field) {
- return m->record_field_text[m->record[m->state_ref[list]].first + field];
+ return m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_TEXT;
}
/* Append bytes to the arena as a value of it. */
@@ -1521,6 +1590,7 @@ static PureResult send(PureApp *app, uint32_t output, const Slot *fields, const
const PureField *field = &m->field[shape->field_start + f];
if (field->type == PURE_FIELD_U32) write32(payload + field->offset, (uint32_t)fields[f].num);
else if (field->type == PURE_FIELD_BOOL) payload[field->offset] = fields[f].num != 0;
+ else if (field->type == PURE_FIELD_I64) write_i64(payload + field->offset, fields[f].i64);
else if (field->type == PURE_FIELD_NUMBER) {
uint64_t bits;
memcpy(&bits, &fields[f].num, 8);
@@ -1692,7 +1762,7 @@ static bool run_lanes(const PureModule *m, const PureValue *values, PureBlock bl
}
case PURE_OP_INDEX_LOAD: stack[sp++] = (Lanes){NULL, values[arg].num, false, m->state_ref[arg]}; continue;
case PURE_OP_ROW: stack[sp++] = (Lanes){NULL, 0, true, (uint16_t)arg}; continue;
- case PURE_OP_LOCAL_LOAD: stack[sp++] = (Lanes){NULL, ev->locals[arg & 0xFF], false, (uint16_t)(arg >> 16)}; continue;
+ case PURE_OP_LOCAL_LOAD: stack[sp++] = (Lanes){NULL, ev->locals[arg & 0xFF].num, false, (uint16_t)(arg >> 16)}; continue;
case PURE_OP_OUTER_ROW: stack[sp++] = (Lanes){NULL, (double)ev->outer, false, (uint16_t)arg}; continue;
case PURE_OP_LIST_COUNT: stack[sp++] = (Lanes){NULL, (double)pure_list_count(app->lists[arg]), false, 0}; continue;
case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: {
@@ -1833,22 +1903,28 @@ static bool run_lanes(const PureModule *m, const PureValue *values, PureBlock bl
}
/* The values a distinct fold has seen: open addressing over their bits (-0
- * as 0), UINT64_MAX free (no finite number's bits). */
-typedef struct { uint64_t *keys; size_t capacity, count; } Seen;
+ * as 0), UINT64_MAX free (no finite number's bits; an i64's -1, whose
+ * being seen is marked apart). */
+typedef struct { uint64_t *keys; size_t capacity, count; bool marked; } Seen;
-/* A fold's running value: what count, sum, max, min, first or distinct has so far. */
-typedef struct { unsigned how; double total; bool any, done; Seen seen; } Fold;
+/* A fold's running value: what count, sum, max, min, first or distinct has
+ * so far, over numbers (total) or i64s (whole; a sum past the range
+ * overflows and ends the fold). */
+typedef struct { unsigned how; bool integer, overflow; double total; int64_t whole; bool any, done; Seen seen; } Fold;
static size_t seen_slot(uint64_t bits, size_t capacity) {
bits ^= bits >> 33, bits *= 0xff51afd7ed558ccdu, bits ^= bits >> 33;
return (size_t)bits & (capacity - 1);
}
-/* Whether value is new to the set (added); false without memory too (*full). */
-static bool seen_new(Seen *seen, double value, bool *full) {
- uint64_t bits;
- value = value == 0 ? 0 : value;
- memcpy(&bits, &value, sizeof(bits));
+/* Whether a value's bits are new to the set (added); false without memory too (*full). */
+static bool seen_bits(Seen *seen, uint64_t bits, bool *full) {
+ if (bits == UINT64_MAX) { /* the free slots' mark: an i64's -1, kept aside */
+ if (seen->marked) return false;
+ seen->marked = true;
+ ++seen->count;
+ return true;
+ }
if (2 * (seen->count + 1) > seen->capacity) { /* half the slots free */
size_t capacity = seen->capacity ? 2 * seen->capacity : 64;
uint64_t *keys = malloc(capacity * sizeof(*keys));
@@ -1872,9 +1948,16 @@ static bool seen_new(Seen *seen, double value, bool *full) {
return true;
}
+static bool seen_new(Seen *seen, double value, bool *full) {
+ uint64_t bits;
+ value = value == 0 ? 0 : value;
+ memcpy(&bits, &value, sizeof(bits));
+ return seen_bits(seen, bits, full);
+}
+
/* One item: its value, and whether it counts (for count and first, the
* value is). false without memory (a distinct fold's set). */
-static bool fold_in(Fold *fold, size_t row, double value, double counts) {
+static bool fold_in(Fold *fold, size_t row, PureValue value, double counts) {
if (counts == 0) return true;
unsigned how = fold->how;
if (how == PURE_FOLD_FIRST) {
@@ -1884,12 +1967,22 @@ static bool fold_in(Fold *fold, size_t row, double value, double counts) {
}
if (how == PURE_FOLD_DISTINCT) {
bool full = false;
- fold->total += seen_new(&fold->seen, value, &full);
+ fold->total += fold->integer ? seen_bits(&fold->seen, (uint64_t)value.i64, &full) : seen_new(&fold->seen, value.num, &full);
fold->any = true;
return !full;
}
- fold->total = how == PURE_FOLD_COUNT ? fold->total + 1 : how == PURE_FOLD_SUM ? fold->total + value :
- !fold->any ? value : how == PURE_FOLD_MAX ? maximum(fold->total, value) : minimum(fold->total, value);
+ if (fold->integer) { /* an i64's sum, most or least */
+ int64_t v = value.i64;
+ if (how == PURE_FOLD_SUM) {
+ if (__builtin_add_overflow(fold->whole, v, &fold->whole)) fold->overflow = fold->done = true;
+ } else if (!fold->any || (how == PURE_FOLD_MAX ? v > fold->whole : v < fold->whole))
+ fold->whole = v;
+ fold->any = true;
+ return true;
+ }
+ double number = value.num;
+ fold->total = how == PURE_FOLD_COUNT ? fold->total + 1 : how == PURE_FOLD_SUM ? fold->total + number :
+ !fold->any ? number : how == PURE_FOLD_MAX ? maximum(fold->total, number) : minimum(fold->total, number);
fold->any = true;
return true;
}
@@ -1905,7 +1998,7 @@ static PureResult fold_rows(const PureModule *m, PureValue *values, PureBlock in
PureResult status = run(m, values, inner, per, out);
per->arena->used = mark;
if (status != PURE_OK) return status;
- if (!fold_in(fold, row, out[0].num, one ? out[0].num : out[1].num)) return PURE_ALLOCATION;
+ if (!fold_in(fold, row, (PureValue){.i64 = out[0].i64}, one ? out[0].num : out[1].num)) return PURE_ALLOCATION;
}
return PURE_OK;
}
@@ -1950,16 +2043,17 @@ static PureResult fold_runs(const PureModule *m, PureValue *values, PureBlock in
fold->any = any;
} else
for (unsigned i = first; i < last && !fold->done; ++i)
- if (!fold_in(fold, items.start + i, v[i], c[i])) return PURE_ALLOCATION;
+ if (!fold_in(fold, items.start + i, (PureValue){.num = v[i]}, c[i])) return PURE_ALLOCATION;
}
return PURE_OK;
}
/* The positions of the items whose keys are from low up to before high. */
-static void slice_of(const PureList *list, unsigned key, double low, double high, size_t *from, size_t *to) {
+/* The items whose keys are from low up to before high (numbers or i64s, as the key is). */
+static void slice_of(const PureList *list, unsigned key, Slot low, Slot high, size_t *from, size_t *to) {
bool found;
- *from = pure_list_seek(list, key, (PureCell){.num = low}, &found);
- *to = pure_list_seek(list, key, (PureCell){.num = high}, &found);
+ *from = pure_list_seek(list, key, (PureCell){.i64 = low.i64}, &found);
+ *to = pure_list_seek(list, key, (PureCell){.i64 = high.i64}, &found);
if (*to < *from) *to = *from;
}
@@ -2104,7 +2198,7 @@ static Group *group_of(struct PurePass *pass, double key, unsigned how, bool add
}
size_t at = group_slot(order_bits(key), pass->capacity);
while (pass->groups[at].used) at = (at + 1) & (pass->capacity - 1);
- pass->groups[at] = (Group){key, {how, 0, false, false, {0}}, true};
+ pass->groups[at] = (Group){key, {.how = how}, true};
++pass->count;
return &pass->groups[at];
}
@@ -2128,7 +2222,7 @@ static bool group_pass(const PureModule *m, PureValue *values, PureBlock inner,
if (c[i] == 0) continue;
Group *group = group_of(pass, keys[i], how, true);
if (!group) return false;
- if (!group->fold.done && !fold_in(&group->fold, items.start + i, v[i], c[i])) return false;
+ if (!group->fold.done && !fold_in(&group->fold, items.start + i, (PureValue){.num = v[i]}, c[i])) return false;
}
}
return true;
@@ -2225,8 +2319,8 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
uint32_t arg = op->arg;
Slot *top = sp ? &stack[sp - 1] : stack;
switch (op->op) {
- case PURE_OP_CONST: stack[sp++].num = op->value; continue;
- case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; continue; /* the bits, a number's or a bool's */
+ case PURE_OP_CONST: stack[sp++].i64 = op->integer; continue; /* the bits: a number's, a bool's or an i64's */
+ case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; continue;
case PURE_OP_DT: stack[sp++].num = ev->dt; continue;
case PURE_OP_NOW: stack[sp++].num = app->now; continue;
case PURE_OP_FLOOR: top->num = floor(top->num); continue;
@@ -2247,6 +2341,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
if (field->type == PURE_FIELD_U32) stack[sp++].num = read32(given + field->offset);
else if (field->type == PURE_FIELD_NUMBER) stack[sp++].num = read_double(given + field->offset);
else if (field->type == PURE_FIELD_BOOL) stack[sp++].num = given[field->offset];
+ else if (field->type == PURE_FIELD_I64) stack[sp++].i64 = read_i64(given + field->offset);
else stack[sp++].bytes = (PureBytes){ev->arguments ? FROM_ARGUMENTS : FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0],
ev->spans[arg][1]};
continue;
@@ -2286,7 +2381,8 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
case PURE_OP_TEXT_INPUT: stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, 0, app->payload.size}; continue;
case PURE_OP_NUM_TEXT: {
char digits[32];
- size_t length = arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false")
+ size_t length = arg == 2 ? (size_t)snprintf(digits, sizeof(digits), "%" PRId64, top->i64)
+ : arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false")
: pure_format_number(top->num, digits);
if ((status = keep_bytes(arena, digits, length, &top->bytes)) != PURE_OK) return status;
continue;
@@ -2325,10 +2421,10 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
stack[sp++].bytes.owner = (uint16_t)arg;
continue;
case PURE_OP_LOCAL_LOAD:
- stack[sp].num = ev->locals[arg & 0xFF];
+ stack[sp].i64 = ev->locals[arg & 0xFF].i64; /* the bits: a number's, an index's, a bool's or an i64's */
stack[sp++].bytes.owner = (uint16_t)(arg >> 16); /* an index's list */
continue;
- case PURE_OP_LOCAL_STORE: ev->locals[arg & 0xFF] = stack[--sp].num; continue;
+ case PURE_OP_LOCAL_STORE: ev->locals[arg & 0xFF].i64 = stack[--sp].i64; continue;
case PURE_OP_CUR_MOVE: case PURE_OP_INDEX_MOVE: {
double count = stack[--sp].num;
Slot *at = &stack[sp - 1];
@@ -2449,7 +2545,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
if (at >= pure_list_count(app->lists[list])) { kind = PURE_FAULT_NOT_FOUND; goto failed; }
PureCell cell = pure_list_get(app->lists[list], at, field);
/* A text is copied: the item may change or go before the value is used. */
- if (!text_field(m, list, field)) top->num = cell.num;
+ if (!text_field(m, list, field)) top->i64 = cell.i64; /* the bits: a number's, a bool's or an i64's */
else if ((status = keep_bytes(arena, cell.text, cell.size, &top->bytes)) != PURE_OK) return status;
continue;
}
@@ -2483,12 +2579,12 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
PureBlock inner = {pc, arg >> 16};
PureList *list = app->lists[arg >> 8 & 0xFF];
pc += inner.length;
- Fold fold = {arg & 0x7F, 0, false, false, {0}};
+ Fold fold = {.how = arg & PURE_FOLD_KIND, .integer = arg & PURE_FOLD_INTEGER};
Event per = *ev;
per.outer = ev->row;
size_t from = 0, to = pure_list_count(list);
if (arg & PURE_FOLD_SLICED) { /* the items with keys from low up to before high */
- double high = stack[--sp].num, low = stack[--sp].num;
+ Slot high = stack[--sp], low = stack[--sp];
slice_of(list, m->state_key[arg >> 8 & 0xFF] - 1u, low, high, &from, &to);
}
if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) {
@@ -2499,12 +2595,14 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
free(fold.seen.keys);
if (status != PURE_OK) return status;
}
+ if (fold.overflow) { kind = PURE_FAULT_OVERFLOW; goto failed; }
if (!fold.any && (fold.how == PURE_FOLD_MAX || fold.how == PURE_FOLD_MIN || fold.how == PURE_FOLD_FIRST)) {
kind = PURE_FAULT_NOT_FOUND;
goto failed;
}
- if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; }
- stack[sp].num = fold.total;
+ if (fold.integer && fold.how != PURE_FOLD_DISTINCT) stack[sp].i64 = fold.whole;
+ else if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; }
+ else stack[sp].num = fold.total;
stack[sp++].bytes.owner = (uint16_t)(arg >> 8 & 0xFF); /* first: the list the index is in */
continue;
}
@@ -2517,7 +2615,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
Event per = *ev;
per.walk = &walk;
if (arg & PURE_EACH_SLICED) { /* the items with keys from low up to before high */
- double high = stack[--sp].num, low = stack[--sp].num;
+ Slot high = stack[--sp], low = stack[--sp];
slice_of(app->lists[walk.list], m->state_key[walk.list] - 1u, low, high, &walk.at, &walk.end);
} else if (op->range) /* a where that bounds the key: only the items in bounds */
range_bounds(m, values, &m->range[op->range - 1], app->lists[walk.list], m->state_key[walk.list] - 1u, &per,
@@ -2547,9 +2645,7 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
int order = shorter ? memcmp(a.data, b.data, shorter) : 0;
if (!order) order = (a.size > b.size) - (a.size < b.size);
arena->used = mark;
- stack[sp - 1].num = arg == PURE_LESS ? order < 0 : arg == PURE_LESS_EQUAL ? order <= 0 :
- arg == PURE_GREATER ? order > 0 : arg == PURE_GREATER_EQUAL ? order >= 0 :
- arg == PURE_EQUAL ? order == 0 : order != 0;
+ stack[sp - 1].num = related(arg, order);
continue;
}
case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: {
@@ -2573,6 +2669,52 @@ static PureResult run(const PureModule *m, PureValue *values, PureBlock block, E
}
case PURE_OP_PICK: stack[sp] = stack[sp - 1 - arg]; ++sp; continue;
case PURE_OP_SLIDE: stack[sp - 1 - arg] = stack[sp - 1]; sp -= arg; continue;
+ /* i64s: exact, and failing as OVERFLOW past their range */
+ case PURE_OP_I64_ADD: case PURE_OP_I64_SUB: case PURE_OP_I64_MUL: {
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64, r;
+ bool over = op->op == PURE_OP_I64_ADD ? __builtin_add_overflow(a, b, &r) :
+ op->op == PURE_OP_I64_SUB ? __builtin_sub_overflow(a, b, &r) : __builtin_mul_overflow(a, b, &r);
+ if (over) { kind = PURE_FAULT_OVERFLOW; goto failed; }
+ stack[sp - 1].i64 = r;
+ continue;
+ }
+ case PURE_OP_I64_DIV: case PURE_OP_I64_MOD: { /* floored: the remainder takes the divisor's sign */
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
+ if (b == 0) { kind = PURE_FAULT_DIVIDE; goto failed; }
+ if (b == -1) { /* the least i64 over -1 is past the range; any remainder by -1 is 0 */
+ if (op->op == PURE_OP_I64_DIV && a == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; goto failed; }
+ stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? -a : 0;
+ continue;
+ }
+ int64_t q = a / b, r = a % b;
+ if (r != 0 && (r < 0) != (b < 0)) { --q; r += b; }
+ stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? q : r;
+ continue;
+ }
+ case PURE_OP_I64_NEG:
+ if (top->i64 == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; goto failed; }
+ top->i64 = -top->i64;
+ continue;
+ case PURE_OP_I64_MIN: case PURE_OP_I64_MAX: {
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
+ stack[sp - 1].i64 = (op->op == PURE_OP_I64_MIN ? b < a : b > a) ? b : a;
+ continue;
+ }
+ case PURE_OP_I64_COMPARE: {
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
+ stack[sp - 1].num = related(arg, (a > b) - (a < b));
+ continue;
+ }
+ case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM: { /* a whole number in range; an i64 within 2^53 */
+ PureCell cell = {.i64 = top->i64};
+ bool to_i64 = op->op == PURE_OP_I64_FROM;
+ if (!pure_cell_as(to_i64 ? PURE_CELL_NUMBER : PURE_CELL_INTEGER, to_i64 ? PURE_CELL_INTEGER : PURE_CELL_NUMBER, &cell)) {
+ kind = PURE_FAULT_LIMIT;
+ goto failed;
+ }
+ top->i64 = cell.i64;
+ continue;
+ }
default: break;
}
double b = stack[--sp].num, a = stack[sp - 1].num, value;
@@ -2623,7 +2765,10 @@ PURE_COLD static PureResult new_text(PureDraft **out, const PureModule *m, uint3
}
PURE_COLD static PureList *new_list(const PureModule *m, uint32_t record) {
- return pure_list_new(m->record[record].count, m->record_field_text + m->record[record].first);
+ const PureRecord *r = &m->record[record];
+ uint8_t kinds[PURE_RECORD_FIELDS];
+ for (unsigned f = 0; f < r->count; ++f) kinds[f] = pure_record_kind(m, r->first + f);
+ return pure_list_new(r->count, kinds);
}
/* Where key is in the pending index: its slot, or the empty one it would take. */
@@ -2944,8 +3089,7 @@ size_t pure_app_state_bytes(const PureApp *app) {
* holding more than before, fails. */
static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) {
const PureModule *m = app->module;
- PureValue pending[PURE_MODULE_STATES];
- double locals[PURE_MODULE_LOCALS] = {0};
+ PureValue pending[PURE_MODULE_STATES], locals[PURE_MODULE_LOCALS] = {{0}};
ev->locals = locals;
size_t held = app->memory ? pure_app_state_bytes(app) : 0;
Mark mark;
@@ -3387,6 +3531,13 @@ bool pure_app_view(PureApp *app, uint32_t view, double *out) {
return true;
}
+bool pure_app_view_i64(PureApp *app, uint32_t view, int64_t *out) {
+ Slot slot;
+ if (view_of(app, view, false, 0, PURE_VIEW_I64, &slot) != PURE_OK) return false;
+ *out = slot.i64;
+ return true;
+}
+
PureResult pure_app_view_text(PureApp *app, uint32_t view, bool (*visit)(void *, PureSpan), void *context) {
Slot slot;
PureResult result = view_of(app, view, false, 0, PURE_VIEW_TEXT, &slot);
@@ -3407,6 +3558,13 @@ bool pure_app_cell(PureApp *app, uint32_t column, size_t row, double *out) {
return true;
}
+bool pure_app_cell_i64(PureApp *app, uint32_t column, size_t row, int64_t *out) {
+ Slot slot;
+ if (view_of(app, column, true, row, PURE_VIEW_I64, &slot) != PURE_OK) return false;
+ *out = slot.i64;
+ return true;
+}
+
PureResult pure_app_cell_text(PureApp *app, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context) {
Slot slot;
PureResult result = view_of(app, column, true, row, PURE_VIEW_TEXT, &slot);
@@ -3504,12 +3662,14 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
}
uint8_t pure_record_kind(const PureModule *m, uint32_t field) {
- return m->record_field_bool[field] ? PURE_RECORD_BOOL : m->record_field_text[field];
+ static const uint8_t kinds[] = {[PURE_RECORD_NUMBER] = PURE_CELL_NUMBER, [PURE_RECORD_TEXT] = PURE_CELL_TEXT,
+ [PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER};
+ return kinds[m->record_field_type[field]];
}
-/* A number and a bool carry over each other; texts only to texts. */
+/* A number, a bool and an i64 carry over each other (when the value fits); texts only to texts. */
static bool kinds_carry(uint8_t from, uint8_t to) {
- return from == to || ((from == 0 || from == PURE_RECORD_BOOL) && (to == 0 || to == PURE_RECORD_BOOL));
+ return from == to || (!pure_cell_text(from) && !pure_cell_text(to));
}
/* A list with record's fields: each takes the field of `from` with the same
@@ -3530,13 +3690,12 @@ PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, con
PureCell cells[PURE_RECORD_FIELDS];
for (unsigned f = 0; f < r->count; ++f) {
uint32_t field = r->first + f, constant = (uint32_t)m->record_field_default[field].num;
- cells[f] = source[f] >= 0 ? pure_list_get(from, at, (unsigned)source[f]) :
- m->record_field_text[field] ?
- (PureCell){.text = (unsigned char *)m->constant_data + m->constant_offset[constant],
- .size = m->constant_length[constant]} :
- (PureCell){.i64 = m->record_field_default[field].i64};
- if (m->record_field_bool[field])
- cells[f] = (PureCell){.num = pure_as_bool(cells[f].num, m->record_field_default[field].num)};
+ PureCell given = source[f] >= 0 ? pure_list_get(from, at, (unsigned)source[f]) : (PureCell){0};
+ if (source[f] >= 0 && pure_cell_as(kinds[source[f]], pure_record_kind(m, field), &given)) cells[f] = given;
+ else if (m->record_field_type[field] == PURE_RECORD_TEXT)
+ cells[f] = (PureCell){.text = (unsigned char *)m->constant_data + m->constant_offset[constant],
+ .size = m->constant_length[constant]};
+ else cells[f] = (PureCell){.i64 = m->record_field_default[field].i64}; /* a number's, a bool's or an i64's bits */
}
result = pure_list_insert(*out, at, cells, NULL);
}
@@ -3698,10 +3857,10 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
(app->outbox = pure_runtime_list_new(PURE_LIST_OUTBOX)) ? PURE_OK : PURE_ALLOCATION;
for (uint32_t i = 0; i < module->states; ++i) {
int previous = old ? pure_module_state(old, module->state_names[i]) : -1;
- /* A number and a bool carry over each other: 0 and 1 are false and true (a bool's other numbers start anew). */
+ /* A number, a bool and an i64 carry over each other when the value fits (pure_cell_as), else start anew. */
uint8_t was = previous >= 0 ? old->state_type[previous] : 0, now = module->state_type[i];
- bool truth = (was == PURE_STATE_NUM && now == PURE_STATE_BOOL) || (was == PURE_STATE_BOOL && now == PURE_STATE_NUM);
- kept[i] = previous >= 0 && (was == now || truth) ? previous : -1;
+ bool valued = pure_value_kind(was) != 0xFF && pure_value_kind(now) != 0xFF;
+ kept[i] = previous >= 0 && (was == now || valued) ? previous : -1;
if (kept[i] >= 0) saved[i] = app->saved[kept[i]];
if (module->state_type[i] == PURE_STATE_TEXT) {
if (kept[i] >= 0) texts[i] = app->texts[kept[i]];
@@ -3722,8 +3881,11 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
result = pure_save_open_list(app, module, i, &lists[i], &list_drafts[i], &listed[i]);
rewrite[i] = result == PURE_OK && listed[i].rewrite;
} else if (!(lists[i] = new_list(module, module->state_ref[i]))) result = PURE_ALLOCATION;
+ } else if (kept[i] >= 0 && valued) {
+ PureCell cell = {.i64 = app->values[kept[i]].i64};
+ values[i] = pure_cell_as(pure_value_kind(was), pure_value_kind(now), &cell) ? (PureValue){.i64 = cell.i64}
+ : module->initial[i];
} else values[i] = kept[i] >= 0 ? app->values[kept[i]] : module->initial[i];
- if (module->state_type[i] == PURE_STATE_BOOL) values[i].num = pure_as_bool(values[i].num, module->initial[i].num);
}
/* A keyed list keeps its items in key order: one that was not keyed, or
* keyed by another field, is put in order and its indices follow their
diff --git a/core/module.h b/core/module.h
index 10fe6c3..e106fc7 100644
--- a/core/module.h
+++ b/core/module.h
@@ -40,6 +40,8 @@ typedef enum {
PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_FOLD, PURE_OP_TEXT_COMPARE, PURE_OP_LIST_FIND, PURE_OP_LIST_HAS,
PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_SEND, PURE_OP_OUTER_ROW, PURE_OP_EACH,
PURE_OP_LOCAL_LOAD, PURE_OP_LOCAL_STORE,
+ PURE_OP_I64_ADD, PURE_OP_I64_SUB, PURE_OP_I64_MUL, PURE_OP_I64_DIV, PURE_OP_I64_MOD, PURE_OP_I64_NEG,
+ PURE_OP_I64_COMPARE, PURE_OP_I64_MIN, PURE_OP_I64_MAX, PURE_OP_I64_FROM, PURE_OP_NUM_FROM,
PURE_OP_COUNT
} PureOpcode;
/* A bool is its own type (docs/design/016-bool-and-i64.md), held as a
@@ -48,7 +50,15 @@ typedef enum {
* and EQ and NE compare two bools as they compare two numbers. CONST with
* argument 1 pushes a bool (its value 0 or 1); LOAD and STORE work on a
* number or a bool state as its type says; NUM_TEXT with argument 1 makes
- * a bool text (true, false). */
+ * a bool text (true, false).
+ * An i64 is its own type too, an int64_t in a value's 8 bytes: CONST with
+ * argument 2 pushes one (its constant's 8 bytes), NUM_TEXT with argument 2
+ * makes its decimal text, the I64_ opcodes compute on them (ADD, SUB, MUL
+ * and NEG fail as OVERFLOW past its range; DIV and MOD floor, and fail as
+ * DIVIDE by zero; COMPARE takes a relation as TEXT_COMPARE does and gives a
+ * bool), I64_FROM makes one of a whole number in range and NUM_FROM a
+ * number of one within 2^53 (else LIMIT). LOAD, STORE, the item and local
+ * opcodes, FIELD and SEND move them as they move numbers. */
/* A local: one of an event's PURE_MODULE_LOCALS slots, 0 when the event
* begins and never saved. LOCAL_LOAD and LOCAL_STORE name the slot (bits
* 0-7), what it holds (bits 8-15: 0 a number, 1 an index, 2 a bool) and an index's
@@ -61,13 +71,16 @@ enum { PURE_MODULE_LOCALS = 64 };
* when it began, in order, with ROW the item; the block may change states,
* the list's items and other lists, but not insert into the list it walks
* (an item it removes, or one before it, moves the walk with it). */
-/* FOLD's argument: the kind (bits 0-7), the list state (8-15) and the length
- * of the block that follows (16-31), run once per item as a column of that
- * list. count counts the items whose block leaves true; sum, max, min and
- * distinct take a number and whether to include it (a bool) from their
- * block (distinct counts the different values, -0 as 0); first gives the
+/* FOLD's argument: the kind (bits 0-5, with flags above: an i64's, a
+ * slice's), the list state (8-15) and the length of the block that follows
+ * (16-31), run once per item as a column of that list. count counts the
+ * items whose block leaves true; sum, max, min and distinct take a value
+ * and whether to include it (a bool) from their block, the value a number
+ * or, with PURE_FOLD_INTEGER, an i64 (a sum past its range fails as
+ * OVERFLOW; distinct counts the different values, -0 as 0); first gives the
* index of the first item whose block leaves true. */
typedef enum { PURE_FOLD_COUNT, PURE_FOLD_SUM, PURE_FOLD_MAX, PURE_FOLD_MIN, PURE_FOLD_FIRST, PURE_FOLD_DISTINCT } PureFold;
+enum { PURE_FOLD_KIND = 0x3F, PURE_FOLD_INTEGER = 0x40 };
/* A slice: a fold whose kind has PURE_FOLD_SLICED, or a walk whose argument
* has PURE_EACH_SLICED, first takes two numbers, a low and a high key, and
* runs for the items of its list (keyed by a number) whose keys are from
@@ -89,8 +102,8 @@ typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3, PUR
enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 };
/* The largest number a delivery may have: it is kept as a binary64. */
#define PURE_SEQ_MOST (UINT64_C(1) << 53)
-/* A message field: a u32le, a binary64 or a bool's one byte (fixed, at its
- * offset in the payload's prefix), or bytes, text or rows of any length. The
+/* A message field: a u32le, a binary64, a bool's one byte or an i64's eight
+ * (fixed, at its offset in the payload's prefix), or bytes, text or rows of any length. The
* prefix also holds each variable field's u32le length but the last one's
* (its offset is PURE_FIELD_REST), which takes what follows the others; the
* variable fields' bytes follow the prefix in declaration order. A text is
@@ -99,22 +112,27 @@ enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 };
* list's stored form: a message fills that list with them, and fails when
* they are not whole. */
typedef enum { PURE_FIELD_U32 = 1, PURE_FIELD_BYTES = 2, PURE_FIELD_NUMBER = 3, PURE_FIELD_TEXT = 4,
- PURE_FIELD_ROWS = 5, PURE_FIELD_BOOL = 6 } PureFieldType;
+ PURE_FIELD_ROWS = 5, PURE_FIELD_BOOL = 6, PURE_FIELD_I64 = 7 } PureFieldType;
enum { PURE_FIELD_REST = UINT32_MAX };
-/* A field held in its payload's prefix (a u32, a number, a bool), and the bytes it takes there. */
+/* A field held in its payload's prefix (a u32, a number, a bool, an i64), and the bytes it takes there. */
static inline bool pure_field_fixed(uint8_t type) {
- return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL;
+ return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL || type == PURE_FIELD_I64;
}
static inline uint32_t pure_field_size(uint8_t type) {
- return type == PURE_FIELD_NUMBER ? 8 : type == PURE_FIELD_BOOL ? 1 : 4;
+ return type == PURE_FIELD_NUMBER || type == PURE_FIELD_I64 ? 8 : type == PURE_FIELD_BOOL ? 1 : 4;
}
-/* A number as a bool: 0 (of either sign) is false, 1 true, any other the
- * default given. A state or field that becomes a bool keeps its value so. */
-static inline double pure_as_bool(double value, double otherwise) { return value == 0 ? 0 : value == 1 ? 1 : otherwise; }
typedef enum {
PURE_STATE_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4,
- PURE_STATE_BOOL = 5
+ PURE_STATE_BOOL = 5, PURE_STATE_I64 = 6
} PureStateType;
+/* A number's, a bool's or an i64's state as a list cell's kind (PURE_CELL_...,
+ * for pure_cell_as), or 0xFF for any other state. */
+static inline uint8_t pure_value_kind(uint8_t state_type) {
+ return state_type == PURE_STATE_NUM ? PURE_CELL_NUMBER : state_type == PURE_STATE_BOOL ? PURE_CELL_BOOL :
+ state_type == PURE_STATE_I64 ? PURE_CELL_INTEGER : 0xFF;
+}
+/* A record field: a number, a text (its default a constant), a bool or an i64. */
+typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3 } PureRecordType;
/* A value's 8 bytes: a number (a bool's 0 or 1, a cursor's or an index's
* position too), or an integer. What a value is its state's or field's type
* says, and the verifier keeps to it: moves copy the bytes, and only what
@@ -127,19 +145,23 @@ typedef union { double num; int64_t i64; } PureValue;
* every event ends, and it is never saved nor logged for undoing. */
enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256 };
/* A table view has one row per item of a list, or per item its filter
- * keeps; its columns are number, bool or text views evaluated for one row at
- * a time. */
-typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3 } PureViewType;
+ * keeps; its columns are number, bool, i64 or text views evaluated for one
+ * row at a time. */
+typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3, PURE_VIEW_I64 = 4 } PureViewType;
typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit;
typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
/* EXISTS: an item with that key is already in the keyed list (the event
* reports PURE_CONFLICT). MEMORY: the event would leave the states holding
- * more than the app's budget (pure_app_memory; it reports PURE_BUDGET). */
+ * more than the app's budget (pure_app_memory; it reports PURE_BUDGET).
+ * OVERFLOW: an i64 past its range. */
typedef enum {
PURE_FAULT_NONE, PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP,
PURE_FAULT_LIMIT, PURE_FAULT_NOT_FOUND, PURE_FAULT_ENCODING, PURE_FAULT_EXISTS,
- PURE_FAULT_MEMORY
+ PURE_FAULT_MEMORY, PURE_FAULT_OVERFLOW, PURE_FAULT_COUNT
} PureFault;
+/* A fault's name as hosts report it: none, divide, non_finite, clamp, limit,
+ * not_found, encoding, exists, memory, overflow. */
+const char *pure_fault_name(PureFault);
/* plan, shape and range are the runtime's, set at load: a fold whose block
* has a form for every instruction that works on a leaf of its list at once
@@ -221,9 +243,8 @@ typedef struct {
char record_names[PURE_MODULE_RECORDS][PURE_MODULE_NAME];
PureRecord record[PURE_MODULE_RECORDS];
char record_field_names[PURE_MODULE_RECORD_FIELDS][PURE_MODULE_NAME];
- uint8_t record_field_text[PURE_MODULE_RECORD_FIELDS];
- uint8_t record_field_bool[PURE_MODULE_RECORD_FIELDS]; /* a bool: a number field (0 or 1) as lists go */
- PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), or a text constant's index */
+ uint8_t record_field_type[PURE_MODULE_RECORD_FIELDS]; /* PureRecordType */
+ PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, or a text constant's index */
} PureModule;
/* Decode and verify into out, which must be zeroed (a static one is) or hold
@@ -448,8 +469,11 @@ PureResult pure_app_message_fields(PureApp *, uint32_t input, const char *key, i
PureResult pure_app_reply(const PureApp *, bool (*visit)(void *, PureSpan), void *context);
void pure_app_finish(PureApp *);
/* A number view, or a bool view (0 or 1: its type says which); false for a
- * text view or a failed evaluation. */
+ * text or i64 view or a failed evaluation. */
bool pure_app_view(PureApp *, uint32_t view, double *out);
+/* An i64 view, and an i64 column's cell; false for any other or a failed evaluation. */
+bool pure_app_view_i64(PureApp *, uint32_t view, int64_t *out);
+bool pure_app_cell_i64(PureApp *, uint32_t column, size_t row, int64_t *out);
/* A text view, lent in order until the next view evaluation. */
PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, PureSpan), void *context);
/* The rows of a table view (its list's items, or those its filter keeps),
diff --git a/core/test-module.c b/core/test-module.c
index 131d9d9..4fe5182 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -6,6 +6,7 @@
#include "store.h"
#include <dirent.h>
#include <fcntl.h>
+#include <inttypes.h>
#ifdef __GLIBC__
#include <malloc.h>
#endif
@@ -36,18 +37,21 @@ typedef struct { const char *name, *key; int kind; bool keyed; const F *fields;
#define KEY(name, key) {name, key, 0, false, NULL, 0}
/* A timer's period (milliseconds) rides in nfields. */
#define TIMER(name, period) {name, NULL, PURE_KIND_TIMER, false, NULL, period}
-/* A number view, or a bool view when its length carries BOOL_VIEW. */
+/* A number view, or a bool (i64) view when its length carries BOOL_VIEW (I64_VIEW). */
typedef struct { const char *name; uint32_t start, length; } V;
#define BOOL_VIEW (1u << 31)
+#define I64_VIEW (1u << 30)
typedef struct { uint32_t event, start, length; } H;
typedef struct { uint8_t op; uint32_t arg; double value; } O;
/* Text (type 1, ref = constant), cursor (type 2, ref = text state), list
- * (type 3, ref = record), index (type 4, ref = list state) and bool (type 5,
- * ref = its initial value) states follow the number states; text views
+ * (type 3, ref = record), index (type 4, ref = list state), bool (type 5,
+ * ref = its initial value) and i64 (type 6, ref = its initial value, a
+ * 32-bit two's complement) states follow the number states; text views
* follow the number views. */
typedef struct { const char *name; uint32_t type, ref; } T; /* type: the state type, its flags from bit 8 */
/* A record field: a number and its default, text (text = 1) whose default
- * is the constant numbered initial, or a bool (text = 2) and its default. */
+ * is the constant numbered initial, a bool (text = 2) or an i64 (text = 3)
+ * and its default. */
typedef struct { const char *name; uint32_t text; double initial; } RF;
typedef struct { const char *name; const RF *fields; size_t nfields; } R;
/* A further view: a table (type 2, start = list state) or a column of the
@@ -125,6 +129,12 @@ static size_t build(unsigned char *out, Spec spec) {
p = put32(p, offset[n++]);
p = put32(p, spec.t[i].type & 0xFF);
if ((spec.t[i].type & 0xFF) == PURE_STATE_BOOL) { p = put_double(p, spec.t[i].ref); continue; }
+ if ((spec.t[i].type & 0xFF) == PURE_STATE_I64) {
+ int64_t initial = (int32_t)spec.t[i].ref;
+ p = put32(p, (uint32_t)(uint64_t)initial);
+ p = put32(p, (uint32_t)((uint64_t)initial >> 32));
+ continue;
+ }
p = put32(p, spec.t[i].ref);
p = put32(p, spec.t[i].type >> 8);
}
@@ -165,13 +175,18 @@ static size_t build(unsigned char *out, Spec spec) {
p = put32(p, offset[++n]);
p = put32(p, field->text);
if (field->text == 1) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); }
+ else if (field->text == 3) {
+ int64_t initial = (int64_t)field->initial;
+ p = put32(p, (uint32_t)(uint64_t)initial);
+ p = put32(p, (uint32_t)((uint64_t)initial >> 32));
+ }
else p = put_double(p, field->initial);
}
for (size_t i = 0; i < spec.nv; ++i) {
p = put32(p, offset[n++]);
- p = put32(p, spec.v[i].length & BOOL_VIEW ? PURE_VIEW_BOOL : 0);
+ p = put32(p, spec.v[i].length & BOOL_VIEW ? PURE_VIEW_BOOL : spec.v[i].length & I64_VIEW ? PURE_VIEW_I64 : 0);
p = put32(p, spec.v[i].start);
- p = put32(p, spec.v[i].length & ~BOOL_VIEW);
+ p = put32(p, spec.v[i].length & ~(BOOL_VIEW | I64_VIEW));
}
for (size_t i = 0; i < spec.ntv; ++i) { p = put32(p, offset[n++]); p = put32(p, 1); p = put32(p, spec.tv[i].start); p = put32(p, spec.tv[i].length); }
for (size_t i = 0; i < spec.nw; ++i) {
@@ -649,7 +664,7 @@ static void messages(void) {
static const H note_handler[] = {{1, 0, 2}};
bad = (Spec){states, 1, unkeyed, 1, views, 0, note_handler, 1, doc_read, 2, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "document operation outside"));
- static const F unknown_type[] = {{"insert", 7}, {"start", 1}};
+ static const F unknown_type[] = {{"insert", 8}, {"start", 1}};
static const I misplaced[] = {{"edit", NULL, PURE_KIND_MESSAGE, true, unknown_type, 2}};
bad = (Spec){states, 1, misplaced, 1, views, 0, NULL, 0, NULL, 0, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "message field"));
@@ -1473,7 +1488,7 @@ static void list_mutations(void) {
for (size_t at = 0; at < pure_list_count(app.lists[s]); ++at)
for (unsigned f = 0; f < record->count; ++f) {
PureCell cell = pure_list_get(app.lists[s], at, f);
- if (module_b.record_field_text[record->first + f])
+ if ((module_b.record_field_type[record->first + f] == PURE_RECORD_TEXT))
CHECK(pure_utf8_valid((PureSpan){cell.text, cell.size}));
else CHECK(isfinite(cell.num));
}
@@ -3567,7 +3582,7 @@ static void keyed_mutations(void) {
}
if (module_b.state_type[s] != PURE_STATE_LIST || !module_b.state_key[s]) continue;
unsigned key = module_b.state_key[s] - 1u;
- bool text = module_b.record_field_text[module_b.record[module_b.state_ref[s]].first + key];
+ bool text = module_b.record_field_type[module_b.record[module_b.state_ref[s]].first + key] == PURE_RECORD_TEXT;
for (size_t at = 1; at < pure_list_count(app.lists[s]); ++at) {
PureCell a = pure_list_get(app.lists[s], at - 1, key), b = pure_list_get(app.lists[s], at, key);
if (!text) { CHECK(a.num < b.num); continue; }
@@ -3611,7 +3626,7 @@ static void describe(PureApp *app, Shown *s) {
for (size_t at = 0; at < pure_list_count(app->lists[i]); ++at)
for (unsigned f = 0; f < r->count; ++f) {
PureCell cell = pure_list_get(app->lists[i], at, f);
- if (m->record_field_text[r->first + f]) show_span(s, (PureSpan){cell.text, cell.size});
+ if (m->record_field_type[r->first + f] == PURE_RECORD_TEXT) show_span(s, (PureSpan){cell.text, cell.size});
else show(s, "%g", cell.num);
show(s, f + 1 < r->count ? "," : ";", 0);
}
@@ -4656,7 +4671,7 @@ static void bools(void) {
static const O two[] = {{C, 1, 2}, {ST, B_ON, 0}};
static const O minus_zero[] = {{C, 1, -0.0}, {ST, B_ON, 0}};
static const O half[] = {{C, 1, 0.5}, {ST, B_ON, 0}};
- static const O kind_two[] = {{C, 2, 1}, {ST, B_ON, 0}};
+ static const O kind_three[] = {{C, 3, 1}, {ST, B_ON, 0}};
static const struct { const O *code; size_t count; uint32_t event; const char *reason; } refused[] = {
{add_bools, LEN(add_bools), 1, "operand type"},
{branch_number, LEN(branch_number), 1, "operand type"},
@@ -4676,7 +4691,7 @@ static void bools(void) {
{two, LEN(two), 1, "non-canonical operand"},
{minus_zero, LEN(minus_zero), 1, "non-canonical operand"},
{half, LEN(half), 1, "non-canonical operand"},
- {kind_two, LEN(kind_two), 1, "operand names no such"},
+ {kind_three, LEN(kind_three), 1, "operand names no such"},
};
for (size_t i = 0; i < LEN(refused); ++i) {
H handler = {refused[i].event, 0, (uint32_t)refused[i].count};
@@ -4690,7 +4705,7 @@ static void bools(void) {
}
static const O one_number[] = {{C, 0, 1}}, one_bool[] = {{C, 1, 1}};
static const O text_of_number[] = {{C, 0, 1}, {NT, 1, 0}}, text_of_bool[] = {{C, 1, 1}, {NT, 0, 0}},
- text_kind[] = {{C, 1, 1}, {NT, 2, 0}};
+ text_kind[] = {{C, 1, 1}, {NT, 3, 0}};
V as_bool = {"v", 0, 1 | BOOL_VIEW}, as_number = {"v", 0, 1}, as_text = {"v", 0, 2};
Spec bad = spec;
bad.h = NULL, bad.nh = 0, bad.tv = NULL, bad.ntv = 0, bad.no = 1, bad.nv = 1;
@@ -4706,20 +4721,20 @@ static void bools(void) {
bad.o = text_kind;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand names no such"));
/* A bool's initial value and default are 0 or 1; types past bool are none. */
- static const T two_initial[] = {{"on", 5, 2}, {"off", 5, 0}}, sixth[] = {{"on", 6, 0}, {"off", 5, 0}};
+ static const T two_initial[] = {{"on", 5, 2}, {"off", 5, 0}}, seventh[] = {{"on", 7, 0}, {"off", 5, 0}};
static const RF two_default[] = {{"done", 2, 2}};
static const R two_record[] = {{"r", two_default, 1}};
static const T with_list[] = {{"on", 5, 1}, {"off", 5, 0}, {"rs", 3, 0}};
- static const W fourth_view[] = {{"x", 4, 0, 25, 1}};
+ static const W fifth_view[] = {{"x", 5, 0, 25, 1}};
bad = spec;
bad.t = two_initial;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "bool initial value"));
- bad.t = sixth;
+ bad.t = seventh;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "state type"));
bad.t = with_list, bad.nt = 3, bad.r = two_record, bad.nr = 1;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "record field default"));
bad = spec;
- bad.w = fourth_view, bad.nw = 1;
+ bad.w = fifth_view, bad.nw = 1;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view type"));
/* Stored: n and the items' done, numbers, become bools (1 kept, 2 and 3
@@ -4749,6 +4764,289 @@ static void bools(void) {
printf("{\"bools\":{\"refused\":%zu}}\n", LEN(refused) + 9);
}
+/* ---- i64s (docs/design/016-bool-and-i64.md) ------------------------------
+ * An i64 is its own type, exact over its range: arithmetic past it fails
+ * as overflow, division and remainder floor, conversions to and from
+ * numbers happen only where the value fits. Keyed lists order i64 keys as
+ * integers; folds sum, keep the most or least, count the different ones;
+ * stores, logs, messages, sends and replacements keep every bit. */
+enum { IADD = PURE_OP_I64_ADD, ISUB = PURE_OP_I64_SUB, IMUL = PURE_OP_I64_MUL, IDIV = PURE_OP_I64_DIV,
+ IMOD = PURE_OP_I64_MOD, INEG = PURE_OP_I64_NEG, ICMP = PURE_OP_I64_COMPARE, IMIN = PURE_OP_I64_MIN,
+ IMAX = PURE_OP_I64_MAX, IFROM = PURE_OP_I64_FROM, NFROM = PURE_OP_NUM_FROM };
+static const int64_t STEP = INT64_MAX / 7; /* seven of them make the largest i64 */
+
+/* An i64 constant's 8 bytes where an instruction keeps its value. */
+static double integer_bits(int64_t value) {
+ double bits;
+ memcpy(&bits, &value, sizeof(bits));
+ return bits;
+}
+#define IC(v) ((O){C, 2, integer_bits(v)})
+
+/* Code run as a key's handler over the states n (a number), i (an i64) and
+ * b (a bool): the fault it ends with, and the states after. */
+typedef struct { PureFault fault; PureValue values[3]; } Ran;
+static Ran run_code(const O *code, uint32_t count) {
+ static const S nums[] = {{"n", 0}};
+ static const T others[] = {{"i", PURE_STATE_I64, 0}, {"b", PURE_STATE_BOOL, 0}};
+ static const I inputs[] = {KEY("go", "KeyG")};
+ H handler = {1, 0, count};
+ Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, code, count, NULL, 0, others, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
+ Ran ran = {pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK ? PURE_FAULT_NONE : app.fault, {{0}}};
+ memcpy(ran.values, app.values, sizeof(ran.values));
+ pure_app_stop(&app);
+ return ran;
+}
+
+/* The keyed list app: items keyed by an i64 id, next the id to add. */
+enum { KI_N = 0, KI_NEXT = 1, KI_ITEMS = 2, KI_FOUND = 3 };
+static const S keyed_i64_nums[] = {{"n", 0}};
+static const T keyed_i64_states[] = {{"next", PURE_STATE_I64, 0}, {"items", 3 | 1 << 8, 0}, {"found", PURE_STATE_BOOL, 0}};
+static const RF item_i64_fields[] = {{"id", 3, 0}, {"qty", 0, 0}};
+static const R item_i64_records[] = {{"Item", item_i64_fields, 2}};
+static const F put_i64_fields[] = {{"id", PURE_FIELD_I64}, {"qty", PURE_FIELD_NUMBER}};
+static const F echo_i64_fields[] = {{"id", PURE_FIELD_I64}};
+static const I keyed_i64_inputs[] = {KEY("add", "KeyA"), KEY("low", "KeyL"), KEY("drop", "KeyD"), KEY("ask", "KeyK"),
+ {"put", NULL, PURE_KIND_MESSAGE, false, put_i64_fields, 2},
+ {"echo", NULL, PURE_KIND_OUTPUT, false, echo_i64_fields, 1}};
+
+static Spec keyed_i64_spec(O *code) {
+ O made[] = {
+ /* 0 add: items.add(Item { id: next, qty: n }); next = next + STEP; n = n + 1 */
+ {L, KI_NEXT, 0}, {L, KI_N, 0}, {LA, KI_ITEMS, 0}, {L, KI_NEXT, 0}, IC(STEP), {IADD, 0, 0}, {ST, KI_NEXT, 0},
+ {L, KI_N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, KI_N, 0},
+ /* 11 low: items.add(Item { id: -next, qty: -1 }) */
+ {L, KI_NEXT, 0}, {INEG, 0, 0}, {C, 0, -1}, {LA, KI_ITEMS, 0},
+ /* 15 drop: items.remove(items.first) */
+ {XF, KI_ITEMS, 0}, {LR, KI_ITEMS, 0},
+ /* 17 ask: found = items.has(next - STEP) */
+ {L, KI_NEXT, 0}, IC(STEP), {ISUB, 0, 0}, {HAS, KI_ITEMS, 0}, {ST, KI_FOUND, 0},
+ /* 22 view total = sum(i.id for i in items), 26 most, 30 least, 34 kinds (distinct) */
+ {FOLD, FOLDING(PURE_FOLD_SUM | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1},
+ {FOLD, FOLDING(PURE_FOLD_MAX | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1},
+ {FOLD, FOLDING(PURE_FOLD_MIN | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1},
+ {FOLD, FOLDING(PURE_FOLD_DISTINCT | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0},
+ {C, 1, 1},
+ /* 38 view first_id = items.first.id, 40 view said = "{next}" */
+ {XF, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {L, KI_NEXT, 0}, {NT, 2, 0},
+ /* 42 put: items.add(Item { id: put.id, qty: put.qty }); send echo { id: put.id } */
+ {FIELD, 0, 0}, {FIELD, 1, 0}, {LA, KI_ITEMS, 0}, {FIELD, 0, 0}, {SEND, 5, 0},
+ };
+ memcpy(code, made, sizeof(made));
+ static const H handlers[] = {{1, 0, 11}, {3, 11, 4}, {5, 15, 2}, {7, 17, 5}, {9, 42, 5}};
+ static const V views[] = {{"total", 22, 4 | I64_VIEW}, {"most", 26, 4 | I64_VIEW}, {"least", 30, 4 | I64_VIEW},
+ {"kinds", 34, 4}, {"first_id", 38, 2 | I64_VIEW}};
+ static const V texts[] = {{"said", 40, 2}};
+ return (Spec){keyed_i64_nums, 1, keyed_i64_inputs, LEN(keyed_i64_inputs), views, LEN(views), handlers, LEN(handlers),
+ code, LEN(made), NULL, 0, keyed_i64_states, LEN(keyed_i64_states), NULL, 0, texts, 1, item_i64_records, 1,
+ NULL, 0};
+}
+
+static int64_t i64_view(PureApp *app, uint32_t view) {
+ int64_t value = 0;
+ CHECK(pure_app_view_i64(app, view, &value));
+ return value;
+}
+
+static void integers(void) {
+ /* Arithmetic at the ends of the range, floored division and remainders. */
+ static const struct { uint8_t op; int64_t a, b; PureFault fault; int64_t result; } cases[] = {
+ {IADD, INT64_MAX - 1, 1, PURE_FAULT_NONE, INT64_MAX}, {IADD, INT64_MAX, 1, PURE_FAULT_OVERFLOW, 0},
+ {IADD, INT64_MIN, -1, PURE_FAULT_OVERFLOW, 0}, {ISUB, INT64_MIN + 1, 1, PURE_FAULT_NONE, INT64_MIN},
+ {ISUB, INT64_MIN, 1, PURE_FAULT_OVERFLOW, 0}, {ISUB, 0, INT64_MIN, PURE_FAULT_OVERFLOW, 0},
+ {IMUL, 3037000499, 3037000499, PURE_FAULT_NONE, INT64_C(9223372030926249001)},
+ {IMUL, INT64_MAX, 2, PURE_FAULT_OVERFLOW, 0}, {IMUL, INT64_MIN, -1, PURE_FAULT_OVERFLOW, 0},
+ {IMUL, INT64_MIN, 1, PURE_FAULT_NONE, INT64_MIN},
+ {IDIV, 7, 2, PURE_FAULT_NONE, 3}, {IDIV, -7, 2, PURE_FAULT_NONE, -4}, {IDIV, 7, -2, PURE_FAULT_NONE, -4},
+ {IDIV, -7, -2, PURE_FAULT_NONE, 3}, {IDIV, INT64_MIN, -1, PURE_FAULT_OVERFLOW, 0},
+ {IDIV, INT64_MIN, 1, PURE_FAULT_NONE, INT64_MIN}, {IDIV, INT64_MAX, -1, PURE_FAULT_NONE, -INT64_MAX},
+ {IDIV, 5, 0, PURE_FAULT_DIVIDE, 0},
+ {IMOD, 7, 2, PURE_FAULT_NONE, 1}, {IMOD, -7, 2, PURE_FAULT_NONE, 1}, {IMOD, 7, -2, PURE_FAULT_NONE, -1},
+ {IMOD, -7, -2, PURE_FAULT_NONE, -1}, {IMOD, INT64_MIN, -1, PURE_FAULT_NONE, 0}, {IMOD, 6, -3, PURE_FAULT_NONE, 0},
+ {IMOD, INT64_MIN, INT64_MAX, PURE_FAULT_NONE, INT64_MAX - 1}, {IMOD, 5, 0, PURE_FAULT_DIVIDE, 0},
+ {IMIN, -1, INT64_MIN, PURE_FAULT_NONE, INT64_MIN}, {IMAX, -1, INT64_MAX, PURE_FAULT_NONE, INT64_MAX},
+ };
+ for (size_t k = 0; k < LEN(cases); ++k) {
+ O code[] = {IC(cases[k].a), IC(cases[k].b), {cases[k].op, 0, 0}, {ST, 1, 0}};
+ Ran ran = run_code(code, LEN(code));
+ if (ran.fault != cases[k].fault || ran.values[1].i64 != (cases[k].fault ? 0 : cases[k].result)) {
+ fprintf(stderr, "i64 case %zu: fault %d, %" PRId64 "\n", k, (int)ran.fault, ran.values[1].i64);
+ abort();
+ }
+ }
+ O neg_most[] = {IC(INT64_MIN), {INEG, 0, 0}, {ST, 1, 0}}, neg_max[] = {IC(INT64_MAX), {INEG, 0, 0}, {ST, 1, 0}};
+ CHECK(run_code(neg_most, 3).fault == PURE_FAULT_OVERFLOW);
+ CHECK(run_code(neg_max, 3).values[1].i64 == -INT64_MAX);
+ /* Comparisons give bools, by each relation. */
+ static const struct { int64_t a, b; uint8_t truths; } pairs[] = {{-1, 0, 0x23}, {INT64_MAX, INT64_MIN, 0x2C}, {5, 5, 0x1A}};
+ for (size_t k = 0; k < LEN(pairs); ++k)
+ for (uint32_t relation = 0; relation <= PURE_NOT_EQUAL; ++relation) {
+ O code[] = {IC(pairs[k].a), IC(pairs[k].b), {ICMP, relation, 0}, {ST, 2, 0}};
+ CHECK(run_code(code, LEN(code)).values[2].num == (pairs[k].truths >> relation & 1));
+ }
+ /* Conversions only where the value fits. */
+ static const struct { double from; PureFault fault; int64_t to; } wholes[] = {
+ {9007199254740994.0, PURE_FAULT_NONE, INT64_C(9007199254740994)}, {-0.0, PURE_FAULT_NONE, 0},
+ {-9223372036854775808.0, PURE_FAULT_NONE, INT64_MIN}, {9223372036854775808.0, PURE_FAULT_LIMIT, 0},
+ {0.5, PURE_FAULT_LIMIT, 0}, {-1e300, PURE_FAULT_LIMIT, 0}};
+ for (size_t k = 0; k < LEN(wholes); ++k) {
+ O code[] = {{C, 0, wholes[k].from}, {IFROM, 0, 0}, {ST, 1, 0}};
+ Ran ran = run_code(code, LEN(code));
+ CHECK(ran.fault == wholes[k].fault && ran.values[1].i64 == wholes[k].to);
+ }
+ static const struct { int64_t from; PureFault fault; double to; } numbers_[] = {
+ {INT64_C(9007199254740992), PURE_FAULT_NONE, 9007199254740992.0}, {-INT64_C(9007199254740992), PURE_FAULT_NONE, -9007199254740992.0},
+ {INT64_C(9007199254740993), PURE_FAULT_LIMIT, 0}, {INT64_MIN, PURE_FAULT_LIMIT, 0}};
+ for (size_t k = 0; k < LEN(numbers_); ++k) {
+ O code[] = {IC(numbers_[k].from), {NFROM, 0, 0}, {ST, 0, 0}};
+ Ran ran = run_code(code, LEN(code));
+ CHECK(ran.fault == numbers_[k].fault && ran.values[0].num == numbers_[k].to);
+ }
+
+ /* The keyed list app: ids ordered as integers, a sum that overflows, every digit shown. */
+ O code[64];
+ Spec spec = keyed_i64_spec(code);
+ if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ CHECK(module_a.state_type[KI_NEXT] == PURE_STATE_I64 && module_a.view_type[0] == PURE_VIEW_I64 &&
+ module_a.input[4].prefix == 16 && module_a.input[5].prefix == 8);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
+ int64_t value;
+ CHECK(i64_view(&app, 0) == 0 && !pure_app_view_i64(&app, 1, &value) && !pure_app_view_i64(&app, 4, &value));
+ double number;
+ CHECK(!pure_app_view(&app, 0, &number) && pure_app_view(&app, 3, &number) && number == 0);
+ for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* -3 steps */
+ CHECK(pure_list_count(app.lists[KI_ITEMS]) == 4 && pure_list_get(app.lists[KI_ITEMS], 0, 0).i64 == -3 * STEP &&
+ pure_list_get(app.lists[KI_ITEMS], 1, 0).i64 == 0 && pure_list_get(app.lists[KI_ITEMS], 3, 0).i64 == 2 * STEP);
+ CHECK(i64_view(&app, 0) == 0 && i64_view(&app, 1) == 2 * STEP && i64_view(&app, 2) == -3 * STEP &&
+ pure_app_view(&app, 3, &number) && number == 4 && i64_view(&app, 4) == -3 * STEP);
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_CONFLICT && app.fault == PURE_FAULT_EXISTS); /* -3 steps again */
+ for (int k = 0; k < 4; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(app.values[KI_NEXT].i64 == INT64_MAX && !pure_app_view_i64(&app, 0, &value)); /* the sum overflows */
+ CHECK(!strcmp(view_text(&app, 5), "9223372036854775807"));
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_INVALID && app.fault == PURE_FAULT_OVERFLOW);
+ CHECK(pure_list_count(app.lists[KI_ITEMS]) == 8 && app.values[KI_NEXT].i64 == INT64_MAX && app.values[KI_N].num == 7);
+ CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && app.values[KI_FOUND].num == 1);
+ CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_OK && i64_view(&app, 4) == 0);
+ /* A message's i64 is 8 bytes, a send's too. */
+ unsigned char payload[16];
+ int64_t least = INT64_MIN;
+ double qty = 2.5;
+ memcpy(payload, &least, 8);
+ memcpy(payload + 8, &qty, 8);
+ CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 16}) == PURE_OK && i64_view(&app, 2) == INT64_MIN);
+ pure_app_finish(&app);
+ Outbox o = outbox_of(&app);
+ CHECK(o.count == 1 && o.items[0].size == 8 && !memcmp(o.items[0].payload, &least, 8));
+ pure_app_stop(&app);
+
+ /* Recast by name: a u32 widens to an i64; to and from numbers only where the value fits. */
+ PureField wide[2] = {{"id", PURE_FIELD_I64, 0, 0}, {"n", PURE_FIELD_NUMBER, 0, 0}};
+ PureField narrow[2] = {{"id", PURE_FIELD_NUMBER, 0, 0}, {"n", PURE_FIELD_I64, 0, 0}};
+ PureField small[1] = {{"id", PURE_FIELD_U32, 0, 0}};
+ CHECK(pure_fields_lay_out(wide, 2) == 16 && pure_fields_lay_out(narrow, 2) == 16 && pure_fields_lay_out(small, 1) == 4);
+ unsigned char given[16], *made;
+ size_t size;
+ int64_t id = INT64_C(9007199254740993);
+ double n = 2.5;
+ memcpy(given, &id, 8);
+ memcpy(given + 8, &n, 8);
+ CHECK(pure_send_recast(wide, 2, narrow, 2, (PureSpan){given, 16}, &made, &size) == PURE_OK && size == 16);
+ double read_id;
+ int64_t read_n;
+ memcpy(&read_id, made, 8);
+ memcpy(&read_n, made + 8, 8);
+ CHECK(read_id == 0 && read_n == 0); /* 2^53 + 1 is no number, 2.5 no i64 */
+ free(made);
+ uint32_t u = UINT32_MAX;
+ CHECK(pure_send_recast(small, 1, wide, 2, (PureSpan){(unsigned char *)&u, 4}, &made, &size) == PURE_OK && size == 16);
+ memcpy(&id, made, 8);
+ CHECK(id == UINT32_MAX);
+ free(made);
+ static const unsigned char u32_id[] = {1, PURE_FIELD_U32, 2, 'i', 'd'}, number_id[] = {1, PURE_FIELD_NUMBER, 2, 'i', 'd'};
+ CHECK(pure_module_takes(&module_a, 4, u32_id, sizeof(u32_id)) && !pure_module_takes(&module_a, 4, number_id, sizeof(number_id)));
+
+ /* What mixes i64s and numbers is refused. */
+ const struct { O code[4]; uint32_t count; const char *reason; } refused[] = {
+ {{{C, 0, 1}, {C, 0, 2}, {IADD, 0, 0}, {ST, 1, 0}}, 4, "operand type"},
+ {{IC(1), IC(2), {ADD, 0, 0}, {ST, 1, 0}}, 4, "operand type"},
+ {{IC(1), {ST, 0, 0}}, 2, "operand type"},
+ {{{C, 0, 1}, {ST, 1, 0}}, 2, "operand type"},
+ {{IC(1), IC(2), {ICMP, PURE_NOT_EQUAL + 1, 0}, {ST, 2, 0}}, 4, "operand names no such"},
+ {{IC(1), IC(2), {LT, 0, 0}, {ST, 2, 0}}, 4, "operand type"},
+ {{IC(1), {JZ, 2, 0}}, 2, "operand type"},
+ {{{C, 0, 1}, {NFROM, 0, 0}, {ST, 0, 0}}, 3, "operand type"},
+ {{IC(1), {IFROM, 0, 0}, {ST, 1, 0}}, 3, "operand type"},
+ {{{C, 0, 1}, {LOCAL_S, 3u << 8, 0}}, 2, "operand type"},
+ {{IC(1), {LOCAL_S, LOCAL(0, -1), 0}}, 2, "operand type"},
+ };
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ static const S nums[] = {{"n", 0}};
+ static const T others[] = {{"i", PURE_STATE_I64, 0}, {"b", PURE_STATE_BOOL, 0}};
+ static const I inputs[] = {KEY("go", "KeyG")};
+ H handler = {1, 0, refused[k].count};
+ Spec bad = {nums, 1, inputs, 1, NULL, 0, &handler, 1, refused[k].code, refused[k].count, NULL, 0, others, 2, NULL, 0,
+ NULL, 0, NULL, 0, NULL, 0};
+ if (load(bad, &module_b) != PURE_INVALID || !strstr(error, refused[k].reason)) {
+ fprintf(stderr, "i64 refusal %zu: expected '%s', got '%s'\n", k, refused[k].reason, error);
+ abort();
+ }
+ }
+ /* A fold's value is a number or an i64 as its kind says; count and first take no i64. */
+ O sum_number[] = {{FOLD, FOLDING(PURE_FOLD_SUM | PURE_FOLD_INTEGER, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0},
+ {IG, ITEM(KI_ITEMS, 1), 0}, {C, 1, 1}};
+ O sum_i64[] = {{FOLD, FOLDING(PURE_FOLD_SUM, KI_ITEMS, 3), 0}, {ROW, KI_ITEMS, 0}, {IG, ITEM(KI_ITEMS, 0), 0}, {C, 1, 1}};
+ O count_i64[] = {{FOLD, FOLDING(PURE_FOLD_COUNT | PURE_FOLD_INTEGER, KI_ITEMS, 1), 0}, {C, 1, 1}};
+ V as_i64 = {"v", 0, 4 | I64_VIEW}, as_number = {"v", 0, 4}, as_count = {"v", 0, 2};
+ Spec bad = spec;
+ bad.h = NULL, bad.nh = 0, bad.tv = NULL, bad.ntv = 0, bad.nv = 1;
+ bad.o = sum_number, bad.no = 4, bad.v = &as_i64;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "fold block must leave an i64 and a bool"));
+ bad.o = sum_i64, bad.v = &as_number;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "fold block must leave a number and a bool"));
+ bad.o = count_i64, bad.no = 2, bad.v = &as_count;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand names no such"));
+
+ /* Replaced: a number, a bool and an i64 carry over each other where the value fits, else take the default. */
+ static const S was_nums[] = {{"x", 5}, {"y", 2.5}};
+ static const T was_others[] = {{"w", PURE_STATE_I64, 7}, {"v", PURE_STATE_I64, 0}, {"rows", 3, 0}};
+ static const RF was_fields[] = {{"a", 0, 0}};
+ static const R was_records[] = {{"Row", was_fields, 1}};
+ static const S now_nums[] = {{"w", 3}};
+ static const T now_others[] = {{"x", PURE_STATE_I64, (uint32_t)-1}, {"y", PURE_STATE_I64, (uint32_t)-2},
+ {"v", PURE_STATE_BOOL, 0}, {"rows", 3, 0}};
+ static const RF now_fields[] = {{"a", 3, 9}};
+ static const R now_records[] = {{"Row", now_fields, 1}};
+ Spec was = {was_nums, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, was_others, 3, NULL, 0, NULL, 0, was_records, 1, NULL, 0};
+ Spec now = {now_nums, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, now_others, 4, NULL, 0, NULL, 0, now_records, 1, NULL, 0};
+ CHECK(load(was, &module_a) == PURE_OK && load(now, &module_b) == PURE_OK);
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK && app.values[2].i64 == 7);
+ app.values[3].i64 = INT64_C(9007199254740993);
+ static const double rows[] = {3, 2.5, -0.0, 1e300};
+ for (size_t k = 0; k < LEN(rows); ++k) CHECK(pure_list_insert(app.lists[4], k, (PureCell[]){{.num = rows[k]}}, NULL) == PURE_OK);
+ CHECK(pure_app_replace(&app, &module_b) == PURE_OK);
+ /* now: w (a number), x, y (i64s), v (a bool), rows of i64s */
+ CHECK(app.values[0].num == 7 && app.values[1].i64 == 5 && app.values[2].i64 == -2 && app.values[3].num == 0);
+ static const int64_t migrated[] = {3, 9, 0, 9};
+ for (size_t k = 0; k < LEN(migrated); ++k) CHECK(pure_list_get(app.lists[4], k, 0).i64 == migrated[k]);
+ app.values[3].num = 1; /* v true: an i64 1 when it goes back */
+ CHECK(pure_app_replace(&app, &module_a) == PURE_OK);
+ CHECK(app.values[0].num == 5 && app.values[1].num == -2 && app.values[2].i64 == 7 && app.values[3].i64 == 1);
+ for (size_t k = 0; k < LEN(migrated); ++k) CHECK(pure_list_get(app.lists[4], k, 0).num == (double)migrated[k]);
+ pure_app_stop(&app);
+
+ /* Stored: random adds, drops and asks, restarting now and then, as in memory; extremes logged and imaged. */
+ static const char *const none[] = {""};
+ unsigned long checkpoints = pure_save_test_checkpoints;
+ for (uint32_t seed = 1; seed <= 3; ++seed) stored_equivalence(keyed_i64_spec(code), none, 1, seed * 0x2545f491u);
+ printf("{\"integers\":{\"cases\":%zu,\"refused\":%zu,\"checkpoints\":%lu}}\n", LEN(cases), LEN(refused) + 3,
+ pure_save_test_checkpoints - checkpoints);
+}
+
/* A timer's tick sees the time the host set; floor and a floored modulo
* take it apart. now is for handlers only. */
enum { NOW = PURE_OP_NOW, FLOOR = PURE_OP_FLOOR, MOD = PURE_OP_MOD };
@@ -4866,6 +5164,7 @@ int main(void) {
send_recast();
output_meaning();
bools();
+ integers();
memory_budget();
fold_mutations();
pointing();