aboutsummaryrefslogtreecommitdiffstats
path: root/serve
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-30 15:24:56 +0100
committereunhhu <[email protected]>2026-09-30 15:24:56 +0100
commitbfa54e02881f56b3a969220ecaac706542046149 (patch)
tree8c1e1b6d5a4b8a222cc56a6dc8e6a08a52a7a508 /serve
parent16fab9f3d71fa1fdb4cd38f7e9ae1e1eb4e75340 (diff)
downloadpure-bfa54e02881f56b3a969220ecaac706542046149.tar.gz
pure-bfa54e02881f56b3a969220ecaac706542046149.zip
Give bools their own type in the module format (v6, 016 stage 1)
A condition was a number, non-zero being true: a flag was a number field, a view showed 1 for true, and nothing kept a number from standing for a bool or a bool from being added to. Now the verifier has a bool among its stack types, and a verified module never mixes the two. No opcode is new: - CONST with argument 1 is a bool (+0 or 1), NUM_TEXT with argument 1 writes one as true or false; LOAD and STORE take a number or a bool state, locals may hold a bool (2). - Comparisons (LT to NE, TEXT_COMPARE) and LIST_HAS give a bool; AND, OR and NOT take and give them; JZ, REQUIRE, filters and folds' tests take one (count and first leave a bool, sum, max, min and distinct a number and a bool); EQ and NE compare two bools too. - Type codes: state 5, record field 2, message field 6 (one byte in the prefix, 0 or 1, or the message fails as limit), view 3. A bool's constant, initial value and default are +0 or 1; a list is not keyed by one. PUREMOD6: v5 modules are built again. At run time a bool is a number's 0 or 1, so leaf-at-a-time folds run as before. A number that becomes a bool (a module replaced, a stored state or item read again, a waiting send recast) keeps 0 (either sign) and 1 and takes the default otherwise; a bool that becomes a number is 0 or 1. The host carries bools on every boundary: JSON true and false only (a row's field too), a query's true and false, true and false in views, table JSON and HTTP deliveries; a link takes a bool only into a bool. A stored outbox's field types were read back with a check that refused bool (at or past rows): a node whose output had a bool field and a send waiting could not start again. test-module now stores such a send and opens it (it fails on the old check), beside 28 bool refusals of the verifier and bool states, fields, views, sends, links and migration. The lane generator makes numbers and bools apart.
Diffstat (limited to 'serve')
-rw-r--r--serve/deliver.c6
-rw-r--r--serve/message-json.c34
-rw-r--r--serve/message-json.h4
-rw-r--r--serve/serve.c28
-rw-r--r--serve/test-message-json.c11
5 files changed, 62 insertions, 21 deletions
diff --git a/serve/deliver.c b/serve/deliver.c
index b00984b..6e402a5 100644
--- a/serve/deliver.c
+++ b/serve/deliver.c
@@ -149,6 +149,8 @@ static bool encode(const PureWire *w, PureSpan payload, Text *t) {
char digits[32];
if (field->type == PURE_FIELD_U32) {
put(t, digits, (size_t)snprintf(digits, sizeof(digits), "%u", (unsigned)read_u32(payload.data + field->offset)));
+ } else if (field->type == PURE_FIELD_BOOL) {
+ put(t, payload.data[field->offset] ? "true" : "false", payload.data[field->offset] ? 4 : 5);
} else if (field->type == PURE_FIELD_NUMBER) {
uint64_t bits = read_u32(payload.data + field->offset) | (uint64_t)read_u32(payload.data + field->offset + 4) << 32;
double value;
@@ -544,8 +546,8 @@ static bool form_answer(const PureWire *w, int status, const Text *body, const P
free(bytes);
return false;
}
- if (field->type == PURE_FIELD_U32 || field->type == PURE_FIELD_NUMBER)
- memcpy(prefix + field->offset, p->sent + from->offset, field->type == PURE_FIELD_U32 ? 4 : 8);
+ if (pure_field_fixed(field->type))
+ memcpy(prefix + field->offset, p->sent + from->offset, pure_field_size(field->type));
else {
write_u32(bytes + 8 * f, (uint32_t)(copy + start - prefix));
write_u32(bytes + 8 * f + 4, (uint32_t)length);
diff --git a/serve/message-json.c b/serve/message-json.c
index 643cc28..0ace68f 100644
--- a/serve/message-json.c
+++ b/serve/message-json.c
@@ -83,9 +83,22 @@ static bool json_double(const unsigned char *text, size_t size, double *value) {
return isfinite(*value);
}
-/* A fixed field's value from its text (a query value or a JSON number) into
- * the prefix: a u32 in range, or a finite number. */
+/* A JSON true or false at text: its length (4 or 5), or 0. */
+static size_t json_bool(const unsigned char *text, size_t size, bool *value) {
+ if (size >= 4 && !memcmp(text, "true", 4)) return *value = true, 4;
+ if (size >= 5 && !memcmp(text, "false", 5)) return *value = false, 5;
+ return 0;
+}
+
+/* A fixed field's value from its text (a query value or a JSON value) into
+ * the prefix: a u32 in range, a finite number, or true or false for a bool. */
bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t size, unsigned char *prefix) {
+ if (field->type == PURE_FIELD_BOOL) { /* true or false, as a JSON value says it */
+ bool value;
+ if (!size || json_bool(text, size, &value) != size) return false;
+ prefix[field->offset] = value;
+ return true;
+ }
bool integer;
if (!size || json_number(text, size, &integer) != size || size > 40) return false;
if (field->type == PURE_FIELD_U32) {
@@ -304,7 +317,14 @@ static const char *rows_of(PureJsonMessage *b, unsigned char *json, size_t size,
}
if (given[k]) return "json_field";
given[k] = true;
- bool text = m->record_field_text[record->first + k];
+ bool text = m->record_field_text[record->first + k], truth;
+ if (m->record_field_bool[record->first + k]) { /* true or false, nothing else */
+ size_t word = json_bool(json + *at, size - *at, &truth);
+ if (!word) return "json_type";
+ numbers[k] = truth;
+ *at = pure_json_space(json, *at + word, size);
+ continue;
+ }
if (*at < size && json[*at] == '"') {
size_t end = json_string_end(json + *at + 1, size - *at - 1), written;
if (!end) return "json";
@@ -406,7 +426,13 @@ static const char *members(PureJsonMessage *b, unsigned char *json, size_t size,
if (b->seen[f] || (b->given && b->given[f])) return "json_field";
b->seen[f] = true;
const PureField *field = &m->field[b->shape->field_start + f];
- bool variable = field->type == PURE_FIELD_BYTES || field->type == PURE_FIELD_TEXT, integer;
+ bool variable = field->type == PURE_FIELD_BYTES || field->type == PURE_FIELD_TEXT, integer, truth;
+ size_t word = field->type == PURE_FIELD_BOOL ? json_bool(json + *at, size - *at, &truth) : 0;
+ if (word) { /* any other value for a bool is of another type (below) */
+ b->payload[field->offset] = truth;
+ *at = pure_json_space(json, *at + word, size);
+ continue;
+ }
if (*at < size && json[*at] == '"') {
size_t end = json_string_end(json + *at + 1, size - *at - 1), written;
if (!end) return "json";
diff --git a/serve/message-json.h b/serve/message-json.h
index c5332b4..ffb1651 100644
--- a/serve/message-json.h
+++ b/serve/message-json.h
@@ -26,7 +26,7 @@ typedef struct {
* "limit": rows past what a payload holds, "memory"). */
const char *pure_json_message(PureJsonMessage *, unsigned char *json, size_t size, size_t *at);
size_t pure_json_space(const unsigned char *json, size_t at, size_t size);
-/* A fixed field's value from its text (a query value or a JSON number) into
- * the prefix: a u32 in range, or a finite number. */
+/* A fixed field's value from its text (a query value or a JSON value) into
+ * the prefix: a u32 in range, a finite number, or true or false for a bool. */
bool pure_json_fixed(const PureField *field, const unsigned char *text, size_t size, unsigned char *prefix);
#endif
diff --git a/serve/serve.c b/serve/serve.c
index f2d455f..43297c1 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -805,7 +805,7 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route *
while (f < (message ? shape->field_count : 0) && strcmp(m->field[shape->field_start + f].name, pair)) ++f;
const PureField *field = message && f < shape->field_count ? &m->field[shape->field_start + f] : NULL;
long length = percent_decoded(value, strlen(value)); /* as a view's arguments are */
- if (!field || c->given[f] || (field->type != PURE_FIELD_U32 && field->type != PURE_FIELD_NUMBER) ||
+ if (!field || c->given[f] || !pure_field_fixed(field->type) ||
length < 0 || !pure_json_fixed(field, (const unsigned char *)value, (size_t)length, c->fields))
return "query";
c->given[f] = true;
@@ -814,7 +814,7 @@ static const char *resolve(const Host *host, Connection *c, char *query, Route *
}
for (uint32_t f = 0; message && !c->json && f < shape->field_count; ++f) {
uint8_t type = m->field[shape->field_start + f].type;
- if ((type == PURE_FIELD_U32 || type == PURE_FIELD_NUMBER) && !c->given[f]) return "missing_field";
+ if (pure_field_fixed(type) && !c->given[f]) return "missing_field";
}
for (uint32_t f = 0; c->batch > 1 && f < shape->field_count; ++f)
if (c->given[f]) return "delivery"; /* a batch's fields are all in its objects */
@@ -871,8 +871,8 @@ static const char *convert(Connection *c) {
if (b.seen[f] || c->given[f]) continue;
const PureField *field = &c->host->module->field[shape->field_start + f];
if (!delivered || field->type == PURE_FIELD_ROWS) why = "missing_field";
- else if (field->type == PURE_FIELD_U32 || field->type == PURE_FIELD_NUMBER)
- memset(b.payload + field->offset, 0, field->type == PURE_FIELD_NUMBER ? 8 : 4);
+ else if (pure_field_fixed(field->type))
+ memset(b.payload + field->offset, 0, pure_field_size(field->type));
else put_u32(b.table + 8 * f, 0), put_u32(b.table + 8 * f + 4, 0);
}
if (!why && c->batch > 1 && b.rows_size) why = "delivery"; /* an output sends no rows */
@@ -1258,7 +1258,7 @@ static unsigned char *arguments_payload(const PureModule *m, int input, const ch
size_t at = shape->prefix;
for (uint32_t f = 0; ok && f < shape->field_count; ++f) {
const PureField *field = &m->field[shape->field_start + f];
- if (field->type == PURE_FIELD_U32 || field->type == PURE_FIELD_NUMBER) {
+ if (pure_field_fixed(field->type)) {
ok = values[f] && pure_json_fixed(field, (const unsigned char *)values[f], (size_t)lengths[f], payload);
continue;
}
@@ -1303,10 +1303,11 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available,
PureResult status = PURE_OK;
double value;
if (m->view_type[view] == PURE_VIEW_TABLE && !(o.bytes = malloc(o.capacity = 1024))) return PURE_ALLOCATION;
- if (m->view_type[view] == PURE_VIEW_NUM) {
+ if (m->view_type[view] == PURE_VIEW_NUM || m->view_type[view] == PURE_VIEW_BOOL) {
*type = "text/plain; charset=utf-8";
- if (pure_app_view(app, view, &value)) put_number(&o, value);
- else status = PURE_NOT_FOUND;
+ if (!pure_app_view(app, view, &value)) status = PURE_NOT_FOUND;
+ else if (m->view_type[view] == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5);
+ else put_number(&o, value);
} else if (m->view_type[view] == PURE_VIEW_TEXT) {
*type = "text/plain; charset=utf-8";
/* A text state shown as it is goes out as its stored extents. */
@@ -1341,9 +1342,11 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available,
put_bytes(&o, ",{" + (row == from), 2 - (row == from));
for (unsigned k = 0; k < count; ++k) {
put_bytes(&o, columns[k].key + !k, columns[k].length - !k);
- if (m->view_type[columns[k].view] == PURE_VIEW_NUM) {
- if (pure_app_cell(app, columns[k].view, row, &value)) put_number(&o, value);
- else put_bytes(&o, "null", 4);
+ uint8_t kind = m->view_type[columns[k].view];
+ if (kind == PURE_VIEW_NUM || kind == PURE_VIEW_BOOL) {
+ if (!pure_app_cell(app, columns[k].view, row, &value)) put_bytes(&o, "null", 4);
+ else if (kind == PURE_VIEW_BOOL) put_bytes(&o, value ? "true" : "false", value ? 4 : 5);
+ else put_number(&o, value);
continue;
}
size_t mark = o.size;
@@ -1855,7 +1858,8 @@ static bool same_states(const PureModule *a, const PureModule *b) {
a->record[i].count != b->record[i].count)
return false;
for (uint32_t i = 0; i < a->record_fields; ++i)
- if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_text[i] != b->record_field_text[i])
+ if (strcmp(a->record_field_names[i], b->record_field_names[i]) || a->record_field_text[i] != b->record_field_text[i] ||
+ a->record_field_bool[i] != b->record_field_bool[i])
return false;
for (uint32_t i = 0; i < a->collections; ++i)
if (strcmp(a->collection_names[i], b->collection_names[i])) return false;
diff --git a/serve/test-message-json.c b/serve/test-message-json.c
index 618a363..b2bad37 100644
--- a/serve/test-message-json.c
+++ b/serve/test-message-json.c
@@ -1,7 +1,7 @@
/* JSON numbers are read as strtod reads them, bit for bit: in the forms a
* client writes (a program's shortest digits, fixed decimals, exponents), at
* the edges of the exact path (2^53, 10^22) and past them, and refused when
- * not finite or not JSON. */
+ * not finite or not JSON. A bool is true or false, nothing else. */
#include "message-json.h"
#include <math.h>
#include <stdint.h>
@@ -43,6 +43,15 @@ int main(void) {
double value;
CHECK(!read_number(refused[i], &value));
}
+ static const PureField truth = {.type = PURE_FIELD_BOOL, .offset = 1};
+ unsigned char prefix[2] = {7, 7};
+ CHECK(pure_json_fixed(&truth, (const unsigned char *)"true", 4, prefix) && prefix[1] == 1 && prefix[0] == 7);
+ CHECK(pure_json_fixed(&truth, (const unsigned char *)"false", 5, prefix) && prefix[1] == 0);
+ static const char *const not_bools[] = {"", "1", "0", "True", "\"true\"", "tru", "truex", "false ", "null", "t"};
+ for (size_t i = 0; i < sizeof(not_bools) / sizeof(*not_bools); ++i)
+ CHECK(!pure_json_fixed(&truth, (const unsigned char *)not_bools[i], strlen(not_bools[i]), prefix) && prefix[1] == 0);
+ double number;
+ CHECK(!read_number("true", &number) && !read_number("false", &number));
uint64_t seed = 0x6a736f6eu;
size_t read = 0;
for (unsigned i = 0; i < 400000; ++i) {