{"atom":"regression/builtin-char-class-rules","family":"regression","slug":"builtin-char-class-rules","header":{"atom":"regression/builtin-char-class-rules","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/builtin-char-class-rules.raku","counts":{"a":1,"f":0,"d":0,"c":0,"r":0,"n":0},"files":["atoms/regression/builtin-char-class-rules.grid"],"records":[["0001","","# Regression: the POSIX-NAMED character classes are UNICODE, not <ctype.h>.\n# They were byte tests in four separate places (the rule form, the ASCII byteset\n# a class compiles to, the multibyte arm of a class match, and a fourth copy on\n# the grammar subrule path), so `<alpha>` stopped at the first accented letter:\n# URI's RFC-3986 grammar routes every path character through <alpha>, and\n# `URI.new(\"http://test.de/ö\")` threw \"Could not parse URI\". Fixed by one\n# predicate over a codepoint, shared by all four.\n#\n# The table below is Rakudo's, derived character by character. The traps:\n#   <alpha> includes '_' and excludes :Nl (Ⅰ is not a letter here)\n#   <punct> is :P ALONE — '+', '$', '|' are symbols and are OUT\n#   <graph> is alnum ∪ punct, so symbols are out of that too\n#   <print> is everything that is not :Cc — including NBSP and emoji\n#   \\w and <alnum> are :L ∪ :Nd ∪ '_' — '²' (:No) is NOT a word character\n# Contract: exit 0 + last line PASS.\nmy @fail;\n\nsub check($desc, $got, $want) { @fail.push(\"$desc: got $got, want $want\") unless $got eqv $want }\n\n# name => [cp, alpha, digit, space, blank, alnum, upper, lower, xdigit, punct, cntrl, graph, print]\nmy @table =\n    ('a',    0x61, 1,0,0,0, 1,0,1,1, 0,0,1,1),\n    ('Z',    0x5A, 1,0,0,0, 1,1,0,0, 0,0,1,1),\n    ('5',    0x35, 0,1,0,0, 1,0,0,1, 0,0,1,1),\n    ('_',    0x5F, 1,0,0,0, 1,0,0,0, 1,0,1,1),\n    ('o-um', 0xF6, 1,0,0,0, 1,0,1,0, 0,0,1,1),\n    ('Omega',0x3A9,1,0,0,0, 1,1,0,0, 0,0,1,1),\n    ('de',   0x434,1,0,0,0, 1,0,1,0, 0,0,1,1),\n    ('CJK',  0x4E2D,1,0,0,0,1,0,0,0, 0,0,1,1),\n    ('dot',  0x2E, 0,0,0,0, 0,0,0,0, 1,0,1,1),\n    ('arab3',0x663,0,1,0,0, 1,0,0,0, 0,0,1,1),\n    ('full3',0xFF13,0,1,0,0,1,0,0,0, 0,0,1,1),\n    ('sup2', 0xB2, 0,0,0,0, 0,0,0,0, 0,0,0,1),   # :No — not a digit, not alnum\n    ('nbsp', 0xA0, 0,0,1,1, 0,0,0,0, 0,0,0,1),\n    ('ideo-sp',0x3000,0,0,1,1,0,0,0,0,0,0,0,1),\n    ('LS',   0x2028,0,0,1,0, 0,0,0,0, 0,0,0,1),  # space but not blank\n    ('inv-!',0xA1, 0,0,0,0, 0,0,0,0, 1,0,1,1),\n    ('euro', 0x20AC,0,0,0,0,0,0,0,0, 0,0,0,1),   # :Sc — punct and graph say no\n    ('acute',0x301,0,0,0,0, 0,0,0,0, 0,0,0,1),\n    ('roman-I',0x2160,0,0,0,0,0,0,0,0,0,0,0,1),  # :Nl — not alpha\n    ('emoji',0x1F600,0,0,0,0,0,0,0,0,0,0,0,1),\n    ('plus', 0x2B, 0,0,0,0, 0,0,0,0, 0,0,0,1),   # :Sm — NOT punct, NOT graph\n    ('dollar',0x24,0,0,0,0, 0,0,0,0, 0,0,0,1),\n    ('backtick',0x60,0,0,0,0,0,0,0,0, 0,0,0,1),\n    ('NUL',  0x00, 0,0,0,0, 0,0,0,0, 0,1,0,0),\n    ('tab',  0x09, 0,0,1,1, 0,0,0,0, 0,1,0,0),\n    ('LF',   0x0A, 0,0,1,0, 0,0,0,0, 0,1,0,0),\n    ('DEL',  0x7F, 0,0,0,0, 0,0,0,0, 0,1,0,0),\n    ('NEL',  0x85, 0,0,1,0, 0,0,0,0, 0,1,0,0),\n    ('SHY',  0xAD, 0,0,0,0, 0,0,0,0, 0,0,0,1),   # :Cf — not a control\n    ('ZWSP', 0x200B,0,0,0,0,0,0,0,0, 0,0,0,1);\n\nfor @table -> @row {\n    my ($nm, $cp) = @row[0, 1];\n    my $c = $cp.chr;\n    my @got = (so $c ~~ /^<alpha>$/),  (so $c ~~ /^<digit>$/), (so $c ~~ /^<space>$/),\n              (so $c ~~ /^<blank>$/),  (so $c ~~ /^<alnum>$/), (so $c ~~ /^<upper>$/),\n              (so $c ~~ /^<lower>$/),  (so $c ~~ /^<xdigit>$/),(so $c ~~ /^<punct>$/),\n              (so $c ~~ /^<cntrl>$/),  (so $c ~~ /^<graph>$/), (so $c ~~ /^<print>$/);\n    my @want = @row[2 .. 13].map(* == 1);\n    check(\"$nm rules\", @got, @want);\n    # the `<+name>` charset form must agree with the rule form, member for member\n    my @cls = (so $c ~~ /^<+alpha>$/),  (so $c ~~ /^<+digit>$/), (so $c ~~ /^<+space>$/),\n              (so $c ~~ /^<+blank>$/),  (so $c ~~ /^<+alnum>$/), (so $c ~~ /^<+upper>$/),\n              (so $c ~~ /^<+lower>$/),  (so $c ~~ /^<+xdigit>$/),(so $c ~~ /^<+punct>$/),\n              (so $c ~~ /^<+cntrl>$/),  (so $c ~~ /^<+graph>$/), (so $c ~~ /^<+print>$/);\n    check(\"$nm charset\", @cls, @want);\n}\n\n# \\w is alnum: :No and :Nl are out\ncheck('word chars', (0xB2, 0x2160, 0x663, 0x5F, 0x4E2D, 0x301).map({ so .chr ~~ /^\\w$/ }).List,\n      (False, False, True, True, True, False));\n\n# <ident> runs over Unicode letters and does not start on a digit\ncheck('ident', (\"aö1_x\" ~~ /^<ident>/).Str, 'aö1_x');\ncheck('ident-digit', so (\"5a\" ~~ /^<ident>/), False);\n\n# inside a GRAMMAR the same names must resolve the same way — this path had its\n# own ASCII copy, so `token inner { <alpha> }` failed where /<alpha>/ succeeded\ngrammar G {\n    token TOP     { <unreserved>+ }\n    token unreserved { <[\\-._~] +alnum> }\n}\ncheck('grammar class', so G.parse('öÄ-x9'), True);\ngrammar H { token TOP { <inner>+ }; token inner { <alpha> } }\ncheck('grammar rule', so H.parse('ö'), True);\n\n# a grammar's OWN definition still wins over the built-in\ngrammar Shadow { token TOP { <alpha>+ }; token alpha { 'zz' } }\ncheck('grammar shadows', so Shadow.parse('zzzz'), True);\ncheck('grammar shadows 2', so Shadow.parse('ab'), False);\n\n# the zero-width word assertions: <ww> only between two word chars, <wb> at an edge\ncheck('ww', so ('ab' ~~ /a <ww> b/), True);\ncheck('ww-edge', so ('ab' ~~ /^ <ww> ab/), False);\ncheck('wb-start', so ('ab' ~~ /^ <wb> ab/), True);\ncheck('wb-punct', so ('.a' ~~ /^ <wb> '.'/), False);\n\nif @fail { note \"FAILED:\\n\" ~ @fail.join(\"\\n\"); say 'FAIL' } else { say 'PASS' }","output PASS","a",["PASS","PASS"],0,[["budget","20s"]]]]}