diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/compile.c | 115 |
1 files changed, 104 insertions, 11 deletions
diff --git a/src/compile.c b/src/compile.c index 8a28384..9e90d26 100644 --- a/src/compile.c +++ b/src/compile.c @@ -17,7 +17,7 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR CUR_LOAD, CUR_START, CUR_END, CUR_MOVE, CUR_STORE, TEXT_INSERT, TEXT_REMOVE, TEXT_LINE, TEXT_SET, TEXT_FROM, BYTES_FROM, CUR_COUNT, LIST_COUNT, LIST_APPEND, LIST_INSERT, LIST_REMOVE, ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW, - PICK, SLIDE, FOLD }; + PICK, SLIDE, FOLD, TEXT_COMPARE, LIST_FIND, LIST_HAS }; enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST }; enum { KIND_KEY = 1, KIND_MESSAGE = 2, KIND_TEXT = 3, FIELD_U32 = 1, FIELD_BYTES = 2 }; enum { STATE_NUM = 0, STATE_TEXT = 1, STATE_CURSOR = 2, STATE_LIST = 3, STATE_INDEX = 4, UNIT_CHAR = 0, UNIT_LINE = 1 }; @@ -60,7 +60,7 @@ typedef struct Expr { size_t size; } Expr; -typedef enum { S_ASSIGN, S_IF, S_REPLACE, S_REPLY, S_REQUIRE, S_INSERT, S_REMOVE, S_APPEND, S_SET } StmtKind; +typedef enum { S_ASSIGN, S_IF, S_REPLACE, S_REPLY, S_REQUIRE, S_INSERT, S_REMOVE, S_APPEND, S_ADD, S_SET } StmtKind; typedef struct Stmt { StmtKind kind; int line, column; @@ -122,6 +122,9 @@ typedef struct { char state_names[STATES][NAME], state_target[STATES][NAME]; int state_types[STATES], state_line[STATES], state_column[STATES]; bool state_stays[STATES]; /* a mark: a cursor that stays before text inserted at it */ + /* A keyed list: the field named after by, and once resolved, that field + 1. */ + char state_key[STATES][NAME]; + int state_key_line[STATES], state_key_column[STATES], list_key[STATES]; double initial[STATES]; Expr *state_text[STATES]; unsigned states; @@ -872,12 +875,12 @@ static Stmt *statement(Compiler *c, unsigned depth) { s->last = expression(c, 0, true); newlines(c); } - } else if (!strcmp(method.text, "append")) { - s->kind = S_APPEND; + } else if (!strcmp(method.text, "append") || !strcmp(method.text, "add")) { + s->kind = method.text[1] == 'p' ? S_APPEND : S_ADD; s->value = inserted(c); newlines(c); } else fail_at(c, method.line, method.column, - "a text supports insert and remove; a list supports append, insert and remove"); + "a text supports insert and remove; a list supports append, insert and remove (add when keyed)"); expect(c, ")", "')'"); return s; } @@ -1088,6 +1091,13 @@ static void state_declaration(Compiler *c) { expect_word(c, "of"); Token target = expect_name(c, type.text[0] == 'l' ? "a record" : "a list state"); memcpy(c->state_target[index], target.text, NAME); + if (type.text[0] == 'l' && word(c, "by")) { /* keyed: items in key order, each key once */ + take(c); + Token key = expect_name(c, "a field of the record"); + memcpy(c->state_key[index], key.text, NAME); + c->state_key_line[index] = key.line; + c->state_key_column[index] = key.column; + } } else fail_at(c, type.line, type.column, "state types are numbers, text, cursor or mark in a text, list of a record, index of a list, " "and bytes by key"); @@ -1409,6 +1419,31 @@ static void index_of(Compiler *c, const Expr *e, bool handler, int height, int l c->state_names[list]); } +/* The item name[e] names: e is an index of the list, or for a keyed list a + * key, found by LIST_FIND. */ +static void item_of(Compiler *c, const Expr *e, bool handler, int height, int list) { + Kind kind = value(c, e, handler, height); + int key = c->list_key[list] - 1; + if (kind.type != INDEX && key >= 0) { + const Record *r = record_of(c, list); + Type type = r->field_text[key] ? TEXT : NUM; + if (kind.type != type) + fail_at(c, e->line, e->column, "'%s' is keyed by %s, %s: expected %s or an index, not %s", c->state_names[list], + r->field_names[key], type_name(type), type_name(type), type_name(kind.type)); + emit(c, e->line, e->column, LIST_FIND, (uint32_t)list, 0); + return; + } + expect_kind(c, e, kind, INDEX); + if (kind.owner != list) + fail_at(c, e->line, e->column, "an index of '%s' cannot point into '%s'", c->state_names[kind.owner], + c->state_names[list]); +} + +/* The key field of a keyed list, or NULL. */ +static const char *keyed_by(const Compiler *c, int list) { + return c->list_key[list] ? c->state_key[list] : NULL; +} + /* A fn inlined: its arguments go on the stack once, the body reads them with * PICK, and SLIDE leaves only the result. e names the call (E_FN), or is the * name of a fn without parameters. */ @@ -1580,6 +1615,22 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { if (!*e->member) return inline_fn(c, e, c->module, e->name, handler, height); int library = parameter(c, e->member) < 0 ? used_library(c, c->module, e->member) : -1; if (library >= 0) return inline_fn(c, e, library, e->name, handler, height); + int list = state_index(c, e->member); + if (!strcmp(e->name, "has") && list >= 0 && c->state_types[list] == STATE_LIST) { + /* people.has("bob"): whether a keyed list holds that key. */ + app_name(c, e, e->member); + const char *key = keyed_by(c, list); + if (!key) fail_at(c, e->line, e->column, "'%s' has no key; declare it as list of %s by a field to ask has", + e->member, record_of(c, list)->name); + if (e->count != 1) fail_at(c, e->line, e->column, "%s.has takes one key", e->member); + Type type = record_of(c, list)->field_text[c->list_key[list] - 1] ? TEXT : NUM; + Kind given = value(c, e->args[0], handler, height); + if (given.type != type) + fail_at(c, e->args[0]->line, e->args[0]->column, "'%s' is keyed by %s, %s: expected %s, not %s", e->member, key, + type_name(type), type_name(type), type_name(given.type)); + emit(c, e->line, e->column, LIST_HAS, (uint32_t)list, 0); + return (Kind){NUM, -1}; + } if (strcmp(e->name, "line")) fail_at(c, e->line, e->column, "unknown library '%s'; write use %s first", e->member, e->member); /* note.line(at): the line a cursor is on. */ app_name(c, e, e->member); @@ -1610,9 +1661,18 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { app_name(c, e, e->name); int list = list_state(c, e->name, e->line, e->column, e->args[0]); const Record *r = record_of(c, list); - if (!*e->member) fail_at(c, e->line, e->column, "read a field of the item, as in %s[index].%s", e->name, r->field_names[0]); + if (!*e->member) { + /* people["bob"]: the index of the item with that key. */ + if (!keyed_by(c, list)) + fail_at(c, e->line, e->column, "read a field of the item, as in %s[index].%s", e->name, r->field_names[0]); + item_of(c, e->args[0], handler, height, list); + if (c->code[c->code_count - 1].op != LIST_FIND) + fail_at(c, e->line, e->column, "%s[index] is the index itself; read a field, as in %s[index].%s", e->name, + e->name, r->field_names[0]); + return (Kind){INDEX, list}; + } unsigned field = record_field(c, r, e->member, e->line, e->column); - index_of(c, e->args[0], handler, height, list); + item_of(c, e->args[0], handler, height, list); emit(c, e->line, e->column, ITEM_GET, (uint32_t)list | field << 16, 0); return (Kind){r->field_text[field] ? TEXT : NUM, -1}; } @@ -1699,6 +1759,19 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { else emit(c, e->line, e->column, INDEX_MOVE, 0, 0); return left; } + if (e->op >= LT && e->op <= NE) { + /* Texts compare by their bytes; numbers as numbers. */ + Kind left = value(c, e->args[0], handler, height); + if (left.type == TEXT) { + expect_kind(c, e->args[1], value(c, e->args[1], handler, height + 1), TEXT); + emit(c, e->line, e->column, TEXT_COMPARE, (uint32_t)(e->op - LT), 0); + return (Kind){NUM, -1}; + } + expect_kind(c, e->args[0], left, NUM); + number(c, e->args[1], handler, height + 1); + emit(c, e->line, e->column, (uint8_t)e->op, 0, 0); + return (Kind){NUM, -1}; + } /* fall through */ default: for (unsigned i = 0; i < e->count; ++i) number(c, e->args[i], handler, height + (int)i); @@ -1768,7 +1841,11 @@ static void statements(Compiler *c, const Stmt *s) { int list = state_index(c, s->name); if (list >= 0 && c->state_types[list] == STATE_LIST) { if (s->last) fail_at(c, s->last->line, s->last->column, "a list removes one item, as in %s.remove(index)", s->name); - index_of(c, s->first, true, 0, list); + if (s->kind == S_INSERT && keyed_by(c, list)) + fail_at(c, s->line, s->column, "'%s' is keyed by %s: %s.add(%s { ... }) places an item by its key", s->name, + keyed_by(c, list), s->name, record_of(c, list)->name); + if (s->kind == S_REMOVE) item_of(c, s->first, true, 0, list); + else index_of(c, s->first, true, 0, list); if (s->kind == S_INSERT) record_fields(c, s->value, list, 1); emit(c, s->line, s->column, s->kind == S_INSERT ? LIST_INSERT : LIST_REMOVE, (uint32_t)list, 0); break; @@ -1785,8 +1862,16 @@ static void statements(Compiler *c, const Stmt *s) { emit(c, s->line, s->column, TEXT_REMOVE, (uint32_t)text, 0); break; } - case S_APPEND: { + case S_APPEND: case S_ADD: { + /* A keyed list places an added item by its key (it fails if the key is there); others append. */ int list = list_state(c, s->name, s->line, s->column, NULL); + const char *key = keyed_by(c, list), *record = record_of(c, list)->name; + if (s->kind == S_APPEND && key) + fail_at(c, s->line, s->column, "'%s' is keyed by %s: %s.add(%s { ... }) places an item by its key", s->name, + key, s->name, record); + if (s->kind == S_ADD && !key) + fail_at(c, s->line, s->column, "'%s' has no key: append or insert places its items (add needs list of %s by a field)", + s->name, record); record_fields(c, s->value, list, 0); emit(c, s->line, s->column, LIST_APPEND, (uint32_t)list, 0); break; @@ -1800,7 +1885,9 @@ static void statements(Compiler *c, const Stmt *s) { } const Record *r = record_of(c, list); unsigned field = record_field(c, r, s->member, s->line, s->column); - if (*s->name) index_of(c, s->first, true, 0, list); + if (c->list_key[list] == (int)field + 1) + fail_at(c, s->line, s->column, "the key of '%s' does not change; remove the item and add it again", c->state_names[list]); + if (*s->name) item_of(c, s->first, true, 0, list); expect_kind(c, s->value, value(c, s->value, true, 1), r->field_text[field] ? TEXT : NUM); emit(c, s->line, s->column, ITEM_SET, (uint32_t)list | field << 16, 0); break; @@ -1890,6 +1977,9 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { if (record < 0) fail_at(c, c->state_line[i], c->state_column[i], "a list holds a record; '%s' is not one", c->state_target[i]); state_ref[i] = (uint32_t)record; + if (*c->state_key[i]) + c->list_key[i] = 1 + (int)record_field(c, &c->records[record], c->state_key[i], c->state_key_line[i], + c->state_key_column[i]); } } for (unsigned i = 0; i < c->record_count; ++i) { @@ -2001,7 +2091,10 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { p = put32(p, state_name[i]); p = put32(p, (uint32_t)c->state_types[i]); if (c->state_types[i] == STATE_NUM) p = put_double(p, c->initial[i]); - else { p = put32(p, state_ref[i]); p = put32(p, c->state_stays[i] ? 1 : 0); } /* 1: the cursor stays */ + else { /* flags: a mark (1: the cursor stays), or a keyed list's key field + 1 */ + p = put32(p, state_ref[i]); + p = put32(p, c->state_types[i] == STATE_LIST ? (uint32_t)c->list_key[i] : c->state_stays[i] ? 1 : 0); + } } 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; |
