diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/module-jit.c | 2 | ||||
| -rw-r--r-- | core/module-list.c | 2 | ||||
| -rw-r--r-- | core/module-list.h | 2 | ||||
| -rw-r--r-- | core/module-save.c | 6 | ||||
| -rw-r--r-- | core/module.c | 86 | ||||
| -rw-r--r-- | core/module.h | 13 | ||||
| -rw-r--r-- | core/test-module.c | 61 |
7 files changed, 117 insertions, 55 deletions
diff --git a/core/module-jit.c b/core/module-jit.c index 328a69f..d5c5d3a 100644 --- a/core/module-jit.c +++ b/core/module-jit.c @@ -1245,7 +1245,7 @@ static bool event_list(const PureModule *m, uint32_t list) { static bool numbers_only(const PureModule *m, uint32_t list) { const PureRecord *record = &m->record[m->state_ref[list]]; for (unsigned f = 0; f < record->count; ++f) - if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT || m->record_field_type[record->first + f] == PURE_RECORD_LIST) + if (pure_record_bytes(m->record_field_type[record->first + f]) || m->record_field_type[record->first + f] == PURE_RECORD_LIST) return false; /* a list in a record is shared by count (module.c's APPEND_IN) */ return true; } diff --git a/core/module-list.c b/core/module-list.c index 57e44c4..2f2c359 100644 --- a/core/module-list.c +++ b/core/module-list.c @@ -2072,7 +2072,7 @@ bool pure_cell_fits(uint8_t kind, PureCell cell) { } bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell) { - if (from == to) return true; + if (from == to || (from == PURE_CELL_TEXT && to == PURE_CELL_BYTES)) return true; /* a text is bytes */ if (pure_cell_text(from) || pure_cell_text(to)) return false; if (to == PURE_CELL_INTEGER) { /* a whole number in range: -2^63 is, 2^63 is not */ double v = cell->num; diff --git a/core/module-list.h b/core/module-list.h index 928b12a..13c2550 100644 --- a/core/module-list.h +++ b/core/module-list.h @@ -65,7 +65,7 @@ bool pure_cell_fits(uint8_t kind, PureCell cell); * replaced, a stored value read by another module, a waiting send recast): * true with *cell made the other when it fits (a bool from 0 or 1, either * zero false; a number from an integer within 2^53; an integer from a - * whole number in its range), else false. Texts only as themselves. */ + * whole number in its range), else false. Texts as themselves and as bytes, bytes as themselves. */ bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell); PureList *pure_list_new(unsigned fields, const uint8_t *kinds); void pure_list_free(PureList *); diff --git a/core/module-save.c b/core/module-save.c index 3b1284c..f87cd24 100644 --- a/core/module-save.c +++ b/core/module-save.c @@ -221,7 +221,7 @@ static bool read_fields(const unsigned char *bytes, size_t size, size_t *at, uns for (unsigned f = 0; f < count; ++f) { if (!named(bytes, size, at, 0, &l->record[k].kinds[f], l->record[k].names[f])) return false; uint8_t kind = l->record[k].kinds[f]; - if (kind > PURE_CELL_LIST || kind == PURE_CELL_BYTES) return false; + if (kind > PURE_CELL_LIST) return false; for (unsigned g = 0; g < f; ++g) if (!strcmp(l->record[k].names[g], l->record[k].names[f])) return false; l->record[k].items[f] = 0; if (kind != PURE_CELL_LIST) continue; @@ -497,8 +497,8 @@ PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint if (result == PURE_OK && pure_draft_revision(*legacy) && pure_draft_size(*legacy)) result = read_whole(*legacy, &bytes, &size); } - /* The stored record graph: its fields' kinds (PURE_CELL_...: a record's, no runtime bytes) and names, and the - * records of the lists in its fields (an image's alone, 027's stage 3). */ + /* The stored record graph: its fields' kinds (PURE_CELL_...: a record's, its lists' PURE_CELL_LIST) and names, and + * the records of the lists in its fields (an image's alone, 027's stage 3). */ PureLayout stored_layout = {.records = 1}; unsigned count = 0; size_t at = 0, items = 0; diff --git a/core/module.c b/core/module.c index 03cc434..b8782f3 100644 --- a/core/module.c +++ b/core/module.c @@ -352,10 +352,14 @@ static bool state_of(const PureModule *m, uint32_t state, uint8_t type) { /* The list an item operation names: its low 16 bits (the field is above). */ +/* The stack type of a record field's type (PureRecordType), and of a record field (the module's numbering). */ +static unsigned char record_type_of(uint8_t type) { + static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT, [PURE_RECORD_BOOL] = T_BOOL, + [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST, [PURE_RECORD_BYTES] = T_BYTES}; + return types[type]; +} static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) { - static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT, - [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST}; - return types[m->record_field_type[m->record[m->state_ref[list]].first + field]]; + return record_type_of(m->record_field_type[m->record[m->state_ref[list]].first + field]); } /* The record of the lists a LIST field of a record holds (field: the module's numbering), or UINT32_MAX. */ @@ -378,18 +382,10 @@ static bool bound_list(const PureModule *m, uint32_t list) { return event_list_state(m, list) && m->state_flags[list] & PURE_LIST_BOUND >> 8; } -/* The stack type of a record field's type (PureRecordType), and of a record field (the module's numbering). */ -static unsigned char record_type_of(uint8_t type) { - static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT, - [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST}; - return types[type]; -} static unsigned char record_type(const PureModule *m, uint32_t field) { return record_type_of(m->record_field_type[field]); } -/* The stack type of a function's signature item (a record field's type, or PURE_SIGNATURE_BYTES). */ -static unsigned char signature_stack_type(const PureModule *m, uint32_t item) { - return m->signature_type[item] == PURE_SIGNATURE_BYTES ? T_BYTES : record_type_of(m->signature_type[item]); -} +/* The stack type of a function's signature item (a record field's type). */ +static unsigned char signature_stack_type(const PureModule *m, uint32_t item) { return record_type_of(m->signature_type[item]); } /* The record of the list value an instruction takes as its operand k (bottom 0). */ static unsigned list_wanted(const PureModule *m, const PureOp *op, unsigned k) { @@ -419,15 +415,15 @@ static unsigned char state_type(const PureModule *m, uint32_t state) { return m->state_type[state] == PURE_STATE_BOOL ? T_BOOL : m->state_type[state] == PURE_STATE_I64 ? T_I64 : T_NUM; } -/* Whether a list's record field is a text. */ +/* Whether a list's record field is held as bytes are: a text or bytes. */ static bool text_field(const PureModule *m, uint32_t list, uint32_t field) { - return m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_TEXT; + return pure_record_bytes(m->record_field_type[m->record[m->state_ref[list]].first + field]); } -/* Whether its cells are bytes: a text, or a list in a list kept past an event (027's stage 3). */ +/* Whether its cells are bytes: a text, bytes, or a list in a list kept past an event (027's stage 3). */ static bool bytes_field(const PureModule *m, uint32_t list, uint32_t field) { uint8_t type = m->record_field_type[m->record[m->state_ref[list]].first + field]; - return type == PURE_RECORD_TEXT || (type == PURE_RECORD_LIST && !(m->state_flags[list] & PURE_LIST_ROWS >> 8)); + return pure_record_bytes(type) || (type == PURE_RECORD_LIST && !(m->state_flags[list] & PURE_LIST_ROWS >> 8)); } /* What a local holds (its argument's bits 8-15): a number, an index, a bool, an i64 or a list (027's 2c). */ @@ -1793,21 +1789,22 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES; uint32_t type = read32(entry + 4); /* PureRecordType */ out->record_field_type[f] = (uint8_t)type; - if (!name_at(text, strings, read32(entry), NAME_PATH, out->record_field_names[f]) || type > PURE_RECORD_LIST) + if (!name_at(text, strings, read32(entry), NAME_PATH, out->record_field_names[f]) || type > PURE_RECORD_BYTES) 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); - /* A default: a text's constant, an i64's 8 bytes, a number (a bool's +0 or 1). */ - if (type == PURE_RECORD_TEXT) out->record_field_default[f].num = read32(entry + 8); + /* A default: a text's or bytes' constant, an i64's 8 bytes, a number (a bool's +0 or 1). */ + if (pure_record_bytes((uint8_t)type)) out->record_field_default[f].num = read32(entry + 8); else if (type == PURE_RECORD_I64 || type == PURE_RECORD_LIST) /* a LIST's: the record of its items */ out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32); else out->record_field_default[f].num = read_double(entry + 8); double given = out->record_field_default[f].num; - if (type == PURE_RECORD_TEXT ? read32(entry + 12) || read32(entry + 8) >= out->constants + if (pure_record_bytes((uint8_t)type) ? read32(entry + 12) || read32(entry + 8) >= out->constants : type == PURE_RECORD_LIST ? (uint64_t)out->record_field_default[f].i64 >= out->records : type != PURE_RECORD_I64 && (!isfinite(given) || (type == PURE_RECORD_BOOL && !canonical_bool(given)))) REFUSE("record field default", f); if (type == PURE_RECORD_LIST) out->record_nested[i] = true; + if (pure_record_bytes((uint8_t)type)) out->record_held[i] |= (uint16_t)(1u << (f - record->first)); } next_record_field += record->count; } @@ -1822,11 +1819,12 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_BOOL))) || (out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST))) REFUSE("state reference", i); - /* No list is keyed by a list in its records; a register is no message's rows. */ + /* No list is keyed by a list or bytes in its records (a key is a number or a text); a register is no message's rows. */ for (uint32_t i = 0; i < out->states; ++i) { - if (out->state_type[i] != PURE_STATE_LIST) continue; - if (out->state_key[i] && out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_LIST) - REFUSE("list in a record as a key", i); + if (out->state_type[i] != PURE_STATE_LIST || !out->state_key[i]) continue; + uint8_t key = out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u]; + if (key == PURE_RECORD_LIST) REFUSE("list in a record as a key", i); + if (key == PURE_RECORD_BYTES) REFUSE("bytes as a key", i); } for (uint32_t f = 0; f < out->fields; ++f) if (out->field[f].type == PURE_FIELD_ROWS && out->state_flags[out->field[f].rows] & PURE_LIST_BOUND >> 8) @@ -1885,7 +1883,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule for (uint32_t k = 0; k < signatures; ++k, at += SIGNATURE_BYTES) { out->signature_type[k] = at[0]; out->signature_record[k] = (uint16_t)read16(at + 2); - if (at[1] || at[0] > PURE_SIGNATURE_BYTES || + if (at[1] || at[0] > PURE_RECORD_BYTES || (at[0] == PURE_RECORD_LIST ? out->signature_record[k] >= out->records : out->signature_record[k] != 0)) REFUSE("function signature", k); } @@ -2486,7 +2484,7 @@ static size_t value_size(const PureApp *app, uint32_t record, uint64_t list) { for (unsigned f = 0; f < r->count; ++f) { uint8_t type = m->record_field_type[r->first + f]; PureCell cell = pure_list_get(items, at, f); - size += type == PURE_RECORD_TEXT ? 4 + cell.size : + size += pure_record_bytes(type) ? 4 + cell.size : type == PURE_RECORD_LIST ? 4 + value_size(app, nested_record(m, r->first + f), (uint64_t)cell.i64) : 8; } return size; @@ -2500,7 +2498,7 @@ static unsigned char *value_put(const PureApp *app, uint32_t record, uint64_t li for (unsigned f = 0; f < r->count; ++f) { uint8_t type = m->record_field_type[r->first + f]; PureCell cell = pure_list_get(items, at, f); - if (type == PURE_RECORD_TEXT) { + if (pure_record_bytes(type)) { write32(out, (uint32_t)cell.size); if (cell.size) memcpy(out + 4, cell.text, cell.size); out += 4 + cell.size; @@ -2532,7 +2530,7 @@ static uint64_t value_take(PureApp *app, uint32_t record, const unsigned char *b for (unsigned f = 0; f < r->count; ++f) { uint32_t field = r->first + f; uint8_t type = m->record_field_type[field]; - if (type == PURE_RECORD_TEXT || type == PURE_RECORD_LIST) { + if (pure_record_bytes(type) || type == PURE_RECORD_LIST) { if (size - at < 4) return *bad = true, *made = false, 0; uint32_t length = read32(bytes + at); if (length > size - at - 4 || (type == PURE_RECORD_TEXT && !pure_utf8_valid((PureSpan){bytes + at + 4, length}))) @@ -2580,7 +2578,7 @@ static __attribute__((cold, noinline)) PureResult kept_cells(PureApp *app, PureA for (unsigned f = 0; f < r->count; ++f) { uint8_t type = m->record_field_type[r->first + f]; total += type == PURE_RECORD_LIST ? value_size(app, nested_record(m, r->first + f), (uint64_t)values[f].i64) : - type == PURE_RECORD_TEXT ? (size_t)values[f].bytes.length : 0; + pure_record_bytes(type) ? (size_t)values[f].bytes.length : 0; } if (!reserve(arena, total)) return PURE_ALLOCATION; for (unsigned f = 0; f < r->count; ++f) { @@ -2588,7 +2586,7 @@ static __attribute__((cold, noinline)) PureResult kept_cells(PureApp *app, PureA PureSpan span = {0}; PureResult status = PURE_OK; if (type == PURE_RECORD_LIST) status = value_cell(app, arena, nested_record(m, r->first + f), (uint64_t)values[f].i64, &cells[f]); - else if (type == PURE_RECORD_TEXT && (status = contiguous(app, arena, values[f].bytes, &span)) == PURE_OK) + else if (pure_record_bytes(type) && (status = contiguous(app, arena, values[f].bytes, &span)) == PURE_OK) cells[f] = (PureCell){.text = (unsigned char *)span.data, .size = span.size}; else cells[f] = (PureCell){.i64 = values[f].i64}; if (status != PURE_OK) return status; @@ -2627,12 +2625,12 @@ static __attribute__((cold, noinline)) int append_kept(PureApp *app, PureArena * if (!own) return *status = PURE_ALLOCATION, 3; size_t mark = arena->used, total = 0; for (unsigned f = 0; f < record->count; ++f) - if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) total += (size_t)values[f].bytes.length; + if (pure_record_bytes(m->record_field_type[record->first + f])) total += (size_t)values[f].bytes.length; if (!reserve(arena, total)) return *status = PURE_ALLOCATION, 3; PureCell cells[PURE_RECORD_FIELDS]; for (unsigned f = 0; f < record->count; ++f) { PureSpan span = {0}; - if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT && + if (pure_record_bytes(m->record_field_type[record->first + f]) && (*status = contiguous(app, arena, values[f].bytes, &span)) != PURE_OK) return 3; cells[f] = (PureCell){.i64 = values[f].i64, .text = (unsigned char *)span.data, .size = span.size}; @@ -3901,15 +3899,16 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp kind = PURE_FAULT_NOT_FOUND; FAILED; } - /* Room for every text first, so no span moves while the next is copied. */ + /* Room for every text and bytes first, so no span moves while the next is copied. */ size_t mark = arena->used, total = 0; + uint32_t held = m->record_held[m->state_ref[arg]]; for (unsigned f = 0; f < record->count; ++f) - if (text_field(m, arg, f)) total += (size_t)stack[base + f].bytes.length; + if (held >> f & 1) total += (size_t)stack[base + f].bytes.length; if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION); PureCell cells[PURE_RECORD_FIELDS]; for (unsigned f = 0; f < record->count; ++f) { PureSpan span = {0}; - if (text_field(m, arg, f) && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK) + if (held >> f & 1 && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK) STOPPED(status); cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size}; } @@ -4244,12 +4243,12 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp PureList *in = app->nested.list[into - 1]; size_t mark = arena->used, total = 0; for (unsigned f = 0; f < record->count; ++f) - if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) total += (size_t)stack[base + f].bytes.length; + if (pure_record_bytes(m->record_field_type[record->first + f])) total += (size_t)stack[base + f].bytes.length; if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION); PureCell cells[PURE_RECORD_FIELDS]; for (unsigned f = 0; f < record->count; ++f) { PureSpan span = {0}; - if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT && + if (pure_record_bytes(m->record_field_type[record->first + f]) && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK) STOPPED(status); cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size}; @@ -4550,7 +4549,7 @@ static Call *give_back(PureApp *app, Call *call, Event *ev) { /* A bytes or text value a call would lose (held by name in a slot the callee has its own of): a copy in the arena. */ static bool kept_apart(PureApp *app, PureArena *arena, const PureFunction *f, uint8_t type, PureBytes *v) { - if ((type != PURE_RECORD_TEXT && type != PURE_SIGNATURE_BYTES) || v->source != FROM_HELD || !(f->held >> v->owner & 1)) + if (!pure_record_bytes(type) || v->source != FROM_HELD || !(f->held >> v->owner & 1)) return true; size_t start = arena->used; if (append_value(app, arena, *v) != PURE_OK) return false; @@ -5778,7 +5777,7 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { uint8_t pure_record_kind(const PureModule *m, uint32_t field) { static const uint8_t kinds[] = {[PURE_RECORD_NUMBER] = PURE_CELL_NUMBER, [PURE_RECORD_TEXT] = PURE_CELL_TEXT, [PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER, - [PURE_RECORD_LIST] = PURE_CELL_LIST}; + [PURE_RECORD_LIST] = PURE_CELL_LIST, [PURE_RECORD_BYTES] = PURE_CELL_BYTES}; return kinds[m->record_field_type[field]]; } @@ -5825,9 +5824,10 @@ PURE_COLD bool pure_layout_same(const PureLayout *a, const PureLayout *b) { return true; } -/* A number, a bool and an i64 carry over each other (when the value fits); texts only to texts, lists to lists. */ +/* A number, a bool and an i64 carry over each other (when the value fits); texts to texts and bytes, bytes to bytes, lists + * to lists. */ static bool kinds_carry(uint8_t from, uint8_t to) { - return from == to || (!stored_bytes(from) && !stored_bytes(to)); + return from == to || (from == PURE_CELL_TEXT && to == PURE_CELL_BYTES) || (!stored_bytes(from) && !stored_bytes(to)); } /* Bytes made for a list value's stored form as it moves (a growing buffer). */ @@ -5938,7 +5938,7 @@ PURE_COLD PureResult pure_list_migrate(PureList **out, const PureList *from, con } else if (m->record_field_type[field] == PURE_RECORD_LIST) cells[f] = (PureCell){.i64 = 0}; /* an event's: none */ else if (source[f] >= 0 && pure_cell_as(from_layout->record[0].kinds[source[f]], pure_record_kind(m, field), &given)) cells[f] = given; - else if (m->record_field_type[field] == PURE_RECORD_TEXT) + else if (pure_record_bytes(m->record_field_type[field])) cells[f] = (PureCell){.text = (unsigned char *)m->constant_data + m->constant_offset[constant], .size = m->constant_length[constant]}; else cells[f] = (PureCell){.i64 = m->record_field_default[field].i64}; /* a number's, a bool's or an i64's bits */ diff --git a/core/module.h b/core/module.h index 349c4d2..d23cb49 100644 --- a/core/module.h +++ b/core/module.h @@ -137,7 +137,7 @@ enum { PURE_FORMAT_WIDTH = 8, PURE_FORMAT_ZEROS = 1 << 16, PURE_FORMAT_FORM = 17 * point), failing as LIMIT where no character begins there; CHAR makes the * text of a code point, failing as LIMIT outside 0-0x10FFFF and as ENCODING * for a surrogate. */ -enum { PURE_MODULE_FUNCTIONS = 64, PURE_MODULE_SIGNATURES = 1024, PURE_SIGNATURE_BYTES = 5 }; +enum { PURE_MODULE_FUNCTIONS = 64, PURE_MODULE_SIGNATURES = 1024 }; /* What a call's frame counts in the event's room: a table, as gas is, not the implementation's sizes (each at least * what it stands for), so a module's calls fail at the same depth wherever and on whatever build they run: so much a * frame, a value of its stack's height, a local, a loop it nests, a list of its own, a held value and a register. */ @@ -237,8 +237,12 @@ static inline uint8_t pure_value_kind(uint8_t state_type) { return state_type == PURE_STATE_NUM ? PURE_CELL_NUMBER : state_type == PURE_STATE_BOOL ? PURE_CELL_BOOL : state_type == PURE_STATE_I64 ? PURE_CELL_INTEGER : 0xFF; } -/* A record field: a number, a text (its default a constant), a bool or an i64. */ -typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3, PURE_RECORD_LIST = 4 } PureRecordType; +/* A record field: a number, a text (its default a constant), a bool, an i64, a list (027's 2b) or bytes (4b: its + * default a constant too, held as a text is but for UTF-8). */ +typedef enum { PURE_RECORD_NUMBER = 0, PURE_RECORD_TEXT = 1, PURE_RECORD_BOOL = 2, PURE_RECORD_I64 = 3, PURE_RECORD_LIST = 4, + PURE_RECORD_BYTES = 5 } PureRecordType; +/* A field held as bytes are: a text or bytes. */ +static inline bool pure_record_bytes(uint8_t type) { return type == PURE_RECORD_TEXT || type == PURE_RECORD_BYTES; } /* A value's 8 bytes: a number (a bool's 0 or 1, a cursor's or an index's * position too), or an integer. What a value is its state's or field's type * says, and the verifier keeps to it: moves copy the bytes, and only what @@ -291,7 +295,7 @@ typedef struct { union { double value; int64_t integer; }; uint32_t arg; uint8_t typedef struct { uint32_t start, length; } PureBlock; typedef struct { uint32_t event; PureBlock code; } PureHandler; /* A function's block and its parameters' and results' types, from first among the module's signature items: a record - * field's type (PureRecordType: a list's with its record) or PURE_SIGNATURE_BYTES. Set at load: the event's lists, + * field's type (PureRecordType: a list's with its record, bytes as PURE_RECORD_BYTES). Set at load: the event's lists, * registers and held values each call has its own of (a bit each), the lists not an event's it changes (or a function * it calls does), and its frame: its stack's height at most, its locals and its loops. */ typedef struct { @@ -393,6 +397,7 @@ typedef struct { PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, a text constant's index * or a list's record (LIST, its i64) */ bool record_nested[PURE_MODULE_RECORDS]; /* a record with a LIST field (set at load) */ + uint16_t record_held[PURE_MODULE_RECORDS]; /* its fields held as bytes are (pure_record_bytes), a bit each (set at load) */ uint16_t functions; PureFunction function[PURE_MODULE_FUNCTIONS]; uint8_t signature_type[PURE_MODULE_SIGNATURES]; diff --git a/core/test-module.c b/core/test-module.c index 9d925dd..181f96d 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -56,7 +56,8 @@ typedef struct { const char *name; uint32_t type, ref; } T; /* type: the state t #define TR (1u << 24) /* in a T's type: a transient state */ /* A record field: a number and its default, text (text = 1) whose default * is the constant numbered initial, a bool (text = 2) or an i64 (text = 3) - * and its default. */ + * and its default, a list (4, initial its record) or bytes (5, its default + * a constant as a text's). */ 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 @@ -185,7 +186,7 @@ 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 == 1) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); } + if (field->text == 1 || field->text == 5) { p = put32(p, (uint32_t)field->initial); p = put32(p, 0); } else if (field->text == 3 || field->text == 4) { /* an i64's default, a list's record */ int64_t initial = (int64_t)field->initial; p = put32(p, (uint32_t)(uint64_t)initial); @@ -6686,6 +6687,61 @@ static void nested_rows(void) { printf("{\"nested_rows\":{\"rounds\":3,\"refused\":%zu}}\n", LEN(cuts) + 1); } +/* 027's 4b: a record field holding bytes. A message's bytes (any, UTF-8 or not) go into a list's item and come back + * as they were; a text goes where bytes go only as bytes (BYTES_FROM), and bytes never where a text goes. Replaced, a + * text field becomes a bytes field keeping its value, and a new bytes field takes its constant. */ +static void bytes_fields(void) { + enum { BF = PURE_OP_BYTES_FROM, TC = PURE_OP_TEXT_CONST, XL_ = PURE_OP_INDEX_LAST, ITEMS = 1, BLOB = 2 }; + static const S nums[] = {{"n", 0}}; + static const char *const constants[] = {"", "zz"}; + static const RF text_fields[] = {{"t", 1, 0}}; + static const RF bytes_fields_[] = {{"t", 5, 0}, {"b", 5, 1}}; + static const R before[] = {{"Item", text_fields, 1}}, after[] = {{"Item", bytes_fields_, 2}}; + static const T others[] = {{"items", 3, 0}, {"blob", PURE_STATE_TEXT, 0}}; + static const F text_in[] = {{"t", PURE_FIELD_TEXT}}, bytes_in[] = {{"b", PURE_FIELD_BYTES}}; + static const I inputs_a[] = {{"put", NULL, PURE_KIND_MESSAGE, false, text_in, 1}}; + static const I inputs_b[] = {{"put", NULL, PURE_KIND_MESSAGE, false, bytes_in, 1}}; + /* A: items.append(Item { t: put.t }); n = items.count */ + static const O code_a[] = {{FIELD, 0, 0}, {LA, ITEMS, 0}, {LC, ITEMS, 0}, {ST, 0, 0}}; + /* B: items.append(Item { t: put.b, b: bytes("zz") + put.b }); n = items.last.b's length */ + static const O code_b[] = {{FIELD, 0, 0}, {TC, 1, 0}, {BF, 0, 0}, {FIELD, 0, 0}, {CAT, 1, 0}, {LA, ITEMS, 0}, + {XL_, ITEMS, 0}, {IG, ITEM(ITEMS, 1), 0}, {PURE_OP_LEN, 0, 0}, {ST, 0, 0}}; + H ha = {1, 0, LEN(code_a)}, hb = {1, 0, LEN(code_b)}; + Spec a = {nums, 1, inputs_a, 1, NULL, 0, &ha, 1, code_a, LEN(code_a), NULL, 0, others, LEN(others), constants, LEN(constants), + NULL, 0, before, 1, NULL, 0}; + Spec b = {nums, 1, inputs_b, 1, NULL, 0, &hb, 1, code_b, LEN(code_b), NULL, 0, others, LEN(others), constants, LEN(constants), + NULL, 0, after, 1, NULL, 0}; + if (load(a, &module_a) != PURE_OK || load(b, &module_b) != PURE_OK) { fprintf(stderr, "bytes fields: %s\n", error); abort(); } + PureApp app; + CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK); + CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){(const unsigned char *)"héllo", 6}) == PURE_OK); + CHECK(pure_app_replace(&app, &module_b) == PURE_OK); + PureCell t = pure_list_get(app.lists[ITEMS], 0, 0), zz = pure_list_get(app.lists[ITEMS], 0, 1); + CHECK(t.size == 6 && !memcmp(t.text, "héllo", 6) && zz.size == 2 && !memcmp(zz.text, "zz", 2)); + static const unsigned char raw[] = {0xFF, 0x00, 0xC3, 0x28, 0x80}; /* no UTF-8 */ + CHECK(pure_app_message(&app, 0, NULL, -1, (PureSpan){raw, sizeof(raw)}) == PURE_OK && app.values[0].num == 2 + sizeof(raw)); + PureCell back = pure_list_get(app.lists[ITEMS], 1, 0), joined = pure_list_get(app.lists[ITEMS], 1, 1); + CHECK(back.size == sizeof(raw) && !memcmp(back.text, raw, sizeof(raw)) && joined.size == 2 + sizeof(raw) && + !memcmp(joined.text, "zz", 2) && !memcmp(joined.text + 2, raw, sizeof(raw))); + pure_app_stop(&app); + /* Refused: a text where bytes go, bytes where a text goes (the field read into a text state). */ + static const O text_in_bytes[] = {{TC, 0, 0}, {TC, 1, 0}, {LA, ITEMS, 0}, {C, 0, 0}, {ST, 0, 0}}; + static const O bytes_as_text[] = {{XL_, ITEMS, 0}, {IG, ITEM(ITEMS, 0), 0}, {PURE_OP_TEXT_SET, BLOB, 0}, {C, 0, 0}, {ST, 0, 0}}; + const O *refused[] = {text_in_bytes, bytes_as_text}; + for (size_t k = 0; k < LEN(refused); ++k) { + H one = {1, 0, 5}; + Spec bad = {nums, 1, inputs_b, 1, NULL, 0, &one, 1, refused[k], 5, NULL, 0, others, LEN(others), constants, LEN(constants), + NULL, 0, after, 1, NULL, 0}; + CHECK(load(bad, &module_a) != PURE_OK && strstr(error, "operand type")); + } + static const T keyed[] = {{"items", 3 | 1u << 8, 0}, {"blob", PURE_STATE_TEXT, 0}}; /* keyed by t, bytes */ + H one = {1, 0, LEN(code_b)}; + Spec by_bytes = {nums, 1, inputs_b, 1, NULL, 0, &one, 1, code_b, LEN(code_b), NULL, 0, keyed, LEN(keyed), constants, + LEN(constants), NULL, 0, after, 1, NULL, 0}; + CHECK(load(by_bytes, &module_a) != PURE_OK && strstr(error, "bytes as a key")); + printf("{\"bytes_fields\":{\"items\":2,\"refused\":%zu}}\n", LEN(refused) + 1); +} + /* 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 }; @@ -7066,6 +7122,7 @@ int main(void) { nested_crashes(); nested_replace(); nested_rows(); + bytes_fields(); memory_budget(); work_budget(); fold_mutations(); |
