aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/design/002-dsl-direction.md3
-rw-r--r--src/compile.c28
-rw-r--r--tests/check.py12
3 files changed, 28 insertions, 15 deletions
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index 7924083..2ba217a 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -165,6 +165,9 @@ view best = max(p.score for p in players where p.active)
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 조건])`. 값을 둘 받는
`max(a, b)`·`min(a, b)`는 그대로 호출이고, 같은 이름의 fn(`fn sum(a, b)`)도 그대로 부를 수 있다. 첫 식 뒤의
`for`, 또는 `count(`의 `이름 in`으로 가른다.
diff --git a/src/compile.c b/src/compile.c
index 2eac523..8a28384 100644
--- a/src/compile.c
+++ b/src/compile.c
@@ -18,7 +18,7 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR
TEXT_SET, TEXT_FROM, BYTES_FROM, CUR_COUNT, LIST_COUNT, LIST_APPEND, LIST_INSERT, LIST_REMOVE,
ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW,
PICK, SLIDE, FOLD };
-enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN };
+enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST };
enum { KIND_KEY = 1, KIND_MESSAGE = 2, KIND_TEXT = 3, FIELD_U32 = 1, FIELD_BYTES = 2 };
enum { STATE_NUM = 0, STATE_TEXT = 1, STATE_CURSOR = 2, STATE_LIST = 3, STATE_INDEX = 4, UNIT_CHAR = 0, UNIT_LINE = 1 };
typedef enum { NUM = 1, BYTES, TEXT, CURSOR, UNIT, INDEX } Type;
@@ -47,7 +47,7 @@ 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 (no value), sum, max, min */
+ E_FOLD, /* op(args[1] for name in member where args[0]): count, first (no value), sum, max, min */
} ExprKind;
typedef struct Expr {
ExprKind kind;
@@ -638,24 +638,25 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) {
{{"min", 2, MIN}, {"max", 2, MAX}, {"clamp", 3, CLAMP}, {"text", 1, TEXT_FROM}, {"bytes", 1, BYTES_FROM}};
int call = -1, how = -1;
for (int i = 0; i < 5; ++i) if (!strcmp(token.text, calls[i].name)) call = i;
- static const char *const folds[] = {"count", "sum", "max", "min"};
- for (int i = 0; i < 4; ++i) if (!strcmp(token.text, folds[i])) how = 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;
Expr *first = NULL;
- /* count(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 ...):
+ * folds; max(a, b) stays a call. */
+ bool itemwise = how == FOLD_COUNT || how == FOLD_FIRST;
if (how >= 0 && is(c, "(")) {
take(c);
newlines(c);
- if (how == FOLD_COUNT && c->token.kind == T_NAME && lookahead(c)->kind == T_NAME &&
- !strcmp(lookahead(c)->text, "in")) {
- left = fold(c, &token, how, NULL);
+ if (itemwise && c->token.kind == T_NAME && lookahead(c)->kind == T_NAME && !strcmp(lookahead(c)->text, "in")) {
+ left = postfix(c, fold(c, &token, how, NULL)); /* first(...).title */
goto parsed;
}
- if (how != FOLD_COUNT) {
+ if (!itemwise) {
first = expression(c, 0, true);
newlines(c);
if (word(c, "for")) {
take(c);
- left = fold(c, &token, how, first);
+ left = postfix(c, fold(c, &token, how, first));
goto parsed;
}
}
@@ -1665,18 +1666,19 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
c->fold_scope = c->scope;
memcpy(c->fold_name, e->name, NAME);
c->joins = 0;
- if (e->op != FOLD_COUNT) {
+ bool itemwise = e->op == FOLD_COUNT || e->op == FOLD_FIRST;
+ 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));
}
- if (e->args[0]) number(c, e->args[0], false, e->op != FOLD_COUNT);
+ if (e->args[0]) number(c, e->args[0], false, !itemwise);
else emit(c, e->line, e->column, CONST, 0, 1);
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;
- return (Kind){NUM, -1};
+ return e->op == FOLD_FIRST ? (Kind){INDEX, list} : (Kind){NUM, -1};
}
case E_RECORD: case E_FIELD:
fail_at(c, e->line, e->column, "a record value goes into a list, as in list.append(%s { ... })", e->name);
diff --git a/tests/check.py b/tests/check.py
index 3716ff6..ec8e9de 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -177,6 +177,7 @@ DIAGNOSTICS = [
(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 v = first(x in xs)\n', '6:10', 'a view is a number or text, 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"),
]
@@ -975,10 +976,12 @@ input toggle = key Space
input up = key ArrowUp
input down = key ArrowDown
input drop = key Delete
+input jump = key KeyJ
on add {
tasks.append(Task { title: add, points: add.chars })
at = tasks.last
}
+on jump.down { at = first(t in tasks where !t.done) }
on toggle.down { at.done = !at.done }
on up.down { at = at - 1 }
on down.down { at = at + 1 }
@@ -992,6 +995,8 @@ 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 first_open = first(t in tasks where !t.done).position
+view at_now = at.position
view open_list = each t in tasks where !t.done {
label = t.title
item = t.position
@@ -1019,9 +1024,11 @@ def folds(aup, pure_run, work, seeds, steps=300):
tasks.append({'title': word, 'done': 0, 'points': len(word)})
at = len(tasks) - 1
else:
- name = rng.choice(['toggle', 'up', 'down', 'drop'])
+ name = rng.choice(['toggle', 'up', 'down', 'drop', 'jump'])
commands.append(f'down {name}')
- if name == 'up': at = max(at - 1, 0)
+ open_now = [i for i, t in enumerate(tasks) if not t['done']]
+ if name == 'jump' and open_now: at = open_now[0] # none open: the event fails
+ elif name == 'up': at = max(at - 1, 0)
elif name == 'down': at = min(at + 1, max(len(tasks) - 1, 0))
elif tasks and name == 'toggle': tasks[at]['done'] = int(not tasks[at]['done'])
elif tasks and name == 'drop':
@@ -1033,6 +1040,7 @@ def folds(aup, pure_run, work, seeds, steps=300):
'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)}',
+ 'first_open': next((i for i, t in enumerate(tasks) if not t['done']), None), 'at_now': at,
'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()