aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-28 05:22:14 +0100
committereunhhu <[email protected]>2026-09-28 05:22:14 +0100
commit25af3db3643c7a4e961b172104ca289f5923e83a (patch)
tree8d431d9dc0e6d0f455fe5d0fb93b726c6a3a134a
parent0134f492c9fcb4ecf079a9dad93db1f8c70ced79 (diff)
downloadaup-25af3db3643c7a4e961b172104ca289f5923e83a.tar.gz
aup-25af3db3643c7a4e961b172104ca289f5923e83a.zip
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.
-rw-r--r--compiler/compile.c43
-rw-r--r--docs/design/002-dsl-direction.md5
-rw-r--r--tests/check.py8
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'