aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/program.c
diff options
context:
space:
mode:
Diffstat (limited to 'prototypes/connections/program.c')
-rw-r--r--prototypes/connections/program.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/prototypes/connections/program.c b/prototypes/connections/program.c
new file mode 100644
index 0000000..780d11a
--- /dev/null
+++ b/prototypes/connections/program.c
@@ -0,0 +1,374 @@
+#include "program.h"
+#include "store.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+enum { NAME = 256, NUMBER, SYMBOLS = 32, LAYOUTS = 4, FIELDS = 16 };
+typedef enum { BYTE_VALUE, RESULT_VALUE, LAYOUT_VALUE, INDEX_VALUE } ValueKind;
+typedef struct {
+ char name[32];
+ ValueKind kind;
+ PureRegion region;
+ size_t step, layout;
+ PureIndex index;
+} Symbol;
+typedef struct {
+ char name[32];
+ size_t offset, length;
+ bool number, tail;
+} Field;
+typedef struct {
+ char name[32];
+ Field fields[FIELDS];
+ size_t count, prefix;
+ bool tail;
+} Layout;
+typedef struct {
+ const char *source;
+ size_t length, at, line, token_line;
+ int token;
+ char name[32];
+ size_t number;
+ char *error;
+ size_t error_size;
+ bool failed, put, effect;
+ Symbol symbols[SYMBOLS];
+ size_t count, layout_count;
+ Layout layouts[LAYOUTS];
+ PureProgram program;
+} Parser;
+
+static bool reject(Parser *p, const char *message) {
+ if (!p->failed && p->error_size) snprintf(p->error, p->error_size, "line %zu: %s", p->token_line, message);
+ p->failed = true;
+ return false;
+}
+
+static bool letter(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
+}
+
+static bool next(Parser *p) {
+ while (p->at < p->length) {
+ char c = p->source[p->at];
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
+ if (c == '\n') ++p->line;
+ ++p->at;
+ } else if (c == '/' && p->at + 1 < p->length && p->source[p->at + 1] == '/') {
+ while (p->at < p->length && p->source[p->at] != '\n') ++p->at;
+ } else break;
+ }
+ p->token_line = p->line;
+ p->name[0] = 0;
+ p->token = 0;
+ if (p->at == p->length) return true;
+ char c = p->source[p->at++];
+ if (letter(c)) {
+ size_t start = p->at - 1;
+ while (p->at < p->length && (letter(p->source[p->at]) ||
+ (p->source[p->at] >= '0' && p->source[p->at] <= '9'))) ++p->at;
+ size_t size = p->at - start;
+ if (size >= sizeof(p->name)) return reject(p, "name exceeds 31 bytes");
+ memcpy(p->name, p->source + start, size);
+ p->name[size] = 0;
+ p->token = NAME;
+ } else if (c >= '0' && c <= '9') {
+ size_t value = (size_t)(c - '0');
+ while (p->at < p->length && p->source[p->at] >= '0' && p->source[p->at] <= '9') {
+ unsigned digit = (unsigned)(p->source[p->at++] - '0');
+ if (value > (SIZE_MAX - digit) / 10) return reject(p, "integer overflow");
+ value = value * 10 + digit;
+ }
+ p->number = value;
+ p->token = NUMBER;
+ } else if (c && strchr("{}().,=;:[]", c)) p->token = (unsigned char)c;
+ else return reject(p, "unexpected character");
+ return true;
+}
+
+static bool is_word(Parser *p, const char *word) {
+ return p->token == NAME && !strcmp(p->name, word);
+}
+
+static bool word(Parser *p, const char *expected) {
+ return is_word(p, expected) ? next(p) : reject(p, expected);
+}
+
+static bool token(Parser *p, int expected) {
+ return p->token == expected ? next(p) : reject(p, "unexpected token");
+}
+
+static Symbol *find(Parser *p, const char *name) {
+ for (size_t i = 0; i < p->count; ++i)
+ if (!strcmp(name, p->symbols[i].name)) return &p->symbols[i];
+ return NULL;
+}
+
+static bool field(Parser *p, const char *object, const char *name) {
+ return word(p, object) && token(p, '.') && word(p, name);
+}
+
+static bool require_bytes(Parser *p, const Symbol *value) {
+ return value->kind == BYTE_VALUE || reject(p, "expected bytes; select a byte field");
+}
+
+static void require_length(Parser *p, size_t required) {
+ size_t *minimum = p->put ? &p->program.input_required : &p->program.read_required;
+ if (required > *minimum) *minimum = required;
+}
+
+static Layout *find_layout(Parser *p, const char *name) {
+ for (size_t i = 0; i < p->layout_count; ++i)
+ if (!strcmp(name, p->layouts[i].name)) return &p->layouts[i];
+ return NULL;
+}
+
+static bool layout_definition(Parser *p) {
+ if (p->layout_count == LAYOUTS) return reject(p, "program exceeds 4 layouts");
+ if (!next(p)) return false;
+ if (p->token != NAME || find_layout(p, p->name)) return reject(p, "expected a unique layout name");
+ Layout *layout = &p->layouts[p->layout_count];
+ strcpy(layout->name, p->name);
+ if (!next(p) || !token(p, '{')) return false;
+ while (p->token != '}') {
+ if (layout->count == FIELDS) return reject(p, "layout exceeds 16 fields");
+ if (layout->tail) return reject(p, "variable bytes must be the last field");
+ if (p->token != NAME) return reject(p, "expected a field name");
+ for (size_t i = 0; i < layout->count; ++i)
+ if (!strcmp(layout->fields[i].name, p->name)) return reject(p, "duplicate field");
+ Field *f = &layout->fields[layout->count];
+ strcpy(f->name, p->name);
+ f->offset = layout->prefix;
+ if (!next(p) || !token(p, ':')) return false;
+ if (is_word(p, "u32le")) {
+ f->number = true;
+ f->length = 4;
+ if (!next(p)) return false;
+ } else if (is_word(p, "bytes")) {
+ if (!next(p)) return false;
+ if (p->token == '[') {
+ if (!next(p)) return false;
+ if (p->token != NUMBER) return reject(p, "fixed bytes requires a constant size");
+ f->length = p->number;
+ if (!next(p) || !token(p, ']')) return false;
+ } else { f->tail = true; layout->tail = true; }
+ } else return reject(p, "supported field types: u32le, bytes[N], bytes");
+ if (f->length > PURE_OBJECT_LIMIT - layout->prefix) return reject(p, "layout exceeds object bound");
+ layout->prefix += f->length;
+ ++layout->count;
+ if (!token(p, ';')) return false;
+ }
+ if (!layout->count) return reject(p, "empty layout");
+ ++p->layout_count;
+ return token(p, '}');
+}
+
+static bool reference(Parser *p, Symbol *out) {
+ if (is_word(p, "request")) {
+ if (!p->put) return reject(p, "request.body is only available in on put");
+ *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}};
+ return field(p, "request", "body");
+ }
+ Symbol *symbol = p->token == NAME ? find(p, p->name) : NULL;
+ if (!symbol) return reject(p, "unknown value");
+ *out = *symbol;
+ if (!next(p)) return false;
+ if (p->token != '.') return true;
+ if (!next(p)) return false;
+ if (symbol->kind == RESULT_VALUE) {
+ if (p->put) return reject(p, "write result has no body; use the input value");
+ *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}};
+ return word(p, "body");
+ }
+ if (symbol->kind != LAYOUT_VALUE || p->token != NAME) return reject(p, "expected a layout field");
+ Layout *layout = &p->layouts[symbol->layout];
+ Field *f = NULL;
+ for (size_t i = 0; i < layout->count; ++i)
+ if (!strcmp(p->name, layout->fields[i].name)) f = &layout->fields[i];
+ if (!f) return reject(p, "unknown layout field");
+ size_t offset = symbol->region.offset + f->offset;
+ *out = (Symbol){.kind = f->number ? INDEX_VALUE : BYTE_VALUE};
+ if (f->number) out->index = (PureIndex){.value = offset, .from_u32le = true};
+ else out->region = (PureRegion){.offset = offset,
+ .length = f->tail ? (symbol->region.to_end ? 0 : symbol->region.length - f->offset) : f->length,
+ .to_end = f->tail && symbol->region.to_end};
+ return next(p);
+}
+
+static bool byte_reference(Parser *p, Symbol *out) {
+ return reference(p, out) && require_bytes(p, out);
+}
+
+static bool index_value(Parser *p, PureIndex *out) {
+ if (p->token == NUMBER) {
+ *out = (PureIndex){.value = p->number};
+ return next(p);
+ }
+ Symbol value;
+ if (!reference(p, &value)) return false;
+ if (value.kind != INDEX_VALUE) return reject(p, "range requires an integer or u32le field");
+ *out = value.index;
+ return true;
+}
+
+static bool layout_view(Parser *p, Symbol *out) {
+ if (!next(p) || !token(p, '(')) return false;
+ Layout *layout = p->token == NAME ? find_layout(p, p->name) : NULL;
+ if (!layout) return reject(p, "unknown layout");
+ if (!next(p) || !token(p, ',')) return false;
+ Symbol source;
+ if (!byte_reference(p, &source) || !token(p, ')')) return false;
+ if (source.step) return reject(p, "layout view requires a statically located source");
+ size_t bound = source.region.to_end ? PURE_OBJECT_LIMIT - source.region.offset : source.region.length;
+ if (layout->prefix > bound) return reject(p, "layout exceeds source bound");
+ require_length(p, source.region.offset + layout->prefix);
+ *out = (Symbol){.kind = LAYOUT_VALUE, .region = source.region, .layout = (size_t)(layout - p->layouts)};
+ if (!layout->tail) { out->region.length = layout->prefix; out->region.to_end = false; }
+ return true;
+}
+
+static bool view(Parser *p, Symbol *out) {
+ if (is_word(p, "view")) return layout_view(p, out);
+ if (!is_word(p, "range")) return reference(p, out);
+ Symbol parent;
+ PureIndex offset, length;
+ if (!next(p) || !token(p, '(') || !byte_reference(p, &parent) || !token(p, ',') ||
+ !index_value(p, &offset) || !token(p, ',') || !index_value(p, &length) || !token(p, ')')) return false;
+ bool dynamic = parent.step || offset.from_u32le || length.from_u32le;
+ *out = (Symbol){.kind = BYTE_VALUE};
+ if (dynamic) {
+ if (!p->put) return reject(p, "data-dependent ranges currently require PUT input");
+ if (p->program.input_steps == PURE_DYNAMIC_LIMIT) return reject(p, "input exceeds 8 dynamic ranges");
+ if ((!offset.from_u32le && offset.value > PURE_OBJECT_LIMIT) ||
+ (!length.from_u32le && length.value > PURE_OBJECT_LIMIT)) return reject(p, "range exceeds object bound");
+ PureRangeStep *step = &p->program.steps[p->program.input_steps++];
+ *step = (PureRangeStep){.source = parent.region, .source_step = parent.step,
+ .offset = offset, .length = length};
+ out->step = p->program.input_steps;
+ } else {
+ size_t bound = parent.region.to_end ? PURE_OBJECT_LIMIT - parent.region.offset : parent.region.length;
+ if (offset.value > bound || length.value > bound - offset.value) return reject(p, "range exceeds its source bound");
+ out->region = (PureRegion){.offset = parent.region.offset + offset.value, .length = length.value};
+ require_length(p, out->region.offset + out->region.length);
+ }
+ ++p->program.range_definitions;
+ return true;
+}
+
+static bool effect(Parser *p, Symbol *out) {
+ if (p->effect) return reject(p, "only one storage effect is supported per handler");
+ if (!word(p, "store") || !token(p, '.')) return false;
+ bool splice = p->put && is_word(p, "splice");
+ if (!word(p, p->put ? (splice ? "splice" : "write") : "read") ||
+ !token(p, '(') || !field(p, "request", "key")) return false;
+ if (p->put) {
+ Symbol value;
+ if (!token(p, ',') || !field(p, "request", "revision") || !token(p, ',')) return false;
+ if (splice) {
+ if (!index_value(p, &p->program.splice_offset) || !token(p, ',') ||
+ !index_value(p, &p->program.splice_remove) || !token(p, ',')) return false;
+ if ((!p->program.splice_offset.from_u32le && p->program.splice_offset.value > PURE_OBJECT_LIMIT) ||
+ (!p->program.splice_remove.from_u32le && p->program.splice_remove.value > PURE_OBJECT_LIMIT))
+ return reject(p, "splice exceeds object bound");
+ p->program.splice = true;
+ }
+ if (!byte_reference(p, &value)) return false;
+ p->program.write = value.region;
+ p->program.write_step = value.step;
+ if (splice && p->token == ',') {
+ if (!next(p)) return false;
+ if (p->token != NUMBER || p->number > PURE_OBJECT_LIMIT)
+ return reject(p, "splice limit requires a constant within the object bound");
+ p->program.splice_limit = (uint32_t)p->number;
+ if (!next(p)) return false;
+ }
+ }
+ if (!token(p, ')')) return false;
+ out->kind = RESULT_VALUE;
+ p->effect = true;
+ return true;
+}
+
+static bool handler(Parser *p) {
+ p->count = 0;
+ p->effect = false;
+ if (!token(p, '{')) return false;
+ while (is_word(p, "let")) {
+ if (p->put && p->effect) return reject(p, "all views must precede the storage effect; reply must follow it");
+ if (p->count == SYMBOLS) return reject(p, "handler exceeds 32 definitions");
+ if (!next(p)) return false;
+ if (p->token != NAME || is_word(p, "request") || is_word(p, "store") ||
+ is_word(p, "range") || is_word(p, "view")) return reject(p, "expected a non-reserved definition name");
+ if (find(p, p->name)) return reject(p, "duplicate definition");
+ char name[32];
+ strcpy(name, p->name);
+ Symbol symbol = {0};
+ if (!next(p) || !token(p, '=')) return false;
+ if (is_word(p, "store")) {
+ if (!effect(p, &symbol)) return false;
+ } else {
+ if (!view(p, &symbol)) return false;
+ ++p->program.view_definitions;
+ }
+ if (!token(p, ';')) return false;
+ strcpy(symbol.name, name);
+ p->symbols[p->count++] = symbol;
+ }
+ if (!p->effect) return reject(p, "handler requires its storage effect");
+ if (!word(p, "reply") || !token(p, '(')) return false;
+ Symbol *result = p->token == NAME ? find(p, p->name) : NULL;
+ if (!result || result->kind != RESULT_VALUE) return reject(p, "reply requires this handler's storage result");
+ if (!next(p)) return false;
+ Symbol body = {0};
+ bool has_body = p->token == ',';
+ if (has_body && (!next(p) || !byte_reference(p, &body))) return false;
+ if (!token(p, ')') || !token(p, ';') || !token(p, '}')) return false;
+ if (p->put) {
+ p->program.reply = body.region;
+ p->program.reply_step = body.step;
+ p->program.reply_input = has_body;
+ } else p->program.read = body.region;
+ return true;
+}
+
+bool pure_program_parse(const char *source, size_t length, PureProgram *out,
+ char *error, size_t error_size) {
+ Parser p = {.source = source, .length = length, .line = 1, .token_line = 1,
+ .error = error, .error_size = error_size,
+ .program.splice_limit = PURE_OBJECT_LIMIT};
+ if (error_size) error[0] = 0;
+ if (length > PURE_PROGRAM_LIMIT) return reject(&p, "program exceeds 16 KiB");
+ if (!next(&p)) return false;
+ while (is_word(&p, "layout")) if (!layout_definition(&p)) return false;
+ unsigned handlers = 0;
+ while (p.token) {
+ if (!word(&p, "on")) return false;
+ if (!is_word(&p, "get") && !is_word(&p, "put")) return reject(&p, "expected get or put handler");
+ p.put = is_word(&p, "put");
+ unsigned bit = p.put ? 2 : 1;
+ if (handlers & bit) return reject(&p, "duplicate handler");
+ handlers |= bit;
+ if (!next(&p) || !handler(&p)) return false;
+ }
+ if (handlers != 3) return reject(&p, "both get and put handlers are required");
+ *out = p.program; /* Invalid preparation never changes the installed plan. */
+ return true;
+}
+
+bool pure_program_load(const char *path, PureProgram *out, char *error, size_t error_size) {
+ FILE *file = fopen(path, "rb");
+ if (!file) {
+ if (error_size) snprintf(error, error_size, "cannot open program");
+ return false;
+ }
+ char source[PURE_PROGRAM_LIMIT + 1];
+ size_t length = fread(source, 1, sizeof(source), file);
+ bool bad = ferror(file) != 0;
+ if (fclose(file)) bad = true;
+ if (bad) {
+ if (error_size) snprintf(error, error_size, "cannot read program");
+ return false;
+ }
+ return pure_program_parse(source, length, out, error, error_size);
+}