diff options
| author | eunhhu <[email protected]> | 2026-10-04 14:43:11 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-04 14:43:11 +0100 |
| commit | da7361aae4e896edaad37dbcf404c5005a7f8596 (patch) | |
| tree | b01bde38a23df0f92093bd19dfc9ec9e39424b62 | |
| parent | 7340ba9af757b6f135dff9c51a193ba527f061b3 (diff) | |
| download | aup-da7361aae4e896edaad37dbcf404c5005a7f8596.tar.gz aup-da7361aae4e896edaad37dbcf404c5005a7f8596.zip | |
Give a view a body that computes and returns its value (Pure 018)
| -rw-r--r-- | compiler/compile.c | 127 | ||||
| -rw-r--r-- | docs/design/001-language-v0.md | 4 | ||||
| -rw-r--r-- | docs/design/002-dsl-direction.md | 40 | ||||
| -rw-r--r-- | examples/routes.aup | 44 | ||||
| -rw-r--r-- | tests/check.py | 21 |
5 files changed, 193 insertions, 43 deletions
diff --git a/compiler/compile.c b/compiler/compile.c index 25a761b..69bfdb5 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -80,7 +80,7 @@ typedef struct Expr { } Expr; typedef enum { S_ASSIGN, S_IF, S_REPLACE, S_REPLY, S_REQUIRE, S_INSERT, S_REMOVE, S_APPEND, S_ADD, S_SET, S_SEND, S_FOR, - S_LET, S_COUNT, S_BREAK } StmtKind; + S_LET, S_COUNT, S_BREAK, S_RETURN } StmtKind; typedef struct Stmt { StmtKind kind; int line, column; @@ -180,6 +180,7 @@ typedef struct { /* Views in order; a table (no value) is followed by its columns. */ char view_names[VIEWS][NAME], view_row[VIEWS][NAME], view_list[VIEWS][NAME]; Expr *view_value[VIEWS]; + Stmt *view_body[VIEWS]; /* a view that computes: its statements, return last (view_value) */ Expr *view_filter[VIEWS]; /* a table's where condition, or NULL */ unsigned char view_byte[VIEWS]; /* a filtered table's list + 1 */ int view_table[VIEWS]; /* a column's table + 1, or 0 */ @@ -207,6 +208,9 @@ typedef struct { struct { char name[NAME]; int list, item; Scope *scope; } loops[NESTING]; unsigned loop_count; unsigned nested; /* blocks within the handler's own (an if's, a for's) */ + bool handling; /* statements of a handler (else of a view's body: they change no state) */ + Kind returned; /* the value a view's body returned, once it has */ + bool has_returned; /* Libraries: module 0 is the main source; each module lists the libraries it uses. */ struct { char name[NAME], path[AUP_PATH]; bool uses[MODULES], parsing; } modules[MODULES]; unsigned module_count; @@ -232,7 +236,7 @@ static const char *const reserved[] = {"state", "input", "view", "on", "if", "el "dt", "min", "max", "clamp", "message", "by", "bytes", "u32", "reply", "require", "record", "each", "fn", "use", "now", "every", "floor", "output", "send", "for", "let", "true", "false", - "transient", "break"}; + "transient", "break", "return"}; /* An error in the module being parsed or inlined; inside a fn it names the call. */ static _Noreturn void fail_at(Compiler *c, int line, int column, const char *format, ...) { @@ -1026,6 +1030,11 @@ static Stmt *statement(Compiler *c, unsigned depth) { s->kind = S_BREAK; return s; } + if (!strcmp(token.text, "return")) { /* a view's body's last line: its value */ + s->kind = S_RETURN; + s->value = expression(c, 0, false); + return s; + } if (!strcmp(token.text, "for")) { /* for item in list [where condition] { ... }: the block runs once for every item the list has */ s->kind = S_FOR; @@ -1807,10 +1816,19 @@ static void declaration(Compiler *c) { if (c->views == VIEWS) fail_at(c, name.line, name.column, "at most 64 views are allowed"); unsigned index = c->views++; if (is(c, "(")) view_arguments(c, index, &name); - expect(c, "=", "'='"); memcpy(c->view_names[index], name.text, NAME); - if (!word(c, "each")) c->view_value[index] = expression(c, 0, false); - else table_declaration(c, index); + if (is(c, "{")) { /* view name(arguments) { statements ... return value }: a view that computes */ + Stmt *body = block(c, 0), *last = body; + while (last && last->next) last = last->next; + if (!last || last->kind != S_RETURN) + fail_at(c, name.line, name.column, "a view's body ends with return and its value, as in return total"); + c->view_body[index] = body; + c->view_value[index] = last->value; + } else { + expect(c, "=", "'='"); + if (!word(c, "each")) c->view_value[index] = expression(c, 0, false); + else table_declaration(c, index); + } } 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"); @@ -2635,7 +2653,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { /* Bytes, or text sent as its UTF-8 bytes. */ static void bytes_value(Compiler *c, const Expr *e, int height, const char *what) { - Kind kind = value(c, e, true, height); + Kind kind = value(c, e, c->handling, height); if (kind.type == TEXT) emit(c, e->line, e->column, BYTES_FROM, 0, 0); else if (kind.type != BYTES) fail_at(c, e->line, e->column, "%s takes bytes or text, not %s", what, type_name(kind.type)); } @@ -2737,7 +2755,7 @@ static void record_fields(Compiler *c, const Expr *e, int list, int height) { record_values(c, e, list, values); for (unsigned k = 0; k < r->fields; ++k) { int at = height + (int)k; - if (values[k]) expect_kind(c, values[k], wanted(c, values[k], true, at, field_kind(r, k)), field_kind(r, k)); + if (values[k]) expect_kind(c, values[k], wanted(c, values[k], c->handling, at, field_kind(r, k)), field_kind(r, k)); else if (at + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values"); else if (r->field_kinds[k] == TEXT) emit(c, e->line, e->column, TEXT_CONST, r->field_constant[k], 0); else if (r->field_kinds[k] == I64) emit_integer(c, e->line, e->column, r->field_integer[k]); @@ -2774,6 +2792,12 @@ static void send_values(Compiler *c, const Input *out, const Expr *group, const } } +/* A view's body changes only its lets and its own lists (a let's list): its states are a snapshot's. */ +static void view_changes(Compiler *c, const Stmt *s, int list, const char *name) { + if (!c->handling && (list < 0 || !c->state_event[list])) + fail_at(c, s->line, s->column, "a view's body changes only its lets and its own lists, not '%s'", name); +} + static void statements(Compiler *c, const Stmt *s) { unsigned scope = c->local_count; /* a block's lets end with it */ for (; s; s = s->next) { @@ -2800,7 +2824,7 @@ static void statements(Compiler *c, const Stmt *s) { break; } if (c->local_slots == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); - Kind held = value(c, s->value, true, 0); + Kind held = value(c, s->value, c->handling, 0); if (held.type != NUM && held.type != BOOL && held.type != I64 && held.type != INDEX) fail_at(c, s->value->line, s->value->column, "a let holds a number, a bool, an i64 or an index, not %s", type_name(held.type)); @@ -2818,7 +2842,7 @@ static void statements(Compiler *c, const Stmt *s) { if (local >= 0 && c->locals[local].counter) fail_at(c, s->line, s->column, "'%s' is what this for counts; it is not assigned", s->name); if (local >= 0) { /* a let takes a new value of its kind */ - Kind kind = wanted(c, s->value, true, 0, c->locals[local].kind.type); + Kind kind = wanted(c, s->value, c->handling, 0, c->locals[local].kind.type); if (kind.type != c->locals[local].kind.type || (kind.type == INDEX && kind.owner != c->locals[local].kind.owner)) fail_at(c, s->value->line, s->value->column, "'%s' holds %s", s->name, c->locals[local].kind.type == INDEX ? "an index of its list" : type_name(c->locals[local].kind.type)); @@ -2831,29 +2855,31 @@ static void statements(Compiler *c, const Stmt *s) { 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); } + view_changes(c, s, -1, s->name); if (c->state_types[index] == STATE_I64) { - integer(c, s->value, true, 0); + integer(c, s->value, c->handling, 0); emit(c, s->line, s->column, STORE, (uint32_t)index, 0); } else if (c->state_types[index] == STATE_NUM || c->state_types[index] == STATE_BOOL) { - expect_kind(c, s->value, value(c, s->value, true, 0), c->state_types[index] == STATE_BOOL ? BOOL : NUM); + expect_kind(c, s->value, value(c, s->value, c->handling, 0), c->state_types[index] == STATE_BOOL ? BOOL : NUM); emit(c, s->line, s->column, STORE, (uint32_t)index, 0); } else if (c->state_types[index] == STATE_LIST) { fail_at(c, s->line, s->column, "a list changes by append, insert, remove and %s[index].field = value", s->name); } else if (c->state_types[index] == STATE_INDEX) { - index_of(c, s->value, true, 0, cursor_owner(c, index)); + index_of(c, s->value, c->handling, 0, cursor_owner(c, index)); emit(c, s->line, s->column, INDEX_STORE, (uint32_t)index, 0); } else if (c->state_types[index] == STATE_TEXT) { - expect_kind(c, s->value, value(c, s->value, true, 0), TEXT); + expect_kind(c, s->value, value(c, s->value, c->handling, 0), TEXT); emit(c, s->line, s->column, TEXT_SET, (uint32_t)index, 0); } else { int owner = cursor_owner(c, index); - cursor_of(c, s->value, true, 0, owner, c->state_names[owner]); + cursor_of(c, s->value, c->handling, 0, owner, c->state_names[owner]); emit(c, s->line, s->column, CUR_STORE, (uint32_t)index, 0); } break; } case S_INSERT: case S_REMOVE: { int list = state_index(c, s->name); + view_changes(c, s, list >= 0 && c->state_types[list] == STATE_LIST ? list : -1, 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); if (s->kind == S_INSERT && walked(c, list)) @@ -2863,27 +2889,28 @@ static void statements(Compiler *c, const Stmt *s) { 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_shown(c, c->module, record_of(c, list))); - 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_REMOVE) item_of(c, s->first, c->handling, 0, list); + else index_of(c, s->first, c->handling, 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; } int text = text_state(c, s->name, s->line, s->column); - cursor_of(c, s->first, true, 0, text, s->name); + cursor_of(c, s->first, c->handling, 0, text, s->name); if (s->kind == S_INSERT) { - expect_kind(c, s->value, value(c, s->value, true, 1), TEXT); + expect_kind(c, s->value, value(c, s->value, c->handling, 1), TEXT); emit(c, s->line, s->column, TEXT_INSERT, (uint32_t)text, 0); break; } if (!s->last) fail_at(c, s->line, s->column, "a text removes a range, as in %s.remove(first .. last)", s->name); - cursor_of(c, s->last, true, 1, text, s->name); + cursor_of(c, s->last, c->handling, 1, text, s->name); emit(c, s->line, s->column, TEXT_REMOVE, (uint32_t)text, 0); break; } 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); + view_changes(c, s, list, s->name); if (walked(c, list)) fail_at(c, s->line, s->column, "a for does not add to the list it walks; add to another list, or after the for"); const char *key = keyed_by(c, list), *record = record_shown(c, c->module, record_of(c, list)); @@ -2900,6 +2927,7 @@ static void statements(Compiler *c, const Stmt *s) { case S_SET: { int list = *s->name ? list_state(c, s->name, s->line, s->column, s->first) : -1; if (!s->member[0]) { /* tasks[at] = Task { ... }: each field set, the key the same */ + view_changes(c, s, list, s->name); const Record *r = record_of(c, list); const Expr *values[FIELDS_PER_RECORD]; record_values(c, s->value, list, values); @@ -2917,40 +2945,43 @@ static void statements(Compiler *c, const Stmt *s) { same->args[0] = item; same->args[1] = (Expr *)values[key]; same->count = 2; - truth(c, same, true, 0, "require"); + truth(c, same, c->handling, 0, "require"); emit(c, s->line, s->column, REQUIRE, 0, 0); } for (unsigned k = 0; k < r->fields; ++k) { if ((int)k == key) continue; - item_of(c, s->first, true, 0, list); - expect_kind(c, values[k], wanted(c, values[k], true, 1, field_kind(r, k)), field_kind(r, k)); + item_of(c, s->first, c->handling, 0, list); + expect_kind(c, values[k], wanted(c, values[k], c->handling, 1, field_kind(r, k)), field_kind(r, k)); emit(c, s->line, s->column, ITEM_SET, (uint32_t)list | k << 16, 0); } break; } if (list < 0) { - Kind index = value(c, s->first, true, 0); + Kind index = value(c, s->first, c->handling, 0); expect_kind(c, s->first, index, INDEX); list = index.owner; } + view_changes(c, s, list, c->state_names[list]); const Record *r = record_of(c, list); unsigned field = record_field(c, r, s->member, s->line, s->column); 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, wanted(c, s->value, true, 1, field_kind(r, field)), field_kind(r, field)); + if (*s->name) item_of(c, s->first, c->handling, 0, list); + expect_kind(c, s->value, wanted(c, s->value, c->handling, 1, field_kind(r, field)), field_kind(r, field)); emit(c, s->line, s->column, ITEM_SET, (uint32_t)list | field << 16, 0); break; } case S_REPLACE: { + view_changes(c, s, -1, s->name); uint32_t target = collection(c, s->name, s->line, s->column); - number(c, s->first, true, 0); - number(c, s->last, true, 1); + number(c, s->first, c->handling, 0); + number(c, s->last, c->handling, 1); bytes_value(c, s->value, 2, "replace"); emit(c, s->line, s->column, REPLACE, target, 0); break; } case S_SEND: { + if (!c->handling) fail_at(c, s->line, s->column, "a view sends nothing; a handler sends"); int output = input_index(c, s->name); if (output < 0 || c->inputs[output].kind != KIND_OUTPUT) { const char *kind = declared_kind(c, s->name); @@ -2968,7 +2999,7 @@ static void statements(Compiler *c, const Stmt *s) { if (!given[f]) fail_at(c, s->line, s->column, "send %s needs '%s'", s->name, out->field_names[f]); int type = out->field_types[f]; if (type == FIELD_BYTES) bytes_value(c, given[f], (int)f, "a bytes field"); - else expect_kind(c, given[f], wanted(c, given[f], true, (int)f, message_kind(type)), message_kind(type)); + else expect_kind(c, given[f], wanted(c, given[f], c->handling, (int)f, message_kind(type)), message_kind(type)); } emit(c, s->line, s->column, SEND, (uint32_t)output, 0); break; @@ -2981,7 +3012,8 @@ static void statements(Compiler *c, const Stmt *s) { emit(c, s->line, s->column, REPLY, 0, 0); break; case S_REQUIRE: - truth(c, s->value, true, 0, "require"); + if (!c->handling) fail_at(c, s->line, s->column, "a view requires nothing; its value says what it found"); + truth(c, s->value, c->handling, 0, "require"); emit(c, s->line, s->column, REQUIRE, 0, 0); break; case S_FOR: { @@ -2997,8 +3029,8 @@ static void statements(Compiler *c, const Stmt *s) { if (key != NUM && key != I64) fail_at(c, s->first->line, s->first->column, "'%s' is not keyed by a number or an i64: a slice takes one", s->member); - expect_kind(c, s->low, wanted(c, s->low, true, 0, key), key); - expect_kind(c, s->high, wanted(c, s->high, true, 1, key), key); + expect_kind(c, s->low, wanted(c, s->low, c->handling, 0, key), key); + expect_kind(c, s->high, wanted(c, s->high, c->handling, 1, key), key); } unsigned at = c->code_count, jump = 0, loop = c->loop_count++; emit(c, s->line, s->column, EACH, 0, 0); @@ -3007,7 +3039,7 @@ static void statements(Compiler *c, const Stmt *s) { c->loops[loop].item = -1; c->loops[loop].scope = c->scope; if (s->value) { - truth(c, s->value, true, 0, "where"); + truth(c, s->value, c->handling, 0, "where"); jump = c->code_count; emit(c, s->line, s->column, JZ, 0, 0); } @@ -3034,7 +3066,7 @@ static void statements(Compiler *c, const Stmt *s) { break; } case S_IF: { - truth(c, s->value, true, 0, "if"); + truth(c, s->value, c->handling, 0, "if"); unsigned jump = c->code_count; emit(c, s->line, s->column, JZ, 0, 0); ++c->nested; @@ -3059,10 +3091,10 @@ static void statements(Compiler *c, const Stmt *s) { fail_at(c, s->line, s->column, "'%s' is already a let or a for's name here", s->name); if (c->local_slots == LOCALS || c->local_count == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); - bool integer = probe(c, s->low, true, 0) == I64 || probe(c, s->high, true, 0) == I64; + bool integer = probe(c, s->low, c->handling, 0) == I64 || probe(c, s->high, c->handling, 0) == I64; Type type = integer ? I64 : NUM; - expect_kind(c, s->low, wanted(c, s->low, true, 0, type), type); - expect_kind(c, s->high, wanted(c, s->high, true, 1, type), type); + expect_kind(c, s->low, wanted(c, s->low, c->handling, 0, type), type); + expect_kind(c, s->high, wanted(c, s->high, c->handling, 1, type), type); unsigned scope = c->local_count, local = c->local_count++, loop = c->loop_count++, at = c->code_count; memcpy(c->locals[local].name, s->name, NAME); c->locals[local].slot = c->local_slots++; @@ -3086,6 +3118,12 @@ static void statements(Compiler *c, const Stmt *s) { if (!c->loop_count) fail_at(c, s->line, s->column, "break ends a for; this one is outside any"); emit(c, s->line, s->column, BREAK, 0, 0); break; + case S_RETURN: /* a view's body's value, its last line */ + if (c->handling) fail_at(c, s->line, s->column, "return gives a view's body its value; a handler returns nothing"); + if (s->next || c->nested) fail_at(c, s->line, s->column, "return is the last line of a view's body"); + c->returned = value(c, s->value, false, 0); + c->has_returned = true; + break; } } c->local_count = scope; @@ -3133,8 +3171,8 @@ static uint32_t intern(unsigned char *strings, size_t *used, const char *text) { * blocks, which hold one record keyed one way; no two handlers of one event * have one, so each begins empty. */ static void event_lists(Compiler *c) { - for (unsigned h = 0; h < c->handler_count; ++h) - for (const Stmt *s = c->handlers[h].body; s; s = s->next) { + for (unsigned h = 0; h < c->handler_count + c->views; ++h) /* the handlers', then the views' bodies' */ + for (const Stmt *s = h < c->handler_count ? c->handlers[h].body : c->view_body[h - c->handler_count]; s; s = s->next) { if (s->kind != S_LET || !s->listed) continue; char path[2 * NAME]; snprintf(path, sizeof(path), "let.%s", s->name); @@ -3148,7 +3186,7 @@ static void event_lists(Compiler *c) { strcmp(c->state_key[found], s->key)) fail_at(c, s->line, s->column, "'%s' is a let's list of another record or key in another handler; one name " "holds one kind of list", s->name); - for (unsigned k = 0; k < h; ++k) { + for (unsigned k = 0; h < c->handler_count && k < h; ++k) { if (strcmp(c->handlers[k].input, c->handlers[h].input) || c->handlers[k].edge != c->handlers[h].edge) continue; for (const Stmt *t = c->handlers[k].body; t; t = t->next) if (t->kind == S_LET && t->listed && !strcmp(t->name, s->name)) @@ -3337,7 +3375,13 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { c->row = c->view_table[i] - 1; c->arguments = c->view_arguments[c->row >= 0 ? (unsigned)c->row : i] - 1; c->view_code[i][0] = c->code_count; - Kind kind = value(c, c->view_value[i], false, 0); + Kind kind; + if (c->view_body[i]) { /* statements as a handler's, changing only its lets and lists, and the value returned */ + c->local_count = c->local_slots = c->loop_count = c->nested = 0; + c->handling = c->has_returned = false; + statements(c, c->view_body[i]); + kind = c->returned; + } else kind = value(c, c->view_value[i], false, 0); if (kind.type != NUM && kind.type != BOOL && kind.type != I64 && kind.type != TEXT) fail_at(c, c->view_value[i]->line, c->view_value[i]->column, "a %s is a number, a bool, an i64 or text, not %s", c->row >= 0 ? "column" : "view", type_name(kind.type)); @@ -3347,10 +3391,11 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { c->row = -1; c->arguments = -1; c->fold_list = -1; + c->handling = true; for (unsigned i = 0; i < c->handler_count; ++i) { int input = handler_input[i]; c->message = input >= 0 && c->inputs[input].kind != KIND_KEY ? input : -1; - c->local_count = c->local_slots = 0; + c->local_count = c->local_slots = c->loop_count = c->nested = 0; 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]; diff --git a/docs/design/001-language-v0.md b/docs/design/001-language-v0.md index b8ec8e9..2e07e7c 100644 --- a/docs/design/001-language-v0.md +++ b/docs/design/001-language-v0.md @@ -1,6 +1,6 @@ # aup v0 — 한 언어, 한 모듈 형식 -2026-09-25. 상태: v0a(scalar), v0b(바이트·영속 문서·text·커서), v0c(레코드·리스트·인덱스·조건식·표 보기·fn·use·라이브러리의 레코드·`bool`·`i64`·`transient state`·세는 `for`·`break`·중첩·이벤트의 리스트) 구현. +2026-09-25. 상태: v0a(scalar), v0b(바이트·영속 문서·text·커서), v0c(레코드·리스트·인덱스·조건식·표 보기·fn·use·라이브러리의 레코드·`bool`·`i64`·`transient state`·세는 `for`·`break`·중첩·이벤트의 리스트·계산하는 보기) 구현. text와 리스트 문법은 [002](002-dsl-direction.md). 착수 기준: @@ -49,7 +49,7 @@ on frame { # 1/60초 진행. 핸들러 안에서 dt를 쓸 - 선언과 문장은 줄바꿈이나 `;`로 끝난다. 이름은 ASCII `[A-Za-z_][A-Za-z_0-9]*`, 31바이트 이하다. - 예약어: `state input view on if else key frame dt min max clamp message by bytes u32 reply require record each fn use now - every floor output send for let true false transient break`. + every floor output send for let true false transient break return`. - 키: `Space Enter ArrowLeft ArrowRight ArrowUp ArrowDown KeyA..KeyZ Digit0..Digit9`. 입력 이름과 키는 각각 한 번만 쓴다. 상태·입력·보기의 이름공간은 서로 겹칠 수 없다. - 식: 숫자, `true`·`false`, 상태 이름, `dt`(핸들러 안에서만), 괄호, 단항 `- !`, 이항 `* /`, `+ -`, diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index b5c647a..efdca29 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -387,6 +387,46 @@ on route { 세 탐색이 Python 모델과 같다(600번). 뉴턴의 제곱근 200개가 같은 순서로 계산한 Python의 binary64와 비트까지, 단계 수까지 같다. 배낭 문제 100개가 동적 계획법 모델과 같다. 이벤트가 끝난 뒤 이벤트의 리스트는 모두 비어 있다. +### 계산하는 보기 (구현) + +Pure [018](/srv/git/pure.git)의 3단계다. 핸들러의 계산은 소유자 스레드에서 돌아 그동안 다른 이벤트를 받지 못한다. 상태를 바꾸지 않는 +계산(경로 찾기, 통계)은 보기가 맞는 자리다: 보기는 스냅숏을 읽는 연결 스레드에서 돈다. + +```text +view distance(start: u32, goal: u32) { + let seen: list of Seen by node + let queue: list of Step + queue.append(Step { node: start }) + seen.add(Seen { node: start }) + for i in 0 .. nodes.count { + if i >= queue.count { break } + let q = queue.first + i + for e in edges[q.node * 1048576 .. q.node * 1048576 + 1048576] { + if !seen.has(e.to) { + seen.add(Seen { node: e.to, depth: seen[q.node].depth + 1 }) + queue.append(Step { node: e.to }) + } + } + } + return if seen.has(goal) { seen[goal].depth } else { -1 } +} +``` + +- **`view 이름(인자…) { 문장… return 값 }`** 은 핸들러처럼 문장을 갖는 보기다. `let`(값과 리스트), `for`, `break`, `if`를 쓰고, 마지막 + 줄의 `return`이 보기의 값이다(수, bool, i64, text). `return`은 본문의 마지막 줄에만 있다. +- **본문은 상태를 바꾸지 않는다.** 바꾸는 것은 그 `let`과 자기 리스트(`let x: list of R`)뿐이다. `send`, `reply`, `require`, `now`, `dt`는 + 없다. 평가마다 `let`은 새로 시작하고 리스트는 비어서 시작한다. +- HTTP로는 지금의 보기와 같다(`GET /views/distance?start=1&goal=5`). 읽는 연결마다 자기 리스트를 가지므로 여러 연결이 동시에 다른 인자로 + 물어도 된다. 이벤트처럼 실행량 예산과 메모리 예산이 있다(넘으면 422 `work`, 503 `memory`). +- 표 보기의 열과 필터는 지금처럼 식 하나다. +- `return`은 예약어다. + +예제: `examples/routes.aup`의 `distance`(인자를 받는 BFS)와 `reach`(첫 노드에서 닿는 노드 수와 가장 먼 거리). + +검증(`tests/check.py`의 `computing`과 진단 8건, Pure의 `test-module`의 `view_bodies`와 `server_view_bodies`): `reach`가 무작위 그래프 +20개의 모든 단계에서 모델과 같다. `pure-serve`에서 64연결이 동시에 물은 `distance` 1,280개가 모두 모델과 같다. 실행량과 메모리 예산을 +넘는 본문은 `work`와 `memory`로 답한다. + ### 시간 ```text diff --git a/examples/routes.aup b/examples/routes.aup index 6656947..cf6a914 100644 --- a/examples/routes.aup +++ b/examples/routes.aup @@ -117,4 +117,48 @@ on path { shortest = if best.has(path.to) { best[path.to].depth } else { -1 } } +# The same search as a view (Pure 018's stage 3): it reads a snapshot, so +# it keeps no event from the owner, and readers search at once. +view distance(start: u32, goal: u32) { + let seen: list of Seen by node + let queue: list of Step + queue.append(Step { node: start }) + seen.add(Seen { node: start }) + for i in 0 .. nodes.count { + if i >= queue.count { break } + let q = queue.first + i + for e in edges[q.node * 1048576 .. q.node * 1048576 + 1048576] { + if !seen.has(e.to) { + seen.add(Seen { node: e.to, depth: seen[q.node].depth + 1 }) + queue.append(Step { node: e.to }) + } + } + } + return if seen.has(goal) { seen[goal].depth } else { -1 } +} + +# How many nodes the first node reaches (times 1000), and how far the farthest is. +view reach { + let seen: list of Seen by node + let queue: list of Step + let farthest = 0 + if nodes.count > 0 { + queue.append(Step { node: nodes.first.id }) + seen.add(Seen { node: nodes.first.id }) + } + for i in 0 .. nodes.count { + if i >= queue.count { break } + let q = queue.first + i + for e in edges[q.node * 1048576 .. q.node * 1048576 + 1048576] { + if !seen.has(e.to) { + let far = seen[q.node].depth + 1 + seen.add(Seen { node: e.to, depth: far }) + queue.append(Step { node: e.to }) + farthest = max(farthest, far) + } + } + } + return seen.count * 1000 + farthest +} + view graph = "{nodes.count} nodes, {edges.count} edges" diff --git a/tests/check.py b/tests/check.py index eb37c35..c2e34e9 100644 --- a/tests/check.py +++ b/tests/check.py @@ -317,6 +317,16 @@ DIAGNOSTICS = [ (LETS + 'on k.down {\n for i in t .. 3 {\n }\n}\n', '10:12', 'expected a number'), ('state break = 0\n', '1:7', "'break' is reserved"), (LETS + 'on k.down {\n let q: list of T\n for i in 0 .. 3 {\n let q = 1\n }\n}\n', '12:9', "'q' is already a let here"), + # A view that computes (Pure 018's stage 3) changes only its lets and its own lists, and returns its value last. + (LETS + 'view v {\n n = 1\n return n\n}\n', '10:3', "a view's body changes only its lets and its own lists, not 'n'"), + (LETS + 'view v {\n xs.append(T { a: 1 })\n return 0\n}\n', '10:3', + "a view's body changes only its lets and its own lists, not 'xs'"), + (LETS + 'view v {\n let q = 1\n}\n', '9:6', "a view's body ends with return and its value"), + (LETS + 'view v {\n if n > 0 {\n return 1\n }\n return 0\n}\n', '11:5', "return is the last line of a view's body"), + (LETS + 'on k.down {\n return 1\n}\n', '10:3', "return gives a view's body its value; a handler returns nothing"), + (LETS + 'view v {\n require n > 0\n return n\n}\n', '10:3', "a view requires nothing; its value says what it found"), + (LETS + 'view v {\n let x = now\n return x\n}\n', '10:11', 'now is only available in handlers'), + ('state return = 0\n', '1:7', "'return' is reserved"), (LETS + 'view v = x\non k.down {\n let x = 1\n}\n', '9:10', "unknown state 'x'"), (LETS + 'on k.down {\n let let = 1\n}\n', '10:7', "'let' is reserved"), # A slice takes a list keyed by a number. @@ -1176,6 +1186,17 @@ def computing(aup, pure_run, work, graphs=20): heapq.heappush(pending, (best[t], t)) assert path['states']['shortest'] == best.get(b, -1), (seed, a, b, path['states']['shortest'], best.get(b, -1)) searched += 3 + # The view that computes (Pure 018's stage 3): from the first node, evaluated on the app after each search. + first = min(v for edge in edges for v in edge) + depth, frontier = {first: 0}, [first] + for v in frontier: + for t in out[v]: + if t not in depth: + depth[t] = depth[v] + 1 + frontier.append(t) + reach = len(depth) * 1000 + max(depth.values()) + assert all(json.loads(line)['views']['reach'] == reach for line in lines), (seed, reach) + assert all(json.loads(line)['views']['distance'] is None for line in lines) # no arguments given rng = random.Random(18) xs = [1.0, 2.0, 4.0, 0.25, 1e-6, 1e12] + [10 ** rng.uniform(-6, 12) for _ in range(194)] |
