aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-19 07:05:44 +0100
committereunhhu <[email protected]>2026-10-19 07:05:44 +0100
commit24e3994b4c845831f867430cc4bb4c6e1e564a6d (patch)
tree2df643661d701baf1f6402deaaab557a475507d8
parent3f870ba7abf4e8abd5ddfeced58c64a2e2b86cec (diff)
downloadaup-24e3994b4c845831f867430cc4bb4c6e1e564a6d.tar.gz
aup-24e3994b4c845831f867430cc4bb4c6e1e564a6d.zip
Names together without a core change (140's frictions): lets' lists declared together (let a, b: list of R, a let each, empty as it begins; given elements refused), and targets together with fields and items among them (xs[i].x, xs[i].y = a, b; a, xs[i] = ...): the values first, each into a let of its own (a literal straight), then each target left to right as Python's; names alone are S_ASSIGNS as before; frictions in make check, two diagnostics; no module of aup's, Pure's or Vega's changes
-rw-r--r--compiler/compile.c144
-rw-r--r--tests/check.py83
2 files changed, 217 insertions, 10 deletions
diff --git a/compiler/compile.c b/compiler/compile.c
index 5c88b97..eab1b3c 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -1162,7 +1162,7 @@ static bool path_append(Compiler *c, Stmt *s, const Token *name) {
}
/* , b, c = x, y, z after the first name: every name, then as many values on the line. */
-static void several(Compiler *c, Stmt *s, const char *first) {
+static void several_names(Compiler *c, Stmt *s, const char *first) {
s->names = allocate(c, STACK * sizeof(*s->names));
memcpy(s->names[s->name_count++], first, NAME);
while (is(c, ",")) {
@@ -1174,6 +1174,10 @@ static void several(Compiler *c, Stmt *s, const char *first) {
if (!strcmp(s->names[i], name.text)) fail_at(c, name.line, name.column, "'%s' is named twice", name.text);
memcpy(s->names[s->name_count++], name.text, NAME);
}
+}
+
+/* = x, y (after the names): every name's value, or one call that gives them all. */
+static void several_values(Compiler *c, Stmt *s) {
expect(c, "=", "'='");
Expr **tail = &s->value;
unsigned values = 0;
@@ -1189,6 +1193,99 @@ static void several(Compiler *c, Stmt *s, const char *first) {
fail_at(c, s->line, s->column, "%u names take %u values, not %u", s->name_count, s->name_count, values);
}
+/* A target of a, xs[i].x = ...: a name (S_ASSIGN), a list's item's field or its item at a position (S_SET), or an
+ * index's field (at.x, S_SET); its value comes later. */
+static Stmt *target(Compiler *c, Token name) {
+ Stmt *t = allocate(c, sizeof(*t));
+ t->line = name.line, t->column = name.column;
+ memcpy(t->name, name.text, NAME);
+ if (is(c, "[")) {
+ take(c);
+ t->kind = S_SET;
+ t->first = expression(c, 0, true);
+ newlines(c);
+ expect(c, "]", "']'");
+ if (is(c, ".")) {
+ take(c);
+ Token field = expect_name(c, "a field");
+ memcpy(t->member, field.text, NAME);
+ }
+ return t;
+ }
+ if (is(c, ".")) {
+ take(c);
+ Token field = expect_name(c, "a field");
+ t->kind = S_SET;
+ t->first = node(c, E_NAME, name.line, name.column);
+ memcpy(t->first->name, name.text, NAME);
+ t->name[0] = 0;
+ memcpy(t->member, field.text, NAME);
+ return t;
+ }
+ if (reserved_name(name.text)) fail_at(c, name.line, name.column, "'%s' is reserved", name.text);
+ t->kind = S_ASSIGN;
+ return t;
+}
+
+/* a, xs[i].x = x, y after its first target: every target its value at once, the values first, in order. Names alone
+ * are S_ASSIGNS; with a field or an item among them, the values go to lets of their own first (a literal, which
+ * reads nothing, goes straight), then each target takes its own: a let each and a statement each, linked. */
+static Stmt *together(Compiler *c, Stmt *first) {
+ Stmt *targets[STACK];
+ unsigned n = 0;
+ bool names = first->kind == S_ASSIGN;
+ targets[n++] = first;
+ while (is(c, ",")) {
+ take(c);
+ Token name = expect_name(c, "a name");
+ if (n == STACK) fail_at(c, name.line, name.column, "at most %d names take values at once", STACK);
+ Stmt *t = target(c, name);
+ for (unsigned i = 0; t->kind == S_ASSIGN && i < n; ++i)
+ if (targets[i]->kind == S_ASSIGN && !strcmp(targets[i]->name, t->name))
+ fail_at(c, name.line, name.column, "'%s' is named twice", name.text);
+ names = names && t->kind == S_ASSIGN;
+ targets[n++] = t;
+ }
+ Stmt *s = allocate(c, sizeof(*s));
+ s->line = first->line, s->column = first->column;
+ s->names = allocate(c, STACK * sizeof(*s->names));
+ s->name_count = n;
+ for (unsigned i = 0; i < n; ++i)
+ if (names) memcpy(s->names[i], targets[i]->name, NAME);
+ else snprintf(s->names[i], NAME, ".%d.%d.%u", s->line, s->column, i); /* no name of the source's */
+ s->kind = names ? S_ASSIGNS : S_LETS;
+ several_values(c, s);
+ if (names) return s;
+ Expr *values[STACK];
+ unsigned count = 0;
+ for (Expr *v = s->value; v; v = v->next) values[count++] = v;
+ Stmt *last = s;
+ Expr **link = &s->value;
+ unsigned kept = 0;
+ for (unsigned i = 0; i < n; ++i) {
+ Expr *v = count == n ? values[i] : NULL; /* NULL: one call gives them all */
+ if (v && (v->kind == E_NUMBER || v->kind == E_BOOL || v->kind == E_TEXT)) {
+ v->next = NULL; /* a literal reads nothing: straight to its target */
+ targets[i]->value = v;
+ } else {
+ memcpy(s->names[kept++], s->names[i], NAME);
+ Expr *given = node(c, E_NAME, s->line, s->column);
+ memcpy(given->name, s->names[i], NAME);
+ targets[i]->value = given;
+ if (v) *link = v, link = &v->next;
+ }
+ last = last->next = targets[i];
+ }
+ if (count == n) *link = NULL;
+ s->name_count = kept;
+ if (kept == 1) { /* one let: as let x = value */
+ s->kind = S_LET;
+ memcpy(s->name, s->names[0], NAME);
+ s->names = NULL, s->name_count = 0;
+ }
+ return kept ? s : s->next; /* every value a literal: no lets */
+}
+
static Stmt *statement(Compiler *c, unsigned depth) {
Token token = expect_name(c, "a statement or '}'");
Stmt *s = allocate(c, sizeof(*s));
@@ -1219,10 +1316,13 @@ static Stmt *statement(Compiler *c, unsigned depth) {
memcpy(s->name, name.text, NAME);
s->line = name.line;
s->column = name.column;
- if (is(c, ",")) { /* let a, b = x, y */
- s->kind = S_LETS;
- several(c, s, name.text);
- return s;
+ if (is(c, ",")) { /* let a, b = x, y, or let a, b: list of R (a list each, empty as it begins) */
+ several_names(c, s, name.text);
+ if (!is(c, ":")) {
+ s->kind = S_LETS;
+ several_values(c, s);
+ return s;
+ }
}
if (is(c, ":")) { /* let name: list of R [by field]: the event's list, empty as it begins */
take(c);
@@ -1239,6 +1339,20 @@ static Stmt *statement(Compiler *c, unsigned depth) {
s->key_column = key.column;
}
s->listed = true;
+ if (s->name_count > 1) { /* the names after the first: lets of their own, of the same list, after it */
+ Stmt *last = s;
+ for (unsigned i = 1; i < s->name_count; ++i) {
+ Stmt *other = allocate(c, sizeof(*other));
+ *other = *s;
+ other->names = NULL, other->name_count = 0;
+ memcpy(other->name, s->names[i], NAME);
+ last = last->next = other;
+ }
+ s->names = NULL, s->name_count = 0;
+ if (is(c, "="))
+ fail_at(c, c->token.line, c->token.column, "lists declared together begin empty: give each its elements apart");
+ return s;
+ }
if (is(c, "=")) { /* = [a, b, ...]: what it holds as it begins, appended in order */
take(c);
Token open = expect(c, "[", "'[' and the list's elements");
@@ -1435,6 +1549,9 @@ static Stmt *statement(Compiler *c, unsigned depth) {
}
if (is(c, "(")) fail_at(c, field.line, field.column, "a field changes by '=', as in %s[at].%s = value", s->name, field.text);
memcpy(s->member, field.text, NAME);
+ if (is(c, ",")) return together(c, s); /* xs[i].x, xs[i].y = a, b */
+ } else if (is(c, ",")) {
+ return together(c, s); /* xs[i], ys[j] = a, b */
} else if (is(c, "=")) { /* tasks[at] = Task { ... }: every field of the item (no member); xs[i] = 5 */
take(c);
s->value = inserted_value(c, false);
@@ -1450,6 +1567,14 @@ static Stmt *statement(Compiler *c, unsigned depth) {
if (is(c, ".")) {
take(c);
Token method = expect_name(c, "insert, remove, append or a field");
+ if (is(c, ",")) { /* at.x, at.y = a, b */
+ s->kind = S_SET;
+ s->first = node(c, E_NAME, token.line, token.column);
+ memcpy(s->first->name, s->name, NAME);
+ s->name[0] = 0;
+ memcpy(s->member, method.text, NAME);
+ return together(c, s);
+ }
if (is(c, "=")) {
/* at.field = value: a field of the item an index names. */
take(c);
@@ -1488,10 +1613,9 @@ static Stmt *statement(Compiler *c, unsigned depth) {
expect(c, ")", "')'");
return s;
}
- if (is(c, ",")) { /* a, b = b, a */
- s->kind = S_ASSIGNS;
- several(c, s, token.text);
- return s;
+ if (is(c, ",")) { /* a, b = b, a; a, xs[i].x = 1, 2 */
+ s->kind = S_ASSIGN;
+ return together(c, s);
}
s->kind = S_ASSIGN;
expect(c, "=", "'='");
@@ -1508,7 +1632,7 @@ static Stmt *block(Compiler *c, unsigned depth) {
while (!is(c, "}")) {
if (c->token.kind == T_EOF) fail_at(c, c->token.line, c->token.column, "expected '}'");
*tail = statement(c, depth);
- tail = &(*tail)->next;
+ while (*tail) tail = &(*tail)->next; /* let a, b: list of R is a let each */
end(c);
}
take(c);
diff --git a/tests/check.py b/tests/check.py
index e299911..72dee99 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -77,6 +77,9 @@ DIAGNOSTICS = [
('state y: i64\nview v = 0xffffffffffffffff + y\n', '2:10', "the number is past an i64's range"),
('state a = 0\nstate b = 0\non frame {\n a, b = 1\n}\n', '4:3', '2 names take 2 values, not 1'),
('state a = 0\non frame {\n a, a = 1, 2\n}\n', '3:6', "'a' is named twice"),
+ ('state a = 0\non frame {\n let xs, ys: list of number = [1]\n}\n', '3:30',
+ 'lists declared together begin empty: give each its elements apart'),
+ ('state a = 0\non frame {\n let xs, xs: list of number\n}\n', '3:11', "'xs' 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, text, a list or a record, not a cursor'),
@@ -645,6 +648,53 @@ on m {
}
'''
+FRICTIONS = '''record P {
+ x = 0
+}
+record B {
+ x = 0
+ y = 0
+}
+state total = 0
+state n = 0
+state bs: list of B
+state a = 0
+state b = 0
+state i = 0
+input go = message { k: u32 }
+on go {
+ if bs.count == 0 { bs.append(B {}) }
+ bs.append(B {})
+ bs[0].x, bs[0].y = go.k, go.k * 2
+ a, bs[1].x = bs[0].y, a + 1
+ i, bs[i].y = 1, 7
+ a, b = b, a
+ let ns: list of number
+ ns.append(0)
+ ns.append(0)
+ ns[0], ns[1] = 3, go.k
+ b = b + ns[0] * 100 + ns[1]
+ let xs, ys: list of number
+ let ps, qs: list of P
+ for j in 0 .. go.k {
+ xs.append(j)
+ ys.append(j * 2)
+ ps.append(P { x: j })
+ }
+ if go.k > 1 {
+ let inner, outer: list of number
+ inner.append(go.k)
+ outer.append(1)
+ n = inner.count * 10 + outer.count + inner[0]
+ }
+ let t = 0
+ for v in xs { t = t + v }
+ for v in ys { t = t + v * 100 }
+ for p in ps { t = t + p.x * 10000 }
+ total = t + qs.count
+}
+'''
+
INNER_LISTS = '''record Seen {
node = 0
depth = 0
@@ -1732,6 +1782,38 @@ def rows_at(aup, pure_run, work, events=200):
return events
+def frictions(aup, pure_run, work, events=100):
+ """140's frictions written away in aup alone, no core change: lets' lists declared together (let a, b: list of R),
+ each its own and empty as it begins, in inner blocks too; targets together with fields and items among them
+ (xs[i].x, xs[i].y = a, b; a, xs[i] = ...), the values first, then each target left to right as Python's. Each event
+ against Python's."""
+ import random
+ import struct
+ path = work / 'frictions.pmod'
+ (work / 'frictions.aup').write_text(FRICTIONS)
+ compile_module(aup, work / 'frictions.aup', path)
+ rng = random.Random(140)
+ asked = [rng.randrange(0, 9) for _ in range(events)]
+ lines = run(pure_run, path, stdin=''.join(f"message go {struct.pack('<I', k).hex()}\n" for k in asked)).stdout.splitlines()
+ assert len(lines) == events
+ n, bs, a, b, i = 0, [], 0, 0, 0
+ for k, line in zip(asked, lines):
+ state = json.loads(line)
+ if k > 1: n = 10 + 1 + k
+ bs += [{'x': 0, 'y': 0}] * (2 if not bs else 1)
+ bs = [dict(x) for x in bs]
+ bs[0]['x'], bs[0]['y'] = k, k * 2
+ a, bs[1]['x'] = bs[0]['y'], a + 1
+ i, bs[i]['y'] = 1, 7
+ a, b = b, a
+ ns = [0, 0]
+ ns[0], ns[1] = 3, k
+ b = b + ns[0] * 100 + ns[1]
+ want = {'total': sum(range(k)) * (1 + 200 + 10000), 'n': n, 'bs': bs, 'a': a, 'b': b, 'i': i}
+ assert state['fault'] == 'none' and {key: state['states'][key] for key in want} == want, (k, state['states'])
+ return events
+
+
def bytes_compared(aup, pure_run, work, events=400):
"""Bytes compared (Pure's TEXT_COMPARE with PURE_COMPARE_BYTES, after 140's DNS and HTTP): random bytes, 0x00 and
0xff among them, in the six relations, against literals on either side and a string with a format (a string where
@@ -2927,6 +3009,7 @@ def main():
report['bytes_compared'] = bytes_compared(aup, pure_run, work)
report['inner_lists'] = inner_lists(aup, pure_run, work)
report['rows_at'] = rows_at(aup, pure_run, work)
+ report['frictions'] = frictions(aup, pure_run, work)
report['bytes_state'] = bytes_state(aup, pure_run, work)
report['stored_nested'] = stored_nested(aup, pure_run, work, range(6))
report['documents'] = documents_model(aup, pure_run, work, range(4))