diff options
| author | eunhhu <[email protected]> | 2026-09-28 05:14:16 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 05:14:16 +0100 |
| commit | 0134f492c9fcb4ecf079a9dad93db1f8c70ced79 (patch) | |
| tree | c7f7190df538ddcb367999140c5c326f9d05d28f | |
| parent | 2ed0e467398cc73cedb9516efc7655c9a74cbde5 (diff) | |
| download | aup-0134f492c9fcb4ecf079a9dad93db1f8c70ced79.tar.gz aup-0134f492c9fcb4ecf079a9dad93db1f8c70ced79.zip | |
Count distinct values
distinct(value for item in list [where condition]) counts the different
values the included items give, compiled to Pure's fold of kind 5 (SQL's
count(distinct ...)). An accepted app walks and folds a keyed list in a
range of its key (where l.id >= start && l.id < start + 16), which Pure
reads by key.
| -rw-r--r-- | compiler/compile.c | 14 | ||||
| -rw-r--r-- | docs/design/002-dsl-direction.md | 5 | ||||
| -rw-r--r-- | tests/check.py | 8 |
3 files changed, 19 insertions, 8 deletions
diff --git a/compiler/compile.c b/compiler/compile.c index ececec9..ee83c8b 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -20,7 +20,7 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR PICK, SLIDE, FOLD, TEXT_COMPARE, LIST_FIND, LIST_HAS, NOW, FLOOR, MOD, SEND, OUTER_ROW, EACH, LOCAL_LOAD, LOCAL_STORE }; enum { LOCALS = 64 }; /* an event's locals (Pure's) */ -enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST }; +enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST, FOLD_DISTINCT }; enum { KIND_KEY = 1, KIND_MESSAGE = 2, KIND_TEXT = 3, KIND_TIMER = 4, KIND_OUTPUT = 5, KIND_ARGUMENTS = 6, FIELD_U32 = 1, FIELD_BYTES = 2, FIELD_NUMBER = 3, FIELD_TEXT = 4, FIELD_ROWS = 5 }; @@ -682,11 +682,12 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { {"floor", 1, FLOOR}}; int call = -1, how = -1; for (int i = 0; i < (int)(sizeof(calls) / sizeof(*calls)); ++i) if (!strcmp(token.text, calls[i].name)) call = i; - static const char *const folds[] = {"count", "sum", "max", "min", "first"}; - for (int i = 0; i < 5; ++i) if (!strcmp(token.text, folds[i])) how = i; + static const char *const folds[] = {"count", "sum", "max", "min", "first", "distinct"}; + for (int i = 0; i < 6; ++i) if (!strcmp(token.text, folds[i])) how = i; Expr *first = NULL; - /* count(t in tasks ...), first(t in tasks ...), sum(t.x for t in tasks ...): - * folds; max(a, b) stays a call. */ + /* count(t in tasks ...), first(t in tasks ...), sum(t.x for t in tasks ...), + * distinct(t.x for t in tasks ...) (how many different values): folds; + * max(a, b) stays a call. */ bool itemwise = how == FOLD_COUNT || how == FOLD_FIRST; if (how >= 0 && is(c, "(")) { take(c); @@ -2049,7 +2050,8 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { if (!itemwise) { Kind kind = value(c, e->args[1], false, 0); if (kind.type != NUM) - fail_at(c, e->args[1]->line, e->args[1]->column, "sum, max and min take numbers, not %s", type_name(kind.type)); + fail_at(c, e->args[1]->line, e->args[1]->column, "sum, max, min and distinct take numbers, not %s", + type_name(kind.type)); } if (e->args[0]) number(c, e->args[0], false, !itemwise); else emit(c, e->line, e->column, CONST, 0, 1); diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index 8b252b3..c4aaddf 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -169,7 +169,8 @@ view summary = "{count(t in tasks where !t.done)} open of {tasks.count}" - **찾기.** `first(항목 in 리스트 [where 조건])`은 조건이 참인 첫 항목의 인덱스다(그 리스트의 인덱스 타입이라 `at = first(t in tasks where t.title == name)`, `first(…).done = 1`, `first(…).position`으로 쓴다). 없으면 보기는 비고 핸들러는 `not_found`로 실패한다. 찾으면 거기서 멈춘다. -- **모양.** `count(항목 in 리스트 [where 조건])`, `sum|max|min(값 for 항목 in 리스트 [where 조건])`. 값을 둘 받는 +- **모양.** `count(항목 in 리스트 [where 조건])`, `sum|max|min|distinct(값 for 항목 in 리스트 [where 조건])`. `distinct`는 + 포함된 항목의 서로 다른 값의 수다(SQL의 `count(distinct …)`). 값을 둘 받는 `max(a, b)`·`min(a, b)`는 그대로 호출이고, 같은 이름의 fn(`fn sum(a, b)`)도 그대로 부를 수 있다. 첫 식 뒤의 `for`, 또는 `count(`의 `이름 in`으로 가른다. - **의미.** 항목마다 블록을 계산해 센다(조건이 참인 항목), 더한다, 가장 큰·작은 값을 고른다. 포함된 항목이 없으면 @@ -328,6 +329,8 @@ on clean { 상태가 되고, 이벤트가 끝나면 비워지며 저장되지 않는다(Pure 008). HTTP로는 JSON 객체의 배열로 보내고, 행 필드 하나뿐인 메시지는 행을 리스트의 저장 형식 바이트로 보내도 된다(대량 넣기). 출력은 행을 보내지 않는다. - 이것으로 여러 행을 한꺼번에 바꾸는 트랜잭션(TPC-C의 주문처럼 행마다 재고를 줄이고 주문 행을 넣는 일)이 이벤트 하나다. +- 키 있는 리스트의 키를 범위로 묻는 `where`(`for l in lines where l.id >= start && l.id < start + 16`)와 집계, 표의 필터는 Pure가 + 그 범위의 항목만 읽는다(키로 찾아서). 인덱스를 선언하지 않는다. 검증: 진단 9건(`for` 안의 `for`, 걷는 리스트에 넣기, `for` 안의 `reply`, 행을 보내는 출력, 핸들러 밖의 행, 값으로 쓴 행, 리스트가 아닌 것, 이미 선언된 항목 이름, 없는 레코드)과, 그룹 안의 행·`where`·걷는 항목을 읽는 집계·지우는 걷기를 쓴 앱을 Pure가 diff --git a/tests/check.py b/tests/check.py index a393270..9b10f02 100644 --- a/tests/check.py +++ b/tests/check.py @@ -202,7 +202,7 @@ DIAGNOSTICS = [ (LIST + 'view v = count(x in xs where count(y in xs) > 0)\n', '6:30', 'a count, sum, max or min cannot hold another'), (LIST + 'view v = count(x in at)\n', '6:10', "'at' is not a list"), ('record T {\n t: text\n}\nstate xs: list of T\nview v = sum(x.t for x in xs)\n', '5:14', - 'sum, max and min take numbers, not text'), + 'sum, max, min and distinct take numbers, not text'), (LIST + 'fn f(n) = count(x in xs where x.a == n)\nview v = f(1)\n', '6:38', "'n' is a parameter outside this count, sum, max or min"), (LIST + 'view v = sum(dt for x in xs)\n', '6:14', 'dt is not available in count, sum, max or min'), @@ -273,6 +273,12 @@ DIAGNOSTICS = [ ] ACCEPTED = [ + # distinct, and tests bounding a keyed list's key: a range walk (Pure reads only the items in bounds) and a range view. + 'record Line {\n id = 0\n item = 0\n qty = 0\n done = 0\n}\nstate lines: list of Line by id\n' + 'input deliver = message {\n order: u32\n}\non deliver {\n let start = deliver.order * 16\n' + ' 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', # 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' |
