{"atom":"regression/string-bitwise-codepoints","family":"regression","slug":"string-bitwise-codepoints","header":{"atom":"regression/string-bitwise-codepoints","kind":"whole-program — a minimal repro, not yet reduced to an atom","source":"curated"},"axes":null,"cols":null,"engines":["rakudo-2026.07","rakupp-3.14.0"],"kinds":["ref","pp"],"defFrom":"rakupp:t/regression/string-bitwise-codepoints.raku","counts":{"a":1,"f":0,"d":0,"c":0,"r":0,"n":0},"files":["atoms/regression/string-bitwise-codepoints.grid"],"records":[["0001","","# Regression: the string bitwise operators combined the UTF-8 BYTES of their\n# operands instead of the CODEPOINTS, and prefix:<~^> complemented bytes and put\n# the result back into a Str.\n#\n# For ASCII the two are the same, which is why it hid. For anything else it was\n# wrong twice over: `\"A\" ~^ chr(0xFF)` answered TWO characters (0x82, 0xBF) where\n# Rakudo answers one (0x00BE), and the bytes left in the Str were not valid\n# UTF-8 — `~^ \"1\"` reported `.ords` of 0x0E while printing the byte 0xCE. That\n# byte reached a generated page on the website and aborted a build, since BSD\n# sed refuses an illegal byte sequence under a UTF-8 locale.\n#\n# prefix:<~^> on a Str now declines exactly as Rakudo does — there is no agreed\n# meaning for \"the complement of a codepoint\" — but on a Blob/Buf Rakudo DOES\n# implement it, and declining there too cost 151 Roast assertions before this\n# test existed.\n#\n# NOT fixed here, and pre-existing: the INFIX `Buf ~^ Buf` answers a Str rather\n# than a Buf and gets the wrong bytes. Untouched by this change — identical\n# before and after — and tracked separately. (The PREFIX form on a Buf is\n# correct and asserted below.)\n# Contract: exit 0 + last line PASS.\nmy @fail;\n\n# codepoints, not bytes — the case that was wrong\nmy $x = \"A\" ~^ chr(0xFF);\n@fail.push(\"A ~^ chr(0xFF): {$x.ords.map(*.fmt('%04X')).join(',')}\")\n    unless $x.chars == 1 && $x.ords[0] == 0xBE;\n\n# whatever comes out is well-formed text: .ords and the encoded bytes agree\n@fail.push('ords/bytes disagree')\n    unless $x.encode('utf8').list.join(',') eq '194,190';   # U+00BE\n\n# ASCII still behaves as it always did\n@fail.push('A ~^ B')  unless (\"A\" ~^ \"B\").ords[0] == 0x03;\n@fail.push('A ~| b')  unless (\"A\" ~| \"b\").ords[0] == 0x63;\n@fail.push('A ~& z')  unless (\"A\" ~& \"z\").ords[0] == 0x40;\n\n# a high codepoint that OR-ing must preserve exactly\n@fail.push('A ~| é') unless (\"A\" ~| chr(0xE9)).ords[0] == 0xE9;\n\n# `~&` truncates to the shorter operand, `~|`/`~^` extend\n@fail.push('~& truncates') unless (\"abc\" ~& \"ab\").chars == 2;\n@fail.push('~| extends')   unless (\"ab\" ~| \"abc\").chars == 3;\n@fail.push('~^ extends')   unless (\"ab\" ~^ \"abc\").chars == 3;\n\n# prefix:<~^> on a Str declines, as in Rakudo, rather than manufacturing an\n# invalid Str\n@fail.push('prefix ~^ on Str should decline')\n    unless (try { ~^ \"1\"; 0 } // 1) == 1;\n\n# ...but on a Blob/Buf Rakudo DOES implement it, as the byte-wise complement,\n# and so must we. Declining unconditionally killed S03-operators/buf.t outright\n# — the file stopped emitting TAP and 151 assertions vanished from the Roast\n# count, which is how this was caught.\nmy $nb = ~^ Buf.new(0x41, 0xFF);\n@fail.push(\"prefix ~^ on Buf: {$nb.^name} {$nb.list.map(*.fmt('%02X')).join(' ')}\")\n    unless $nb.^name eq 'Buf' && $nb.list.map(*.fmt('%02X')).join(' ') eq 'BE 00';\n\n# a longer round trip: every character of the result is valid text\nmy $mix = \"héllo wörld\" ~^ (\"\\0\" x 11);\n@fail.push('xor with NUL is identity') unless $mix eq \"héllo wörld\";\n@fail.push('result is valid UTF-8')\n    unless $mix.encode('utf8').decode('utf8') eq $mix;\n\nif @fail { note \"FAILED:\\n\" ~ @fail.join(\"\\n\"); say 'FAIL' }\nelse     { say 'PASS' }","output PASS","a",["PASS","PASS"],0,[["budget","20s"]]]]}