From 557a69ef5102292eceb25f823ebcfc989810ab0c Mon Sep 17 00:00:00 2001 From: eunhhu Date: Tue, 13 Oct 2026 05:08:06 +0100 Subject: Bits, bytes and lists of numbers (Pure 027's stage 1): hex literals (i64s), & | ^ ~ << >> on i64s by Python's precedence (a shift's count a number or an i64), b[i] the byte at a position of bytes (a field's, an argument's, a parameter's, bytes(t)), list of i64 (number, bool, text) as records of one field whose items read as their values, a let's list begun with [elements], xs[i] read and written by position (not_found past the end), bytes(xs), and a, b = x, y and let a, b = x, y given at once; examples/sha256.aup; 12 diagnostics, one accepted form, 192 random bit expressions against Python's integers and an app of bytes and lists --- compiler/compile.c | 329 ++++++++++++++++++++++++++++++++++++--- docs/design/002-dsl-direction.md | 47 ++++++ examples/sha256.aup | 56 +++++++ tests/check.py | 135 ++++++++++++++++ 4 files changed, 547 insertions(+), 20 deletions(-) create mode 100644 examples/sha256.aup diff --git a/compiler/compile.c b/compiler/compile.c index 69bfdb5..1548c31 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -1,4 +1,5 @@ #include "aup.h" +#include #include #include #include @@ -20,7 +21,9 @@ enum { CONST, LOAD, DT, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR ITEM_GET, ITEM_SET, INDEX_LOAD, INDEX_STORE, INDEX_MOVE, INDEX_FIRST, INDEX_LAST, INDEX_NUM, ROW, PICK, SLIDE, FOLD, TEXT_COMPARE, LIST_FIND, LIST_HAS, NOW, FLOOR, MOD, SEND, OUTER_ROW, EACH, LOCAL_LOAD, LOCAL_STORE, I64_ADD, I64_SUB, I64_MUL, I64_DIV, I64_MOD, I64_NEG, I64_COMPARE, I64_MIN, I64_MAX, - I64_FROM, NUM_FROM, LOOP, BREAK }; + I64_FROM, NUM_FROM, LOOP, BREAK, I64_BITS, BYTE_AT, BYTES_OF, INDEX_AT }; +/* Bit operations (Pure's I64_BITS by its argument), apart from the opcodes: ~x is x ^ -1. */ +enum { BIT_AND = 0x100, BIT_OR, BIT_XOR, BIT_LEFT, BIT_RIGHT, BIT_NOT }; enum { LOCALS = 64 }; /* an event's locals (Pure's) */ enum { NESTING = 8, LOOP_INTEGER = 256 }; /* loops (for) nest 8 deep; an i64's count (Pure 018) */ enum { FOLD_COUNT, FOLD_SUM, FOLD_MAX, FOLD_MIN, FOLD_FIRST, FOLD_DISTINCT, FOLD_INTEGER = 0x40 }; @@ -44,7 +47,7 @@ typedef struct { int line, column; double number; int64_t integer; /* a literal of digits alone (whole): its exact value, when not past an i64's range (wide) */ - bool whole, wide; + bool whole, wide, hex; /* hex: written 0x..., an i64 wherever it stands */ char text[NAME + 1]; /* name, or punctuation of one or two characters */ size_t start, end; /* string contents in the source */ } Token; @@ -65,13 +68,15 @@ typedef enum { E_IF, /* if args[0] { args[1] } else { args[2] } */ E_FOLD, /* op(args[1] for name in member[args[2] .. args[3]] where args[0]): count, first (no value), sum, max, min, distinct; args[2] and args[3], when given, slice a list keyed by a number */ + E_INDEX, /* args[0][args[1]]: the byte at a position */ + E_LIST, /* [ elements linked from args[0] by next ]: a let's list as it begins */ } ExprKind; typedef struct Expr { ExprKind kind; int line, column, op; double number; int64_t integer; /* E_NUMBER: as its token's */ - bool whole, wide; + bool whole, wide, hex; char name[NAME], member[NAME]; struct Expr *args[PARAMS], *next; unsigned count; @@ -80,7 +85,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 } StmtKind; + S_LET, S_COUNT, S_BREAK, S_RETURN, S_ASSIGNS, S_LETS } StmtKind; typedef struct Stmt { StmtKind kind; int line, column; @@ -93,6 +98,10 @@ typedef struct Stmt { bool listed; int library, key_line, key_column; char key[NAME]; + /* S_ASSIGNS and S_LETS: a, b = x, y gives every name its value at once (the values first, in order): the names, and + * the values linked from value by next. */ + char (*names)[NAME]; + unsigned name_count; struct Stmt *then, *otherwise, *next; } Stmt; @@ -126,6 +135,7 @@ typedef struct { Expr *field_string[FIELDS_PER_RECORD]; /* a text default, or NULL for "" */ uint32_t field_constant[FIELDS_PER_RECORD]; unsigned fields; + bool scalar; /* list of i64 (number, bool, text): one field, value, its item read as the value itself */ } Record; /* A fn: one expression over its parameters, inlined where it is called. */ @@ -307,6 +317,25 @@ static Token scan(Compiler *c) { advance(c); return token; } + if (ch == '0' && (peek_char(c, 1) == 'x' || peek_char(c, 1) == 'X') && isxdigit((unsigned char)peek_char(c, 2))) { + /* 0x then hex digits: an i64 (past its range, wide) */ + advance(c); + advance(c); + uint64_t exact = 0; + token.kind = T_NUMBER; + token.whole = token.hex = true; + while (isxdigit((unsigned char)peek_char(c, 0))) { + char h = peek_char(c, 0); + unsigned v = (unsigned)(digit(h) ? h - '0' : (h | 0x20) - 'a' + 10); + token.wide = token.wide || exact > (UINT64_C(9223372036854775807) - v) / 16; + exact = exact * 16 + v; + advance(c); + } + if (letter(peek_char(c, 0))) fail_at(c, token.line, token.column, "a hex number is 0x and the digits 0-9 and a-f"); + token.integer = token.wide ? 0 : (int64_t)exact; + token.number = (double)exact; + return token; + } if (digit(ch) || (ch == '.' && digit(peek_char(c, 1)))) { /* \d+(\.\d*)?|\.\d+ with an optional exponent; a '.' that starts '..' ends the number. */ char text[400]; @@ -352,7 +381,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); @@ -361,7 +390,7 @@ static Token scan(Compiler *c) { token.kind = T_PUNCT; return token; } - if (strchr("=+-*/%!<>(){}[],.;:", ch)) { + if (strchr("=+-*/%!<>(){}[],.;:&|^~", ch)) { token.text[0] = ch; advance(c); token.kind = T_PUNCT; @@ -456,8 +485,10 @@ static bool utf8_valid(const unsigned char *bytes, size_t size) { static const struct { const char *text; int precedence, op; } binary[] = { {"||", 1, OR}, {"&&", 2, AND}, {"==", 3, EQ}, {"!=", 3, NE}, {"<", 4, LT}, {"<=", 4, LE}, - {">", 4, GT}, {">=", 4, GE}, {"+", 5, ADD}, {"-", 5, SUB}, {"*", 6, MUL}, {"/", 6, DIV}, {"%", 6, MOD}, + {">", 4, GT}, {">=", 4, GE}, {"|", 5, BIT_OR}, {"^", 6, BIT_XOR}, {"&", 7, BIT_AND}, {"<<", 8, BIT_LEFT}, + {">>", 8, BIT_RIGHT}, {"+", 9, ADD}, {"-", 9, SUB}, {"*", 10, MUL}, {"/", 10, DIV}, {"%", 10, MOD}, }; +enum { UNARY = 11 }; /* a unary operator's operand binds tighter than any binary one */ static Expr *node(Compiler *c, ExprKind kind, int line, int column) { Expr *e = allocate(c, sizeof(*e)); @@ -529,7 +560,18 @@ static Expr *postfix(Compiler *c, Expr *e) { } e = item; } - while (is(c, ".")) { + while (is(c, ".") || is(c, "[")) { + if (is(c, "[")) { /* packet.data[i]: the byte at a position */ + Token open = take(c); + Expr *at = node(c, E_INDEX, open.line, open.column); + at->args[0] = e; + at->args[1] = expression(c, 0, true); + at->count = 2; + newlines(c); + expect(c, "]", "']'"); + e = at; + continue; + } take(c); Token member = expect_name(c, "a field or member name"); if (is(c, "(")) { @@ -742,12 +784,13 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { left->integer = token.integer; left->whole = token.whole; left->wide = token.wide; + left->hex = token.hex; } else if (token.kind == T_STRING) { left = string(c, &token); - } else if (token.kind == T_PUNCT && (!strcmp(token.text, "-") || !strcmp(token.text, "!"))) { + } else if (token.kind == T_PUNCT && (!strcmp(token.text, "-") || !strcmp(token.text, "!") || !strcmp(token.text, "~"))) { left = node(c, E_UNARY, token.line, token.column); - left->op = token.text[0] == '-' ? NEG : NOT; - left->args[0] = expression(c, 7, multiline); + left->op = token.text[0] == '-' ? NEG : token.text[0] == '!' ? NOT : BIT_NOT; + left->args[0] = expression(c, UNARY, multiline); left->count = 1; if (left->op == NEG && left->args[0]->kind == E_NUMBER) { /* -5: one literal, a number or an i64 */ Expr *literal = left->args[0]; @@ -760,6 +803,16 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { left = expression(c, 0, true); newlines(c); expect(c, ")", "')'"); + } else if (token.kind == T_PUNCT && !strcmp(token.text, "[")) { /* refused where it stands (only a let's list begins so) */ + left = node(c, E_LIST, token.line, token.column); + newlines(c); + while (!is(c, "]")) { + expression(c, 0, true); + newlines(c); + if (!is(c, ",")) break; + take(c); + } + expect(c, "]", "']'"); } else if (token.kind == T_NAME && (!strcmp(token.text, "true") || !strcmp(token.text, "false"))) { left = node(c, E_BOOL, token.line, token.column); left->number = token.text[0] == 't'; @@ -828,6 +881,7 @@ static Expr *expression(Compiler *c, int minimum, bool multiline) { if (left->count != calls[call].count) fail_at(c, token.line, token.column, "%s expects %u argument%s", token.text, calls[call].count, calls[call].count == 1 ? "" : "s"); + if (left->kind == E_CONVERT && is(c, "[")) left = postfix(c, left); /* bytes(t)[i] */ } else if (!strcmp(token.text, "dt") || !strcmp(token.text, "now")) { left = node(c, token.text[0] == 'd' ? E_DT : E_NOW, token.line, token.column); } else if (!strcmp(token.text, "if")) { @@ -946,8 +1000,12 @@ static Expr *send_fields(Compiler *c) { /* A value inserted into a text or a list: an expression or a record * (NAME { ... }, or LIBRARY.NAME { ... }: looked at two tokens ahead, the * scanner then put back as it was). */ -static Expr *inserted(Compiler *c) { - newlines(c); +static Expr *inserted_value(Compiler *c, bool multiline); +static Expr *inserted(Compiler *c) { return inserted_value(c, true); } + +/* A record value, or an expression that goes into a list (one line unless multiline). */ +static Expr *inserted_value(Compiler *c, bool multiline) { + if (multiline) newlines(c); if (c->token.kind == T_NAME && lookahead(c)->kind == T_PUNCT && !strcmp(lookahead(c)->text, "{")) return record_value(c, NULL); if (c->token.kind == T_NAME && lookahead(c)->kind == T_PUNCT && !strcmp(lookahead(c)->text, ".")) { @@ -962,7 +1020,7 @@ static Expr *inserted(Compiler *c) { c->at = at, c->line = line, c->column = column; c->token = token, c->ahead = ahead, c->has_ahead = has_ahead; } - return expression(c, 0, true); + return expression(c, 0, multiline); } static Stmt *block(Compiler *c, unsigned depth); @@ -975,6 +1033,34 @@ static void range(Compiler *c, Stmt *s) { newlines(c); } +/* , 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) { + s->names = allocate(c, STACK * sizeof(*s->names)); + memcpy(s->names[s->name_count++], first, NAME); + while (is(c, ",")) { + take(c); + Token name = expect_name(c, "a name"); + if (reserved_name(name.text)) fail_at(c, name.line, name.column, "'%s' is reserved", name.text); + if (s->name_count == STACK) fail_at(c, name.line, name.column, "at most %d names take values at once", STACK); + for (unsigned i = 0; i < s->name_count; ++i) + 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); + } + expect(c, "=", "'='"); + Expr **tail = &s->value; + unsigned values = 0; + for (;;) { + *tail = expression(c, 0, 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) + fail_at(c, s->line, s->column, "%u names take %u values, not %u", s->name_count, s->name_count, values); +} + static Stmt *statement(Compiler *c, unsigned depth) { Token token = expect_name(c, "a statement or '}'"); Stmt *s = allocate(c, sizeof(*s)); @@ -1005,6 +1091,11 @@ 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 name: list of R [by field]: the event's list, empty as it begins */ take(c); Token type = expect_name(c, "list"); @@ -1020,6 +1111,23 @@ static Stmt *statement(Compiler *c, unsigned depth) { s->key_column = key.column; } s->listed = true; + 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"); + Expr *list = node(c, E_LIST, open.line, open.column), **tail = &list->args[0]; + newlines(c); + while (!is(c, "]")) { + *tail = inserted(c); + tail = &(*tail)->next; + ++list->count; + newlines(c); + if (!is(c, ",")) break; + take(c); + newlines(c); + } + expect(c, "]", "']'"); + s->value = list; + } return s; } expect(c, "=", "'='"); @@ -1107,9 +1215,9 @@ static Stmt *statement(Compiler *c, unsigned depth) { if (is(c, "(")) fail_at(c, s->first->line, s->first->column, "a collection is indexed by the message key, as in name[key]"); memcpy(s->member, field.text, NAME); - } else if (is(c, "=")) { /* tasks[at] = Task { ... }: every field of the item (no member) */ + } else if (is(c, "=")) { /* tasks[at] = Task { ... }: every field of the item (no member); xs[i] = 5 */ take(c); - s->value = inserted(c); + s->value = inserted_value(c, false); return s; } else fail_at(c, c->token.line, c->token.column, @@ -1173,6 +1281,11 @@ 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; + } s->kind = S_ASSIGN; expect(c, "=", "'='"); s->value = expression(c, 0, false); @@ -2107,6 +2220,11 @@ static void item_of(Compiler *c, const Expr *e, bool handler, int height, int li emit(c, e->line, e->column, LIST_FIND, (uint32_t)list, 0); return; } + if (kind.type == NUM || kind.type == I64) { /* xs[2]: the item at a position */ + if (kind.type == I64) emit(c, e->line, e->column, NUM_FROM, 0, 0); + emit(c, e->line, e->column, INDEX_AT, (uint32_t)list, 0); + return; + } expect_kind(c, e, kind, INDEX); if (kind.owner != list) fail_at(c, e->line, e->column, "an index of '%s' cannot point into '%s'", c->state_names[kind.owner], @@ -2118,6 +2236,20 @@ static const char *keyed_by(const Compiler *c, int list) { return c->list_key[list] ? c->state_key[list] : NULL; } +/* An item of list, its index on the stack: the value itself in a list of + * i64 (number, bool, text), else the index. */ +static Kind item_value(Compiler *c, const Expr *e, int list) { + const Record *r = record_of(c, list); + if (!r->scalar) return (Kind){INDEX, list}; + emit(c, e->line, e->column, ITEM_GET, (uint32_t)list, 0); + return (Kind){field_kind(r, 0), -1}; +} + +static int collection_index(const Compiler *c, const char *name) { + for (unsigned i = 0; i < c->collections; ++i) if (!strcmp(c->collection_names[i], name)) return (int)i; + return -1; +} + /* 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. */ @@ -2257,7 +2389,14 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { c->want = 0; if (height + 1 > STACK) fail_at(c, e->line, e->column, "expression needs more than 64 stack values"); switch (e->kind) { - case E_NUMBER: emit(c, e->line, e->column, CONST, 0, e->number); return (Kind){NUM, -1}; + case E_NUMBER: + if (e->hex) { /* 0x...: an i64 */ + emit(c, e->line, e->column, CONST, 2, 0); + made_i64(c, e, c->code_count - 1); + return (Kind){I64, -1}; + } + emit(c, e->line, e->column, CONST, 0, e->number); + return (Kind){NUM, -1}; case E_BOOL: emit(c, e->line, e->column, CONST, 1, e->number); return (Kind){BOOL, -1}; case E_DT: case E_NOW: { const char *name = e->kind == E_DT ? "dt" : "now"; @@ -2284,7 +2423,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { case E_NAME: { if (c->folding && c->scope == c->fold_scope && !strcmp(e->name, c->fold_name)) { emit(c, e->line, e->column, ROW, (uint32_t)c->fold_list, 0); /* the item a fold's block runs for */ - return (Kind){INDEX, c->fold_list}; + return item_value(c, e, c->fold_list); } int loop = loop_named(c, e->name); if (loop >= 0 && c->loops[loop].list >= 0 && c->scope == c->loops[loop].scope) { @@ -2293,7 +2432,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { int list = c->loops[loop].list; if (loop == innermost_walk(c)) emit(c, e->line, e->column, c->folding ? OUTER_ROW : ROW, (uint32_t)list, 0); else emit(c, e->line, e->column, LOCAL_LOAD, (uint32_t)c->loops[loop].item | 1u << 8 | (uint32_t)list << 16, 0); - return (Kind){INDEX, list}; + return item_value(c, e, list); } int param = parameter(c, e->name); if (param >= 0 && c->folding && c->scope == c->fold_scope) @@ -2386,7 +2525,7 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { if (!strcmp(m, "count")) { emit(c, e->line, e->column, LIST_COUNT, (uint32_t)state, 0); return (Kind){NUM, -1}; } if (!strcmp(m, "first") || !strcmp(m, "last")) { emit(c, e->line, e->column, m[0] == 'f' ? INDEX_FIRST : INDEX_LAST, (uint32_t)state, 0); - return (Kind){INDEX, state}; + return item_value(c, e, state); } fail_at(c, e->line, e->column, "a list has count, first and last, not '%s'", m); } @@ -2460,6 +2599,16 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { emit(c, e->line, e->column, DOC_SIZE, collection(c, e->name, e->line, e->column), 0); return (Kind){NUM, -1}; case E_CONVERT: { + const Expr *a = e->args[0]; + int list = e->op == BYTES_FROM && a->kind == E_NAME && !c->module && parameter(c, a->name) < 0 ? state_index(c, a->name) : -1; + if (list >= 0 && c->state_types[list] == STATE_LIST) { /* bytes(xs): a list of i64s, each a byte */ + list = list_state(c, a->name, a->line, a->column, NULL); + const Record *r = record_of(c, list); + if (!r->scalar || r->field_kinds[0] != I64) + fail_at(c, a->line, a->column, "bytes(...) makes bytes of a list of i64, not of %s", record_shown(c, c->module, r)); + emit(c, e->line, e->column, BYTES_OF, (uint32_t)list, 0); + return (Kind){BYTES, -1}; + } Kind inner = value(c, e->args[0], handler, height); Type from = e->op == TEXT_FROM ? BYTES : TEXT; if (inner.type != from) @@ -2469,9 +2618,24 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { return (Kind){e->op == TEXT_FROM ? TEXT : BYTES, -1}; } case E_ITEM: { + int named = c->module || parameter(c, e->name) >= 0 ? -1 : state_index(c, e->name); + if (!*e->member && (named < 0 || c->state_types[named] != STATE_LIST) && collection_index(c, e->name) < 0) { + Expr name = *e, at = *e; /* data[i]: a parameter's, an argument's or a state's byte */ + name.kind = E_NAME; + at.kind = E_INDEX; + at.args[0] = &name; + at.args[1] = e->args[0]; + at.count = 2; + return value(c, &at, handler, height); + } app_name(c, e, e->name); int list = list_state(c, e->name, e->line, e->column, e->args[0]); const Record *r = record_of(c, list); + if (!*e->member && r->scalar) { /* xs[i]: the value at a position (or a key) */ + item_of(c, e->args[0], handler, height, list); + emit(c, e->line, e->column, ITEM_GET, (uint32_t)list, 0); + return (Kind){field_kind(r, 0), -1}; + } if (!*e->member) { /* people["bob"]: the index of the item with that key. */ if (!keyed_by(c, list)) @@ -2487,6 +2651,19 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { emit(c, e->line, e->column, ITEM_GET, (uint32_t)list | field << 16, 0); return (Kind){field_kind(r, field), -1}; } + case E_INDEX: { + Kind base = value(c, e->args[0], handler, height); + if (base.type == TEXT) + fail_at(c, e->line, e->column, "a text is read by position through its bytes, as in bytes(t)[i]"); + if (base.type != BYTES) fail_at(c, e->line, e->column, "only bytes are read by position, not %s", type_name(base.type)); + Kind at = value(c, e->args[1], handler, height + 1); + if (at.type != NUM && at.type != I64) + fail_at(c, e->args[1]->line, e->args[1]->column, "a position is a number or an i64, not %s", type_name(at.type)); + emit(c, e->line, e->column, BYTE_AT, at.type == I64, 0); + return (Kind){I64, -1}; + } + case E_LIST: + fail_at(c, e->line, e->column, "a list's elements begin a let's list, as in let xs: list of i64 = [1, 2]"); case E_IF: { /* Arms of an else-if chain share one end; a value crosses it, so the * branches ahead that carry one are counted against Pure's limit. An @@ -2605,6 +2782,25 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { } /* fall through */ default: { + if (e->op >= BIT_AND && e->op <= BIT_NOT) { /* &, |, ^, <<, >> and ~: on i64s, as integers without end (Pure 027) */ + for (unsigned i = 0; i < e->count; ++i) { + bool count = i == 1 && (e->op == BIT_LEFT || e->op == BIT_RIGHT); /* a number or an i64, as a position is */ + Kind kind = count && !loose(e->args[i]) ? value(c, e->args[i], handler, height + 1) + : wanted(c, e->args[i], handler, height + (int)i, I64); + if (count && kind.type == NUM) { + emit(c, e->args[i]->line, e->args[i]->column, I64_FROM, 0, 0); + continue; + } + if (kind.type != I64) + fail_at(c, e->args[i]->line, e->args[i]->column, kind.type == NUM ? + "bit operations take i64s, not numbers: convert, as in i64(x)" : "bit operations take i64s, not %s", + type_name(kind.type)); + } + if (e->op == BIT_NOT) emit_integer(c, e->line, e->column, -1); + static const uint8_t how[] = {0, 1, 2, 3, 4, 2}; /* Pure's PureBits: and, or, xor, left, right; ~ is xor -1 */ + emit(c, e->line, e->column, I64_BITS, how[e->op - BIT_AND], 0); + return (Kind){I64, -1}; + } /* !, && and || take bools (both sides are computed); *, /, %, min and * max numbers or i64s; clamp and floor numbers. */ bool logic = e->op == AND || e->op == OR || e->op == NOT; @@ -2726,6 +2922,10 @@ static Expr *spread_field(Compiler *c, const Expr *spread, const char *name) { * given): those written, and the rest its spread's source has by name. */ static void record_values(Compiler *c, const Expr *e, int list, const Expr **values) { const Record *r = record_of(c, list); + if (r->scalar && e->kind != E_RECORD) { /* xs.append(5): the value itself */ + values[0] = e; + return; + } char shown[2 * NAME + 1]; snprintf(shown, sizeof(shown), "%s", record_shown(c, c->module, r)); if (e->kind != E_RECORD) @@ -2821,6 +3021,12 @@ static void statements(Compiler *c, const Stmt *s) { memset(&c->locals[local], 0, sizeof(c->locals[local])); memcpy(c->locals[local].name, s->name, NAME); c->locals[local].list = list; + for (const Expr *element = s->value ? s->value->args[0] : NULL; element; element = element->next) { + if (keyed_by(c, list)) + fail_at(c, element->line, element->column, "a keyed list adds its elements by key; add them one by one"); + record_fields(c, element, list, 0); + emit(c, element->line, element->column, LIST_APPEND, (uint32_t)list, 0); + } break; } if (c->local_slots == LOCALS) fail_at(c, s->line, s->column, "at most %d lets are allowed in a handler", LOCALS); @@ -2837,6 +3043,63 @@ static void statements(Compiler *c, const Stmt *s) { emit(c, s->line, s->column, LOCAL_STORE, local_arg(c, (int)local), 0); break; } + 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 (kinds[i].type != NUM && kinds[i].type != BOOL && kinds[i].type != I64 && kinds[i].type != INDEX) + fail_at(c, v->line, v->column, "a let holds a number, a bool, an i64 or an index, not %s", type_name(kinds[i].type)); + } + unsigned first = c->local_count; + for (unsigned i = 0; i < s->name_count; ++i) { + const char *name = s->names[i]; + if (let_named(c, name) >= 0) fail_at(c, s->line, s->column, "'%s' is already a let here", name); + 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); + c->locals[local].slot = c->local_slots++; + c->locals[local].kind = kinds[i]; + c->locals[local].list = -1; + } + for (unsigned i = s->name_count; i-- > 0;) emit(c, s->line, s->column, LOCAL_STORE, local_arg(c, (int)(first + i)), 0); + break; + } + case S_ASSIGNS: { + const Expr *v = s->value; + int locals[STACK], states[STACK]; + for (unsigned i = 0; i < s->name_count; ++i, v = v->next) { + const char *name = s->names[i]; + locals[i] = local_named(c, name); + states[i] = locals[i] < 0 ? state_index(c, name) : -1; + if (locals[i] >= 0 && c->locals[locals[i]].counter) + fail_at(c, s->line, s->column, "'%s' is what this for counts; it is not assigned", name); + Kind want; + if (locals[i] >= 0) want = c->locals[locals[i]].kind; + else if (states[i] >= 0 && (c->state_types[states[i]] == STATE_NUM || c->state_types[states[i]] == STATE_BOOL || + c->state_types[states[i]] == STATE_I64)) { + want = (Kind){c->state_types[states[i]] == STATE_I64 ? I64 : c->state_types[states[i]] == STATE_BOOL ? BOOL : NUM, -1}; + view_changes(c, s, -1, name); + } else if (states[i] >= 0) fail_at(c, s->line, s->column, "'%s' takes its value apart, as in %s = ...", name, name); + else { + const char *kind = declared_kind(c, name); + if (kind) fail_at(c, s->line, s->column, "cannot assign to %s '%s'", kind, name); + fail_at(c, s->line, s->column, "unknown state '%s'", name); + } + Kind got = wanted(c, v, c->handling, (int)i, want.type); + if (got.type != want.type || (got.type == INDEX && got.owner != want.owner)) + fail_at(c, v->line, v->column, "'%s' holds %s", name, want.type == INDEX ? "an index of its list" : type_name(want.type)); + } + for (unsigned i = s->name_count; i-- > 0;) + if (locals[i] >= 0) emit(c, s->line, s->column, LOCAL_STORE, local_arg(c, locals[i]), 0); + else emit(c, s->line, s->column, STORE, (uint32_t)states[i], 0); + break; + } case S_ASSIGN: { int local = local_named(c, s->name); if (local >= 0 && c->locals[local].counter) @@ -3268,6 +3531,30 @@ static void pull_records(Compiler *c) { } } +/* The record a list of i64 (number, bool, text) holds: one field, value, made once the app holds such a list; -1 for + * another name. */ +static int scalar_record(Compiler *c, const char *name, int line, int column) { + static const struct { const char *name; Type kind; } scalars[] = {{"i64", I64}, {"number", NUM}, {"bool", BOOL}, {"text", TEXT}}; + for (unsigned k = 0; k < sizeof(scalars) / sizeof(*scalars); ++k) { + if (strcmp(name, scalars[k].name)) continue; + int found = module_record(c, 0, name); + if (found >= 0) return found; + if (c->record_count == RECORDS) fail_at(c, line, column, "at most 16 records are allowed, a list of %s among them", name); + if (c->record_fields == RECORD_FIELDS) fail_at(c, line, column, "at most 128 record fields are allowed"); + Record *r = &c->records[c->record_count]; + memset(r, 0, sizeof(*r)); + memcpy(r->name, name, strlen(name) + 1); + memcpy(r->field_names[0], "value", 6); + r->field_kinds[0] = scalars[k].kind; + r->fields = 1; + r->scalar = true; + r->line = line, r->column = column; + ++c->record_fields; + return (int)c->record_count++; + } + return -1; +} + static void generate(Compiler *c, unsigned char **module, size_t *size) { uint32_t events[HANDLERS], state_ref[STATES] = {0}; int handler_input[HANDLERS]; @@ -3290,6 +3577,8 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) { "transient too", c->state_names[i], c->state_target[i]); } else if (c->state_types[i] == STATE_LIST) { int record = module_record(c, c->state_library[i], c->state_target[i]); + if (record < 0 && !c->state_library[i]) + record = scalar_record(c, c->state_target[i], c->state_line[i], c->state_column[i]); for (unsigned l = 1; record < 0 && l < c->module_count; ++l) /* a library's, written without its name */ if (c->modules[0].uses[l] && library_record(c, (int)l, c->state_target[i]) >= 0) fail_at(c, c->state_line[i], c->state_column[i], "'%s' is library %s's record: write %s.%s", diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md index efdca29..f05123f 100644 --- a/docs/design/002-dsl-direction.md +++ b/docs/design/002-dsl-direction.md @@ -596,3 +596,50 @@ on remote { 검증: 진단 7건(펼치기가 끝이 아님, 펼칠 수 없는 source, 핸들러 밖의 메시지, 그룹 안의 펼치기, 채워지지 않은 send 필드, 필드가 빠진 항목 전체, 레코드가 아닌 값). 펼치기와 항목 전체를 쓴 모듈과 필드를 적은 모듈이 바이트로 같다. `pure-run`에서 키가 다른 항목 전체 대입은 `limit`으로 실패하고 값을 바꾸지 않으며, 키가 같은 것은 바꾼다. + +### 바이트, 비트, 수의 리스트 (Pure 027의 1단계) + +Pure [027](/srv/git/pure.git)(`docs/design/027-computation-core.md`)의 1단계이고, 세상의 명세가 aup에서 처음 부딪치던 것 가운데 셋을 +연다(Pure `docs/research/140-spec-suite.md`). `examples/sha256.aup`가 그것으로 쓴 SHA-256이다. + +```text +input hash = message { data: bytes } +fn rotr(x, n) = (x >> n | x << (32 - n)) & 0xffffffff +on hash { + let k: list of i64 = [0x428a2f98, 0x71374491, ...] # 리스트의 처음 값 + let a, b = hash.data[0], hash.data[1] # 메시지 바이트의 그 자리(i64 0–255) + a, b = b, a # 값을 먼저 모두 계산하고 한 번에 준다 + let hex: list of i64 + hex.append(48 + (a >> 4)) + digest = text(bytes(hex)) # i64 리스트의 바이트 +} +``` + +- **16진 리터럴** `0x…`는 어디서나 i64다. +- **비트 연산** `&`, `|`, `^`, `~`, `<<`, `>>`는 i64를 끝없는 2의 보수 위의 정수로 계산한다(파이썬의 정수와 같은 뜻). + - `x << n`은 x·2^n이고, i64를 넘으면 이벤트가 `overflow`로 실패한다. + - `x >> n`은 내림한 x / 2^n이다. + - 시프트 수는 수나 i64이고, 0–63 밖이면 `limit`이다. + - 우선순위는 파이썬과 같다: 비교 < `|` < `^` < `&` < 시프트 < `+ -` < `* / %` < 단항. 그래서 `e & f ^ ~e & g`는 `(e & f) ^ ((~e) & g)`다. + - 수에 쓰면 진단이 변환을 권한다(`bit operations take i64s, not numbers: convert, as in i64(x)`). +- **자리의 바이트** `b[i]`는 바이트 값(메시지와 인자의 필드, fn의 매개변수, `bytes(t)`)의 그 자리 바이트를 i64로 준다. + - 자리는 수나 i64다. 정수가 아니거나 값 밖이면 `limit`이다. + - text는 바이트를 거쳐 읽는다(`bytes(t)[i]`). 글자로 읽기는 2단계다. +- **수의 리스트** `list of i64`(`number`, `bool`, `text`도)는 필드가 `value` 하나인 레코드의 리스트로 Pure에 간다. + - `xs.append(5)`처럼 값 자체를 넣는다. `for x in xs`, 집계의 항목, `xs.first`, `xs.last`, `xs[i]`는 값이다. + - `let xs: list of i64 = [1, 2, 3]`은 이벤트의 리스트를 그 값들로 시작한다. 리스트 리터럴은 거기에만 쓴다. +- **자리로 읽고 쓰기.** 키 없는 리스트의 `xs[i]`(레코드의 리스트면 `xs[i].field`)는 i번째 항목이다. `xs[i] = 값`은 그것을 바꾼다. + 그 자리에 항목이 없으면 `not_found`다(Pure의 INDEX_AT. 끝에서 멈추는 `xs.first + i`와 다르다). +- **`bytes(xs)`** 는 i64 리스트의 항목을 한 바이트씩 잇는다. 0–255 밖이면 `limit`이다. +- **한 번에 주기.** + - `a, b = x, y`는 오른쪽을 모두 계산한 뒤 왼쪽에 한 번에 준다. 대상은 let과 수, bool, i64 상태다. + - `let a, b = x, y`는 let 여럿을 만든다. + - 줄은 쉼표 뒤에서 넘길 수 있다. + +검증: +- 진단 12건: 수의 비트 연산, text나 수를 자리로 읽기, 잘못된 16진, i64 밖의 16진, 이름과 값의 수, 같은 이름 두 번, 한 번에 줄 수 + 없는 상태, let에 text, 레코드 리스트의 `bytes`, 식 안의 리스트 리터럴. +- 받아들이는 꼴 하나: 위의 모든 꼴과 보기의 인자, fn 매개변수의 바이트. +- 무작위 비트 식 192개(파이썬의 정수와 i64의 넘침, 시프트 한도로) 사건 768개, 그중 실패 196개가 파이썬과 같다. +- 바이트와 리스트의 앱이 파이썬과 같다(`limit`, `not_found` 포함). +- `examples/sha256.aup`는 Pure의 검사가 FIPS 180-2의 예와 길이 0–129 등의 메시지로 `hashlib`와 대조한다(세 실행 길). diff --git a/examples/sha256.aup b/examples/sha256.aup new file mode 100644 index 0000000..2558e55 --- /dev/null +++ b/examples/sha256.aup @@ -0,0 +1,56 @@ +# SHA-256 (FIPS 180-4): the digest of a message's bytes, in hex. +state digest: text +input hash = message { data: bytes } + +fn rotr(x, n) = (x >> n | x << (32 - n)) & 0xffffffff +# The byte at j of the message padded to whole blocks: 0x80 after it, its length in bits in the last 8. +fn padded(data, size, total, j) = (if j < size { data[j] } else if j == size { 0x80 } + else if j >= total - 8 { i64(size) * 8 >> 8 * (total - 1 - j) & 0xff } else { 0 }) + +on hash { + let k: list of i64 = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ] + let h0, h1, h2, h3, h4, h5, h6, h7 = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19 + let w: list of i64 + for t in 0 .. 64 { w.append(0) } + let size = hash.data.bytes + let total = (floor((size + 8) / 64) + 1) * 64 + for block in 0 .. total / 64 { + for t in 0 .. 16 { + let j = block * 64 + t * 4 + w[t] = (padded(hash.data, size, total, j) << 24 | padded(hash.data, size, total, j + 1) << 16 | + padded(hash.data, size, total, j + 2) << 8 | padded(hash.data, size, total, j + 3)) + } + for t in 16 .. 64 { + let x = w[t - 15] + let y = w[t - 2] + w[t] = (w[t - 16] + (rotr(x, 7) ^ rotr(x, 18) ^ x >> 3) + w[t - 7] + (rotr(y, 17) ^ rotr(y, 19) ^ y >> 10)) & 0xffffffff + } + let a, b, c, d, e, f, g, hh = h0, h1, h2, h3, h4, h5, h6, h7 + for t in 0 .. 64 { + let t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + k[t] + w[t]) & 0xffffffff + let t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c)) & 0xffffffff + a, b, c, d, e, f, g, hh = (t1 + t2) & 0xffffffff, a, b, c, (d + t1) & 0xffffffff, e, f, g + } + h0, h1, h2, h3 = (h0 + a) & 0xffffffff, (h1 + b) & 0xffffffff, (h2 + c) & 0xffffffff, (h3 + d) & 0xffffffff + h4, h5, h6, h7 = (h4 + e) & 0xffffffff, (h5 + f) & 0xffffffff, (h6 + g) & 0xffffffff, (h7 + hh) & 0xffffffff + } + let h: list of i64 = [h0, h1, h2, h3, h4, h5, h6, h7] + let hex: list of i64 + for i in 0 .. 8 { + for s in 0 .. 8 { + let nibble = h[i] >> 28 - 4 * s & 0xf + hex.append(if nibble < 10 { 48 + nibble } else { 87 + nibble }) + } + } + digest = text(bytes(hex)) +} diff --git a/tests/check.py b/tests/check.py index c2e34e9..9ddffb6 100644 --- a/tests/check.py +++ b/tests/check.py @@ -65,6 +65,21 @@ CARDS = ('record Card {\n title: text\n lane = 0\n note: text\n}\nstate cards SENDS = ('state n = 0\ninput buy = message {\n item: text\n count: u32\n}\noutput ordered = message {\n' ' item: text\n count: u32\n to: {\n city: text\n }\n}\n') DIAGNOSTICS = [ + # Bits and bytes (Pure 027's stage 1): bit operations take i64s; only bytes are read by position; a, b = x, y names + # as many values, each a let's or a number's, bool's or i64's state; bytes(xs) of a list of i64. + ('state n = 0\nview v = n & 1\n', '2:10', 'bit operations take i64s, not numbers'), + ('state y: i64\non frame {\n y = 1.5 << 2\n}\n', '3:7', 'bit operations take i64s, not numbers'), + ('state t: text\nview v = t[0]\n', '2:10', 'a text is read by position through its bytes, as in bytes(t)[i]'), + ('state n = 0\nview v = n[0]\n', '2:10', 'only bytes are read by position, not a number'), + ('state n = 0\nview v = 0x1G\n', '2:10', 'a hex number is 0x and the digits 0-9 and a-f'), + ('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 s: text\nstate a = 0\non frame {\n s, a = "x", 1\n}\n', '4:3', "'s' takes its value apart"), + ('state x = 0\non frame {\n let a, b = 1, "s"\n}\n', '3:17', 'a let holds a number, a bool, an i64 or an index, not text'), + ('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"), ('state x = 1\nstate x = 2\n', '2:7', "already declared as a state"), ('state a = b\n', '1:11', 'expected a numeric state initializer'), ('view v = dt\n', '1:10', 'dt is only available in handlers'), @@ -377,6 +392,16 @@ DIAGNOSTICS = [ ] ACCEPTED = [ + # Bits and bytes (Pure 027's stage 1): hex, bit operations by Python's precedence, a byte of a message's bytes, of an + # argument's and of a fn's parameter, lists of i64, number, bool and text with their elements, positions read and + # written, a, b = b, a and let a, b = ..., bytes(xs). + 'state r: i64\nstate n = 0\nstate t: text\ninput go = message { d: bytes }\n' + 'fn last(data) = data[data.bytes - 1]\n' + 'on go {\n let xs: list of i64 = [0x10, 2,\n 3]\n let a, b = xs[0], xs[2]\n a, b = b, a\n xs[1] = a ^ ~b & 0xff\n' + ' xs.append(last(go.d) >> 1 | go.d[0] << 2)\n r = sum(x for x in xs) + xs.first + xs.last\n' + ' let ns: list of number = [1.5, 2]\n let bs: list of bool = [true]\n let ts: list of text = ["a", "b"]\n' + ' for u in ts { t = "{t}{u}" }\n n = ns[1] + ns.count + bs.count + number(bytes(t)[0])\n t = text(bytes(xs))\n}\n' + 'view v(d: bytes) = d[0] & 1\n', # Literals beside numbers and i64s, nested deep: each chain is typed in one probe (a probe per level would take 2^30). 'state x = 0\nstate y: i64\nview v = ' + '1 + (' * 30 + 'x' + ')' * 30 + '\nview w = ' + '1 + (' * 30 + 'y' + ')' * 30 + '\n', # i64s: states, fields, keys, slices, messages, outputs, arguments, lets, arithmetic, conditionals, folds, text. @@ -1124,6 +1149,115 @@ def transients(aup, pure_run, work): return {'store_files_compared': len(compared.common_files), 'type_words_differ': len(differ)} +def bits_and_bytes(aup, pure_run, work, apps=8, per_app=24): + """Pure 027's stage 1. Random bit expressions over two i64s a message + gives (&, |, ^, ~, <<, >>, + and - by Python's precedence, hex and decimal + literals, shift counts of numbers and i64s) against Python's integers in + an i64: a left shift or a sum past the range fails as overflow, a shift + count outside 0-63 as limit. Then an app of bytes and lists: a message's + bytes by position (past the end: limit), lists of i64 written and read + by position (past the end: not_found), a, b = b, a, and bytes(xs) shown + as text, each against Python.""" + import struct + rng = random.Random(27) + low, high = -(1 << 63), (1 << 63) - 1 + + class Fault(Exception): + pass + + def fits(v): + if not low <= v <= high: + raise Fault('overflow') + return v + + def expression(depth): + if depth == 0 or rng.random() < 0.25: + pick = rng.random() + if pick < 0.3: + return 'e.x', lambda x, y: x + if pick < 0.6: + return 'e.y', lambda x, y: y + v = rng.choice([rng.randrange(0, 70), rng.randrange(1 << 32), 0xff, 0xffffffff, -1, -(1 << 31)]) + text = f'0x{v:x}' if v >= 0 and rng.random() < 0.5 else (f'({v})' if v < 0 else str(v)) + return text, lambda x, y, v=v: v + if rng.random() < 0.15: + text, f = expression(depth - 1) + return f'~({text})', lambda x, y: ~f(x, y) + op = rng.choice(['&', '|', '^', '<<', '>>', '+', '-']) + a, fa = expression(depth - 1) + if op in ('<<', '>>'): + count = rng.choice([rng.randrange(0, 64), rng.randrange(0, 70), -1]) + b, fb = (str(count) if count >= 0 else f'({count})'), (lambda x, y, c=count: c) + if rng.random() < 0.3: + b, fb = '(e.y & 63)', (lambda x, y: y & 63) + else: + b, fb = expression(depth - 1) + + def run(x, y, op=op, fa=fa, fb=fb): + u, v = fa(x, y), fb(x, y) + if op in ('<<', '>>'): + if not 0 <= v <= 63: + raise Fault('limit') + return fits(u << v) if op == '<<' else u >> v + return fits({'&': u & v, '|': u | v, '^': u ^ v, '+': u + v, '-': u - v}[op]) + return f'({a} {op} {b})', run + + checked = faults = 0 + for app in range(apps): + cases = [expression(3) for _ in range(per_app)] + source = 'state r: i64\n' + ''.join(f'input e{i} = message {{\n x: i64\n y: i64\n}}\n' for i in range(per_app)) + source += ''.join(f'on e{i} {{\n r = {text.replace("e.", f"e{i}.")}\n}}\n' for i, (text, _) in enumerate(cases)) + path = work / f'bits{app}.aup' + path.write_text(source) + compile_module(aup, path, work / f'bits{app}.pmod') + given, wanted = '', [] + for i, (_, f) in enumerate(cases): + for _ in range(4): + x = rng.choice([rng.randrange(low, high), rng.randrange(1 << 32), rng.randrange(-100, 100)]) + y = rng.choice([rng.randrange(low, high), rng.randrange(64), rng.randrange(1 << 16)]) + given += f'message e{i} {struct.pack("= len(data): + wanted.append('limit') + elif at + 3 >= 4: + wanted.append('not_found') + else: + xs = [0x41, data[at], 0x43, 0x43] # a, b = b, a: a is 0x43 when it is appended + wanted.append((sum(xs) + xs[0] * 1000 + xs[-1] + xs[at + 3], bytes(xs).decode())) + lines = run(pure_run, work / 'bytes.pmod', stdin=given, check=False).stdout.splitlines() + for line, want in zip(lines, wanted): + state = json.loads(line) + if isinstance(want, str): + assert state['fault'] == want, (state, want) + else: + assert state['fault'] == 'none' and int(state['states']['r']) == want[0] and state['states']['t'] == want[1], (state, want) + return {'expressions': apps * per_app, 'events': checked, 'faults': faults, 'bytes_events': len(wanted)} + + 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 @@ -1929,6 +2063,7 @@ def main(): report['library_records'] = library_records(aup, pure_run, args.pure.resolve(), work) report['transients'] = transients(aup, pure_run, work) report['computing'] = computing(aup, pure_run, work) + report['bits_and_bytes'] = bits_and_bytes(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)) -- cgit v1.2.3