diff options
| author | eunhhu <[email protected]> | 2026-10-04 13:58:19 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-04 13:58:19 +0100 |
| commit | 7340ba9af757b6f135dff9c51a193ba527f061b3 (patch) | |
| tree | 955bb163aa584d52cf402fb123c901ec2271939c | |
| parent | fafed7edaf36ba18c200d395bd229d1f071cd409 (diff) | |
| download | aup-7340ba9af757b6f135dff9c51a193ba527f061b3.tar.gz aup-7340ba9af757b6f135dff9c51a193ba527f061b3.zip | |
Count with for i in a .. b, break, nest fors and let an event's own lists (Pure 018)
| -rw-r--r-- | compiler/compile.c | 276 | ||||
| -rw-r--r-- | docs/design/001-language-v0.md | 6 | ||||
| -rw-r--r-- | docs/design/002-dsl-direction.md | 46 | ||||
| -rw-r--r-- | examples/numbers.aup | 62 | ||||
| -rw-r--r-- | examples/routes.aup | 120 | ||||
| -rw-r--r-- | tests/check.py | 119 |
6 files changed, 592 insertions, 37 deletions
diff --git a/compiler/compile.c b/compiler/compile.c index 2d66286..25a761b 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -20,13 +20,15 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW, PICK, SLIDE, FOLD, TEXT_COMPARE, LIST_FIND, LIST_HAS, NOW, FLOOR, MOD, SEND, OUTER_ROW, EACH, LOCAL_LOAD, LOCAL_STORE, I64_ADD, I64_SUB, I64_MUL, I64_DIV, I64_MOD, I64_NEG, I64_COMPARE, I64_MIN, I64_MAX, - I64_FROM, NUM_FROM }; + I64_FROM, NUM_FROM, LOOP, BREAK }; enum { LOCALS = 64 }; /* an event's locals (Pure's) */ +enum { NESTING = 8, LOOP_INTEGER = 256 }; /* loops (for) nest 8 deep; an i64's count (Pure 018) */ enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST, FOLD_DISTINCT, FOLD_INTEGER = 0x40 }; 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, FIELD_BOOL = 6, FIELD_I64 = 7 }; -/* A list of a message's rows: filled as the message arrives, emptied when its event ends (Pure's flag). */ +/* An event's list: a message's rows, filled as the message arrives, or a + * let's list; emptied when its event ends (Pure's flag). */ enum { LIST_ROWS = 256 }; enum { STATE_TRANSIENT = 256 }; enum { STATE_NUM = 0, STATE_TEXT = 1, STATE_CURSOR = 2, STATE_LIST = 3, STATE_INDEX = 4, STATE_BOOL = 5, STATE_I64 = 6, @@ -78,15 +80,19 @@ typedef struct Expr { } Expr; typedef enum { S_ASSIGN, S_IF, S_REPLACE, S_REPLY, S_REQUIRE, S_INSERT, S_REMOVE, S_APPEND, S_ADD, S_SET, S_SEND, S_FOR, - S_LET } StmtKind; + S_LET, S_COUNT, S_BREAK } StmtKind; typedef struct Stmt { StmtKind kind; int line, column; char name[NAME], member[NAME]; /* member: the item field an S_SET changes; S_FOR: name is the item, member the list */ Expr *value; /* assigned value, condition, reply, requirement or insertion */ Expr *first, *last; /* a range, the insertion cursor or index, or the item index; last may be NULL */ - Expr *low, *high; /* S_FOR: a slice of the list's keys, or NULL */ + Expr *low, *high; /* S_FOR: a slice of the list's keys, or NULL; S_COUNT: its range */ /* S_SET: name is the list, or empty when first is an index naming the item */ + /* S_LET of a list (listed): member is its record (of library), key the field it is keyed by or empty. */ + bool listed; + int library, key_line, key_column; + char key[NAME]; struct Stmt *then, *otherwise, *next; } Stmt; @@ -154,6 +160,7 @@ typedef struct { bool state_stays[STATES]; /* a mark: a cursor that stays before text inserted at it */ bool state_transient[STATES]; /* held while the app runs, never stored (Pure 018) */ int state_rows[STATES]; /* a list of a message's rows: its input + 1, or 0 */ + bool state_event[STATES]; /* a let's list (let.NAME): its handlers' alone, found only by its let */ int state_library[STATES]; /* a list's record's library (LIBRARY.NAME), or 0: the app's */ /* A keyed list: the field named after by, and once resolved, that field + 1. */ char state_key[STATES][NAME]; @@ -191,14 +198,15 @@ typedef struct { char fold_name[NAME]; int fold_list; Scope *fold_scope; - /* A handler's locals in scope (let), innermost last, and the slots it used. */ - struct { char name[NAME]; unsigned slot; Kind kind; } locals[LOCALS]; + /* A handler's locals in scope (let), innermost last, and the slots it used. A let's list (list: its + * state) has no slot; a for's count (counter) is read, never assigned. */ + struct { char name[NAME]; unsigned slot; Kind kind; int list; bool counter; } locals[LOCALS]; unsigned local_count, local_slots; - /* The for whose block is being generated: its item's name and list. */ - bool walking; - char walk_name[NAME]; - int walk_list; - Scope *walk_scope; + /* The fors whose blocks are being generated, innermost last: the item's or count's name, the list a walk + * walks (-1: a count), and the index local an outer walk's item is kept in for the fors inside it (-1: none). */ + struct { char name[NAME]; int list, item; Scope *scope; } loops[NESTING]; + unsigned loop_count; + unsigned nested; /* blocks within the handler's own (an if's, a for's) */ /* Libraries: module 0 is the main source; each module lists the libraries it uses. */ struct { char name[NAME], path[AUP_PATH]; bool uses[MODULES], parsing; } modules[MODULES]; unsigned module_count; @@ -224,7 +232,7 @@ static const char *const reserved[] = {"state", "input", "view", "on", "if", "el "dt", "min", "max", "clamp", "message", "by", "bytes", "u32", "reply", "require", "record", "each", "fn", "use", "now", "every", "floor", "output", "send", "for", "let", "true", "false", - "transient"}; + "transient", "break"}; /* An error in the module being parsed or inlined; inside a fn it names the call. */ static _Noreturn void fail_at(Compiler *c, int line, int column, const char *format, ...) { @@ -993,10 +1001,31 @@ static Stmt *statement(Compiler *c, unsigned depth) { memcpy(s->name, name.text, NAME); s->line = name.line; s->column = name.column; + if (is(c, ":")) { /* let name: list of R [by field]: the event's list, empty as it begins */ + take(c); + Token type = expect_name(c, "list"); + if (strcmp(type.text, "list")) + fail_at(c, type.line, type.column, "a let holds a list as let %s: list of a record; others take '='", s->name); + expect_word(c, "of"); + held_record(c, s->member, &s->library); + if (word(c, "by")) { + take(c); + Token key = expect_name(c, "a field of the record"); + memcpy(s->key, key.text, NAME); + s->key_line = key.line; + s->key_column = key.column; + } + s->listed = true; + return s; + } expect(c, "=", "'='"); s->value = expression(c, 0, false); return s; } + if (!strcmp(token.text, "break")) { /* ends the innermost for */ + s->kind = S_BREAK; + return s; + } if (!strcmp(token.text, "for")) { /* for item in list [where condition] { ... }: the block runs once for every item the list has */ s->kind = S_FOR; @@ -1007,6 +1036,29 @@ static Stmt *statement(Compiler *c, unsigned depth) { if (!word(c, "in")) fail_at(c, c->token.line, c->token.column, "expected 'in' and a list, as in for %s in tasks { }", item.text); take(c); + /* for i in low .. high counts; for x in list walks (a list's name, then '{', '[' or where: looked at, the + * scanner then put back when it counts) */ + bool walks = false; + if (c->token.kind == T_NAME) { + size_t at = c->at; + int line = c->line, column = c->column; + Token token = c->token, ahead = c->ahead; + bool has_ahead = c->has_ahead; + char path[NAME]; + list_path(c, path); + walks = is(c, "{") || is(c, "[") || word(c, "where"); + c->at = at, c->line = line, c->column = column; + c->token = token, c->ahead = ahead, c->has_ahead = has_ahead; + } + if (!walks) { + s->kind = S_COUNT; + s->low = expression(c, 0, false); + if (!is(c, "..")) fail_at(c, c->token.line, c->token.column, "expected '..', as in for %s in 0 .. n { }", item.text); + take(c); + s->high = expression(c, 0, false); + s->then = block(c, depth + 1); + return s; + } s->first = node(c, E_NAME, c->token.line, c->token.column); /* where the list is named */ list_path(c, s->member); if (is(c, "[")) slice(c, &s->low, &s->high); @@ -1140,7 +1192,9 @@ static int input_index(const Compiler *c, const char *name) { } static int state_index(const Compiler *c, const char *name) { - for (unsigned i = 0; i < c->states; ++i) if (!strcmp(c->state_names[i], name)) return (int)i; + for (unsigned i = c->local_count; i-- > 0;) /* a let's list, by the let's name */ + if (c->locals[i].list >= 0 && !strcmp(c->locals[i].name, name)) return c->locals[i].list; + for (unsigned i = 0; i < c->states; ++i) if (!c->state_event[i] && !strcmp(c->state_names[i], name)) return (int)i; return -1; } @@ -2130,12 +2184,49 @@ static void app_name(Compiler *c, const Expr *e, const char *name) { } /* The local named name in scope, or -1. */ +/* A let of a value (or a for's count) in scope, by name, or -1; a let's list is a state (state_index). */ static int local_named(const Compiler *c, const char *name) { for (unsigned i = c->local_count; i-- > 0;) + if (c->locals[i].list < 0 && !strcmp(c->locals[i].name, name)) return (int)i; + return -1; +} + +/* Any let in scope by name (a value's or a list's), or -1. */ +static int let_named(const Compiler *c, const char *name) { + for (unsigned i = c->local_count; i-- > 0;) if (!strcmp(c->locals[i].name, name)) return (int)i; return -1; } +/* The for (walk or count) in whose block a name is its item or count, innermost first, or -1. */ +static int loop_named(const Compiler *c, const char *name) { + for (unsigned i = c->loop_count; i-- > 0;) + if (!strcmp(c->loops[i].name, name)) return (int)i; + return -1; +} + +/* The innermost walk among the fors around, or -1. */ +static int innermost_walk(const Compiler *c) { + for (unsigned i = c->loop_count; i-- > 0;) + if (c->loops[i].list >= 0) return (int)i; + return -1; +} + +/* Whether a list is walked by a for around: the innermost walk's (1), an outer walk's (2), or none (0). */ +static int walked(const Compiler *c, int list) { + int inner = innermost_walk(c); + for (unsigned i = 0; inner >= 0 && i < c->loop_count; ++i) + if (c->loops[i].list == list) return (int)i == inner ? 1 : 2; + return 0; +} + +/* Whether statements hold a walk (a for over a list), at any depth. */ +static bool holds_walk(const Stmt *s) { + for (; s; s = s->next) + if (s->kind == S_FOR || holds_walk(s->then) || holds_walk(s->otherwise)) return true; + return false; +} + static uint32_t local_arg(const Compiler *c, int local) { const Kind *kind = &c->locals[local].kind; return c->locals[local].slot | (kind->type == INDEX ? 1u << 8 | (uint32_t)kind->owner << 16 : @@ -2177,10 +2268,14 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { emit(c, e->line, e->column, ROW, (uint32_t)c->fold_list, 0); /* the item a fold's block runs for */ return (Kind){INDEX, c->fold_list}; } - if (c->walking && c->scope == c->walk_scope && !strcmp(e->name, c->walk_name)) { - /* the item a for's block runs for; in a count, sum, max or min inside it, still there */ - emit(c, e->line, e->column, c->folding ? OUTER_ROW : ROW, (uint32_t)c->walk_list, 0); - return (Kind){INDEX, c->walk_list}; + int loop = loop_named(c, e->name); + if (loop >= 0 && c->loops[loop].list >= 0 && c->scope == c->loops[loop].scope) { + /* the item a for's block runs for; in a count, sum, max or min inside it, still there; an outer + * walk's, inside the walks within it, kept in a local */ + int list = c->loops[loop].list; + if (loop == innermost_walk(c)) emit(c, e->line, e->column, c->folding ? OUTER_ROW : ROW, (uint32_t)list, 0); + else emit(c, e->line, e->column, LOCAL_LOAD, (uint32_t)c->loops[loop].item | 1u << 8 | (uint32_t)list << 16, 0); + return (Kind){INDEX, list}; } int param = parameter(c, e->name); if (param >= 0 && c->folding && c->scope == c->fold_scope) @@ -2580,7 +2675,8 @@ static Expr *spread_field(Compiler *c, const Expr *spread, const char *name) { } if (source->kind == E_NAME) { int local = local_named(c, source->name), state = state_index(c, source->name); - if (c->walking && !strcmp(source->name, c->walk_name)) list = c->walk_list; + int loop = loop_named(c, source->name); + if (loop >= 0 && c->loops[loop].list >= 0) list = c->loops[loop].list; else if (local >= 0 && c->locals[local].kind.type == INDEX) list = c->locals[local].kind.owner; else if (local < 0 && state >= 0 && c->state_types[state] == STATE_INDEX) list = cursor_owner(c, state); } else if (source->kind == E_ITEM && !source->member[0]) @@ -2683,11 +2779,26 @@ static void statements(Compiler *c, const Stmt *s) { for (; s; s = s->next) { switch (s->kind) { case S_LET: { + if (let_named(c, s->name) >= 0) fail_at(c, s->line, s->column, "'%s' is already a let here", s->name); const char *kind = declared_kind(c, s->name); if (kind) fail_at(c, s->line, s->column, "'%s' is already declared as %s", s->name, kind); - if (local_named(c, s->name) >= 0) fail_at(c, s->line, s->column, "'%s' is already a let here", s->name); - if (c->walking && !strcmp(s->name, c->walk_name)) - fail_at(c, s->line, s->column, "'%s' is the item this for walks", s->name); + int loop = loop_named(c, s->name); + if (loop >= 0) + fail_at(c, s->line, s->column, c->loops[loop].list >= 0 ? "'%s' is the item this for walks" : + "'%s' is what this for counts", s->name); + if (c->local_count == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); + if (s->listed) { /* the event's list: its state let.NAME, made before the handlers (event_lists) */ + if (c->nested) fail_at(c, s->line, s->column, "a let's list goes in the handler's own block, not in an if or a for"); + char path[2 * NAME]; + snprintf(path, sizeof(path), "let.%s", s->name); + int list = -1; + for (unsigned i = 0; i < c->states; ++i) if (c->state_event[i] && !strcmp(c->state_names[i], path)) list = (int)i; + unsigned local = c->local_count++; + memset(&c->locals[local], 0, sizeof(c->locals[local])); + memcpy(c->locals[local].name, s->name, NAME); + c->locals[local].list = list; + break; + } if (c->local_slots == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); Kind held = value(c, s->value, true, 0); if (held.type != NUM && held.type != BOOL && held.type != I64 && held.type != INDEX) @@ -2697,11 +2808,15 @@ static void statements(Compiler *c, const Stmt *s) { memcpy(c->locals[local].name, s->name, NAME); c->locals[local].slot = c->local_slots++; c->locals[local].kind = held; + c->locals[local].list = -1; + c->locals[local].counter = false; emit(c, s->line, s->column, LOCAL_STORE, local_arg(c, (int)local), 0); break; } case S_ASSIGN: { int local = local_named(c, s->name); + if (local >= 0 && c->locals[local].counter) + fail_at(c, s->line, s->column, "'%s' is what this for counts; it is not assigned", s->name); if (local >= 0) { /* a let takes a new value of its kind */ Kind kind = wanted(c, s->value, true, 0, c->locals[local].kind.type); if (kind.type != c->locals[local].kind.type || (kind.type == INDEX && kind.owner != c->locals[local].kind.owner)) @@ -2741,8 +2856,10 @@ static void statements(Compiler *c, const Stmt *s) { int list = state_index(c, s->name); if (list >= 0 && c->state_types[list] == STATE_LIST) { if (s->last) fail_at(c, s->last->line, s->last->column, "a list removes one item, as in %s.remove(index)", s->name); - if (s->kind == S_INSERT && c->walking && list == c->walk_list) + if (s->kind == S_INSERT && walked(c, list)) fail_at(c, s->line, s->column, "a for does not add to the list it walks; add to another list, or after the for"); + if (s->kind == S_REMOVE && walked(c, list) == 2) + fail_at(c, s->line, s->column, "a for removes from the list it walks, not from one an outer for walks"); if (s->kind == S_INSERT && keyed_by(c, list)) fail_at(c, s->line, s->column, "'%s' is keyed by %s: %s.add(%s { ... }) places an item by its key", s->name, keyed_by(c, list), s->name, record_shown(c, c->module, record_of(c, list))); @@ -2767,7 +2884,7 @@ static void statements(Compiler *c, const Stmt *s) { case S_APPEND: case S_ADD: { /* A keyed list places an added item by its key (it fails if the key is there); others append. */ int list = list_state(c, s->name, s->line, s->column, NULL); - if (c->walking && list == c->walk_list) + if (walked(c, list)) fail_at(c, s->line, s->column, "a for does not add to the list it walks; add to another list, or after the for"); const char *key = keyed_by(c, list), *record = record_shown(c, c->module, record_of(c, list)); if (s->kind == S_APPEND && key) @@ -2859,7 +2976,7 @@ static void statements(Compiler *c, const Stmt *s) { case S_REPLY: if (c->message < 0 || c->inputs[c->message].kind != KIND_MESSAGE) fail_at(c, s->line, s->column, "reply is only available in a message handler"); - if (c->walking) fail_at(c, s->line, s->column, "a message replies once: reply after the for"); + if (c->loop_count) fail_at(c, s->line, s->column, "a message replies once: reply after the for"); bytes_value(c, s->value, 0, "reply"); emit(c, s->line, s->column, REPLY, 0, 0); break; @@ -2869,10 +2986,12 @@ static void statements(Compiler *c, const Stmt *s) { break; case S_FOR: { /* EACH and the block after it, run once for every item the list has, with the item as ROW. */ - if (c->walking) fail_at(c, s->line, s->column, "a for cannot hold another for"); + if (c->loop_count == NESTING) fail_at(c, s->line, s->column, "fors nest at most %d deep", NESTING); int list = list_state(c, s->member, s->first->line, s->first->column, NULL); const char *kind = declared_kind(c, s->name); if (kind) fail_at(c, s->last->line, s->last->column, "'%s' is already declared as %s", s->name, kind); + if (let_named(c, s->name) >= 0 || loop_named(c, s->name) >= 0) + fail_at(c, s->last->line, s->last->column, "'%s' is already a let or a for's name here", s->name); if (s->low) { /* a slice: the keys from low up to before high */ Type key = keyed_by(c, list) ? field_kind(record_of(c, list), (unsigned)c->list_key[list] - 1) : TEXT; if (key != NUM && key != I64) @@ -2881,20 +3000,28 @@ static void statements(Compiler *c, const Stmt *s) { expect_kind(c, s->low, wanted(c, s->low, true, 0, key), key); expect_kind(c, s->high, wanted(c, s->high, true, 1, key), key); } - unsigned at = c->code_count, jump = 0; + unsigned at = c->code_count, jump = 0, loop = c->loop_count++; emit(c, s->line, s->column, EACH, 0, 0); - c->walking = true; - c->walk_list = list; - c->walk_scope = c->scope; - memcpy(c->walk_name, s->name, NAME); + memcpy(c->loops[loop].name, s->name, NAME); + c->loops[loop].list = list; + c->loops[loop].item = -1; + c->loops[loop].scope = c->scope; if (s->value) { truth(c, s->value, true, 0, "where"); jump = c->code_count; emit(c, s->line, s->column, JZ, 0, 0); } + if (holds_walk(s->then)) { /* the walks within read this item from a local */ + if (c->local_slots == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); + c->loops[loop].item = (int)c->local_slots++; + emit(c, s->line, s->column, ROW, (uint32_t)list, 0); + emit(c, s->line, s->column, LOCAL_STORE, (uint32_t)c->loops[loop].item | 1u << 8 | (uint32_t)list << 16, 0); + } + ++c->nested; statements(c, s->then); + --c->nested; if (s->value) c->code[jump].arg = c->code_count; - c->walking = false; + --c->loop_count; bool empty = c->code_count == at + 1; if (empty && !s->low) c->code_count = at; /* nothing to do for any item */ else { @@ -2910,6 +3037,7 @@ static void statements(Compiler *c, const Stmt *s) { truth(c, s->value, true, 0, "if"); unsigned jump = c->code_count; emit(c, s->line, s->column, JZ, 0, 0); + ++c->nested; statements(c, s->then); if (s->otherwise) { unsigned skip = c->code_count; @@ -2918,8 +3046,46 @@ static void statements(Compiler *c, const Stmt *s) { statements(c, s->otherwise); c->code[skip].arg = c->code_count; } else c->code[jump].arg = c->code_count; + --c->nested; + break; + } + case S_COUNT: { + /* LOOP and the block after it, run once for each value from low up to before high, the value in a local: + * numbers (whole), or i64s when either is one. */ + if (c->loop_count == NESTING) fail_at(c, s->line, s->column, "fors nest at most %d deep", NESTING); + const char *kind = declared_kind(c, s->name); + if (kind) fail_at(c, s->line, s->column, "'%s' is already declared as %s", s->name, kind); + if (let_named(c, s->name) >= 0 || loop_named(c, s->name) >= 0) + fail_at(c, s->line, s->column, "'%s' is already a let or a for's name here", s->name); + if (c->local_slots == LOCALS || c->local_count == LOCALS) + fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); + bool integer = probe(c, s->low, true, 0) == I64 || probe(c, s->high, true, 0) == I64; + Type type = integer ? I64 : NUM; + expect_kind(c, s->low, wanted(c, s->low, true, 0, type), type); + expect_kind(c, s->high, wanted(c, s->high, true, 1, type), type); + unsigned scope = c->local_count, local = c->local_count++, loop = c->loop_count++, at = c->code_count; + memcpy(c->locals[local].name, s->name, NAME); + c->locals[local].slot = c->local_slots++; + c->locals[local].kind = (Kind){type, -1}; + c->locals[local].list = -1; + c->locals[local].counter = true; + memcpy(c->loops[loop].name, s->name, NAME); + c->loops[loop].list = -1; + c->loops[loop].item = -1; + c->loops[loop].scope = c->scope; + emit(c, s->line, s->column, LOOP, 0, 0); + ++c->nested; + statements(c, s->then); + --c->nested; + c->code[at].arg = c->locals[local].slot | (integer ? LOOP_INTEGER : 0) | (c->code_count - at - 1) << 16; + --c->loop_count; + c->local_count = scope; break; } + case S_BREAK: + if (!c->loop_count) fail_at(c, s->line, s->column, "break ends a for; this one is outside any"); + emit(c, s->line, s->column, BREAK, 0, 0); + break; } } c->local_count = scope; @@ -2962,6 +3128,50 @@ static uint32_t intern(unsigned char *strings, size_t *used, const char *text) { /* A message's rows field fills a list of its own, named by the input and the * field (order.lines): a state that holds rows only while its event runs. */ +/* A let's list (let x: list of R) is a state of its own, let.x: an event's + * list (Pure 018), one for the lets of that name in the handlers' own + * blocks, which hold one record keyed one way; no two handlers of one event + * have one, so each begins empty. */ +static void event_lists(Compiler *c) { + for (unsigned h = 0; h < c->handler_count; ++h) + for (const Stmt *s = c->handlers[h].body; s; s = s->next) { + if (s->kind != S_LET || !s->listed) continue; + char path[2 * NAME]; + snprintf(path, sizeof(path), "let.%s", s->name); + if (strlen(path) >= NAME) + fail_at(c, s->line, s->column, "a let's list has a name of %d characters at most", NAME - 5); + int found = -1; + for (unsigned i = 0; i < c->states; ++i) + if (c->state_event[i] && !strcmp(c->state_names[i], path)) found = (int)i; + if (found >= 0) { + if (strcmp(c->state_target[found], s->member) || c->state_library[found] != s->library || + strcmp(c->state_key[found], s->key)) + fail_at(c, s->line, s->column, "'%s' is a let's list of another record or key in another handler; one name " + "holds one kind of list", s->name); + for (unsigned k = 0; k < h; ++k) { + if (strcmp(c->handlers[k].input, c->handlers[h].input) || c->handlers[k].edge != c->handlers[h].edge) continue; + for (const Stmt *t = c->handlers[k].body; t; t = t->next) + if (t->kind == S_LET && t->listed && !strcmp(t->name, s->name)) + fail_at(c, s->line, s->column, "another handler of this event has a list '%s'; name this one apart", + s->name); + } + continue; + } + if (c->states == STATES) fail_at(c, s->line, s->column, "at most 32 states are allowed, a let's list among them"); + unsigned i = c->states++; + memcpy(c->state_names[i], path, strlen(path) + 1); + c->state_types[i] = STATE_LIST; + c->state_event[i] = true; + memcpy(c->state_target[i], s->member, NAME); + c->state_library[i] = s->library; + memcpy(c->state_key[i], s->key, NAME); + c->state_key_line[i] = s->key_line; + c->state_key_column[i] = s->key_column; + c->state_line[i] = s->line; + c->state_column[i] = s->column; + } +} + static void rows_lists(Compiler *c) { for (unsigned i = 0; i < c->input_count; ++i) { Input *in = &c->inputs[i]; @@ -3024,6 +3234,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { uint32_t events[HANDLERS], state_ref[STATES] = {0}; int handler_input[HANDLERS]; rows_lists(c); + event_lists(c); pull_records(c); for (unsigned i = 0; i < c->states; ++i) { if (c->state_types[i] == STATE_TEXT) { @@ -3188,7 +3399,8 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { else if (c->state_types[i] == STATE_I64) p = put64(p, (uint64_t)c->state_integer[i]); else { /* flags: a mark (1: the cursor stays), or a keyed list's key field + 1 */ p = put32(p, state_ref[i]); - p = put32(p, c->state_types[i] == STATE_LIST ? (c->state_rows[i] ? LIST_ROWS : (uint32_t)c->list_key[i]) : + p = put32(p, c->state_types[i] == STATE_LIST ? (c->state_rows[i] || c->state_event[i] ? LIST_ROWS : 0) | + (uint32_t)c->list_key[i] : c->state_stays[i] ? 1 : 0); } } diff --git a/docs/design/001-language-v0.md b/docs/design/001-language-v0.md index 1e11f6b..b8ec8e9 100644 --- a/docs/design/001-language-v0.md +++ b/docs/design/001-language-v0.md @@ -1,6 +1,6 @@ # aup v0 — 한 언어, 한 모듈 형식 -2026-09-25. 상태: v0a(scalar), v0b(바이트·영속 문서·text·커서), v0c(레코드·리스트·인덱스·조건식·표 보기·fn·use·라이브러리의 레코드·`bool`·`i64`·`transient state`) 구현. +2026-09-25. 상태: v0a(scalar), v0b(바이트·영속 문서·text·커서), v0c(레코드·리스트·인덱스·조건식·표 보기·fn·use·라이브러리의 레코드·`bool`·`i64`·`transient state`·세는 `for`·`break`·중첩·이벤트의 리스트) 구현. text와 리스트 문법은 [002](002-dsl-direction.md). 착수 기준: @@ -23,7 +23,7 @@ text와 리스트 문법은 [002](002-dsl-direction.md). | 요청/응답 핸들러와 라우터 | 입력은 이름 있는 이벤트다. 키·CLI·HTTP는 호스트 어댑터가 입력으로 바꾼다 | | 화면용 모델을 따로 만든다 | `view`는 상태의 순수 함수다. 호스트와 나중의 Vega가 읽어 표시한다 | | 런타임이 소스를 해석한다 | 툴체인이 모듈로 컴파일하고, 런타임은 검증한 뒤 실행한다(JVM의 class 파일처럼) | -| 이벤트 처리 시간에 상한이 없다 | 반복이 없고 분기는 앞으로만 한다. 이벤트당 실행량의 상한이 모듈에서 정해진다 | +| 이벤트 처리 시간에 상한이 없다 | 분기는 앞으로만 하고 반복은 시작할 때 횟수가 정해진다(리스트의 항목, 세는 범위). 이벤트마다 실행량 예산이 있다(정하지 않으면 2^28 명령, Pure 018) | ## v0a 문법 @@ -49,7 +49,7 @@ on frame { # 1/60초 진행. 핸들러 안에서 dt를 쓸 - 선언과 문장은 줄바꿈이나 `;`로 끝난다. 이름은 ASCII `[A-Za-z_][A-Za-z_0-9]*`, 31바이트 이하다. - 예약어: `state input view on if else key frame dt min max clamp message by bytes u32 reply require record each fn use now - every floor output send for let true false transient`. + every floor output send for let true false transient break`. - 키: `Space Enter ArrowLeft ArrowRight ArrowUp ArrowDown KeyA..KeyZ Digit0..Digit9`. 입력 이름과 키는 각각 한 번만 쓴다. 상태·입력·보기의 이름공간은 서로 겹칠 수 없다. - 식: 숫자, `true`·`false`, 상태 이름, `dt`(핸들러 안에서만), 괄호, 단항 `- !`, 이항 `* /`, `+ -`, diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index a226bc8..b5c647a 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -341,6 +341,52 @@ on tap.down { 모듈을 바꿔 저장되던 것을 transient로, transient를 저장되는 것으로 바꾸면 다시 열었을 때 바뀐 대로다. `pure-serve`에서 transient만 바꾸는 요청 100개가 저장소의 어떤 파일도 바꾸지 않고, `kill -9` 뒤에 다시 띄우면 transient는 초기값이다. +### 메모리 안의 계산 (구현) + +Pure [018](/srv/git/pure.git)의 2단계이고, [외부 리뷰 005](/srv/git/pure.git)의 (1)에서 시작한다. 반복이 리스트의 항목을 도는 +걷기 하나뿐이고 걷기 안에 걷기가 없으며 이벤트 동안만 쓰는 리스트가 없어서, 그래프 탐색이나 수치 반복을 aup로 쓸 수 없었다. + +```text +on route { + let seen: list of Seen by node # 이벤트의 리스트: 비어서 시작하고, 이벤트가 끝나면 없다 + let queue: list of Step + queue.append(Step { node: route.from }) + seen.add(Seen { node: route.from }) + for i in 0 .. nodes.count { # 세는 반복: 0부터 nodes.count 앞까지 + if i >= queue.count { break } # 가장 안쪽 for를 끝낸다 + let q = queue.first + i # 자리로 읽는 큐 + for e in edges[q.node * 1048576 .. q.node * 1048576 + 1048576] { # for 안의 for + if !seen.has(e.to) { + seen.add(Seen { node: e.to, depth: seen[q.node].depth + 1 }) + queue.append(Step { node: e.to }) + } + } + } + hops = if seen.has(route.to) { seen[route.to].depth } else { -1 } +} +``` + +- **`let 이름: list of 레코드 [by 필드]`** 는 이벤트의 리스트다. 핸들러의 맨 바깥 블록에 둔다. 상태 리스트가 하는 것을 모두 한다 + (`add`, `append`, `insert`, `remove`, `[키]`, `has`, `count`, `first`, `last`, `+ n`, 집계, `for`). 이벤트가 시작할 때 비어 있고 + 끝나면 비워진다. 저장되지 않고 되돌리기를 기록하지 않는다. 모듈에서는 `let.이름`이라는 이벤트의 리스트 상태다(Pure 008의 행 + 목록과 같은 플래그). 같은 이름의 리스트를 쓰는 핸들러들은 그 상태 하나를 함께 쓰므로 레코드와 키가 같아야 하고, 한 이벤트의 두 + 핸들러가 같은 이름을 쓰지는 못한다. 상태 32개 안에 든다. +- **`for 이름 in 낮음 .. 높음 { }`** 은 이름을 낮음부터 높음 앞까지 1씩 올리며 돈다. 둘 다 수(정수: 아니면 `limit`)이거나 하나라도 + i64면 i64다. 이름은 읽기만 한다. `for x in 리스트`(걷기)와는 `in` 뒤가 리스트 이름이고 `{`·`[`·`where`가 따르는지로 가른다. +- **`break`** 는 가장 안쪽 `for`(걷기든 세기든)를 끝낸다. +- **중첩.** `for` 안에 `for`를 8겹까지 쓴다. 바깥 걷기의 항목은 안에서 읽는다(컴파일러가 안쪽 걷기 앞에서 인덱스 지역 칸에 담는다). + 안쪽 `for`는 바깥 걷기가 도는 리스트에 넣거나 그 리스트에서 지우지 못한다(가장 안쪽 걷기는 지금처럼 자기 리스트에서 지울 수 있다). +- **끝.** 모든 반복은 시작할 때 횟수의 상한이 정해진다. 끝을 모르는 계산은 상한을 적은 세기와 `break`로 쓴다(BFS는 노드 수, + DFS와 다익스트라는 간선 수 + 1). 그래도 이벤트마다 실행량 예산(정하지 않으면 2^28 명령)과 메모리 한도(2^28 바이트, 예산이 + 있으면 예산)가 있어서 고장 난 반복은 `work`나 `memory`로 끝나고 아무것도 바꾸지 않는다. +- `break`는 예약어다. + +예제: `examples/routes.aup`(너비 우선, 깊이 우선, 무게로 가장 가까운 길), `examples/numbers.aup`(뉴턴의 제곱근, 배낭 문제). + +검증(`tests/check.py`의 `computing`과 진단 14건, Pure의 `test-module`의 `loops`와 `server_runaway`): 무작위 그래프 20개의 질의마다 +세 탐색이 Python 모델과 같다(600번). 뉴턴의 제곱근 200개가 같은 순서로 계산한 Python의 binary64와 비트까지, 단계 수까지 같다. 배낭 +문제 100개가 동적 계획법 모델과 같다. 이벤트가 끝난 뒤 이벤트의 리스트는 모두 비어 있다. + ### 시간 ```text diff --git a/examples/numbers.aup b/examples/numbers.aup new file mode 100644 index 0000000..3352993 --- /dev/null +++ b/examples/numbers.aup @@ -0,0 +1,62 @@ +# Numbers worked out in one event (Pure 018): a square root by Newton's +# steps, stopping when they stop shrinking, and the best a knapsack holds, +# its table an event's list. +record Item { + id = 0 + weight = 0 + worth = 0 +} +record Cell { + at = 0 + best = 0 +} +state items: list of Item by id +state root = 0 +state steps = 0 +state packed = 0 +input stock = message { + id: u32 + weight: u32 + worth: u32 +} +input sqrt = message { + x: number +} +input pack = message { + capacity: u32 +} + +on stock { + items.add(Item { id: stock.id, weight: stock.weight, worth: stock.worth }) +} + +on sqrt { + let r = if sqrt.x > 1 { sqrt.x } else { 1 } + steps = 0 + for i in 0 .. 200 { + let next = (r + sqrt.x / r) / 2 + if next >= r { break } + r = next + steps = steps + 1 + } + root = r +} + +on pack { + let table: list of Cell by at + for c in 0 .. pack.capacity + 1 { + table.add(Cell { at: c }) + } + for it in items { + for k in 0 .. pack.capacity + 1 { + let c = pack.capacity - k + if c >= it.weight { + let with = table[c - it.weight].best + it.worth + if with > table[c].best { table[c].best = with } + } + } + } + packed = table[pack.capacity].best +} + +view answer = "root {root} in {steps} steps, packed {packed}" diff --git a/examples/routes.aup b/examples/routes.aup new file mode 100644 index 0000000..6656947 --- /dev/null +++ b/examples/routes.aup @@ -0,0 +1,120 @@ +# Routes over a graph in one event each (Pure 018): breadth first, depth +# first and the nearest by weight. A node's edges are a slice of the edges +# list (keyed by from * 1048576 + to); the searches keep their queues and +# what they have seen in the event's own lists, gone when it ends. +record Node { + id = 0 +} +record Edge { + link = 0 + to = 0 + w = 1 +} +record Seen { + node = 0 + depth = 0 +} +record Step { + node = 0 +} +record Pending { + rank = 0 +} +state nodes: list of Node by id +state edges: list of Edge by link +state hops = -1 +state visited = 0 +state order = 0 +state shortest = -1 +input link = message { + from: u32 + to: u32 + w: u32 +} +input route = message { + from: u32 + to: u32 +} +input walk = message { + from: u32 +} +input path = message { + from: u32 + to: u32 +} + +on link { + if !nodes.has(link.from) { nodes.add(Node { id: link.from }) } + if !nodes.has(link.to) { nodes.add(Node { id: link.to }) } + edges.add(Edge { link: link.from * 1048576 + link.to, to: link.to, w: link.w }) +} + +# Breadth first: the queue is read by position, every node enters it once. +on route { + let seen: list of Seen by node + let queue: list of Step + queue.append(Step { node: route.from }) + seen.add(Seen { node: route.from }) + for i in 0 .. nodes.count { + if i >= queue.count { break } + let q = queue.first + i + for e in edges[q.node * 1048576 .. q.node * 1048576 + 1048576] { + if !seen.has(e.to) { + seen.add(Seen { node: e.to, depth: seen[q.node].depth + 1 }) + queue.append(Step { node: e.to }) + } + } + } + hops = if seen.has(route.to) { seen[route.to].depth } else { -1 } +} + +# Depth first: a stack, each edge pushed once at most; depth is the order a node was reached in. +on walk { + let seen: list of Seen by node + let stack: list of Step + stack.append(Step { node: walk.from }) + let reached = 0 + for i in 0 .. edges.count + 1 { + if stack.count == 0 { break } + let v = stack.last.node + stack.remove(stack.last) + if !seen.has(v) { + seen.add(Seen { node: v, depth: reached }) + reached = reached + 1 + for e in edges[v * 1048576 .. v * 1048576 + 1048576] { + if !seen.has(e.to) { stack.append(Step { node: e.to }) } + } + } + } + visited = seen.count + order = sum(s.depth * (s.node + 1) for s in seen) +} + +# The nearest by weight (Dijkstra): pending ranks by dist * 1048576 + node, the nearest first. +on path { + let best: list of Seen by node + let pending: list of Pending by rank + best.add(Seen { node: path.from }) + pending.add(Pending { rank: path.from }) + for i in 0 .. edges.count + 1 { + if pending.count == 0 { break } + let d = floor(pending.first.rank / 1048576) + let v = pending.first.rank - d * 1048576 + pending.remove(pending.first) + if best[v].depth == d { + for e in edges[v * 1048576 .. v * 1048576 + 1048576] { + let far = d + e.w + if !best.has(e.to) { + best.add(Seen { node: e.to, depth: far }) + pending.add(Pending { rank: far * 1048576 + e.to }) + } else if far < best[e.to].depth { + best[e.to].depth = far + pending.add(Pending { rank: far * 1048576 + e.to }) + } + } + } + } + shortest = if best.has(path.to) { best[path.to].depth } else { -1 } +} + +view graph = "{nodes.count} nodes, {edges.count} edges" diff --git a/tests/check.py b/tests/check.py index aa18217..eb37c35 100644 --- a/tests/check.py +++ b/tests/check.py @@ -249,8 +249,9 @@ DIAGNOSTICS = [ ('view v = floor(1, 2)\n', '1:10', 'floor expects 1 argument'), ('state now = 1\n', '1:7', "'now' is reserved"), # A for walks a list once; a message's rows are a list its handler reads. - (ROWS + 'on o {\n for a in xs {\n for b in xs {\n n = n + 1\n }\n }\n}\n', '12:5', - 'a for cannot hold another for'), + # Fors nest (Pure 018): an inner for changes no list an outer one walks. + (ROWS + 'on o {\n for a in xs {\n for b in o.rows {\n xs.append(L { item: 1 })\n }\n }\n}\n', '13:7', + 'a for does not add to the list it walks; add to another list, or after the for'), (ROWS + 'on o {\n for a in xs {\n xs.append(L { item: 1 })\n }\n}\n', '12:5', 'a for does not add to the list it walks; add to another list, or after the for'), (ROWS + 'on o {\n for a in o.rows {\n reply "x"\n }\n}\n', '12:5', 'a message replies once: reply after the for'), @@ -296,6 +297,26 @@ DIAGNOSTICS = [ (LETS + 'on k.down {\n let i = xs.first\n i = ys.first\n}\n', '11:7', "'i' holds an index of its list"), (LETS + 'on k.down {\n if n > 0 {\n let x = 1\n }\n n = x\n}\n', '13:7', "unknown state 'x'"), (LETS + 'on k.down {\n for r in xs {\n let r = 1\n }\n}\n', '11:9', "'r' is the item this for walks"), + # Counting fors, break and a let's list (Pure 018). + (LETS + 'on k.down {\n break\n}\n', '10:3', "break ends a for; this one is outside any"), + (LETS + 'on k.down {\n if n > 0 {\n let q: list of T\n }\n}\n', '11:9', + "a let's list goes in the handler's own block, not in an if or a for"), + (LETS + 'on k.down {\n for i in 0 .. 3 {\n i = 1\n }\n}\n', '11:5', "'i' is what this for counts; it is not assigned"), + (LETS + 'on k.down {\n for a in xs {\n for b in ys {\n xs.remove(a)\n }\n }\n}\n', '12:7', + 'a for removes from the list it walks, not from one an outer for walks'), + (LETS + 'on k.down {\n for a in xs {\n for i in 0 .. 2 {\n xs.append(T { a: 1 })\n }\n }\n}\n', '12:7', + 'a for does not add to the list it walks; add to another list, or after the for'), + (LETS + 'record U {\n b = 0\n}\ninput j = key KeyJ\non k.down {\n let q: list of T\n}\non j.down {\n' + ' let q: list of U\n}\n', '17:7', "'q' is a let's list of another record or key in another handler"), + (LETS + 'on k.down {\n let q: list of T\n}\non k.down {\n let q: list of T\n}\n', '13:7', + "another handler of this event has a list 'q'; name this one apart"), + (LETS + 'on k.down {\n' + ''.join(' ' * (d + 1) + f'for i{d} in 0 .. 2 {{\n' for d in range(9)) + '}\n' * 10, '18:19', 'fors nest at most 8 deep'), + (LETS + 'on k.down {\n let x: text\n}\n', '10:10', "a let holds a list as let x: list of a record; others take '='"), + (LETS + 'on k.down {\n let q: list of Nope\n}\n', '10:7', "a list holds a record; 'Nope' is not one"), + (LETS + 'on k.down {\n let ' + 'q' * 28 + ': list of T\n}\n', '10:7', "a let's list has a name of 27 characters at most"), + (LETS + 'on k.down {\n for i in t .. 3 {\n }\n}\n', '10:12', 'expected a number'), + ('state break = 0\n', '1:7', "'break' is reserved"), + (LETS + 'on k.down {\n let q: list of T\n for i in 0 .. 3 {\n let q = 1\n }\n}\n', '12:9', "'q' is already a let here"), (LETS + 'view v = x\non k.down {\n let x = 1\n}\n', '9:10', "unknown state 'x'"), (LETS + 'on k.down {\n let let = 1\n}\n', '10:7', "'let' is reserved"), # A slice takes a list keyed by a number. @@ -1093,6 +1114,99 @@ def transients(aup, pure_run, work): return {'store_files_compared': len(compared.common_files), 'type_words_differ': len(differ)} +def computing(aup, pure_run, work, graphs=20): + """Computing in one event (Pure 018): examples/routes.aup's searches + over random graphs (breadth first, depth first, nearest by weight) and + examples/numbers.aup's square roots (bit for bit as Python works them out + in the same order) and knapsacks, each against a model. The searches' + lists are the event's own: none is left after it.""" + import heapq + import random + import struct + routes, numbers = work / 'routes.pmod', work / 'numbers.pmod' + compile_module(aup, ROOT / 'examples' / 'routes.aup', routes) + compile_module(aup, ROOT / 'examples' / 'numbers.aup', numbers) + + def message(name, layout, *values): + return f'message {name} {struct.pack(layout, *values).hex()}\n' + + searched = 0 + for seed in range(graphs): + rng = random.Random(seed) + n = rng.randint(5, 60) + edges = {} + for _ in range(rng.randint(n, 4 * n)): + a, b = rng.randrange(n), rng.randrange(n) + if a != b: + edges[(a, b)] = rng.randint(1, 9) + out = {v: sorted(b for (a, b) in edges if a == v) for v in range(n)} + commands = ''.join(message('link', '<III', a, b, w) for (a, b), w in edges.items()) + queries = [(rng.randrange(n), rng.randrange(n)) for _ in range(10)] + for a, b in queries: + commands += message('route', '<II', a, b) + message('walk', '<I', a) + message('path', '<II', a, b) + lines = run(pure_run, routes, stdin=commands).stdout.splitlines()[len(edges):] + for k, (a, b) in enumerate(queries): + route, walk, path = (json.loads(line) for line in lines[3 * k:3 * k + 3]) + assert all(r['error'] is None and r['fault'] == 'none' for r in (route, walk, path)), (seed, k) + assert all(not r['states'][name] for r in (route, walk, path) + for name in ('let.seen', 'let.queue', 'let.stack', 'let.best', 'let.pending')) + depth, frontier = {a: 0}, [a] + for v in frontier: + for t in out[v]: + if t not in depth: + depth[t] = depth[v] + 1 + frontier.append(t) + assert route['states']['hops'] == depth.get(b, -1), (seed, a, b, route['states']['hops'], depth.get(b, -1)) + seen, stack = {}, [a] + while stack: + v = stack.pop() + if v not in seen: + seen[v] = len(seen) + stack.extend(t for t in out[v] if t not in seen) + assert walk['states']['visited'] == len(seen), (seed, a) + assert walk['states']['order'] == sum(d * (v + 1) for v, d in seen.items()), (seed, a) + best, pending = {a: 0}, [(0, a)] + while pending: + d, v = heapq.heappop(pending) + if best[v] != d: + continue + for t in out[v]: + if d + edges[(v, t)] < best.get(t, float('inf')): + best[t] = d + edges[(v, t)] + heapq.heappush(pending, (best[t], t)) + assert path['states']['shortest'] == best.get(b, -1), (seed, a, b, path['states']['shortest'], best.get(b, -1)) + searched += 3 + + rng = random.Random(18) + xs = [1.0, 2.0, 4.0, 0.25, 1e-6, 1e12] + [10 ** rng.uniform(-6, 12) for _ in range(194)] + lines = run(pure_run, numbers, stdin=''.join(message('sqrt', '<d', x) for x in xs)).stdout.splitlines() + for x, line in zip(xs, lines): + r, steps = (x if x > 1 else 1.0), 0 + for _ in range(200): + following = (r + x / r) / 2 + if following >= r: + break + r, steps = following, steps + 1 + states = json.loads(line)['states'] + assert struct.pack('<d', states['root']) == struct.pack('<d', r) and states['steps'] == steps, (x, states, r, steps) + packs = 0 + for seed in range(20): + rng = random.Random(seed) + items = [(rng.randint(1, 30), rng.randint(1, 50)) for _ in range(rng.randint(1, 20))] + capacities = [rng.randint(0, 100) for _ in range(5)] + commands = ''.join(message('stock', '<III', i, w, v) for i, (w, v) in enumerate(items)) + commands += ''.join(message('pack', '<I', c) for c in capacities) + lines = run(pure_run, numbers, stdin=commands).stdout.splitlines()[len(items):] + for c, line in zip(capacities, lines): + table = [0] * (c + 1) + for w, v in items: + for at in range(c, w - 1, -1): + table[at] = max(table[at], table[at - w] + v) + assert json.loads(line)['states']['packed'] == table[c], (seed, c) + packs += 1 + return {'searches': searched, 'square_roots': len(xs), 'knapsacks': packs} + + def stored_runs(aup, pure_run, work, seeds, steps=240): """Declared state is stored state: the list app and the text editor run on a store, restarting (a new pure-run process) at random points, and keep @@ -1793,6 +1907,7 @@ def main(): report['library_programs'], report['library_diagnostics'] = libraries(aup, pure_run, work) report['library_records'] = library_records(aup, pure_run, args.pure.resolve(), work) report['transients'] = transients(aup, pure_run, work) + report['computing'] = computing(aup, pure_run, work) report['editor_parity_operations'], report['editor_parity_outcomes'] = \ editor_parity(aup, args.pure.resolve(), work, range(args.editor_seeds)) print(json.dumps(report)) |
