aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-19 04:04:09 +0100
committereunhhu <[email protected]>2026-10-19 04:04:09 +0100
commit5f223bd45d9279982ec6e4ca334c089c07782639 (patch)
tree02b1074fdeef902ebe806c22a3b93ef022cfa9af
parent4aefbab3cdbcb5a7c4b7598795baa276f82fd842 (diff)
downloadaup-5f223bd45d9279982ec6e4ca334c089c07782639.tar.gz
aup-5f223bd45d9279982ec6e4ca334c089c07782639.zip
Lets' lists in inner blocks and xs = [...] (after Pure 140's inflate): a let's list in an if's, a for's, a while's or a match's block (a handler's, a fn's or a view's body's) begins empty each time its let is run, one state let.NAME a name as before (gathered from inner blocks now), emptied there a item at a time (while xs.count > 0 remove the last: a list of numbers' by its place count - 1, others' .last; Pure has no op that empties a list); xs = [...] empties a let's list and gives it its elements ([] none), a stored list refused; a list literal in an expression keeps its elements (it dropped them: a field's [1, 2] was empty); folds and filters hold no let's list; test inner_lists (100 random events: a for's list, a keyed one, a fn's, a view body's, xs = [...] and [] against Python), a diagnostic retired (an inner block's list now reads as unknown past it), one added; the 60 sources the same modules; 002
-rw-r--r--compiler/compile.c145
-rw-r--r--docs/design/002-dsl-direction.md23
-rw-r--r--tests/check.py80
3 files changed, 205 insertions, 43 deletions
diff --git a/compiler/compile.c b/compiler/compile.c
index 1fdd43a..942e6a0 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -901,8 +901,8 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) {
} else if (token.kind == T_PUNCT && !strcmp(token.text, "[")) { /* refused where it stands (only a let's list begins so) */
left = node(c, E_LIST, token.line, token.column);
newlines(c);
- while (!is(c, "]")) {
- expression(c, 0, true);
+ for (Expr **tail = &left->args[0]; !is(c, "]"); tail = &(*tail)->next) {
+ *tail = expression(c, 0, true);
newlines(c);
if (!is(c, ",")) break;
take(c);
@@ -4360,6 +4360,44 @@ static unsigned held_slot(Compiler *c, const Stmt *s, Type type) {
static void chars_loop(Compiler *c, const Stmt *s, const Expr *subject);
static bool text_path(Compiler *c, const char *path);
+/* A let's list emptied (at a let in an inner block, or by xs = [...]): while xs.count > 0 { xs.remove(xs.last) } (a list
+ * of numbers' place count - 1), the same as a new list each time the let is run. Its items go one at a time (Pure has no op that empties a list). */
+static Stmt *emptied(Compiler *c, const char *name, int line, int column) {
+ Stmt *loop = allocate(c, sizeof(*loop)), *check = allocate(c, sizeof(*check)), *stop = allocate(c, sizeof(*stop)),
+ *remove = allocate(c, sizeof(*remove));
+ Expr *list = node(c, E_NAME, line, column), *count = node(c, E_MEMBER, line, column), *none = node(c, E_NUMBER, line, column),
+ *empty = node(c, E_BINARY, line, column), *last = node(c, E_MEMBER, line, column);
+ memcpy(list->name, name, NAME);
+ count->args[0] = last->args[0] = list;
+ count->count = last->count = 1;
+ memcpy(count->member, "count", 6);
+ memcpy(last->member, "last", 5);
+ none->whole = true;
+ empty->op = LE, empty->args[0] = count, empty->args[1] = none, empty->count = 2;
+ check->kind = S_IF, check->line = line, check->column = column, check->value = empty, check->then = stop;
+ stop->kind = S_BREAK, stop->line = line, stop->column = column;
+ remove->kind = S_REMOVE, remove->line = line, remove->column = column, remove->first = last;
+ int state = state_index(c, name);
+ if (state >= 0 && record_of(c, state)->scalar) { /* a list of numbers' last is its value: its place is count - 1 */
+ Expr *place = node(c, E_BINARY, line, column), *one = node(c, E_NUMBER, line, column);
+ one->number = 1, one->integer = 1, one->whole = true;
+ place->op = SUB, place->args[0] = count, place->args[1] = one, place->count = 2;
+ remove->first = place;
+ }
+ memcpy(remove->name, name, NAME);
+ check->next = remove;
+ loop->kind = S_COUNT, loop->line = line, loop->column = column;
+ snprintf(loop->name, NAME, "while.%u", c->whiles++);
+ loop->low = node(c, E_NUMBER, line, column);
+ loop->low->whole = true;
+ loop->high = node(c, E_NUMBER, line, column);
+ loop->high->number = 9007199254740992.0;
+ loop->high->integer = INT64_C(9007199254740992);
+ loop->high->whole = true;
+ loop->then = check;
+ return loop;
+}
+
static void statements(Compiler *c, const Stmt *s) {
unsigned scope = c->local_count; /* a block's lets end with it */
for (const Stmt *before = NULL; s; before = s, s = s->next) {
@@ -4507,7 +4545,7 @@ static void statements(Compiler *c, const Stmt *s) {
"'%s' is what this for counts", s->name);
if (c->local_count == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
if (s->listed) { /* the event's list: its state let.NAME, made before the handlers (event_lists) */
- if (c->nested) fail_at(c, s->line, s->column, "a let's list goes in the handler's own block, not in an if or a for");
+ if (c->folding || c->filtering) fail_at(c, s->line, s->column, "a fold or a filter holds no let's list");
char path[2 * NAME];
snprintf(path, sizeof(path), "let.%s", s->name);
int list = -1;
@@ -4516,6 +4554,7 @@ static void statements(Compiler *c, const Stmt *s) {
memset(&c->locals[local], 0, sizeof(c->locals[local]));
memcpy(c->locals[local].name, s->name, NAME);
c->locals[local].list = list;
+ if (c->nested) statements(c, emptied(c, s->name, s->line, s->column)); /* an inner block's: new each time */
for (const Expr *element = s->value ? s->value->args[0] : NULL; element; element = element->next) {
if (keyed_by(c, list))
fail_at(c, element->line, element->column, "a keyed list adds its elements by key; add them one by one");
@@ -4690,8 +4729,17 @@ static void statements(Compiler *c, const Stmt *s) {
} else if (c->state_types[index] == STATE_NUM || c->state_types[index] == STATE_BOOL) {
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 && c->state_event[index] && s->value->kind == E_LIST) {
+ statements(c, emptied(c, s->name, s->line, s->column)); /* xs = [...]: a let's list emptied, then its elements */
+ if (keyed_by(c, index) && s->value->args[0])
+ fail_at(c, s->value->line, s->value->column, "a keyed list adds its elements by key; add them one by one");
+ for (const Expr *element = s->value->args[0]; element; element = element->next) {
+ record_fields(c, element, index, 0);
+ emit(c, element->line, element->column, LIST_APPEND, (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);
+ fail_at(c, s->line, s->column, "a list changes by append, insert, remove, %s[index].field = value and (a let's) "
+ "%s = [...]", s->name, s->name);
} else if (c->state_types[index] == STATE_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);
@@ -5159,45 +5207,60 @@ static uint32_t intern(unsigned char *strings, size_t *used, const char *text) {
* list (Pure 018), one for the lets of that name in the handlers' own
* 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 + c->views + c->fn_count; ++h) /* the handlers', the views' bodies', the fns' */
- for (const Stmt *s = h < c->handler_count ? c->handlers[h].body : h < c->handler_count + c->views ?
- c->view_body[h - c->handler_count] : c->fns[h - c->handler_count - c->views].block; s; s = s->next) {
- if (s->kind != S_LET || !s->listed) continue;
- char path[2 * NAME];
- snprintf(path, sizeof(path), "let.%s", s->name);
- if (strlen(path) >= NAME)
- fail_at(c, s->line, s->column, "a let's list has a name of %d characters at most", NAME - 5);
- int found = -1;
- for (unsigned i = 0; i < c->states; ++i)
- if (c->state_event[i] && !strcmp(c->state_names[i], path)) found = (int)i;
- if (found >= 0) {
- if (strcmp(c->state_target[found], s->member) || c->state_library[found] != s->library ||
- 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; 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))
- fail_at(c, s->line, s->column, "another handler of this event has a list '%s'; name this one apart",
- s->name);
- }
- continue;
+/* Whether the statements (and the blocks in them) hold a let's list of that name. */
+static bool holds_list_let(const Stmt *s, const char *name) {
+ for (; s; s = s->next)
+ if ((s->kind == S_LET && s->listed && !strcmp(s->name, name)) || holds_list_let(s->then, name) ||
+ holds_list_let(s->otherwise, name))
+ return true;
+ return false;
+}
+
+/* The lets' lists of statements h's (a handler's, a view's body's or a fn's), in its inner blocks too: each name one
+ * state let.NAME of one record, emptied at each inner block's let (emptied). */
+static void event_lists_in(Compiler *c, unsigned h, const Stmt *s) {
+ for (; s; s = s->next) {
+ event_lists_in(c, h, s->then);
+ event_lists_in(c, h, s->otherwise);
+ if (s->kind != S_LET || !s->listed) continue;
+ char path[2 * NAME];
+ snprintf(path, sizeof(path), "let.%s", s->name);
+ if (strlen(path) >= NAME)
+ fail_at(c, s->line, s->column, "a let's list has a name of %d characters at most", NAME - 5);
+ int found = -1;
+ for (unsigned i = 0; i < c->states; ++i)
+ if (c->state_event[i] && !strcmp(c->state_names[i], path)) found = (int)i;
+ if (found >= 0) {
+ if (strcmp(c->state_target[found], s->member) || c->state_library[found] != s->library ||
+ 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 or block; "
+ "one name holds one kind of list", s->name);
+ 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;
+ if (holds_list_let(c->handlers[k].body, s->name))
+ fail_at(c, s->line, s->column, "another handler of this event has a list '%s'; name this one apart", s->name);
}
- if (c->states == STATES) fail_at(c, s->line, s->column, "at most %d states are allowed, a let's list among them", STATES);
- unsigned i = c->states++;
- memcpy(c->state_names[i], path, strlen(path) + 1);
- c->state_types[i] = STATE_LIST;
- c->state_event[i] = true;
- memcpy(c->state_target[i], s->member, NAME);
- c->state_library[i] = s->library;
- memcpy(c->state_key[i], s->key, NAME);
- c->state_key_line[i] = s->key_line;
- c->state_key_column[i] = s->key_column;
- c->state_line[i] = s->line;
- c->state_column[i] = s->column;
+ continue;
}
+ if (c->states == STATES) fail_at(c, s->line, s->column, "at most %d states are allowed, a let's list among them", STATES);
+ unsigned i = c->states++;
+ memcpy(c->state_names[i], path, strlen(path) + 1);
+ c->state_types[i] = STATE_LIST;
+ c->state_event[i] = true;
+ memcpy(c->state_target[i], s->member, NAME);
+ c->state_library[i] = s->library;
+ memcpy(c->state_key[i], s->key, NAME);
+ c->state_key_line[i] = s->key_line;
+ c->state_key_column[i] = s->key_column;
+ c->state_line[i] = s->line;
+ c->state_column[i] = s->column;
+ }
+}
+
+static void event_lists(Compiler *c) {
+ for (unsigned h = 0; h < c->handler_count + c->views + c->fn_count; ++h) /* the handlers', the views' bodies', the fns' */
+ event_lists_in(c, h, h < c->handler_count ? c->handlers[h].body : h < c->handler_count + c->views ?
+ c->view_body[h - c->handler_count] : c->fns[h - c->handler_count - c->views].block);
}
static void rows_lists(Compiler *c) {
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index 3ba3d5d..d13c595 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -933,3 +933,26 @@ on packet {
i64인 것과 같다. 값끼리는 섞이지 않는다: text의 값과 바이트의 값은 비교도 잇기도 하지 않고(진단), `bytes(t)`나 `text(b)`로 옮긴다.
- 검증: `bytes_compared`가 0x00과 0xff가 든 무작위 바이트 400쌍을 여섯 관계, 양쪽의 리터럴, 형식이 든 문자열, 리터럴과의 잇기로
파이썬과 견준다. 진단 둘(바이트와 text의 값). 그 전에 컴파일되던 소스 60개의 모듈은 바이트까지 같다.
+
+### 블록 안의 let 리스트, `xs = [...]` (Pure 140의 inflate 뒤)
+
+```text
+for block in blocks {
+ let lengths: list of number # 블록 안의 let 리스트: let을 지날 때마다 빈 리스트로 시작한다
+ for s in 0 .. 288 { lengths.append(8) }
+}
+let ys: list of number = [1, 2, 3]
+ys = [4, 5] # let의 리스트를 비우고 그 원소로 채운다. []는 비우기만
+```
+
+- **블록 안.** let의 리스트를 if, for, while, match의 블록에도 둔다. 뜻은 let을 지날 때마다 새 리스트다. 리스트는 블록이 끝나면
+ 보이지 않는다. 같은 이름은 한 레코드의 리스트 하나이고, 핸들러나 fn의 이벤트 리스트 하나다(`let.이름`). 접기와 필터의 블록에는 둘 수
+ 없다.
+- **`xs = [...]`.** let의 리스트를 비우고 원소를 차례로 붙인다. 저장되는 리스트에는 쓰지 않는다(진단).
+- **비용.** 비우기는 항목을 하나씩 지운다(`while xs.count > 0 { xs.remove(…) }`, 수의 리스트는 자리 `count - 1`, 다른 리스트는
+ `.last`). Pure에 한 번에 비우는 명령이 없어서다. 항목 수에 비례한다.
+- **고친 것.** 식 안의 리스트 리터럴이 원소를 버리고 있었다(필드의 `[1, 2]`가 빈 리스트로 됐다). 이제 원소를 잇는다. 레코드 필드에는
+ 여전히 let의 리스트나 `[]`만 준다(진단).
+- 검증: `inner_lists`가 for의 블록, 키 있는 리스트, fn의 for, 보기의 본문에 둔 let 리스트와 `xs = [...]`, `xs = []`를 무작위 100
+ 사건으로 파이썬과 견준다. 진단 하나가 바뀌었다(블록 밖에서 그 리스트를 읽으면 `unknown state`). 그 전에 컴파일되던 소스 60개의
+ 모듈은 바이트까지 같다.
diff --git a/tests/check.py b/tests/check.py
index 7a3e9f3..b999262 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -83,6 +83,9 @@ DIAGNOSTICS = [
('record R {\n v = 0\n}\ninput m = message { d: bytes }\nstate t: text\non m {\n let xs: list of R\n'
' t = text(bytes(xs))\n}\n', '8:18', 'bytes(...) makes bytes of a list of i64, not of R'),
('state x = 0\nview v = [1, 2]\n', '2:10', "a list's elements begin a let's list"),
+ # Lets' lists in inner blocks and xs = [...] (after 140's inflate): a stored list is not given elements so.
+ ('record R {\n v = 0\n}\nstate rs: list of R\non frame {\n rs = []\n}\n', '6:3',
+ "a list changes by append, insert, remove, rs[index].field = value and (a let's) rs = [...]"),
# Bytes compared (after 140's DNS and HTTP): bytes compare with bytes (a string where bytes go is its bytes), and
# neither compares nor joins with a text's value.
('state t: text\ninput m = message { name: bytes }\non m {\n require m.name == t\n}\n', '4:21',
@@ -398,8 +401,7 @@ DIAGNOSTICS = [
(LETS + 'on k.down {\n for r in xs {\n let r = 1\n }\n}\n', '11:9', "'r' is the item this for walks"),
# Counting fors, break and a let's list (Pure 018).
(LETS + 'on k.down {\n break\n}\n', '10:3', "break ends a for; this one is outside any"),
- (LETS + 'on k.down {\n if n > 0 {\n let q: list of T\n }\n}\n', '11:9',
- "a let's list goes in the handler's own block, not in an if or a for"),
+ (LETS + 'on k.down {\n if n > 0 {\n let q: list of T\n }\n n = q.count\n}\n', '13:7', "unknown state 'q'"), # its block's
(LETS + 'on k.down {\n for i in 0 .. 3 {\n i = 1\n }\n}\n', '11:5', "'i' is what this for counts; it is not assigned"),
(LETS + 'on k.down {\n for a in xs {\n for b in ys {\n xs.remove(a)\n }\n }\n}\n', '12:7',
'a for removes from the list it walks, not from one an outer for walks'),
@@ -615,6 +617,55 @@ on m {
}
'''
+INNER_LISTS = '''record Seen {
+ node = 0
+ depth = 0
+}
+state total = 0
+state keyed = 0
+state again = 0
+state counted = 0
+input go = message { n: u32, k: u32 }
+
+fn rows(n) {
+ let t = 0
+ for r in 0 .. n {
+ let zs: list of number
+ zs.append(r)
+ t = t + zs.count + zs.first
+ }
+ return t
+}
+
+on go {
+ total = 0
+ keyed = 0
+ for r in 0 .. go.n {
+ let xs: list of number = [r]
+ for k in 0 .. r % go.k + 1 { xs.append(k * r) }
+ total = total + xs.count * 1000 + sum(x for x in xs)
+ let seen: list of Seen by node
+ for k in 0 .. r % 3 + 1 { seen.add(Seen { node: k * 7 + r, depth: k }) }
+ keyed = keyed + seen.count * 100 + sum(s.depth for s in seen)
+ }
+ let ys: list of number = [1, 2, 3]
+ ys = [go.n, go.k]
+ again = ys.count * 1000 + ys.first * 10 + ys.last
+ ys = []
+ again = again + ys.count
+ counted = rows(go.n)
+}
+
+view inner {
+ let t = 0
+ for r in 0 .. 3 {
+ let zs: list of number = [r, r]
+ t = t + zs.count + sum(z for z in zs)
+ }
+ return t
+}
+'''
+
ACCEPTED = [
# Views of any value (Pure 027's 4e): a list as it is, filtered, a map with a list and a bytes column, a list a body
# built, bytes.
@@ -1532,6 +1583,30 @@ def nested_values(aup, pure_run, work):
+def inner_lists(aup, pure_run, work, events=100):
+ """Lets' lists in inner blocks, and xs = [...] (after 140's inflate): a let's list in a for's block, a keyed one, one
+ in a fn's for and one in a view's body begin empty each time their let is run; xs = [...] empties a let's list and
+ gives it its elements, [] none; each event's states and the view against Python's."""
+ import random
+ import struct
+ path = work / 'inner_lists.pmod'
+ (work / 'inner_lists.aup').write_text(INNER_LISTS)
+ compile_module(aup, work / 'inner_lists.aup', path)
+ rng = random.Random(140)
+ asked = [(rng.randrange(0, 12), rng.randrange(1, 5)) for _ in range(events)]
+ commands = ''.join(f"message go {struct.pack('<II', n, k).hex()}\n" for n, k in asked)
+ lines = run(pure_run, path, stdin=commands).stdout.splitlines()
+ assert len(lines) == events
+ for (n, k), line in zip(asked, lines):
+ state = json.loads(line)
+ total = sum(len(xs) * 1000 + sum(xs) for xs in ([r] + [j * r for j in range(r % k + 1)] for r in range(n)))
+ keyed = sum((r % 3 + 1) * 100 + sum(range(r % 3 + 1)) for r in range(n))
+ want = {'total': total, 'keyed': keyed, 'again': 2000 + n * 10 + k, 'counted': sum(1 + r for r in range(n))}
+ assert state['fault'] == 'none' and {key: state['states'][key] for key in want} == want, ((n, k), state['states'])
+ assert state['views']['inner'] == sum(2 + 2 * r for r in range(3)), state['views']
+ return events
+
+
def bytes_compared(aup, pure_run, work, events=400):
"""Bytes compared (Pure's TEXT_COMPARE with PURE_COMPARE_BYTES, after 140's DNS and HTTP): random bytes, 0x00 and
0xff among them, in the six relations, against literals on either side and a string with a format (a string where
@@ -2725,6 +2800,7 @@ def main():
report['functions'] = functions(aup, pure_run, work)
report['bare_lists'] = bare_lists(aup, pure_run, work)
report['bytes_compared'] = bytes_compared(aup, pure_run, work)
+ report['inner_lists'] = inner_lists(aup, pure_run, work)
report['stored_nested'] = stored_nested(aup, pure_run, work, range(6))
report['documents'] = documents_model(aup, pure_run, work, range(4))
print(json.dumps(report))