diff options
| author | eunhhu <[email protected]> | 2026-10-19 03:47:47 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-19 03:47:47 +0100 |
| commit | 0d66663351c411693c71788804717ad2694c81cd (patch) | |
| tree | 2837bb3840537f925c9ebc42917a19c3f5233c6d | |
| parent | 7934d1ce23653e5843140a990837c058e0ed86a4 (diff) | |
| download | aup-0d66663351c411693c71788804717ad2694c81cd.tar.gz aup-0d66663351c411693c71788804717ad2694c81cd.zip | |
Lists bare as values (after Pure 140's inflate and HTTP): a let's list or a message's rows named where a value goes is a list value (Pure's LIST_VALUE, a copy of its items), given to fns, returned (several at once too), held by a let or put in a field; a list value (a parameter's, a let's from a fn, a field's) moves by its number uncopied and is read through a register (for, a fold at its height, [i], [i].field, .count, .first, .last); changing one says it is a list value, a stored list is still no value; a list where a number goes says to walk it or read .count; test bare_lists (200 random lists through fns that fold, walk, read by position and give back two lists and a list of numbers, against Python), two diagnostics (one changed: o.rows where a number goes); the 60 sources compiled before are the same modules byte for byte; 002
| -rw-r--r-- | compiler/compile.c | 46 | ||||
| -rw-r--r-- | docs/design/002-dsl-direction.md | 28 | ||||
| -rw-r--r-- | tests/check.py | 85 |
3 files changed, 149 insertions, 10 deletions
diff --git a/compiler/compile.c b/compiler/compile.c index 7927acf..ec99e50 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -2320,6 +2320,9 @@ static int text_state(Compiler *c, const char *name, int line, int column) { static int cursor_owner(const Compiler *c, int cursor) { return state_index(c, c->state_target[cursor]); } /* The list state name[...] indexes (where: the index, or NULL). */ +static int local_named(const Compiler *c, const char *name); +static int parameter(const Compiler *c, const char *name); + static int list_state(Compiler *c, const char *name, int line, int column, const Expr *where) { int index = state_index(c, name); if (index >= 0 && c->state_types[index] == STATE_LIST) { @@ -2328,6 +2331,10 @@ static int list_state(Compiler *c, const char *name, int line, int column, const return index; } (void)where; + int local = local_named(c, name); + if (parameter(c, name) >= 0 || (local >= 0 && c->locals[local].kind.type == LIST)) + fail_at(c, line, column, "'%s' is a list value (a copy, given or made): it is read by for, a fold, [i], .count, .first " + "and .last, not changed; changes go to a let's list", name); const char *kind = declared_kind(c, name); if (kind) fail_at(c, line, column, "'%s' is %s, not a list", name, kind); fail_at(c, line, column, "unknown list '%s'", name); @@ -2377,6 +2384,9 @@ static void expect_kind(Compiler *c, const Expr *e, Kind actual, Type type) { const char *name = e->kind == E_MEMBER ? e->member : e->name; if (actual.type != type && actual.type == RECORD && c->records[actual.owner].variant && *name) fail_at(c, e->line, e->column, "'%s' holds a variant: match it, as in match %s { ... }", name, name); + if (actual.type == LIST && type != LIST) + fail_at(c, e->line, e->column, "expected %s, not a list: walk it (for x in ...) or read .count, .first or .last", + type_name(type)); if (actual.type != type) fail_at(c, e->line, e->column, "expected %s, not %s", type_name(type), type_name(actual.type)); } @@ -2736,6 +2746,7 @@ static int hidden_list(Compiler *c, int record, bool bound, int line, int column static Kind value(Compiler *c, const Expr *e, bool handler, int height); static int walked_list(Compiler *c, const char *path, int line, int column); +static int walked_list_at(Compiler *c, const char *path, int line, int column, int height); static void item_of(Compiler *c, const Expr *e, bool handler, int height, int list); static Kind item_value(Compiler *c, const Expr *e, int list); static int local_named(const Compiler *c, const char *name); @@ -3407,6 +3418,11 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { int index = state_index(c, e->name); if (index >= 0) { int type = c->state_types[index]; + if (type == STATE_LIST && c->state_event[index] && !c->state_hidden[index]) { + /* a let's list as a value (a copy of its items): to a fn, a return, a let or a field */ + emit(c, e->line, e->column, LIST_VALUE, (uint32_t)index, 0); + return (Kind){LIST, module_record(c, c->state_library[index], c->state_target[index])}; + } if (type == STATE_LIST) fail_at(c, e->line, e->column, "'%s' is a list; read %s.count or a field, as in %s[index].field", e->name, e->name, e->name); @@ -3500,9 +3516,11 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { if (library >= 0) return inline_fn(c, e, library, e->member, handler, height); /* library.constant */ int message; char path[NAME]; - if (rows_list(c, e) >= 0) - fail_at(c, e->line, e->column, "'%s' holds a message's rows: walk them (for r in %s) or read .count, .first or .last", - c->state_names[rows_list(c, e)], c->state_names[rows_list(c, e)]); + int given = rows_list(c, e); + if (given >= 0) { /* a message's rows as a value (a copy of them): to a fn, a return, a let or a field */ + emit(c, e->line, e->column, LIST_VALUE, (uint32_t)given, 0); + return (Kind){LIST, module_record(c, c->state_library[given], c->state_target[given])}; + } if (message_path(c, e, &message, path)) { const Input *in = &c->inputs[message]; if (message != c->message) @@ -3662,8 +3680,13 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { return value(c, &at, handler, height); } if (e->count == 2) fail_at(c, e->line, e->column, "a list's slice goes in a for or a fold, as in for x in %s[a .. b]", e->name); - app_name(c, e, e->name); - int list = list_state(c, e->name, e->line, e->column, e->args[0]); + int local = c->module ? -1 : local_named(c, e->name), list; + if (!c->module && (parameter(c, e->name) >= 0 || (local >= 0 && c->locals[local].kind.type == LIST))) + list = walked_list_at(c, e->name, e->line, e->column, height); /* a list value, bound to be read */ + else { + app_name(c, e, e->name); + list = list_state(c, e->name, e->line, e->column, e->args[0]); + } const Record *r = record_of(c, list); if (!*e->member && r->scalar) { /* xs[i]: the value at a position (or a key) */ item_of(c, e->args[0], handler, height, list); @@ -3755,7 +3778,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { case E_FOLD: { /* The block after FOLD runs once per item, as a column of the list: it * starts with an empty stack and reads only its item and states. */ - int list = walked_list(c, e->member, e->line, e->column); /* a message's rows in its handler, a list value bound */ + int list = walked_list_at(c, e->member, e->line, e->column, height); /* rows in its handler, a list value bound */ if (c->folding) fail_at(c, e->line, e->column, "a count, sum, max or min cannot hold another"); if (c->filtering) fail_at(c, e->line, e->column, "a table's where cannot hold a count, sum, max or min"); bool sliced = e->args[2] != NULL; @@ -4287,17 +4310,22 @@ static void view_changes(Compiler *c, const Stmt *s, int list, const char *name) /* The list a for or a fold walks, written as its name (a list, a message's rows, a let's) or as a path to a list * value (item.items, a match's binding): a value is bound to a register first. */ -static int walked_list(Compiler *c, const char *path, int line, int column) { +static int walked_list_at(Compiler *c, const char *path, int line, int column, int height) { int named = state_index(c, path), local = strchr(path, '.') ? -1 : local_named(c, path); if (named >= 0 || (c->module == 0 && strchr(path, '.') == NULL && alias_named(c, path) < 0 && loop_named(c, path) < 0 && - (local < 0 || c->locals[local].kind.type != LIST))) + parameter(c, path) < 0 && (local < 0 || c->locals[local].kind.type != LIST))) return list_state(c, path, line, column, NULL); Expr *e = path_expr(c, path, line, column); - Kind kind = value(c, e, c->handling, 0); + Kind kind = value(c, e, c->handling, height); if (kind.type != LIST) fail_at(c, line, column, "'%s' is not a list", path); return bind_list(c, e, kind); } +/* The list a for walks or a fold folds, its path read with an empty stack below it. */ +static int walked_list(Compiler *c, const char *path, int line, int column) { + return walked_list_at(c, path, line, column, 0); +} + /* A slot for a let of bytes or text (Pure's: 0-15 bytes, 16-31 texts), the handler's next. */ static unsigned held_slot(Compiler *c, const Stmt *s, Type type) { unsigned *count = type == BYTES ? &c->held_bytes : &c->held_texts; diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index 8a70906..31d4212 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -889,3 +889,31 @@ on edit { 언어는 그대로다(`cursor in note`, `mark in note`, `index of tasks`). Pure의 모듈에서는 커서와 인덱스가 앵커 하나다: 상태 종류 2가 text나 리스트 안의 자리이고, 명령 일곱(LOAD, STORE, START, END, MOVE, COUNT, AT)이 그 둘의 열셋을 대신한다. aup는 MOVE와 COUNT에 커서가 든 text나 인덱스가 든 리스트를 적어 낸다. Pure의 줄 수가 0부터이므로 `at.line`은 1을 더해 내보낸다. + +### 맨 리스트를 값으로 (Pure 140의 inflate와 HTTP 뒤) + +```text +fn total(xs) = sum(x.v for x in xs) # 리스트를 받는 fn: 걷기, 접기, xs[i], .count, .first, .last로 읽는다 +fn split(xs) { + let odds: list of Item + let evens: list of Item + for x in xs { if x.v % 2 == 0 { evens.append(x) } else { odds.append(x) } } + return odds, evens # 리스트를 돌려주는 fn: 그 값(항목의 복사)이 결과다 +} +on give { + let o, e = split(give.items) # 메시지의 행도, let의 리스트도 값으로 넘어간다 + n = total(o) + total(e) +} +``` + +- **값.** let의 리스트나 메시지의 행을 그 이름으로 쓰면 리스트 값이 된다(Pure의 LIST_VALUE: 항목의 복사). fn의 인자, `return`, let, + 레코드의 리스트 필드에 그렇게 준다. 이미 값인 리스트(매개변수, fn이 돌려준 것을 담은 let, 레코드의 리스트 필드)는 복사하지 않고 + 번호로 옮겨진다. +- **읽기만.** 리스트 값은 레지스터에 묶여 읽힌다(LIST_BIND). 바꾸려 하면 진단이 나온다: "a list value ... is read ..., not changed; + changes go to a let's list". +- **저장되는 리스트는 값이 아니다.** 상태의 리스트를 fn에 넘기면 지금처럼 거절한다(Pure의 LIST_VALUE는 이벤트의 리스트만 받는다). +- **비용.** let의 리스트를 넘길 때마다 그 항목을 복사한다. 자주 부르는 fn에는 한 번 값으로 만든 것(`let t = table(xs)`)을 넘긴다. + 복사 없이 나누는 길은 Pure의 일로 남는다. +- 검증: `bare_lists`가 무작위 리스트 200개를 행으로 받아 리스트를 받는 fn, 두 리스트를 돌려주는 fn, 수의 리스트를 돌려주는 fn을 + 지나게 하고 파이썬과 견준다. 진단 둘(리스트 값을 바꾸기, 저장되는 리스트를 넘기기). 그 전에 컴파일되던 aup 소스 60개의 모듈은 + 바이트까지 같다. diff --git a/tests/check.py b/tests/check.py index 234bb4e..b19b37e 100644 --- a/tests/check.py +++ b/tests/check.py @@ -83,6 +83,11 @@ 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"), + # Lists bare as values (after 140's inflate and HTTP): a list value (a parameter's, or what a fn gave back) is read, + # not changed; a stored list is not passed as one. + ('fn grow(xs) {\n xs.append(1)\n return 0\n}\nstate n = 0\non frame {\n let ys: list of number = [1]\n n = grow(ys)\n}\n', + '2:3', "'xs' is a list value (a copy, given or made): it is read"), + ('record R {\n v = 0\n}\nstate rs: list of R\nfn f(xs) = xs.count\nview v = f(rs)\n', '6:12', "'rs' is a list; read rs.count"), # Fns that call themselves, blocks, several values, records' values and characters (Pure 027's stage 2c). ('fn f(n) = if n == 0 { 0 } else { f(n - 1) + 1 }\nstate x = 0\non frame {\n x = f(3)\n}\n', '1:34', "fn 'f' calls itself before a return gives its value: write its type"), @@ -363,7 +368,7 @@ DIAGNOSTICS = [ "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"), + 'expected a number, not a list: walk it (for x in ...) or read .count, .first or .last'), (ROWS + 'on o {\n for a in n {\n }\n}\n', '11:12', "'n' is a state, not a list"), (ROWS + 'on o {\n for n in xs {\n }\n}\n', '11:7', "'n' is already declared as a state"), ('input o = message {\n rows: list of Nope\n}\n', '2:17', "a list holds a record; 'Nope' is not one"), @@ -536,6 +541,54 @@ on forever.down { } ''' +BARE_LISTS = '''record Item { + v = 0 + w = 0 +} +state total = 0 +state odd = 0 +state even = 0 +state far = -1 +state second = -1 +state twice = 0 +state last_odd = -1 +input give = message { items: list of Item } + +fn sum_v(xs) = sum(x.v for x in xs) +fn split(xs) { + let odds: list of Item + let evens: list of Item + for x in xs { + if x.v % 2 == 0 { evens.append(Item { v: x.v, w: x.w }) } else { odds.append(Item { v: x.v, w: x.w }) } + } + return odds, evens +} +fn farthest(xs) = max(x.v * x.v + x.w * x.w for x in xs) +fn second_w(xs) = xs[1].w +fn doubled(xs) { + let out: list of number + for x in xs { out.append(2 * x.v + x.w) } + return out +} +fn added(ns) { + let t = 0 + for n in ns { t = t + n } + return t +} + +on give { + total = sum_v(give.items) + let o, e = split(give.items) + odd = sum_v(o) * 1000 + o.count + even = sum_v(e) * 1000 + e.count + if give.items.count > 0 { far = farthest(give.items) } + if give.items.count > 1 { second = second_w(give.items) } + let d = doubled(give.items) + twice = added(d) + if o.count > 0 { last_odd = o.last.v } +} +''' + 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. @@ -1453,6 +1506,35 @@ def nested_values(aup, pure_run, work): +def bare_lists(aup, pure_run, work, events=200): + """Lists passed to fns and given back bare (Pure 027 after 140's inflate and HTTP): a message's rows and a let's + list given to fns as values (copies), fns that fold, walk and read them by position, a fn giving back two lists + and one giving back a list of numbers, read again by fns; each event's states against Python's.""" + import random + import struct + path = work / 'bare_lists.pmod' + (work / 'bare_lists.aup').write_text(BARE_LISTS) + compile_module(aup, work / 'bare_lists.aup', path) + rng = random.Random(140) + asked = [[(rng.randint(-50, 50), rng.randint(-50, 50)) for _ in range(rng.randrange(0, 12))] for _ in range(events)] + commands = ''.join(f"message give {b''.join(struct.pack('<dd', v, w) for v, w in items).hex() or '-'}\n" for items in asked) + lines = run(pure_run, path, stdin=commands).stdout.splitlines() + model = {'far': -1, 'second': -1, 'last_odd': -1} + for items, line in zip(asked, lines): + state = json.loads(line) + odd, even = [i for i in items if i[0] % 2], [i for i in items if not i[0] % 2] + model['total'] = sum(v for v, _ in items) + model['odd'] = sum(v for v, _ in odd) * 1000 + len(odd) + model['even'] = sum(v for v, _ in even) * 1000 + len(even) + if items: model['far'] = max(v * v + w * w for v, w in items) + if len(items) > 1: model['second'] = items[1][1] + model['twice'] = sum(2 * v + w for v, w in items) + if odd: model['last_odd'] = odd[-1][0] + assert state['fault'] == 'none' and {k: state['states'][k] for k in model} == model, (items, state['states']) + assert len(lines) == events + return events + + def functions(aup, pure_run, work, trees=40, texts=60): """Pure 027's 2c: fns that call themselves, each call its own (a let's list each, records' values passed and given back, several values at once), against the same in Python: random trees built and summed; the characters of @@ -2592,6 +2674,7 @@ def main(): report['formats_and_texts'] = formats_and_texts(aup, pure_run, work) report['nested_values'] = nested_values(aup, pure_run, work) report['functions'] = functions(aup, pure_run, work) + report['bare_lists'] = bare_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)) |
