aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 11:35:20 +0100
committereunhhu <[email protected]>2026-09-26 11:35:20 +0100
commit92f5545efb7aab0b3a4a4c73a65364ef519cd5d4 (patch)
tree10633f366478737d6bb25286ac074e7f4ded7b20
parent6ca1a1e598b9aa2f0b8f3947c27883b45b366346 (diff)
downloadpure-92f5545efb7aab0b3a4a4c73a65364ef519cd5d4.tar.gz
pure-92f5545efb7aab0b3a4a4c73a65364ef519cd5d4.zip
Add first: the index of the first item a block keeps
A fifth fold kind, first, runs its block over the items until one leaves a non-zero number and pushes that item's index, typed as an index of the list, or fails not_found when none does. Handlers can so find the item to change by a condition, as a message naming a task by its title would.
-rw-r--r--docs/design/008-module-format.md6
-rw-r--r--prototypes/connections/module.c17
-rw-r--r--prototypes/connections/module.h5
-rw-r--r--prototypes/connections/test-module.c8
4 files changed, 26 insertions, 10 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 7ced5e7..ab42542 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -96,7 +96,7 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마�
| 62 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 |
| 63 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 |
| 64 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 |
-| 65 | FOLD | → n | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 남긴 수가 0이 아닌 항목을 세고, sum·max·min은 블록이 남긴 값과 포함 여부(0이 아니면 포함)로 계산한다. 포함된 항목이 없으면 count·sum은 0, max·min은 `not_found` |
+| 65 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 남긴 수가 0이 아닌 항목을 세고, sum·max·min은 블록이 남긴 값과 포함 여부(0이 아니면 포함)로 계산한다. first는 블록이 0이 아닌 수를 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` |
`f`는 레코드 필드의 타입(수 또는 text)이다. 수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든
핸들러가 한 일이 취소된다.
@@ -119,8 +119,8 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마�
합류 지점에는 모든 경로가 같은 스택(높이, 타입, 커서·인덱스의 대상)을 가져와야 한다. 그래서 조건식처럼 값이
합류를 건너가도 타입이 섞이지 않는다. 앞에 남은, 값을 가진 합류는 한 번에 8개까지다. 모든 명령에 도달할 수
있어야 하고, 핸들러는 값을 남기지 않으며 보기와 열은 자기 타입의 값 하나를 남긴다.
-- 집계(FOLD)의 블록은 그 리스트의 열로 따로 검증한다. 읽기만 하고(효과는 거절), count는 수 하나, sum·max·min은 수 둘을
- 남긴다. 블록 안에 다시 집계를 둘 수 없다(비용 상한). 둘레의 블록은 FOLD와 그 블록을 명령 하나로 보고, 앞의 분기가
+- 집계(FOLD)의 블록은 그 리스트의 열로 따로 검증한다. 읽기만 하고(효과는 거절), count·first는 수 하나, sum·max·min은
+ 수 둘을 남긴다. first의 결과는 그 리스트의 인덱스 타입이다. 블록 안에 다시 집계를 둘 수 없다(비용 상한). 둘레의 블록은 FOLD와 그 블록을 명령 하나로 보고, 앞의 분기가
블록 안으로 들어오지 못한다. 블록은 둘레 블록 안에 끝나야 한다.
- 걸러진 표의 필터 블록은 그 리스트의 열로 검증한다. 읽기만 하고 수 하나를 남기며, 안에 집계를 둘 수 없다.
타입 다음 바이트는 표에만 쓸 수 있다.
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index ffdcaf7..d616b9f 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -173,7 +173,7 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg
case A_CONSTANT: return arg < m->constants;
case A_UNIT: return arg <= PURE_UNIT_LINE;
case A_ROW: return (int)arg == row;
- case A_FOLD: return (arg & 0xFF) <= PURE_FOLD_MIN && state_of(m, arg >> 8 & 0xFF, PURE_STATE_LIST) && arg >> 16;
+ case A_FOLD: return (arg & 0xFF) <= PURE_FOLD_FIRST && state_of(m, arg >> 8 & 0xFF, PURE_STATE_LIST) && arg >> 16;
case A_NONE: case A_TARGET: case A_DEPTH: return true;
default: return state_of(m, arg, (uint8_t)(kind - A_STATE));
}
@@ -266,7 +266,9 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
skip = arg >> 16;
if (view >= FOLD_ONE) REFUSE(view == FILTER ? "fold inside a filter" : "fold inside a fold");
if (skip > block.length - i - 1) REFUSE("fold block outside its block");
- const char *why = verify_block(m, (PureBlock){pc + 1, skip}, (arg & 0xFF) == PURE_FOLD_COUNT ? FOLD_ONE : FOLD_TWO,
+ unsigned how = arg & 0xFF;
+ const char *why = verify_block(m, (PureBlock){pc + 1, skip},
+ how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : FOLD_TWO,
(int)(arg >> 8 & 0xFF), 0, &inner);
if (why) { *at = inner; return why; }
for (uint32_t k = i + 1; k <= i + skip; ++k) {
@@ -296,6 +298,11 @@ PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block,
REFUSE(type == T_CURSOR ? "cursor of another text" : "index of another list");
}
unsigned char out = signature[op->op].out;
+ if (kind == A_FOLD && (arg & 0xFF) == PURE_FOLD_FIRST) { /* an index of its list */
+ out = T_INDEX;
+ of = O_ARG;
+ owner = arg >> 8 & 0xFF;
+ }
if (out == T_FIELD) out = m->field[shape->field_start + arg].type == PURE_FIELD_U32 ? T_NUM : T_BYTES;
if (out == T_ITEM) out = field_type(m, list_of(arg), item_field(arg));
if (out) {
@@ -959,14 +966,16 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even
arena->used = mark;
if (status != PURE_OK) return status;
double value = out[0].num;
- if (how == PURE_FOLD_COUNT ? value == 0 : out[1].num == 0) continue;
+ if (how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? value == 0 : out[1].num == 0) continue;
+ if (how == PURE_FOLD_FIRST) { total = (double)row; any = true; break; }
total = how == PURE_FOLD_COUNT ? total + 1 : how == PURE_FOLD_SUM ? total + value :
!any ? value : how == PURE_FOLD_MAX ? maximum(total, value) : minimum(total, value);
any = true;
}
if (!any && how >= PURE_FOLD_MAX) { kind = PURE_FAULT_NOT_FOUND; goto failed; }
if (!isfinite(total)) { kind = PURE_FAULT_NON_FINITE; goto failed; }
- stack[sp++].num = total;
+ stack[sp].num = total;
+ stack[sp++].bytes.owner = (uint16_t)(arg >> 8 & 0xFF); /* first: the list the index is in */
continue;
}
case PURE_OP_PICK: stack[sp] = stack[sp - 1 - arg]; ++sp; continue;
diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h
index b57e11f..4264710 100644
--- a/prototypes/connections/module.h
+++ b/prototypes/connections/module.h
@@ -43,8 +43,9 @@ typedef enum {
/* FOLD's argument: the kind (bits 0-7), the list state (8-15) and the length
* of the block that follows (16-31), run once per item as a column of that
* list. count counts the items whose block leaves a non-zero number; sum,
- * max and min take a value and an include flag from their block. */
-typedef enum { PURE_FOLD_COUNT, PURE_FOLD_SUM, PURE_FOLD_MAX, PURE_FOLD_MIN } PureFold;
+ * max and min take a value and an include flag from their block; first gives
+ * the index of the first item whose block leaves a non-zero number. */
+typedef enum { PURE_FOLD_COUNT, PURE_FOLD_SUM, PURE_FOLD_MAX, PURE_FOLD_MIN, PURE_FOLD_FIRST } PureFold;
typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3 } PureInputKind;
typedef enum { PURE_FIELD_U32 = 1, PURE_FIELD_BYTES = 2 } PureFieldType;
diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c
index 6b11c3f..07a0cbb 100644
--- a/prototypes/connections/test-module.c
+++ b/prototypes/connections/test-module.c
@@ -1779,10 +1779,12 @@ static const O fold_code[] = {
{FOLD, FOLDING(PURE_FOLD_MIN, 0, 5), 0}, {ROW, 0, 0}, {IG, F_POINTS, 0}, {ROW, 0, 0}, /* 30 fewest_open */
{IG, F_DONE, 0}, {NOT, 0, 0},
{C, 0, 10}, {FOLD, FOLDING(PURE_FOLD_COUNT, 0, 1), 0}, {C, 0, 1}, {ADD, 0, 0}, /* 36 count_plus */
+ {FOLD, FOLDING(PURE_FOLD_FIRST, 0, 3), 0}, {ROW, 0, 0}, {IG, F_DONE, 0}, {NOT, 0, 0}, /* 40 first_open */
+ {XN, 0, 0},
};
static const H fold_handlers[] = {{1, 0, 7}, {3, 7, 5}};
static const V fold_views[] = {{"open", 12, 4}, {"points", 16, 4}, {"longest", 20, 5}, {"done_points", 25, 5},
- {"fewest_open", 30, 6}, {"count_plus", 36, 4}};
+ {"fewest_open", 30, 6}, {"count_plus", 36, 4}, {"first_open", 40, 5}};
static Spec fold_spec(const O *code, size_t count, const V *views, size_t nviews, const H *handlers, size_t nhandlers) {
return (Spec){NULL, 0, fold_inputs, LEN(fold_inputs), views, nviews, handlers, nhandlers, code, count, NULL, 0,
fold_states, 2, fold_constants, 1, NULL, 0, fold_records, 1, NULL, 0};
@@ -1795,6 +1797,7 @@ static void folds(void) {
CHECK(pure_app_start(&app, &module_a, (PureData){0}) == PURE_OK);
CHECK(number_view(&app, 0) == 0 && number_view(&app, 1) == 0 && !pure_app_view(&app, 2, &value));
CHECK(number_view(&app, 3) == 0 && !pure_app_view(&app, 4, &value) && number_view(&app, 5) == 10);
+ CHECK(!pure_app_view(&app, 6, &value)); /* no first open task yet */
const char *const words[] = {"a", "bbb", "\xea\xb0\x80\xea\xb0\x80"}; /* one, three and two characters */
for (size_t i = 0; i < 3; ++i)
CHECK(pure_app_text(&app, 0, (PureSpan){(const unsigned char *)words[i], strlen(words[i])}) == PURE_OK);
@@ -1802,8 +1805,11 @@ static void folds(void) {
CHECK(number_view(&app, 3) == 0 && number_view(&app, 4) == 1 && number_view(&app, 5) == 13);
CHECK(pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK); /* the last task is done */
CHECK(number_view(&app, 0) == 2 && number_view(&app, 3) == 2 && number_view(&app, 4) == 1);
+ CHECK(number_view(&app, 6) == 0); /* the first open one: an index */
CHECK(pure_app_point(&app, 1, 0) == PURE_OK && pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(number_view(&app, 6) == 1);
CHECK(pure_app_point(&app, 1, 1) == PURE_OK && pure_app_input(&app, 1, PURE_EDGE_DOWN) == PURE_OK);
+ CHECK(!pure_app_view(&app, 6, &value));
CHECK(number_view(&app, 0) == 0 && number_view(&app, 3) == 6 && !pure_app_view(&app, 4, &value)); /* none open */
pure_app_stop(&app);