diff options
| author | eunhhu <[email protected]> | 2026-09-25 11:55:47 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-25 11:55:47 +0100 |
| commit | 3265bc65ef6d2e399be6141f678f4f299a2d7491 (patch) | |
| tree | ebeb9661d528dbf99a4b3f68b56580255fe4a7a2 /src | |
| parent | a6bf235045a5354b00a5021b70de6dad1524f3ef (diff) | |
| download | aup-3265bc65ef6d2e399be6141f678f4f299a2d7491.tar.gz aup-3265bc65ef6d2e399be6141f678f4f299a2d7491.zip | |
Compile messages and keyed documents to module v2
aup now emits Pure module v2 and adds keyed byte collections
(state documents: bytes by key), message inputs by key with u32 and a
trailing bytes field, on-message handlers, field access, name[key] reads
and .bytes sizes, replace(a .. b, value), reply and require, all checked
for number/bytes types with file:line:column diagnostics.
The document editor and its size-bounded variant are rewritten in aup.
make check runs them through pure-edit-module against pure-edit running the
original editor.pure programs: 1,280 random show/edit/apply operations,
including conflicts, limits, missing documents, budgets and invalid keys,
produce byte-identical stdout, stderr and exit codes, and each CLI reads the
other's store identically. v0a parity with the workbench engine, 33
diagnostics and generated/damaged programs still pass, also with a
sanitizer-built compiler.
Diffstat (limited to 'src')
| -rw-r--r-- | src/compile.c | 436 |
1 files changed, 353 insertions, 83 deletions
diff --git a/src/compile.c b/src/compile.c index 24b5c71..e2000ee 100644 --- a/src/compile.c +++ b/src/compile.c @@ -7,10 +7,13 @@ #include <stdlib.h> #include <string.h> -/* Limits and opcodes of Pure module format v1. */ -enum { STATES = 32, INPUTS = 8, VIEWS = 24, HANDLERS = 64, CODE = 512, STACK = 64, NAME = 32, DEPTH = 64 }; +/* Limits and opcodes of Pure module format v2. */ +enum { STATES = 32, INPUTS = 8, VIEWS = 24, HANDLERS = 64, CODE = 512, STACK = 64, NAME = 32, + DEPTH = 64, FIELDS = 32, COLLECTIONS = 1 }; enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR, NOT, - MIN, MAX, CLAMP, STORE, JZ, JMP }; + MIN, MAX, CLAMP, STORE, JZ, JMP, FIELD, DOC, DOC_SIZE, LEN, REPLACE, REPLY, REQUIRE }; +enum { KIND_KEY = 1, KIND_MESSAGE = 2, FIELD_U32 = 1, FIELD_BYTES = 2 }; +typedef enum { NUM = 1, BYTES = 2 } Type; typedef enum { T_EOF, T_NL, T_NUMBER, T_NAME, T_PUNCT } TokenKind; typedef struct { @@ -20,25 +23,42 @@ typedef struct { char text[NAME + 1]; /* name, or punctuation of one or two characters */ } Token; -typedef enum { E_NUMBER, E_STATE, E_DT, E_UNARY, E_BINARY, E_CALL } ExprKind; +typedef enum { + E_NUMBER, E_NAME, E_DT, E_UNARY, E_BINARY, E_CALL, + E_FIELD, /* name.member */ + E_DOC, /* name[key] */ + E_SIZE, /* name[key].bytes */ + E_LEN, /* value.bytes */ +} ExprKind; typedef struct Expr { ExprKind kind; - int line, column, op, state; + int line, column, op; double number; - char name[NAME]; + char name[NAME], member[NAME]; struct Expr *args[3]; unsigned count; } Expr; +typedef enum { S_ASSIGN, S_IF, S_REPLACE, S_REPLY, S_REQUIRE } StmtKind; typedef struct Stmt { - bool branch; - int line, column, state; + StmtKind kind; + int line, column; char name[NAME]; - Expr *value; /* assignment value or branch condition */ + Expr *value; /* assigned value, condition, reply, requirement or insertion */ + Expr *first, *last; /* replace range */ struct Stmt *then, *otherwise, *next; } Stmt; typedef struct { + char name[NAME], key[NAME]; + int kind; + bool keyed; + char field_names[FIELDS][NAME]; + int field_types[FIELDS]; + unsigned fields; +} Input; + +typedef struct { const char *source; size_t length, at; int line, column; @@ -53,21 +73,25 @@ typedef struct { char state_names[STATES][NAME]; double initial[STATES]; unsigned states; - char input_names[INPUTS][NAME], input_keys[INPUTS][NAME]; - unsigned inputs; + char collection_names[COLLECTIONS][NAME]; + unsigned collections; + Input inputs[INPUTS]; + unsigned input_count, field_count; char view_names[VIEWS][NAME]; Expr *view_value[VIEWS]; unsigned views; struct { char input[NAME]; int edge, line, column; Stmt *body; } handlers[HANDLERS]; unsigned handler_count; /* Code generation. */ + int message; /* input of the handler being generated, or -1 */ struct { uint8_t op; uint32_t arg; double value; } code[CODE]; unsigned code_count; uint32_t view_code[VIEWS][2], handler_code[HANDLERS][2]; } Compiler; -static const char *const reserved[] = {"state", "input", "view", "on", "if", "else", "key", - "frame", "dt", "min", "max", "clamp"}; +static const char *const reserved[] = {"state", "input", "view", "on", "if", "else", "key", "frame", + "dt", "min", "max", "clamp", "message", "by", "bytes", "u32", + "reply", "require"}; static _Noreturn void fail_at(Compiler *c, int line, int column, const char *format, ...) { va_list args; @@ -117,10 +141,10 @@ static Token scan(Compiler *c) { char ch = peek_char(c, 0); if (ch == '\n') { advance(c); token.kind = T_NL; return token; } if (digit(ch) || (ch == '.' && digit(peek_char(c, 1)))) { - /* \d+(\.\d*)?|\.\d+ with an optional exponent, as the workbench reads it. */ + /* \d+(\.\d*)?|\.\d+ with an optional exponent; a '.' that starts '..' ends the number. */ char text[400]; size_t n = 0; - while (digit(peek_char(c, 0)) || peek_char(c, 0) == '.') { + while (digit(peek_char(c, 0)) || (peek_char(c, 0) == '.' && peek_char(c, 1) != '.')) { if (peek_char(c, 0) == '.' && memchr(text, '.', n)) break; if (n + 1 >= sizeof(text)) fail_at(c, token.line, token.column, "number is too long"); text[n++] = peek_char(c, 0); @@ -152,7 +176,7 @@ static Token scan(Compiler *c) { token.kind = T_NAME; return token; } - static const char *const pairs[] = {"<=", ">=", "==", "!=", "&&", "||"}; + static const char *const pairs[] = {"<=", ">=", "==", "!=", "&&", "||", ".."}; for (size_t i = 0; i < sizeof(pairs) / sizeof(*pairs); ++i) if (ch == pairs[i][0] && peek_char(c, 1) == pairs[i][1]) { memcpy(token.text, pairs[i], 3); @@ -161,7 +185,7 @@ static Token scan(Compiler *c) { token.kind = T_PUNCT; return token; } - if (strchr("=+-*/!<>(){},.;", ch)) { + if (strchr("=+-*/!<>(){}[],.;:", ch)) { token.text[0] = ch; advance(c); token.kind = T_PUNCT; @@ -202,6 +226,11 @@ static Token expect_name(Compiler *c, const char *what) { return take(c); } +static void expect_word(Compiler *c, const char *name) { + if (!word(c, name)) fail_at(c, c->token.line, c->token.column, "expected '%s'", name); + take(c); +} + static void newlines(Compiler *c) { while (c->token.kind == T_NL) take(c); } static void separators(Compiler *c) { while (c->token.kind == T_NL || is(c, ";")) take(c); } @@ -232,6 +261,42 @@ static Expr *node(Compiler *c, ExprKind kind, const Token *at) { return e; } +/* name[key]: collections are indexed only by the message key. */ +static void index_key(Compiler *c) { + take(c); /* [ */ + if (!word(c, "key")) + fail_at(c, c->token.line, c->token.column, "a collection is indexed by the message key, as in name[key]"); + take(c); + expect(c, "]", "']'"); +} + +static Expr *postfix(Compiler *c, Expr *e) { + if (is(c, "[")) { + index_key(c); + e->kind = E_DOC; + if (is(c, ".") && lookahead(c)->kind == T_NAME && !strcmp(lookahead(c)->text, "bytes")) { + take(c); + take(c); + e->kind = E_SIZE; + } + return e; + } + while (is(c, ".")) { + take(c); + Token member = expect_name(c, "a field name or bytes"); + if (!strcmp(member.text, "bytes")) { + Expr *length = node(c, E_LEN, &member); + length->args[0] = e; + length->count = 1; + e = length; + } else if (e->kind == E_NAME) { + e->kind = E_FIELD; + memcpy(e->member, member.text, NAME); + } else fail_at(c, member.line, member.column, "only bytes can follow a field"); + } + return e; +} + static Expr *expression(Compiler *c, int minimum, bool multiline) { if (++c->depth > DEPTH) fail_at(c, c->token.line, c->token.column, "expression nesting exceeds 64"); if (multiline) newlines(c); @@ -274,8 +339,9 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { left = node(c, E_DT, &token); } else { if (reserved_name(token.text)) fail_at(c, token.line, token.column, "expected a number, state, or expression"); - left = node(c, E_STATE, &token); + left = node(c, E_NAME, &token); memcpy(left->name, token.text, NAME); + left = postfix(c, left); } } else fail_at(c, token.line, token.column, "expected a number, state, or expression"); for (;;) { @@ -299,15 +365,15 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { static Stmt *block(Compiler *c, unsigned depth); static Stmt *statement(Compiler *c, unsigned depth) { - Token token = expect_name(c, "assignment, if, or '}'"); + Token token = expect_name(c, "a statement or '}'"); Stmt *s = allocate(c, sizeof(*s)); s->line = token.line; s->column = token.column; if (!strcmp(token.text, "if")) { - s->branch = true; + s->kind = S_IF; s->value = expression(c, 0, false); s->then = block(c, depth + 1); - /* else may follow on the same line or after newlines */ + /* else may follow on the same line or after one newline */ bool has_else = word(c, "else"); if (!has_else && c->token.kind == T_NL) { const Token *next = lookahead(c); @@ -320,8 +386,32 @@ static Stmt *statement(Compiler *c, unsigned depth) { } return s; } - if (reserved_name(token.text)) fail_at(c, token.line, token.column, "expected assignment, if, or '}'"); + if (!strcmp(token.text, "reply") || !strcmp(token.text, "require")) { + s->kind = token.text[2] == 'p' ? S_REPLY : S_REQUIRE; + s->value = expression(c, 0, false); + return s; + } + if (reserved_name(token.text)) fail_at(c, token.line, token.column, "expected a statement or '}'"); memcpy(s->name, token.text, NAME); + if (is(c, "[")) { + index_key(c); + expect(c, ".", "'.replace'"); + Token method = expect_name(c, "replace"); + if (strcmp(method.text, "replace")) fail_at(c, method.line, method.column, "a collection entry supports replace(first .. last, value)"); + expect(c, "(", "'('"); + s->kind = S_REPLACE; + s->first = expression(c, 0, true); + newlines(c); + expect(c, "..", "'..'"); + s->last = expression(c, 0, true); + newlines(c); + expect(c, ",", "','"); + s->value = expression(c, 0, true); + newlines(c); + expect(c, ")", "')'"); + return s; + } + s->kind = S_ASSIGN; expect(c, "=", "'='"); s->value = expression(c, 0, false); return s; @@ -343,9 +433,16 @@ static Stmt *block(Compiler *c, unsigned depth) { return first; } +static int input_index(const Compiler *c, const char *name) { + for (unsigned i = 0; i < c->input_count; ++i) if (!strcmp(c->inputs[i].name, name)) return (int)i; + return -1; +} + static const char *declared_kind(const Compiler *c, const char *name) { for (unsigned i = 0; i < c->states; ++i) if (!strcmp(c->state_names[i], name)) return "state"; - for (unsigned i = 0; i < c->inputs; ++i) if (!strcmp(c->input_names[i], name)) return "input"; + for (unsigned i = 0; i < c->collections; ++i) if (!strcmp(c->collection_names[i], name)) return "collection"; + int input = input_index(c, name); + if (input >= 0) return c->inputs[input].kind == KIND_KEY ? "key input" : "message"; for (unsigned i = 0; i < c->views; ++i) if (!strcmp(c->view_names[i], name)) return "view"; return NULL; } @@ -364,32 +461,78 @@ static bool key_name(const char *key) { return false; } +/* { name: u32 ... } with at most one bytes field, which comes last. */ +static void message_fields(Compiler *c, Input *in) { + if (!is(c, "{")) return; /* the field block opens on the declaration's line */ + take(c); + separators(c); + while (!is(c, "}")) { + Token name = expect_name(c, "a field name"); + if (reserved_name(name.text)) fail_at(c, name.line, name.column, "'%s' is reserved", name.text); + for (unsigned i = 0; i < in->fields; ++i) + if (!strcmp(in->field_names[i], name.text)) fail_at(c, name.line, name.column, "duplicate field '%s'", name.text); + if (in->fields && in->field_types[in->fields - 1] == FIELD_BYTES) + fail_at(c, name.line, name.column, "a bytes field must be the last field"); + if (c->field_count == FIELDS) fail_at(c, name.line, name.column, "at most 32 message fields are allowed"); + expect(c, ":", "':'"); + Token type = expect_name(c, "u32 or bytes"); + if (strcmp(type.text, "u32") && strcmp(type.text, "bytes")) + fail_at(c, type.line, type.column, "field types are u32 and bytes"); + memcpy(in->field_names[in->fields], name.text, NAME); + in->field_types[in->fields++] = type.text[0] == 'u' ? FIELD_U32 : FIELD_BYTES; + ++c->field_count; + end(c); + } + take(c); +} + static void declaration(Compiler *c) { Token kind = expect_name(c, "state, input, view, or on"); if (!strcmp(kind.text, "state")) { Token name = expect_name(c, "state name"); declare(c, &name); - if (c->states == STATES) fail_at(c, name.line, name.column, "at most 32 states are allowed"); - expect(c, "=", "'='"); - double sign = 1; - if (is(c, "-") || is(c, "+")) sign = take(c).text[0] == '-' ? -1 : 1; - if (c->token.kind != T_NUMBER) fail_at(c, c->token.line, c->token.column, "expected a numeric state initializer"); - memcpy(c->state_names[c->states], name.text, NAME); - c->initial[c->states++] = sign * take(c).number; + if (is(c, ":")) { + take(c); + expect_word(c, "bytes"); + expect_word(c, "by"); + expect_word(c, "key"); + if (c->collections == COLLECTIONS) fail_at(c, name.line, name.column, "at most one keyed collection is allowed"); + memcpy(c->collection_names[c->collections++], name.text, NAME); + } else { + if (c->states == STATES) fail_at(c, name.line, name.column, "at most 32 states are allowed"); + expect(c, "=", "'='"); + double sign = 1; + if (is(c, "-") || is(c, "+")) sign = take(c).text[0] == '-' ? -1 : 1; + if (c->token.kind != T_NUMBER) fail_at(c, c->token.line, c->token.column, "expected a numeric state initializer"); + memcpy(c->state_names[c->states], name.text, NAME); + c->initial[c->states++] = sign * take(c).number; + } } else if (!strcmp(kind.text, "input")) { Token name = expect_name(c, "input name"); declare(c, &name); - if (c->inputs == INPUTS) fail_at(c, name.line, name.column, "at most 8 inputs are allowed"); + if (c->input_count == INPUTS) fail_at(c, name.line, name.column, "at most 8 inputs are allowed"); expect(c, "=", "'='"); - if (!word(c, "key")) fail_at(c, c->token.line, c->token.column, "expected 'key'"); - take(c); - Token key = expect_name(c, "key name"); - if (!key_name(key.text)) fail_at(c, key.line, key.column, "unknown key '%s'", key.text); - for (unsigned i = 0; i < c->inputs; ++i) - if (!strcmp(c->input_keys[i], key.text)) - fail_at(c, key.line, key.column, "key %s is already used by input '%s'", key.text, c->input_names[i]); - memcpy(c->input_names[c->inputs], name.text, NAME); - memcpy(c->input_keys[c->inputs++], key.text, NAME); + Input *in = &c->inputs[c->input_count]; + memset(in, 0, sizeof(*in)); + memcpy(in->name, name.text, NAME); + if (word(c, "message")) { + take(c); + in->kind = KIND_MESSAGE; + if (word(c, "by")) { take(c); expect_word(c, "key"); in->keyed = true; } + ++c->input_count; + message_fields(c, in); + } else { + if (!word(c, "key")) fail_at(c, c->token.line, c->token.column, "expected 'key' or 'message'"); + take(c); + Token key = expect_name(c, "key name"); + if (!key_name(key.text)) fail_at(c, key.line, key.column, "unknown key '%s'", key.text); + for (unsigned i = 0; i < c->input_count; ++i) + if (c->inputs[i].kind == KIND_KEY && !strcmp(c->inputs[i].key, key.text)) + fail_at(c, key.line, key.column, "key %s is already used by input '%s'", key.text, c->inputs[i].name); + in->kind = KIND_KEY; + memcpy(in->key, key.text, NAME); + ++c->input_count; + } } else if (!strcmp(kind.text, "view")) { Token name = expect_name(c, "view name"); declare(c, &name); @@ -399,18 +542,21 @@ static void declaration(Compiler *c) { c->view_value[c->views++] = expression(c, 0, false); } else if (!strcmp(kind.text, "on")) { if (c->handler_count == HANDLERS) fail_at(c, kind.line, kind.column, "at most 64 handlers are allowed"); - Token event = expect_name(c, "frame or an input event"); + Token event = expect_name(c, "frame or an input"); unsigned index = c->handler_count++; c->handlers[index].line = event.line; c->handlers[index].column = event.column; + c->handlers[index].edge = -1; if (strcmp(event.text, "frame")) { /* The input may be declared later; it is resolved at generation. */ - expect(c, ".", "'.' and down or up"); - Token phase = expect_name(c, "down or up"); - if (strcmp(phase.text, "down") && strcmp(phase.text, "up")) - fail_at(c, phase.line, phase.column, "expected down or up"); memcpy(c->handlers[index].input, event.text, NAME); - c->handlers[index].edge = phase.text[0] == 'u'; + if (is(c, ".")) { + take(c); + Token phase = expect_name(c, "down or up"); + if (strcmp(phase.text, "down") && strcmp(phase.text, "up")) + fail_at(c, phase.line, phase.column, "expected down or up"); + c->handlers[index].edge = phase.text[0] == 'u'; + } } c->handlers[index].body = block(c, 0); } else fail_at(c, kind.line, kind.column, "unknown declaration '%s'", kind.text); @@ -431,16 +577,32 @@ static int state_index(const Compiler *c, const char *name) { return -1; } -/* Emit an expression; height is the stack height before it. */ -static void value(Compiler *c, const Expr *e, bool handler, int height) { +static const char *type_name(Type type) { return type == NUM ? "a number" : "bytes"; } + +/* The keyed collection used by name[key] in the current handler. */ +static uint32_t collection(Compiler *c, const char *name, int line, int column) { + int index = -1; + for (unsigned i = 0; i < c->collections; ++i) if (!strcmp(c->collection_names[i], name)) index = (int)i; + if (index < 0) { + const char *kind = declared_kind(c, name); + if (kind) fail_at(c, line, column, "'%s' is a %s, not a collection", name, kind); + fail_at(c, line, column, "unknown collection '%s'", name); + } + if (c->message < 0 || !c->inputs[c->message].keyed) + fail_at(c, line, column, "%s[key] needs the handler of a message by key", name); + return (uint32_t)index; +} + +/* Emit an expression; height is the stack height before it. Returns its type. */ +static Type value(Compiler *c, const Expr *e, bool handler, int height) { if (height + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values"); switch (e->kind) { - case E_NUMBER: emit(c, e->line, e->column, CONST, 0, e->number); return; + case E_NUMBER: emit(c, e->line, e->column, CONST, 0, e->number); return NUM; case E_DT: if (!handler) fail_at(c, e->line, e->column, "dt is only available in handlers"); emit(c, e->line, e->column, DT, 0, 0); - return; - case E_STATE: { + return NUM; + case E_NAME: { int index = state_index(c, e->name); if (index < 0) { const char *kind = declared_kind(c, e->name); @@ -448,41 +610,110 @@ static void value(Compiler *c, const Expr *e, bool handler, int height) { fail_at(c, e->line, e->column, "unknown state '%s'", e->name); } emit(c, e->line, e->column, LOAD, (uint32_t)index, 0); - return; + return NUM; + } + case E_FIELD: { + int input = input_index(c, e->name); + if (input < 0 || c->inputs[input].kind != KIND_MESSAGE) { + const char *kind = declared_kind(c, e->name); + if (kind) fail_at(c, e->line, e->column, "'%s' is a %s and has no fields", e->name, kind); + fail_at(c, e->line, e->column, "unknown message '%s'", e->name); + } + if (input != c->message) + fail_at(c, e->line, e->column, "fields of '%s' are only available in its handler", e->name); + const Input *in = &c->inputs[input]; + for (unsigned i = 0; i < in->fields; ++i) + if (!strcmp(in->field_names[i], e->member)) { + emit(c, e->line, e->column, FIELD, i, 0); + return in->field_types[i] == FIELD_U32 ? NUM : BYTES; + } + fail_at(c, e->line, e->column, "message '%s' has no field '%s'", e->name, e->member); + } + case E_DOC: + emit(c, e->line, e->column, DOC, collection(c, e->name, e->line, e->column), 0); + return BYTES; + case E_SIZE: + emit(c, e->line, e->column, DOC_SIZE, collection(c, e->name, e->line, e->column), 0); + return NUM; + case E_LEN: { + Type inner = value(c, e->args[0], handler, height); + if (inner != BYTES) fail_at(c, e->line, e->column, ".bytes needs bytes, not %s", type_name(inner)); + emit(c, e->line, e->column, LEN, 0, 0); + return NUM; } default: - for (unsigned i = 0; i < e->count; ++i) value(c, e->args[i], handler, height + (int)i); + for (unsigned i = 0; i < e->count; ++i) { + Type operand = value(c, e->args[i], handler, height + (int)i); + if (operand != NUM) + fail_at(c, e->args[i]->line, e->args[i]->column, "expected a number, not %s", type_name(operand)); + } emit(c, e->line, e->column, (uint8_t)e->op, 0, 0); + return NUM; } } +static void expect_type(Compiler *c, const Expr *e, Type type, bool handler) { + Type actual = value(c, e, handler, 0 /* statement operands start at the top */); + if (actual != type) fail_at(c, e->line, e->column, "expected %s, not %s", type_name(type), type_name(actual)); +} + static void statements(Compiler *c, const Stmt *s) { for (; s; s = s->next) { - if (!s->branch) { + switch (s->kind) { + case S_ASSIGN: { int index = state_index(c, s->name); if (index < 0) { const char *kind = declared_kind(c, s->name); if (kind) fail_at(c, s->line, s->column, "cannot assign to %s '%s'", kind, s->name); fail_at(c, s->line, s->column, "unknown state '%s'", s->name); } - value(c, s->value, true, 0); + expect_type(c, s->value, NUM, true); emit(c, s->line, s->column, STORE, (uint32_t)index, 0); - continue; + break; + } + case S_REPLACE: { + uint32_t target = collection(c, s->name, s->line, s->column); + expect_type(c, s->first, NUM, true); + Type last = value(c, s->last, true, 1); + if (last != NUM) fail_at(c, s->last->line, s->last->column, "expected a number, not bytes"); + Type insert = value(c, s->value, true, 2); + if (insert != BYTES) fail_at(c, s->value->line, s->value->column, "replace inserts bytes, not a number"); + emit(c, s->line, s->column, REPLACE, target, 0); + break; + } + case S_REPLY: + if (c->message < 0) fail_at(c, s->line, s->column, "reply is only available in a message handler"); + expect_type(c, s->value, BYTES, true); + emit(c, s->line, s->column, REPLY, 0, 0); + break; + case S_REQUIRE: + expect_type(c, s->value, NUM, true); + emit(c, s->line, s->column, REQUIRE, 0, 0); + break; + case S_IF: { + expect_type(c, s->value, NUM, true); + unsigned jump = c->code_count; + emit(c, s->line, s->column, JZ, 0, 0); + statements(c, s->then); + if (s->otherwise) { + unsigned skip = c->code_count; + emit(c, s->line, s->column, JMP, 0, 0); + c->code[jump].arg = c->code_count; + statements(c, s->otherwise); + c->code[skip].arg = c->code_count; + } else c->code[jump].arg = c->code_count; + break; + } } - value(c, s->value, true, 0); - unsigned jump = c->code_count; - emit(c, s->line, s->column, JZ, 0, 0); - statements(c, s->then); - if (s->otherwise) { - unsigned skip = c->code_count; - emit(c, s->line, s->column, JMP, 0, 0); - c->code[jump].arg = c->code_count; - statements(c, s->otherwise); - c->code[skip].arg = c->code_count; - } else c->code[jump].arg = c->code_count; } } +static unsigned char *put16(unsigned char *p, uint32_t v) { + p[0] = (unsigned char)v; + p[1] = (unsigned char)(v >> 8); + return p + 2; +} + static unsigned char *put32(unsigned char *p, uint32_t v) { for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(v >> (8 * i)); return p + 4; @@ -509,49 +740,88 @@ static uint32_t intern(unsigned char *strings, size_t *used, const char *text) { static void generate(Compiler *c, unsigned char **module, size_t *size) { uint32_t events[HANDLERS]; + int handler_input[HANDLERS]; for (unsigned i = 0; i < c->handler_count; ++i) { const char *name = c->handlers[i].input; + int line = c->handlers[i].line, column = c->handlers[i].column; events[i] = 0; /* frame */ + handler_input[i] = -1; if (!*name) continue; - int input = -1; - for (unsigned k = 0; k < c->inputs; ++k) if (!strcmp(c->input_names[k], name)) input = (int)k; + int input = input_index(c, name); if (input < 0) { const char *kind = declared_kind(c, name); - if (kind) fail_at(c, c->handlers[i].line, c->handlers[i].column, "'%s' is a %s, not an input", name, kind); - fail_at(c, c->handlers[i].line, c->handlers[i].column, "unknown input '%s'", name); + if (kind) fail_at(c, line, column, "'%s' is a %s, not an input", name, kind); + fail_at(c, line, column, "unknown input '%s'", name); } - events[i] = 1u + 2u * (uint32_t)input + (uint32_t)c->handlers[i].edge; + if (c->inputs[input].kind == KIND_MESSAGE && c->handlers[i].edge >= 0) + fail_at(c, line, column, "'%s' is a message; write on %s { }", name, name); + if (c->inputs[input].kind == KIND_KEY && c->handlers[i].edge < 0) + fail_at(c, line, column, "'%s' is a key input; write on %s.down or on %s.up", name, name, name); + handler_input[i] = input; + events[i] = 1u + 2u * (uint32_t)input + (c->handlers[i].edge > 0); } /* Views, then handlers, each as one contiguous block. */ + c->message = -1; for (unsigned i = 0; i < c->views; ++i) { c->view_code[i][0] = c->code_count; - value(c, c->view_value[i], false, 0); + Type type = value(c, c->view_value[i], false, 0); + if (type != NUM) fail_at(c, c->view_value[i]->line, c->view_value[i]->column, "a view is a number"); c->view_code[i][1] = c->code_count - c->view_code[i][0]; } for (unsigned i = 0; i < c->handler_count; ++i) { + int input = handler_input[i]; + c->message = input >= 0 && c->inputs[input].kind == KIND_MESSAGE ? input : -1; c->handler_code[i][0] = c->code_count; statements(c, c->handlers[i].body); c->handler_code[i][1] = c->code_count - c->handler_code[i][0]; } - static unsigned char strings[STATES * NAME + 2 * INPUTS * NAME + VIEWS * NAME]; + static unsigned char strings[(STATES + COLLECTIONS + 2 * INPUTS + FIELDS + VIEWS) * NAME]; size_t used = 0; - uint32_t state_name[STATES], input_name[INPUTS], input_key[INPUTS], view_name[VIEWS]; + uint32_t state_name[STATES] = {0}, collection_name[COLLECTIONS] = {0}, input_name[INPUTS] = {0}, input_key[INPUTS] = {0}; + uint32_t field_name[FIELDS] = {0}, view_name[VIEWS] = {0}; + unsigned fields = 0; for (unsigned i = 0; i < c->states; ++i) state_name[i] = intern(strings, &used, c->state_names[i]); - for (unsigned i = 0; i < c->inputs; ++i) { - input_name[i] = intern(strings, &used, c->input_names[i]); - input_key[i] = intern(strings, &used, c->input_keys[i]); + for (unsigned i = 0; i < c->collections; ++i) collection_name[i] = intern(strings, &used, c->collection_names[i]); + for (unsigned i = 0; i < c->input_count; ++i) { + input_name[i] = intern(strings, &used, c->inputs[i].name); + input_key[i] = c->inputs[i].kind == KIND_KEY ? intern(strings, &used, c->inputs[i].key) : 0; + for (unsigned f = 0; f < c->inputs[i].fields; ++f) + field_name[fields++] = intern(strings, &used, c->inputs[i].field_names[f]); } for (unsigned i = 0; i < c->views; ++i) view_name[i] = intern(strings, &used, c->view_names[i]); - *size = 32 + 16u * c->states + 12u * (c->inputs + c->views + c->handler_count) + 16u * c->code_count + used; + *size = 32 + 16u * c->states + 12u * c->collections + 16u * c->input_count + 8u * fields + + 12u * (c->views + c->handler_count) + 16u * c->code_count + used; unsigned char *p = *module = allocate(c, *size); - memcpy(p, "PUREMOD1", 8); - p[8] = (unsigned char)c->states; p[10] = (unsigned char)c->inputs; - p[12] = (unsigned char)c->views; p[14] = (unsigned char)c->handler_count; + memcpy(p, "PUREMOD2", 8); + put16(p + 8, c->states); + put16(p + 10, c->input_count); + put16(p + 12, c->views); + put16(p + 14, c->handler_count); put32(p + 16, c->code_count); put32(p + 20, (uint32_t)used); + put16(p + 24, fields); + put16(p + 26, c->collections); p += 32; for (unsigned i = 0; i < c->states; ++i) { p = put32(p, state_name[i]); p = put32(p, 0); p = put_double(p, c->initial[i]); } - for (unsigned i = 0; i < c->inputs; ++i) { p = put32(p, input_name[i]); p = put32(p, 1); p = put32(p, input_key[i]); } + for (unsigned i = 0; i < c->collections; ++i) { p = put32(p, collection_name[i]); p = put32(p, 1); p = put32(p, 1); } + unsigned first = 0; + for (unsigned i = 0; i < c->input_count; ++i) { + const Input *in = &c->inputs[i]; + p = put32(p, input_name[i]); + *p++ = (unsigned char)in->kind; + *p++ = in->keyed; + p = put16(p, 0); + p = put32(p, input_key[i]); + p = put16(p, in->kind == KIND_MESSAGE ? first : 0); + p = put16(p, in->fields); + first += in->fields; + } + fields = 0; + for (unsigned i = 0; i < c->input_count; ++i) + for (unsigned f = 0; f < c->inputs[i].fields; ++f) { + p = put32(p, field_name[fields++]); + p = put32(p, (uint32_t)c->inputs[i].field_types[f]); + } for (unsigned i = 0; i < c->views; ++i) { p = put32(p, view_name[i]); p = put32(p, c->view_code[i][0]); p = put32(p, c->view_code[i][1]); } for (unsigned i = 0; i < c->handler_count; ++i) { p = put32(p, events[i]); |
