From aa279fb959024134416fc33f90f648d8332e9af4 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 05:59:09 +0100 Subject: Write a message once for its output and the input that takes it Growing a board two nodes share (Pure's research 105), one field took five places, two of them the same list: the output a node sends its changes on and the input it takes the other's with. A message may now take the fields of a message input or output declared before, in their order: input remote = message like changed. The module is the same as with the fields written out, byte for byte (checked). An unknown name, one that is no message, the message itself, and an output taking an input's rows are diagnosed. --- compiler/compile.c | 36 ++++++++++++++++++++++++++++++++++-- docs/design/001-language-v0.md | 4 ++++ 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' -- cgit v1.2.3