From 4aefbab3cdbcb5a7c4b7598795baa276f82fd842 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 19 Oct 2026 03:56:43 +0100 Subject: 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 --- compiler/compile.c | 38 ++++++++++++++++++++++++------ docs/design/002-dsl-direction.md | 16 +++++++++++++ tests/check.py | 50 ++++++++++++++++++++++++++++++++++++++++ 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(' 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)) -- cgit v1.2.3