aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compiler/compile.c186
-rw-r--r--tests/check.py88
2 files changed, 246 insertions, 28 deletions
diff --git a/compiler/compile.c b/compiler/compile.c
index 0c71bc3..d2a3c74 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -10,7 +10,8 @@
/* Limits and opcodes of Pure module format v6. */
enum { STATES = 32, INPUTS = 32, VIEWS = 64, HANDLERS = 64, CODE = 65535, STACK = 64, NAME = 32, JOINS = 8,
DEPTH = 64, FIELDS = 64, COLLECTIONS = 1, CONSTANTS = 4096, CONSTANT_BYTES = 1 << 20,
- RECORDS = 16, RECORD_FIELDS = 128, FIELDS_PER_RECORD = 16, FNS = 64, PARAMS = 8, MODULES = 9 };
+ RECORDS = 16, RECORD_FIELDS = 128, FIELDS_PER_RECORD = 16, FNS = 64, PARAMS = 8, MODULES = 9,
+ LIBRARY_RECORDS = 64 };
enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR, NOT,
MIN, MAX, CLAMP, STORE, JZ, JMP, FIELD, DOC, DOC_SIZE, LEN, REPLACE, REPLY, REQUIRE,
TEXT_CONST, TEXT_LOAD, TEXT_INPUT, NUM_TEXT, CONCAT, CHARS, LINES, TEXT_BYTES,
@@ -95,17 +96,22 @@ typedef struct {
bool keyed;
char field_names[FIELDS][NAME];
int field_types[FIELDS];
- /* A rows field's record, where it was written, and the list its rows fill. */
+ /* A rows field's record (its library: 0 the app's), where it was written, and the list its rows fill. */
char field_record[FIELDS][NAME];
- int field_line[FIELDS], field_column[FIELDS], field_list[FIELDS];
+ int field_library[FIELDS], field_line[FIELDS], field_column[FIELDS], field_list[FIELDS];
unsigned fields;
unsigned view; /* arguments: the view they are for */
} Input;
-/* A record: fields in order, each a number, a bool, an i64 or text with its default. */
+/* A record: fields in order, each a number, a bool, an i64 or text with its
+ * default. Declared in the app (module 0) or in a library; a library's goes
+ * into the app's module once a list or a rows field of the app holds it
+ * (used: where first). */
typedef struct {
char name[NAME];
- int line, column;
+ int line, column, module;
+ bool used;
+ int used_line, used_column;
char field_names[FIELDS_PER_RECORD][NAME];
Type field_kinds[FIELDS_PER_RECORD]; /* NUM, BOOL, I64 or TEXT */
double field_number[FIELDS_PER_RECORD];
@@ -146,6 +152,7 @@ typedef struct {
int state_types[STATES], state_line[STATES], state_column[STATES];
bool state_stays[STATES]; /* a mark: a cursor that stays before text inserted at it */
int state_rows[STATES]; /* a list of a message's rows: its input + 1, or 0 */
+ 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];
int state_key_line[STATES], state_key_column[STATES], list_key[STATES];
@@ -155,8 +162,10 @@ typedef struct {
unsigned states;
char collection_names[COLLECTIONS][NAME];
unsigned collections;
- Record records[RECORDS];
+ Record records[RECORDS]; /* the module's: the app's, then the libraries' it uses (pull_records) */
unsigned record_count, record_fields;
+ Record library_records[LIBRARY_RECORDS]; /* the libraries', in the order they were read */
+ unsigned library_record_count;
Input inputs[INPUTS];
unsigned input_count, field_count;
/* Views in order; a table (no value) is followed by its columns. */
@@ -444,6 +453,9 @@ static Expr *node(Compiler *c, ExprKind kind, int line, int column) {
}
static Expr *expression(Compiler *c, int minimum, bool multiline);
+static Token held_record(Compiler *c, char name[NAME], int *library);
+static int used_library(const Compiler *c, int module, const char *name);
+static int library_record(const Compiler *c, int library, const char *name);
/* name[key]: collections are indexed only by the message key. */
static void index_key(Compiler *c) {
@@ -858,11 +870,21 @@ static bool spread(Compiler *c, Expr ***tail) {
return true;
}
-static Expr *record_value(Compiler *c) {
+/* NAME { ... }, or LIBRARY.NAME { ... } when library is given (its name in
+ * member): a record value, its fields as written. */
+static Expr *record_value(Compiler *c, const Token *library) {
Token name = take(c);
take(c); /* { */
Expr *r = node(c, E_RECORD, name.line, name.column), **tail = &r->args[0];
memcpy(r->name, name.text, NAME);
+ if (library) {
+ int used = used_library(c, c->module, library->text);
+ if (used < 0)
+ fail_at(c, library->line, library->column, "unknown library '%s'; write use %s first", library->text, library->text);
+ if (library_record(c, used, name.text) < 0)
+ fail_at(c, name.line, name.column, "library '%s' has no record '%s'", library->text, name.text);
+ memcpy(r->member, library->text, NAME);
+ }
separators(c);
while (!is(c, "}")) {
if (spread(c, &tail)) break;
@@ -906,11 +928,25 @@ static Expr *send_fields(Compiler *c) {
return r;
}
-/* A value inserted into a text or a list: an expression or a record. */
+/* A value inserted into a text or a list: an expression or a record
+ * (NAME { ... }, or LIBRARY.NAME { ... }: looked at two tokens ahead, the
+ * scanner then put back as it was). */
static Expr *inserted(Compiler *c) {
newlines(c);
if (c->token.kind == T_NAME && lookahead(c)->kind == T_PUNCT && !strcmp(lookahead(c)->text, "{"))
- return record_value(c);
+ return record_value(c, NULL);
+ if (c->token.kind == T_NAME && lookahead(c)->kind == T_PUNCT && !strcmp(lookahead(c)->text, ".")) {
+ 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;
+ Token library = take(c);
+ take(c); /* . */
+ if (c->token.kind == T_NAME && lookahead(c)->kind == T_PUNCT && !strcmp(lookahead(c)->text, "{"))
+ return record_value(c, &library);
+ c->at = at, c->line = line, c->column = column;
+ c->token = token, c->ahead = ahead, c->has_ahead = has_ahead;
+ }
return expression(c, 0, true);
}
@@ -1105,6 +1141,28 @@ static int state_index(const Compiler *c, const char *name) {
return -1;
}
+/* A library's record, by name: its place among the libraries' records, or -1. */
+static int library_record(const Compiler *c, int library, const char *name) {
+ for (unsigned i = 0; i < c->library_record_count; ++i)
+ if (c->library_records[i].module == library && !strcmp(c->library_records[i].name, name)) return (int)i;
+ return -1;
+}
+
+/* A record as the file of module `from` writes it: its name, or LIBRARY.NAME for a library's. */
+static const char *record_shown(const Compiler *c, int from, const Record *r) {
+ static char shown[2 * NAME + 1];
+ if (r->module == from) return r->name;
+ snprintf(shown, sizeof(shown), "%s.%s", c->modules[r->module].name, r->name);
+ return shown;
+}
+
+/* The module's record of a library (0: the app) by name, or -1. */
+static int module_record(const Compiler *c, int library, const char *name) {
+ for (unsigned i = 0; i < c->record_count; ++i)
+ if (c->records[i].module == library && !strcmp(c->records[i].name, name)) return (int)i;
+ return -1;
+}
+
static int record_index(const Compiler *c, const char *name) {
for (unsigned i = 0; i < c->record_count; ++i) if (!strcmp(c->records[i].name, name)) return (int)i;
return -1;
@@ -1127,7 +1185,7 @@ static const char *declared_kind(const Compiler *c, const char *name) {
static const char *const states[] = {"a state", "a text state", "a cursor", "a list", "a list index", "a state", "a state"};
if (fn_index(c, c->module, name) >= 0) return "a fn";
if (used_library(c, c->module, name) >= 0) return "a library";
- if (c->module) return NULL; /* a library has only fns and uses */
+ if (c->module) return library_record(c, c->module, name) >= 0 ? "a record" : NULL; /* fns, records and uses */
int state = state_index(c, name);
if (state >= 0) return states[c->state_types[state]];
if (record_index(c, name) >= 0) return "a record";
@@ -1188,6 +1246,7 @@ static void message_like(Compiler *c, Input *in) {
if (c->field_count == FIELDS) fail_at(c, other.line, other.column, "at most %d message fields are allowed", FIELDS);
memcpy(in->field_names[in->fields], from->field_names[f], NAME);
memcpy(in->field_record[in->fields], from->field_record[f], NAME);
+ in->field_library[in->fields] = from->field_library[f];
in->field_line[in->fields] = from->field_line[f];
in->field_column[in->fields] = from->field_column[f];
in->field_types[in->fields++] = from->field_types[f];
@@ -1226,8 +1285,7 @@ static void message_fields(Compiler *c, Input *in, const char *group) {
/* rows: list of a record, a JSON array of objects; the handler walks them as a list */
if (in->kind == KIND_OUTPUT) fail_at(c, type.line, type.column, "an output sends no rows: send one message a row");
expect_word(c, "of");
- Token record = expect_name(c, "a record");
- memcpy(in->field_record[in->fields], record.text, NAME);
+ Token record = held_record(c, in->field_record[in->fields], &in->field_library[in->fields]);
in->field_line[in->fields] = record.line;
in->field_column[in->fields] = record.column;
field_type = FIELD_ROWS;
@@ -1276,12 +1334,16 @@ static Expr *plain_string(Compiler *c, const char *what) {
static void record_declaration(Compiler *c) {
Token name = expect_name(c, "record name");
declare(c, &name);
- if (c->record_count == RECORDS) fail_at(c, name.line, name.column, "at most 8 records are allowed");
- Record *r = &c->records[c->record_count];
+ bool library = c->module != 0; /* a library's: into the module once the app holds it in a list */
+ if (!library && c->record_count == RECORDS) fail_at(c, name.line, name.column, "at most 16 records are allowed");
+ if (library && c->library_record_count == LIBRARY_RECORDS)
+ fail_at(c, name.line, name.column, "the libraries declare at most %d records", LIBRARY_RECORDS);
+ Record *r = library ? &c->library_records[c->library_record_count] : &c->records[c->record_count];
memset(r, 0, sizeof(*r));
memcpy(r->name, name.text, NAME);
r->line = name.line;
r->column = name.column;
+ r->module = c->module;
expect(c, "{", "'{' on the line of the record name");
separators(c);
while (!is(c, "}")) {
@@ -1292,10 +1354,11 @@ static void record_declaration(Compiler *c) {
for (unsigned i = 0; i < r->fields; ++i)
if (!strcmp(r->field_names[i], field.text)) fail_at(c, field.line, field.column, "duplicate field '%s'", field.text);
if (r->fields == FIELDS_PER_RECORD) fail_at(c, field.line, field.column, "a record has at most 16 fields");
- if (c->record_fields == RECORD_FIELDS) fail_at(c, field.line, field.column, "at most 64 record fields are allowed");
+ if (!library && c->record_fields == RECORD_FIELDS)
+ fail_at(c, field.line, field.column, "at most 128 record fields are allowed");
unsigned index = r->fields++;
memcpy(r->field_names[index], field.text, NAME);
- ++c->record_fields;
+ c->record_fields += !library;
if (is(c, ":")) {
take(c);
Token type = expect_name(c, "text or i64");
@@ -1320,7 +1383,29 @@ static void record_declaration(Compiler *c) {
}
take(c);
if (!r->fields) fail_at(c, name.line, name.column, "a record needs at least one field");
- ++c->record_count;
+ if (library) ++c->library_record_count;
+ else ++c->record_count;
+}
+
+/* The record a list or a message's rows field holds: NAME, the app's, or
+ * LIBRARY.NAME, a library's the app uses, which goes into the module.
+ * Its name into name, its library (0: the app's) into library. */
+static Token held_record(Compiler *c, char name[NAME], int *library) {
+ Token first = expect_name(c, "a record");
+ *library = 0;
+ memcpy(name, first.text, NAME);
+ if (!is(c, ".")) return first;
+ take(c);
+ Token second = expect_name(c, "the library's record");
+ int used = used_library(c, c->module, first.text);
+ if (used < 0) fail_at(c, first.line, first.column, "unknown library '%s'; write use %s first", first.text, first.text);
+ int found = library_record(c, used, second.text);
+ if (found < 0) fail_at(c, second.line, second.column, "library '%s' has no record '%s'", first.text, second.text);
+ Record *r = &c->library_records[found];
+ if (!r->used) r->used = true, r->used_line = second.line, r->used_column = second.column;
+ *library = used;
+ memcpy(name, second.text, NAME);
+ return second;
}
static void state_declaration(Compiler *c) {
@@ -1365,8 +1450,8 @@ static void state_declaration(Compiler *c) {
} else if (!strcmp(type.text, "list") || !strcmp(type.text, "index")) {
c->state_types[index] = type.text[0] == 'l' ? STATE_LIST : STATE_INDEX;
expect_word(c, "of");
- Token target = expect_name(c, type.text[0] == 'l' ? "a record" : "a list state");
- memcpy(c->state_target[index], target.text, NAME);
+ if (type.text[0] == 'l') held_record(c, c->state_target[index], &c->state_library[index]);
+ else memcpy(c->state_target[index], expect_name(c, "a list state").text, NAME);
if (type.text[0] == 'l' && word(c, "by")) { /* keyed: items in key order, each key once */
take(c);
Token key = expect_name(c, "a field of the record");
@@ -1516,12 +1601,13 @@ static void use_declaration(Compiler *c) {
c->modules[c->module].uses[library] = true;
}
-/* A library declares fns and uses only: it has no state and sees none. */
+/* A library declares fns, records and uses only: it has no state and sees none. */
static void library_declaration(Compiler *c) {
- Token kind = expect_name(c, "fn or use");
+ Token kind = expect_name(c, "fn, record or use");
if (!strcmp(kind.text, "fn")) fn_declaration(c);
+ else if (!strcmp(kind.text, "record")) record_declaration(c);
else if (!strcmp(kind.text, "use")) use_declaration(c);
- else fail_at(c, kind.line, kind.column, "a library declares only fn and use");
+ else fail_at(c, kind.line, kind.column, "a library declares only fn, record and use");
end(c);
}
@@ -1760,12 +1846,12 @@ static int list_state(Compiler *c, const char *name, int line, int column, const
}
static const Record *record_of(const Compiler *c, int list) {
- return &c->records[record_index(c, c->state_target[list])];
+ return &c->records[module_record(c, c->state_library[list], c->state_target[list])];
}
static unsigned record_field(Compiler *c, const Record *r, const char *field, int line, int column) {
for (unsigned i = 0; i < r->fields; ++i) if (!strcmp(r->field_names[i], field)) return i;
- fail_at(c, line, column, "record '%s' has no field '%s'", r->name, field);
+ fail_at(c, line, column, "record '%s' has no field '%s'", record_shown(c, c->module, r), field);
}
/* A record field's kind: text, a bool or a number. */
@@ -2516,10 +2602,16 @@ static Expr *spread_field(Compiler *c, const Expr *spread, const char *name) {
* given): those written, and the rest its spread's source has by name. */
static void record_values(Compiler *c, const Expr *e, int list, const Expr **values) {
const Record *r = record_of(c, list);
+ char shown[2 * NAME + 1];
+ snprintf(shown, sizeof(shown), "%s", record_shown(c, c->module, r));
if (e->kind != E_RECORD)
fail_at(c, e->line, e->column, "'%s' holds %s records, as in %s.append(%s { %s: ... })", c->state_names[list],
- r->name, c->state_names[list], r->name, r->field_names[0]);
- if (strcmp(e->name, r->name)) fail_at(c, e->line, e->column, "'%s' holds %s, not %s", c->state_names[list], r->name, e->name);
+ shown, c->state_names[list], shown, r->field_names[0]);
+ /* The record it names, as the file it is written in names it: its own, or a library's it uses. */
+ int from = e->member[0] ? used_library(c, c->module, e->member) : c->module;
+ if (from != r->module || strcmp(e->name, r->name))
+ fail_at(c, e->line, e->column, "'%s' holds %s, not %s%s%s", c->state_names[list], shown, e->member,
+ e->member[0] ? "." : "", e->name);
for (unsigned k = 0; k < r->fields; ++k) values[k] = NULL;
for (const Expr *f = e->args[0]; f; f = f->next) {
if (!f->member[0]) continue; /* the spread, below */
@@ -2874,6 +2966,7 @@ static void rows_lists(Compiler *c) {
unsigned index = c->states++;
memcpy(c->state_names[index], name, (size_t)length + 1);
memcpy(c->state_target[index], in->field_record[f], NAME);
+ c->state_library[index] = in->field_library[f];
c->state_types[index] = STATE_LIST;
c->state_line[index] = in->field_line[f];
c->state_column[index] = in->field_column[f];
@@ -2883,10 +2976,45 @@ static void rows_lists(Compiler *c) {
}
}
+/* The libraries' records the app's lists and rows fields hold go into the
+ * module after the app's own: library by library as they were read, each
+ * in its declaration order (so records moved from the top of an app into
+ * a library it uses first leave the module as it was). In a module the
+ * records share one namespace with the states, inputs, views and
+ * collections (Pure's), however they were written. */
+static void pull_records(Compiler *c) {
+ for (unsigned i = 0; i < c->library_record_count; ++i) {
+ const Record *r = &c->library_records[i];
+ if (!r->used) continue;
+ const char *library = c->modules[r->module].name;
+ int other = record_index(c, r->name), input = input_index(c, r->name);
+ const char *taken = other >= 0 ? NULL : state_index(c, r->name) >= 0 ? "a state" :
+ input >= 0 ? (c->inputs[input].kind == KIND_OUTPUT ? "an output" : "an input") : NULL;
+ for (unsigned v = 0; !taken && other < 0 && v < c->views; ++v)
+ if (!c->view_table[v] && !strcmp(c->view_names[v], r->name)) taken = "a view";
+ for (unsigned k = 0; !taken && other < 0 && k < c->collections; ++k)
+ if (!strcmp(c->collection_names[k], r->name)) taken = "a collection";
+ if (other >= 0)
+ fail_at(c, r->used_line, r->used_column, "%s.%s is '%s' in the module, as %s is: two records cannot share a name",
+ library, r->name, r->name, record_shown(c, 0, &c->records[other]));
+ if (taken)
+ fail_at(c, r->used_line, r->used_column, "%s.%s is '%s' in the module, and '%s' is %s here", library, r->name,
+ r->name, r->name, taken);
+ if (c->record_count == RECORDS)
+ fail_at(c, r->used_line, r->used_column, "at most 16 records are allowed (%s.%s is one more)", library, r->name);
+ if (c->record_fields + r->fields > RECORD_FIELDS)
+ fail_at(c, r->used_line, r->used_column, "at most 128 record fields are allowed (%s.%s has %u more)", library,
+ r->name, r->fields);
+ c->records[c->record_count++] = *r;
+ c->record_fields += r->fields;
+ }
+}
+
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);
+ pull_records(c);
for (unsigned i = 0; i < c->states; ++i) {
if (c->state_types[i] == STATE_TEXT) {
Expr empty = {.kind = E_TEXT, .line = c->state_line[i], .column = c->state_column[i]};
@@ -2899,7 +3027,11 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
"an index points into a list state; '%s' is not one", c->state_target[i]);
state_ref[i] = (uint32_t)target;
} else if (c->state_types[i] == STATE_LIST) {
- int record = record_index(c, c->state_target[i]);
+ int record = module_record(c, c->state_library[i], c->state_target[i]);
+ for (unsigned l = 1; record < 0 && l < c->module_count; ++l) /* a library's, written without its name */
+ if (c->modules[0].uses[l] && library_record(c, (int)l, c->state_target[i]) >= 0)
+ fail_at(c, c->state_line[i], c->state_column[i], "'%s' is library %s's record: write %s.%s",
+ c->state_target[i], c->modules[l].name, c->modules[l].name, c->state_target[i]);
if (record < 0)
fail_at(c, c->state_line[i], c->state_column[i], "a list holds a record; '%s' is not one", c->state_target[i]);
state_ref[i] = (uint32_t)record;
diff --git a/tests/check.py b/tests/check.py
index df670ee..fdb7060 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -915,7 +915,7 @@ def libraries(aup, pure_run, work):
assert shown['views'] == views, (source, shown['views'])
bad = [
('use loopa\n', 'loopb.aup:1:5', "library 'loopa' uses itself through its uses"),
- ('use stateful\n', 'stateful.aup:2:1', 'a library declares only fn and use'),
+ ('use stateful\n', 'stateful.aup:2:1', 'a library declares only fn, record and use'),
('use peek\nstate x = 1\nview v = peek.f(1)\n', 'peek.aup:1:15',
"'x' is not a parameter; a library fn reads only its parameters (in fn 'f' called at 3:15)"),
('use shapes\nview v = shapes.volume(1)\n', 'main.aup:2:17', "library 'shapes' has no fn 'volume'"),
@@ -926,6 +926,91 @@ def libraries(aup, pure_run, work):
return len(good), len(bad)
+
+def library_records(aup, pure_run, work):
+ """A library declares records (docs/design/002, Pure's review 005): an
+ app writes them LIBRARY.NAME, and they are NAME in its module, so the
+ task list with its record moved into a library builds the same module,
+ byte for byte. Only the records the app's lists and rows hold go into
+ its module. A second module using the same library (a migration) reads
+ and changes what the app stored, and the app reads it after. Names are
+ one namespace in a module, however they were written."""
+ import re
+ import struct
+ lib = work / 'record-libs'
+ lib.mkdir(parents=True, exist_ok=True)
+ env = {'PATH': '/usr/bin:/bin'}
+
+ def build(name, source):
+ path = lib / f'{name}.aup'
+ path.write_text(source)
+ return subprocess.run([str(aup), 'build', str(path), '-o', str(lib / f'{name}.pmod')], capture_output=True,
+ text=True, env=env, timeout=60)
+
+ # The task list, its record moved into a library: the same module.
+ tasks = (ROOT / 'examples' / 'tasks.aup').read_text()
+ record = re.search(r'record Task \{.*?\n\}\n', tasks, re.S).group(0)
+ (lib / 'words.aup').write_text((ROOT / 'examples' / 'words.aup').read_text())
+ (lib / 'taskrecords.aup').write_text(record)
+ moved = 'use taskrecords\n' + tasks.replace(record, '').replace('list of Task', 'list of taskrecords.Task') \
+ .replace('Task {', 'taskrecords.Task {')
+ assert build('tasks-before', tasks).returncode == 0 and build('tasks-moved', moved).returncode == 0
+ assert (lib / 'tasks-before.pmod').read_bytes() == (lib / 'tasks-moved.pmod').read_bytes()
+ # Only the records used: one of three.
+ (lib / 'people.aup').write_text('record Person { name: text }\nrecord Team { name: text, size = 0 }\n'
+ 'record Badge { label: text }\nfn big(n) = n > 10\n')
+ assert build('teams', 'use people\nstate teams: list of people.Team\ninput add = text\n'
+ 'on add { teams.append(people.Team { name: add, size: 12 }) }\n'
+ 'view big = count(t in teams where people.big(t.size))\n').returncode == 0
+ module = (lib / 'teams.pmod').read_bytes()
+ assert struct.unpack_from('<H', module, 30)[0] == 1, struct.unpack_from('<H', module, 30)
+ lines = run(pure_run, lib / 'teams.pmod', stdin='type add 61\nshow\n').stdout.splitlines()
+ assert json.loads(lines[-1])['views'] == {'big': 1}, lines[-1]
+ # A migration: another module of the same library changes what the app stored.
+ (lib / 'store.aup').write_text('record User { name: text, points = 0 }\n')
+ assert build('main', 'use store\nstate users: list of store.User by name\ninput join = text\n'
+ 'on join { users.add(store.User { name: join }) }\n'
+ 'view count = users.count\nview points = sum(u.points for u in users)\n').returncode == 0
+ assert build('migrate', 'use store\nstate users: list of store.User by name\ninput bump = key Space\n'
+ 'on bump.down {\n for u in users {\n u.points = u.points + 10\n }\n}\n').returncode == 0
+ store = lib / 'users-store'
+ run(pure_run, lib / 'main.pmod', '--store', store, stdin='type join 616e6e\ntype join 626f\ntype join 6379\n')
+ run(pure_run, lib / 'migrate.pmod', '--store', store, stdin='down bump\nup bump\n')
+ shown = json.loads(run(pure_run, lib / 'main.pmod', '--store', store, stdin='show\n').stdout.splitlines()[-1])
+ assert shown['views'] == {'count': 3, 'points': 30}, shown
+ # A message's rows of a library's record.
+ assert build('rows', 'use store\ninput load = message {\n rows: list of store.User\n}\nstate n = 0\n'
+ 'on load {\n n = n + load.rows.count\n}\nview loaded = n\n').returncode == 0, \
+ build('rows', '').stderr
+ (lib / 'other.aup').write_text('record User { name: text }\n')
+ (lib / 'twofold.aup').write_text('record Item { n = 0 }\nfn Item = 1\n')
+ (lib / 'stateful.aup').write_text('record R { n = 0 }\nstate x = 0\n')
+ bad = [
+ ('use store\nstate users: list of User\n', 'main.aup:2:7', "'User' is library store's record: write store.User"),
+ ('use store\nstate users: list of store.Nope\n', 'main.aup:2:28', "library 'store' has no record 'Nope'"),
+ ('state users: list of nope.User\n', 'main.aup:1:22', "unknown library 'nope'; write use nope first"),
+ ('use store\nrecord User { n = 0 }\nstate users: list of store.User\n', 'main.aup:3:28',
+ "store.User is 'User' in the module, as User is: two records cannot share a name"),
+ ('use store\nuse other\nstate a: list of store.User\nstate b: list of other.User\n', 'main.aup:4:24',
+ "other.User is 'User' in the module, as store.User is: two records cannot share a name"),
+ ('use store\nstate User = 0\nstate users: list of store.User\n', 'main.aup:3:28',
+ "store.User is 'User' in the module, and 'User' is a state here"),
+ ('use store\nuse other\nstate users: list of store.User\ninput j = text\n'
+ 'on j { users.append(other.User { name: j }) }\n', 'main.aup:5:27', "'users' holds store.User, not other.User"),
+ ('use store\nstate users: list of store.User\ninput j = text\non j { users.append(User { name: j }) }\n',
+ 'main.aup:4:21', "'users' holds store.User, not User"),
+ ('use store\nstate users: list of store.User\ninput j = text\non j { users.append(store.Nope { name: j }) }\n',
+ 'main.aup:4:27', "library 'store' has no record 'Nope'"),
+ ('use twofold\n', 'twofold.aup:2:4', "'Item' is already declared as a record"),
+ ('use stateful\n', 'stateful.aup:2:1', 'a library declares only fn, record and use'),
+ ]
+ for source, where, message in bad:
+ result = build('main', source)
+ assert result.returncode == 1 and f'{where}: {message}' in result.stderr, (source, where, message, result.stderr)
+ return {'moved_module_bytes': len((lib / 'tasks-moved.pmod').read_bytes()), 'migrated_points': 30,
+ 'diagnostics': len(bad)}
+
+
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
@@ -1624,6 +1709,7 @@ def main():
report['keyed_snapshots'], report['keyed_failed_events'] = keyed(aup, pure_run, work, range(args.seeds))
report['stored_snapshots'], report['stored_restarts'] = stored_runs(aup, pure_run, work, range(args.text_seeds))
report['library_programs'], report['library_diagnostics'] = libraries(aup, pure_run, work)
+ report['library_records'] = library_records(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))