From 5f223bd45d9279982ec6e4ca334c089c07782639 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 19 Oct 2026 04:04:09 +0100 Subject: 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 --- compiler/compile.c | 145 ++++++++++++++++++++++++++++----------- docs/design/002-dsl-direction.md | 23 +++++++ tests/check.py | 80 ++++++++++++++++++++- 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('