diff options
| author | eunhhu <[email protected]> | 2026-09-30 16:16:08 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-30 16:16:08 +0100 |
| commit | 46c303a41e9dd9cd0e6bd50511e8d7253a0ef438 (patch) | |
| tree | 7564bf80ac43ff0964c64acd6a15f7299fa48997 /core/module-list.c | |
| parent | e02bfc916743a0327d76144b32b6ec6381e5907f (diff) | |
| download | pure-46c303a41e9dd9cd0e6bd50511e8d7253a0ef438.tar.gz pure-46c303a41e9dd9cd0e6bd50511e8d7253a0ef438.zip | |
Give i64 its own type in modules, lists, stores and the host (016 stage 2)
An i64 is an int64_t in a value's 8 bytes, its own stack type: CONST and
NUM_TEXT take argument 2 for one, eleven opcodes are appended (ADD, SUB
and MUL fail as overflow past its range, DIV and MOD floor and fail as
divide by zero, NEG, COMPARE with a relation, MIN, MAX, and I64_FROM and
NUM_FROM, which convert only where the value fits and else fail as
limit), and moves (LOAD, STORE, locals, items, FIELD, SEND) copy it as
they copy a number. Type codes: state 6, record field 3, message field 7
(8 bytes), view 4, local 3. v6 stays: all of it is added.
- Folds with PURE_FOLD_INTEGER sum (overflow fails), keep the most or
least of, or count the different i64s; distinct's set keeps an i64's
-1 apart from its free mark. Blocks with an i64 run item by item.
- A list's kinds are its stored header's (number, text, bytes, bool,
integer): integer columns are int64_t, ordered as integers as keys
(found without guide keys for now), stored in 8 bytes and logged as
zigzag varints of any size. Records name their fields' type in one
array (record_field_type) in place of text and bool flags.
- A number, a bool and an i64 carry over each other by one rule,
pure_cell_as, wherever one becomes another: a module replaced, a stored
value or item read by another module, a waiting send recast. A u32
field delivers to an i64 one.
- The host reads i64 fields from JSON, rows and queries as integer digits
in range and nothing else, and shows every digit in views, tables,
deliveries and pure-run. Faults are named by one function,
pure_fault_name, in place of three tables; overflow is new.
test-module: 28 arithmetic cases at the range's ends, relations,
conversions both ways, 14 refusals, a keyed i64 list app (order, a sum
that overflows, a failed add undone, messages and sends of 8 bytes, every
digit as text), recasts, replacement between numbers, bools and i64s, and
three stored runs against memory with restarts (46 images written).
Diffstat (limited to 'core/module-list.c')
| -rw-r--r-- | core/module-list.c | 142 |
1 files changed, 94 insertions, 48 deletions
diff --git a/core/module-list.c b/core/module-list.c index 20fed2b..51148eb 100644 --- a/core/module-list.c +++ b/core/module-list.c @@ -49,7 +49,7 @@ enum { INNER_SIZE = sizeof(Node) + FANOUT * sizeof(Node *) + sizeof(Guide) }; /* Where a leaf keeps each field's cells, and what an item stores. */ typedef struct { unsigned fields, texts; - uint8_t text_fields[16]; + uint8_t kinds[16]; /* each field's (PURE_CELL_...) */ uint8_t text_field[16]; /* the text fields, in order */ uint32_t column[16]; /* where each field's cells start in a leaf */ size_t leaf_size; @@ -127,17 +127,20 @@ static unsigned child_at(const Node *node, size_t at) { return (unsigned)(base - before) + (*base <= at); } static double *numbers(const Layout *l, const Node *leaf, unsigned f) { return (double *)((char *)leaf + l->column[f]); } +static int64_t *integers(const Layout *l, const Node *leaf, unsigned f) { return (int64_t *)((char *)leaf + l->column[f]); } static PureSpan *texts(const Layout *l, const Node *leaf, unsigned f) { return (PureSpan *)((char *)leaf + l->column[f]); } static PureCell cell_at(const Layout *l, const Node *leaf, unsigned i, unsigned f) { - if (!l->text_fields[f]) return (PureCell){.num = numbers(l, leaf, f)[i]}; + if (l->kinds[f] == PURE_CELL_INTEGER) return (PureCell){.i64 = integers(l, leaf, f)[i]}; + if (!pure_cell_text(l->kinds[f])) return (PureCell){.num = numbers(l, leaf, f)[i]}; PureSpan text = texts(l, leaf, f)[i]; return (PureCell){.text = (unsigned char *)text.data, .size = text.size}; } static void put_cells(const Layout *l, Node *leaf, unsigned i, const PureCell *cells) { for (unsigned f = 0; f < l->fields; ++f) - if (l->text_fields[f]) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size}; + if (pure_cell_text(l->kinds[f])) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size}; + else if (l->kinds[f] == PURE_CELL_INTEGER) integers(l, leaf, f)[i] = cells[f].i64; else numbers(l, leaf, f)[i] = cells[f].num; } @@ -149,7 +152,7 @@ static void move_items(const Layout *l, Node *to, unsigned at, const Node *from, return; } for (unsigned f = 0; f < l->fields; ++f) { - size_t width = l->text_fields[f] ? sizeof(PureSpan) : sizeof(double); + size_t width = pure_cell_text(l->kinds[f]) ? sizeof(PureSpan) : 8; memmove((char *)to + l->column[f] + at * width, (const char *)from + l->column[f] + start * width, count * width); } } @@ -293,7 +296,7 @@ static void drop_node(PureList *list, Node *node) { /* ---- The stored form: each field in order, a number as its binary64 bits * (little-endian), a text as a u32le length and its bytes. */ -static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 4 + cell.size : 8; } +static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return pure_cell_text(l->kinds[field]) ? 4 + cell.size : 8; } static size_t cells_bytes(const Layout *l, const PureCell *cells) { size_t size = l->number_bytes; @@ -556,7 +559,7 @@ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within /* ---- The list */ -PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) { +PureList *pure_list_new(unsigned fields, const uint8_t *kinds) { if (fields > 16) return NULL; PureList *list = calloc(1, sizeof(*list)); if (!list) return NULL; @@ -564,10 +567,10 @@ PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) { l->fields = fields; size_t at = sizeof(Node); for (unsigned f = 0; f < fields; ++f) { - l->text_fields[f] = text_fields[f]; /* 1 text (UTF-8), 2 bytes */ + l->kinds[f] = kinds[f]; l->column[f] = (uint32_t)at; - at += FANOUT * (l->text_fields[f] ? sizeof(PureSpan) : sizeof(double)); - if (l->text_fields[f]) l->text_field[l->texts++] = (uint8_t)f; + at += FANOUT * (pure_cell_text(kinds[f]) ? sizeof(PureSpan) : 8); + if (pure_cell_text(kinds[f])) l->text_field[l->texts++] = (uint8_t)f; else l->number_bytes += 8; } l->leaf_size = at; @@ -588,7 +591,7 @@ static bool rekey(PureList *list, Node **place) { PureResult pure_list_keyed(PureList *list, int field) { Layout *l = &list->layout; - int key = field >= 0 && (unsigned)field < l->fields && !l->text_fields[field] ? field : -1; + int key = field >= 0 && (unsigned)field < l->fields && l->kinds[field] == PURE_CELL_NUMBER ? field : -1; if (key == l->key) return PURE_OK; l->key = key; if (list->root && !rekey(list, &list->root)) { @@ -674,37 +677,69 @@ static bool log_change(PureUndo *undo, Entry entry) { /* A cell the list can keep: a text's bytes copied (NULL when empty). */ static bool own_cell(const PureList *list, unsigned field, PureCell from, PureCell *to) { - *to = (PureCell){.num = list->layout.text_fields[field] ? 0 : from.num}; - if (!list->layout.text_fields[field] || !from.size) return true; + bool text = pure_cell_text(list->layout.kinds[field]); + *to = (PureCell){.i64 = text ? 0 : from.i64}; /* a number's or an integer's bits */ + if (!text || !from.size) return true; if (!(to->text = new_text(list, from.text, from.size))) return false; to->size = from.size; return true; } static unsigned char *encode_cell(const Layout *l, unsigned field, PureCell cell, unsigned char *out) { - uint64_t bits = cell.size; - unsigned width = 4; - if (!l->text_fields[field]) { memcpy(&bits, &cell.num, sizeof(bits)); width = 8; } + bool text = pure_cell_text(l->kinds[field]); + uint64_t bits = text ? cell.size : (uint64_t)cell.i64; /* a number's bits, or an integer's */ + unsigned width = text ? 4 : 8; for (unsigned i = 0; i < width; ++i) *out++ = (unsigned char)(bits >> (8 * i)); - if (l->text_fields[field] && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; } + if (text && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; } return out; } +bool pure_cell_fits(uint8_t kind, PureCell cell) { + return kind == PURE_CELL_INTEGER || (kind == PURE_CELL_BOOL ? cell.num == 0 || cell.num == 1 : isfinite(cell.num)); +} + +bool pure_cell_as(uint8_t from, uint8_t to, PureCell *cell) { + if (from == to) return true; + 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; + if (!(v >= -9223372036854775808.0 && v < 9223372036854775808.0) || v != floor(v)) return false; + cell->i64 = (int64_t)v; + return true; + } + if (from == PURE_CELL_INTEGER) { + if (cell->i64 > INT64_C(9007199254740992) || cell->i64 < -INT64_C(9007199254740992)) return false; + cell->num = (double)cell->i64; + } + if (to == PURE_CELL_BOOL) { + if (cell->num != 0 && cell->num != 1) return false; + cell->num = cell->num == 1; /* -0 is false as 0 is */ + } + return true; +} + +/* Whether a number cell holds what its kind may: a finite number, a bool 0 or 1. */ +static bool fits(const Layout *l, unsigned field, double value) { + return pure_cell_fits(l->kinds[field], (PureCell){.num = value}); +} + /* A stored cell of field at *at in bytes (a text's bytes borrowed from - * them): false when it is cut short, not finite or not UTF-8. */ + * them): false when it is cut short, not finite (not 0 or 1 for a bool) or + * not UTF-8. */ static bool decode_cell(const Layout *l, unsigned field, const unsigned char *bytes, size_t size, size_t *at, PureCell *cell) { - size_t width = l->text_fields[field] ? 4 : 8; + bool text = pure_cell_text(l->kinds[field]); + size_t width = text ? 4 : 8; if (size - *at < width) return false; uint64_t bits = 0; for (size_t i = 0; i < width; ++i) bits |= (uint64_t)bytes[*at + i] << (8 * i); *at += width; *cell = (PureCell){0}; - if (!l->text_fields[field]) { - memcpy(&cell->num, &bits, sizeof(bits)); - return isfinite(cell->num); + if (!text) { + cell->i64 = (int64_t)bits; + return l->kinds[field] == PURE_CELL_INTEGER || fits(l, field, cell->num); } - if (bits > size - *at || (l->text_fields[field] == 1 && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)bits}))) + if (bits > size - *at || (l->kinds[field] == PURE_CELL_TEXT && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)bits}))) return false; *cell = (PureCell){.text = (unsigned char *)bytes + *at, .size = (size_t)bits}; *at += (size_t)bits; @@ -716,9 +751,10 @@ static bool decode_cell(const Layout *l, unsigned field, const unsigned char *by * item's fields, for a set the u8 field and its cell. A record is written * compact (kinds 4-6): the position as a varint (LEB128), a number as the * varint 2 * zigzag(n) when it is an integer n below 2^53 in size (not -0), - * else 1 and its binary64 bits (8 bytes, little-endian), a text as a varint - * length and its bytes. Records stored before (kinds 1-3, a list's tree - * value "PLS2") had a u32le position and cells in stored form. */ + * else 1 and its binary64 bits (8 bytes, little-endian), an integer as the + * varint zigzag(n), a text as a varint length and its bytes. Records stored + * before (kinds 1-3, a list's tree value "PLS2") had a u32le position and + * cells in stored form. */ enum { FIXED_INSERT = 1, FIXED_REMOVE = 2, FIXED_SET = 3, RECORD_INSERT = 4, RECORD_REMOVE = 5, RECORD_SET = 6, FIXED_HEAD = 5, RECORD_HEAD = 6, JOURNAL_KEPT = 1 << 20 }; @@ -740,20 +776,23 @@ static bool get_varint(const unsigned char *bytes, size_t size, size_t *at, uint } /* A cell in compact form (the most it takes: compact_bound). */ -static size_t compact_bound(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 5 + cell.size : 9; } +static size_t compact_bound(const Layout *l, unsigned field, PureCell cell) { + return pure_cell_text(l->kinds[field]) ? 5 + cell.size : 10; +} + +static uint64_t zigzag(int64_t n) { return (uint64_t)n << 1 ^ (n < 0 ? UINT64_MAX : 0); } +static int64_t unzigzag(uint64_t z) { return z & 1 ? -(int64_t)(z >> 1) - 1 : (int64_t)(z >> 1); } static unsigned char *put_compact(const Layout *l, unsigned field, PureCell cell, unsigned char *out) { - if (l->text_fields[field]) { + if (l->kinds[field] == PURE_CELL_INTEGER) return put_varint(out, zigzag(cell.i64)); + if (pure_cell_text(l->kinds[field])) { out = put_varint(out, cell.size); if (cell.size) memcpy(out, cell.text, cell.size); return out + cell.size; } double v = cell.num; - if (fabs(v) < 9007199254740992.0 && v == (double)(int64_t)v && !(v == 0 && signbit(v))) { - int64_t n = (int64_t)v; - uint64_t zigzag = (uint64_t)n << 1 ^ (n < 0 ? UINT64_MAX : 0); - return put_varint(out, zigzag << 1); - } + if (fabs(v) < 9007199254740992.0 && v == (double)(int64_t)v && !(v == 0 && signbit(v))) + return put_varint(out, zigzag((int64_t)v) << 1); uint64_t bits; memcpy(&bits, &v, sizeof(bits)); *out++ = 1; @@ -766,26 +805,28 @@ static bool get_compact(const Layout *l, unsigned field, const unsigned char *by uint64_t value; *cell = (PureCell){0}; if (!get_varint(bytes, size, at, &value)) return false; - if (l->text_fields[field]) { - if (value > size - *at || (l->text_fields[field] == 1 && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)value}))) + if (l->kinds[field] == PURE_CELL_INTEGER) { + cell->i64 = unzigzag(value); + return true; + } + if (pure_cell_text(l->kinds[field])) { + if (value > size - *at || (l->kinds[field] == PURE_CELL_TEXT && !pure_utf8_valid((PureSpan){bytes + *at, (size_t)value}))) return false; *cell = (PureCell){.text = (unsigned char *)bytes + *at, .size = (size_t)value}; *at += (size_t)value; return true; } if (!(value & 1)) { /* an integer */ - uint64_t zigzag = value >> 1; - if (zigzag >= UINT64_C(1) << 54) return false; - int64_t n = zigzag & 1 ? -(int64_t)(zigzag >> 1) - 1 : (int64_t)(zigzag >> 1); - cell->num = (double)n; - return true; + if (value >> 1 >= UINT64_C(1) << 54) return false; + cell->num = (double)unzigzag(value >> 1); + return fits(l, field, cell->num); } if (value != 1 || size - *at < 8) return false; uint64_t bits = 0; for (unsigned i = 0; i < 8; ++i) bits |= (uint64_t)bytes[*at + i] << (8 * i); *at += 8; memcpy(&cell->num, &bits, sizeof(bits)); - return isfinite(cell->num); + return fits(l, field, cell->num); } /* Room for size more bytes at the journal's end, or NULL without memory. */ @@ -1029,7 +1070,8 @@ PureResult pure_list_set(PureList *list, size_t at, unsigned field, PureCell val list->journal_size += (size_t)(put_compact(l, field, copy, end) - record); } list->bytes += cell_bytes(l, field, copy) - cell_bytes(l, field, old); - if (l->text_fields[field]) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size}; + if (pure_cell_text(l->kinds[field])) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size}; + else if (l->kinds[field] == PURE_CELL_INTEGER) integers(l, leaf, field)[within] = copy.i64; else numbers(l, leaf, field)[within] = copy.num; if (!undo) let_go_text(list, old.text); if (keyed) sum_path(list, path, depth); @@ -1046,7 +1088,8 @@ PureCell pure_list_get(const PureList *list, size_t at, unsigned field) { static void open_run(const Layout *l, const Node *leaf, PureRun *run) { run->count = leaf->count; for (unsigned f = 0; f < l->fields; ++f) - if (l->text_fields[f]) run->column[f].texts = texts(l, leaf, f); + if (pure_cell_text(l->kinds[f])) run->column[f].texts = texts(l, leaf, f); + else if (l->kinds[f] == PURE_CELL_INTEGER) run->column[f].integers = integers(l, leaf, f); else run->column[f].numbers = numbers(l, leaf, f); } @@ -1095,10 +1138,11 @@ bool pure_list_next(const PureList *list, PureRun *run) { return true; } -/* How two cells of a field order: numbers by value (-0 and +0 are one key), - * texts by their bytes. */ +/* How two cells of a field order: numbers and integers by value (-0 and +0 + * are one key), texts by their bytes. */ static int order(const Layout *l, unsigned field, PureCell a, PureCell b) { - if (!l->text_fields[field]) return (a.num > b.num) - (a.num < b.num); + if (l->kinds[field] == PURE_CELL_INTEGER) return (a.i64 > b.i64) - (a.i64 < b.i64); + if (!pure_cell_text(l->kinds[field])) return (a.num > b.num) - (a.num < b.num); size_t shorter = a.size < b.size ? a.size : b.size; int bytes = shorter ? memcmp(a.text, b.text, shorter) : 0; return bytes ? (bytes > 0) - (bytes < 0) : (a.size > b.size) - (a.size < b.size); @@ -1202,7 +1246,7 @@ PURE_COLD PureResult pure_list_sorted(const PureList *list, unsigned field, Pure PureResult result = PURE_OK; for (size_t i = 1; i < list->count && result == PURE_OK; ++i) if (!order(l, field, ref_cell(l, items[at[i - 1]], field), ref_cell(l, items[at[i]], field))) result = PURE_CONFLICT; - PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->text_fields) : NULL; + PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->kinds) : NULL; if (result == PURE_OK && !sorted) result = PURE_ALLOCATION; for (size_t i = 0; result == PURE_OK && i < list->count; ++i) { PureCell cells[16]; @@ -1249,11 +1293,13 @@ void pure_undo_rollback(PureUndo *undo, size_t mark) { continue; } list->bytes += cell_bytes(l, e->field, e->old) - cell_bytes(l, e->field, cell_at(l, leaf, within, e->field)); - if (l->text_fields[e->field]) { + if (pure_cell_text(l->kinds[e->field])) { PureSpan *text = &texts(l, leaf, e->field)[within]; free_text(text->data); *text = (PureSpan){e->old.text, e->old.size}; - } else + } else if (l->kinds[e->field] == PURE_CELL_INTEGER) + integers(l, leaf, e->field)[within] = e->old.i64; + else numbers(l, leaf, e->field)[within] = e->old.num; sum_path(list, path, depth); ++list->version; |
