From 25af3db3643c7a4e961b172104ca289f5923e83a Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 28 Sep 2026 05:22:14 +0100 Subject: Slice a keyed list by its keys list[low .. high], as a fold's or a for's list, is the items whose keys are from low up to before high (a list keyed by a number), read by key: distinct(l.item for l in lines[o * 16 .. o * 16 + 16] where stock[l.item].quantity < t) reads one order's lines, whatever its test does outside them. It compiles to Pure's sliced folds and walks; an empty for over a slice still computes its keys. Two diagnostics and an app of slices are checked. --- compiler/compile.c | 43 ++++++++++++++++++++++++++++++++++++---- docs/design/002-dsl-direction.md | 5 ++++- tests/check.py | 8 ++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/compiler/compile.c b/compiler/compile.c index ee83c8b..bf3c882 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -53,7 +53,8 @@ typedef enum { E_RECORD, /* name { fields }: E_FIELD nodes linked from args[0] */ E_FIELD, /* member: args[0] */ E_IF, /* if args[0] { args[1] } else { args[2] } */ - E_FOLD, /* op(args[1] for name in member where args[0]): count, first (no value), sum, max, min */ + E_FOLD, /* op(args[1] for name in member[args[2] .. args[3]] where args[0]): count, first (no value), sum, max, min, + distinct; args[2] and args[3], when given, slice a list keyed by a number */ } ExprKind; typedef struct Expr { ExprKind kind; @@ -74,6 +75,7 @@ typedef struct Stmt { char name[NAME], member[NAME]; /* member: the item field an S_SET changes; S_FOR: name is the item, member the list */ Expr *value; /* assigned value, condition, reply, requirement or insertion */ Expr *first, *last; /* a range, the insertion cursor or index, or the item index; last may be NULL */ + Expr *low, *high; /* S_FOR: a slice of the list's keys, or NULL */ /* S_SET: name is the list, or empty when first is an index naming the item */ struct Stmt *then, *otherwise, *next; } Stmt; @@ -617,6 +619,17 @@ static Expr *conditional(Compiler *c, const Token *token) { return e; } +/* [low .. high] after a list: the items whose keys are from low up to before high. */ +static void slice(Compiler *c, Expr **low, Expr **high) { + take(c); /* [ */ + *low = expression(c, 0, true); + newlines(c); + expect(c, "..", "'..'"); + *high = expression(c, 0, true); + newlines(c); + expect(c, "]", "']'"); +} + /* A list's name, or a message's rows as input.field (a group's too). */ static void list_path(Compiler *c, char out[NAME]) { Token list = expect_name(c, "a list"); @@ -646,6 +659,7 @@ static Expr *fold(Compiler *c, const Token *name, int how, Expr *value) { name->text, item.text); take(c); list_path(c, e->member); + if (is(c, "[")) slice(c, &e->args[2], &e->args[3]); newlines(c); e->args[1] = value; if (word(c, "where")) { @@ -892,6 +906,7 @@ static Stmt *statement(Compiler *c, unsigned depth) { take(c); s->first = node(c, E_NAME, c->token.line, c->token.column); /* where the list is named */ list_path(c, s->member); + if (is(c, "[")) slice(c, &s->low, &s->high); if (word(c, "where")) { take(c); s->value = expression(c, 0, false); @@ -2039,6 +2054,13 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { list = list_state(c, e->member, e->line, e->column, NULL); /* a message's rows: in its handler */ 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; + if (sliced) { /* the keys from low up to before high, found by key */ + if (!keyed_by(c, list) || record_of(c, list)->field_text[c->list_key[list] - 1]) + fail_at(c, e->line, e->column, "'%s' is not keyed by a number: a slice takes one", e->member); + number(c, e->args[2], handler, height); + number(c, e->args[3], handler, height + 1); + } unsigned at = c->code_count, joins = c->joins; emit(c, e->line, e->column, FOLD, 0, 0); c->folding = true; @@ -2058,7 +2080,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { c->folding = false; c->fold_list = -1; c->joins = joins; - c->code[at].arg = (uint32_t)e->op | (uint32_t)list << 8 | (uint32_t)(c->code_count - at - 1) << 16; + c->code[at].arg = (uint32_t)e->op | (sliced ? 0x80u : 0) | (uint32_t)list << 8 | (uint32_t)(c->code_count - at - 1) << 16; return e->op == FOLD_FIRST ? (Kind){INDEX, list} : (Kind){NUM, -1}; } case E_RECORD: case E_FIELD: @@ -2314,6 +2336,12 @@ static void statements(Compiler *c, const Stmt *s) { int list = list_state(c, s->member, s->first->line, s->first->column, NULL); const char *kind = declared_kind(c, s->name); if (kind) fail_at(c, s->last->line, s->last->column, "'%s' is already declared as %s", s->name, kind); + if (s->low) { /* a slice: the keys from low up to before high */ + if (!keyed_by(c, list) || record_of(c, list)->field_text[c->list_key[list] - 1]) + fail_at(c, s->first->line, s->first->column, "'%s' is not keyed by a number: a slice takes one", s->member); + number(c, s->low, true, 0); + number(c, s->high, true, 1); + } unsigned at = c->code_count, jump = 0; emit(c, s->line, s->column, EACH, 0, 0); c->walking = true; @@ -2328,8 +2356,15 @@ static void statements(Compiler *c, const Stmt *s) { statements(c, s->then); if (s->value) c->code[jump].arg = c->code_count; c->walking = false; - if (c->code_count == at + 1) c->code_count = at; /* nothing to do for any item */ - else c->code[at].arg = (uint32_t)list << 8 | (c->code_count - at - 1) << 16; + bool empty = c->code_count == at + 1; + if (empty && !s->low) c->code_count = at; /* nothing to do for any item */ + else { + if (empty) { /* a slice's keys were computed (they fault or not): a block that does nothing */ + emit(c, s->line, s->column, CONST, 0, 0); + emit(c, s->line, s->column, JZ, c->code_count + 1, 0); + } + c->code[at].arg = (uint32_t)list << 8 | (s->low ? 1u : 0) | (c->code_count - at - 1) << 16; + } break; } case S_IF: { diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index c4aaddf..07aee73 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -330,7 +330,10 @@ on clean { 메시지는 행을 리스트의 저장 형식 바이트로 보내도 된다(대량 넣기). 출력은 행을 보내지 않는다. - 이것으로 여러 행을 한꺼번에 바꾸는 트랜잭션(TPC-C의 주문처럼 행마다 재고를 줄이고 주문 행을 넣는 일)이 이벤트 하나다. - 키 있는 리스트의 키를 범위로 묻는 `where`(`for l in lines where l.id >= start && l.id < start + 16`)와 집계, 표의 필터는 Pure가 - 그 범위의 항목만 읽는다(키로 찾아서). 인덱스를 선언하지 않는다. + 그 범위의 항목만 읽는다(키로 찾아서). 인덱스를 선언하지 않는다. 다만 집계와 걷기는 나머지 조건이 결함을 낼 수 없을 때만 그렇게 + 하므로, 범위를 뜻으로 적으려면 **조각**을 쓴다: `lines[a .. b]`는 키가 a부터 b 앞까지인 항목들이다(`for l in lines[a .. b]`, + `distinct(l.item for l in lines[a .. b] where stock[l.item].quantity < t)`). 조각 밖의 항목은 돌지 않으므로 그 조건이 결함을 낼 + 수 있어도 된다. 수로 키를 둔 리스트에만 쓴다. 검증: 진단 9건(`for` 안의 `for`, 걷는 리스트에 넣기, `for` 안의 `reply`, 행을 보내는 출력, 핸들러 밖의 행, 값으로 쓴 행, 리스트가 아닌 것, 이미 선언된 항목 이름, 없는 레코드)과, 그룹 안의 행·`where`·걷는 항목을 읽는 집계·지우는 걷기를 쓴 앱을 Pure가 diff --git a/tests/check.py b/tests/check.py index 9b10f02..06e029b 100644 --- a/tests/check.py +++ b/tests/check.py @@ -270,6 +270,9 @@ 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"), (LETS + 'view v = x\non k.down {\n let x = 1\n}\n', '9:10', "unknown state 'x'"), (LETS + 'on k.down {\n let let = 1\n}\n', '10:7', "'let' is reserved"), + # A slice takes a list keyed by a number. + (LETS + 'view v = count(x in xs[0 .. 3])\n', '9:10', "'xs' is not keyed by a number: a slice takes one"), + (LETS + 'on k.down {\n for x in xs[0 .. 1] {\n n = 1\n }\n}\n', '10:12', "'xs' is not keyed by a number: a slice takes one"), ] ACCEPTED = [ @@ -279,6 +282,11 @@ ACCEPTED = [ ' for l in lines where l.id >= start && l.id < start + 16 {\n l.done = 1\n }\n}\n' 'view items(order: u32) = distinct(l.item for l in lines where l.id >= order * 16 && l.id < order * 16 + 16)\n' 'view level = distinct(l.item for l in lines where l.qty < 10)\n', + # Slices: a fold over the keys of one order whose test may fault, a walk over them, one that does nothing. + 'record L {\n id = 0\n item = 0\n qty = 0\n}\nrecord S {\n id = 0\n quantity = 0\n}\nstate lines: list of L by id\n' + 'state stock: list of S by id\ninput k = message {\n o: u32\n}\n' + 'view level(o: u32, t: number) = distinct(l.item for l in lines[o * 16 .. o * 16 + 16] where stock[l.item].quantity < t)\n' + 'on k {\n for l in lines[k.o * 16 .. k.o * 16 + 16] {\n l.qty = 0\n }\n for l in lines[0 .. 1] {\n }\n}\n', # Lets: a sum in a for, an index let changing an item, a let in a for, one read in a fold, one per branch. 'record Stock {\n item = 0\n left = 0\n price = 0\n}\nrecord Line {\n item = 0\n qty = 1\n}\n' 'state stock: list of Stock by item\nstate revenue = 0\nstate sold = 0\ninput order = message {\n lines: list of Line\n}\n' -- cgit v1.2.3