diff options
| -rw-r--r-- | compiler/compile.c | 36 | ||||
| -rw-r--r-- | docs/design/001-language-v0.md | 4 | ||||
| -rw-r--r-- | tests/check.py | 18 |
3 files changed, 56 insertions, 2 deletions
diff --git a/compiler/compile.c b/compiler/compile.c index af9899a..4e49b4b 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -1102,6 +1102,30 @@ static bool path_taken(const Input *in, const char *path) { * (Pure lays them out: docs/design/008-module-format.md). name: { ... } is a * group of fields inside the message, named group.name (a JSON object inside * a request's body). */ +/* message like other: the fields of a message input or output declared + * before, in its order. So an output and the input that takes what another + * node sends from it are written once. */ +static void message_like(Compiler *c, Input *in) { + Token other = expect_name(c, "a message input or output declared before"); + const Input *from = NULL; + for (unsigned i = 0; i + 1 < c->input_count && !from; ++i) + if (!strcmp(c->inputs[i].name, other.text)) from = &c->inputs[i]; + if (!from || (from->kind != KIND_MESSAGE && from->kind != KIND_OUTPUT)) + fail_at(c, other.line, other.column, "'%s' is no message input or output declared before", other.text); + for (unsigned f = 0; f < from->fields; ++f) { + if (in->kind == KIND_OUTPUT && from->field_types[f] == FIELD_ROWS) + fail_at(c, other.line, other.column, "an output sends no rows: '%s' has rows (%s)", other.text, + from->field_names[f]); + if (c->field_count == FIELDS) fail_at(c, other.line, other.column, "at most %d message fields are allowed", FIELDS); + memcpy(in->field_names[in->fields], from->field_names[f], NAME); + memcpy(in->field_record[in->fields], from->field_record[f], NAME); + in->field_line[in->fields] = from->field_line[f]; + in->field_column[in->fields] = from->field_column[f]; + in->field_types[in->fields++] = from->field_types[f]; + ++c->field_count; + } +} + static void message_fields(Compiler *c, Input *in, const char *group) { if (!is(c, "{")) return; /* the field block opens on the declaration's line */ take(c); @@ -1471,7 +1495,11 @@ static void declaration(Compiler *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, ""); + if (word(c, "like")) { + take(c); + message_like(c, in); + } else + message_fields(c, in, ""); } else if (word(c, "text")) { take(c); in->kind = KIND_TEXT; @@ -1519,7 +1547,11 @@ static void declaration(Compiler *c) { expect_word(c, "message"); in->kind = KIND_OUTPUT; ++c->input_count; - message_fields(c, in, ""); + if (word(c, "like")) { + take(c); + message_like(c, in); + } else + message_fields(c, in, ""); } else if (!strcmp(kind.text, "view")) { Token name = expect_name(c, "view name"); declare(c, &name); diff --git a/docs/design/001-language-v0.md b/docs/design/001-language-v0.md index 3fe9ff8..305838e 100644 --- a/docs/design/001-language-v0.md +++ b/docs/design/001-language-v0.md @@ -123,6 +123,10 @@ on edit { `send 이름 { 필드: 값, 그룹: { 필드: 값 } }`으로 모든 필드를 한 번씩 주어 보낸다. 보냄은 이벤트의 일부라서 이벤트가 실패하면 보내지 않았고, 호스트는 저장된 보냄만 전한다. 무엇이 받는지는 앱 밖(호스트의 배선)이 정한다. 받는 앱은 같은 메시지를 여러 번 받아도 한 번만 적용한다(Pure `docs/research/078-app-connections.md`). 출력에는 핸들러가 없다. +- `message like 이름`은 앞에 선언한 메시지 입력이나 출력의 필드를 그 순서대로 갖는다. 다른 노드가 보내는 것을 받는 입력과 + 제 출력을 한 번만 적는다(`output changed = message { ... }`, `input remote = message like changed`). 필드를 적은 것과 + 같은 모듈이 나온다. 노드 사이에서는 필드를 이름으로 받으므로(Pure `docs/design/014-fields-by-name.md`) 순서가 달라도 + 되지만, 같은 모양이면 옮기는 일이 없다. - 외부 API의 응답은 보통의 메시지 입력으로 받는다. 호스트가 출력의 응답을 그 입력에 이으면(`pure-serve --reply 출력=입력`), 필드는 응답 JSON에서, 응답에 없는 필드는 보낸 메시지의 같은 이름 필드에서, `http_status: u32`는 상태 코드로 채워진다 (Pure `docs/research/083-delivery-over-http.md`). diff --git a/tests/check.py b/tests/check.py index 06e029b..6408b05 100644 --- a/tests/check.py +++ b/tests/check.py @@ -244,6 +244,11 @@ DIAGNOSTICS = [ (ROWS + 'on o {\n for a in o.rows {\n reply "x"\n }\n}\n', '12:5', 'a message replies once: reply after the for'), ('record L {\n item = 0\n}\noutput o = message {\n rows: list of L\n}\n', '5:9', 'an output sends no rows: send one message a row'), + ('input a = message like nothing\n', '1:24', "'nothing' is no message input or output declared before"), + ('input k = key Space\ninput a = message like k\n', '2:24', "'k' is no message input or output declared before"), + ('input a = message like a\n', '1:24', "'a' is no message input or output declared before"), + ('record L {\n item = 0\n}\ninput i = message {\n rows: list of L\n}\noutput o = message like i\n', '7:25', + "an output sends no rows: 'i' has rows (rows)"), (ROWS + 'view v = o.rows.count\n', '10:10', "the rows of 'o' are only available in its handler"), (ROWS + 'on o {\n n = o.rows\n}\n', '11:7', "'o.rows' holds a message's rows: walk them (for r in o.rows) or read .count, .first or .last"), @@ -1279,6 +1284,19 @@ def main(): report['diagnostics'] = len(DIAGNOSTICS) report['accepted_forms'] = len(ACCEPTED) + # message like X is X's fields, written once: the same module, byte for byte. + fields = ' title: text\n lane: u32\n to: {\n city: text\n }\n' + body = 'on remote {\n n = remote.lane\n}\non tap.down {\n send changed { title: "t", lane: 1, to: { city: "c" } }\n}\n' + written = (f'state n = 0\ninput tap = key Space\noutput changed = message {{\n{fields}}}\n' + f'input remote = message {{\n{fields}}}\n{body}') + liked = (f'state n = 0\ninput tap = key Space\noutput changed = message {{\n{fields}}}\n' + f'input remote = message like changed\n{body}') + (work / 'written.aup').write_text(written) + (work / 'liked.aup').write_text(liked) + assert compile_module(aup, work / 'written.aup', work / 'written.pmod') == \ + compile_module(aup, work / 'liked.aup', work / 'liked.pmod'), 'message like differs from its fields written' + report['message_like'] = True + # Replacing a running module keeps states by name. compile_module(aup, ROOT / 'examples/counter.aup', work / 'counter.pmod') (work / 'counter2.aup').write_text('state bonus = 7\nstate count = 100\ninput tap = key Space\n' |
