aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-13 05:08:06 +0100
committereunhhu <[email protected]>2026-10-13 05:08:06 +0100
commit5a44af0308017f4e7b54a8ff6ccdc797b5d94015 (patch)
tree18df52e099b435bf1397126e7638e3209b654e1f /core
parent0b39099c536ac01eaa7e1867db0cdb515873b543 (diff)
downloadpure-5a44af0308017f4e7b54a8ff6ccdc797b5d94015.tar.gz
pure-5a44af0308017f4e7b54a8ff6ccdc797b5d94015.zip
Module format: bits and bytes (design 027's stage 1): I64_BITS (and, or, xor, a left shift as a times 2^b failing as overflow past the range, a right shift as floor(a / 2^b), a count outside 0-63 failing as limit: integers in two's complement without end), BYTE_AT (the byte of any bytes value at a whole position, a number or an i64, else limit), BYTES_OF (a list of one i64 field as bytes, a value outside 0-255 failing as limit) and INDEX_AT (a list's item at a position, not_found where there is none); the fold-shape and batch analyses count their arity and the lists they read; unit tests of the ends (27 bit cases, 11 reads of a constant's and a text state's bytes, 7 refused)
Diffstat (limited to 'core')
-rw-r--r--core/module.c75
-rw-r--r--core/module.h15
-rw-r--r--core/test-module.c119
3 files changed, 204 insertions, 5 deletions
diff --git a/core/module.c b/core/module.c
index 0aa51a4..d95769b 100644
--- a/core/module.c
+++ b/core/module.c
@@ -31,10 +31,11 @@ enum { READ = IN_VIEW | IN_HANDLER };
* collection, a constant, a unit, the column's row, a branch target, a stack
* depth, a fold, a relation, a keyed list, an output, the row of the column
* a fold runs in, a number, bool or i64 state (A_VALUE), or the value's type
- * (A_TYPE: 0 a number, 1 a bool, 2 an i64). */
+ * (A_TYPE: 0 a number, 1 a bool, 2 an i64), how I64_BITS computes (A_BITS) or
+ * what a position is (A_POSITION: 0 a number, 1 an i64). */
enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLECTION, A_CONSTANT, A_UNIT,
A_ROW, A_TARGET, A_DEPTH, A_FOLD, A_RELATION, A_KEYED, A_OUTPUT, A_OUTER, A_EACH, A_LOCAL, A_VALUE, A_TYPE,
- A_LOOP };
+ A_LOOP, A_BITS, A_POSITION };
/* A fold's block is verified as a column that leaves one bool (count, first)
* or a value and whether to include it (a bool): a number (FOLD_TWO) or an
* i64 (FOLD_I64). A table's filter is verified as a column that leaves a
@@ -133,6 +134,10 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[PURE_OP_I64_COMPARE] = {2, {T_I64, T_I64}, T_BOOL, READ, A_RELATION, O_ANY},
[PURE_OP_I64_FROM] = {1, {T_NUM}, T_I64, READ, A_NONE, O_ANY},
[PURE_OP_NUM_FROM] = {1, {T_I64}, T_NUM, READ, A_NONE, O_ANY},
+ [PURE_OP_I64_BITS] = {2, {T_I64, T_I64}, T_I64, READ, A_BITS, O_ANY},
+ [PURE_OP_BYTE_AT] = {2, {T_BYTES, T_NUM}, T_I64, READ, A_POSITION, O_ANY},
+ [PURE_OP_BYTES_OF] = {0, {0}, T_BYTES, READ, STATE(LIST), O_ANY},
+ [PURE_OP_INDEX_AT] = {1, {T_NUM}, T_INDEX, READ, STATE(LIST), O_ARG},
};
#undef NUMS
#undef COMPARE
@@ -382,6 +387,8 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
case A_VALUE: return state_of(m, arg, PURE_STATE_NUM) || state_of(m, arg, PURE_STATE_BOOL) || state_of(m, arg, PURE_STATE_I64);
case A_LOOP: return (arg & 0xFF) < PURE_MODULE_LOCALS && !(arg & 0xFE00); /* a slot, and whether i64s */
case A_TYPE: return arg <= 2;
+ case A_BITS: return arg <= PURE_BITS_RIGHT;
+ case A_POSITION: 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;
@@ -496,6 +503,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (op->op == PURE_OP_BREAK && height) REFUSE("break with values on the stack");
if (op->op == PURE_OP_ITEM_SET && m->state_key[list_of(arg)] == item_field(arg) + 1)
REFUSE("change of a keyed list's key");
+ if (op->op == PURE_OP_BYTES_OF && (m->record[m->state_ref[arg]].count != 1 || field_type(m, arg, 0) != T_I64))
+ REFUSE("bytes of a list not of one i64");
/* A view's body changes an event's lists alone: its states are a snapshot's. */
if (body && (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE ||
op->op == PURE_OP_ITEM_SET) &&
@@ -562,6 +571,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
need[0] = state_type(m, arg);
} else if (op->op == PURE_OP_NUM_TEXT && arg) {
need[0] = arg == 1 ? T_BOOL : T_I64;
+ } else if (op->op == PURE_OP_BYTE_AT && arg) {
+ need[1] = T_I64; /* the position an i64 */
} else if ((op->op == PURE_OP_EQ || op->op == PURE_OP_NE) && height >= 2 && types[height - 1] == T_BOOL &&
types[height - 2] == T_BOOL) {
need[0] = need[1] = T_BOOL; /* two bools compare as two numbers do */
@@ -795,7 +806,7 @@ PURE_COLD static int read_list(const PureOp *op) {
switch (op->op) {
case PURE_OP_ITEM_GET: case PURE_OP_ITEM_SET: return (int)list_of(op->arg);
case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST:
- case PURE_OP_ROW: case PURE_OP_OUTER_ROW: return (int)(op->arg & 0xFFFF);
+ case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_BYTES_OF: case PURE_OP_INDEX_AT: return (int)(op->arg & 0xFFFF);
default: return -1;
}
}
@@ -1085,9 +1096,11 @@ PURE_COLD static unsigned arity(uint8_t op) {
switch (op) {
case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_INDEX_LOAD: case PURE_OP_ROW: case PURE_OP_OUTER_ROW:
case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_FIELD: case PURE_OP_LOCAL_LOAD:
+ case PURE_OP_BYTES_OF:
return 0;
case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT:
case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: case PURE_OP_I64_NEG: case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM:
+ case PURE_OP_INDEX_AT:
return 1;
case PURE_OP_CLAMP: return 3;
default: return 2;
@@ -1099,7 +1112,7 @@ PURE_COLD static int list_read(const PureOp *op) {
switch (op->op) {
case PURE_OP_ITEM_GET: return (int)list_of(op->arg);
case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_LIST_FIND:
- case PURE_OP_LIST_HAS: case PURE_OP_ROW:
+ case PURE_OP_LIST_HAS: case PURE_OP_ROW: case PURE_OP_BYTES_OF: case PURE_OP_INDEX_AT:
return (int)op->arg;
default: return -1;
}
@@ -2083,6 +2096,14 @@ static void move_indices(const PureModule *m, PureValue *values, uint32_t list,
+/* x / 2^n rounded down, whatever the sign (027's RIGHT). */
+static inline int64_t floor_shift(int64_t x, unsigned n) { return x >= 0 ? x >> n : ~(~x >> n); }
+
+static bool take_byte(void *context, PureSpan span) {
+ if (span.size) *(unsigned char *)context = span.data[0];
+ return true;
+}
+
/* Append bytes to the arena as a value of it. */
static PureResult keep_bytes(PureArena *arena, const void *bytes, size_t length, PureBytes *out) {
if (!reserve(arena, length)) return PURE_ALLOCATION;
@@ -3428,6 +3449,52 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
top->i64 = cell.i64;
NEXT;
}
+ case PURE_OP_I64_BITS: { /* on integers in two's complement without end (027) */
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64, r;
+ if (arg == PURE_BITS_AND) r = a & b;
+ else if (arg == PURE_BITS_OR) r = a | b;
+ else if (arg == PURE_BITS_XOR) r = a ^ b;
+ else {
+ if (b < 0 || b > 63) { kind = PURE_FAULT_LIMIT; FAILED; }
+ if (arg == PURE_BITS_RIGHT) r = floor_shift(a, (unsigned)b);
+ else if (floor_shift(r = (int64_t)((uint64_t)a << b), (unsigned)b) != a) { kind = PURE_FAULT_OVERFLOW; FAILED; }
+ }
+ stack[sp - 1].i64 = r;
+ NEXT;
+ }
+ case PURE_OP_BYTE_AT: { /* the byte at a whole position inside the value */
+ Slot at = stack[--sp];
+ PureBytes v = stack[sp - 1].bytes;
+ if (arg ? at.i64 < 0 || (uint64_t)at.i64 >= v.length
+ : !(at.num >= 0 && at.num < (double)v.length) || at.num != floor(at.num)) {
+ kind = PURE_FAULT_LIMIT;
+ FAILED;
+ }
+ unsigned char byte = 0;
+ v.offset += arg ? (uint64_t)at.i64 : (uint64_t)at.num;
+ v.length = 1;
+ if ((status = visit_value(app, arena, v, take_byte, &byte)) != PURE_OK) STOPPED(status);
+ stack[sp - 1].i64 = byte;
+ NEXT;
+ }
+ case PURE_OP_BYTES_OF: { /* a list of one i64, an item a byte */
+ const PureList *list = app->lists[arg];
+ size_t count = pure_list_count(list);
+ if (!reserve(arena, count)) STOPPED(PURE_ALLOCATION);
+ for (size_t i = 0; i < count; ++i) {
+ int64_t byte = pure_list_get(list, i, 0).i64;
+ if (byte < 0 || byte > 255) { kind = PURE_FAULT_LIMIT; FAILED; }
+ arena->bytes[arena->used + i] = (unsigned char)byte;
+ }
+ stack[sp++].bytes = (PureBytes){FROM_ARENA, 0, {0, 0}, arena->used, count};
+ arena->used += count;
+ NEXT;
+ }
+ case PURE_OP_INDEX_AT: /* the item at a whole position */
+ if (top->num != floor(top->num)) { kind = PURE_FAULT_LIMIT; FAILED; }
+ if (top->num < 0 || top->num >= (double)pure_list_count(app->lists[arg])) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ top->bytes.owner = (uint16_t)arg;
+ NEXT;
default: break;
}
double b = stack[--sp].num, a = stack[sp - 1].num, value;
diff --git a/core/module.h b/core/module.h
index e10894d..b230fc3 100644
--- a/core/module.h
+++ b/core/module.h
@@ -43,6 +43,7 @@ typedef enum {
PURE_OP_I64_ADD, PURE_OP_I64_SUB, PURE_OP_I64_MUL, PURE_OP_I64_DIV, PURE_OP_I64_MOD, PURE_OP_I64_NEG,
PURE_OP_I64_COMPARE, PURE_OP_I64_MIN, PURE_OP_I64_MAX, PURE_OP_I64_FROM, PURE_OP_NUM_FROM,
PURE_OP_LOOP, PURE_OP_BREAK,
+ PURE_OP_I64_BITS, PURE_OP_BYTE_AT, PURE_OP_BYTES_OF, PURE_OP_INDEX_AT,
PURE_OP_COUNT
} PureOpcode;
/* A bool is its own type (docs/design/016-bool-and-i64.md), held as a
@@ -59,7 +60,19 @@ typedef enum {
* DIVIDE by zero; COMPARE takes a relation as TEXT_COMPARE does and gives a
* bool), I64_FROM makes one of a whole number in range and NUM_FROM a
* number of one within 2^53 (else LIMIT). LOAD, STORE, the item and local
- * opcodes, FIELD and SEND move them as they move numbers. */
+ * opcodes, FIELD and SEND move them as they move numbers.
+ * I64_BITS computes on two i64s as on integers in two's complement without
+ * end (docs/design/027): its argument says how (PureBits). AND, OR and XOR
+ * work bit by bit; LEFT is a * 2^b, failing as OVERFLOW past the range;
+ * RIGHT is floor(a / 2^b); a shift by b outside 0-63 fails as LIMIT.
+ * BYTE_AT gives the byte of a bytes value at a position (argument 0: a
+ * number, 1: an i64) as an i64 0-255, failing as LIMIT where there is none
+ * (not a whole number, or outside the value). BYTES_OF makes the bytes of a
+ * list state whose record is one i64 field, an item a byte (a value outside
+ * 0-255 fails as LIMIT). INDEX_AT makes the index of a list state's item at
+ * a position (a number), failing as LIMIT if it is not a whole number and
+ * as NOT_FOUND where there is no item. */
+typedef enum { PURE_BITS_AND, PURE_BITS_OR, PURE_BITS_XOR, PURE_BITS_LEFT, PURE_BITS_RIGHT } PureBits;
/* A local: one of an event's PURE_MODULE_LOCALS slots, 0 when the event
* begins and never saved. LOCAL_LOAD and LOCAL_STORE name the slot (bits
* 0-7), what it holds (bits 8-15: 0 a number, 1 an index, 2 a bool) and an index's
diff --git a/core/test-module.c b/core/test-module.c
index 42f01af..dca1b67 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -6013,6 +6013,124 @@ static void integers(void) {
pure_save_test_checkpoints - checkpoints);
}
+/* ---- Bits and bytes (docs/design/027, stage 1) ---------------------------
+ * I64_BITS works on integers in two's complement without end: a left shift
+ * past the range fails as overflow, a right shift rounds down, a shift
+ * outside 0-63 fails as limit. BYTE_AT reads the byte at a whole position
+ * of any bytes value (here a constant's and a text state's), BYTES_OF makes
+ * the bytes of a list of one i64, INDEX_AT finds a list's item by position,
+ * failing where there is none. */
+enum { BITS = PURE_OP_I64_BITS, BAT = PURE_OP_BYTE_AT, BOF = PURE_OP_BYTES_OF, XAT = PURE_OP_INDEX_AT };
+
+/* Code run as a key's handler over n (a number), note (text "Az\xc3\xa9"),
+ * bytes (a list of Byte { v: i64 }), i (an i64) and said (text): the fault,
+ * and n, i, said and the list's count after it. */
+typedef struct { PureFault fault; double n; int64_t i; char said[64]; size_t count; } Byted;
+static Byted run_bytes(const O *code, uint32_t count) {
+ static const S nums[] = {{"n", 0}};
+ static const T others[] = {{"note", 1, 0}, {"bytes", 3, 0}, {"i", PURE_STATE_I64, 0}, {"said", 1, 1}};
+ static const RF byte_fields[] = {{"v", 3, 0}};
+ static const R records[] = {{"Byte", byte_fields, 1}};
+ static const char *const constants[] = {"Az\xc3\xa9", ""};
+ static const I inputs[] = {KEY("go", "KeyG")};
+ H handler = {1, 0, count};
+ Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, code, count, NULL, 0, others, LEN(others), constants,
+ LEN(constants), NULL, 0, records, 1, NULL, 0};
+ if (load(spec, &module_b) != PURE_OK) { fprintf(stderr, "%s\n", error); abort(); }
+ PureApp app;
+ CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK);
+ Byted ran = {pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK ? PURE_FAULT_NONE : app.fault, app.values[0].num,
+ app.values[3].i64, {0}, pure_list_count(app.lists[2])};
+ snprintf(ran.said, sizeof(ran.said), "%s", state_text(&app, 4));
+ pure_app_stop(&app);
+ return ran;
+}
+
+static void bits_and_bytes(void) {
+ enum { AND = PURE_BITS_AND, OR = PURE_BITS_OR, XOR = PURE_BITS_XOR, LEFT = PURE_BITS_LEFT, RIGHT = PURE_BITS_RIGHT };
+ static const struct { uint32_t how; int64_t a, b; PureFault fault; int64_t result; } cases[] = {
+ {AND, 0xF0F0, 0x0FF0, PURE_FAULT_NONE, 0x00F0}, {AND, -1, 5, PURE_FAULT_NONE, 5},
+ {AND, INT64_MIN, -1, PURE_FAULT_NONE, INT64_MIN}, {AND, -8, 0xFF, PURE_FAULT_NONE, 0xF8},
+ {OR, 0xF000, 0x000F, PURE_FAULT_NONE, 0xF00F}, {OR, -8, 3, PURE_FAULT_NONE, -5},
+ {XOR, 5, -1, PURE_FAULT_NONE, -6}, {XOR, 0x55, 0xFF, PURE_FAULT_NONE, 0xAA}, {XOR, INT64_MAX, -1, PURE_FAULT_NONE, INT64_MIN},
+ {LEFT, 1, 62, PURE_FAULT_NONE, INT64_C(1) << 62}, {LEFT, 1, 63, PURE_FAULT_OVERFLOW, 0},
+ {LEFT, -1, 63, PURE_FAULT_NONE, INT64_MIN}, {LEFT, 3, 62, PURE_FAULT_OVERFLOW, 0}, {LEFT, 0, 63, PURE_FAULT_NONE, 0},
+ {LEFT, 0x80000000, 32, PURE_FAULT_OVERFLOW, 0}, {LEFT, 0xFFFFFFFF, 7, PURE_FAULT_NONE, INT64_C(0x7FFFFFFF80)},
+ {LEFT, -3, 2, PURE_FAULT_NONE, -12}, {LEFT, 5, -1, PURE_FAULT_LIMIT, 0}, {LEFT, 5, 64, PURE_FAULT_LIMIT, 0},
+ {RIGHT, 7, 1, PURE_FAULT_NONE, 3}, {RIGHT, -7, 1, PURE_FAULT_NONE, -4}, {RIGHT, INT64_MIN, 63, PURE_FAULT_NONE, -1},
+ {RIGHT, -1, 63, PURE_FAULT_NONE, -1}, {RIGHT, 1, 63, PURE_FAULT_NONE, 0}, {RIGHT, INT64_MAX, 0, PURE_FAULT_NONE, INT64_MAX},
+ {RIGHT, 5, 64, PURE_FAULT_LIMIT, 0}, {RIGHT, 5, -64, PURE_FAULT_LIMIT, 0},
+ };
+ for (size_t k = 0; k < LEN(cases); ++k) {
+ O code[] = {IC(cases[k].a), IC(cases[k].b), {BITS, cases[k].how, 0}, {ST, 1, 0}};
+ Ran ran = run_code(code, LEN(code));
+ if (ran.fault != cases[k].fault || ran.values[1].i64 != (cases[k].fault ? 0 : cases[k].result)) {
+ fprintf(stderr, "bits case %zu: fault %d, %" PRId64 "\n", k, (int)ran.fault, ran.values[1].i64);
+ abort();
+ }
+ }
+ /* A byte at a position: of a constant's bytes, of a text state's, by a number or an i64. */
+ static const struct { uint32_t text_state; uint32_t as_i64; double at; PureFault fault; int64_t byte; } reads[] = {
+ {0, 0, 0, PURE_FAULT_NONE, 'A'}, {0, 0, 3, PURE_FAULT_NONE, 0xA9}, {0, 1, 2, PURE_FAULT_NONE, 0xC3},
+ {0, 0, 4, PURE_FAULT_LIMIT, 0}, {0, 0, -1, PURE_FAULT_LIMIT, 0}, {0, 0, 1.5, PURE_FAULT_LIMIT, 0},
+ {0, 1, 4, PURE_FAULT_LIMIT, 0}, {0, 1, -1, PURE_FAULT_LIMIT, 0}, {1, 0, 1, PURE_FAULT_NONE, 'z'},
+ {1, 1, 3, PURE_FAULT_NONE, 0xA9}, {1, 0, 4, PURE_FAULT_LIMIT, 0},
+ };
+ for (size_t k = 0; k < LEN(reads); ++k) {
+ O position = reads[k].as_i64 ? IC((int64_t)reads[k].at) : (O){C, 0, reads[k].at};
+ O code[] = {reads[k].text_state ? (O){TL, 1, 0} : (O){TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, position,
+ {BAT, reads[k].as_i64, 0}, {ST, 3, 0}};
+ Byted ran = run_bytes(code, LEN(code));
+ if (ran.fault != reads[k].fault || ran.i != reads[k].byte) {
+ fprintf(stderr, "byte read %zu: fault %d, %" PRId64 "\n", k, (int)ran.fault, ran.i);
+ abort();
+ }
+ }
+ /* Bytes of a list of one i64: said = text(bytes(list)); a value past a byte fails and leaves nothing. */
+ O made[] = {IC('H'), {LA, 2, 0}, IC('i'), {LA, 2, 0}, {BOF, 2, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ Byted ran = run_bytes(made, LEN(made));
+ CHECK(ran.fault == PURE_FAULT_NONE && !strcmp(ran.said, "Hi") && ran.count == 2);
+ O none[] = {{BOF, 2, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ ran = run_bytes(none, LEN(none));
+ CHECK(ran.fault == PURE_FAULT_NONE && !strcmp(ran.said, "") && ran.count == 0);
+ O past[] = {IC('H'), {LA, 2, 0}, IC(256), {LA, 2, 0}, {BOF, 2, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ ran = run_bytes(past, LEN(past));
+ CHECK(ran.fault == PURE_FAULT_LIMIT && !strcmp(ran.said, "") && ran.count == 0);
+ O negative[] = {IC(-1), {LA, 2, 0}, {BOF, 2, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ CHECK(run_bytes(negative, LEN(negative)).fault == PURE_FAULT_LIMIT);
+ /* An item by position: i = bytes[at].v, where there is one. */
+ static const struct { double at; PureFault fault; int64_t v; } items[] = {
+ {0, PURE_FAULT_NONE, 7}, {1, PURE_FAULT_NONE, 9}, {2, PURE_FAULT_NOT_FOUND, 0}, {-1, PURE_FAULT_NOT_FOUND, 0},
+ {0.5, PURE_FAULT_LIMIT, 0}};
+ for (size_t k = 0; k < LEN(items); ++k) {
+ O code[] = {IC(7), {LA, 2, 0}, IC(9), {LA, 2, 0}, {C, 0, items[k].at}, {XAT, 2, 0}, {IG, ITEM(2, 0), 0}, {ST, 3, 0}};
+ Byted at = run_bytes(code, LEN(code));
+ CHECK(at.fault == items[k].fault && at.i == items[k].v && at.count == (items[k].fault ? 0 : 2));
+ }
+ /* Refused: bits of numbers or of no such kind, a byte of a text, by a position of the other type or of no such kind,
+ * bytes of a number state, an item by an i64's position. */
+ const O refused[][4] = {
+ {{C, 0, 1}, {C, 0, 2}, {BITS, AND, 0}, {ST, 0, 0}}, {IC(1), IC(2), {BITS, RIGHT + 1, 0}, {ST, 3, 0}},
+ {{TK, 0, 0}, {C, 0, 0}, {BAT, 0, 0}, {ST, 3, 0}}, {{TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, IC(0), {BAT, 0, 0}},
+ {{TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {C, 0, 0}, {BAT, 2, 0}}, {{BOF, 0, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}},
+ {IC(0), {XAT, 2, 0}, {IG, ITEM(2, 0), 0}, {ST, 3, 0}},
+ };
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ static const S nums[] = {{"n", 0}};
+ static const T others[] = {{"note", 1, 0}, {"bytes", 3, 0}, {"i", PURE_STATE_I64, 0}, {"said", 1, 1}};
+ static const RF byte_fields[] = {{"v", 3, 0}};
+ static const R records[] = {{"Byte", byte_fields, 1}};
+ static const char *const constants[] = {"Az\xc3\xa9", ""};
+ static const I inputs[] = {KEY("go", "KeyG")};
+ uint32_t length = k == 5 ? 3 : 4;
+ H handler = {1, 0, length};
+ Spec spec = {nums, 1, inputs, 1, NULL, 0, &handler, 1, refused[k], length, NULL, 0, others, LEN(others), constants,
+ LEN(constants), NULL, 0, records, 1, NULL, 0};
+ if (load(spec, &module_b) == PURE_OK) { fprintf(stderr, "bits and bytes: refused %zu loaded\n", k); abort(); }
+ }
+ printf("{\"bits_and_bytes\":{\"bits\":%zu,\"reads\":%zu,\"refused\":%zu}}\n", LEN(cases), LEN(reads), LEN(refused));
+}
+
/* 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 };
@@ -6386,6 +6504,7 @@ int main(void) {
output_meaning();
bools();
integers();
+ bits_and_bytes();
memory_budget();
work_budget();
fold_mutations();