aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-19 01:13:07 +0100
committereunhhu <[email protected]>2026-10-19 01:13:07 +0100
commited9d62ae1ef8b60e72be5bbeefa432aa798ff299 (patch)
tree10f1e94338ffd5bc91951f18703571632236d29b
parente0298482010b0f78398b3c94cb0d127db5042a78 (diff)
downloadaup-ed9d62ae1ef8b60e72be5bbeefa432aa798ff299.tar.gz
aup-ed9d62ae1ef8b60e72be5bbeefa432aa798ff299.zip
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)
-rw-r--r--compiler/compile.c16
-rw-r--r--tests/check.py27
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'