aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compiler/compile.c14
-rw-r--r--docs/design/002-dsl-direction.md5
-rw-r--r--tests/check.py8
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'