aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compiler/compile.c1067
-rw-r--r--docs/design/002-dsl-direction.md70
-rw-r--r--examples/json_tree.aup247
-rw-r--r--tests/check.py155
4 files changed, 1292 insertions, 247 deletions
diff --git a/compiler/compile.c b/compiler/compile.c
index c7e684b..e03a98e 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -22,7 +22,7 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR
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, LOOP, BREAK, I64_BITS, BYTE_AT, BYTES_OF, INDEX_AT, BYTES_SLICE, BYTE, BYTES_LOAD, BYTES_STORE,
- LIST_VALUE, LIST_BIND, APPEND_IN, NUM_PARSE };
+ LIST_VALUE, LIST_BIND, APPEND_IN, NUM_PARSE, CALL, RETURN, CHAR_AT, CHAR };
/* Pure's values held by name: 0-15 bytes, 16-31 texts; BYTES_STORE's bit 8 appends. NUM_TEXT's format (Pure 027). */
enum { HELD = 16, HELD_TEXT = 16, HELD_APPEND = 1 << 8 };
enum { FORMAT_WIDTH = 8, FORMAT_ZEROS = 1 << 16, FORMAT_FORM = 17, FORMAT_DIGITS = 20,
@@ -42,9 +42,11 @@ 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,
UNIT_CHAR = 0, UNIT_LINE = 1 };
/* Pure's view types, record field types (0 a number, 1 a text, 2 a bool, 3 an i64) and what a local holds. */
-enum { VIEW_NUM = 0, VIEW_TEXT = 1, VIEW_TABLE = 2, VIEW_BOOL = 3, VIEW_I64 = 4, LOCAL_BOOL = 2, LOCAL_I64 = 3 };
-typedef enum { NUM = 1, BYTES, TEXT, CURSOR, UNIT, INDEX, BOOL, I64, LIST } Type; /* LIST: a list in a record (Pure 027) */
-typedef struct { Type type; int owner; } Kind; /* owner: a cursor's text state or an index's list state */
+enum { VIEW_NUM = 0, VIEW_TEXT = 1, VIEW_TABLE = 2, VIEW_BOOL = 3, VIEW_I64 = 4, LOCAL_BOOL = 2, LOCAL_I64 = 3, LOCAL_LIST = 4 };
+/* LIST: a list in a record (Pure 027). RECORD: a record's or a variant's value, its fields on the stack in its order
+ * (027's 2c); TUPLE: the values a fn gives (owner: its instance); NOTHING: what a fn that gives none gives. */
+typedef enum { NUM = 1, BYTES, TEXT, CURSOR, UNIT, INDEX, BOOL, I64, LIST, RECORD, TUPLE, NOTHING } Type;
+typedef struct { Type type; int owner; } Kind; /* owner: a cursor's text state, an index's list state, a record's or a list's record */
typedef enum { T_EOF, T_NL, T_NUMBER, T_NAME, T_PUNCT, T_STRING } TokenKind;
typedef struct {
@@ -92,7 +94,7 @@ 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, S_COUNT, S_BREAK, S_RETURN, S_ASSIGNS, S_LETS, S_MATCH, S_ARM } StmtKind;
+ S_LET, S_COUNT, S_BREAK, S_RETURN, S_ASSIGNS, S_LETS, S_MATCH, S_ARM, S_CALL } StmtKind;
typedef struct Stmt {
StmtKind kind;
int line, column;
@@ -154,15 +156,37 @@ typedef struct {
bool variant, typed, flattened, flattening;
} Record;
-/* A fn: one expression over its parameters, inlined where it is called. */
+/* A fn: one expression over its parameters, inlined where it is called; or, with a block (statements and returns) or
+ * calling itself, a function of Pure's (027's 2c), its results' types written after -> or given by its returns. */
typedef struct {
char name[NAME], params[PARAMS][NAME];
unsigned param_count;
int module, line, column;
Expr *body;
+ Stmt *block;
+ struct { char name[NAME]; bool list; int line, column; } results[PARAMS];
+ unsigned result_count;
+ bool typed; /* -> its results' types */
bool active; /* being inlined: a call back into it is recursion */
+ bool called; /* run as a function (a block, or a cycle of calls through it) */
} Fn;
+typedef struct { uint8_t op; uint32_t arg; union { double value; int64_t integer; }; } Op;
+
+/* A fn run as a function, once for each kinds of arguments it is called with (an item's as its record's value): Pure's
+ * function of the same number. Its results are known once declared or a return gave them; its code is its own (ops,
+ * count of them: the module's code after the handlers' and views', at start, its jumps moved with it). */
+enum { INSTANCES = 64 };
+typedef struct {
+ int fn;
+ Kind params[PARAMS], results[STACK];
+ unsigned param_count, result_count;
+ bool known;
+ Op *ops;
+ unsigned count, capacity;
+ uint32_t start;
+} Instance;
+
/* The fn being inlined: where its arguments are on the stack and their kinds. */
typedef struct Scope {
int fn, base, line, column;
@@ -231,9 +255,12 @@ typedef struct {
Scope *fold_scope;
/* 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];
+ /* (027's 2c) A record's value held by name: the name (its record its kind, no slot) and name.field for each of its
+ * fields; variant: a variant's case; alias: a match's name for a case's value (read, never assigned). */
+ struct { char name[NAME]; unsigned slot; Kind kind; int list; bool counter, variant, alias; } locals[LOCALS];
unsigned local_count, local_slots, held_bytes, held_texts; /* held: the bytes' and texts' lets' slots */
unsigned whiles; /* while loops so far: each counts by a name of its own */
+ unsigned settled; /* records' values calls gave, held by names of their own (027's 2c) */
const struct Expr *given; /* the left side of the && or || whose right side faultless looks at */
/* A match's bindings (027's 2b): a name for the item's field at path (the item an index in a local, of list), or
* for its fields under path (prefix: a record's or a variant's). */
@@ -258,8 +285,12 @@ typedef struct {
Scope *scope; /* the fn being inlined, or NULL */
unsigned joins; /* branches ahead that carry a value */
Type want; /* what the expression value() compiles next is wanted as: an i64 makes a literal one */
- struct { uint8_t op; uint32_t arg; union { double value; int64_t integer; }; } code[CODE];
- unsigned code_count;
+ /* The code being made: the handlers' and views' (ops), or a function's (its instance's, growing). */
+ Op ops[CODE], *code;
+ unsigned code_count, code_capacity;
+ Instance instances[INSTANCES];
+ unsigned instance_count;
+ int current; /* the instance whose code is being made, or -1 */
uint32_t view_code[VIEWS][2], handler_code[HANDLERS][2];
int view_types[VIEWS];
unsigned char constant_data[CONSTANT_BYTES];
@@ -407,7 +438,7 @@ static Token scan(Compiler *c) {
token.kind = T_NAME;
return token;
}
- static const char *const pairs[] = {"<=", ">=", "==", "!=", "&&", "||", "..", "<<", ">>"};
+ static const char *const pairs[] = {"<=", ">=", "==", "!=", "&&", "||", "..", "<<", ">>", "->"};
for (size_t i = 0; i < sizeof(pairs) / sizeof(*pairs); ++i)
if (ch == pairs[i][0] && peek_char(c, 1) == pairs[i][1]) {
memcpy(token.text, pairs[i], 3);
@@ -540,11 +571,12 @@ static void index_key(Compiler *c) {
/* (a, b, ...) after a fn name. */
/* Arguments once the opening parenthesis is taken. */
+static Expr *inserted_value(Compiler *c, bool multiline);
static Expr *arguments_after(Compiler *c, Expr *call) {
newlines(c);
while (!is(c, ")")) {
if (call->count == PARAMS) fail_at(c, c->token.line, c->token.column, "a call takes at most 8 arguments");
- call->args[call->count++] = expression(c, 0, true);
+ call->args[call->count++] = inserted_value(c, true); /* a record's value written out too (027's 2c) */
newlines(c);
if (!is(c, ",")) break;
take(c);
@@ -886,7 +918,7 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) {
} else if (token.kind == T_NAME) {
static const struct { const char *name; unsigned count; int op; } calls[] =
{{"min", 2, MIN}, {"max", 2, MAX}, {"clamp", 3, CLAMP}, {"text", 1, TEXT_FROM}, {"bytes", 1, BYTES_FROM},
- {"floor", 1, FLOOR}, {"i64", 1, I64_FROM}, {"number", 1, NUM_FROM}, {"byte", 1, BYTE}};
+ {"floor", 1, FLOOR}, {"i64", 1, I64_FROM}, {"number", 1, NUM_FROM}, {"byte", 1, BYTE}, {"char", 1, CHAR}};
int call = -1, how = -1;
for (int i = 0; i < (int)(sizeof(calls) / sizeof(*calls)); ++i) if (!strcmp(token.text, calls[i].name)) call = i;
static const char *const folds[] = {"count", "sum", "max", "min", "first", "distinct"};
@@ -1157,14 +1189,14 @@ static void several(Compiler *c, Stmt *s, const char *first) {
Expr **tail = &s->value;
unsigned values = 0;
for (;;) {
- *tail = expression(c, 0, false);
+ *tail = inserted_value(c, false);
tail = &(*tail)->next;
++values;
if (!is(c, ",")) break;
take(c);
newlines(c); /* a line may end after a comma */
}
- if (values != s->name_count)
+ if (values != s->name_count && values != 1) /* one: a fn's call that gives them (027's 2c) */
fail_at(c, s->line, s->column, "%u names take %u values, not %u", s->name_count, s->name_count, values);
}
@@ -1238,7 +1270,7 @@ static Stmt *statement(Compiler *c, unsigned depth) {
return s;
}
expect(c, "=", "'='");
- s->value = expression(c, 0, false);
+ s->value = inserted_value(c, false);
return s;
}
if (!strcmp(token.text, "match")) {
@@ -1305,9 +1337,17 @@ static Stmt *statement(Compiler *c, unsigned depth) {
s->kind = S_BREAK;
return s;
}
- if (!strcmp(token.text, "return")) { /* a view's body's last line: its value */
+ if (!strcmp(token.text, "return")) { /* a view's body's last line: its value; in a fn's block, what it gives (none or several) */
s->kind = S_RETURN;
- s->value = expression(c, 0, false);
+ if (c->token.kind == T_NL || is(c, "}") || is(c, ";")) return s;
+ Expr **tail = &s->value;
+ for (;;) {
+ *tail = inserted_value(c, false);
+ tail = &(*tail)->next;
+ if (!is(c, ",")) break;
+ take(c);
+ newlines(c);
+ }
return s;
}
if (!strcmp(token.text, "for")) {
@@ -1369,6 +1409,13 @@ static Stmt *statement(Compiler *c, unsigned depth) {
}
if (reserved_name(token.text)) fail_at(c, token.line, token.column, "expected a statement or '}'");
memcpy(s->name, token.text, NAME);
+ if (is(c, "(")) { /* f(x): a fn's call for what it does (027's 2c) */
+ s->kind = S_CALL;
+ Expr *call = node(c, E_FN, token.line, token.column);
+ memcpy(call->name, token.text, NAME);
+ s->value = arguments(c, call);
+ return s;
+ }
if (is(c, "[") && !(lookahead(c)->kind == T_NAME && !strcmp(lookahead(c)->text, "key"))) {
/* tasks[at].field = value */
take(c);
@@ -1474,7 +1521,7 @@ static Stmt *statement(Compiler *c, unsigned depth) {
}
s->kind = S_ASSIGN;
expect(c, "=", "'='");
- s->value = expression(c, 0, false);
+ s->value = inserted_value(c, false);
return s;
}
@@ -1984,8 +2031,31 @@ static void fn_declaration(Compiler *c) {
}
expect(c, ")", "')'");
}
- expect(c, "=", "'='");
- f->body = expression(c, 0, false);
+ if (is(c, "->")) { /* its results' types (027's 2c): number, i64, bool, text, bytes, a record or list of one */
+ take(c);
+ f->typed = true;
+ do {
+ if (f->result_count) take(c);
+ Token type = expect_name(c, "a type: number, i64, bool, text, bytes, a record or list of one");
+ bool list = !strcmp(type.text, "list");
+ if (list) {
+ expect_word(c, "of");
+ type = expect_name(c, "a record");
+ }
+ if (f->result_count == PARAMS) fail_at(c, type.line, type.column, "a fn gives at most 8 values");
+ memcpy(f->results[f->result_count].name, type.text, NAME);
+ f->results[f->result_count].list = list;
+ f->results[f->result_count].line = type.line, f->results[f->result_count].column = type.column;
+ ++f->result_count;
+ } while (is(c, ","));
+ }
+ if (is(c, "{")) { /* a block: statements, and returns (027's 2c) */
+ f->block = block(c, 1);
+ f->called = true;
+ } else {
+ expect(c, "=", f->typed ? "'=' or '{'" : "'=', '{' or '->'");
+ f->body = expression(c, 0, false);
+ }
++c->fn_count;
}
@@ -2229,6 +2299,12 @@ static void declaration(Compiler *c) {
static void emit(Compiler *c, int line, int column, uint8_t op, uint32_t arg, double value) {
if (c->code_count == CODE) fail_at(c, line, column, "program exceeds 65535 instructions");
+ if (c->code_count == c->code_capacity) { /* a function's code grows */
+ Op *grown = realloc(c->code, 2 * c->code_capacity * sizeof(*grown));
+ if (!grown) fail_at(c, line, column, "out of memory");
+ c->code = grown, c->code_capacity *= 2;
+ c->instances[c->current].ops = grown;
+ }
c->code[c->code_count].op = op;
c->code[c->code_count].arg = arg;
c->code[c->code_count++].value = value;
@@ -2242,7 +2318,7 @@ static void emit_integer(Compiler *c, int line, int column, int64_t value) {
static const char *type_name(Type type) {
static const char *const names[] = {"", "a number", "bytes", "text", "a cursor", "a distance", "an index", "a bool", "an i64",
- "a list"};
+ "a list", "a record", "several values", "no value"};
return names[type];
}
@@ -2342,6 +2418,9 @@ static Type message_kind(int type) {
static Kind value(Compiler *c, const Expr *e, bool handler, int height);
static void expect_kind(Compiler *c, const Expr *e, Kind actual, Type type) {
+ const char *name = e->kind == E_MEMBER ? e->member : e->name;
+ if (actual.type != type && actual.type == RECORD && c->records[actual.owner].variant && *name)
+ fail_at(c, e->line, e->column, "'%s' holds a variant: match it, as in match %s { ... }", name, name);
if (actual.type != type) fail_at(c, e->line, e->column, "expected %s, not %s", type_name(type), type_name(actual.type));
}
@@ -2407,10 +2486,11 @@ static bool literal_only(const Expr *e) {
/* The type e has, compiled and taken back: what literals beside it take. */
static Type probe(Compiler *c, const Expr *e, bool handler, int height) {
- unsigned code = c->code_count, constants = c->constants, joins = c->joins;
+ unsigned code = c->code_count, constants = c->constants, joins = c->joins, instances = c->instance_count;
size_t used = c->constant_used;
Type type = value(c, e, handler, height).type;
- c->code_count = code, c->constants = constants, c->constant_used = used, c->joins = joins;
+ c->code_count = code, c->joins = joins;
+ if (c->instance_count == instances) c->constants = constants, c->constant_used = used; /* a function made keeps its own */
return type;
}
@@ -2530,6 +2610,10 @@ static int collection_index(const Compiler *c, const char *name) {
return -1;
}
+static Kind call_fn(Compiler *c, const Expr *e, int fn, bool handler, int height, Kind *kinds, unsigned given);
+static bool variant_case(Compiler *c, const Expr *e, int *variant, unsigned *index, const Expr **payload);
+static bool composite(const Record *r, const char *path);
+
/* A fn inlined: its arguments go on the stack once, the body reads them with
* PICK, and SLIDE leaves only the result. e names the call (E_FN), or is the
* name of a fn without parameters. */
@@ -2547,7 +2631,14 @@ static Kind inline_fn(Compiler *c, const Expr *e, int module, const char *name,
fail_at(c, e->line, e->column, "%s expects %u argument%s", name, f->param_count, f->param_count == 1 ? "" : "s");
if (f->active) fail_at(c, e->line, e->column, "fn '%s' calls itself", name);
Scope scope = {.fn = index, .base = height, .line = e->line, .column = e->column};
- for (unsigned i = 0; i < count; ++i) scope.kinds[i] = value(c, e->args[i], handler, height + (int)i);
+ for (unsigned i = 0; i < count; ++i) {
+ scope.kinds[i] = value(c, e->args[i], handler, height + (int)i);
+ if (scope.kinds[i].type == RECORD || scope.kinds[i].type == TUPLE || scope.kinds[i].type == NOTHING) {
+ if (scope.kinds[i].type != RECORD) fail_at(c, e->args[i]->line, e->args[i]->column, "an argument is one value");
+ /* a record's value: the fn runs as a function, its arguments so far on the stack (027's 2c) */
+ return call_fn(c, e, index, handler, height, scope.kinds, i + 1);
+ }
+ }
Scope *outer = c->scope;
int caller = c->module;
c->scope = &scope;
@@ -2660,6 +2751,7 @@ static bool holds_walk(const Stmt *s) {
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 :
+ kind->type == LIST ? (uint32_t)LOCAL_LIST << 8 | (uint32_t)kind->owner << 16 :
kind->type == BOOL ? (uint32_t)LOCAL_BOOL << 8 : kind->type == I64 ? (uint32_t)LOCAL_I64 << 8 : 0);
}
@@ -2847,6 +2939,401 @@ static bool faultless(Compiler *c, const Expr *e) {
}
}
+/* ---- Fns run as Pure's functions, and records' values (027's 2c) */
+
+static void statements(Compiler *c, const Stmt *s);
+static int typed_record(Compiler *c, const char *type, bool list, int line, int column);
+static void record_values_of(Compiler *c, const Expr *e, const Record *r, const char *label, const Expr **values, int height);
+static void record_fields_of(Compiler *c, const Expr *e, const Record *r, const Expr **values, int height);
+static int scalar_record(Compiler *c, const char *name, int line, int column);
+
+/* The stack values a value of a kind takes: a record's fields, else one. */
+static unsigned cells(const Compiler *c, Kind kind) { return kind.type == RECORD ? c->records[kind.owner].fields : 1; }
+
+static bool same_kind(Kind a, Kind b) {
+ return a.type == b.type && (a.owner == b.owner || (a.type != RECORD && a.type != LIST && a.type != INDEX && a.type != CURSOR));
+}
+
+/* A kind as written: the name of a type (a record or a variant its value), or list of a record. */
+static Kind written_kind(Compiler *c, int module, const char *name, bool list, int line, int column) {
+ static const struct { const char *name; Type type; } scalars[] = {{"number", NUM}, {"i64", I64}, {"bool", BOOL}, {"text", TEXT},
+ {"bytes", BYTES}};
+ for (unsigned k = 0; !list && k < sizeof(scalars) / sizeof(*scalars); ++k)
+ if (!strcmp(name, scalars[k].name)) return (Kind){scalars[k].type, -1};
+ int record = module_record(c, module, name);
+ if (record < 0 && list) record = scalar_record(c, name, line, column);
+ if (record < 0) fail_at(c, line, column, "unknown type '%s': number, i64, bool, text, bytes, a record or list of one", name);
+ return (Kind){list ? LIST : RECORD, record};
+}
+
+/* The record or variant a record holds at a path of its fields as written (at, a.b, or a variant's case pt), or -1. */
+static int sub_record(Compiler *c, int record, const char *path) {
+ const Record *r = &c->records[record];
+ size_t n = strcspn(path, ".");
+ for (unsigned i = 0; i < r->declared_count; ++i)
+ if (strlen(r->declared[i].name) == n && !strncmp(r->declared[i].name, path, n) && *r->declared[i].type &&
+ !r->declared[i].list) {
+ int inner = typed_record(c, r->declared[i].type, false, r->declared[i].line, r->declared[i].column);
+ return path[n] ? sub_record(c, inner, path + n + 1) : inner;
+ }
+ return -1;
+}
+
+/* A slot of the block for a let of bytes or text (Pure's: 0-15 bytes, 16-31 texts). */
+static unsigned held_slot_at(Compiler *c, Type type, int line, int column) {
+ unsigned *count = type == BYTES ? &c->held_bytes : &c->held_texts;
+ if (*count == HELD) fail_at(c, line, column, "at most %d lets of %s are allowed in a handler", HELD, type_name(type));
+ return (type == TEXT ? HELD_TEXT : 0) + (*count)++;
+}
+
+/* A name for a value of a kind in the block's scope: a let's slot, held by name (bytes, text), or a record's: the name
+ * (its record its kind, no slot) and name.field for each field. */
+static void name_value(Compiler *c, const char *name, Kind kind, int line, int column) {
+ if (c->local_count == LOCALS) fail_at(c, line, column, "at most %d lets are allowed in a handler", LOCALS);
+ unsigned local = c->local_count++;
+ memset(&c->locals[local], 0, sizeof(c->locals[local]));
+ snprintf(c->locals[local].name, NAME, "%s", name);
+ c->locals[local].kind = kind;
+ c->locals[local].list = -1;
+ if (kind.type == RECORD) {
+ const Record *r = &c->records[kind.owner];
+ for (unsigned k = 0; k < r->fields; ++k) {
+ char field[2 * NAME + 2];
+ snprintf(field, sizeof(field), "%s.%s", name, r->field_names[k]);
+ if (strlen(field) >= NAME) fail_at(c, line, column, "'%s' passes 31 bytes: give '%s' a shorter name", field, name);
+ name_value(c, field, field_value(r, k), line, column);
+ c->locals[c->local_count - 1].variant = r->field_variant[k] != 0;
+ }
+ } else if (kind.type == BYTES || kind.type == TEXT) c->locals[local].slot = held_slot_at(c, kind.type, line, column);
+ else if (kind.type == NUM || kind.type == BOOL || kind.type == I64 || kind.type == LIST || kind.type == INDEX) {
+ if (c->local_slots == LOCALS) fail_at(c, line, column, "at most %d lets are allowed in a handler", LOCALS);
+ c->locals[local].slot = c->local_slots++;
+ } else fail_at(c, line, column, "a name holds a number, a bool, an i64, bytes, text, a list or a record, not %s", type_name(kind.type));
+}
+
+/* The local that holds field k of record r at base: a name's own record's (root: base.field), or a record under a
+ * name's (the variant's case is base itself, its other fields base.field). */
+static void field_path(const Record *r, unsigned k, const char *base, bool root, char out[2 * NAME + 2]) {
+ if (!root && r->field_variant[k] && !strcmp(r->field_names[k], "case")) snprintf(out, 2 * NAME + 2, "%s", base);
+ else snprintf(out, 2 * NAME + 2, "%s.%s", base, r->field_names[k]);
+}
+
+static void load_named(Compiler *c, int local, int line, int column) {
+ if (c->locals[local].kind.type == BYTES || c->locals[local].kind.type == TEXT)
+ emit(c, line, column, BYTES_LOAD, c->locals[local].slot, 0);
+ else emit(c, line, column, LOCAL_LOAD, local_arg(c, local), 0);
+}
+
+static void store_named(Compiler *c, int local, int line, int column) {
+ if (c->locals[local].alias) fail_at(c, line, column, "'%s' names what a case holds; it is not assigned", c->locals[local].name);
+ if (c->locals[local].kind.type == BYTES || c->locals[local].kind.type == TEXT)
+ emit(c, line, column, BYTES_STORE, c->locals[local].slot, 0);
+ else emit(c, line, column, LOCAL_STORE, local_arg(c, local), 0);
+}
+
+/* A record's value at base onto the stack, field by field (root: a name's own record), or taken off it into them. */
+static void push_record(Compiler *c, const char *base, bool root, int record, int line, int column) {
+ const Record *r = &c->records[record];
+ for (unsigned k = 0; k < r->fields; ++k) {
+ char path[2 * NAME + 2];
+ field_path(r, k, base, root, path);
+ load_named(c, local_named(c, path), line, column);
+ }
+}
+static void pop_record(Compiler *c, const char *base, bool root, int record, int line, int column) {
+ const Record *r = &c->records[record];
+ for (unsigned k = r->fields; k-- > 0;) {
+ char path[2 * NAME + 2];
+ field_path(r, k, base, root, path);
+ store_named(c, local_named(c, path), line, column);
+ }
+}
+
+/* A value off the stack into a name of the block (a record's into its fields'). */
+static void pop_named(Compiler *c, int local, int line, int column) {
+ if (c->locals[local].kind.type == RECORD) pop_record(c, c->locals[local].name, true, c->locals[local].kind.owner, line, column);
+ else store_named(c, local, line, column);
+}
+
+/* A path down a record's value a name holds (a let's, a parameter's, a match's name for a case's value): the path and
+ * the name's local, or -1 when e is none. */
+static int binding_path(Compiler *c, const Expr *e, char path[2 * NAME + 2]) {
+ if (e->kind == E_NAME) {
+ int local = c->scope ? -1 : local_named(c, e->name);
+ if (local < 0 || c->locals[local].kind.type != RECORD) return -1;
+ snprintf(path, 2 * NAME + 2, "%s", e->name);
+ return local;
+ }
+ if (e->kind != E_MEMBER) return -1;
+ int local = binding_path(c, e->args[0], path);
+ size_t n = strlen(path);
+ if (local >= 0) snprintf(path + n, 2 * NAME + 2 - n, ".%s", e->member);
+ return local;
+}
+
+/* The item whose index is on the stack as its record's value (or its record's at a path, prefix): its fields, by way
+ * of a local. A function has its own lists, so an item goes to one as a value. */
+static Kind item_record(Compiler *c, const Expr *e, int list, const char *prefix, int record) {
+ if (c->local_slots == LOCALS) fail_at(c, e->line, e->column, "at most %d lets are allowed in a handler", LOCALS);
+ uint32_t local = c->local_slots++ | 1u << 8 | (uint32_t)list << 16;
+ emit(c, e->line, e->column, LOCAL_STORE, local, 0);
+ const Record *r = &c->records[record], *in = record_of(c, list);
+ for (unsigned k = 0; k < r->fields; ++k) {
+ char path[2 * NAME + 2];
+ if (*prefix) field_path(r, k, prefix, false, path);
+ else snprintf(path, sizeof(path), "%s", r->field_names[k]);
+ emit(c, e->line, e->column, LOCAL_LOAD, local, 0);
+ emit(c, e->line, e->column, ITEM_GET, (uint32_t)list | (uint32_t)field_named(in, path) << 16, 0);
+ }
+ return (Kind){RECORD, record};
+}
+
+/* A value as a function takes it or gives it: an item's as its record's (its index on the stack). */
+static Kind as_value(Compiler *c, const Expr *e, Kind kind) {
+ if (kind.type == INDEX) {
+ int record = (int)(record_of(c, kind.owner) - c->records);
+ return item_record(c, e, kind.owner, "", record);
+ }
+ if (kind.type == CURSOR || kind.type == UNIT || kind.type == NOTHING)
+ fail_at(c, e->line, e->column, "%s does not go to a fn's call or come back from one", type_name(kind.type));
+ return kind;
+}
+
+/* Whether code that follows a statement in its block never runs (it returns or breaks on every way), and whether
+ * statements return on every way through them. */
+static bool stops(const Stmt *s, bool breaks);
+static bool stops_one(const Stmt *s, bool breaks) {
+ switch (s->kind) {
+ case S_RETURN: return true;
+ case S_BREAK: return breaks;
+ case S_IF: return s->otherwise && stops(s->then, breaks) && stops(s->otherwise, breaks);
+ case S_MATCH:
+ for (const Stmt *arm = s->then; arm; arm = arm->next) if (!stops(arm->then, breaks)) return false;
+ return s->then != NULL; /* a match is exhaustive (its compiling says so) */
+ default: return false;
+ }
+}
+static bool stops(const Stmt *s, bool breaks) {
+ if (!s) return false;
+ while (s->next) s = s->next;
+ return stops_one(s, breaks);
+}
+
+static bool gives_values(const Stmt *s) {
+ for (; s; s = s->next)
+ if ((s->kind == S_RETURN && s->value) || gives_values(s->then) || gives_values(s->otherwise)) return true;
+ return false;
+}
+
+/* The fns a fn's body names (calls, or names a fn without parameters), a bit each. */
+static void named_fns(const Compiler *c, int module, const Expr *e, uint64_t *out) {
+ for (; e; e = e->next) {
+ if ((e->kind == E_FN && !*e->member) || e->kind == E_NAME) {
+ int f = fn_index(c, module, e->name);
+ if (f >= 0) *out |= UINT64_C(1) << f;
+ }
+ if (e->kind == E_FN && *e->member) {
+ int library = used_library(c, module, e->member), f = library >= 0 ? fn_index(c, library, e->name) : -1;
+ if (f >= 0) *out |= UINT64_C(1) << f;
+ }
+ for (unsigned i = 0; i < PARAMS; ++i) if (e->args[i]) named_fns(c, module, e->args[i], out);
+ }
+}
+static void named_by(const Compiler *c, int module, const Stmt *s, uint64_t *out) {
+ for (; s; s = s->next) {
+ const Expr *parts[] = {s->value, s->first, s->last, s->low, s->high};
+ for (unsigned i = 0; i < 5; ++i) named_fns(c, module, parts[i], out);
+ named_by(c, module, s->then, out);
+ named_by(c, module, s->otherwise, out);
+ }
+}
+
+/* Which fns run as functions: those with a block, and those a cycle of calls goes through. */
+static void called_fns(Compiler *c) {
+ uint64_t reach[FNS] = {0};
+ for (unsigned i = 0; i < c->fn_count; ++i) {
+ named_fns(c, c->fns[i].module, c->fns[i].body, &reach[i]);
+ named_by(c, c->fns[i].module, c->fns[i].block, &reach[i]);
+ }
+ for (unsigned k = 0; k < c->fn_count; ++k)
+ for (unsigned i = 0; i < c->fn_count; ++i)
+ if (reach[i] >> k & 1) reach[i] |= reach[k];
+ for (unsigned i = 0; i < c->fn_count; ++i) c->fns[i].called = c->fns[i].block || (reach[i] >> i & 1);
+}
+
+/* What making a function's code puts aside of what was being made. */
+typedef struct {
+ int message, arguments, row, fold_list, module, current;
+ bool folding, filtering, handling, has_returned;
+ char fold_name[NAME];
+ Scope *fold_scope, *scope;
+ const Expr *given;
+ unsigned local_count, local_slots, held_bytes, held_texts, alias_count, loop_count, nested, joins, code_count, code_capacity;
+ Type want;
+ Kind returned;
+ Op *code;
+} Aside;
+
+static void put_aside(Compiler *c, Aside *a, void *locals, void *aliases, void *loops) {
+ *a = (Aside){c->message, c->arguments, c->row, c->fold_list, c->module, c->current, c->folding, c->filtering, c->handling,
+ c->has_returned, {0}, c->fold_scope, c->scope, c->given, c->local_count, c->local_slots, c->held_bytes,
+ c->held_texts, c->alias_count, c->loop_count, c->nested, c->joins, c->code_count, c->code_capacity, c->want,
+ c->returned, c->code};
+ memcpy(a->fold_name, c->fold_name, NAME);
+ memcpy(locals, c->locals, sizeof(c->locals));
+ memcpy(aliases, c->aliases, sizeof(c->aliases));
+ memcpy(loops, c->loops, sizeof(c->loops));
+}
+
+static void take_back(Compiler *c, const Aside *a, const void *locals, const void *aliases, const void *loops) {
+ c->message = a->message, c->arguments = a->arguments, c->row = a->row, c->fold_list = a->fold_list, c->module = a->module;
+ c->current = a->current, c->folding = a->folding, c->filtering = a->filtering, c->handling = a->handling;
+ c->has_returned = a->has_returned, c->fold_scope = a->fold_scope, c->scope = a->scope, c->given = a->given;
+ c->local_count = a->local_count, c->local_slots = a->local_slots, c->held_bytes = a->held_bytes, c->held_texts = a->held_texts;
+ c->alias_count = a->alias_count, c->loop_count = a->loop_count, c->nested = a->nested, c->joins = a->joins;
+ c->code_count = a->code_count, c->code_capacity = a->code_capacity, c->want = a->want, c->returned = a->returned;
+ c->code = a->code;
+ memcpy(c->fold_name, a->fold_name, NAME);
+ memcpy(c->locals, locals, sizeof(c->locals));
+ memcpy(c->aliases, aliases, sizeof(c->aliases));
+ memcpy(c->loops, loops, sizeof(c->loops));
+}
+
+/* An instance's code, made in the functions' code: its parameters' values taken off the stack into names, its block
+ * (or its expression, returned), and a return at its end when its way may reach it (giving nothing). */
+static void make_instance(Compiler *c, int index) {
+ static __typeof__(c->locals) locals[INSTANCES];
+ static __typeof__(c->aliases) aliases[INSTANCES];
+ static __typeof__(c->loops) loops[INSTANCES];
+ static unsigned depth;
+ Aside aside;
+ put_aside(c, &aside, locals[depth], aliases[depth], loops[depth]);
+ ++depth;
+ Instance *in = &c->instances[index];
+ const Fn *f = &c->fns[in->fn];
+ if (!(in->ops = malloc(256 * sizeof(*in->ops)))) fail_at(c, f->line, f->column, "out of memory");
+ c->code = in->ops, c->code_count = 0, c->code_capacity = 256;
+ c->module = f->module, c->current = index, c->scope = NULL, c->handling = true, c->has_returned = false;
+ c->message = c->arguments = c->row = c->fold_list = -1;
+ c->folding = c->filtering = false, c->fold_scope = NULL, c->given = NULL, c->want = 0, c->joins = 0;
+ c->local_count = c->local_slots = c->held_bytes = c->held_texts = c->alias_count = c->loop_count = c->nested = 0;
+ for (unsigned i = 0; i < in->param_count; ++i) name_value(c, f->params[i], in->params[i], f->line, f->column);
+ for (unsigned i = in->param_count; i-- > 0;) pop_named(c, local_named(c, f->params[i]), f->line, f->column);
+ if (f->block) {
+ statements(c, f->block);
+ if (!stops(f->block, false)) {
+ if (in->known && in->result_count)
+ fail_at(c, f->line, f->column, "fn '%s' may end without giving its value: return it at the end", f->name);
+ in->known = true, in->result_count = 0;
+ emit(c, f->line, f->column, RETURN, (uint32_t)index, 0);
+ }
+ } else { /* an expression that calls itself, returned */
+ Kind kind = as_value(c, f->body, value(c, f->body, true, 0));
+ if (in->known && (in->result_count != 1 || !same_kind(in->results[0], kind)))
+ fail_at(c, f->body->line, f->body->column, "fn '%s' gives %s, not %s", f->name,
+ in->result_count == 1 ? type_name(in->results[0].type) : "several values", type_name(kind.type));
+ in->results[0] = kind, in->result_count = 1, in->known = true;
+ emit(c, f->line, f->column, RETURN, (uint32_t)index, 0);
+ }
+ in->count = c->code_count, in->capacity = c->code_capacity;
+ --depth;
+ take_back(c, &aside, locals[depth], aliases[depth], loops[depth]);
+}
+
+/* The instance of fn for arguments of these kinds: one made already, or a new one (made now). */
+static int instance_of(Compiler *c, int fn, const Kind *kinds, unsigned count, const Expr *at) {
+ for (unsigned i = 0; i < c->instance_count; ++i) {
+ const Instance *in = &c->instances[i];
+ bool same = in->fn == fn && in->param_count == count;
+ for (unsigned k = 0; same && k < count; ++k) same = same_kind(in->params[k], kinds[k]);
+ if (same) return (int)i;
+ }
+ if (c->instance_count == INSTANCES)
+ fail_at(c, at->line, at->column, "at most %d functions (a fn's for each kinds of its arguments) are allowed", INSTANCES);
+ int index = (int)c->instance_count++;
+ Instance *in = &c->instances[index];
+ const Fn *f = &c->fns[fn];
+ memset(in, 0, sizeof(*in));
+ in->fn = fn, in->param_count = count;
+ memcpy(in->params, kinds, count * sizeof(*kinds));
+ if (f->typed) {
+ for (unsigned k = 0; k < f->result_count; ++k)
+ in->results[k] = written_kind(c, f->module, f->results[k].name, f->results[k].list, f->results[k].line, f->results[k].column);
+ in->result_count = f->result_count, in->known = true;
+ } else if (f->block && !gives_values(f->block)) in->known = true; /* it gives nothing */
+ make_instance(c, index);
+ return index;
+}
+
+/* A call of a fn run as a function: its arguments onto the stack (an item's as its record's value), the CALL, and
+ * what it gives (a value, several: TUPLE, or none). Arguments already given (count of them, kinds) are on the stack. */
+static Kind call_fn(Compiler *c, const Expr *e, int fn, bool handler, int height, Kind *kinds, unsigned given) {
+ const Fn *f = &c->fns[fn];
+ unsigned count = e->kind == E_FN ? e->count : 0;
+ if (count != f->param_count)
+ fail_at(c, e->line, e->column, "%s expects %u argument%s", f->name, f->param_count, f->param_count == 1 ? "" : "s");
+ if (c->folding) fail_at(c, e->line, e->column, "count, sum, max and min call no fn with a block, nor one that calls itself");
+ if (!handler && c->current < 0)
+ fail_at(c, e->line, e->column, "a view calls no fn with a block, nor one that calls itself: handlers do ('%s')", f->name);
+ int h = height;
+ Kind own[PARAMS];
+ if (!kinds) kinds = own;
+ for (unsigned i = 0; i < given; ++i) h += (int)cells(c, kinds[i]);
+ for (unsigned i = given; i < count; ++i) {
+ kinds[i] = as_value(c, e->args[i], value(c, e->args[i], handler, h));
+ h += (int)cells(c, kinds[i]);
+ if (h > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values");
+ }
+ int index = instance_of(c, fn, kinds, count, e);
+ const Instance *in = &c->instances[index];
+ if (!in->known)
+ fail_at(c, e->line, e->column, "fn '%s' calls itself before a return gives its value: write its type, as in fn %s(...) -> type",
+ f->name, f->name);
+ emit(c, e->line, e->column, CALL, (uint32_t)index, 0);
+ unsigned gives = 0;
+ for (unsigned k = 0; k < in->result_count; ++k) gives += cells(c, in->results[k]);
+ if (height + (int)gives > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values");
+ return in->result_count == 0 ? (Kind){NOTHING, -1} : in->result_count == 1 ? in->results[0] : (Kind){TUPLE, index};
+}
+
+/* A record's or a variant's value written out (R { ... }, V.case(x), V.case) onto the stack: its fields in order. */
+static Kind made_record(Compiler *c, const Expr *e, int record, int height) {
+ const Record *r = &c->records[record];
+ const Expr *values[FIELDS_PER_RECORD];
+ record_values_of(c, e, r, r->name, values, height);
+ record_fields_of(c, e, r, values, height);
+ return (Kind){RECORD, record};
+}
+
+/* A name's record's value, or a value under it (a field, a record's or variant's value at a path), onto the stack. */
+static Kind binding_value(Compiler *c, const Expr *e, int binding, char *path) {
+ int local = local_named(c, path);
+ size_t n = strlen(path);
+ if (local >= 0 && c->locals[local].kind.type == RECORD) {
+ push_record(c, path, true, c->locals[local].kind.owner, e->line, e->column);
+ return c->locals[local].kind;
+ }
+ if (local >= 0) {
+ if (c->locals[local].variant && !e->raw)
+ fail_at(c, e->line, e->column, "'%s' holds a variant: match it, as in match %s { ... }", path, path);
+ load_named(c, local, e->line, e->column);
+ return c->locals[local].kind;
+ }
+ if (n > 5 && !strcmp(path + n - 5, ".case")) { /* a variant's case under it, read as its own field */
+ path[n - 5] = 0;
+ local = local_named(c, path);
+ if (local >= 0 && c->locals[local].variant) {
+ load_named(c, local, e->line, e->column);
+ return c->locals[local].kind;
+ }
+ path[n - 5] = '.';
+ }
+ int record = sub_record(c, c->locals[binding].kind.owner, path + strlen(c->locals[binding].name) + 1);
+ if (record < 0) return (Kind){0, -1}; /* none here: what the expression reads of a field (name.text.bytes) */
+ push_record(c, path, false, record, e->line, e->column);
+ return (Kind){RECORD, record};
+}
+
static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
Type want = c->want; /* set by wanted() for this expression alone */
c->want = 0;
@@ -2905,9 +3392,15 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
}
int alias = c->scope ? -1 : alias_named(c, e->name);
if (alias >= 0) { /* a match's binding: its item's field */
- if (c->aliases[alias].prefix)
- fail_at(c, e->line, e->column, "'%s' holds a record or a variant: read its fields (%s.x) or match it", e->name, e->name);
int list = c->aliases[alias].list;
+ if (c->aliases[alias].prefix) { /* a record's or a variant's value under the item (027's 2c) */
+ const Record *r = record_of(c, list);
+ int sub = sub_record(c, (int)(r - c->records), c->aliases[alias].path);
+ if (sub < 0)
+ fail_at(c, e->line, e->column, "'%s' holds a record or a variant: read its fields (%s.x) or match it", e->name, e->name);
+ emit(c, e->line, e->column, LOCAL_LOAD, c->aliases[alias].slot | 1u << 8 | (uint32_t)list << 16, 0);
+ return item_record(c, e, list, c->aliases[alias].path, sub);
+ }
emit(c, e->line, e->column, LOCAL_LOAD, c->aliases[alias].slot | 1u << 8 | (uint32_t)list << 16, 0);
int field = field_named(record_of(c, list), c->aliases[alias].path);
emit(c, e->line, e->column, ITEM_GET, (uint32_t)list | (uint32_t)field << 16, 0);
@@ -2921,15 +3414,25 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
emit(c, e->line, e->column, PICK, (uint32_t)(height - 1 - (c->scope->base + param)), 0);
return c->scope->kinds[param];
}
- if (fn_index(c, c->module, e->name) >= 0) return inline_fn(c, e, c->module, e->name, handler, height);
- if (c->module) fail_at(c, e->line, e->column, "'%s' is not a parameter; a library fn reads only its parameters", e->name);
+ int fn = fn_index(c, c->module, e->name);
+ if (fn >= 0 && c->fns[fn].called) return call_fn(c, e, fn, handler, height, NULL, 0);
+ if (fn >= 0) return inline_fn(c, e, c->module, e->name, handler, height);
+ if (c->module && c->current < 0)
+ fail_at(c, e->line, e->column, "'%s' is not a parameter; a library fn reads only its parameters", e->name);
int local = c->scope ? -1 : local_named(c, e->name);
+ if (local >= 0 && c->locals[local].kind.type == RECORD) { /* a record's value held by name (027's 2c) */
+ push_record(c, e->name, true, c->locals[local].kind.owner, e->line, e->column);
+ return c->locals[local].kind;
+ }
+ if (local >= 0 && c->locals[local].variant && !e->raw)
+ fail_at(c, e->line, e->column, "'%s' holds a variant: match it, as in match %s { ... }", e->name, e->name);
if (local >= 0) { /* a let in the handler */
if (c->locals[local].kind.type == BYTES || c->locals[local].kind.type == TEXT)
emit(c, e->line, e->column, BYTES_LOAD, c->locals[local].slot, 0);
else emit(c, e->line, e->column, LOCAL_LOAD, local_arg(c, local), 0);
return c->locals[local].kind;
}
+ if (c->module) fail_at(c, e->line, e->column, "'%s' is not a parameter; a library fn reads only its parameters", e->name);
if (!c->scope && c->arguments >= 0) { /* an argument of the view (a table's, in its columns and filter) */
const Input *in = &c->inputs[c->arguments];
for (unsigned f = 0; f < in->fields; ++f)
@@ -2970,12 +3473,23 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
const Expr *base = e->args[0];
int item_list;
char item[2 * NAME + 2];
+ int binding = binding_path(c, e, item);
+ if (binding >= 0) { /* down a record's value held by name (027's 2c) */
+ Kind kind = binding_value(c, e, binding, item);
+ if (kind.type) return kind;
+ }
+ int variant;
+ unsigned which;
+ const Expr *payload;
+ if (variant_case(c, e, &variant, &which, &payload)) return made_record(c, e, variant, height); /* V.case */
unsigned saved_code = c->code_count;
if ((base->kind != E_NAME || alias_named(c, base->name) >= 0 || base->kind == E_ITEM) &&
(base->kind == E_MEMBER || base->kind == E_ITEM || base->kind == E_INDEX || base->kind == E_NAME) &&
item_path(c, e, handler, height, &item_list, item)) { /* item.at.x: a field down a path (027's 2b) */
char *path = item;
const Record *r = record_of(c, item_list);
+ int sub = composite(r, path) ? sub_record(c, (int)(r - c->records), path) : -1;
+ if (sub >= 0) return item_record(c, e, item_list, path, sub); /* a record's or a variant's value there (2c) */
int field = field_named(r, path);
size_t length = strlen(path);
if (field < 0 && length > 5 && !strcmp(path + length - 5, ".case")) { /* a nested variant's case: its own field */
@@ -3095,8 +3609,10 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
fail_at(c, e->line, e->column, "a list has count, first and last, not '%s'", m);
}
if (inner.type == INDEX) {
- /* at.title: a field of the item the index names. */
+ /* at.title: a field of the item the index names; a record's or a variant's value there (027's 2c). */
const Record *r = record_of(c, inner.owner);
+ int sub = composite(r, m) ? sub_record(c, (int)(r - c->records), m) : -1;
+ if (sub >= 0) return item_record(c, e, inner.owner, m, sub);
if (field_named(r, m) < 0 && holds_under(r, m))
fail_at(c, e->line, e->column, "'%s' holds a record or a variant: read its fields (%s.x) or match it", m, m);
unsigned field = record_field(c, r, m, e->line, e->column);
@@ -3108,9 +3624,17 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
fail_at(c, e->line, e->column, "%s has no '%s'", type_name(inner.type), m);
}
case E_FN: {
+ int fn = fn_index(c, c->module, e->name);
+ if (!*e->member && fn >= 0 && c->fns[fn].called) return call_fn(c, e, fn, handler, height, NULL, 0);
if (!*e->member) return inline_fn(c, e, c->module, e->name, handler, height);
int library = parameter(c, e->member) < 0 ? used_library(c, c->module, e->member) : -1;
+ fn = library >= 0 ? fn_index(c, library, e->name) : -1;
+ if (fn >= 0 && c->fns[fn].called) return call_fn(c, e, fn, handler, height, NULL, 0);
if (library >= 0) return inline_fn(c, e, library, e->name, handler, height);
+ int variant;
+ unsigned which;
+ const Expr *payload;
+ if (variant_case(c, e, &variant, &which, &payload)) return made_record(c, e, variant, height); /* V.case(x) */
int list = state_index(c, e->member);
if (!strcmp(e->name, "has") && list >= 0 && c->state_types[list] == STATE_LIST) {
/* people.has("bob"): whether a keyed list holds that key. */
@@ -3307,7 +3831,13 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
return e->op == FOLD_FIRST ? (Kind){INDEX, list} :
(Kind){values == I64 && e->op != FOLD_DISTINCT ? I64 : NUM, -1}; /* an i64's sum, most or least */
}
- case E_RECORD: case E_FIELD:
+ case E_RECORD: { /* R { ... }: its fields onto the stack (027's 2c) */
+ int from = e->member[0] ? used_library(c, c->module, e->member) : c->module;
+ int record = from >= 0 ? module_record(c, from, e->name) : -1;
+ if (record < 0) fail_at(c, e->line, e->column, "unknown record '%s'", e->name);
+ return made_record(c, e, record, height);
+ }
+ case E_FIELD:
fail_at(c, e->line, e->column, "a record value goes into a list, as in list.append(%s { ... })", e->name);
case E_UNIT:
fail_at(c, e->line, e->column, "a distance moves a cursor, as in at + 1 char");
@@ -3414,6 +3944,11 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
emit(c, e->line, e->column, BYTE, 0, 0);
return (Kind){BYTES, -1};
}
+ if (e->op == CHAR) { /* char(n): the text of a code point, n an i64 (027's 2c) */
+ integer(c, e->args[0], handler, height);
+ emit(c, e->line, e->column, CHAR, 0, 0);
+ return (Kind){TEXT, -1};
+ }
if (e->op == I64_FROM) { /* i64(x): a whole number in range; a literal is one exactly */
if (loose(e->args[0])) integer(c, e->args[0], handler, height);
else {
@@ -3535,13 +4070,13 @@ static bool variant_case(Compiler *c, const Expr *e, int *variant, unsigned *ind
*payload = NULL;
if (e->kind == E_FN && *e->member) {
type = e->member, label = e->name;
- if (e->count > 1) fail_at(c, e->line, e->column, "%s.%s takes one value", type, label);
*payload = e->count ? e->args[0] : NULL;
} else if (e->kind == E_MEMBER && e->args[0]->kind == E_NAME) {
type = e->args[0]->name, label = e->member;
} else return false;
int found = c->module ? -1 : module_record(c, 0, type);
if (found < 0 || !c->records[found].variant) return false;
+ if (e->kind == E_FN && e->count > 1) fail_at(c, e->line, e->column, "%s.%s takes one value", type, label);
const Record *v = &c->records[found];
for (unsigned i = 0; i < v->declared_count; ++i) {
if (strcmp(v->declared[i].name, label)) continue;
@@ -3583,8 +4118,11 @@ static Expr *member_of(Compiler *c, const Expr *x, const char *path) {
/* The values of r's fields under prefix ("": all of them; a variant's case is the field named prefix, or case) from x:
* a record written out, a variant's case, or an item (an index, a match's binding) whose fields of those names give
* them (027's 2b). label names where it goes, for messages. */
-static void values_under(Compiler *c, const Record *r, const char *prefix, const Expr *x, const Expr **values, const char *label) {
+static const Expr *settled(Compiler *c, const Expr *x, int height);
+static void values_under(Compiler *c, const Record *r, const char *prefix, const Expr *x, const Expr **values, const char *label,
+ int height) {
char at[2 * NAME + 2];
+ x = settled(c, x, height);
int tag = field_named(r, *prefix ? prefix : "case");
int variant = tag >= 0 ? r->field_variant[tag] - 1 : -1;
int which;
@@ -3598,7 +4136,7 @@ static void values_under(Compiler *c, const Record *r, const char *prefix, const
if (!payload) return;
const char *name = c->records[which].declared[index].name;
snprintf(at, sizeof(at), "%s%s%s", prefix, *prefix ? "." : "", name);
- if (composite(r, at)) values_under(c, r, at, payload, values, label); /* a record's or a variant's fields */
+ if (composite(r, at)) values_under(c, r, at, payload, values, label, height); /* a record's or a variant's fields */
else values[field_named(r, at)] = payload;
return;
}
@@ -3607,7 +4145,7 @@ static void values_under(Compiler *c, const Record *r, const char *prefix, const
if (!f->member[0]) continue; /* the spread: below */
snprintf(at, sizeof(at), "%s%s%s", prefix, *prefix ? "." : "", f->member);
int field = field_named(r, at);
- if (composite(r, at)) values_under(c, r, at, f->args[0], values, label);
+ if (composite(r, at)) values_under(c, r, at, f->args[0], values, label, height);
else if (field >= 0) {
if (values[field]) fail_at(c, f->line, f->column, "field '%s' is given twice", f->member);
values[field] = f->args[0];
@@ -3625,28 +4163,56 @@ static void values_under(Compiler *c, const Record *r, const char *prefix, const
}
}
-static void record_values_of(Compiler *c, const Expr *e, const Record *r, const char *label, const Expr **values);
+static void record_values_of(Compiler *c, const Expr *e, const Record *r, const char *label, const Expr **values, int height);
+
+static void record_values(Compiler *c, const Expr *e, int list, const Expr **values, int height) {
+ record_values_of(c, e, record_of(c, list), c->state_names[list], values, height);
+}
-static void record_values(Compiler *c, const Expr *e, int list, const Expr **values) {
- record_values_of(c, e, record_of(c, list), c->state_names[list], values);
+/* Whether e is a call of a fn run as a function (027's 2c). */
+static bool called_call(const Compiler *c, const Expr *e) {
+ int module = e->kind == E_FN && *e->member ? used_library(c, c->module, e->member) : c->module;
+ int fn = (e->kind == E_FN || e->kind == E_NAME) && module >= 0 ? fn_index(c, module, e->name) : -1;
+ return fn >= 0 && c->fns[fn].called;
}
-static void record_values_of(Compiler *c, const Expr *e, const Record *r, const char *label, const Expr **values) {
+/* A record's value a call gives, held by a name of its own first (its fields are read one by one); e else. */
+static const Expr *settled(Compiler *c, const Expr *x, int height) {
+ if (!called_call(c, x)) return x;
+ Kind kind = value(c, x, c->handling, height);
+ if (kind.type != RECORD) fail_at(c, x->line, x->column, "'%s' gives %s, not a record's value", x->name, type_name(kind.type));
+ Expr *name = node(c, E_NAME, x->line, x->column);
+ snprintf(name->name, NAME, ":%u", c->settled++);
+ name_value(c, name->name, kind, x->line, x->column);
+ pop_named(c, local_named(c, name->name), x->line, x->column);
+ return name;
+}
+
+static void record_values_of(Compiler *c, const Expr *e, const Record *r, const char *label, const Expr **values, int height) {
if (r->scalar && e->kind != E_RECORD) { /* xs.append(5): the value itself */
values[0] = e;
return;
}
for (unsigned k = 0; k < r->fields; ++k) values[k] = NULL;
+ e = settled(c, e, height);
int which;
unsigned index;
const Expr *payload;
if (r->variant && variant_case(c, e, &which, &index, &payload)) { /* xs.append(Json.num(3)) */
- values_under(c, r, "", e, values, label);
+ values_under(c, r, "", e, values, label, height);
return;
}
char shown[2 * NAME + 1];
snprintf(shown, sizeof(shown), "%s", record_shown(c, c->module, r));
- if (r->variant && e->kind != E_RECORD) { /* an item of the variant, or a variant in a field: its fields */
+ char path[2 * NAME + 2];
+ int binding = e->kind != E_RECORD ? binding_path(c, e, path) : -1;
+ if (binding >= 0) { /* a record's value held by name, or one under it (027's 2c): of r alone */
+ bool root = !strcmp(path, c->locals[binding].name);
+ int held = root ? c->locals[binding].kind.owner : sub_record(c, c->locals[binding].kind.owner, path + strlen(c->locals[binding].name) + 1);
+ if (held != (int)(r - c->records))
+ fail_at(c, e->line, e->column, "'%s' holds %s, not %s", label, shown, held >= 0 ? c->records[held].name : path);
+ }
+ if ((r->variant || binding >= 0) && e->kind != E_RECORD) { /* an item of the variant, a variant in a field, or a value held by name */
for (unsigned k = 0; k < r->fields; ++k) values[k] = member_of(c, e, r->field_names[k]);
return;
}
@@ -3657,7 +4223,7 @@ static void record_values_of(Compiler *c, const Expr *e, const Record *r, const
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", label, shown, e->member, e->member[0] ? "." : "", e->name);
- values_under(c, r, "", e, values, label);
+ values_under(c, r, "", e, values, label, height);
const Expr *spreading = spread_of(e);
for (unsigned k = 0; spreading && k < r->fields; ++k)
if (!values[k]) values[k] = spread_field(c, spreading, r->field_names[k]);
@@ -3703,7 +4269,7 @@ static void record_fields_of(Compiler *c, const Expr *e, const Record *r, const
static void record_fields(Compiler *c, const Expr *e, int list, int height) {
const Expr *values[FIELDS_PER_RECORD];
- record_values(c, e, list, values);
+ record_values(c, e, list, values, height);
record_fields_of(c, e, record_of(c, list), values, height);
}
@@ -3745,8 +4311,9 @@ static void view_changes(Compiler *c, const Stmt *s, int list, const char *name)
/* The list a for or a fold walks, written as its name (a list, a message's rows, a let's) or as a path to a list
* value (item.items, a match's binding): a value is bound to a register first. */
static int walked_list(Compiler *c, const char *path, int line, int column) {
- int named = state_index(c, path);
- if (named >= 0 || (c->module == 0 && strchr(path, '.') == NULL && alias_named(c, path) < 0 && loop_named(c, path) < 0))
+ int named = state_index(c, path), local = strchr(path, '.') ? -1 : local_named(c, path);
+ if (named >= 0 || (c->module == 0 && strchr(path, '.') == NULL && alias_named(c, path) < 0 && loop_named(c, path) < 0 &&
+ (local < 0 || c->locals[local].kind.type != LIST)))
return list_state(c, path, line, column, NULL);
Expr *e = path_expr(c, path, line, column);
Kind kind = value(c, e, c->handling, 0);
@@ -3761,36 +4328,57 @@ static unsigned held_slot(Compiler *c, const Stmt *s, Type type) {
return (type == TEXT ? HELD_TEXT : 0) + (*count)++;
}
+static void chars_loop(Compiler *c, const Stmt *s, const Expr *subject);
+static bool text_path(Compiler *c, const char *path);
+
static void statements(Compiler *c, const Stmt *s) {
unsigned scope = c->local_count; /* a block's lets end with it */
- for (; s; s = s->next) {
+ for (const Stmt *before = NULL; s; before = s, s = s->next) {
+ if (before && stops_one(before, true))
+ fail_at(c, s->line, s->column, "this is never run: the line before it returns or breaks");
switch (s->kind) {
case S_ARM: break; /* a match's: compiled with it */
case S_MATCH: {
- /* The item's index in a local; then for each arm, if its case is the one held, the arm (its binding the
- * case's value: a field, a list, or a record's or a variant's fields under it). */
- int list;
- char path[2 * NAME + 2] = "";
+ /* The item's index in a local, or a record's value held by name (027's 2c); then for each arm, if its case
+ * is the one held, the arm (its binding the case's value: a field, a list, or a record's or a variant's
+ * fields under it). The last arm of a match that names every case is taken without asking. */
+ int list = -1, tag = -1;
+ char path[2 * NAME + 2] = "", base[2 * NAME + 2];
const Expr *subject = s->value;
- if (!((subject->kind == E_MEMBER || subject->kind == E_ITEM ||
- (subject->kind == E_NAME && alias_named(c, subject->name) >= 0)) &&
- item_path(c, subject, c->handling, 0, &list, path))) {
- Kind kind = value(c, subject, c->handling, 0);
- if (kind.type != INDEX)
- fail_at(c, subject->line, subject->column, "match takes an item that holds a variant, not %s", type_name(kind.type));
- list = kind.owner;
+ int binding = binding_path(c, subject, base);
+ bool root = binding >= 0 && !strcmp(base, c->locals[binding].name);
+ const Record *r = NULL, *v;
+ unsigned slot = 0;
+ if (binding >= 0) {
+ int held = root ? c->locals[binding].kind.owner
+ : sub_record(c, c->locals[binding].kind.owner, base + strlen(c->locals[binding].name) + 1);
+ if (held < 0 || !c->records[held].variant)
+ fail_at(c, subject->line, subject->column, "match takes a variant; '%s' holds none", base);
+ v = &c->records[held];
+ } else {
+ if (!((subject->kind == E_MEMBER || subject->kind == E_ITEM ||
+ (subject->kind == E_NAME && alias_named(c, subject->name) >= 0)) &&
+ item_path(c, subject, c->handling, 0, &list, path))) {
+ Kind kind = value(c, subject, c->handling, 0);
+ if (kind.type != INDEX)
+ fail_at(c, subject->line, subject->column, "match takes an item that holds a variant, not %s", type_name(kind.type));
+ list = kind.owner;
+ }
+ r = record_of(c, list);
+ tag = field_named(r, *path ? path : "case");
+ if (tag < 0 || !r->field_variant[tag])
+ fail_at(c, subject->line, subject->column, "match takes an item that holds a variant; '%s' holds none",
+ *path ? path : r->name);
+ v = &c->records[r->field_variant[tag] - 1];
+ if (c->local_slots == LOCALS || c->alias_count == 16)
+ fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
+ slot = c->local_slots++;
+ emit(c, s->line, s->column, LOCAL_STORE, slot | 1u << 8 | (uint32_t)list << 16, 0);
}
- const Record *r = record_of(c, list);
- int tag = field_named(r, *path ? path : "case");
- if (tag < 0 || !r->field_variant[tag])
- fail_at(c, subject->line, subject->column, "match takes an item that holds a variant; '%s' holds none",
- *path ? path : r->name);
- const Record *v = &c->records[r->field_variant[tag] - 1];
- if (c->local_slots == LOCALS || c->alias_count == 16)
- fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
- unsigned slot = c->local_slots++, ends[FIELDS_PER_RECORD + 1], arms = 0;
- emit(c, s->line, s->column, LOCAL_STORE, slot | 1u << 8 | (uint32_t)list << 16, 0);
- bool seen[FIELDS_PER_RECORD] = {false}, rest = false;
+ unsigned ends[FIELDS_PER_RECORD + 1], arms = 0, count = 0;
+ bool seen[FIELDS_PER_RECORD] = {false}, rest = false, every = true;
+ for (const Stmt *arm = s->then; arm; arm = arm->next) every = every && strcmp(arm->name, "_"), ++count;
+ every = every && count == v->declared_count; /* the last arm then takes what is left */
for (const Stmt *arm = s->then; arm; arm = arm->next) {
if (rest) fail_at(c, arm->line, arm->column, "an arm after _ is never taken");
bool other = !strcmp(arm->name, "_");
@@ -3801,40 +4389,79 @@ static void statements(Compiler *c, const Stmt *s) {
fail_at(c, arm->line, arm->column, "variant '%s' has no case '%s'", v->name, arm->name);
if (seen[index]) fail_at(c, arm->line, arm->column, "case '%s' is matched twice", arm->name);
seen[index] = true;
- emit(c, arm->line, arm->column, LOCAL_LOAD, slot | 1u << 8 | (uint32_t)list << 16, 0);
- emit(c, arm->line, arm->column, ITEM_GET, (uint32_t)list | (uint32_t)tag << 16, 0);
+ }
+ bool asks = !other && !(every && !arm->next);
+ if (asks) {
+ if (binding >= 0) {
+ char at[2 * NAME + 2];
+ field_path(v, 0, base, root, at);
+ load_named(c, local_named(c, at), arm->line, arm->column);
+ } else {
+ emit(c, arm->line, arm->column, LOCAL_LOAD, slot | 1u << 8 | (uint32_t)list << 16, 0);
+ emit(c, arm->line, arm->column, ITEM_GET, (uint32_t)list | (uint32_t)tag << 16, 0);
+ }
emit_integer(c, arm->line, arm->column, index);
emit(c, arm->line, arm->column, I64_COMPARE, EQ - LT, 0);
skip = c->code_count;
emit(c, arm->line, arm->column, JZ, 0, 0);
}
bool bound = *arm->member;
+ unsigned names = c->local_count;
if (bound) {
if (other) fail_at(c, arm->key_line, arm->key_column, "_ holds no value to name");
if (!v->declared[index].kind && !*v->declared[index].type)
fail_at(c, arm->key_line, arm->key_column, "case '%s' holds no value to name", arm->name);
if (declared_kind(c, arm->member) || let_named(c, arm->member) >= 0 || loop_named(c, arm->member) >= 0)
fail_at(c, arm->key_line, arm->key_column, "'%s' is already a name here", arm->member);
- unsigned a = c->alias_count++;
- memcpy(c->aliases[a].name, arm->member, NAME);
- char at[2 * NAME + 2];
- snprintf(at, sizeof(at), "%s%s%s", path, *path ? "." : "", arm->name);
+ char at[4 * NAME];
+ snprintf(at, sizeof(at), "%s%s%s", binding >= 0 ? base : path, binding >= 0 || *path ? "." : "", arm->name);
if (strlen(at) >= NAME) fail_at(c, arm->line, arm->column, "a field's path '%s' passes 31 bytes", at);
- memcpy(c->aliases[a].path, at, strlen(at) + 1);
- c->aliases[a].list = list;
- c->aliases[a].slot = slot;
- c->aliases[a].prefix = composite(r, c->aliases[a].path);
+ if (binding >= 0) { /* names for what the case holds: its fields' locals, under the name */
+ int field = local_named(c, at);
+ int held = field >= 0 ? -1 : sub_record(c, (int)(v - c->records), arm->name);
+ if (field < 0 && held < 0) fail_at(c, arm->line, arm->column, "case '%s' holds nothing here", arm->name);
+ if (c->local_count == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
+ unsigned local = c->local_count++;
+ if (field >= 0) c->locals[local] = c->locals[field];
+ else {
+ memset(&c->locals[local], 0, sizeof(c->locals[local]));
+ c->locals[local].kind = (Kind){RECORD, held};
+ c->locals[local].list = -1;
+ }
+ memcpy(c->locals[local].name, arm->member, NAME);
+ c->locals[local].alias = true;
+ const Record *inner = held >= 0 ? &c->records[held] : NULL;
+ for (unsigned k = 0; inner && k < inner->fields; ++k) {
+ char from[2 * NAME + 2], name[2 * NAME + 2];
+ field_path(inner, k, at, false, from);
+ snprintf(name, sizeof(name), "%s.%s", arm->member, inner->field_names[k]);
+ if (strlen(name) >= NAME) fail_at(c, arm->line, arm->column, "'%s' passes 31 bytes", name);
+ if (c->local_count == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
+ unsigned each = c->local_count++;
+ c->locals[each] = c->locals[local_named(c, from)];
+ memcpy(c->locals[each].name, name, strlen(name) + 1);
+ c->locals[each].alias = true;
+ }
+ } else {
+ unsigned a = c->alias_count++;
+ memcpy(c->aliases[a].name, arm->member, NAME);
+ memcpy(c->aliases[a].path, at, strlen(at) + 1);
+ c->aliases[a].list = list;
+ c->aliases[a].slot = slot;
+ c->aliases[a].prefix = composite(r, c->aliases[a].path);
+ }
}
++c->nested;
statements(c, arm->then);
--c->nested;
- if (bound) --c->alias_count;
+ if (bound && binding < 0) --c->alias_count;
+ c->local_count = names;
if (other) rest = true;
- else {
+ if (arm->next && !stops(arm->then, true)) {
ends[arms++] = c->code_count;
emit(c, arm->line, arm->column, JMP, 0, 0);
- c->code[skip].arg = c->code_count;
}
+ if (asks) c->code[skip].arg = c->code_count;
}
for (unsigned i = 0; !rest && i < v->declared_count; ++i)
if (!seen[i]) fail_at(c, s->line, s->column, "match misses case '%s': add it, or _ for the rest", v->declared[i].name);
@@ -3870,6 +4497,13 @@ static void statements(Compiler *c, const Stmt *s) {
}
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, c->handling, 0);
+ if (held.type == TUPLE)
+ fail_at(c, s->value->line, s->value->column, "this gives several values: name each, as in let a, b = ...");
+ if (held.type == RECORD || held.type == LIST) { /* a record's value, a list's (027's 2c) */
+ name_value(c, s->name, held, s->line, s->column);
+ pop_named(c, local_named(c, s->name), s->line, s->column);
+ break;
+ }
if (held.type == BYTES || held.type == TEXT) { /* held by name in Pure: a slot of its own */
unsigned slot = held_slot(c, s, held.type);
unsigned local = c->local_count++;
@@ -3896,12 +4530,20 @@ static void statements(Compiler *c, const Stmt *s) {
case S_LETS: {
const Expr *v = s->value;
Kind kinds[STACK];
- for (unsigned i = 0; i < s->name_count; ++i, v = v->next) {
- kinds[i] = value(c, v, c->handling, (int)i);
+ if (!v->next) { /* one value: a fn's call that gives them all (027's 2c) */
+ Kind given = value(c, v, c->handling, 0);
+ const Instance *in = given.type == TUPLE ? &c->instances[given.owner] : NULL;
+ if (!in || in->result_count != s->name_count)
+ fail_at(c, in ? v->line : s->line, in ? v->column : s->column, "%u names take %u values, not %u", s->name_count,
+ s->name_count, in ? in->result_count : 1);
+ memcpy(kinds, in->results, s->name_count * sizeof(*kinds));
+ } else for (unsigned i = 0, height = 0; i < s->name_count; ++i, v = v->next) {
+ kinds[i] = value(c, v, c->handling, (int)height);
+ height += cells(c, kinds[i]);
if (kinds[i].type != NUM && kinds[i].type != BOOL && kinds[i].type != I64 && kinds[i].type != INDEX &&
- kinds[i].type != BYTES && kinds[i].type != TEXT)
- fail_at(c, v->line, v->column, "a let holds a number, a bool, an i64, an index, bytes or text, not %s",
- type_name(kinds[i].type));
+ kinds[i].type != BYTES && kinds[i].type != TEXT && kinds[i].type != RECORD && kinds[i].type != LIST)
+ fail_at(c, v->line, v->column, "a let holds a number, a bool, an i64, an index, bytes, text, a list or a record, "
+ "not %s", type_name(kinds[i].type));
}
unsigned first = c->local_count;
for (unsigned i = 0; i < s->name_count; ++i) {
@@ -3910,26 +4552,37 @@ static void statements(Compiler *c, const Stmt *s) {
const char *kind = declared_kind(c, name);
if (kind) fail_at(c, s->line, s->column, "'%s' is already declared as %s", name, kind);
if (loop_named(c, name) >= 0) fail_at(c, s->line, s->column, "'%s' is named by a for around it", name);
- if (c->local_count == LOCALS || c->local_slots == LOCALS)
- fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS);
- unsigned local = c->local_count++;
- memset(&c->locals[local], 0, sizeof(c->locals[local]));
- memcpy(c->locals[local].name, name, NAME);
- bool held = kinds[i].type == BYTES || kinds[i].type == TEXT;
- c->locals[local].slot = held ? held_slot(c, s, kinds[i].type) : c->local_slots++;
- c->locals[local].kind = kinds[i];
- c->locals[local].list = -1;
- }
- for (unsigned i = s->name_count; i-- > 0;) {
- const Kind *kind = &c->locals[first + i].kind;
- if (kind->type == BYTES || kind->type == TEXT) emit(c, s->line, s->column, BYTES_STORE, c->locals[first + i].slot, 0);
- else emit(c, s->line, s->column, LOCAL_STORE, local_arg(c, (int)(first + i)), 0);
+ name_value(c, name, kinds[i], s->line, s->column);
}
+ (void)first;
+ for (unsigned i = s->name_count; i-- > 0;) pop_named(c, local_named(c, s->names[i]), s->line, s->column);
break;
}
case S_ASSIGNS: {
const Expr *v = s->value;
int locals[STACK], states[STACK];
+ if (!v->next) { /* a fn's call that gives them all (027's 2c): each into a let of its kind */
+ Kind given = value(c, v, c->handling, 0);
+ const Instance *in = given.type == TUPLE ? &c->instances[given.owner] : NULL;
+ if (!in || in->result_count != s->name_count)
+ fail_at(c, in ? v->line : s->line, in ? v->column : s->column, "%u names take %u values, not %u", s->name_count,
+ s->name_count, in ? in->result_count : 1);
+ for (unsigned i = 0; i < s->name_count; ++i) { /* a let of its kind, or a number's, bool's or i64's state */
+ locals[i] = local_named(c, s->names[i]);
+ states[i] = locals[i] < 0 ? state_index(c, s->names[i]) : -1;
+ int type = states[i] >= 0 ? c->state_types[states[i]] : -1;
+ Kind held = locals[i] >= 0 ? c->locals[locals[i]].kind :
+ (Kind){type == STATE_NUM ? NUM : type == STATE_BOOL ? BOOL : type == STATE_I64 ? I64 : 0, -1};
+ if ((locals[i] < 0 && !held.type) || (locals[i] >= 0 && c->locals[locals[i]].counter) || !same_kind(held, in->results[i]))
+ fail_at(c, s->line, s->column, "'%s' takes %s here: a let or a state of that kind", s->names[i],
+ type_name(in->results[i].type));
+ if (states[i] >= 0) view_changes(c, s, -1, s->names[i]);
+ }
+ for (unsigned i = s->name_count; i-- > 0;)
+ if (locals[i] >= 0) pop_named(c, locals[i], s->line, s->column);
+ else emit(c, s->line, s->column, STORE, (uint32_t)states[i], 0);
+ break;
+ }
for (unsigned i = 0; i < s->name_count; ++i, v = v->next) {
const char *name = s->names[i];
locals[i] = local_named(c, name);
@@ -3977,6 +4630,16 @@ static void statements(Compiler *c, const Stmt *s) {
emit(c, s->line, s->column, BYTES_STORE, c->locals[local].slot | (append ? HELD_APPEND : 0), 0);
break;
}
+ if (local >= 0 && (c->locals[local].kind.type == RECORD || c->locals[local].kind.type == LIST)) { /* (027's 2c) */
+ Kind kind = value(c, s->value, c->handling, 0);
+ if (!same_kind(kind, c->locals[local].kind))
+ fail_at(c, s->value->line, s->value->column, "'%s' holds %s of %s", s->name, type_name(c->locals[local].kind.type),
+ c->records[c->locals[local].kind.owner].name);
+ pop_named(c, local, s->line, s->column);
+ break;
+ }
+ if (local >= 0 && c->locals[local].alias)
+ fail_at(c, s->line, s->column, "'%s' names what a case holds; it is not assigned", s->name);
if (local >= 0) { /* a let takes a new value of its kind */
Kind kind = wanted(c, s->value, c->handling, 0, c->locals[local].kind.type);
if (kind.type != c->locals[local].kind.type || (kind.type == INDEX && kind.owner != c->locals[local].kind.owner))
@@ -4059,7 +4722,7 @@ static void statements(Compiler *c, const Stmt *s) {
view_changes(c, s, list, c->state_names[list]);
const Record *inner = &c->records[r->field_record[field]];
const Expr *values[FIELDS_PER_RECORD];
- record_values_of(c, s->value, inner, path, values);
+ record_values_of(c, s->value, inner, path, values, 1);
record_fields_of(c, s->value, inner, values, 1);
emit(c, s->line, s->column, APPEND_IN, (uint32_t)list | (uint32_t)field << 16, 0);
break;
@@ -4086,7 +4749,7 @@ static void statements(Compiler *c, const Stmt *s) {
view_changes(c, s, list, s->name);
const Record *r = record_of(c, list);
const Expr *values[FIELDS_PER_RECORD];
- record_values(c, s->value, list, values);
+ record_values(c, s->value, list, values, 0);
int key = c->list_key[list] - 1;
for (unsigned k = 0; k < r->fields; ++k)
if (!values[k]) fail_at(c, s->value->line, s->value->column, "%s[...] = %s { ... } needs '%s'", s->name,
@@ -4175,6 +4838,10 @@ static void statements(Compiler *c, const Stmt *s) {
case S_FOR: {
/* EACH and the block after it, run once for every item the list has, with the item as ROW. */
if (c->loop_count == NESTING) fail_at(c, s->line, s->column, "fors nest at most %d deep", NESTING);
+ if (!s->low && !s->value && text_path(c, s->member)) { /* for ch in t: its characters (027's 2c) */
+ chars_loop(c, s, path_expr(c, s->member, s->first->line, s->first->column));
+ break;
+ }
int list = walked_list(c, s->member, s->first->line, s->first->column);
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);
@@ -4228,11 +4895,12 @@ static void statements(Compiler *c, const Stmt *s) {
++c->nested;
statements(c, s->then);
if (s->otherwise) {
+ bool past = !stops(s->then, true); /* (a then that returns or breaks has no way past the else) */
unsigned skip = c->code_count;
- emit(c, s->line, s->column, JMP, 0, 0);
+ if (past) emit(c, s->line, s->column, JMP, 0, 0);
c->code[jump].arg = c->code_count;
statements(c, s->otherwise);
- c->code[skip].arg = c->code_count;
+ if (past) c->code[skip].arg = c->code_count;
} else c->code[jump].arg = c->code_count;
--c->nested;
break;
@@ -4274,7 +4942,48 @@ static void statements(Compiler *c, const Stmt *s) {
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;
- case S_RETURN: /* a view's body's value, its last line */
+ case S_CALL: {
+ Kind kind = value(c, s->value, c->handling, 0);
+ if (kind.type != NOTHING)
+ fail_at(c, s->line, s->column, "'%s' gives %s: name it, as in let x = %s(...)", s->value->name,
+ kind.type == TUPLE ? "several values" : type_name(kind.type), s->value->name);
+ break;
+ }
+ case S_RETURN:
+ if (c->current >= 0) { /* what a fn's call gives (027's 2c) */
+ Instance *in = &c->instances[c->current];
+ const char *name = c->fns[in->fn].name;
+ Kind kinds[STACK];
+ unsigned count = 0;
+ int height = 0;
+ for (const Expr *v = s->value; v; v = v->next) { /* each as its type, once known (a literal an i64's) */
+ Type want = in->known && count < in->result_count ? in->results[count].type : 0;
+ Kind kind = as_value(c, v, want == NUM || want == I64 ? wanted(c, v, true, height, want) : value(c, v, true, height));
+ if (kind.type == TUPLE) { /* return f(x): what it gives */
+ if (v != s->value || v->next) fail_at(c, v->line, v->column, "a call that gives several values returns alone");
+ memcpy(kinds, c->instances[kind.owner].results, c->instances[kind.owner].result_count * sizeof(*kinds));
+ count = c->instances[kind.owner].result_count;
+ break;
+ }
+ if (count == STACK) fail_at(c, v->line, v->column, "a fn gives at most %d values", STACK);
+ kinds[count++] = kind;
+ height += (int)cells(c, kind);
+ if (height > STACK) fail_at(c, v->line, v->column, "expression needs more than 64 stack values");
+ }
+ if (!in->known) {
+ memcpy(in->results, kinds, count * sizeof(*kinds));
+ in->result_count = count, in->known = true;
+ }
+ bool same = count == in->result_count;
+ for (unsigned k = 0; same && k < count; ++k) same = same_kind(kinds[k], in->results[k]);
+ if (!same)
+ fail_at(c, s->line, s->column, "fn '%s' gives %u value%s here (%s first), and %u before (%s first)", name, count,
+ count == 1 ? "" : "s", count ? type_name(kinds[0].type) : "none", in->result_count,
+ in->result_count ? type_name(in->results[0].type) : "none");
+ emit(c, s->line, s->column, RETURN, (uint32_t)c->current, 0);
+ break;
+ }
+ /* a view's body's value, its last line */
if (c->handling) fail_at(c, s->line, s->column, "return gives a view's body its value; a handler returns nothing");
if (s->next || c->nested) fail_at(c, s->line, s->column, "return is the last line of a view's body");
c->returned = value(c, s->value, false, 0);
@@ -4285,6 +4994,98 @@ static void statements(Compiler *c, const Stmt *s) {
c->local_count = scope;
}
+/* Whether a for's list names a text (027's 2c): a let's, a parameter's, a state's, or a text field of a record's value
+ * held by name. */
+static bool text_path(Compiler *c, const char *path) {
+ if (strchr(path, '.')) { /* a record's text field, or a message's */
+ char bound[2 * NAME + 2], field[NAME];
+ Expr *e = path_expr(c, path, 0, 0);
+ int binding = binding_path(c, e, bound), local = binding >= 0 ? local_named(c, bound) : -1, input;
+ if (binding < 0 && message_path(c, e, &input, field))
+ for (unsigned f = 0; f < c->inputs[input].fields; ++f)
+ if (!strcmp(c->inputs[input].field_names[f], field)) return c->inputs[input].field_types[f] == FIELD_TEXT;
+ return local >= 0 && c->locals[local].kind.type == TEXT;
+ }
+ int param = parameter(c, path), local = c->scope ? -1 : local_named(c, path), state = c->module ? -1 : state_index(c, path);
+ if (param >= 0) return c->scope->kinds[param].type == TEXT;
+ if (local >= 0) return c->locals[local].kind.type == TEXT;
+ return state >= 0 && c->state_types[state] == STATE_TEXT;
+}
+
+/* for ch in t: t held by a name of its own, then a count whose block begins if at >= end { break }, takes the character
+ * at at (its code point, an i64 named ch, read only) and moves at past it (1 to 4 bytes, by the code point). */
+static void chars_loop(Compiler *c, const Stmt *s, const Expr *subject) {
+ int line = s->line, column = s->column;
+ 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);
+ unsigned scope = c->local_count;
+ expect_kind(c, subject, value(c, subject, c->handling, 0), TEXT);
+ char name[NAME];
+ int locals[4];
+ static const char *const parts[] = {"text", "at", "end", "n"};
+ static const Type types[] = {TEXT, I64, I64, NUM};
+ for (unsigned k = 0; k < 4; ++k) {
+ snprintf(name, sizeof(name), ":%.24s.%s", s->name, parts[k]);
+ name_value(c, name, (Kind){types[k], -1}, line, column);
+ locals[k] = (int)c->local_count - 1;
+ }
+ name_value(c, s->name, (Kind){I64, -1}, line, column);
+ int ch = (int)c->local_count - 1;
+ c->locals[ch].counter = true;
+ store_named(c, locals[0], line, column);
+ load_named(c, locals[0], line, column);
+ emit(c, line, column, TEXT_BYTES, 0, 0);
+ emit(c, line, column, I64_FROM, 0, 0);
+ store_named(c, locals[2], line, column);
+ emit_integer(c, line, column, 0);
+ store_named(c, locals[1], line, column);
+ emit(c, line, column, CONST, 0, 0);
+ emit(c, line, column, CONST, 0, 9007199254740992.0);
+ unsigned loop = c->loop_count++, at = c->code_count;
+ 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, line, column, LOOP, 0, 0);
+ load_named(c, locals[1], line, column); /* if at >= end { break } */
+ load_named(c, locals[2], line, column);
+ emit(c, line, column, I64_COMPARE, GE - LT, 0);
+ unsigned skip = c->code_count;
+ emit(c, line, column, JZ, 0, 0);
+ emit(c, line, column, BREAK, 0, 0);
+ c->code[skip].arg = c->code_count;
+ load_named(c, locals[0], line, column); /* ch = the character at at */
+ load_named(c, locals[1], line, column);
+ emit(c, line, column, CHAR_AT, 0, 0);
+ emit(c, line, column, LOCAL_STORE, local_arg(c, ch), 0);
+ load_named(c, locals[1], line, column); /* at = at + its bytes */
+ static const int64_t below[] = {0x80, 0x800, 0x10000};
+ unsigned exits[3];
+ for (unsigned k = 0; k < 3; ++k) {
+ emit(c, line, column, LOCAL_LOAD, local_arg(c, ch), 0);
+ emit_integer(c, line, column, below[k]);
+ emit(c, line, column, I64_COMPARE, 0, 0);
+ unsigned next = c->code_count;
+ emit(c, line, column, JZ, 0, 0);
+ emit_integer(c, line, column, (int64_t)k + 1);
+ exits[k] = c->code_count;
+ emit(c, line, column, JMP, 0, 0);
+ c->code[next].arg = c->code_count;
+ }
+ emit_integer(c, line, column, 4);
+ for (unsigned k = 0; k < 3; ++k) c->code[exits[k]].arg = c->code_count;
+ emit(c, line, column, I64_ADD, 0, 0);
+ store_named(c, locals[1], line, column);
+ ++c->nested;
+ statements(c, s->then);
+ --c->nested;
+ c->code[at].arg = c->locals[locals[3]].slot | (c->code_count - at - 1) << 16;
+ --c->loop_count;
+ c->local_count = scope;
+}
+
static unsigned char *put16(unsigned char *p, uint32_t v) {
p[0] = (unsigned char)v;
p[1] = (unsigned char)(v >> 8);
@@ -4327,8 +5128,9 @@ static uint32_t intern(unsigned char *strings, size_t *used, const char *text) {
* 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 + c->views; ++h) /* the handlers', then the views' bodies' */
- for (const Stmt *s = h < c->handler_count ? c->handlers[h].body : c->view_body[h - c->handler_count]; s; s = s->next) {
+ for (unsigned h = 0; h < c->handler_count + c->views + c->fn_count; ++h) /* the handlers', the views' bodies', the fns' */
+ for (const Stmt *s = h < c->handler_count ? c->handlers[h].body : h < c->handler_count + c->views ?
+ c->view_body[h - c->handler_count] : c->fns[h - c->handler_count - c->views].block; s; s = s->next) {
if (s->kind != S_LET || !s->listed) continue;
char path[2 * NAME];
snprintf(path, sizeof(path), "let.%s", s->name);
@@ -4533,6 +5335,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
event_lists(c);
pull_records(c);
flatten_records(c);
+ called_fns(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]};
@@ -4665,6 +5468,24 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
statements(c, c->handlers[i].body);
c->handler_code[i][1] = c->code_count - c->handler_code[i][0];
}
+ /* The functions' code after the handlers' and views' (027's 2c), each one's jumps moved with it. */
+ unsigned signature_items = 0;
+ for (unsigned i = 0; i < c->instance_count; ++i) {
+ Instance *in = &c->instances[i];
+ if (c->code_count + in->count > CODE) fail_at(c, 1, 1, "program exceeds 65535 instructions");
+ in->start = c->code_count;
+ for (unsigned k = 0; k < in->count; ++k) {
+ Op op = in->ops[k];
+ if (op.op == JZ || op.op == JMP) op.arg += in->start;
+ c->ops[c->code_count++] = op;
+ }
+ unsigned taken = 0, given = 0;
+ for (unsigned k = 0; k < in->param_count; ++k) taken += cells(c, in->params[k]);
+ for (unsigned k = 0; k < in->result_count; ++k) given += cells(c, in->results[k]);
+ if (taken > STACK || given > STACK)
+ fail_at(c, c->fns[in->fn].line, c->fns[in->fn].column, "fn '%s' takes or gives more than 64 values", c->fns[in->fn].name);
+ signature_items += taken + given;
+ }
static unsigned char strings[(STATES + COLLECTIONS + 2 * INPUTS + FIELDS + VIEWS + RECORDS + RECORD_FIELDS) * NAME];
size_t used = 0;
uint32_t state_name[STATES] = {0}, collection_name[COLLECTIONS] = {0}, input_name[INPUTS] = {0}, input_key[INPUTS] = {0};
@@ -4689,7 +5510,8 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
record_field_name[i][k] = intern(strings, &used, c->records[i].field_names[k]);
}
*size = 40 + 16u * c->states + 12u * c->collections + 16u * c->input_count + 8u * fields + 8u * c->constants +
- 8u * c->record_count + 16u * c->record_fields + 16u * c->views + 12u * c->handler_count + 16u * c->code_count + used + c->constant_used;
+ 8u * c->record_count + 16u * c->record_fields + 16u * c->views + 12u * c->handler_count + 12u * c->instance_count +
+ 4u * signature_items + 16u * c->code_count + used + c->constant_used;
unsigned char *p = *module = allocate(c, *size);
memcpy(p, "PUREMOD6", 8);
put16(p + 8, c->states);
@@ -4704,6 +5526,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
put16(p + 30, c->record_count);
put32(p + 32, (uint32_t)c->constant_used);
put16(p + 36, c->record_fields);
+ put16(p + 38, c->instance_count);
p += 40;
for (unsigned i = 0; i < c->states; ++i) {
p = put32(p, state_name[i]);
@@ -4770,6 +5593,32 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
p = put32(p, c->handler_code[i][0]);
p = put32(p, c->handler_code[i][1]);
}
+ for (unsigned i = 0; i < c->instance_count; ++i) { /* functions: a block, how many values it takes and gives */
+ const Instance *in = &c->instances[i];
+ unsigned taken = 0, given = 0;
+ for (unsigned k = 0; k < in->param_count; ++k) taken += cells(c, in->params[k]);
+ for (unsigned k = 0; k < in->result_count; ++k) given += cells(c, in->results[k]);
+ p = put32(p, in->start);
+ p = put32(p, in->count);
+ *p++ = (unsigned char)taken, *p++ = (unsigned char)given;
+ p = put16(p, 0);
+ }
+ for (unsigned i = 0; i < c->instance_count; ++i) /* their values' types, those taken then those given */
+ for (unsigned k = 0; k < c->instances[i].param_count + c->instances[i].result_count; ++k) {
+ const Instance *in = &c->instances[i];
+ Kind kind = k < in->param_count ? in->params[k] : in->results[k - in->param_count];
+ static const uint8_t types[] = {[NUM] = 0, [TEXT] = 1, [BOOL] = 2, [I64] = 3, [LIST] = 4, [BYTES] = 5};
+ if (kind.type != RECORD) {
+ *p++ = types[kind.type], *p++ = 0;
+ p = put16(p, kind.type == LIST ? (uint32_t)kind.owner : 0);
+ continue;
+ }
+ const Record *r = &c->records[kind.owner];
+ for (unsigned f = 0; f < r->fields; ++f) {
+ *p++ = types[r->field_kinds[f]], *p++ = 0;
+ p = put16(p, r->field_kinds[f] == LIST ? (uint32_t)r->field_record[f] : 0);
+ }
+ }
for (unsigned i = 0; i < c->code_count; ++i) {
*p++ = c->code[i].op; *p++ = 0; *p++ = 0; *p++ = 0;
p = put32(p, c->code[i].arg);
@@ -4793,6 +5642,9 @@ bool aup_compile(const char *source, size_t length, AupLoad load, void *context,
c->row = -1;
c->arguments = -1;
c->fold_list = -1;
+ c->code = c->ops;
+ c->code_capacity = CODE;
+ c->current = -1;
c->load = load;
c->load_context = context;
c->module_count = 1; /* the main source */
@@ -4812,6 +5664,7 @@ bool aup_compile(const char *source, size_t length, AupLoad load, void *context,
*size = 0;
}
for (size_t i = 0; i < c->block_count; ++i) free(c->blocks[i]);
+ for (unsigned i = 0; i < c->instance_count; ++i) free(c->instances[i].ops);
free(c->blocks);
free(c);
return *module != NULL;
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index 3b02291..49e6ecd 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -11,8 +11,8 @@ aup는 범용 언어가 아니라 **무엇이 무엇에 연결되는지를 선�
## 원칙
-1. 모든 구문은 Pure가 로드 전에 검증할 수 있는 것으로 컴파일된다. 무제한 반복·재귀는 없다.
- 길이에 비례하는 연산은 이벤트 작업 예산을 쓰고, 넘으면 이벤트가 실패한다.
+1. 모든 구문은 Pure가 로드 전에 검증할 수 있는 것으로 컴파일된다. 끝을 모르는 반복(`while`)과 재귀도 있다(Pure 027).
+ 끝남은 문법이 아니라 예산이 보증한다. 걸음은 이벤트 작업 예산을, 부름의 깊이는 메모리 몫을 쓰고, 넘으면 이벤트가 실패한다.
2. 이벤트가 트랜잭션이다. 효과는 이벤트 경계에서만 일어나고, 실패하면 아무것도 바뀌지 않는다.
3. 형태는 한 번만 선언한다. 같은 `record`가 저장·메시지·전송·보기에 쓰이고 표현은 런타임이 고른다.
4. 표면은 가독성을 우선한다. 줄바꿈으로 끝나는 문장, `{}` 블록, `state / input / on / view`.
@@ -234,8 +234,8 @@ view summary = words.counted(tasks.count, "task", "tasks")
매개변수에 타입을 적지 않는다. 부르는 곳마다 인라인하므로 수·text·커서·인덱스 무엇이든 받는다. 본문의
타입 오류는 본문 위치에 `(in fn 'f' called at 줄:열)`을 붙여 알린다.
- **런타임 호출이 없다.** 인자는 부를 때 한 번 계산해 스택에 두고, 본문은 Pure의 PICK으로 읽고 SLIDE로 치운다.
- 인자를 여러 번 써도 다시 계산하지 않으며, 쓰지 않는 인자도 계산한다(값 전달). 재귀는 컴파일 오류다.
- 그래서 실행량 상한과 로드 전 검증이 그대로 유지된다.
+ 인자를 여러 번 써도 다시 계산하지 않으며, 쓰지 않는 인자도 계산한다(값 전달). 재귀하는 fn과 블록 본문의 fn은 Pure의 함수로
+ 돈다(아래 "스스로 부르는 fn", Pure 027의 2c).
- **use는 라이브러리를 가져온다.** `use words`는 소스 옆의 `words.aup`, 없으면 `AUP_PATH`(`:`로 구분)의 디렉터리에서
찾는다. 라이브러리는 `fn`, `record`(아래), `use`만 선언하고 앱의 상태를 볼 수 없다(매개변수만 읽는다). 부를 때는
`words.counted(...)`처럼 라이브러리 이름을 붙인다. 순환 use는 오류이고, 라이브러리의 오류는 그 파일 경로로 알린다.
@@ -752,3 +752,65 @@ on check {
- 받아들이는 꼴 하나.
- 중첩 값의 앱이 기대값을 낸다. 같은 이벤트를 두 번 해도 같은 몫이 늘고, 실패한 이벤트는 아무것도 남기지 않는다.
- `examples/json_tree.aup`는 Pure의 검사가 JSONTestSuite와 무작위 문서 100개로 파이썬의 읽기와 대조한다.
+
+### 스스로 부르는 fn, 블록, 여러 값, 값으로서의 레코드, 글자 (Pure 027의 2c)
+
+`examples/json_tree.aup`가 이것으로 재귀 하강 파서와 깊이 우선 걷기로 다시 쓰였다.
+
+```text
+fn value(d, i) -> Json, number { # 블록 본문의 fn. 스스로 부르면 결과의 타입을 적는다
+ let items: list of Json # let의 리스트는 부를 때마다 제 것이다
+ let j = skip(d, i)
+ if d[j] == 0x5b {
+ let v, k = value(d, j + 1) # 여러 값을 한 번에 받는다. v는 Json 값이다
+ items.append(v)
+ return Json.arr(items), k + 1 # 레코드나 갈래의 값을 돌려준다
+ }
+ ...
+}
+fn walk(x) { # 값을 주지 않는 fn: 부름은 문장이다
+ match x { # 이름에 담긴 갈래의 값을 match한다
+ arr(items) => { for y in items { walk(y) } } # 항목은 값으로 넘어간다
+ _ => {}
+ }
+}
+on check {
+ let root, end = value(bytes(text(check.data)), 0)
+ walk(root)
+ for ch in check.name { n = n + number(ch) } # text의 글자(코드 포인트, i64)
+}
+```
+
+- **fn이 Pure의 함수가 되는 때.** 블록 본문을 가졌거나, 부름의 고리가 자신을 지나면 Pure의 함수(CALL)로 돈다. 식 하나의 fn은
+ 지금처럼 인라인한다. 인자의 타입마다 한 벌을 만든다(단형화). 레코드 값을 받는 인라인 fn도 함수로 돈다.
+- **결과.**
+ - `return a, b`의 값들이 결과다. 값 없는 `return`과, 끝에 이르는 블록은 아무것도 주지 않는다.
+ - 결과의 타입은 처음 컴파일된 `return`이 정한다. 그보다 먼저 자신을 부르면 `-> 타입, …`을 적어야 한다(진단).
+ - 모든 `return`은 같은 타입을 준다. 결과가 있는 fn은 모든 길이 `return`으로 끝나야 한다.
+ - 적은 결과의 타입은 `return`의 값이 바란다. 그래서 `-> i64`인 fn의 `return 1`은 i64다.
+- **부름.** 식 안에서 쓰거나, 값을 주지 않으면 문장으로 쓴다(`walk(root)`). 여러 값은 `let a, b = f(x)`나 `a, b = f(x)`(let과 수,
+ bool, i64 상태)로 받고, `return f(x)`로 그대로 넘긴다.
+- **보기에서는 부르지 않는다.** 보기와 count·sum·max·min의 블록은 함수를 부르지 않는다(Pure가 그렇다). 핸들러와 함수가 부른다.
+- **레코드의 값.**
+ - `R { … }`, `V.case(x)`, `V.case`, 레코드 값을 주는 부름, 이름에 담긴 레코드 값은 그 필드들로 스택에 놓인다.
+ - `let p = …`는 이름 하나에 레코드를 담는다. 필드는 `p.x`로 읽힌다(이름 `p.필드`의 let들).
+ - 항목(`for y in ys`의 y, `m.value` 같은 레코드 필드의 경로)은 함수에 값으로 넘어간다. 함수는 제 리스트를 가지므로 항목의 자리가
+ 아니라 값을 받는다.
+ - `match`는 이름에 담긴 갈래의 값도 받는다. 경우의 이름은 읽기 전용이다.
+ - 리스트 값은 지역 칸에 담기고(Pure의 지역 칸 타입 4), `for y in items`가 레지스터에 묶어 걷는다.
+- **도달하지 않는 코드.** `return`이나 `break` 뒤의 줄은 진단이 나온다. 모든 경우가 이름 붙은 `match`의 마지막 갈래는 묻지 않고
+ 탄다. `if`의 then이 돌아가거나 멈추면 else를 건너는 점프를 두지 않는다.
+- **글자.**
+ - `for ch in t`는 text의 글자를 코드 포인트(i64)로 돈다. t는 let, 매개변수, 상태, 레코드 값이나 메시지의 text 필드다. 돌기 전에
+ 제 이름에 담는다.
+ - `char(n)`은 코드 포인트 하나의 text다. 서로게이트면 `encoding`, 0–0x10FFFF 밖이면 `limit`이다(Pure의 CHAR).
+
+검증:
+- 진단 13건: 결과를 모른 채 자신을 부름, 보기의 부름, 값 없이 끝날 수 있는 fn, 서로 다른 결과, 도달하지 않는 줄, 값을 주는 부름의
+ 문장, 여러 값을 이름 하나에, 이름과 값의 수, 모르는 타입, 커서 인자, 경우 이름에 대입, 갈래가 아닌 match, 접기 안의 부름.
+- 받아들이는 꼴 둘.
+- 함수의 앱이 파이썬과 같다. 무작위 트리 40개를 재귀로 짓고 합하고, 깊이와 잎 수를 여러 값으로 받는다. 무작위 text 60개의 글자 수,
+ 코드 포인트의 합, 넓은 글자 수, 그리고 `char`로 다시 지은 text다. 끝없는 재귀는 `memory`로 실패하고 아무것도 바꾸지 않는다.
+- 모듈은 2b의 컴파일러와 바이트까지 같다(aup와 Pure의 aup 소스 49개). 다른 것은 `match`를 쓴 `json_tree.aup`뿐이다.
+- 바뀐 진단 문구 셋: 레코드가 이제 값이라 `n.insert(at, T { … })`는 "expected text, not a record"다. let의 타입 목록에 리스트와
+ 레코드가 들었다. 자신을 부르는 식 fn은 이제 보기에서만 거절된다.
diff --git a/examples/json_tree.aup b/examples/json_tree.aup
index 4451c39..bc97a73 100644
--- a/examples/json_tree.aup
+++ b/examples/json_tree.aup
@@ -1,5 +1,5 @@
-# A JSON text (RFC 8259) read into a tree of values, then walked breadth first for a digest of it: how many of each
-# kind, the numbers' sum, the strings' and the keys' bytes. Input that is no JSON text leaves valid false.
+# A JSON text (RFC 8259) read into a tree of values by recursive descent, then walked for a digest of it: how many of
+# each kind, the numbers' sum, the strings' and the keys' bytes. A text that is no JSON fails the event.
variant Json {
null
bool(bool)
@@ -12,12 +12,6 @@ record Member {
name: text
value: Json
}
-record Open {
- items: list of Json
- members: list of Member
- name: text
- object = false
-}
state valid = false
state nulls = 0
state trues = 0
@@ -35,137 +29,124 @@ fn space(b) = b == 0x20 || b == 0x09 || b == 0x0a || b == 0x0d
fn digit(b) = b >= 0x30 && b <= 0x39
fn hex(b) = digit(b) || (b >= 0x61 && b <= 0x66) || (b >= 0x41 && b <= 0x46)
fn nibble(b) = if digit(b) { b - 0x30 } else if b >= 0x61 { b - 0x57 } else { b - 0x37 }
+fn code(d, i) = nibble(d[i]) << 12 | nibble(d[i + 1]) << 8 | nibble(d[i + 2]) << 4 | nibble(d[i + 3])
+fn escape(d, i) = d[i] == 0x5c && d[i + 1] == 0x75 && hex(d[i + 2]) && hex(d[i + 3]) && hex(d[i + 4]) && hex(d[i + 5])
-on check {
- nulls, trues, falses, numbers, strings, arrays, objects, sum, text_bytes, key_bytes = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- let d = bytes(text(check.data))
- let n, i, ok, mode = d.bytes, 0, true, 0 # mode 0: a value, 1: after one, 2: a key, 3: a key or }, 4: a value or ]
- let root: list of Json
- let open: list of Open
- let v: list of Json # the value just read, then placed
- let q: list of Json # the tree's values, breadth first
- while ok {
- while i < n && space(d[i]) { i = i + 1 }
- if i == n {
- ok = mode == 1 && open.count == 0
- break
- }
- let b = d[i]
- if mode == 1 {
- if open.count == 0 { ok = false }
- else if b == 0x2c { mode = if open.last.object { 2 } else { 0 } }
- else if b == 0x5d && !open.last.object { v.append(Json.arr(open.last.items)) }
- else if b == 0x7d && open.last.object { v.append(Json.obj(open.last.members)) }
- else { ok = false }
- if v.count > 0 { open.remove(open.last) }
- i = i + 1
- } else if mode == 3 && b == 0x7d || mode == 4 && b == 0x5d {
- if b == 0x7d { v.append(Json.obj(open.last.members)) } else { v.append(Json.arr(open.last.items)) }
- open.remove(open.last)
- i = i + 1
- } else if mode == 2 || mode == 3 {
- ok = b == 0x22
- mode = 5 # the key, then ':'
- } else if b == 0x5b || b == 0x7b {
- open.append(Open { object: b == 0x7b })
- mode = if b == 0x5b { 4 } else { 3 }
- i = i + 1
- } else if b == 0x22 || mode == 5 {
- i = i + 1 # a string: its bytes, escapes decoded
- let s = d[i .. i]
- while ok && i < n && d[i] != 0x22 {
- let start = i
- while i < n && d[i] != 0x22 && d[i] != 0x5c && d[i] >= 0x20 { i = i + 1 }
- s = s + d[start .. i]
- if i < n && d[i] < 0x20 { ok = false }
- else if i + 1 < n && d[i] == 0x5c {
- let e = d[i + 1]
- if e == 0x75 {
- ok = i + 5 < n && hex(d[i + 2]) && hex(d[i + 3]) && hex(d[i + 4]) && hex(d[i + 5])
- let u = if ok { nibble(d[i + 2]) << 12 | nibble(d[i + 3]) << 8 | nibble(d[i + 4]) << 4 | nibble(d[i + 5]) } else { 0 }
- i = i + 6
- if (u >= 0xd800 && u < 0xdc00 && i + 5 < n && d[i] == 0x5c && d[i + 1] == 0x75 && hex(d[i + 2]) && hex(d[i + 3]) &&
- hex(d[i + 4]) && hex(d[i + 5])) {
- let low = nibble(d[i + 2]) << 12 | nibble(d[i + 3]) << 8 | nibble(d[i + 4]) << 4 | nibble(d[i + 5])
- if low >= 0xdc00 && low < 0xe000 {
- u = 0x10000 + ((u - 0xd800) << 10 | low - 0xdc00)
- i = i + 6
- }
- }
- if u < 0x80 { s = s + byte(u) }
- else if u < 0x800 { s = s + byte(0xc0 | u >> 6) + byte(0x80 | u & 0x3f) }
- else if u < 0x10000 { s = s + byte(0xe0 | u >> 12) + byte(0x80 | u >> 6 & 0x3f) + byte(0x80 | u & 0x3f) }
- else { s = s + byte(0xf0 | u >> 18) + byte(0x80 | u >> 12 & 0x3f) + byte(0x80 | u >> 6 & 0x3f) + byte(0x80 | u & 0x3f) }
- } else {
- let to = (if e == 0x62 { 0x08 } else if e == 0x66 { 0x0c } else if e == 0x6e { 0x0a } else if e == 0x72 { 0x0d }
- else if e == 0x74 { 0x09 } else { e })
- ok = e == 0x22 || e == 0x5c || e == 0x2f || e == 0x62 || e == 0x66 || e == 0x6e || e == 0x72 || e == 0x74
- s = s + byte(to)
- i = i + 2
- }
- } else if i < n && d[i] == 0x5c { ok = false }
+fn skip(d, i) {
+ let j = i
+ while j < d.bytes && space(d[j]) { j = j + 1 }
+ return j
+}
+
+# The string whose quote is at i, and where it ends.
+fn string(d, i) {
+ require d[i] == 0x22
+ let s = d[i .. i]
+ let j = i + 1
+ while d[j] != 0x22 {
+ let start = j
+ while d[j] != 0x22 && d[j] != 0x5c && d[j] >= 0x20 { j = j + 1 }
+ s = s + d[start .. j]
+ require d[j] >= 0x20
+ if d[j] == 0x5c {
+ let e = d[j + 1]
+ if e == 0x75 {
+ require escape(d, j)
+ let u = code(d, j + 2)
+ j = j + 6
+ if u >= 0xd800 && u < 0xdc00 && j + 5 < d.bytes && escape(d, j) && code(d, j + 2) >= 0xdc00 && code(d, j + 2) < 0xe000 {
+ u = 0x10000 + ((u - 0xd800) << 10 | code(d, j + 2) - 0xdc00)
+ j = j + 6
+ }
+ s = s + bytes(char(u))
+ } else {
+ require e == 0x22 || e == 0x5c || e == 0x2f || e == 0x62 || e == 0x66 || e == 0x6e || e == 0x72 || e == 0x74
+ s = s + byte(if e == 0x62 { 0x08 } else if e == 0x66 { 0x0c } else if e == 0x6e { 0x0a } else if e == 0x72 { 0x0d }
+ else if e == 0x74 { 0x09 } else { e })
+ j = j + 2
}
- ok = ok && i < n
- i = i + 1
- if mode == 5 {
- open[open.count - 1].name = text(s)
- while i < n && space(d[i]) { i = i + 1 }
- ok = ok && i < n && d[i] == 0x3a
- i = i + 1
- mode = 0
- } else { v.append(Json.str(text(s))) }
- } else if b == 0x2d || digit(b) { # -?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?: its text read as a number
- let start = i
- if b == 0x2d { i = i + 1 }
- while i < n && (digit(d[i]) || d[i] == 0x2e || d[i] == 0x65 || d[i] == 0x45 || d[i] == 0x2b || d[i] == 0x2d) { i = i + 1 }
- v.append(Json.num(number(text(d[start .. i]))))
- } else {
- let word = if b == 0x74 { "true" } else if b == 0x66 { "false" } else { "null" }
- ok = i + word.bytes <= n && text(d[i .. i + word.bytes]) == word
- i = i + word.bytes
- if ok && b == 0x74 { v.append(Json.bool(true)) }
- else if ok && b == 0x66 { v.append(Json.bool(false)) }
- else if ok { v.append(Json.null) }
- }
- if ok && v.count > 0 { # a value read: placed in what is open, or the root
- if open.count == 0 {
- ok = root.count == 0
- root.append(v.last)
- } else if open.last.object { open.last.members.append(Member { name: open.last.name, value: v.last }) }
- else { open.last.items.append(v.last) }
- v.remove(v.last)
- mode = 1
}
}
- valid = ok
- if ok {
- q.append(root.first)
- let at = 0
- while at < q.count {
- match q[at] {
- null => nulls = nulls + 1
- bool(t) => if t { trues = trues + 1 } else { falses = falses + 1 }
- num(x) => {
- numbers = numbers + 1
- sum = sum + x
- }
- str(t) => {
- strings = strings + 1
- text_bytes = text_bytes + t.bytes
- }
- arr(items) => {
- arrays = arrays + 1
- for y in items { q.append(y) }
- }
- obj(members) => {
- objects = objects + 1
- for m in members {
- key_bytes = key_bytes + m.name.bytes
- q.append(m.value)
- }
+ return text(s), j + 1
+}
+
+# The value from i (past white space), and where it ends.
+fn value(d, i) -> Json, number {
+ let items: list of Json # each call's own
+ let members: list of Member
+ let j = skip(d, i)
+ let b = d[j]
+ if b == 0x5b || b == 0x7b {
+ j = skip(d, j + 1)
+ if d[j] != b + 2 {
+ while true {
+ if b == 0x5b {
+ let v, k = value(d, j)
+ items.append(v)
+ j = skip(d, k)
+ } else {
+ let name, k = string(d, j)
+ k = skip(d, k)
+ require d[k] == 0x3a
+ let v, after = value(d, k + 1)
+ members.append(Member { name: name, value: v })
+ j = skip(d, after)
}
+ if d[j] != 0x2c { break }
+ j = skip(d, j + 1)
}
- at = at + 1
+ require d[j] == b + 2
}
+ if b == 0x5b { return Json.arr(items), j + 1 }
+ return Json.obj(members), j + 1
}
+ if b == 0x22 {
+ let s, k = string(d, j)
+ return Json.str(s), k
+ }
+ if b == 0x2d || digit(b) { # -?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?, read as a number
+ let start = j
+ j = j + 1
+ while j < d.bytes && (digit(d[j]) || d[j] == 0x2e || d[j] == 0x65 || d[j] == 0x45 || d[j] == 0x2b || d[j] == 0x2d) { j = j + 1 }
+ return Json.num(number(text(d[start .. j]))), j
+ }
+ let word = if b == 0x74 { "true" } else if b == 0x66 { "false" } else { "null" }
+ require j + word.bytes <= d.bytes && text(d[j .. j + word.bytes]) == word
+ if b == 0x6e { return Json.null, j + 4 }
+ return Json.bool(b == 0x74), j + word.bytes
+}
+
+fn walk(x) {
+ match x {
+ null => nulls = nulls + 1
+ bool(t) => if t { trues = trues + 1 } else { falses = falses + 1 }
+ num(n) => {
+ numbers = numbers + 1
+ sum = sum + n
+ }
+ str(t) => {
+ strings = strings + 1
+ text_bytes = text_bytes + t.bytes
+ }
+ arr(items) => {
+ arrays = arrays + 1
+ for y in items { walk(y) }
+ }
+ obj(members) => {
+ objects = objects + 1
+ for m in members {
+ key_bytes = key_bytes + m.name.bytes
+ walk(m.value)
+ }
+ }
+ }
+}
+
+on check {
+ nulls, trues, falses, numbers, strings, arrays, objects, sum, text_bytes, key_bytes = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ let d = bytes(text(check.data))
+ let root, end = value(d, 0)
+ require skip(d, end) == d.bytes
+ walk(root)
+ valid = true
}
diff --git a/tests/check.py b/tests/check.py
index abbdc0a..d5bb0af 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -78,10 +78,34 @@ DIAGNOSTICS = [
('state a = 0\non frame {\n a, a = 1, 2\n}\n', '3:6', "'a' is named twice"),
('state s: text\nstate a = 0\non frame {\n s, a = "x", 1\n}\n', '4:3', "'s' takes its value apart"),
('state t: text\nstate c: cursor in t\non frame {\n let a, b = 1, c\n}\n', '4:17',
- 'a let holds a number, a bool, an i64, an index, bytes or text, not a cursor'),
+ 'a let holds a number, a bool, an i64, an index, bytes, text, a list or a record, not a cursor'),
('record R {\n v = 0\n}\ninput m = message { d: bytes }\nstate t: text\non m {\n let xs: list of R\n'
' t = text(bytes(xs))\n}\n', '8:18', 'bytes(...) makes bytes of a list of i64, not of R'),
('state x = 0\nview v = [1, 2]\n', '2:10', "a list's elements begin a let's list"),
+ # Fns that call themselves, blocks, several values, records' values and characters (Pure 027's stage 2c).
+ ('fn f(n) = if n == 0 { 0 } else { f(n - 1) + 1 }\nstate x = 0\non frame {\n x = f(3)\n}\n', '1:34',
+ "fn 'f' calls itself before a return gives its value: write its type"),
+ ('fn f(x) {\n return x\n}\nview v = f(1)\n', '4:10', 'a view calls no fn with a block, nor one that calls itself'),
+ ('fn f(x) {\n if x > 0 { return 1 }\n}\nstate y = 0\non frame {\n y = f(1)\n}\n', '1:4',
+ "fn 'f' may end without giving its value: return it at the end"),
+ ('fn f(x) {\n if x > 0 { return 1 }\n return "a"\n}\nstate y = 0\non frame {\n y = f(1)\n}\n', '3:3',
+ "fn 'f' gives 1 value here (text first), and 1 before (a number first)"),
+ ('fn f(x) {\n return x\n x = 2\n}\nstate y = 0\non frame {\n y = f(1)\n}\n', '3:3',
+ 'this is never run: the line before it returns or breaks'),
+ ('fn f(x) {\n return x\n}\non frame {\n f(1)\n}\n', '5:3', "'f' gives a number: name it, as in let x = f(...)"),
+ ('fn f(x) {\n return x, x\n}\nstate y = 0\non frame {\n let a = f(1)\n y = a\n}\n', '6:11',
+ 'this gives several values: name each, as in let a, b = ...'),
+ ('fn f(x) {\n return x, x\n}\nstate y = 0\non frame {\n let a, b, c = f(1)\n y = a\n}\n', '6:17',
+ '3 names take 3 values, not 2'),
+ ('fn f(x) -> nope {\n return x\n}\nstate y = 0\non frame {\n y = f(1)\n}\n', '1:12', "unknown type 'nope'"),
+ ('state t: text\nstate at: cursor in t\nfn f(x) {\n return 1\n}\nstate y = 0\non frame {\n y = f(at)\n}\n', '8:9',
+ "a cursor does not go to a fn's call or come back from one"),
+ (JSON_M + 'state n = 0\nfn f(j) {\n match j {\n num(x) => x = 1\n _ => n = 1\n }\n}\non frame {\n f(Json.null)\n}\n',
+ '13:15', "'x' names what a case holds; it is not assigned"),
+ ('record P {\n x = 0\n}\nstate n = 0\non frame {\n let p = P { x: 1 }\n match p {\n _ => n = 1\n }\n}\n', '7:9',
+ "match takes a variant; 'p' holds none"),
+ ('record T {\n a = 0\n}\nstate xs: list of T\nfn f(x) {\n return x > 0\n}\nview v = count(x in xs where f(x.a))\n',
+ '8:30', 'count, sum, max and min call no fn with a block'),
# Records in records, variants, lists in records and match (Pure 027's stage 2b).
('record A {\n b: B\n}\nrecord B {\n a: A\n}\nstate xs: list of A\n', '1:8', "'A' holds itself: hold it in a list"),
('record A {\n b: Nope\n}\nstate xs: list of A\n', '2:3', "unknown record or variant 'Nope'"),
@@ -239,7 +263,7 @@ DIAGNOSTICS = [
(RECORD + 'state xs: list of T\nstate at: index of xs\n' + ON_K + ' xs[at].a = "x"\n}\n', '8:14',
'expected a number, not text'),
(RECORD + 'state n: text\nstate at: cursor in n\n' + ON_K + ' n.insert(at, T { a: 1 })\n}\n', '8:16',
- 'a record value goes into a list'),
+ 'expected text, not a record'),
('state n: text\nstate at: cursor in n\n' + ON_K + ' n.remove(at)\n}\n', '5:3', 'a text removes a range'),
('record T {\n position = 0\n}\n', '2:3', "'position' is where an index is"),
(LIST + 'view v = at.b\n', '6:10', "record 'T' has no field 'b'"),
@@ -262,7 +286,7 @@ DIAGNOSTICS = [
('view each = 1\n', '1:6', "'each' is reserved"),
# fns
('fn f(x) = x\nview v = f(1, 2)\n', '2:10', 'f expects 1 argument'),
- ('fn f(x) = f(x)\nview v = f(1)\n', '1:11', "fn 'f' calls itself (in fn 'f' called at 2:10)"),
+ ('fn f(x) = f(x)\nview v = f(1)\n', '2:10', "a view calls no fn with a block, nor one that calls itself"),
('fn f(x, x) = 1\n', '1:9', "duplicate parameter 'x'"),
('view v = g(1)\n', '1:10', "unknown fn 'g'"),
('fn f(x) = x + "a"\nview v = f(1)\n', '1:15', "expected a number, not text (in fn 'f' called at 2:10)"),
@@ -437,7 +461,86 @@ DIAGNOSTICS = [
]
NESTED = 'variant Json {\n null\n bool(bool)\n num(number)\n str(text)\n arr(list of Json)\n obj(list of Member)\n}\nrecord Member {\n name: text\n value: Json\n}\nrecord Point {\n x = 0\n y = 0\n}\nrecord Box {\n at: Point\n tag: text\n}\nstate boxes: list of Box\nstate total = 0\nstate chars = 0\nstate nulls = 0\nstate trues = 0\nstate corner = 0\ninput go = key KeyG\non go.down {\n let docs: list of Json\n docs.append(Json.num(3))\n docs.append(Json.str("hi"))\n docs.append(Json.null)\n let kids: list of Json\n kids.append(Json.num(1))\n kids.append(Json.num(2))\n docs.append(Json.arr(kids))\n docs.last.arr.append(Json.num(10))\n let ms: list of Member\n ms.append(Member { name: "a", value: Json.bool(true) })\n ms.append(Member { name: "b", value: Json.arr([]) })\n docs.append(Json.obj(ms))\n for d in docs {\n match d {\n num(n) => total = total + n\n str(t) => chars = chars + t.chars\n null => nulls = nulls + 1\n arr(items) => {\n for y in items {\n match y {\n num(n) => total = total + n\n _ => nulls = nulls + 100\n }\n }\n }\n obj(members) => {\n for m in members {\n match m.value {\n bool(b) => trues = trues + (if b { 1 } else { 0 })\n arr(xs) => chars = chars + xs.count * 1000\n _ => nulls = nulls + 1000\n }\n }\n }\n bool(b) => nulls = nulls + 10000\n }\n }\n boxes.append(Box { at: Point { x: 1, y: 2 }, tag: "a" })\n corner = boxes.last.at.x * 10 + boxes.last.at.y + docs.count * 100\n}\n'
+FUNCTIONS = '''variant Tree {
+ leaf(i64)
+ node(list of Tree)
+}
+state summed: i64 = 0
+state depth: i64 = 0
+state leaves = 0
+state count = 0
+state total: i64 = 0
+state wide = 0
+state said: text = ""
+state ran: i64 = 0
+input make = message { seed: i64, levels: i64 }
+input chars = message { t: text }
+input forever = key KeyF
+
+# A tree of levels levels: a leaf of seed at the bottom, else a node of seed % 3 + 1 trees, each its own seed.
+fn tree(seed, levels) -> Tree {
+ let kids: list of Tree
+ if levels == 0 { return Tree.leaf(seed) }
+ for k in 0 .. seed % 3 + 1 { kids.append(tree(seed * 7 + k, levels - 1)) }
+ return Tree.node(kids)
+}
+fn sum(t) -> i64 {
+ match t {
+ leaf(v) => return v
+ node(kids) => {
+ let s = i64(0)
+ for k in kids { s = s + sum(k) }
+ return s
+ }
+ }
+}
+fn deepest(t) -> i64, number {
+ match t {
+ leaf(v) => return 0x0, 1
+ node(kids) => {
+ let most, n = 0x0, 0
+ for k in kids {
+ let d, m = deepest(k)
+ if d + 1 > most { most = d + 1 }
+ n = n + m
+ }
+ return most, n
+ }
+ }
+}
+fn without_end(n) -> i64 = without_end(n + 1)
+
+on make {
+ let t = tree(make.seed, make.levels)
+ summed = sum(t)
+ depth, leaves = deepest(t)
+}
+on chars {
+ count, total, wide = 0, 0, 0
+ let out = ""
+ for ch in chars.t {
+ count = count + 1
+ total = total + ch
+ if ch >= 0x80 { wide = wide + 1 }
+ out = out + char(ch)
+ }
+ said = out
+}
+on forever.down {
+ ran = ran + 1
+ ran = without_end(0)
+}
+'''
+
ACCEPTED = [
+ # Fns that call themselves (Pure 027's 2c): a block, several values, records' values made, held, matched, given and
+ # appended, an item and a field's record passed as values, a walk of a list a value holds, a text's characters.
+ FUNCTIONS,
+ JSON_M + 'state n = 0\nstate t: text = "a\u00e9"\nfn both(a, b) {\n return b, a\n}\nfn tag(j) -> i64 {\n match j {\n'
+ ' num(x) => return 1\n arr(xs) => {\n for y in xs { n = n + number(tag(y)) }\n return 2\n }\n _ => return 0\n }\n}\n'
+ 'on frame {\n let xs: list of Json\n let j = Json.num(2)\n xs.append(j)\n let a, b = both(j, 3)\n xs.append(b)\n'
+ ' let ms: list of M\n ms.append(M { name: "k", value: Json.arr(xs) })\n for m in ms { n = n + number(tag(m.value)) }\n'
+ ' for ch in t { n = n + number(ch) }\n n = n + a\n}\n',
# Records in records, variants, lists in records, match, appends into a field's list (Pure 027's 2b).
NESTED,
# Formats, joins, slices, lets of bytes and text appended to, while, && and || that decide alone (Pure 027's 2a).
@@ -1407,6 +1510,51 @@ def nested_values(aup, pure_run, work):
return {'events': 3}
+
+
+def functions(aup, pure_run, work, trees=40, texts=60):
+ """Pure 027's 2c: fns that call themselves, each call its own (a let's list each, records' values passed and
+ given back, several values at once), against the same in Python: random trees built and summed; the characters of
+ random texts (code points, and the text again of them); a call without end fails the event, nothing changed."""
+ import random
+ import struct
+ path = work / 'functions.pmod'
+ (work / 'functions.aup').write_text(FUNCTIONS)
+ compile_module(aup, work / 'functions.aup', path)
+
+ def tree(seed, levels):
+ return seed if levels == 0 else [tree(seed * 7 + k, levels - 1) for k in range(seed % 3 + 1)]
+
+ def deepest(t):
+ if not isinstance(t, list):
+ return 0, 1
+ found = [deepest(k) for k in t]
+ return max(d for d, _ in found) + 1, sum(m for _, m in found)
+
+ def total(t):
+ return t if not isinstance(t, list) else sum(total(k) for k in t)
+
+ rng = random.Random(2)
+ asked = [(rng.randrange(1000), rng.randrange(7)) for _ in range(trees)]
+ alphabet = ['a', 'Z', ' ', '\n', 'é', 'ü', 'ß', '€', '한', 'ࠀ', '￿', '😀', '\U0010ffff', '\u007f', '\u0080']
+ words = [''.join(rng.choice(alphabet) for _ in range(rng.randrange(0, 40))) for _ in range(texts)]
+ commands = ''.join(f"message make {struct.pack('<qq', seed, levels).hex()}\n" for seed, levels in asked)
+ commands += ''.join(f"message chars {(w.encode() or b'').hex() or '-'}\n" for w in words)
+ commands += 'down forever\n'
+ lines = run(pure_run, path, stdin=commands, check=False).stdout.splitlines()
+ for (seed, levels), line in zip(asked, lines):
+ state = json.loads(line)
+ t = tree(seed, levels)
+ assert state['fault'] == 'none', (seed, levels, state['fault'])
+ assert state['states']['summed'] == total(t) and (state['states']['depth'], state['states']['leaves']) == deepest(t), (seed, levels)
+ for w, line in zip(words, lines[trees:]):
+ state = json.loads(line)['states']
+ assert (state['count'], state['total'], state['wide'], state['said']) == (len(w), sum(map(ord, w)), sum(ord(c) >= 0x80 for c in w), w), w
+ last = json.loads(lines[-1])
+ assert last['fault'] == 'memory' and last['states']['ran'] == 0, last['fault']
+ return {'trees': trees, 'texts': texts}
+
+
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
@@ -2215,6 +2363,7 @@ def main():
report['bits_and_bytes'] = bits_and_bytes(aup, pure_run, work)
report['formats_and_texts'] = formats_and_texts(aup, pure_run, work)
report['nested_values'] = nested_values(aup, pure_run, work)
+ report['functions'] = functions(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))