aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compiler/compile.c36
-rw-r--r--docs/design/001-language-v0.md4
-rw-r--r--tests/check.py18
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'