aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compiler/compile.c276
-rw-r--r--docs/design/001-language-v0.md6
-rw-r--r--docs/design/002-dsl-direction.md46
-rw-r--r--examples/numbers.aup62
-rw-r--r--examples/routes.aup120
-rw-r--r--tests/check.py119
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))