aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module-save.c28
-rw-r--r--core/module-save.h22
-rw-r--r--core/module.c227
-rw-r--r--core/module.h64
-rw-r--r--core/test-module.c490
5 files changed, 616 insertions, 215 deletions
diff --git a/core/module-save.c b/core/module-save.c
index 414d6b7..2b96252 100644
--- a/core/module-save.c
+++ b/core/module-save.c
@@ -176,7 +176,9 @@ static size_t header_of(unsigned count, const uint8_t *text, const char (*names)
static size_t image_header(const PureModule *m, uint32_t record, uint64_t seq, unsigned char *out) {
const PureRecord *r = &m->record[record];
- return header_of(r->count, m->record_field_text + r->first, m->record_field_names + r->first, seq, out);
+ uint8_t kinds[PURE_RECORD_FIELDS];
+ for (unsigned f = 0; f < r->count; ++f) kinds[f] = pure_record_kind(m, r->first + f);
+ return header_of(r->count, kinds, m->record_field_names + r->first, seq, out);
}
PureList *pure_runtime_list_new(uint32_t place) {
@@ -240,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_ROWS || at[k] > PURE_FIELD_BOOL) /* an output sends no rows */
return -1;
if (shapes) {
shapes[t].fields[f] = (PureField){.type = at[k]};
@@ -456,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;
+ ok = named(bytes, size, &at, 0, &text[f], names[f]) && (text[f] <= 1 || text[f] == PURE_RECORD_BOOL);
for (unsigned k = 0; k < f; ++k) ok = ok && strcmp(names[k], names[f]);
}
if (ok && image) { /* the save it holds, then its items */
@@ -472,7 +474,9 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint
}
PureList *from = NULL;
if (result == PURE_OK && stored) {
- from = pure_list_new(count, text);
+ 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);
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);
}
@@ -491,7 +495,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] == m->record_field_text[r->first + f] && !strcmp(names[f], m->record_field_names[r->first + f]);
+ same = text[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;
@@ -665,14 +669,19 @@ void pure_app_checkpointed(PureApp *app, PureCheckpoint *c, PureResult result) {
}
static bool scalar(uint8_t type) {
- return type == PURE_STATE_NUM || type == PURE_STATE_CURSOR || type == PURE_STATE_INDEX;
+ return type == PURE_STATE_NUM || type == PURE_STATE_BOOL || 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. */
+ * its text, an index on an item of its list (or 0), a finite number, a bool
+ * 0 or 1. */
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 (m->state_type[state] == PURE_STATE_INDEX) {
size_t count = pure_list_count(lists[m->state_ref[state]]);
@@ -731,9 +740,10 @@ PURE_COLD PureResult pure_save_open_values(PureApp *app, const PureModule *m, do
double value;
memcpy(&value, &bits, sizeof(value));
int state = pure_module_state(m, name);
- if (state >= 0 && kept[state] < 0 && m->state_type[state] == type && scalar(type) &&
+ 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] = value;
+ values[state] = m->state_type[state] == PURE_STATE_BOOL ? pure_as_bool(value, 0) : value;
}
free(bytes);
return ok ? PURE_OK : PURE_STORE_ERROR;
diff --git a/core/module-save.h b/core/module-save.h
index d17cace..9c0f74e 100644
--- a/core/module-save.h
+++ b/core/module-save.h
@@ -65,15 +65,23 @@ PureResult pure_save_commit(PureApp *, const double *values, void *const *docume
uint32_t pure_fields_lay_out(PureField *fields, uint32_t count);
/* A send's payload, laid out for from's fields, made anew for to's (both
* laid out): each field takes the value of from's of the same name when
- * its type can hold it (u32 and number are numbers, bytes and text are
- * bytes; a number that is not a u32, or bytes that are not UTF-8 for a
- * text, cannot), else it is 0 or empty. The same rule moves a list's items
- * (pure_list_migrate). STORE_ERROR when payload is not laid out by from. */
+ * its type can hold it (u32, number and bool are numbers, bytes and text
+ * are bytes; a number that is not a u32, one that is not 0 or 1 for a bool,
+ * or bytes that are not UTF-8 for a text, cannot), else it is 0, false or
+ * empty. The same rule moves a list's items (pure_list_migrate). A link
+ * delivers only to a field of the same kind or a wider one
+ * (pure_module_takes). STORE_ERROR when payload is not laid out by from. */
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 list with record's fields, taking from `from` each field of the same name
- * and kind and giving the others their defaults (module.c). */
+/* 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 };
+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). */
PureResult pure_list_migrate(PureList **out, const PureList *from, const char *const *names,
- const uint8_t *text, unsigned count, const PureModule *m, uint32_t record);
+ const uint8_t *kinds, unsigned count, const PureModule *m, uint32_t record);
#endif
diff --git a/core/module.c b/core/module.c
index e33f43f..db19222 100644
--- a/core/module.c
+++ b/core/module.c
@@ -12,8 +12,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. */
-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 };
+ * the named local holds, T_STATE for the type of the number or bool 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 };
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). */
@@ -24,14 +26,16 @@ enum { READ = IN_VIEW | IN_HANDLER };
/* What an argument names: nothing (it is 0), a state of one type (A_STATE +
* 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, or the row of the
- * column a fold runs in. */
-enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_INDEX + 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 fold's block is verified as a column that leaves one number (count) or
- * two (a value and whether to include it), a table's filter as a column that
- * leaves one: 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). */
+ * 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'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 };
/* 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:
@@ -39,22 +43,24 @@ enum { FOLD_ONE = 100, FOLD_TWO, FILTER, WALK };
* O_REF: the text or list of the argument's cursor or index state. O_LIST:
* the list of an item argument. */
enum { O_ANY, O_OPERAND, O_ARG, O_REF, O_LIST };
-static const unsigned char magic[8] = {'P','U','R','E','M','O','D','5'};
+static const unsigned char magic[8] = {'P','U','R','E','M','O','D','6'};
/* Operand types popped (bottom to top), the type pushed, where allowed, what
* the argument names and the text or list worked on. */
#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 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] = NUMS(0), [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] = NUMS(2), [PURE_OP_LE] = NUMS(2),
- [PURE_OP_GT] = NUMS(2), [PURE_OP_GE] = NUMS(2), [PURE_OP_EQ] = NUMS(2), [PURE_OP_NE] = NUMS(2),
- [PURE_OP_AND] = NUMS(2), [PURE_OP_OR] = NUMS(2), [PURE_OP_NOT] = NUMS(1), [PURE_OP_MIN] = NUMS(2),
- [PURE_OP_MAX] = NUMS(2), [PURE_OP_CLAMP] = NUMS(3),
- [PURE_OP_LOAD] = {0, {0}, T_NUM, READ, STATE(NUM), O_ANY},
+ [PURE_OP_CONST] = {0, {0}, T_NUM, READ, A_BOOL, 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),
+ [PURE_OP_MIN] = NUMS(2), [PURE_OP_MAX] = NUMS(2), [PURE_OP_CLAMP] = NUMS(3),
+ [PURE_OP_LOAD] = {0, {0}, T_STATE, READ, A_VALUE, O_ANY},
[PURE_OP_DT] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
- [PURE_OP_STORE] = {1, {T_NUM}, 0, IN_HANDLER, STATE(NUM), O_ANY},
- [PURE_OP_JZ] = {1, {T_NUM}, 0, READ, A_TARGET, O_ANY},
+ [PURE_OP_STORE] = {1, {T_STATE}, 0, IN_HANDLER, A_VALUE, O_ANY},
+ [PURE_OP_JZ] = {1, {T_BOOL}, 0, READ, A_TARGET, O_ANY},
[PURE_OP_JMP] = {0, {0}, 0, READ, A_TARGET, O_ANY},
[PURE_OP_FIELD] = {0, {0}, T_FIELD, IN_MESSAGE | IN_ARGUMENTS, A_FIELD, O_ANY},
[PURE_OP_DOC] = {0, {0}, T_BYTES, IN_KEYED, A_COLLECTION, O_ANY},
@@ -62,11 +68,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_LEN] = {1, {T_BYTES}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
[PURE_OP_REPLACE] = {3, {T_NUM, T_NUM, T_BYTES}, 0, IN_KEYED, A_COLLECTION, O_ANY},
[PURE_OP_REPLY] = {1, {T_BYTES}, 0, IN_MESSAGE, A_NONE, O_ANY},
- [PURE_OP_REQUIRE] = {1, {T_NUM}, 0, IN_HANDLER, A_NONE, O_ANY},
+ [PURE_OP_REQUIRE] = {1, {T_BOOL}, 0, IN_HANDLER, A_NONE, O_ANY},
[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_NONE, O_ANY},
+ [PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_BOOL, 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},
@@ -103,9 +109,9 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_PICK] = {0, {0}, 0, READ, A_DEPTH, O_ANY},
[PURE_OP_SLIDE] = {0, {0}, 0, READ, A_DEPTH, O_ANY},
[PURE_OP_FOLD] = {0, {0}, T_NUM, READ, A_FOLD, O_ANY},
- [PURE_OP_TEXT_COMPARE] = {2, {T_TEXT, T_TEXT}, T_NUM, READ, A_RELATION, O_ANY},
+ [PURE_OP_TEXT_COMPARE] = {2, {T_TEXT, T_TEXT}, T_BOOL, READ, A_RELATION, O_ANY},
[PURE_OP_LIST_FIND] = {1, {T_KEY}, T_INDEX, READ, A_KEYED, O_ARG},
- [PURE_OP_LIST_HAS] = {1, {T_KEY}, T_NUM, READ, A_KEYED, O_ANY},
+ [PURE_OP_LIST_HAS] = {1, {T_KEY}, T_BOOL, READ, A_KEYED, O_ANY},
[PURE_OP_NOW] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
[PURE_OP_SEND] = {0, {0}, 0, IN_HANDLER, A_OUTPUT, O_ANY},
[PURE_OP_EACH] = {0, {0}, 0, IN_HANDLER, A_EACH, O_ANY},
@@ -114,6 +120,8 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_FLOOR] = NUMS(1), [PURE_OP_MOD] = NUMS(2),
};
#undef NUMS
+#undef COMPARE
+#undef LOGIC
#undef STATE
static uint32_t read16(const unsigned char *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8; }
@@ -140,6 +148,9 @@ static void write_double(unsigned char *p, double value) {
write32(p + 4, (uint32_t)(bits >> 32));
}
+/* A bool's value: 0 (not -0) or 1. */
+static bool canonical_bool(double value) { return value == 1 || (value == 0 && !signbit(value)); }
+
static bool variable_field(uint8_t type) {
return type == PURE_FIELD_BYTES || type == PURE_FIELD_TEXT || type == PURE_FIELD_ROWS;
}
@@ -150,7 +161,7 @@ uint32_t pure_fields_lay_out(PureField *fields, uint32_t count) {
if (variable_field(fields[f].type)) last = f;
for (uint32_t f = 0; f < count; ++f) {
fields[f].offset = f == last ? PURE_FIELD_REST : prefix;
- if (f != last) prefix += fields[f].type == PURE_FIELD_NUMBER ? 8 : 4;
+ if (f != last) prefix += pure_field_size(fields[f].type);
}
return prefix;
}
@@ -163,7 +174,7 @@ typedef struct { double num; PureSpan bytes; } FieldValue;
static bool read_fields(const PureField *fields, uint32_t count, PureSpan payload, FieldValue *values) {
uint32_t prefix = 0;
for (uint32_t f = 0; f < count; ++f)
- if (fields[f].offset != PURE_FIELD_REST) prefix += fields[f].type == PURE_FIELD_NUMBER ? 8 : 4;
+ if (fields[f].offset != PURE_FIELD_REST) prefix += pure_field_size(fields[f].type);
if (payload.size < prefix) return false;
size_t at = prefix;
for (uint32_t f = 0; f < count; ++f) {
@@ -171,7 +182,10 @@ 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_BYTES || field->type == PURE_FIELD_TEXT) {
+ else if (field->type == PURE_FIELD_BOOL) {
+ if (payload.data[field->offset] > 1) return false;
+ values[f].num = payload.data[field->offset];
+ } else if (field->type == PURE_FIELD_BYTES || field->type == PURE_FIELD_TEXT) {
size_t size = field->offset == PURE_FIELD_REST ? payload.size - at : read32(payload.data + field->offset);
if (size > payload.size - at) return false;
values[f].bytes = (PureSpan){payload.data + at, size};
@@ -194,29 +208,34 @@ PureResult pure_send_recast(const PureField *from, uint32_t from_count, const Pu
now[g] = (FieldValue){0};
uint32_t f = 0;
while (f < from_count && strcmp(from[f].name, field->name)) ++f;
- bool number = field->type == PURE_FIELD_U32 || field->type == PURE_FIELD_NUMBER;
- if (f < from_count && number && (from[f].type == PURE_FIELD_U32 || from[f].type == PURE_FIELD_NUMBER)) {
+ 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 || (v >= 0 && v <= UINT32_MAX && v == (double)(uint32_t)v);
+ 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;
- } else if (f < from_count && !number && (from[f].type == PURE_FIELD_BYTES || from[f].type == PURE_FIELD_TEXT)) {
+ } 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;
}
- total += field->offset == PURE_FIELD_REST || number ? 0 : 4;
- total += number ? (field->type == PURE_FIELD_NUMBER ? 8 : 4) : now[g].bytes.size;
+ total += field->offset == PURE_FIELD_REST || value ? 0 : 4;
+ total += value ? pure_field_size(field->type) : now[g].bytes.size;
}
unsigned char *made = malloc(total ? total : 1);
if (!made) return PURE_ALLOCATION;
uint32_t prefix = 0;
for (uint32_t g = 0; g < to_count; ++g)
- if (to[g].offset != PURE_FIELD_REST) prefix += to[g].type == PURE_FIELD_NUMBER ? 8 : 4;
+ if (to[g].offset != PURE_FIELD_REST) prefix += pure_field_size(to[g].type);
size_t at = prefix;
for (uint32_t g = 0; g < to_count; ++g) {
const PureField *field = &to[g];
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_BOOL) *place = (unsigned char)now[g].num;
else {
if (field->offset != PURE_FIELD_REST) write32(place, (uint32_t)now[g].bytes.size);
if (now[g].bytes.size) memcpy(made + at, now[g].bytes.data, now[g].bytes.size);
@@ -284,7 +303,13 @@ 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) {
- return m->record_field_text[m->record[m->state_ref[list]].first + field] ? T_TEXT : T_NUM;
+ 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;
+}
+
+/* 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;
}
PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg, const PureInputShape *shape, int row,
@@ -305,9 +330,11 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
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 or an index of a list */
- return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 1 &&
- ((arg >> 8 & 0xFF) ? state_of(m, arg >> 16, PURE_STATE_LIST) : !(arg >> 16));
+ 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 &&
+ ((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_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;
@@ -395,7 +422,9 @@ 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;
- if ((kind == A_NONE && arg) || (op->op == PURE_OP_CONST ? !isfinite(op->value) : op->value != 0))
+ bool truth = op->op == PURE_OP_CONST && arg;
+ if ((kind == A_NONE && arg) ||
+ (truth ? !canonical_bool(op->value) : op->op == PURE_OP_CONST ? !isfinite(op->value) : op->value != 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. */
@@ -445,10 +474,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
memcpy(need, signature[op->op].in, sizeof(signature[op->op].in));
if (op->op == PURE_OP_SEND) {
const PureInputShape *output = &m->input[arg];
- for (unsigned f = 0; f < output->field_count; ++f) {
- uint8_t type = m->field[output->field_start + f].type;
- need[f] = type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : T_NUM;
- }
+ for (unsigned f = 0; f < output->field_count; ++f) need[f] = message_type(m->field[output->field_start + f].type);
pops = output->field_count;
} else if (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT) {
const PureRecord *record = &m->record[m->state_ref[arg]];
@@ -459,7 +485,14 @@ 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 ? T_INDEX : T_NUM;
+ need[0] = (arg >> 8 & 0xFF) == 1 ? T_INDEX : (arg >> 8 & 0xFF) == 2 ? T_BOOL : T_NUM;
+ } else if (need[0] == T_STATE) {
+ need[0] = m->state_type[arg] == PURE_STATE_BOOL ? T_BOOL : T_NUM;
+ } else if (op->op == PURE_OP_NUM_TEXT && arg) {
+ need[0] = T_BOOL;
+ } 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 */
pops = 2;
@@ -484,12 +517,11 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
of = O_ARG;
owner = arg >> 8 & 0xFF;
}
- if (out == T_FIELD) {
- uint8_t type = m->field[shape->field_start + arg].type;
- out = type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : T_NUM;
- }
+ 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 ? T_INDEX : T_NUM;
+ 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 (truth) out = T_BOOL;
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);
@@ -516,12 +548,11 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
i += skip;
}
if (!reachable) return "block end is unreachable";
- if (view == FILTER) return height != 1 || types[0] != T_NUM ? "filter must leave one number" : NULL;
- if (folding) {
- int want = view - FOLD_ONE + 1;
- return height != want || types[0] != T_NUM || types[want - 1] != T_NUM ? "fold block must leave its numbers" : NULL;
- }
- unsigned char result = view == PURE_VIEW_TEXT ? T_TEXT : T_NUM;
+ if (view == FILTER) return height != 1 || types[0] != T_BOOL ? "filter must leave one bool" : NULL;
+ 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 (!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;
@@ -542,9 +573,9 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
* once: numbers and indexes, with no branch and no text. */
PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op, int input) {
switch (op->op) {
- case PURE_OP_FIELD: { /* a number the message or the arguments give, the same for every item */
+ 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;
+ 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_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: case PURE_OP_EQ: case PURE_OP_NE:
@@ -553,7 +584,7 @@ PURE_COLD static bool has_lanes(const PureModule *m, const PureOp *op, int input
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_NUM;
+ case PURE_OP_ITEM_GET: return field_type(m, list_of(op->arg), item_field(op->arg)) != T_TEXT;
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;
}
@@ -902,7 +933,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (error_size) error[0] = 0;
pure_module_release(out);
memset(out, 0, sizeof(*out));
- if (!p || size < PURE_MODULE_HEADER || memcmp(p, magic, sizeof(magic))) REFUSE("not a Pure module v5", 0);
+ if (!p || size < PURE_MODULE_HEADER || memcmp(p, magic, sizeof(magic))) REFUSE("not a Pure module v6", 0);
out->states = (uint16_t)read16(p + 8);
out->inputs = (uint16_t)read16(p + 10);
out->views = (uint16_t)read16(p + 12);
@@ -948,18 +979,21 @@ 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_INDEX) REFUSE("state type", i);
- /* Flags: a mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */
- uint32_t flags = out->state_type[i] == PURE_STATE_NUM ? 0 : read32(at + 12);
+ 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;
+ 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;
/* A list of a message's rows is named by the field's path (input.field), no other state. */
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 (out->state_type[i] == PURE_STATE_NUM) {
+ if (value) {
out->initial[i] = read_double(at + 8);
if (!isfinite(out->initial[i])) REFUSE("non-finite initial value", i);
+ if (out->state_type[i] == PURE_STATE_BOOL && !canonical_bool(out->initial[i])) REFUSE("bool initial value", i);
} else {
if ((flags && !(cursor && flags == PURE_CURSOR_STAYS) && !(list && flags <= PURE_RECORD_FIELDS) && !rows) ||
read32(at + 8) > UINT16_MAX) REFUSE("state reference", i);
@@ -1016,7 +1050,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_ROWS || (field->type != PURE_FIELD_ROWS && type >> 8) || type >> 24)
+ field->type > PURE_FIELD_BOOL || (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) {
@@ -1060,14 +1094,16 @@ 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);
+ uint32_t type = read32(entry + 4); /* 0 a number, 1 a text, 2 a bool */
out->record_field_text[f] = type == 1;
- if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[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)
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] = type ? read32(entry + 8) : read_double(entry + 8);
- if (type ? read32(entry + 12) || read32(entry + 8) >= out->constants : !isfinite(out->record_field_default[f]))
+ out->record_field_default[f] = type == 1 ? read32(entry + 8) : read_double(entry + 8);
+ if (type == 1 ? read32(entry + 12) || read32(entry + 8) >= out->constants
+ : !isfinite(out->record_field_default[f]) || (type == 2 && !canonical_bool(out->record_field_default[f])))
REFUSE("record field default", f);
}
next_record_field += record->count;
@@ -1078,7 +1114,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if ((out->state_type[i] == PURE_STATE_TEXT && out->state_ref[i] >= out->constants) ||
(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_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_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. */
@@ -1088,7 +1125,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_TABLE || (at[5] && at[4] != PURE_VIEW_TABLE) ||
+ if (at[4] > PURE_VIEW_BOOL || (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
@@ -1235,7 +1272,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_ROWS || bytes[at] > PURE_FIELD_BOOL)
return -1;
fields[f] = (PureField){.type = bytes[at]};
memcpy(fields[f].name, bytes + at + 2, length);
@@ -1482,6 +1519,7 @@ static PureResult send(PureApp *app, uint32_t output, const Slot *fields, const
for (uint32_t f = 0; f < shape->field_count; ++f) {
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_NUMBER) {
uint64_t bits;
memcpy(&bits, &fields[f].num, 8);
@@ -1646,7 +1684,8 @@ static bool run_lanes(const PureModule *m, const double *values, PureBlock block
case PURE_OP_FIELD: {
const PureField *field = &m->field[ev->shape->field_start + arg];
const unsigned char *given = ev->arguments ? app->arguments.data : app->payload.data;
- double value = field->type == PURE_FIELD_U32 ? read32(given + field->offset) : read_double(given + field->offset);
+ double value = field->type == PURE_FIELD_U32 ? read32(given + field->offset)
+ : field->type == PURE_FIELD_BOOL ? given[field->offset] : read_double(given + field->offset);
stack[sp++] = (Lanes){NULL, value, false, 0};
continue;
}
@@ -2206,6 +2245,7 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even
const unsigned char *given = ev->arguments ? app->arguments.data : app->payload.data;
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 stack[sp++].bytes = (PureBytes){ev->arguments ? FROM_ARGUMENTS : FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0],
ev->spans[arg][1]};
continue;
@@ -2245,8 +2285,9 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even
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];
- if ((status = keep_bytes(arena, digits, pure_format_number(top->num, digits), &top->bytes)) != PURE_OK)
- return status;
+ size_t length = 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;
}
case PURE_OP_CONCAT: {
@@ -3010,8 +3051,9 @@ static PureResult keep_reply(PureApp *app) {
/* Where each variable field of a payload of shape is: where placed says
* (start and size in the payload), or, without it, one after another after
- * the prefix. A length past the payload, a text that is not UTF-8 or a
- * number that is not finite is its fault (else NONE). */
+ * the prefix. A length past the payload or a bool that is not 0 or 1 is
+ * LIMIT, a text that is not UTF-8 ENCODING, a number that is not finite
+ * NON_FINITE (else NONE). */
static PureFault place_fields(const PureModule *m, const PureInputShape *shape, PureSpan payload, const uint32_t *placed,
uint32_t spans[][2]) {
if (payload.size < shape->prefix) return PURE_FAULT_LIMIT;
@@ -3020,6 +3062,7 @@ static PureFault place_fields(const PureModule *m, const PureInputShape *shape,
const PureField *field = &m->field[shape->field_start + f];
if (field->type == PURE_FIELD_NUMBER && !isfinite(read_double(payload.data + field->offset)))
return PURE_FAULT_NON_FINITE;
+ if (field->type == PURE_FIELD_BOOL && payload.data[field->offset] > 1) return PURE_FAULT_LIMIT;
if (field->type != PURE_FIELD_BYTES && field->type != PURE_FIELD_TEXT && field->type != PURE_FIELD_ROWS) continue;
size_t start = placed ? placed[2 * f] : at;
if (start > payload.size) return PURE_FAULT_LIMIT;
@@ -3327,7 +3370,9 @@ bool pure_app_item_row(PureApp *app, uint32_t table, size_t item, size_t *row) {
/* A view outside a table (column false), or a column and a row of its table. */
static PureResult view_of(PureApp *app, uint32_t view, bool column, size_t row, uint8_t type, Slot *slot) {
const PureModule *m = app->module;
- if (view >= m->views || m->view_type[view] != type || (m->view_table[view] != 0) != column) return PURE_INVALID;
+ if (view >= m->views) return PURE_INVALID;
+ uint8_t kind = m->view_type[view] == PURE_VIEW_BOOL ? PURE_VIEW_NUM : m->view_type[view]; /* a bool's value is 0 or 1 */
+ if (kind != type || (m->view_table[view] != 0) != column) return PURE_INVALID;
if (column && !pure_app_row_item(app, m->view_table[view] - 1u, row, &row)) return PURE_NOT_FOUND;
return evaluate(app, view, row, slot);
}
@@ -3455,16 +3500,25 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) {
memcpy(reader->lists, snapshot->lists, sizeof(reader->lists));
}
+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];
+}
+
+/* A number and a bool carry over each other; 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));
+}
+
/* A list with record's fields: each takes the field of `from` with the same
- * name and kind, or its default. */
+ * name and a kind it carries over from, or its default. */
PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, const char *const *names,
- const uint8_t *text, unsigned count, const PureModule *m, uint32_t record) {
+ const uint8_t *kinds, unsigned count, const PureModule *m, uint32_t record) {
const PureRecord *r = &m->record[record];
int source[PURE_RECORD_FIELDS];
for (unsigned f = 0; f < r->count; ++f) {
source[f] = -1;
for (unsigned k = 0; k < count; ++k)
- if (!strcmp(m->record_field_names[r->first + f], names[k]) && m->record_field_text[r->first + f] == text[k])
+ if (!strcmp(m->record_field_names[r->first + f], names[k]) && kinds_carry(kinds[k], pure_record_kind(m, r->first + f)))
source[f] = (int)k;
}
if (!(*out = new_list(m, record))) return PURE_ALLOCATION;
@@ -3478,6 +3532,8 @@ PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, con
(PureCell){0, (unsigned char *)m->constant_data + m->constant_offset[constant],
m->constant_length[constant]} :
(PureCell){m->record_field_default[field], NULL, 0};
+ if (m->record_field_bool[field])
+ cells[f] = (PureCell){pure_as_bool(cells[f].num, m->record_field_default[field]), NULL, 0};
}
result = pure_list_insert(*out, at, cells, NULL);
}
@@ -3490,14 +3546,15 @@ PURE_COLD static PureResult keep_list(PureList **out, PureList *from, const Pure
const PureModule *m, uint32_t record) {
const PureRecord *r = &m->record[record], *o = &old->record[old_record];
const char *names[PURE_RECORD_FIELDS];
+ uint8_t kinds[PURE_RECORD_FIELDS];
bool same = r->count == o->count;
for (unsigned k = 0; k < o->count; ++k) {
names[k] = old->record_field_names[o->first + k];
- same = same && !strcmp(names[k], m->record_field_names[r->first + k]) &&
- old->record_field_text[o->first + k] == m->record_field_text[r->first + k];
+ kinds[k] = pure_record_kind(old, o->first + k);
+ same = same && !strcmp(names[k], m->record_field_names[r->first + k]) && kinds[k] == pure_record_kind(m, r->first + k);
}
if (same) { *out = from; return PURE_OK; }
- return pure_list_migrate(out, from, names, old->record_field_text + o->first, o->count, m, record);
+ return pure_list_migrate(out, from, names, kinds, o->count, m, record);
}
/* Open a module in an app. States keep their values by name and kind (texts
@@ -3638,7 +3695,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;
- kept[i] = previous >= 0 && old->state_type[previous] == module->state_type[i] ? previous : -1;
+ /* A number and a bool carry over each other: 0 and 1 are false and true (a bool's other numbers 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;
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]];
@@ -3660,6 +3720,7 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
rewrite[i] = result == PURE_OK && listed[i].rewrite;
} else if (!(lists[i] = new_list(module, module->state_ref[i]))) result = PURE_ALLOCATION;
} else values[i] = kept[i] >= 0 ? app->values[kept[i]] : module->initial[i];
+ if (module->state_type[i] == PURE_STATE_BOOL) values[i] = pure_as_bool(values[i], module->initial[i]);
}
/* 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 2cac3b3..0634ed1 100644
--- a/core/module.h
+++ b/core/module.h
@@ -8,7 +8,7 @@
#include <stddef.h>
#include <stdint.h>
-/* Pure module format v5 (docs/design/008-module-format.md). A toolchain such
+/* Pure module format v6 (docs/design/008-module-format.md). A toolchain such
* as aup produces it; Pure verifies it completely before anything executes:
* operand types (including which text a cursor and which list an index
* belongs to), stack heights and the stack at every join, forward-only
@@ -42,9 +42,16 @@ typedef enum {
PURE_OP_LOCAL_LOAD, PURE_OP_LOCAL_STORE,
PURE_OP_COUNT
} PureOpcode;
+/* A bool is its own type (docs/design/016-bool-and-i64.md), held as a
+ * number 0 or 1 while it runs: comparisons (LT to NE, TEXT_COMPARE) and
+ * LIST_HAS push one, AND, OR and NOT work on them, JZ and REQUIRE take one,
+ * 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 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) and an index's
+ * 0-7), what it holds (bits 8-15: 0 a number, 1 an index, 2 a bool) and an index's
* list (bits 16-31); a handler uses a slot for one of them. A handler, the
* walks in it and the folds in those read locals; the handler and its walks
* store them. */
@@ -56,10 +63,10 @@ enum { PURE_MODULE_LOCALS = 64 };
* (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 a non-zero number; sum,
- * max, min and distinct take a value and an include flag from their block
- * (distinct counts the different values, -0 as 0); first gives the index of
- * the first item whose block leaves a non-zero number. */
+ * 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
+ * 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;
/* 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
@@ -82,19 +89,31 @@ 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 or a binary64 (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 UTF-8 and
- * a number finite, or the message fails. Rows are records of a list of a
- * message's rows (rows, a list state), laid out as a list's stored form: a
- * message fills that list with them, and fails when they are not whole. */
+/* 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
+ * 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
+ * UTF-8, a number finite and a bool 0 or 1, or the message fails. Rows are
+ * records of a list of a message's rows (rows, a list state), laid out as a
+ * 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 } PureFieldType;
+ PURE_FIELD_ROWS = 5, PURE_FIELD_BOOL = 6 } 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. */
+static inline bool pure_field_fixed(uint8_t type) {
+ return type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER || type == PURE_FIELD_BOOL;
+}
+static inline uint32_t pure_field_size(uint8_t type) {
+ return type == PURE_FIELD_NUMBER ? 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_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4,
+ PURE_STATE_BOOL = 5
} PureStateType;
/* A cursor that stays before text inserted at it (a mark); other cursors go
* after it (a caret). A list of a message's rows (its state's flags hold
@@ -103,9 +122,9 @@ typedef enum {
* 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 or text views evaluated for one row at a
- * time. */
-typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2 } PureViewType;
+ * 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;
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
@@ -198,7 +217,8 @@ typedef struct {
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];
- double record_field_default[PURE_MODULE_RECORD_FIELDS]; /* number, or text constant index */
+ uint8_t record_field_bool[PURE_MODULE_RECORD_FIELDS]; /* a bool: a number field (0 or 1) as lists go */
+ double record_field_default[PURE_MODULE_RECORD_FIELDS]; /* number (a bool's 0 or 1), or text constant index */
} PureModule;
/* Decode and verify into out, which must be zeroed (a static one is) or hold
@@ -422,7 +442,8 @@ PureResult pure_app_message_fields(PureApp *, uint32_t input, const char *key, i
* next event (deferred: copy it before a later save too). */
PureResult pure_app_reply(const PureApp *, bool (*visit)(void *, PureSpan), void *context);
void pure_app_finish(PureApp *);
-/* A number view; false for a text view or a failed evaluation. */
+/* A number view, or a bool view (0 or 1: its type says which); false for a
+ * text view or a failed evaluation. */
bool pure_app_view(PureApp *, uint32_t view, double *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);
@@ -444,6 +465,7 @@ unsigned pure_app_passes(const PureApp *);
* its filter leaves the item out). Without a filter both are the identity. */
bool pure_app_row_item(PureApp *, uint32_t table, size_t row, size_t *item);
bool pure_app_item_row(PureApp *, uint32_t table, size_t item, size_t *row);
+/* A number or bool column's cell (as pure_app_view), or a text column's. */
bool pure_app_cell(PureApp *, uint32_t column, size_t row, double *out);
PureResult pure_app_cell_text(PureApp *, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context);
/* Switch to another verified module. States keep their value by name (text
diff --git a/core/test-module.c b/core/test-module.c
index fa388ce..0589877 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -36,15 +36,18 @@ 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. */
typedef struct { const char *name; uint32_t start, length; } V;
+#define BOOL_VIEW (1u << 31)
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) and index (type 4, ref = list state) states follow
- * the number states; text views follow the number views. */
+ * (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
+ * 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, or text (text = 1) whose
- * default is the constant numbered initial. */
+/* 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. */
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
@@ -69,7 +72,7 @@ static unsigned char *put_double(unsigned char *p, double value) {
return put32(p + 4, (uint32_t)(bits >> 32));
}
-/* Test-side encoder for format v5; the toolchain has its own. */
+/* Test-side encoder for format v6; the toolchain has its own. */
static size_t build(unsigned char *out, Spec spec) {
unsigned char strings[4096], blob[4096];
size_t used = 0, blob_used = 0;
@@ -106,7 +109,7 @@ static size_t build(unsigned char *out, Spec spec) {
for (size_t i = 0; i < spec.nk; ++i) { memcpy(blob + blob_used, spec.k[i], strlen(spec.k[i])); blob_used += strlen(spec.k[i]); }
unsigned char *p = out;
memset(p, 0, 40);
- memcpy(p, "PUREMOD5", 8);
+ memcpy(p, "PUREMOD6", 8);
size_t states = spec.ns + spec.nt, views = spec.nv + spec.ntv + spec.nw;
p[8] = (unsigned char)states; p[9] = (unsigned char)(states >> 8);
p[10] = (unsigned char)spec.ni; p[12] = (unsigned char)views; p[14] = (unsigned char)spec.nh;
@@ -121,6 +124,7 @@ static size_t build(unsigned char *out, Spec spec) {
for (size_t i = 0; i < spec.nt; ++i) {
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; }
p = put32(p, spec.t[i].ref);
p = put32(p, spec.t[i].type >> 8);
}
@@ -160,10 +164,15 @@ static size_t build(unsigned char *out, Spec spec) {
const RF *field = &spec.r[i].fields[f];
p = put32(p, offset[++n]);
p = put32(p, field->text);
- if (field->text) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); }
+ if (field->text == 1) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); }
else p = put_double(p, field->initial);
}
- for (size_t i = 0; i < spec.nv; ++i) { p = put32(p, offset[n++]); p = put32(p, 0); p = put32(p, spec.v[i].start); p = put32(p, spec.v[i].length); }
+ 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].start);
+ p = put32(p, spec.v[i].length & ~BOOL_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) {
const W *w = &spec.w[i];
@@ -307,10 +316,10 @@ static void rejections(void) {
static const O store[] = {{C, 0, 1}, {ST, 0, 0}, {DT, 0, 0}};
static const H h0[] = {{0, 0, 2}};
static const H h_bad_event[] = {{3, 0, 2}};
- static const O back[] = {{C, 0, 1}, {JZ, 0, 0}};
- static const O beyond[] = {{C, 0, 1}, {JZ, 3, 0}};
+ static const O back[] = {{C, 1, 1}, {JZ, 0, 0}};
+ static const O beyond[] = {{C, 1, 1}, {JZ, 3, 0}};
static const O under[] = {{ADD, 0, 0}};
- static const O join[] = {{C, 0, 1}, {JZ, 4, 0}, {C, 0, 1}, {C, 0, 1}, {ST, 0, 0}};
+ static const O join[] = {{C, 1, 1}, {JZ, 4, 0}, {C, 0, 1}, {C, 0, 1}, {ST, 0, 0}};
static const O leftover[] = {{C, 0, 1}, {C, 0, 2}};
static const O unknown_state[] = {{L, 1, 0}, {ST, 0, 0}};
static const O nan_const[] = {{C, 0, NAN}, {ST, 0, 0}};
@@ -640,7 +649,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", 6}, {"start", 1}};
+ static const F unknown_type[] = {{"insert", 7}, {"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"));
@@ -652,8 +661,8 @@ static void messages(void) {
bad = (Spec){states, 1, inputs, 2, field_view, 1, NULL, 0, field_only, 1, collections, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect"));
/* A value may cross a join when every path brings the same stack. */
- static const O branch_value[] = {{C, 0, 1}, {C, 0, 1}, {JZ, 3, 0}, {ST, 0, 0}};
- static const O branch_mismatch[] = {{C, 0, 1}, {C, 0, 1}, {JZ, 4, 0}, {C, 0, 2}, {ST, 0, 0}};
+ static const O branch_value[] = {{C, 0, 1}, {C, 1, 1}, {JZ, 3, 0}, {ST, 0, 0}};
+ static const O branch_mismatch[] = {{C, 0, 1}, {C, 1, 1}, {JZ, 4, 0}, {C, 0, 2}, {ST, 0, 0}};
static const H frame_handler[] = {{0, 0, 4}};
bad = (Spec){states, 1, inputs, 0, views, 0, frame_handler, 1, branch_value, 4, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
CHECK(load(bad, &module_b) == PURE_OK);
@@ -1284,7 +1293,7 @@ static void list_trees(void) {
static void lists(void) {
static const S nums[] = {{"made", 0}};
static const T states[] = {{"tasks", 3, 0}, {"at", 4, 1}};
- static const RF task_fields[] = {{"title", 1, 0}, {"done", 0, 0}};
+ static const RF task_fields[] = {{"title", 1, 0}, {"done", 2, 0}};
static const R records[] = {{"task", task_fields, 2}};
static const char *const constants[] = {"", "new", "x"};
static const I inputs[] = {
@@ -1292,14 +1301,14 @@ static void lists(void) {
KEY("up", "ArrowUp"), KEY("down", "ArrowDown"), KEY("toggle", "Space"), KEY("rename", "KeyR"), KEY("bad", "KeyB"),
};
static const O code[] = {
- {TI, 0, 0}, {C, 0, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, /* 0 typed */
- {XL, 2, 0}, {TK, 1, 0}, {C, 0, 0}, {LI, 1, 0}, /* 9 ins */
+ {TI, 0, 0}, {C, 1, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, {L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, /* 0 typed */
+ {XL, 2, 0}, {TK, 1, 0}, {C, 1, 0}, {LI, 1, 0}, /* 9 ins */
{XL, 2, 0}, {LR, 1, 0}, /* 13 del */
{XL, 2, 0}, {C, 0, -1}, {XM, 0, 0}, {XS, 2, 0}, /* 15 up */
{XL, 2, 0}, {C, 0, 1}, {XM, 0, 0}, {XS, 2, 0}, /* 19 down */
{XL, 2, 0}, {XL, 2, 0}, {IG, DONE, 0}, {NOT, 0, 0}, {IS, DONE, 0}, /* 23 toggle */
{XL, 2, 0}, {XL, 2, 0}, {IG, TITLE, 0}, {TK, 2, 0}, {CAT, 0, 0}, {IS, TITLE, 0}, /* 28 rename */
- {XL, 2, 0}, {TK, 2, 0}, {IS, TITLE, 0}, {XL, 2, 0}, {LR, 1, 0}, {TK, 1, 0}, {C, 0, 0}, /* 34 bad */
+ {XL, 2, 0}, {TK, 2, 0}, {IS, TITLE, 0}, {XL, 2, 0}, {LR, 1, 0}, {TK, 1, 0}, {C, 1, 0}, /* 34 bad */
{LA, 1, 0}, {C, 0, 1}, {C, 0, 0}, {DIV, 0, 0}, {ST, 0, 0},
{LC, 1, 0}, /* 46 view count */
{XL, 2, 0}, {XN, 0, 0}, /* 47 view position */
@@ -1308,7 +1317,7 @@ static void lists(void) {
};
static const H handlers[] = {{1, 0, 9}, {3, 9, 4}, {5, 13, 2}, {7, 15, 4}, {9, 19, 4}, {11, 23, 5},
{13, 28, 6}, {15, 34, 12}};
- static const V number_views[] = {{"count", 46, 1}, {"position", 47, 2}, {"done", 49, 2}};
+ static const V number_views[] = {{"count", 46, 1}, {"position", 47, 2}, {"done", 49, 2 | BOOL_VIEW}};
static const V text_views[] = {{"current", 51, 2}};
Spec spec = {nums, 1, inputs, LEN(inputs), number_views, 3, handlers, LEN(handlers), code, LEN(code),
NULL, 0, states, 2, constants, 3, text_views, 1, records, 1, NULL, 0};
@@ -1493,12 +1502,12 @@ enum { ROW = PURE_OP_ROW, EQ = PURE_OP_EQ, LT = PURE_OP_LT, PICK = PURE_OP_PICK,
static void tables(void) {
static const S nums[] = {{"n", 0}};
static const T states[] = {{"tasks", 3, 0}, {"at", 4, 1}};
- static const RF task_fields[] = {{"title", 1, 0}, {"done", 0, 0}};
+ static const RF task_fields[] = {{"title", 1, 0}, {"done", 2, 0}};
static const R records[] = {{"task", task_fields, 2}};
static const char *const constants[] = {"", "x", " ", "empty"};
static const I inputs[] = {{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("toggle", "Space")};
static const O code[] = {
- {TI, 0, 0}, {C, 0, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, /* 0 typed */
+ {TI, 0, 0}, {C, 1, 0}, {LA, 1, 0}, {XE, 1, 0}, {XS, 2, 0}, /* 0 typed */
{XL, 2, 0}, {XL, 2, 0}, {IG, DONE, 0}, {NOT, 0, 0}, {IS, DONE, 0}, /* 5 toggle */
{LC, 1, 0}, {C, 0, 0}, {GT, 0, 0}, {JZ, 17, 0}, {XL, 2, 0}, {IG, TITLE, 0}, {JMP, 18, 0},
{TK, 3, 0}, /* 10 current */
@@ -1510,7 +1519,7 @@ static void tables(void) {
static const H handlers[] = {{1, 0, 5}, {3, 5, 5}};
static const V text_views[] = {{"current", 10, 8}};
static const W table[] = {{"board", 2, 0, 1, 0}, {"title", 1, 2, 18, 2}, {"mark", 1, 2, 20, 6},
- {"pos", 0, 2, 26, 2}, {"here", 0, 2, 28, 5}};
+ {"pos", 0, 2, 26, 2}, {"here", PURE_VIEW_BOOL, 2, 28, 5}};
Spec spec = {nums, 1, inputs, 2, NULL, 0, handlers, 2, code, LEN(code), NULL, 0, states, 2, constants, 4,
text_views, 1, records, 1, table, 5};
table_spec = spec;
@@ -1563,16 +1572,20 @@ static void tables(void) {
bad = spec; bad.o = other_row; bad.no = 2; bad.h = NULL; bad.nh = 0; bad.tv = NULL; bad.ntv = 0;
bad.w = other_row_column; bad.nw = 2;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "no such state"));
- static const O typed_join[] = {{L, 0, 0}, {JZ, 4, 0}, {TK, 0, 0}, {JMP, 5, 0}, {C, 0, 1}};
+ static const O typed_join[] = {{C, 1, 1}, {JZ, 4, 0}, {TK, 0, 0}, {JMP, 5, 0}, {C, 0, 1}};
static const V mixed[] = {{"current", 0, 5}};
bad = spec; bad.o = typed_join; bad.no = 5; bad.h = NULL; bad.nh = 0; bad.tv = mixed; bad.w = NULL; bad.nw = 0;
CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "join with different stacks"));
+ static const O truth_join[] = {{C, 1, 1}, {JZ, 4, 0}, {C, 1, 1}, {JMP, 5, 0}, {C, 0, 1}}; /* a bool is not a number */
+ static const V truth_view[] = {{"v", 0, 5}};
+ bad.o = truth_join; bad.tv = NULL; bad.ntv = 0; bad.v = truth_view; bad.nv = 1;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "join with different stacks"));
/* At most 8 joins with a value may be ahead at once. */
for (unsigned open = 8; open <= 9; ++open) {
static O nested[1 + 2 * 9 + 9 + 1];
unsigned n = 0;
nested[n++] = (O){C, 0, 1};
- for (unsigned k = 0; k < open; ++k) { nested[n++] = (O){C, 0, 0}; nested[n++] = (O){JZ, 1 + 2 * open + k, 0}; }
+ for (unsigned k = 0; k < open; ++k) { nested[n++] = (O){C, 1, 0}; nested[n++] = (O){JZ, 1 + 2 * open + k, 0}; }
for (unsigned k = 0; k < open; ++k) nested[n++] = (O){NEG, 0, 0};
nested[n++] = (O){ST, 0, 0};
H frame = {0, 0, n};
@@ -2242,24 +2255,24 @@ static void marks(void) {
* count, a sum, a maximum or a minimum; an empty maximum has no value. */
enum { FOLD = PURE_OP_FOLD };
#define FOLDING(kind, list, length) ((uint32_t)(kind) | (uint32_t)(list) << 8 | (uint32_t)(length) << 16)
-static const RF fold_fields[] = {{"title", 1, 0}, {"done", 0, 0}, {"points", 0, 0}};
+static const RF fold_fields[] = {{"title", 1, 0}, {"done", 2, 0}, {"points", 0, 0}};
static const R fold_records[] = {{"task", fold_fields, 3}};
static const T fold_states[] = {{"tasks", 3, 0}, {"at", 4, 0}};
static const char *const fold_constants[] = {""};
static const I fold_inputs[] = {{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, KEY("toggle", "KeyT")};
enum { F_TITLE = 0, F_DONE = 1u << 16, F_POINTS = 2u << 16 };
static const O fold_code[] = {
- {TI, 0, 0}, {C, 0, 0}, {TI, 0, 0}, {CHR, 0, 0}, {LA, 0, 0}, {XE, 0, 0}, {XS, 1, 0}, /* 0 typed */
+ {TI, 0, 0}, {C, 1, 0}, {TI, 0, 0}, {CHR, 0, 0}, {LA, 0, 0}, {XE, 0, 0}, {XS, 1, 0}, /* 0 typed */
{XL, 1, 0}, {XL, 1, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, {IS, F_DONE, 0}, /* 7 toggle */
{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 3), 0}, {ROW, 0, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, /* 12 open */
- {FOLD, FOLDING(PURE_FOLD_SUM, 0, 3), 0}, {ROW, 0, 0}, {IG, F_POINTS, 0}, {C, 0, 1}, /* 16 points */
+ {FOLD, FOLDING(PURE_FOLD_SUM, 0, 3), 0}, {ROW, 0, 0}, {IG, F_POINTS, 0}, {C, 1, 1}, /* 16 points */
{FOLD, FOLDING(PURE_FOLD_MAX, 0, 4), 0}, {ROW, 0, 0}, {IG, F_TITLE, 0}, {CHR, 0, 0}, /* 20 longest */
- {C, 0, 1},
+ {C, 1, 1},
{FOLD, FOLDING(PURE_FOLD_SUM, 0, 4), 0}, {ROW, 0, 0}, {IG, F_POINTS, 0}, {ROW, 0, 0}, /* 25 done_points */
{IG, F_DONE, 0},
{FOLD, FOLDING(PURE_FOLD_MIN, 0, 5), 0}, {ROW, 0, 0}, {IG, F_POINTS, 0}, {ROW, 0, 0}, /* 30 fewest_open */
{IG, F_DONE, 0}, {NOT, 0, 0},
- {C, 0, 10}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}, {ADD, 0, 0}, /* 36 count_plus */
+ {C, 0, 10}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 1, 1}, {ADD, 0, 0}, /* 36 count_plus */
{FOLD, FOLDING(PURE_FOLD_FIRST, 0, 3), 0}, {ROW, 0, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, /* 40 first_open */
{XN, 0, 0},
};
@@ -2295,20 +2308,29 @@ static void folds(void) {
pure_app_stop(&app);
/* What a fold may not be. */
- static const O nested[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 2), 0}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}};
- static const O into[] = {{C, 0, 1}, {JZ, 3, 0}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}};
- static const O effect[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 3), 0}, {ROW, 0, 0}, {XS, 1, 0}, {C, 0, 1},
- {PURE_OP_REQUIRE, 0, 0}};
- static const O short_sum[] = {{FOLD, FOLDING(PURE_FOLD_SUM, 0, 1), 0}, {C, 0, 1}};
- static const O outside[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 5), 0}, {C, 0, 1}};
- static const O not_list[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 1, 1), 0}, {C, 0, 1}};
+ static const O nested[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 2), 0}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 1, 1}};
+ static const O into[] = {{C, 1, 1}, {JZ, 3, 0}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 1, 1}};
+ static const O effect[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 3), 0}, {ROW, 0, 0}, {XS, 1, 0}, {C, 1, 1},
+ {C, 0, 0}, {GT, 0, 0}, {PURE_OP_REQUIRE, 0, 0}};
+ static const O short_sum[] = {{FOLD, FOLDING(PURE_FOLD_SUM, 0, 1), 0}, {C, 1, 1}};
+ static const O outside[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 5), 0}, {C, 1, 1}};
+ static const O not_list[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 1, 1), 0}, {C, 1, 1}};
+ /* Whether an item counts is a bool, not a number; a sum's value is a number. */
+ static const O count_number[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}};
+ static const O sum_numbers[] = {{FOLD, FOLDING(PURE_FOLD_SUM, 0, 2), 0}, {C, 0, 1}, {C, 0, 1}};
+ static const O sum_bools[] = {{FOLD, FOLDING(PURE_FOLD_SUM, 0, 2), 0}, {C, 1, 1}, {C, 1, 1}};
+ static const O first_number[] = {{FOLD, FOLDING(PURE_FOLD_FIRST, 0, 1), 0}, {C, 0, 0}, {XN, 0, 0}};
static const struct { const O *code; size_t count, length; bool handler; const char *reason; } refused[] = {
{nested, LEN(nested), 3, false, "fold inside a fold"},
{into, LEN(into), 4, false, "branch into a fold"},
- {effect, LEN(effect), 5, true, "fold with an effect"},
- {short_sum, LEN(short_sum), 2, false, "fold block must leave its numbers"},
+ {effect, LEN(effect), 7, true, "fold with an effect"},
+ {short_sum, LEN(short_sum), 2, false, "fold block must leave a number and a bool"},
{outside, LEN(outside), 2, false, "fold block outside its block"},
{not_list, LEN(not_list), 2, false, "no such state"},
+ {count_number, LEN(count_number), 2, false, "fold block must leave one bool"},
+ {sum_numbers, LEN(sum_numbers), 3, false, "fold block must leave a number and a bool"},
+ {sum_bools, LEN(sum_bools), 3, false, "fold block must leave a number and a bool"},
+ {first_number, LEN(first_number), 3, false, "fold block must leave one bool"},
};
for (size_t i = 0; i < LEN(refused); ++i) {
V view = {"v", 0, (uint32_t)refused[i].length};
@@ -2326,7 +2348,7 @@ static void folds(void) {
* item (ROW is the item, not the row), and hosts turn rows into items and
* back. The rows follow every change. */
static const O filter_code[] = {
- {TI, 0, 0}, {C, 0, 0}, {TI, 0, 0}, {CHR, 0, 0}, {LA, 0, 0}, {XE, 0, 0}, {XS, 1, 0}, /* 0 typed */
+ {TI, 0, 0}, {C, 1, 0}, {TI, 0, 0}, {CHR, 0, 0}, {LA, 0, 0}, {XE, 0, 0}, {XS, 1, 0}, /* 0 typed */
{XL, 1, 0}, {XL, 1, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, {IS, F_DONE, 0}, /* 7 toggle */
{ROW, 0, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, /* 12 filter: open */
{ROW, 0, 0}, {IG, F_TITLE, 0}, /* 15 title */
@@ -2370,16 +2392,18 @@ static void filters(void) {
pure_app_stop(&app);
/* What a filter may not be. */
- static const O effect[] = {{ROW, 0, 0}, {XS, 1, 0}, {C, 0, 1}};
+ static const O effect[] = {{ROW, 0, 0}, {XS, 1, 0}, {C, 1, 1}};
static const O text_filter[] = {{ROW, 0, 0}, {IG, F_TITLE, 0}};
- static const O folded[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}};
+ static const O number_filter[] = {{ROW, 0, 0}, {IG, F_POINTS, 0}};
+ static const O folded[] = {{FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 1, 1}};
static const W filtered_by[] = {{"open", FILTERED, 0, 0, 0}}, open3[] = {{"open", FILTERED, 0, 0, 3}},
open2[] = {{"open", FILTERED, 0, 0, 2}};
static const W not_a_list[] = {{"open", 2 | 2 << 8, 0, 0, 3}};
static const W flagged_column[] = {{"open", FILTERED, 0, 0, 3}, {"c", 0 | 1 << 8, 1, 0, 3}};
static const struct { const O *code; size_t count; const W *views; size_t nviews; const char *reason; } refused[] = {
{effect, LEN(effect), open3, 1, "filter with an effect"},
- {text_filter, LEN(text_filter), open2, 1, "filter must leave one number"},
+ {text_filter, LEN(text_filter), open2, 1, "filter must leave one bool"},
+ {number_filter, LEN(number_filter), open2, 1, "filter must leave one bool"},
{folded, LEN(folded), open2, 1, "fold inside a filter"},
{effect, LEN(effect), filtered_by, 1, "table list"}, /* a filtered table needs its block */
{effect, LEN(effect), not_a_list, 1, "table list"}, /* state 1 is an index */
@@ -2428,53 +2452,96 @@ static void gen_index(Gen *g, int list, int depth) {
put_op(g, roll(g, 2) ? PURE_OP_INDEX_FIRST : PURE_OP_INDEX_LAST, (uint32_t)list, 0);
}
+static void gen_bool(Gen *g, int depth);
+
+/* An item's field: a number (L's key, a or b, M's x or y) or a bool (L's c, M's z). */
+static void gen_field(Gen *g, int depth, bool flag) {
+ int list = g->fold_list >= 0 && (depth <= 0 || roll(g, 3)) ? g->fold_list : roll(g, 2) ? LANE_L : LANE_M;
+ gen_index(g, list, depth - 1);
+ uint32_t field = flag ? (list == LANE_L ? 3 : 2) : roll(g, list == LANE_L ? 3 : 2);
+ put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | field << 16, 0);
+}
+
static void gen_num(Gen *g, int depth) {
static const double pool[] = {0, -0.0, 1, -1, 2, 0.5, 3, 7, 100, -3.5, 1e308, -1e308, 4, 10};
- static const uint8_t binary[] = {PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL, PURE_OP_DIV, PURE_OP_LT, PURE_OP_LE,
- PURE_OP_GT, PURE_OP_GE, PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR,
- PURE_OP_MIN, PURE_OP_MAX, PURE_OP_MOD};
- static const uint8_t unary[] = {PURE_OP_NEG, PURE_OP_NOT, PURE_OP_FLOOR};
- unsigned choice = roll(g, depth <= 0 ? 4 : 12);
- if (g->tree && (choice == 9 || choice == 10)) choice = 11;
+ static const uint8_t binary[] = {PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL, PURE_OP_DIV, PURE_OP_MIN, PURE_OP_MAX, PURE_OP_MOD};
+ static const uint8_t unary[] = {PURE_OP_NEG, PURE_OP_FLOOR};
+ unsigned choice = roll(g, depth <= 0 ? 4 : 11);
+ if (g->tree && (choice == 8 || choice == 9)) choice = 10;
switch (choice) {
case 0: put_op(g, C, 0, pool[roll(g, LEN(pool))]); return;
case 1: put_op(g, L, LANE_K, 0); return;
- case 2: case 3: { /* a field of an item */
- int list = g->fold_list >= 0 && (depth <= 0 || roll(g, 3)) ? g->fold_list : roll(g, 2) ? LANE_L : LANE_M;
- gen_index(g, list, depth - 1);
- put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | (uint32_t)roll(g, list == LANE_L ? 3 : 2) << 16, 0);
- return;
- }
+ case 2: case 3: gen_field(g, depth, false); return;
case 4: put_op(g, PURE_OP_LIST_COUNT, roll(g, 2) ? LANE_L : LANE_M, 0); return;
case 5:
gen_index(g, roll(g, 2) ? LANE_L : LANE_M, depth - 1);
put_op(g, PURE_OP_INDEX_NUM, 0, 0);
return;
- case 6: gen_num(g, depth - 1); put_op(g, PURE_OP_LIST_HAS, LANE_L, 0); return;
- case 7: gen_num(g, depth - 1); put_op(g, unary[roll(g, LEN(unary))], 0, 0); return;
- case 8: gen_num(g, depth - 1); gen_num(g, depth - 1); gen_num(g, depth - 1); put_op(g, CL, 0, 0); return;
- case 9: gen_num(g, depth - 1); put_op(g, PURE_OP_PICK, 0, 0); put_op(g, binary[roll(g, LEN(binary))], 0, 0); return;
- case 10: gen_num(g, depth - 1); gen_num(g, depth - 1); put_op(g, PURE_OP_SLIDE, 1, 0); return;
+ case 6: gen_num(g, depth - 1); put_op(g, unary[roll(g, LEN(unary))], 0, 0); return;
+ case 7: gen_num(g, depth - 1); gen_num(g, depth - 1); gen_num(g, depth - 1); put_op(g, CL, 0, 0); return;
+ case 8: gen_num(g, depth - 1); put_op(g, PURE_OP_PICK, 0, 0); put_op(g, binary[roll(g, LEN(binary))], 0, 0); return;
+ case 9: /* a value of either type dropped under a number */
+ if (roll(g, 2)) gen_bool(g, depth - 1);
+ else gen_num(g, depth - 1);
+ gen_num(g, depth - 1);
+ put_op(g, PURE_OP_SLIDE, 1, 0);
+ return;
default: gen_num(g, depth - 1); gen_num(g, depth - 1); put_op(g, binary[roll(g, LEN(binary))], 0, 0); return;
}
}
-/* What reads only the fold's list, its item and constants (a shaped fold's own parts). */
-static void gen_own(Gen *g, int depth) {
+/* A bool: a constant, an item's flag, a key looked up, numbers or bools
+ * compared, or bools combined. */
+static void gen_bool(Gen *g, int depth) {
+ static const uint8_t compare[] = {PURE_OP_LT, PURE_OP_LE, PURE_OP_GT, PURE_OP_GE, PURE_OP_EQ, PURE_OP_NE};
+ static const uint8_t logic[] = {PURE_OP_AND, PURE_OP_OR, PURE_OP_EQ, PURE_OP_NE};
+ unsigned choice = roll(g, depth <= 0 ? 2 : 10);
+ if (g->tree && (choice == 8 || choice == 9)) choice = 7;
+ switch (choice) {
+ case 0: put_op(g, C, 1, roll(g, 2)); return;
+ case 1: gen_field(g, depth, true); return;
+ case 2: gen_num(g, depth - 1); put_op(g, PURE_OP_LIST_HAS, LANE_L, 0); return;
+ case 3: case 4: gen_num(g, depth - 1); gen_num(g, depth - 1); put_op(g, compare[roll(g, LEN(compare))], 0, 0); return;
+ case 5: gen_bool(g, depth - 1); put_op(g, PURE_OP_NOT, 0, 0); return;
+ case 8: gen_bool(g, depth - 1); put_op(g, PURE_OP_PICK, 0, 0); put_op(g, logic[roll(g, LEN(logic))], 0, 0); return;
+ case 9: /* a value of either type dropped under a bool */
+ if (roll(g, 2)) gen_bool(g, depth - 1);
+ else gen_num(g, depth - 1);
+ gen_bool(g, depth - 1);
+ put_op(g, PURE_OP_SLIDE, 1, 0);
+ return;
+ default: gen_bool(g, depth - 1); gen_bool(g, depth - 1); put_op(g, logic[roll(g, LEN(logic))], 0, 0); return;
+ }
+}
+
+/* What reads only the fold's list, its item and constants (a shaped fold's
+ * own parts): a number, or a bool (flag). */
+static void gen_own(Gen *g, int depth, bool flag) {
static const double pool[] = {0, -0.0, 1, -1, 2, 0.5, 3, 5, 1e308};
- static const uint8_t binary[] = {PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL, PURE_OP_DIV, PURE_OP_LT, PURE_OP_GT,
- PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR, PURE_OP_MIN, PURE_OP_MOD};
+ static const uint8_t binary[] = {PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL, PURE_OP_DIV, PURE_OP_MIN, PURE_OP_MOD};
+ static const uint8_t compare[] = {PURE_OP_LT, PURE_OP_GT, PURE_OP_EQ, PURE_OP_NE};
+ static const uint8_t logic[] = {PURE_OP_AND, PURE_OP_OR, PURE_OP_EQ, PURE_OP_NE};
int list = g->fold_list;
switch (roll(g, depth <= 0 ? 3 : 7)) {
- case 0: put_op(g, C, 0, pool[roll(g, LEN(pool))]); return;
+ case 0:
+ if (flag) put_op(g, C, 1, roll(g, 2));
+ else put_op(g, C, 0, pool[roll(g, LEN(pool))]);
+ return;
case 1: case 2:
if (roll(g, 4)) put_op(g, PURE_OP_ROW, (uint32_t)list, 0);
else put_op(g, roll(g, 2) ? PURE_OP_INDEX_FIRST : PURE_OP_INDEX_LAST, (uint32_t)list, 0);
- put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | (uint32_t)roll(g, list == LANE_L ? 3 : 2) << 16, 0);
+ put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | (flag ? (list == LANE_L ? 3u : 2u) : (uint32_t)roll(g, list == LANE_L ? 3 : 2)) << 16, 0);
+ return;
+ case 3:
+ if (flag) { gen_own(g, depth - 1, false); gen_own(g, depth - 1, false); put_op(g, compare[roll(g, LEN(compare))], 0, 0); }
+ else put_op(g, PURE_OP_LIST_COUNT, (uint32_t)list, 0);
+ return;
+ case 4: gen_own(g, depth - 1, flag); put_op(g, flag ? PURE_OP_NOT : NEG, 0, 0); return;
+ default:
+ gen_own(g, depth - 1, flag);
+ gen_own(g, depth - 1, flag);
+ put_op(g, flag ? logic[roll(g, LEN(logic))] : binary[roll(g, LEN(binary))], 0, 0);
return;
- case 3: put_op(g, PURE_OP_LIST_COUNT, (uint32_t)list, 0); return;
- case 4: gen_own(g, depth - 1); put_op(g, roll(g, 2) ? NEG : PURE_OP_NOT, 0, 0); return;
- default: gen_own(g, depth - 1); gen_own(g, depth - 1); put_op(g, binary[roll(g, LEN(binary))], 0, 0); return;
}
}
@@ -2488,9 +2555,9 @@ static void gen_shaped(Gen *g, int list) {
put_op(g, PURE_OP_FOLD, 0, 0);
int fold_list = g->fold_list, fold_outer = g->outer;
g->fold_list = list, g->outer = LANE_L, g->tree = true;
- if (how != PURE_FOLD_COUNT && how != PURE_FOLD_FIRST) gen_own(g, 2);
+ if (how != PURE_FOLD_COUNT && how != PURE_FOLD_FIRST) gen_own(g, 2, false);
for (unsigned k = 0; k < before; ++k) {
- gen_own(g, 2);
+ gen_own(g, 2, true);
if (k) put_op(g, PURE_OP_AND, 0, 0);
}
bool field_first = roll(g, 2);
@@ -2511,7 +2578,7 @@ static void gen_shaped(Gen *g, int list) {
put_op(g, relations[roll(g, LEN(relations))], 0, 0);
if (before) put_op(g, PURE_OP_AND, 0, 0);
for (unsigned k = 0; k < after; ++k) {
- gen_own(g, 2);
+ gen_own(g, 2, true);
put_op(g, PURE_OP_AND, 0, 0);
}
g->fold_list = fold_list, g->outer = fold_outer, g->tree = false;
@@ -2527,7 +2594,7 @@ static void gen_fold(Gen *g, int list, int outer) {
int fold_list = g->fold_list, fold_outer = g->outer;
g->fold_list = list, g->outer = outer;
if (how != PURE_FOLD_COUNT && how != PURE_FOLD_FIRST) gen_num(g, 3);
- gen_num(g, 3);
+ gen_bool(g, 3);
g->fold_list = fold_list, g->outer = fold_outer;
g->code[at].arg = FOLDING(how, list, g->count - at - 1);
if (how == PURE_FOLD_FIRST) put_op(g, PURE_OP_INDEX_NUM, 0, 0);
@@ -2540,8 +2607,9 @@ static bool same_number(bool a_ok, double a, bool b_ok, double b) {
static void lane_folds(void) {
static const S numbers[] = {{"k", 3}, {"h0", 0}, {"h1", 0}, {"h2", 0}};
static const T lists[] = {{"l", 3 | 1 << 8, 0}, {"m", 3, 1}};
- static const RF l_fields[] = {{"key", 0, 0}, {"a", 0, 0}, {"b", 0, 0}}, m_fields[] = {{"x", 0, 0}, {"y", 0, 0}};
- static const R records[] = {{"lrec", l_fields, 3}, {"mrec", m_fields, 2}};
+ static const RF l_fields[] = {{"key", 0, 0}, {"a", 0, 0}, {"b", 0, 0}, {"c", 2, 0}},
+ m_fields[] = {{"x", 0, 0}, {"y", 0, 0}, {"z", 2, 1}};
+ static const R records[] = {{"lrec", l_fields, 4}, {"mrec", m_fields, 3}};
static const I inputs[] = {KEY("go", "KeyG")};
static const double values[] = {0, -0.0, 1, -1, 2, 3, 0.5, -2.5, 5, 10, 1e308, -7};
enum { VIEWS = 6, COLUMNS = 3, HANDLED = 3 };
@@ -2561,7 +2629,7 @@ static void lane_folds(void) {
}
tables[0] = (W){"t", 2 | (LANE_L + 1) << 8, 0, (uint32_t)g.count, 0};
g.fold_list = LANE_L;
- gen_num(&g, 3);
+ gen_bool(&g, 3);
g.fold_list = -1;
tables[0].length = (uint32_t)(g.count - tables[0].start);
for (unsigned c = 0; c < COLUMNS; ++c) {
@@ -2597,8 +2665,10 @@ static void lane_folds(void) {
size_t l_items = roll(&g, 4) ? roll(&g, 200) : 0, m_items = roll(&g, 4) ? roll(&g, 100) : 0;
for (size_t i = 0; i < l_items + m_items; ++i) {
bool in_l = i < l_items;
- PureCell cells_[3] = {{in_l ? 2.0 * (double)i : values[roll(&g, LEN(values))], NULL, 0},
- {values[roll(&g, LEN(values))], NULL, 0}, {values[roll(&g, LEN(values))], NULL, 0}};
+ double flag = roll(&g, 2);
+ PureCell cells_[4] = {{in_l ? 2.0 * (double)i : values[roll(&g, LEN(values))], NULL, 0},
+ {values[roll(&g, LEN(values))], NULL, 0},
+ {in_l ? values[roll(&g, LEN(values))] : flag, NULL, 0}, {flag, NULL, 0}};
PureList *list_a = a.lists[in_l ? LANE_L : LANE_M], *list_b = b.lists[in_l ? LANE_L : LANE_M];
CHECK(pure_list_insert(list_a, pure_list_count(list_a), cells_, NULL) == PURE_OK &&
pure_list_insert(list_b, pure_list_count(list_b), cells_, NULL) == PURE_OK);
@@ -2678,7 +2748,7 @@ static const O walk_code[] = {
/* 21: n += 1; sum += sum(l.qty for l in order.lines) */
{L, W_N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, W_N, 0},
{L, W_SUM, 0}, {FOLD, FOLDING(PURE_FOLD_SUM, W_LINES, 3), 0}, {ROW, W_LINES, 0}, {IG, ITEM(W_LINES, 1), 0},
- {C, 0, 1}, {ADD, 0, 0}, {ST, W_SUM, 0},
+ {C, 1, 1}, {ADD, 0, 0}, {ST, W_SUM, 0},
/* 32 clean: for s in stock { if s.left == 0 { stock.remove(s) } else if s.left == 1 { stock.remove(stock.first) }
* else { s.left = s.left + 1 } } */
{EACH, WALKING(W_STOCK, 22), 0},
@@ -2823,7 +2893,7 @@ static void walks(void) {
static const O nested[] = {{EACH, WALKING(W_STOCK, 3), 0}, {EACH, WALKING(W_LOG, 2), 0}, {C, 0, 1}, {ST, W_N, 0}};
static const O replied[] = {{EACH, WALKING(W_STOCK, 2), 0}, {FIELD, 1, 0}, {REPLY, 0, 0}};
static const O rows_read[] = {{FIELD, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {REPLY, 0, 0}};
- static const O into_each[] = {{C, 0, 1}, {JZ, 3, 0}, {EACH, WALKING(W_STOCK, 2), 0}, {C, 0, 1}, {ST, W_N, 0}};
+ static const O into_each[] = {{C, 1, 1}, {JZ, 3, 0}, {EACH, WALKING(W_STOCK, 2), 0}, {C, 0, 1}, {ST, W_N, 0}};
static const O view_each[] = {{EACH, WALKING(W_STOCK, 2), 0}, {C, 0, 1}, {ST, W_N, 0}, {C, 0, 1}};
static const H first3[] = {{1, 0, 4}}, first3b[] = {{1, 0, 3}}, first5[] = {{1, 0, 5}};
static const V walk_view[] = {{"v", 0, 4}};
@@ -3302,13 +3372,13 @@ static void pointing(void) {
* key once; found by key; a list that becomes keyed is put in order with
* its indices following their items, or refused when two items share a key. */
enum { FIND = PURE_OP_LIST_FIND, HAS = PURE_OP_LIST_HAS, TCMP = PURE_OP_TEXT_COMPARE,
- PEOPLE = 1, K_AT = 2, SCORES = 3, K_NAME = 0, K_VISITS = 1u << 16 };
+ PEOPLE = 1, K_AT = 2, SCORES = 3, K_FOUND = 4, K_NAME = 0, K_VISITS = 1u << 16 };
static const S keyed_nums[] = {{"n", 0}};
-static const T keyed_states[] = {{"people", 3 | 1 << 8, 0}, {"at", 4, 1}, {"scores", 3 | 1 << 8, 1}};
-static const T unkeyed_states[] = {{"people", 3, 0}, {"at", 4, 1}, {"scores", 3, 1}};
+static const T keyed_states[] = {{"people", 3 | 1 << 8, 0}, {"at", 4, 1}, {"scores", 3 | 1 << 8, 1}, {"found", 5, 0}};
+static const T unkeyed_states[] = {{"people", 3, 0}, {"at", 4, 1}, {"scores", 3, 1}, {"found", 5, 0}};
static const RF person_fields[] = {{"name", 1, 0}, {"visits", 0, 0}};
-static const RF score_fields[] = {{"score", 0, 0}};
-static const R keyed_records[] = {{"person", person_fields, 2}, {"score", score_fields, 1}};
+static const RF score_fields[] = {{"score", 0, 0}}, flag_fields[] = {{"on", 2, 0}};
+static const R keyed_records[] = {{"person", person_fields, 2}, {"score", score_fields, 1}, {"flag", flag_fields, 1}};
static const char *const keyed_constants[] = {"", "bob", "m"};
static const I keyed_inputs[] = {
{"typed", NULL, PURE_KIND_TEXT, false, NULL, 0}, {"seek", NULL, PURE_KIND_TEXT, false, NULL, 0},
@@ -3318,7 +3388,7 @@ static const I keyed_inputs[] = {
static const O keyed_code[] = {
{TI, 0, 0}, {C, 0, 0}, {LA, PEOPLE, 0}, {TI, 0, 0}, {FIND, PEOPLE, 0}, {XS, K_AT, 0}, /* 0 typed: add, point at it */
{TI, 0, 0}, {FIND, PEOPLE, 0}, {XS, K_AT, 0}, /* 6 seek */
- {TI, 0, 0}, {HAS, PEOPLE, 0}, {ST, 0, 0}, /* 9 has */
+ {TI, 0, 0}, {HAS, PEOPLE, 0}, {ST, K_FOUND, 0}, /* 9 has */
{XL, K_AT, 0}, {LR, PEOPLE, 0}, /* 12 drop */
{XL, K_AT, 0}, {XL, K_AT, 0}, {IG, PEOPLE | K_VISITS, 0}, {C, 0, 1}, {ADD, 0, 0}, /* 14 bump */
{IS, PEOPLE | K_VISITS, 0},
@@ -3333,7 +3403,7 @@ static const O keyed_code[] = {
};
static const H keyed_handlers[] = {{1, 0, 6}, {3, 6, 3}, {5, 9, 3}, {7, 12, 2}, {9, 14, 6}, {11, 20, 2}, {13, 22, 2},
{15, 24, 2}, {17, 26, 6}};
-static const V keyed_views[] = {{"count", 32, 1}, {"position", 33, 2}, {"is_bob", 35, 4}, {"before_m", 39, 4},
+static const V keyed_views[] = {{"count", 32, 1}, {"position", 33, 2}, {"is_bob", 35, 4 | BOOL_VIEW}, {"before_m", 39, 4 | BOOL_VIEW},
{"scores_count", 43, 1}};
static const V keyed_text_views[] = {{"current", 44, 2}};
/* Unkeyed, the same states: typed appends and points at the new last item. */
@@ -3344,7 +3414,7 @@ static Spec keyed_spec(const T *states, const O *code, size_t count, const H *ha
bool views = code == keyed_code;
return (Spec){keyed_nums, 1, keyed_inputs, LEN(keyed_inputs), keyed_views, views ? LEN(keyed_views) : 0, handlers,
nhandlers, code, count, NULL, 0, states, LEN(keyed_states), keyed_constants, LEN(keyed_constants),
- keyed_text_views, views ? 1 : 0, keyed_records, 2, NULL, 0};
+ keyed_text_views, views ? 1 : 0, keyed_records, LEN(keyed_records), NULL, 0};
}
static PureResult type_text(PureApp *app, uint32_t input, const char *text) {
@@ -3378,8 +3448,8 @@ static void keyed(void) {
CHECK(type_text(&app, 8, "c") == PURE_CONFLICT && !strcmp(names_of(&app, PEOPLE), "a,b,m,z") && app.values[K_AT] == 0);
CHECK(type_text(&app, 1, "z") == PURE_OK && app.values[K_AT] == 3 && !strcmp(view_text(&app, 5), "z"));
CHECK(type_text(&app, 1, "q") == PURE_NOT_FOUND && app.fault == PURE_FAULT_NOT_FOUND && app.values[K_AT] == 3);
- CHECK(type_text(&app, 2, "m") == PURE_OK && app.values[0] == 1);
- CHECK(type_text(&app, 2, "c") == PURE_OK && app.values[0] == 0);
+ CHECK(type_text(&app, 2, "m") == PURE_OK && app.values[K_FOUND] == 1);
+ CHECK(type_text(&app, 2, "c") == PURE_OK && app.values[K_FOUND] == 0);
CHECK(pure_app_input(&app, 4, PURE_EDGE_DOWN) == PURE_OK && pure_list_get(app.lists[PEOPLE], 3, 1).num == 1);
CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK && !strcmp(names_of(&app, PEOPLE), "a,b,m"));
CHECK(app.values[K_AT] == 2 && number_view(&app, 2) == 0 && number_view(&app, 3) == 0); /* "m": not bob, not before m */
@@ -3436,10 +3506,12 @@ static void keyed(void) {
static const O set_key[] = {{XL, K_AT, 0}, {TK, 0, 0}, {IS, PEOPLE | K_NAME, 0}};
static const O find_number[] = {{C, 0, 1}, {FIND, PEOPLE, 0}, {XS, K_AT, 0}};
static const O find[] = {{TI, 0, 0}, {FIND, PEOPLE, 0}, {XS, K_AT, 0}};
- static const O relation[] = {{TI, 0, 0}, {TI, 0, 0}, {TCMP, PURE_NOT_EQUAL + 1, 0}, {ST, 0, 0}};
- static const O compare_numbers[] = {{C, 0, 1}, {C, 0, 1}, {TCMP, PURE_EQUAL, 0}, {ST, 0, 0}};
- static const T far_key[] = {{"people", 3 | 3 << 8, 0}, {"at", 4, 1}, {"scores", 3, 1}};
- static const T keyed_index[] = {{"people", 3, 0}, {"at", 4 | 1 << 8, 1}, {"scores", 3, 1}};
+ static const O relation[] = {{TI, 0, 0}, {TI, 0, 0}, {TCMP, PURE_NOT_EQUAL + 1, 0}, {ST, K_FOUND, 0}};
+ static const O compare_numbers[] = {{C, 0, 1}, {C, 0, 1}, {TCMP, PURE_EQUAL, 0}, {ST, K_FOUND, 0}};
+ static const O has_number[] = {{TI, 0, 0}, {HAS, PEOPLE, 0}, {ST, 0, 0}}; /* a bool is not a number */
+ static const T far_key[] = {{"people", 3 | 3 << 8, 0}, {"at", 4, 1}, {"scores", 3, 1}, {"found", 5, 0}};
+ static const T keyed_index[] = {{"people", 3, 0}, {"at", 4 | 1 << 8, 1}, {"scores", 3, 1}, {"found", 5, 0}};
+ static const T bool_key[] = {{"people", 3, 0}, {"at", 4, 1}, {"scores", 3 | 1 << 8, 2}, {"found", 5, 0}};
static const struct { const T *states; const O *code; size_t count; const char *reason; } refused[] = {
{keyed_states, insert, LEN(insert), "insert by position into a keyed list"},
{keyed_states, set_key, LEN(set_key), "change of a keyed list's key"},
@@ -3447,6 +3519,8 @@ static void keyed(void) {
{unkeyed_states, find, LEN(find), "no such state"},
{keyed_states, relation, LEN(relation), "no such state"},
{keyed_states, compare_numbers, LEN(compare_numbers), "operand type"},
+ {keyed_states, has_number, LEN(has_number), "operand type"},
+ {bool_key, unkeyed_code, LEN(unkeyed_code), "state reference"}, /* a bool is no key */
{far_key, unkeyed_code, LEN(unkeyed_code), "state reference"},
{keyed_index, unkeyed_code, LEN(unkeyed_code), "state reference"},
};
@@ -3964,7 +4038,7 @@ static void links(void) {
{"ordered", NULL, PURE_KIND_OUTPUT, false, ordered_fields, 3}};
static const O sender_code[] = {{L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, {FIELD, 0, 0}, {FIELD, 1, 0},
{L, 0, 0}, {SEND, 2, 0}, /* buy */
- {FIELD, 0, 0}, {FIELD, 1, 0}, {C, 0, 0}, {SEND, 2, 0}, {C, 0, 0}, {REQ, 0, 0}}; /* fail */
+ {FIELD, 0, 0}, {FIELD, 1, 0}, {C, 0, 0}, {SEND, 2, 0}, {C, 1, 0}, {REQ, 0, 0}}; /* fail */
static const H sender_handlers[] = {{1, 0, 8}, {3, 8, 6}};
Spec sender = {sender_nums, 1, sender_inputs, 3, NULL, 0, sender_handlers, 2, sender_code, LEN(sender_code),
NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0};
@@ -4450,6 +4524,231 @@ static void output_meaning(void) {
remove_store(path);
}
+/* ---- Bools (docs/design/016-bool-and-i64.md) -----------------------------
+ * A bool is its own type: comparisons, has and logic give one; branches,
+ * require, filters and folds' tests take one; a number is never one, nor a
+ * bool a number. It runs as 0 or 1 in a number's place, a message holds it
+ * in a byte, and it is written true or false. A number that becomes a bool
+ * (a module replaced, a stored state or item) keeps 0 and 1, of either sign,
+ * and takes the default for any other; a bool that becomes a number is 0 or 1. */
+enum { B_N = 0, B_ON = 1, B_OFF = 2, B_TOLD = 5 };
+#define TRUTH_LOCAL(slot) ((uint32_t)(slot) | 2u << 8)
+static const S bool_nums[] = {{"n", 0}};
+static const T bool_states[] = {{"on", 5, 1}, {"off", 5, 0}};
+static const F set_fields[] = {{"on", PURE_FIELD_BOOL}, {"n", PURE_FIELD_NUMBER}};
+static const F told_fields[] = {{"on", PURE_FIELD_BOOL}, {"big", PURE_FIELD_BOOL}, {"n", PURE_FIELD_NUMBER}};
+static const I bool_inputs[] = {KEY("flip", "KeyF"), KEY("count", "KeyC"), KEY("check", "KeyK"), KEY("tell", "KeyT"),
+ {"set", NULL, PURE_KIND_MESSAGE, false, set_fields, 2},
+ {"told", NULL, PURE_KIND_OUTPUT, false, told_fields, 3}};
+static const O bool_code[] = {
+ {L, B_ON, 0}, {NOT, 0, 0}, {ST, B_ON, 0}, /* 0 flip: on = !on */
+ {L, B_ON, 0}, {JZ, 9, 0}, {L, B_N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, B_N, 0}, /* 3 count: if on { n += 1 } */
+ {C, 1, 1}, {ST, B_OFF, 0}, {L, B_N, 0}, {C, 0, 3}, {LT, 0, 0}, {REQ, 0, 0}, /* 9 check: off = true; require n < 3 */
+ {L, B_ON, 0}, {L, B_N, 0}, {C, 0, 2}, {GT, 0, 0}, {L, B_N, 0}, {SEND, B_TOLD, 0}, /* 15 tell: told { on, big: n > 2, n } */
+ {FIELD, 0, 0}, {ST, B_ON, 0}, {FIELD, 1, 0}, {ST, B_N, 0}, /* 21 set */
+ {L, B_ON, 0}, /* 25 view lit */
+ {L, B_N, 0}, {C, 0, 2}, {GT, 0, 0}, /* 26 view big */
+ {L, B_ON, 0}, {L, B_OFF, 0}, {EQ, 0, 0}, /* 29 view same */
+ {L, B_ON, 0}, {L, B_OFF, 0}, {PURE_OP_OR, 0, 0}, {L, B_N, 0}, {C, 0, 0}, {PURE_OP_NE, 0, 0},
+ {PURE_OP_AND, 0, 0}, /* 32 view either */
+ {L, B_ON, 0}, {NT, 1, 0}, {C, 0, 1}, {NT, 0, 0}, {CAT, 0, 0}, /* 39 view said */
+};
+static const H bool_handlers[] = {{1, 0, 3}, {3, 3, 6}, {5, 9, 6}, {7, 15, 6}, {9, 21, 4}};
+static const V bool_views[] = {{"lit", 25, 1 | BOOL_VIEW}, {"big", 26, 3 | BOOL_VIEW}, {"same", 29, 3 | BOOL_VIEW},
+ {"either", 32, 7 | BOOL_VIEW}};
+static const V bool_texts[] = {{"said", 39, 5}};
+
+static void bools(void) {
+ Spec spec = {bool_nums, 1, bool_inputs, LEN(bool_inputs), bool_views, LEN(bool_views), bool_handlers,
+ LEN(bool_handlers), bool_code, LEN(bool_code), NULL, 0, bool_states, LEN(bool_states), NULL, 0,
+ bool_texts, 1, NULL, 0, NULL, 0};
+ if (load(spec, &module_a) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ CHECK(module_a.state_type[B_ON] == PURE_STATE_BOOL && module_a.view_type[0] == PURE_VIEW_BOOL &&
+ module_a.input[4].prefix == 9 && module_a.input[B_TOLD].prefix == 10);
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK && app.values[B_ON] == 1 && app.values[B_OFF] == 0);
+ CHECK(number_view(&app, 0) == 1 && number_view(&app, 1) == 0 && number_view(&app, 2) == 0 && number_view(&app, 3) == 0);
+ CHECK(!strcmp(view_text(&app, 4), "true1"));
+ for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(app.values[B_N] == 3 && number_view(&app, 1) == 1 && number_view(&app, 3) == 1);
+ CHECK(pure_app_input(&app, 2, PURE_EDGE_DOWN) == PURE_LIMIT && app.values[B_OFF] == 0); /* require failed: undone */
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && app.values[B_ON] == 0 && !strcmp(view_text(&app, 4), "false1"));
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK && app.values[B_N] == 3 && number_view(&app, 2) == 1);
+ /* A message's bool is a byte, 0 or 1; any other byte fails the message. */
+ unsigned char payload[9] = {1};
+ double five = 5;
+ memcpy(payload + 1, &five, 8);
+ CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 9}) == PURE_OK && app.values[B_ON] == 1 && app.values[B_N] == 5);
+ pure_app_finish(&app);
+ payload[0] = 2;
+ five = 6;
+ memcpy(payload + 1, &five, 8);
+ CHECK(pure_app_message(&app, 4, NULL, -1, (PureSpan){payload, 9}) == PURE_LIMIT && app.values[B_N] == 5);
+ pure_app_finish(&app);
+ /* A send holds its bools as bytes. */
+ CHECK(pure_app_input(&app, 3, PURE_EDGE_DOWN) == PURE_OK);
+ Outbox o = outbox_of(&app);
+ double n;
+ memcpy(&n, o.items[0].payload + 2, 8);
+ CHECK(o.count == 1 && o.items[0].size == 10 && o.items[0].payload[0] == 1 && o.items[0].payload[1] == 1 && n == 5);
+ /* Stored, a waiting send keeps its bools. */
+ char *sends = new_store();
+ PureApp kept;
+ CHECK(pure_app_start(&kept, &module_a, pure_tree_data(sends)) == PURE_OK && pure_app_input(&kept, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(pure_app_input(&kept, 3, PURE_EDGE_DOWN) == PURE_OK && pure_app_save(&kept) == PURE_OK);
+ pure_app_stop(&kept);
+ CHECK(pure_app_start(&kept, &module_a, pure_tree_data(sends)) == PURE_OK);
+ Outbox stored_sends = outbox_of(&kept);
+ CHECK(stored_sends.count == 1 && stored_sends.items[0].size == 10 && stored_sends.items[0].payload[0] == 1 &&
+ stored_sends.items[0].payload[1] == 0);
+ pure_app_stop(&kept);
+ remove_store(sends);
+ /* A link delivers a bool to a bool only; a module's outputs replaced
+ * recast a waiting send's values as its states are kept. */
+ static const unsigned char same[] = {2, PURE_FIELD_BOOL, 2, 'o', 'n', PURE_FIELD_NUMBER, 1, 'n'};
+ static const unsigned char number_on[] = {1, PURE_FIELD_NUMBER, 2, 'o', 'n'}, bool_n[] = {1, PURE_FIELD_BOOL, 1, 'n'};
+ CHECK(pure_module_takes(&module_a, 4, same, sizeof(same)) && !pure_module_takes(&module_a, 4, number_on, sizeof(number_on)) &&
+ !pure_module_takes(&module_a, 4, bool_n, sizeof(bool_n)));
+ PureField told[3] = {{"on", PURE_FIELD_BOOL, 0, 0}, {"big", PURE_FIELD_BOOL, 0, 0}, {"n", PURE_FIELD_NUMBER, 0, 0}};
+ PureField other[3] = {{"n", PURE_FIELD_BOOL, 0, 0}, {"on", PURE_FIELD_NUMBER, 0, 0}, {"big", PURE_FIELD_U32, 0, 0}};
+ CHECK(pure_fields_lay_out(told, 3) == 10 && pure_fields_lay_out(other, 3) == 13);
+ unsigned char *made, *back;
+ size_t size, back_size;
+ CHECK(pure_send_recast(told, 3, other, 3, (PureSpan){o.items[0].payload, 10}, &made, &size) == PURE_OK && size == 13);
+ double on;
+ uint32_t big;
+ memcpy(&on, made + 1, 8);
+ memcpy(&big, made + 9, 4);
+ CHECK(made[0] == 0 && on == 1 && big == 1); /* n = 5 is no bool */
+ CHECK(pure_send_recast(other, 3, told, 3, (PureSpan){made, size}, &back, &back_size) == PURE_OK && back_size == 10);
+ memcpy(&n, back + 2, 8);
+ CHECK(back[0] == 1 && back[1] == 1 && n == 0 && !signbit(n));
+ free(made);
+ free(back);
+ /* Replaced: n (5) becomes a bool (its default, false), on a number (1); back again, 0 and 1. */
+ static const S swapped_nums[] = {{"on", 7}};
+ static const T swapped_states[] = {{"n", 5, 0}, {"off", 5, 1}};
+ Spec swapped = {swapped_nums, 1, bool_inputs, LEN(bool_inputs), NULL, 0, NULL, 0, NULL, 0, NULL, 0, swapped_states, 2,
+ NULL, 0, NULL, 0, NULL, 0, NULL, 0};
+ CHECK(load(swapped, &module_b) == PURE_OK);
+ CHECK(pure_app_replace(&app, &module_b) == PURE_OK && app.values[0] == 1 && app.values[1] == 0 && app.values[2] == 0);
+ app.values[0] = -0.0; /* on, a number: -0 is 0 */
+ CHECK(pure_app_replace(&app, &module_a) == PURE_OK && app.values[B_N] == 0 && app.values[B_ON] == 0 &&
+ !signbit(app.values[B_ON]) && app.values[B_OFF] == 0);
+ pure_app_stop(&app);
+
+ /* What mixes bools and numbers is refused. */
+ static const O add_bools[] = {{C, 1, 1}, {C, 1, 0}, {ADD, 0, 0}, {ST, B_N, 0}};
+ static const O branch_number[] = {{C, 0, 1}, {JZ, 2, 0}};
+ static const O bool_in_number[] = {{C, 1, 1}, {ST, B_N, 0}};
+ static const O number_in_bool[] = {{C, 0, 1}, {ST, B_ON, 0}};
+ static const O order_bools[] = {{C, 1, 1}, {C, 1, 0}, {LT, 0, 0}, {ST, B_ON, 0}};
+ static const O bool_is_number[] = {{C, 1, 1}, {C, 0, 0}, {EQ, 0, 0}, {ST, B_ON, 0}};
+ static const O not_number[] = {{C, 0, 1}, {NOT, 0, 0}, {ST, B_ON, 0}};
+ static const O require_number[] = {{C, 0, 1}, {REQ, 0, 0}};
+ static const O and_number[] = {{C, 1, 1}, {C, 0, 1}, {PURE_OP_AND, 0, 0}, {ST, B_ON, 0}};
+ static const O min_bools[] = {{C, 1, 1}, {C, 1, 1}, {MIN, 0, 0}, {ST, B_N, 0}};
+ static const O neg_bool[] = {{C, 1, 1}, {NEG, 0, 0}, {ST, B_N, 0}};
+ static const O send_number[] = {{C, 1, 1}, {C, 0, 1}, {C, 0, 2}, {SEND, B_TOLD, 0}};
+ static const O local_number[] = {{C, 0, 1}, {LOCAL_S, TRUTH_LOCAL(0), 0}};
+ static const O local_bool[] = {{C, 1, 1}, {LOCAL_S, LOCAL(0, -1), 0}};
+ static const O field_number[] = {{FIELD, 0, 0}, {ST, B_N, 0}};
+ 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 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"},
+ {bool_in_number, LEN(bool_in_number), 1, "operand type"},
+ {number_in_bool, LEN(number_in_bool), 1, "operand type"},
+ {order_bools, LEN(order_bools), 1, "operand type"},
+ {bool_is_number, LEN(bool_is_number), 1, "operand type"},
+ {not_number, LEN(not_number), 1, "operand type"},
+ {require_number, LEN(require_number), 1, "operand type"},
+ {and_number, LEN(and_number), 1, "operand type"},
+ {min_bools, LEN(min_bools), 1, "operand type"},
+ {neg_bool, LEN(neg_bool), 1, "operand type"},
+ {send_number, LEN(send_number), 1, "operand type"},
+ {local_number, LEN(local_number), 1, "operand type"},
+ {local_bool, LEN(local_bool), 1, "operand type"},
+ {field_number, LEN(field_number), 9, "operand type"}, /* set's on is a bool */
+ {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"},
+ };
+ for (size_t i = 0; i < LEN(refused); ++i) {
+ H handler = {refused[i].event, 0, (uint32_t)refused[i].count};
+ Spec bad = spec;
+ bad.o = refused[i].code, bad.no = refused[i].count, bad.h = &handler, bad.nh = 1;
+ bad.v = NULL, bad.nv = 0, bad.tv = NULL, bad.ntv = 0;
+ if (load(bad, &module_b) != PURE_INVALID || !strstr(error, refused[i].reason)) {
+ fprintf(stderr, "bool case %zu: expected '%s', got '%s'\n", i, refused[i].reason, error);
+ abort();
+ }
+ }
+ 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}};
+ 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;
+ bad.o = one_number, bad.v = &as_bool;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view must leave one value of its type"));
+ bad.o = one_bool, bad.v = &as_number;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view must leave one value of its type"));
+ bad.v = NULL, bad.nv = 0, bad.tv = &as_text, bad.ntv = 1, bad.no = 2;
+ bad.o = text_of_number;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type"));
+ bad.o = text_of_bool;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "operand type"));
+ 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 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}};
+ bad = spec;
+ bad.t = two_initial;
+ CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "bool initial value"));
+ bad.t = sixth;
+ 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;
+ 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
+ * the default, false) and done a number again. */
+ char *path = new_store();
+ Spec numbers = {crash_nums, 1, crash_inputs, 1, NULL, 0, crash_handlers, 1, crash_code, LEN(crash_code), NULL, 0,
+ crash_states, 4, crash_constants, 2, NULL, 0, crash_records, 1, NULL, 0};
+ static const T flagged_states[] = {{"n", 5, 0}, {"note", 1, 0}, {"at", 2, 1}, {"tasks", 3, 0}, {"i", 4, 3}};
+ static const RF flagged_fields[] = {{"title", 1, 0}, {"done", 2, 0}};
+ static const R flagged_records[] = {{"task", flagged_fields, 2}};
+ Spec flagged = {NULL, 0, crash_inputs, 1, NULL, 0, NULL, 0, NULL, 0, NULL, 0, flagged_states, 5, crash_constants, 2,
+ NULL, 0, flagged_records, 1, NULL, 0};
+ CHECK(load(numbers, &module_a) == PURE_OK && load(flagged, &module_b) == PURE_OK);
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK);
+ for (int k = 0; k < 3; ++k) CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ pure_app_stop(&app);
+ for (int round = 0; round < 2; ++round) { /* migrated, then stored as migrated */
+ CHECK(pure_app_start(&app, &module_b, pure_tree_data(path)) == PURE_OK && app.values[0] == 0);
+ CHECK(pure_list_count(app.lists[TASKS]) == 3 && pure_list_get(app.lists[TASKS], 0, 1).num == 1 &&
+ pure_list_get(app.lists[TASKS], 1, 1).num == 0 && pure_list_get(app.lists[TASKS], 2, 1).num == 0);
+ pure_app_stop(&app);
+ }
+ CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && pure_list_count(app.lists[TASKS]) == 3);
+ CHECK(pure_list_get(app.lists[TASKS], 0, 1).num == 1 && pure_list_get(app.lists[TASKS], 2, 1).num == 0);
+ pure_app_stop(&app);
+ remove_store(path);
+ printf("{\"bools\":{\"refused\":%zu}}\n", LEN(refused) + 9);
+}
+
/* 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 };
@@ -4566,6 +4865,7 @@ int main(void) {
links();
send_recast();
output_meaning();
+ bools();
memory_budget();
fold_mutations();
pointing();
@@ -4575,6 +4875,6 @@ int main(void) {
stored_states();
deferred();
printf("{\"sizeof_module\":%zu,\"sizeof_app\":%zu}\n", sizeof(PureModule), sizeof(PureApp));
- puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters-keyed-lists-text-order-message-fields-timers-now-snapshots-links\"}");
+ puts("{\"module_tests\":\"verified-load-atomic-events-faults-signed-zero-if-else-bools-replace-by-name-rejections-mutations-messages-documents-typed-verification-text-cursors-utf8-views-rollback-replace-lists-indices-undo-migration-joins-tables-columns-pick-slide-stored-equivalence-crashes-frames-messages-migration-deferred-bursts-pointing-marks-folds-filters-keyed-lists-text-order-message-fields-timers-now-snapshots-links\"}");
return 0;
}