From bfa54e02881f56b3a969220ecaac706542046149 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 15:24:56 +0100 Subject: Give bools their own type in the module format (v6, 016 stage 1) A condition was a number, non-zero being true: a flag was a number field, a view showed 1 for true, and nothing kept a number from standing for a bool or a bool from being added to. Now the verifier has a bool among its stack types, and a verified module never mixes the two. No opcode is new: - CONST with argument 1 is a bool (+0 or 1), NUM_TEXT with argument 1 writes one as true or false; LOAD and STORE take a number or a bool state, locals may hold a bool (2). - Comparisons (LT to NE, TEXT_COMPARE) and LIST_HAS give a bool; AND, OR and NOT take and give them; JZ, REQUIRE, filters and folds' tests take one (count and first leave a bool, sum, max, min and distinct a number and a bool); EQ and NE compare two bools too. - Type codes: state 5, record field 2, message field 6 (one byte in the prefix, 0 or 1, or the message fails as limit), view 3. A bool's constant, initial value and default are +0 or 1; a list is not keyed by one. PUREMOD6: v5 modules are built again. At run time a bool is a number's 0 or 1, so leaf-at-a-time folds run as before. A number that becomes a bool (a module replaced, a stored state or item read again, a waiting send recast) keeps 0 (either sign) and 1 and takes the default otherwise; a bool that becomes a number is 0 or 1. The host carries bools on every boundary: JSON true and false only (a row's field too), a query's true and false, true and false in views, table JSON and HTTP deliveries; a link takes a bool only into a bool. A stored outbox's field types were read back with a check that refused bool (at or past rows): a node whose output had a bool field and a send waiting could not start again. test-module now stores such a send and opens it (it fails on the old check), beside 28 bool refusals of the verifier and bool states, fields, views, sends, links and migration. The lane generator makes numbers and bools apart. --- core/module-save.c | 28 ++- core/module-save.h | 22 ++- core/module.c | 227 ++++++++++++++++--------- core/module.h | 64 ++++--- core/test-module.c | 490 ++++++++++++++++++++++++++++++++++++++++++----------- 5 files changed, 616 insertions(+), 215 deletions(-) (limited to 'core') 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 #include -/* 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; } -- cgit v1.2.3