aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 11:30:17 +0100
committereunhhu <[email protected]>2026-09-26 11:30:17 +0100
commitb5cb79948d52fe9dd918a10a446dcf183c35d10d (patch)
tree68120df2b5287c8eec8ca03f1c738a6aac963652
parentdb28e3401e5a137845fdb1661e1af6297477b0c8 (diff)
downloadaup-b5cb79948d52fe9dd918a10a446dcf183c35d10d.tar.gz
aup-b5cb79948d52fe9dd918a10a446dcf183c35d10d.zip
Filter a table view with where
view open_list = each t in tasks where !t.done { ... } has a row for each item the condition keeps, in the list's order; the condition compiles to the table's filter block, generated like a column so it reads the row's item, and the view entry names the list in the byte after its type. A count, sum, max or min inside the condition is a compile error, as Pure refuses a fold in a filter. The random model check now compares a filtered table too; two diagnostics join.
-rw-r--r--docs/design/002-dsl-direction.md4
-rw-r--r--src/compile.c24
-rw-r--r--tests/check.py17
3 files changed, 38 insertions, 7 deletions
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index 9d695f9..7924083 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -147,6 +147,10 @@ view board = each task in tasks { # 항목마다 한 행
- **표 보기.** `view 이름 = each 행 in 리스트 { 열 = 값 ... }`는 리스트 항목마다 행 하나를 낸다. 열은 수나 text이고
행 이름(인덱스)과 다른 상태를 읽는다. 호스트는 보여 줄 행의 칸만 Pure에 물어 계산한다. 목록을 화면 모델로 복사하거나
보이는 범위를 앱이 계산하는 코드(가상 스크롤)가 필요 없다. 열 이름은 표 안에서만 유일하면 된다.
+- **걸러진 표.** `view open_list = each t in tasks where !t.done { label = t.title }`는 조건이 참인 항목마다 행
+ 하나를 낸다(리스트 순서 그대로). 열의 행 이름은 여전히 항목이라 `t.position`은 리스트 안의 위치다. Pure가 상태가
+ 바뀐 뒤 한 번 걸러 행과 항목의 대응을 두므로, 열린 일·끝난 일처럼 같은 리스트를 여러 목록으로 보여도 상태를
+ 둘로 나눠 맞출 필요가 없다. 조건 안에는 집계를 둘 수 없다.
- 목록·표·포커스·톤 같은 인터페이스 어휘는 여전히 aup에 없다. Vega가 이 투영 위에서 제공한다.
검증: 표 열과 조건 보기를 더한 할 일 목록이 참조 모델과 4,800단계에서 같고, 고르지 않은 가지가 계산되지 않음을 확인한다.
diff --git a/src/compile.c b/src/compile.c
index 0c90e18..2eac523 100644
--- a/src/compile.c
+++ b/src/compile.c
@@ -134,6 +134,8 @@ typedef struct {
/* Views in order; a table (no value) is followed by its columns. */
char view_names[VIEWS][NAME], view_row[VIEWS][NAME], view_list[VIEWS][NAME];
Expr *view_value[VIEWS];
+ Expr *view_filter[VIEWS]; /* a table's where condition, or NULL */
+ unsigned char view_byte[VIEWS]; /* a filtered table's list + 1 */
int view_table[VIEWS]; /* a column's table + 1, or 0 */
int view_line[VIEWS][2], view_column[VIEWS][2]; /* a table's row name and list name */
unsigned views;
@@ -144,7 +146,7 @@ typedef struct {
int row; /* the table whose column is being generated, or -1 */
/* The fold whose block is being generated: its item's name and list, and
* the fn scope it began in (whose parameters its block cannot read). */
- bool folding;
+ bool folding, filtering;
char fold_name[NAME];
int fold_list;
Scope *fold_scope;
@@ -1097,7 +1099,8 @@ static void state_declaration(Compiler *c) {
++c->states;
}
-/* view name = each row in list { column = value ... }: one row per item. */
+/* view name = each row in list [where condition] { column = value ... }: one
+ * row per item, or per item the condition keeps. */
static void table_declaration(Compiler *c, unsigned table) {
take(c); /* each */
Token row = expect_name(c, "a name for each item");
@@ -1110,6 +1113,10 @@ static void table_declaration(Compiler *c, unsigned table) {
c->view_column[table][0] = row.column;
c->view_line[table][1] = list.line;
c->view_column[table][1] = list.column;
+ if (word(c, "where")) { /* a row only for the items it keeps */
+ take(c);
+ c->view_filter[table] = expression(c, 0, false);
+ }
expect(c, "{", "'{' on the line of the table");
separators(c);
while (!is(c, "}")) {
@@ -1650,6 +1657,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
int list = state_index(c, e->member);
if (list < 0 || c->state_types[list] != STATE_LIST) fail_at(c, e->line, e->column, "'%s' is not a list", e->member);
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");
unsigned at = c->code_count, joins = c->joins;
emit(c, e->line, e->column, FOLD, 0, 0);
c->folding = true;
@@ -1920,6 +1928,16 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
c->view_types[i] = 2;
c->view_code[i][0] = (uint32_t)list;
c->view_code[i][1] = 0;
+ if (c->view_filter[i]) {
+ /* The filter runs as a column of the list: it reads the table's row. */
+ c->row = (int)i;
+ c->filtering = true;
+ c->view_code[i][0] = c->code_count;
+ number(c, c->view_filter[i], false, 0);
+ c->view_code[i][1] = c->code_count - c->view_code[i][0];
+ c->view_byte[i] = (unsigned char)(list + 1);
+ c->filtering = false;
+ }
continue;
}
c->row = c->view_table[i] - 1;
@@ -2021,7 +2039,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
for (unsigned i = 0; i < c->views; ++i) {
p = put32(p, view_name[i]);
*p++ = (unsigned char)c->view_types[i];
- *p++ = 0;
+ *p++ = c->view_byte[i];
p = put16(p, (uint32_t)c->view_table[i]);
p = put32(p, c->view_code[i][0]);
p = put32(p, c->view_code[i][1]);
diff --git a/tests/check.py b/tests/check.py
index ef8549f..3716ff6 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -176,6 +176,9 @@ DIAGNOSTICS = [
"'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'),
(LIST + 'view v = sum(x.a for x xs)\n', '6:24', "expected 'in' and a list, as in sum(x in tasks)"),
+ (LIST + 'view w = each r in xs where r {\n n = r.a\n}\n', '6:29', 'expected a number, not an index'),
+ (LIST + 'view w = each r in xs where count(y in xs) > 0 {\n n = r.a\n}\n', '6:29',
+ "a table's where cannot hold a count, sum, max or min"),
]
ACCEPTED = [
@@ -989,13 +992,18 @@ view done_points = sum(
view longest = max(t.title.chars for t in tasks)
view fewest_open = min(t.points for t in tasks where !t.done)
view summary = "{count(t in tasks where !t.done)} open of {tasks.count}"
+view open_list = each t in tasks where !t.done {
+ label = t.title
+ item = t.position
+}
"""
def folds(aup, pure_run, work, seeds, steps=300):
- """count, sum, max and min over a list against a Python model after
- random adds, toggles, moves and removals (an empty max or min has no
- value)."""
+ """count, sum, max and min over a list, and a table filtered by a where,
+ against a Python model after random adds, toggles, moves and removals (an
+ empty max or min has no value; a filtered table's rows keep the list's
+ order and its columns read the item)."""
source = work / 'folds.aup'
source.write_text(FOLDS_SOURCE)
compile_module(aup, source, work / 'folds.pmod')
@@ -1024,7 +1032,8 @@ def folds(aup, pure_run, work, seeds, steps=300):
'done_points': sum(t['points'] for t in tasks if t['done']),
'longest': max((len(t['title']) for t in tasks), default=None),
'fewest_open': min((t['points'] for t in open_tasks), default=None),
- 'summary': f'{len(open_tasks)} open of {len(tasks)}'})
+ 'summary': f'{len(open_tasks)} open of {len(tasks)}',
+ 'open_list': [{'label': t['title'], 'item': i} for i, t in enumerate(tasks) if not t['done']]})
# Toggling or removing in an empty list fails; pure-run then exits 1 but shows every event.
lines = run(pure_run, work / 'folds.pmod', stdin='\n'.join(commands) + '\n', check=False).stdout.splitlines()
shown = [json.loads(line)['views'] for line in lines]