From ed9d62ae1ef8b60e72be5bbeefa432aa798ff299 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 19 Oct 2026 01:13:07 +0100 Subject: Pure 027's 5a: // a floored quotient (Pure's FLOORDIV for numbers, I64_DIV for i64s), at /'s precedence; / between i64s refused (an i64 divides by //, or converts: number(a) / number(b)); the three that divided i64s by / (an accepted form, the i64 app, the random programs' i64 expressions) now by //; a check of 200 random a // b and a % b against Python's, bit for bit (-0's sign too); a diagnostic (305) --- compiler/compile.c | 16 ++++++++++------ tests/check.py | 27 ++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/compiler/compile.c b/compiler/compile.c index aad75de..9b11d92 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -24,7 +24,7 @@ enum { CONST, LOAD, ADD, SUB, MUL, DIV, NEG, LT, LE, GT, GE, EQ, NE, AND, OR, NO 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, ANCHOR_AT, BYTES_SLICE, BYTE, BYTES_LOAD, BYTES_STORE, - LIST_VALUE, LIST_BIND, APPEND_IN, NUM_PARSE, CALL, RETURN, CHAR_AT, CHAR }; + LIST_VALUE, LIST_BIND, APPEND_IN, NUM_PARSE, CALL, RETURN, CHAR_AT, CHAR, FLOORDIV }; /* A cursor's and an index's instructions are Pure's anchor's (027's 4c), MOVE and COUNT naming the text or list in * bits 0-15 and the unit above (IN). */ enum { CUR_LOAD = ANCHOR_LOAD, CUR_START = ANCHOR_START, CUR_END = ANCHOR_END, CUR_MOVE = ANCHOR_MOVE, CUR_STORE = ANCHOR_STORE, @@ -452,7 +452,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); @@ -557,7 +557,7 @@ 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, BIT_OR}, {"^", 6, BIT_XOR}, {"&", 7, BIT_AND}, {"<<", 8, BIT_LEFT}, - {">>", 8, BIT_RIGHT}, {"+", 9, ADD}, {"-", 9, SUB}, {"*", 10, MUL}, {"/", 10, DIV}, {"%", 10, MOD}, + {">>", 8, BIT_RIGHT}, {"+", 9, ADD}, {"-", 9, SUB}, {"*", 10, MUL}, {"/", 10, DIV}, {"//", 10, FLOORDIV}, {"%", 10, MOD}, }; enum { UNARY = 11 }; /* a unary operator's operand binds tighter than any binary one */ @@ -2431,7 +2431,8 @@ static void integer(Compiler *c, const Expr *e, bool handler, int height) { static bool literal_only(const Expr *e) { if (loose(e)) return true; if (e->kind == E_IF) return literal_only(e->args[1]) && literal_only(e->args[2]); /* whatever its condition */ - bool arithmetic = (e->kind == E_BINARY && (e->op == ADD || e->op == SUB || e->op == MUL || e->op == DIV || e->op == MOD) && + bool arithmetic = (e->kind == E_BINARY && (e->op == ADD || e->op == SUB || e->op == MUL || e->op == DIV || e->op == FLOORDIV || + e->op == MOD) && e->args[1]->kind != E_UNIT) || (e->kind == E_CALL && (e->op == MIN || e->op == MAX)) || (e->kind == E_UNARY && e->op == NEG); for (unsigned i = 0; arithmetic && i < e->count; ++i) arithmetic = literal_only(e->args[i]); @@ -2481,7 +2482,7 @@ static uint8_t i64_op(int op) { case ADD: return I64_ADD; case SUB: return I64_SUB; case MUL: return I64_MUL; - case DIV: return I64_DIV; + case DIV: case FLOORDIV: return I64_DIV; /* (an i64's / refused: its quotient is //'s) */ case MOD: return I64_MOD; case MIN: return I64_MIN; default: return I64_MAX; @@ -3938,9 +3939,12 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) { emit(c, e->line, e->column, NEG, 0, 0); return (Kind){NUM, -1}; } - if (e->count == 2 && (e->op == MUL || e->op == DIV || e->op == MOD || e->op == MIN || e->op == MAX)) { + if (e->count == 2 && (e->op == MUL || e->op == DIV || e->op == FLOORDIV || e->op == MOD || e->op == MIN || e->op == MAX)) { Kind left = wanted(c, e->args[0], handler, height, left_hint(c, e, handler, height, want)); Type type = second(c, e, left, handler, height); + if (type == I64 && e->op == DIV) /* Pure 027's stage 5: / divides numbers, // gives an i64's floored quotient */ + fail_at(c, e->line, e->column, "an i64 divides by // (its quotient, floored); / divides numbers, as in " + "number(a) / number(b)"); emit(c, e->line, e->column, type == I64 ? i64_op(e->op) : (uint8_t)e->op, 0, 0); return (Kind){type, -1}; } diff --git a/tests/check.py b/tests/check.py index 7d53e1d..66b8db4 100644 --- a/tests/check.py +++ b/tests/check.py @@ -221,6 +221,7 @@ DIAGNOSTICS = [ '6:12', "a cursor of 'a' cannot point into 'b'"), ('state a: text\nstate at: cursor in a\ninput k = key KeyK\non k.down {\n at = 3\n}\n', '5:8', 'expected a cursor, not a number'), + ('state n: i64 = 7\nview v = n / 2\n', '2:12', 'an i64 divides by // (its quotient, floored)'), ('state a: text\nstate at: cursor in a\nview v = at\n', '3:10', 'a view is a number, a bool, an i64, text, bytes or a list, not a cursor'), ('state a: text\nstate at: cursor in a\nview v = "{at}"\n', '3:12', 'a string shows numbers, bools, i64s and text, not a cursor'), ('state x = 1\nstate t: text = "{x}"\n', '2:17', 'a text state starts from a plain string'), @@ -574,7 +575,7 @@ ACCEPTED = [ 'output told = message {\n id: i64\n big: bool\n}\ninput go = key KeyG\n' 'on put {\n orders.add(Order { id: put.id, total: put.total * 2 + 1, note: "{put.id}" })\n' ' send told { id: put.id, big: put.total > 1000 }\n}\n' - 'on go.down {\n let step: = 0\n}\n'.replace(' let step: = 0\n', ' let step = next / 3 % 7 - 1\n next = next + step\n' + 'on go.down {\n let step: = 0\n}\n'.replace(' let step: = 0\n', ' let step = next // 3 % 7 - 1\n next = next + step\n' ' sum = sum + max(-step, 0) + min(1, next)\n seen = number(step) + seen\n' ' for o in orders[0 .. next] where o.total != 0 {\n o.total = -o.total\n }\n if orders.has(5) { orders[5].total = 9 }\n') + 'view count = count(o in orders[-5 .. 5])\nview most = max(o.total for o in orders)\nview kinds = distinct(o.total for o in orders)\n' @@ -785,7 +786,7 @@ def random_program(rng): return f'number({integer(depth - 1, handler)})' if choice < 0.6: return f'(if {truth(depth - 1, handler)} {{ {number(depth - 1, handler)} }} else {{ {number(depth - 1, handler)} }})' - op = rng.choice(['+', '-', '*', '/']) + op = rng.choice(['+', '-', '*', '/', '//']) return f'({number(depth - 1, handler)} {op} {number(depth - 1, handler)})' def integer(depth, handler): @@ -801,7 +802,7 @@ def random_program(rng): return f'(if {truth(depth - 1, handler)} {{ {integer(depth - 1, handler)} }} else {{ {rng.choice(["0", "-1"])} }})' if choice < 0.6: return '-' + integer(depth - 1, handler) - op = rng.choice(['+', '-', '*', '/', '%']) + op = rng.choice(['+', '-', '*', '//', '%']) right = str(rng.choice([1, 2, 10, -3])) if rng.random() < 0.3 else integer(depth - 1, handler) return f'({integer(depth - 1, handler)} {op} {right})' @@ -2305,10 +2306,10 @@ on add.down { n = n + 1 } on grow.down { next = next * 1024 } -on half.down { next = -next / 2 } +on half.down { next = -next // 2 } on far.down { n = number(next) } view total = sum(r.id for r in rows) -view line = "{next} {i64(-7) / 2} {i64(-7) % 2} {i64(7) % -2} {-7 / 2}" +view line = "{next} {i64(-7) // 2} {i64(-7) % 2} {i64(7) % -2} {-7 / 2}" view least = min(r.id for r in rows) """ @@ -2430,6 +2431,22 @@ def main(): assert shown['error'] is None and shown['states']['s299'] == 7 and shown['views']['v69'] == 2, shown['states']['s299'] report['wide_program'] = {'states': 301, 'inputs': 40, 'views': 70, 'records': 20, 'source_bytes': len(wide)} + # Floor division of numbers (Pure 027's stage 5): a // b and a % b as Python's, bit for bit (-0 too), on random pairs. + import math + frng = random.Random(55) + pairs = [(7.0, 2.0), (-7.0, 2.0), (7.0, -2.0), (1.0, 0.1), (0.0, -3.0), (-0.0, 3.0), (1e300, 7.0), (5.5, 1.5)] + pairs += [(frng.uniform(-1e6, 1e6), frng.choice([frng.uniform(-100, 100), float(frng.randint(-9, 9) or 3)])) + for _ in range(192)] + source = ''.join(f'view q{i} = {a!r} // {b!r}\nview r{i} = {a!r} % {b!r}\n' for i, (a, b) in enumerate(pairs)) + (work / 'floors.aup').write_text(source) + compile_module(aup, work / 'floors.aup', work / 'floors.pmod') + shown = json.loads(run(pure_run, work / 'floors.pmod', stdin='show\n').stdout, parse_int=float)['views'] # -0 a float + for i, (a, b) in enumerate(pairs): + q, r = shown[f'q{i}'], shown[f'r{i}'] + assert math.copysign(1, q) == math.copysign(1, a // b) and q == a // b, (a, b, q, a // b) + assert r == a % b and math.copysign(1, r) == math.copysign(1, a % b), (a, b, r, a % b) + report['floor_divisions'] = len(pairs) + # message like X is X's fields, written once: the same module, byte for byte. fields = ' title: text\n lane: u32\n to: {\n city: text\n }\n' body = 'on remote {\n n = remote.lane\n}\non tap.down {\n send changed { title: "t", lane: 1, to: { city: "c" } }\n}\n' -- cgit v1.2.3