aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-19 03:56:43 +0100
committereunhhu <[email protected]>2026-10-19 03:56:43 +0100
commit4aefbab3cdbcb5a7c4b7598795baa276f82fd842 (patch)
treef95850f5b392f6a24491004216564b796f0415ef
parent0d66663351c411693c71788804717ad2694c81cd (diff)
downloadaup-4aefbab3cdbcb5a7c4b7598795baa276f82fd842.tar.gz
aup-4aefbab3cdbcb5a7c4b7598795baa276f82fd842.zip
Bytes compared (Pure's TEXT_COMPARE with PURE_COMPARE_BYTES): ==, !=, <, <=, >, >= between bytes by their bytes; a string (a literal, or one with formats) where bytes are what it meets, compared or joined, is its UTF-8 bytes, as 1 is an i64 where i64s go, values of text and bytes mixing in neither (bytes(t) or text(b)); test bytes_compared (400 random pairs with 0x00 and 0xff in the six relations, literals on either side, a formatted string, joins with literals, against Python), two diagnostics; the 60 sources compiled before the same modules; 002
-rw-r--r--compiler/compile.c38
-rw-r--r--docs/design/002-dsl-direction.md16
-rw-r--r--tests/check.py50
3 files changed, 97 insertions, 7 deletions
diff --git a/compiler/compile.c b/compiler/compile.c
index ec99e50..1fdd43a 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -2452,6 +2452,15 @@ static bool literal_only(const Expr *e) {
}
/* The type e has, compiled and taken back: what literals beside it take. */
+static Kind meeting(Compiler *c, const Expr *x, bool handler, int height, Type other) {
+ Kind kind = value(c, x, handler, height);
+ if (x->kind == E_STRING && kind.type == TEXT && other == BYTES) {
+ emit(c, x->line, x->column, BYTES_FROM, 0, 0);
+ return (Kind){BYTES, -1};
+ }
+ return kind;
+}
+
static Type probe(Compiler *c, const Expr *e, bool handler, int height) {
unsigned code = c->code_count, constants = c->constants, joins = c->joins, instances = c->instance_count;
size_t used = c->constant_used;
@@ -2724,6 +2733,13 @@ static uint32_t local_arg(const Compiler *c, int local) {
* where it cannot fail they compute it anyway (one AND or OR). */
static Type probe(Compiler *c, const Expr *e, bool handler, int height);
+/* A side of a comparison or a join: a string (a literal, or one with formats) where bytes are what it meets is its
+ * UTF-8 bytes, as 1 is an i64 where i64s go; other values do not mix (bytes(t) and text(b) convert them). */
+static Kind meeting(Compiler *c, const Expr *x, bool handler, int height, Type other);
+static bool meets_bytes(Compiler *c, const Expr *string, const Expr *other, bool handler, int height) {
+ return string->kind == E_STRING && probe(c, other, handler, height) == BYTES;
+}
+
/* A list the code makes (027's 2b), an event's: a register a list in a record is bound to, to be read (bound; a new one
* each time: each place reads its own), or the one empty list of a record (a LIST field's default). */
static int hidden_list(Compiler *c, int record, bool bound, int line, int column) {
@@ -3831,10 +3847,12 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
if (e->op == ADD || e->op == SUB) {
/* at + 1 char moves a cursor, at + 1 an index; both stop at the ends. */
bool distance = e->args[1]->kind == E_UNIT;
- Kind left = wanted(c, e->args[0], handler, height, distance ? 0 : left_hint(c, e, handler, height, want));
+ Kind left = !distance && meets_bytes(c, e->args[0], e->args[1], handler, height + 1)
+ ? meeting(c, e->args[0], handler, height, BYTES)
+ : wanted(c, e->args[0], handler, height, distance ? 0 : left_hint(c, e, handler, height, want));
if (!distance && (left.type == BYTES || left.type == TEXT)) { /* joined */
if (e->op == SUB) fail_at(c, e->line, e->column, "texts and bytes are joined with +; - takes nothing apart");
- Kind right = value(c, e->args[1], handler, height + 1);
+ Kind right = meeting(c, e->args[1], handler, height + 1, left.type);
if (right.type != left.type)
fail_at(c, e->args[1]->line, e->args[1]->column, "%s joins %s, not %s", type_name(left.type), type_name(left.type),
type_name(right.type));
@@ -3855,11 +3873,17 @@ static Kind value(Compiler *c, const Expr *e, bool handler, int height) {
return (Kind){type, -1};
}
if (e->op >= LT && e->op <= NE) {
- /* Texts compare by their bytes, numbers and i64s as numbers; bools are equal or not. */
- Kind left = wanted(c, e->args[0], handler, height, left_hint(c, e, handler, height, 0));
- if (left.type == TEXT) {
- expect_kind(c, e->args[1], value(c, e->args[1], handler, height + 1), TEXT);
- emit(c, e->line, e->column, TEXT_COMPARE, (uint32_t)(e->op - LT), 0);
+ /* Texts and bytes compare by their bytes, numbers and i64s as numbers; bools are equal or not. */
+ Kind left = meets_bytes(c, e->args[0], e->args[1], handler, height + 1) ? meeting(c, e->args[0], handler, height, BYTES)
+ : wanted(c, e->args[0], handler, height, left_hint(c, e, handler, height, 0));
+ if (left.type == TEXT || left.type == BYTES) {
+ Kind right = meeting(c, e->args[1], handler, height + 1, left.type);
+ if (right.type == TEXT || right.type == BYTES) {
+ if (right.type != left.type)
+ fail_at(c, e->args[1]->line, e->args[1]->column, "%s compares with %s, not %s: convert one, as in bytes(t) or "
+ "text(b)", type_name(left.type), type_name(left.type), type_name(right.type));
+ } else expect_kind(c, e->args[1], right, left.type);
+ emit(c, e->line, e->column, TEXT_COMPARE, (uint32_t)(e->op - LT) | (left.type == BYTES ? 0x100u : 0), 0);
return (Kind){BOOL, -1};
}
if (left.type == BOOL) {
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index 31d4212..3ba3d5d 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -917,3 +917,19 @@ on give {
- 검증: `bare_lists`가 무작위 리스트 200개를 행으로 받아 리스트를 받는 fn, 두 리스트를 돌려주는 fn, 수의 리스트를 돌려주는 fn을
지나게 하고 파이썬과 견준다. 진단 둘(리스트 값을 바꾸기, 저장되는 리스트를 넘기기). 그 전에 컴파일되던 aup 소스 60개의 모듈은
바이트까지 같다.
+
+### 바이트의 비교 (Pure 140의 DNS와 HTTP 뒤)
+
+```text
+on packet {
+ if f.name == "content-length" { ... } # 바이트를 만나는 문자열은 그 UTF-8 바이트다
+ require s.name < t.name # 바이트는 바이트끼리 바이트 순서로 (Pure의 TEXT_COMPARE, PURE_COMPARE_BYTES)
+ let line = s.name + ": " + s.value + "\r\n" # 잇기도 같다
+}
+```
+
+- **비교.** `==`, `!=`, `<`, `<=`, `>`, `>=`가 바이트 둘을 바이트 순서로 견준다(앞이 같으면 짧은 쪽이 앞). UTF-8을 묻지 않는다.
+- **리터럴은 만나는 것을 따른다.** 문자열(리터럴과 형식이 든 것)이 바이트를 만나면 그 UTF-8 바이트가 된다. i64를 만나는 `1`이
+ i64인 것과 같다. 값끼리는 섞이지 않는다: text의 값과 바이트의 값은 비교도 잇기도 하지 않고(진단), `bytes(t)`나 `text(b)`로 옮긴다.
+- 검증: `bytes_compared`가 0x00과 0xff가 든 무작위 바이트 400쌍을 여섯 관계, 양쪽의 리터럴, 형식이 든 문자열, 리터럴과의 잇기로
+ 파이썬과 견준다. 진단 둘(바이트와 text의 값). 그 전에 컴파일되던 소스 60개의 모듈은 바이트까지 같다.
diff --git a/tests/check.py b/tests/check.py
index b19b37e..7a3e9f3 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -83,6 +83,12 @@ DIAGNOSTICS = [
('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"),
+ # Bytes compared (after 140's DNS and HTTP): bytes compare with bytes (a string where bytes go is its bytes), and
+ # neither compares nor joins with a text's value.
+ ('state t: text\ninput m = message { name: bytes }\non m {\n require m.name == t\n}\n', '4:21',
+ 'bytes compares with bytes, not text: convert one, as in bytes(t) or text(b)'),
+ ('state t: text\ninput m = message { name: bytes }\non m {\n require t < m.name\n}\n', '4:15',
+ 'text compares with text, not bytes: convert one, as in bytes(t) or text(b)'),
# Lists bare as values (after 140's inflate and HTTP): a list value (a parameter's, or what a fn gave back) is read,
# not changed; a stored list is not passed as one.
('fn grow(xs) {\n xs.append(1)\n return 0\n}\nstate n = 0\non frame {\n let ys: list of number = [1]\n n = grow(ys)\n}\n',
@@ -589,6 +595,26 @@ on give {
}
'''
+BYTES_COMPARED = '''state rel = 0
+state lit = 0
+state joined = false
+input m = message { a: bytes, b: bytes }
+on m {
+ rel = 0
+ if m.a < m.b { rel = rel + 1 }
+ if m.a <= m.b { rel = rel + 2 }
+ if m.a > m.b { rel = rel + 4 }
+ if m.a >= m.b { rel = rel + 8 }
+ if m.a == m.b { rel = rel + 16 }
+ if m.a != m.b { rel = rel + 32 }
+ lit = 0
+ if m.a == "ab" { lit = lit + 1 }
+ if "ab" < m.a { lit = lit + 2 }
+ if m.b != "{rel}" { lit = lit + 4 }
+ joined = "<" + m.a + "|" + m.b + ">" == bytes("<") + m.a + bytes("|") + m.b + bytes(">")
+}
+'''
+
ACCEPTED = [
# Views of any value (Pure 027's 4e): a list as it is, filtered, a map with a list and a bytes column, a list a body
# built, bytes.
@@ -1506,6 +1532,29 @@ def nested_values(aup, pure_run, work):
+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
+ bytes are what it meets is its UTF-8 bytes), and joined with literals; each event against Python's."""
+ import random
+ import struct
+ path = work / 'bytes_compared.pmod'
+ (work / 'bytes_compared.aup').write_text(BYTES_COMPARED)
+ compile_module(aup, work / 'bytes_compared.aup', path)
+ rng = random.Random(140)
+ pick = lambda: bytes(rng.choice(b'ab\x00\xff') for _ in range(rng.randrange(0, 4)))
+ asked = [(pick(), pick()) for _ in range(events)]
+ commands = ''.join(f"message m {(struct.pack('<I', len(a)) + a + b).hex()}\n" for a, b in asked)
+ lines = run(pure_run, path, stdin=commands).stdout.splitlines()
+ assert len(lines) == events
+ for (a, b), line in zip(asked, lines):
+ state = json.loads(line)
+ rel = sum(bit for bit, holds in zip((1, 2, 4, 8, 16, 32), (a < b, a <= b, a > b, a >= b, a == b, a != b)) if holds)
+ lit = (a == b'ab') + 2 * (b'ab' < a) + 4 * (b != str(rel).encode())
+ assert state['fault'] == 'none' and state['states'] == {'rel': rel, 'lit': lit, 'joined': True}, (a, b, state)
+ return events
+
+
def bare_lists(aup, pure_run, work, events=200):
"""Lists passed to fns and given back bare (Pure 027 after 140's inflate and HTTP): a message's rows and a let's
list given to fns as values (copies), fns that fold, walk and read them by position, a fn giving back two lists
@@ -2675,6 +2724,7 @@ def main():
report['nested_values'] = nested_values(aup, pure_run, work)
report['functions'] = functions(aup, pure_run, work)
report['bare_lists'] = bare_lists(aup, pure_run, work)
+ report['bytes_compared'] = bytes_compared(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))
print(json.dumps(report))