aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-13 06:16:12 +0100
committereunhhu <[email protected]>2026-10-13 06:16:12 +0100
commit8568c8732ca36673b073688bdf99cc25d6b8065a (patch)
tree85929b84713004f5fd00d97e14cbefed7f8c6a90 /core
parent109ea31ef2ca06210e196c1b8fa7005cd9ef5ce9 (diff)
downloadpure-8568c8732ca36673b073688bdf99cc25d6b8065a.tar.gz
pure-8568c8732ca36673b073688bdf99cc25d6b8065a.zip
Module format and executor: 027's stage 2a: BYTES_SLICE (bytes from a up to before c, whole positions inside, numbers or i64s), BYTE (one byte of an i64 0-255), BYTES_LOAD and BYTES_STORE (an event's 32 values held by name, 0-15 bytes and 16-31 texts so a text's hold UTF-8 alone, emptied as an event or a view's body begins, appended to in place, counted in the room: MEMORY past it), CONCAT of bytes, NUM_TEXT in a format (a width padded with spaces or with zeros after the sign, d x X b o of an i64 or a whole number, f with 0-30 digits; -0 as 0), a reply of a held value kept by value, LEN wherever a value is read (it had been for handlers alone); pure-run's message NAME @PATH (the file's bytes, of any size); unit tests (16 formats, 9 slices, held values set, appended, set to a part of themselves, emptied each event, a doubling stopped by the room, a reply kept, 7 refused)
Diffstat (limited to 'core')
-rw-r--r--core/module.c167
-rw-r--r--core/module.h22
-rw-r--r--core/test-module.c117
3 files changed, 290 insertions, 16 deletions
diff --git a/core/module.c b/core/module.c
index d95769b..76a6821 100644
--- a/core/module.c
+++ b/core/module.c
@@ -18,7 +18,7 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES =
* state named. */
enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7, T_KEY = 8, T_LOCAL = 9,
T_BOOL = 10, T_STATE = 11, T_I64 = 12 };
-enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS };
+enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS, FROM_HELD };
/* 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). */
/* IN_LOCALS: a handler's locals, read in its walks and the folds in them. */
@@ -31,11 +31,13 @@ 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), how I64_BITS computes (A_BITS) or
- * what a position is (A_POSITION: 0 a number, 1 an i64). */
+ * (A_TYPE: 0 a number, 1 a bool, 2 an i64), how I64_BITS computes (A_BITS),
+ * what a position is (A_POSITION: 0 a number, 1 an i64), what CONCAT joins
+ * (A_JOINED: 0 texts, 1 bytes), how NUM_TEXT writes (A_FORMAT) or the value
+ * held by name (A_HELD). */
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_BITS, A_POSITION };
+ A_LOOP, A_BITS, A_POSITION, A_JOINED, A_FORMAT, A_HELD };
/* 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
@@ -73,15 +75,15 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[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},
[PURE_OP_DOC_SIZE] = {0, {0}, T_NUM, IN_KEYED, A_COLLECTION, O_ANY},
- [PURE_OP_LEN] = {1, {T_BYTES}, T_NUM, IN_HANDLER, A_NONE, O_ANY},
+ [PURE_OP_LEN] = {1, {T_BYTES}, T_NUM, READ, 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_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_TYPE, O_ANY},
- [PURE_OP_CONCAT] = {2, {T_TEXT, T_TEXT}, T_TEXT, READ, A_NONE, O_ANY},
+ [PURE_OP_NUM_TEXT] = {1, {T_NUM}, T_TEXT, READ, A_FORMAT, O_ANY},
+ [PURE_OP_CONCAT] = {2, {T_TEXT, T_TEXT}, T_TEXT, READ, A_JOINED, 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},
[PURE_OP_TEXT_BYTES] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY},
@@ -138,6 +140,10 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi
[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},
+ [PURE_OP_BYTES_SLICE] = {3, {T_BYTES, T_NUM, T_NUM}, T_BYTES, READ, A_POSITION, O_ANY},
+ [PURE_OP_BYTE] = {1, {T_I64}, T_BYTES, READ, A_NONE, O_ANY},
+ [PURE_OP_BYTES_LOAD] = {0, {0}, T_BYTES, IN_LOCALS, A_HELD, O_ANY},
+ [PURE_OP_BYTES_STORE] = {1, {T_BYTES}, 0, IN_HANDLER | IN_COMPUTE, A_HELD, O_ANY},
};
#undef NUMS
#undef COMPARE
@@ -388,7 +394,13 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
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_POSITION: case A_JOINED: return arg <= 1;
+ case A_HELD: return (arg & ~(uint32_t)PURE_HELD_APPEND) < PURE_MODULE_HELD;
+ case A_FORMAT: { /* a type; a width, zeros, a form fitting it and digits for FIXED alone */
+ uint32_t type = arg & 3, form = arg >> PURE_FORMAT_FORM & 7, digits = arg >> PURE_FORMAT_DIGITS & 31;
+ return type <= 2 && !(arg & 0xFC) && !(arg >> 25) && form <= PURE_FORMAT_FIXED && (type != 1 || !form) &&
+ (form != PURE_FORMAT_FIXED || type == 0) && (form == PURE_FORMAT_FIXED || !digits);
+ }
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;
@@ -569,8 +581,14 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
need[0] = local_type(arg);
} else if (need[0] == T_STATE) {
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_NUM_TEXT && arg & 3) {
+ need[0] = (arg & 3) == 1 ? T_BOOL : T_I64;
+ } else if (op->op == PURE_OP_CONCAT && arg) {
+ need[0] = need[1] = T_BYTES;
+ } else if (op->op == PURE_OP_BYTES_SLICE && arg) {
+ need[1] = need[2] = T_I64; /* the positions i64s */
+ } else if (op->op == PURE_OP_BYTES_STORE && (arg & 0xFF) >= PURE_HELD_TEXT) {
+ need[0] = T_TEXT;
} 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 &&
@@ -611,6 +629,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (out == T_STATE) out = state_type(m, arg);
if (truth) out = T_BOOL;
if (integer) out = T_I64;
+ if ((op->op == PURE_OP_CONCAT && arg) || (op->op == PURE_OP_BYTES_LOAD && (arg & 0xFF) < PURE_HELD_TEXT)) out = T_BYTES;
+ else if (op->op == PURE_OP_BYTES_LOAD) out = T_TEXT;
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);
@@ -1096,13 +1116,13 @@ 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:
+ case PURE_OP_BYTES_OF: case PURE_OP_BYTES_LOAD:
return 0;
case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT:
case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: case PURE_OP_I64_NEG: case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM:
- case PURE_OP_INDEX_AT:
+ case PURE_OP_INDEX_AT: case PURE_OP_BYTE:
return 1;
- case PURE_OP_CLAMP: return 3;
+ case PURE_OP_CLAMP: case PURE_OP_BYTES_SLICE: return 3;
default: return 2;
}
}
@@ -2007,6 +2027,7 @@ static PureResult visit_value(const PureApp *app, const PureArena *arena, PureBy
case FROM_COPY: direct = app->copied + v.offset; break;
case FROM_CONSTANT: direct = app->module->constant_data + v.offset; break;
case FROM_ARENA: direct = arena->bytes + v.offset; break;
+ case FROM_HELD: direct = app->held[v.owner].data + v.offset; break;
case FROM_TEXT:
return pure_draft_read_version(app->texts[v.owner], v.version, (size_t)v.offset, (size_t)v.length,
visit, context);
@@ -2040,8 +2061,8 @@ static PureResult contiguous(const PureApp *app, PureArena *arena, PureBytes v,
*out = (PureSpan){v.length ? base + v.offset : NULL, (size_t)v.length};
return PURE_OK;
}
- if (v.source == FROM_ARENA) {
- *out = (PureSpan){arena->bytes + v.offset, (size_t)v.length};
+ if (v.source == FROM_ARENA || v.source == FROM_HELD) {
+ *out = (PureSpan){(v.source == FROM_ARENA ? arena->bytes : app->held[v.owner].data) + v.offset, (size_t)v.length};
return PURE_OK;
}
size_t start = arena->used;
@@ -2125,6 +2146,71 @@ static bool copy_out(void *context, PureSpan span) {
return true;
}
+/* BYTES_STORE: the value held by name slot becomes v, or v after it (append); what it holds counts in the room.
+ * 1 where that passes the room (MEMORY), 2 out of memory, else 0. */
+static int hold(PureApp *app, PureArena *arena, unsigned slot, PureBytes v, bool append) {
+ __typeof__(app->held[0]) *h = &app->held[slot];
+ size_t mark = arena->used, before = h->size, at = append ? h->size : 0, size = at + (size_t)v.length;
+ if (v.source == FROM_HELD && v.owner == slot && v.length) { /* a part of itself: by way of the arena */
+ if (append_value(app, arena, v) != PURE_OK) return 2;
+ v = (PureBytes){FROM_ARENA, 0, {0, 0}, mark, v.length};
+ }
+ if (!grows(app, (Held){0, before}, (Held){0, size})) return 1;
+ if (size > h->capacity) {
+ size_t capacity = h->capacity ? h->capacity : 64;
+ while (capacity < size) capacity *= 2;
+ unsigned char *data = realloc(h->data, capacity);
+ if (!data) return 2;
+ h->data = data, h->capacity = capacity;
+ }
+ Copy copy = {h->data + at};
+ if (visit_value(app, arena, v, copy_out, &copy) != PURE_OK) return 2;
+ h->size = size;
+ arena->used = mark;
+ return 0;
+}
+
+/* NUM_TEXT's text of v in a format (the argument past its type, PureFormat): false where an integer's form meets a
+ * number that is not a whole one within 2^53. */
+static bool formatted(Slot v, uint32_t arg, char *out, size_t *length) {
+ unsigned type = arg & 3, width = arg >> PURE_FORMAT_WIDTH & 0xFF, form = arg >> PURE_FORMAT_FORM & 7,
+ digits = arg >> PURE_FORMAT_DIGITS & 31;
+ char body[400];
+ size_t n = 0;
+ if (form == PURE_FORMAT_FIXED) {
+ n = (size_t)snprintf(body, sizeof(body), "%.*f", digits ? (int)digits - 1 : 6, v.num == 0 ? 0.0 : v.num);
+ } else if (form) {
+ int64_t i = v.i64;
+ if (type != 2) {
+ if (!(fabs(v.num) <= 0x1p53 && v.num == floor(v.num))) return false;
+ i = (int64_t)v.num;
+ }
+ uint64_t magnitude = i < 0 ? 0 - (uint64_t)i : (uint64_t)i;
+ unsigned base = form == PURE_FORMAT_DECIMAL ? 10 : form <= PURE_FORMAT_HEX_UPPER ? 16 : form == PURE_FORMAT_BINARY ? 2 : 8;
+ const char *set = form == PURE_FORMAT_HEX_UPPER ? "0123456789ABCDEF" : "0123456789abcdef";
+ char reversed[64];
+ unsigned count = 0;
+ do reversed[count++] = set[magnitude % base], magnitude /= base; while (magnitude);
+ if (i < 0) body[n++] = '-';
+ while (count) body[n++] = reversed[--count];
+ } else
+ n = type == 2 ? (size_t)snprintf(body, sizeof(body), "%" PRId64, v.i64)
+ : type ? (size_t)snprintf(body, sizeof(body), "%s", v.num ? "true" : "false") : pure_format_number(v.num, body);
+ size_t sign = body[0] == '-', pad = width > n ? width - n : 0, k = 0;
+ if (arg & PURE_FORMAT_ZEROS) {
+ if (sign) out[k++] = '-';
+ while (pad--) out[k++] = '0';
+ memcpy(out + k, body + sign, n - sign);
+ k += n - sign;
+ } else {
+ while (pad--) out[k++] = ' ';
+ memcpy(out + k, body, n);
+ k += n;
+ }
+ *length = k;
+ return true;
+}
+
/* An output's message from its field values, laid out as a message's
* payload (size bytes), numbered and kept with the event's changes. */
static PureResult send(PureApp *app, uint32_t output, const Slot *fields, const PureArena *arena, size_t size) {
@@ -3097,7 +3183,17 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
NEXT;
}
/* A reply outlives the arena mark of later statements: keep it by value. */
- case PURE_OP_REPLY: app->reply = stack[--sp].bytes; app->has_reply = true; NEXT;
+ case PURE_OP_REPLY: { /* by value; a value held by name may change after it, so its bytes go to the arena */
+ PureBytes reply = stack[--sp].bytes;
+ if (reply.source == FROM_HELD) {
+ size_t start = arena->used;
+ if ((status = append_value(app, arena, reply)) != PURE_OK) STOPPED(status);
+ reply = (PureBytes){FROM_ARENA, 0, {0, 0}, start, reply.length};
+ }
+ app->reply = reply;
+ app->has_reply = true;
+ NEXT;
+ }
case PURE_OP_REQUIRE: if (stack[--sp].num == 0) { kind = PURE_FAULT_LIMIT; FAILED; } NEXT;
case PURE_OP_TEXT_CONST:
stack[sp++].bytes = (PureBytes){FROM_CONSTANT, 0, {0, 0}, m->constant_offset[arg], m->constant_length[arg]};
@@ -3106,6 +3202,13 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
case PURE_OP_TEXT_INPUT: stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, 0, app->payload.size}; NEXT;
case PURE_OP_NUM_TEXT: {
char digits[32];
+ if (arg > 2) { /* in a format */
+ char text[700];
+ size_t length;
+ if (!formatted(*top, arg, text, &length)) { kind = PURE_FAULT_LIMIT; FAILED; }
+ if ((status = keep_bytes(arena, text, length, &top->bytes)) != PURE_OK) STOPPED(status);
+ NEXT;
+ }
size_t length = arg == 2 ? (size_t)snprintf(digits, sizeof(digits), "%" PRId64, top->i64)
: arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false")
: pure_format_number(top->num, digits);
@@ -3495,6 +3598,35 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp
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;
+ case PURE_OP_BYTES_SLICE: { /* from a up to before c, both whole and within */
+ Slot high = stack[--sp], low = stack[--sp];
+ PureBytes *v = &stack[sp - 1].bytes;
+ if (arg ? low.i64 < 0 || high.i64 < low.i64 || (uint64_t)high.i64 > v->length
+ : !(low.num >= 0 && high.num >= low.num && high.num <= (double)v->length) || low.num != floor(low.num) ||
+ high.num != floor(high.num)) {
+ kind = PURE_FAULT_LIMIT;
+ FAILED;
+ }
+ uint64_t from = arg ? (uint64_t)low.i64 : (uint64_t)low.num, to = arg ? (uint64_t)high.i64 : (uint64_t)high.num;
+ v->offset += from;
+ v->length = to - from;
+ NEXT;
+ }
+ case PURE_OP_BYTE: {
+ if (top->i64 < 0 || top->i64 > 255) { kind = PURE_FAULT_LIMIT; FAILED; }
+ unsigned char byte = (unsigned char)top->i64;
+ if ((status = keep_bytes(arena, &byte, 1, &top->bytes)) != PURE_OK) STOPPED(status);
+ NEXT;
+ }
+ case PURE_OP_BYTES_LOAD:
+ stack[sp++].bytes = (PureBytes){FROM_HELD, (uint16_t)(arg & 0xFF), {0, 0}, 0, app->held[arg & 0xFF].size};
+ NEXT;
+ case PURE_OP_BYTES_STORE: {
+ int held = hold(app, arena, arg & 0xFF, stack[--sp].bytes, arg & PURE_HELD_APPEND);
+ if (held == 1) { kind = PURE_FAULT_MEMORY; FAILED; }
+ if (held) STOPPED(PURE_ALLOCATION);
+ NEXT;
+ }
default: break;
}
double b = stack[--sp].num, a = stack[sp - 1].num, value;
@@ -3916,6 +4048,7 @@ void pure_app_stop(PureApp *app) {
pure_snapshot_release(app->snapshot);
}
pure_retired_free(&app->retired);
+ for (unsigned i = 0; i < PURE_MODULE_HELD; ++i) free(app->held[i].data);
pure_draft_close(app->stored_values);
free(app->carried);
pure_draft_close(app->stored_links);
@@ -4051,6 +4184,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) {
* holds for it the default room, or the budget when that is larger. */
app->grown = app->spent = 0;
app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
+ for (unsigned i = 0; i < PURE_MODULE_HELD; ++i) app->held[i].size = 0;
Mark mark;
mark_event(app, &mark);
memcpy(pending, app->values, sizeof(app->values));
@@ -4398,6 +4532,7 @@ static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) {
if (!own_lists(app, m->view_lists[view])) return PURE_ALLOCATION;
app->grown = app->spent = 0;
app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT;
+ for (unsigned i = 0; i < PURE_MODULE_HELD; ++i) app->held[i].size = 0;
}
PureFault fault = app->fault;
uint32_t fault_pc = app->fault_pc;
diff --git a/core/module.h b/core/module.h
index b230fc3..8477b86 100644
--- a/core/module.h
+++ b/core/module.h
@@ -44,6 +44,7 @@ typedef enum {
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_BYTES_SLICE, PURE_OP_BYTE, PURE_OP_BYTES_LOAD, PURE_OP_BYTES_STORE,
PURE_OP_COUNT
} PureOpcode;
/* A bool is its own type (docs/design/016-bool-and-i64.md), held as a
@@ -73,6 +74,25 @@ typedef enum {
* 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;
+/* 027's stage 2a. CONCAT with argument 1 joins two bytes values (0: two
+ * texts). BYTES_SLICE takes a bytes value and two positions a <= c (numbers,
+ * or i64s with argument 1) and gives its bytes from a up to before c, failing
+ * as LIMIT unless both are whole and within it. BYTE makes one byte of an
+ * i64 0-255 (else LIMIT). BYTES_LOAD and BYTES_STORE read and set one of an
+ * event's PURE_MODULE_HELD values held by name (a let), empty when the event
+ * or a view's body begins: the argument's bits 0-4 name it, a text below 16
+ * bytes and from 16 on a text (so a text one holds UTF-8 alone); STORE with
+ * bit 8 appends the value to what it holds. What they hold counts in the
+ * event's room (MEMORY past it).
+ * NUM_TEXT's argument: bits 0-1 the value's type (as CONST's), 8-15 a width
+ * the text is padded to on the left, bit 16 padding with zeros after a sign
+ * (else spaces), 17-19 the form (PureFormat), 20-24 one more than the digits
+ * after the point of FIXED. A form of an integer takes an i64 or a whole
+ * number (else LIMIT), in its base with a sign; FIXED takes a number. */
+enum { PURE_MODULE_HELD = 32, PURE_HELD_TEXT = 16, PURE_HELD_APPEND = 1 << 8 };
+typedef enum { PURE_FORMAT_PLAIN, PURE_FORMAT_DECIMAL, PURE_FORMAT_HEX, PURE_FORMAT_HEX_UPPER, PURE_FORMAT_BINARY,
+ PURE_FORMAT_OCTAL, PURE_FORMAT_FIXED } PureFormat;
+enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17, PURE_FORMAT_DIGITS = 20 };
/* 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
@@ -499,6 +519,8 @@ typedef struct {
struct PureSnapshot *snapshot; /* the newest snapshot made, or NULL */
PureRetired retired; /* list items let go since it was made */
bool reading; /* a reader: its states are a snapshot's */
+ /* The values an event or a view's body holds by name (BYTES_STORE): emptied as it begins. */
+ struct { unsigned char *data; size_t size, capacity; } held[PURE_MODULE_HELD];
} PureApp;
PureResult pure_app_start(PureApp *, const PureModule *, PureData data);
diff --git a/core/test-module.c b/core/test-module.c
index dca1b67..02d5015 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -6131,6 +6131,122 @@ static void bits_and_bytes(void) {
printf("{\"bits_and_bytes\":{\"bits\":%zu,\"reads\":%zu,\"refused\":%zu}}\n", LEN(cases), LEN(reads), LEN(refused));
}
+/* ---- Formats, slices and values held by name (docs/design/027, stage 2a) --
+ * NUM_TEXT writes a number, a bool or an i64 in a width, with zeros or
+ * spaces, in a base or with fixed digits; an integer's form fails as limit
+ * on a number that is not whole. BYTES_SLICE takes whole positions inside
+ * the value; BYTE makes one byte. BYTES_LOAD and BYTES_STORE keep bytes
+ * and texts by name for the event (a text's slots hold text alone): set,
+ * appended, set to a part of itself, counted in the room, empty at the next
+ * event; a reply of one is kept as it was. */
+enum { NTX = PURE_OP_NUM_TEXT, SLC = PURE_OP_BYTES_SLICE, BYT = PURE_OP_BYTE, HLD = PURE_OP_BYTES_LOAD, HST = PURE_OP_BYTES_STORE };
+#define FMT(type, width, zeros, form, digits) ((uint32_t)(type) | (uint32_t)(width) << PURE_FORMAT_WIDTH | \
+ ((zeros) ? (uint32_t)PURE_FORMAT_ZEROS : 0) | (uint32_t)(form) << PURE_FORMAT_FORM | (uint32_t)(digits) << PURE_FORMAT_DIGITS)
+
+static void formats_and_held(void) {
+ static const struct { int i64; double value; uint32_t format; PureFault fault; const char *text; } shown[] = {
+ {1, 255, FMT(2, 0, 0, PURE_FORMAT_HEX, 0), PURE_FAULT_NONE, "ff"},
+ {1, 255, FMT(2, 8, 1, PURE_FORMAT_HEX, 0), PURE_FAULT_NONE, "000000ff"},
+ {1, -255, FMT(2, 6, 1, PURE_FORMAT_HEX_UPPER, 0), PURE_FAULT_NONE, "-000FF"},
+ {1, -5, FMT(2, 4, 0, PURE_FORMAT_DECIMAL, 0), PURE_FAULT_NONE, " -5"},
+ {1, 5, FMT(2, 0, 0, PURE_FORMAT_BINARY, 0), PURE_FAULT_NONE, "101"},
+ {1, 8, FMT(2, 0, 0, PURE_FORMAT_OCTAL, 0), PURE_FAULT_NONE, "10"},
+ {1, 0, FMT(2, 3, 1, PURE_FORMAT_PLAIN, 0), PURE_FAULT_NONE, "000"},
+ {0, 2.5, FMT(0, 0, 0, PURE_FORMAT_FIXED, 3), PURE_FAULT_NONE, "2.50"},
+ {0, 1.005, FMT(0, 0, 0, PURE_FORMAT_FIXED, 3), PURE_FAULT_NONE, "1.00"}, /* the binary value is below 1.005 */
+ {0, -0.0, FMT(0, 0, 0, PURE_FORMAT_FIXED, 2), PURE_FAULT_NONE, "0.0"},
+ {0, -1.25, FMT(0, 7, 1, PURE_FORMAT_FIXED, 2), PURE_FAULT_NONE, "-0001.2"},
+ {0, 3, FMT(0, 0, 0, PURE_FORMAT_FIXED, 0), PURE_FAULT_NONE, "3.000000"},
+ {0, 0.1, FMT(0, 6, 0, PURE_FORMAT_PLAIN, 0), PURE_FAULT_NONE, " 0.1"},
+ {0, 42, FMT(0, 0, 0, PURE_FORMAT_HEX, 0), PURE_FAULT_NONE, "2a"},
+ {0, 2.5, FMT(0, 0, 0, PURE_FORMAT_HEX, 0), PURE_FAULT_LIMIT, ""},
+ {0, 1, FMT(1, 6, 0, PURE_FORMAT_PLAIN, 0), PURE_FAULT_NONE, " true"},
+ };
+ for (size_t k = 0; k < LEN(shown); ++k) {
+ O value = shown[k].i64 ? IC((int64_t)shown[k].value) : (O){C, shown[k].format & 3, shown[k].value};
+ O code[] = {value, {NTX, shown[k].format, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ Byted ran = run_bytes(code, LEN(code));
+ if (ran.fault != shown[k].fault || strcmp(ran.said, shown[k].text)) {
+ fprintf(stderr, "format %zu: fault %d, '%s'\n", k, (int)ran.fault, ran.said);
+ abort();
+ }
+ }
+ /* Slices of a constant's bytes ("Az\xc3\xa9") and of a text state's, by numbers or i64s; one byte. */
+ static const struct { uint32_t text_state, as_i64; double from, to; PureFault fault; const char *text; } slices[] = {
+ {0, 0, 0, 2, PURE_FAULT_NONE, "Az"}, {0, 1, 2, 4, PURE_FAULT_NONE, "\xc3\xa9"}, {1, 0, 1, 1, PURE_FAULT_NONE, ""},
+ {1, 1, 0, 4, PURE_FAULT_NONE, "Az\xc3\xa9"}, {0, 0, 2, 1, PURE_FAULT_LIMIT, ""}, {0, 0, 0, 5, PURE_FAULT_LIMIT, ""},
+ {0, 0, 0.5, 2, PURE_FAULT_LIMIT, ""}, {0, 1, -1, 2, PURE_FAULT_LIMIT, ""}, {0, 0, 2, 3, PURE_FAULT_ENCODING, ""},
+ };
+ for (size_t k = 0; k < LEN(slices); ++k) {
+ O from = slices[k].as_i64 ? IC((int64_t)slices[k].from) : (O){C, 0, slices[k].from};
+ O to = slices[k].as_i64 ? IC((int64_t)slices[k].to) : (O){C, 0, slices[k].to};
+ O code[] = {slices[k].text_state ? (O){TL, 1, 0} : (O){TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, from, to,
+ {SLC, slices[k].as_i64, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ Byted ran = run_bytes(code, LEN(code));
+ CHECK(ran.fault == slices[k].fault && !strcmp(ran.said, slices[k].text));
+ }
+ O bytes_joined[] = {IC('O'), {BYT, 0, 0}, IC('k'), {BYT, 0, 0}, {CAT, 1, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ CHECK(!strcmp(run_bytes(bytes_joined, LEN(bytes_joined)).said, "Ok"));
+ O past[] = {IC(256), {BYT, 0, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ CHECK(run_bytes(past, LEN(past)).fault == PURE_FAULT_LIMIT);
+ /* Held: bytes slot 3 set to "Az", appended "Az\xc3\xa9", set to a part of itself, then shown; a text slot. */
+ O held[] = {{TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {C, 0, 0}, {C, 0, 2}, {SLC, 0, 0}, {HST, 3, 0},
+ {TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {HST, 3 | PURE_HELD_APPEND, 0},
+ {HLD, 3, 0}, {HLD, 3, 0}, {CAT, 1, 0}, {HST, 3, 0}, /* "AzAz\xc3\xa9" twice */
+ {HLD, 3, 0}, {C, 0, 1}, {C, 0, 6}, {SLC, 0, 0}, {HST, 3 | PURE_HELD_APPEND, 0}, /* itself, appended */
+ {HLD, 3, 0}, {C, 0, 2}, {C, 0, 8}, {SLC, 0, 0}, {HST, 3, 0}, /* a part of itself */
+ {HLD, 3, 0}, {PURE_OP_TEXT_FROM, 0, 0}, {HST, 20, 0}, {HLD, 20, 0}, {PURE_OP_TEXT_SET, 4, 0}};
+ Byted ran = run_bytes(held, LEN(held));
+ CHECK(ran.fault == PURE_FAULT_NONE && !strcmp(ran.said, "Az\xc3\xa9" "Az"));
+ /* Held values start empty each event; doubling past the room fails as memory and leaves nothing. */
+ 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"), KEY("grow", "KeyH"), {"ask", NULL, PURE_KIND_MESSAGE, false, NULL, 0}};
+ static const O code[] = {
+ /* 0 go: held5 = held5 + bytes(note); said = text(held5) */
+ {HLD, 5, 0}, {TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {CAT, 1, 0}, {HST, 5, 0}, {HLD, 5, 0}, {PURE_OP_TEXT_FROM, 0, 0},
+ {PURE_OP_TEXT_SET, 4, 0},
+ /* 8 grow: held6 = bytes(note); for 40 rounds held6 = held6 + held6; said = "x" */
+ {TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {HST, 6, 0}, {C, 0, 0}, {C, 0, 40}, {PURE_OP_LOOP, 0 | 2u << 16, 0},
+ {HLD, 6, 0}, {HST, 6 | PURE_HELD_APPEND, 0}, {TK, 0, 0}, {PURE_OP_TEXT_SET, 4, 0},
+ /* 18 ask: held7 = bytes(note); reply held7; held7 = "" */
+ {TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {HST, 7, 0}, {HLD, 7, 0}, {PURE_OP_REPLY, 0, 0}, {TK, 1, 0},
+ {PURE_OP_BYTES_FROM, 0, 0}, {HST, 7, 0},
+ };
+ static const H handlers[] = {{1, 0, 8}, {3, 8, 10}, {5, 18, 8}};
+ Spec spec = {nums, 1, inputs, LEN(inputs), NULL, 0, handlers, LEN(handlers), code, LEN(code), 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);
+ CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK && pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(!strcmp(state_text(&app, 4), "Az\xc3\xa9")); /* held5 began empty again */
+ CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_BUDGET && app.fault == PURE_FAULT_MEMORY);
+ CHECK(!strcmp(state_text(&app, 4), "Az\xc3\xa9")); /* nothing changed */
+ CHECK(pure_app_message(&app, 2, NULL, -1, (PureSpan){NULL, 0}) == PURE_OK);
+ Text replied = {{0}, 0};
+ CHECK(pure_app_reply(&app, text_span, &replied) == PURE_OK && !strcmp(replied.text, "Az\xc3\xa9"));
+ pure_app_stop(&app);
+ /* Refused: bytes into a text's slot, a slot past the last, a format that does not fit its type. */
+ const O refused[][3] = {
+ {{TK, 0, 0}, {PURE_OP_BYTES_FROM, 0, 0}, {HST, PURE_HELD_TEXT, 0}}, {{TK, 0, 0}, {HST, 3, 0}, {C, 0, 0}},
+ {{TK, 0, 0}, {HST, PURE_MODULE_HELD, 0}, {C, 0, 0}}, {{C, 1, 1}, {NTX, FMT(1, 0, 0, PURE_FORMAT_HEX, 0), 0}, {PURE_OP_TEXT_SET, 4, 0}},
+ {IC(1), {NTX, FMT(2, 0, 0, PURE_FORMAT_FIXED, 2), 0}, {PURE_OP_TEXT_SET, 4, 0}},
+ {IC(1), {NTX, FMT(2, 0, 0, PURE_FORMAT_HEX, 2), 0}, {PURE_OP_TEXT_SET, 4, 0}},
+ {{TK, 0, 0}, {TK, 0, 0}, {CAT, 1, 0}},
+ };
+ for (size_t k = 0; k < LEN(refused); ++k) {
+ H one = {1, 0, 3};
+ Spec bad = {nums, 1, inputs, 1, NULL, 0, &one, 1, refused[k], 3, NULL, 0, others, LEN(others), constants,
+ LEN(constants), NULL, 0, records, 1, NULL, 0};
+ if (load(bad, &module_b) == PURE_OK) { fprintf(stderr, "formats and held: refused %zu loaded\n", k); abort(); }
+ }
+ printf("{\"formats_and_held\":{\"formats\":%zu,\"slices\":%zu,\"refused\":%zu}}\n", LEN(shown), LEN(slices), 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 };
@@ -6505,6 +6621,7 @@ int main(void) {
bools();
integers();
bits_and_bytes();
+ formats_and_held();
memory_budget();
work_budget();
fold_mutations();