hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

+test.ha (23538B)


      1 // License: MPL-2.0
      2 // (c) 2022 Vlad-Stefan Harbuz <vlad@vladh.net>
      3 use fmt;
      4 
      5 type matchres = enum { MATCH, NOMATCH, ERROR };
      6 
      7 fn run_find_case(
      8 	expr: str,
      9 	string: str,
     10 	expected: matchres,
     11 	start: int,
     12 	end: int
     13 ) void = {
     14 	const re = match (compile(expr)) {
     15 	case let re: regex => yield re;
     16 	case let e: error =>
     17 		if (expected == matchres::MATCH) {
     18 			fmt::println(e)!;
     19 			fmt::fatalf("Expected expression /{}/ to match string \"{}\", but it errored",
     20 				expr, string);
     21 		};
     22 		if (expected == matchres::NOMATCH) {
     23 			fmt::println(e)!;
     24 			fmt::fatalf("Expected expression /{}/ to not match string \"{}\", but it errored",
     25 				expr, string);
     26 		};
     27 		return;
     28 	};
     29 
     30 	if (expected == matchres::ERROR) {
     31 		fmt::fatalf("Expected expression /{}/ to have error caught during compilation, but it did not",
     32 			expr);
     33 	};
     34 	defer finish(&re);
     35 
     36 	match (find(&re, string)) {
     37 	case void =>
     38 		if (expected == matchres::MATCH) {
     39 			fmt::fatalf("Expected expression /{}/ to match string \"{}\", but it did not",
     40 				expr, string);
     41 		};
     42 
     43 	case let captures: []capture =>
     44 		defer free_captures(captures);
     45 		if (expected == matchres::NOMATCH) {
     46 			fmt::fatalf("Expected expression /{}/ to not match string \"{}\", but it did",
     47 				expr, string);
     48 		};
     49 		if (start: size != captures[0].start) {
     50 			fmt::fatalf("Expected start of main capture to be {} but it was {}",
     51 				start, captures[0].start);
     52 		};
     53 		if (end: size != captures[0].end) {
     54 			fmt::fatalf("Expected end of main capture to be {} but it was {}",
     55 				end, captures[0].end);
     56 		};
     57 	};
     58 };
     59 
     60 fn run_submatch_case(
     61 	expr: str,
     62 	string: str,
     63 	expected: matchres,
     64 	count: size,
     65 	targets: []str
     66 ) void = {
     67 	const re = compile(expr)!;
     68 	defer finish(&re);
     69 
     70 	const captures = find(&re, string) as []capture;
     71 	defer free_captures(captures);
     72 	assert(len(captures) == count, "Invalid number of captures");
     73 	for (let i = 0z; i < len(targets); i += 1) {
     74 		assert(targets[i] == captures[i].content, "Invalid capture");
     75 	};
     76 };
     77 
     78 fn run_findall_case(
     79 	expr: str,
     80 	string: str,
     81 	expected: matchres,
     82 	count: size,
     83 	targets: []str
     84 ) void = {
     85 	const re = match (compile(expr)) {
     86 	case let re: regex => yield re;
     87 	case let e: error =>
     88 		if (expected == matchres::MATCH) {
     89 			fmt::println(e)!;
     90 			fmt::fatalf("Expected expression /{}/ to match, but it errored",
     91 				expr, string);
     92 		};
     93 		if (expected == matchres::NOMATCH) {
     94 			fmt::println(e)!;
     95 			fmt::fatalf("Expected expression /{}/ to not match, but it errored",
     96 				expr, string);
     97 		};
     98 		return;
     99 	};
    100 	defer finish(&re);
    101 
    102 	if (expected == matchres::ERROR) {
    103 		fmt::fatalf("Expected expression /{}/ to have error caught during compilation, but it did not",
    104 			expr);
    105 	};
    106 
    107 	match (findall(&re, string)) {
    108 	case void =>
    109 		if (expected == matchres::MATCH) {
    110 			fmt::fatalf("Expected expression /{}/ to match string \"{}\", but it did not",
    111 				expr, string);
    112 		};
    113 
    114 	case let matches: [][]capture =>
    115 		defer free_matches(matches);
    116 		if (expected == matchres::NOMATCH) {
    117 			fmt::fatalf("Expected expression /{}/ to not match string \"{}\", but it did",
    118 				expr, string);
    119 		};
    120 		if (count != len(matches)) {
    121 			fmt::fatalf("Expected to find {} matches but found {}",
    122 				count, len(matches));
    123 		};
    124 		for (let i = 0z; i < len(matches); i += 1) {
    125 			if (matches[i][0].content != targets[i]) {
    126 				fmt::printfln("Expected submatch to be {} but it was {}",
    127 					targets[i], matches[i][0].content)!;
    128 				assert(false);
    129 			};
    130 		};
    131 	};
    132 };
    133 
    134 @test fn find() void = {
    135 	const cases = [
    136 		// literals
    137 		(`^$`, "", matchres::MATCH, 0, 0),
    138 		(``, "", matchres::MATCH, 0, -1),
    139 		(`abcd`, "abcd", matchres::MATCH, 0, -1),
    140 		(`abc`, "abcd", matchres::MATCH, 0, 3),
    141 		(`bcd`, "abcd", matchres::MATCH, 1, 4),
    142 		(`^abc$`, "abc", matchres::MATCH, 0, -1),
    143 		(`^abc$`, "axc", matchres::NOMATCH, 0, -1),
    144 		// .
    145 		(`^.$`, "x", matchres::MATCH, 0, 1),
    146 		(`^.$`, "y", matchres::MATCH, 0, 1),
    147 		(`^.$`, "", matchres::NOMATCH, 0, 1),
    148 		// +
    149 		(`^a+$`, "a", matchres::MATCH, 0, 1),
    150 		(`^a+$`, "aaa", matchres::MATCH, 0, 3),
    151 		(`^a+$`, "", matchres::NOMATCH, 0, 0),
    152 		(`^(abc)+$`, "abc", matchres::MATCH, 0, 3),
    153 		(`^(abc)+$`, "abcabc", matchres::MATCH, 0, 6),
    154 		(`^(abc)+$`, "", matchres::NOMATCH, 0, 0),
    155 		// *
    156 		(`^a*$`, "", matchres::MATCH, 0, 0),
    157 		(`^a*$`, "aaaa", matchres::MATCH, 0, 4),
    158 		(`^a*$`, "b", matchres::NOMATCH, 0, 0),
    159 		(`^(abc)*$`, "", matchres::MATCH, 0, 0),
    160 		(`^(abc)*$`, "abc", matchres::MATCH, 0, 3),
    161 		(`^(abc)*$`, "abcabc", matchres::MATCH, 0, 6),
    162 		(`^(abc)*$`, "bbb", matchres::NOMATCH, 0, 3),
    163 		// ?
    164 		(`^a?$`, "", matchres::MATCH, 0, 0),
    165 		(`^a?$`, "a", matchres::MATCH, 0, 1),
    166 		(`^a?$`, "b", matchres::NOMATCH, 0, 0),
    167 		(`^(abc)?$`, "", matchres::MATCH, 0, 0),
    168 		(`^(abc)?$`, "abc", matchres::MATCH, 0, 3),
    169 		(`^(abc)?$`, "bbb", matchres::NOMATCH, 0, 0),
    170 		// ^ and $
    171 		(`^a*`, "aaaa", matchres::MATCH, 0, 4),
    172 		(`a*$`, "aaaa", matchres::MATCH, 0, 4),
    173 		(`^a*$`, "aaaa", matchres::MATCH, 0, 4),
    174 		(`a*`, "aaaa", matchres::MATCH, 0, 4),
    175 		(`b*`, "aaaabbbb", matchres::MATCH, 4, 8),
    176 		(`^b*`, "aaaabbbb", matchres::MATCH, 0, 0),
    177 		(`b*$`, "aaaabbbb", matchres::MATCH, 4, 8),
    178 		// (a|b)
    179 		(`^(cafe|b)x$`, "cafex", matchres::MATCH, 0, 5),
    180 		(`^(cafe|b)x$`, "bx", matchres::MATCH, 0, 2),
    181 		(`^(cafe|b)x$`, "XXXx", matchres::NOMATCH, 0, 0),
    182 		(`^(cafe|b)x$`, "bx", matchres::MATCH, 0, 2),
    183 		(
    184 			`^(Privat|Jagd)(haftpflicht|schaden)versicherungs(police|betrag)$`,
    185 			"Jagdhaftpflichtversicherungsbetrag",
    186 			matchres::MATCH, 0, -1
    187 		),
    188 		(
    189 			`^(Privat|Jagd)(haftpflicht|schaden)versicherungs(police|betrag)$`,
    190 			"Jagdhaftpflichtversicherungsbetrug",
    191 			matchres::NOMATCH, 0, -1
    192 		),
    193 		(
    194 			`^(Privat|Jagd)(haftpflicht|schaden)versicherungs(police|betrag)$`,
    195 			"Jagdversicherungspolice",
    196 			matchres::NOMATCH, 0, -1
    197 		),
    198 		(`)`, "", matchres::ERROR, 0, 0),
    199 		// [abc]
    200 		(`^test[abc]$`, "testa", matchres::MATCH, 0, -1),
    201 		(`^test[abc]$`, "testb", matchres::MATCH, 0, -1),
    202 		(`^test[abc]$`, "testc", matchres::MATCH, 0, -1),
    203 		(`^test[abc]$`, "testd", matchres::NOMATCH, 0, -1),
    204 		(`^test[abc]*$`, "test", matchres::MATCH, 0, -1),
    205 		(`^test[abc]*$`, "testa", matchres::MATCH, 0, -1),
    206 		(`^test[abc]*$`, "testaaa", matchres::MATCH, 0, -1),
    207 		(`^test[abc]*$`, "testabc", matchres::MATCH, 0, -1),
    208 		(`^test[abc]?$`, "test", matchres::MATCH, 0, -1),
    209 		(`^test[abc]?$`, "testa", matchres::MATCH, 0, -1),
    210 		(`^test[abc]+$`, "testa", matchres::MATCH, 0, -1),
    211 		(`^test[abc]+$`, "test", matchres::NOMATCH, 0, -1),
    212 		(`^test[]abc]$`, "test]", matchres::MATCH, 0, -1),
    213 		(`^test[[abc]$`, "test[", matchres::MATCH, 0, -1),
    214 		(`^test[^abc]$`, "testd", matchres::MATCH, 0, -1),
    215 		(`^test[^abc]$`, "test!", matchres::MATCH, 0, -1),
    216 		(`^test[^abc]$`, "testa", matchres::NOMATCH, 0, -1),
    217 		(`^test[^abc]$`, "testb", matchres::NOMATCH, 0, -1),
    218 		(`^test[^abc]$`, "testc", matchres::NOMATCH, 0, -1),
    219 		(`^test[^]abc]$`, "test]", matchres::NOMATCH, 0, -1),
    220 		(`^test[^abc[]$`, "test[", matchres::NOMATCH, 0, -1),
    221 		(`^test[^abc]*$`, "testd", matchres::MATCH, 0, -1),
    222 		(`^test[^abc]*$`, "testqqqqq", matchres::MATCH, 0, -1),
    223 		(`^test[^abc]*$`, "test", matchres::MATCH, 0, -1),
    224 		(`^test[^abc]*$`, "testc", matchres::NOMATCH, 0, -1),
    225 		(`^test[^abc]?$`, "test", matchres::MATCH, 0, -1),
    226 		(`^test[^abc]?$`, "testd", matchres::MATCH, 0, -1),
    227 		(`^test[^abc]?$`, "testc", matchres::NOMATCH, 0, -1),
    228 		(`^test[^abc]+$`, "testd", matchres::MATCH, 0, -1),
    229 		(`^test[^abc]+$`, "testddd", matchres::MATCH, 0, -1),
    230 		(`^test[^abc]+$`, "testc", matchres::NOMATCH, 0, -1),
    231 		(`^test[^abc]+$`, "testcccc", matchres::NOMATCH, 0, -1),
    232 		(`^test[a-c]$`, "testa", matchres::MATCH, 0, -1),
    233 		(`^test[a-c]$`, "testb", matchres::MATCH, 0, -1),
    234 		(`^test[a-c]$`, "testc", matchres::MATCH, 0, -1),
    235 		(`^test[a-c]$`, "testd", matchres::NOMATCH, 0, -1),
    236 		(`^test[a-c]$`, "test!", matchres::NOMATCH, 0, -1),
    237 		(`^test[a-c]$`, "test-", matchres::NOMATCH, 0, -1),
    238 		(`^test[-a-c]$`, "test-", matchres::MATCH, 0, -1),
    239 		(`^test[a-c-]$`, "test-", matchres::MATCH, 0, -1),
    240 		(`^test[a-c]*$`, "test", matchres::MATCH, 0, -1),
    241 		(`^test[a-c]*$`, "testa", matchres::MATCH, 0, -1),
    242 		(`^test[a-c]*$`, "testabb", matchres::MATCH, 0, -1),
    243 		(`^test[a-c]*$`, "testddd", matchres::NOMATCH, 0, -1),
    244 		(`^test[a-c]?$`, "test", matchres::MATCH, 0, -1),
    245 		(`^test[a-c]?$`, "testb", matchres::MATCH, 0, -1),
    246 		(`^test[a-c]?$`, "testd", matchres::NOMATCH, 0, -1),
    247 		(`^test[a-c]+$`, "test", matchres::NOMATCH, 0, -1),
    248 		(`^test[a-c]+$`, "testbcbc", matchres::MATCH, 0, -1),
    249 		(`^test[a-c]+$`, "testd", matchres::NOMATCH, 0, -1),
    250 		(`^test[^a-c]$`, "testa", matchres::NOMATCH, 0, -1),
    251 		(`^test[^a-c]$`, "testb", matchres::NOMATCH, 0, -1),
    252 		(`^test[^a-c]$`, "testc", matchres::NOMATCH, 0, -1),
    253 		(`^test[^a-c]$`, "testd", matchres::MATCH, 0, -1),
    254 		(`^test[^a-c]$`, "test!", matchres::MATCH, 0, -1),
    255 		(`^test[^a-c]$`, "test-", matchres::MATCH, 0, -1),
    256 		(`^test[^-a-c]$`, "test-", matchres::NOMATCH, 0, -1),
    257 		(`^test[^a-c-]$`, "test-", matchres::NOMATCH, 0, -1),
    258 		(`^test[^a-c-]*$`, "test", matchres::MATCH, 0, -1),
    259 		(`^test[^a-c-]*$`, "test--", matchres::NOMATCH, 0, -1),
    260 		(`^test[^a-c-]*$`, "testq", matchres::MATCH, 0, -1),
    261 		(`^test[^a-c-]?$`, "test", matchres::MATCH, 0, -1),
    262 		(`^test[^a-c-]?$`, "testq", matchres::MATCH, 0, -1),
    263 		(`^test[^a-c-]?$`, "test-", matchres::NOMATCH, 0, -1),
    264 		(`^test[^a-c-]+$`, "test", matchres::NOMATCH, 0, -1),
    265 		(`^test[^a-c-]+$`, "testb", matchres::NOMATCH, 0, -1),
    266 		(`^test[^a-c-]+$`, "testddd", matchres::MATCH, 0, -1),
    267 		(`([a-z][a-z0-9]*,)+`, "a5,b7,c9,", matchres::MATCH, 0, -1),
    268 		// [:alpha:] etc.
    269 		(`^test[[:alnum:]]+$`, "testaA1", matchres::MATCH, 0, -1),
    270 		(`^test[[:alnum:]]+$`, "testa_1", matchres::NOMATCH, 0, -1),
    271 		(`^test[[:alpha:]]+$`, "testa", matchres::MATCH, 0, -1),
    272 		(`^test[[:alpha:]]+$`, "testa1", matchres::NOMATCH, 0, -1),
    273 		(`^test[[:blank:]]+$`, "testa", matchres::NOMATCH, 0, -1),
    274 		(`^test[[:blank:]]+$`, "test ", matchres::MATCH, 0, -1),
    275 		(`^test[^[:blank:]]+$`, "testx", matchres::MATCH, 0, -1),
    276 		(`^test[[:blank:]]+$`, "test ", matchres::MATCH, 0, -1),
    277 		(`^test[^[:cntrl:]]+$`, "testa", matchres::MATCH, 0, -1),
    278 		(`^test[[:digit:]]$`, "test1", matchres::MATCH, 0, -1),
    279 		(`^test[[:digit:]]$`, "testa", matchres::NOMATCH, 0, -1),
    280 		(`^test[[:graph:]]+$`, "test\t", matchres::NOMATCH, 0, -1),
    281 		(`^test[[:lower:]]+$`, "testa", matchres::MATCH, 0, -1),
    282 		(`^test[[:lower:]]+$`, "testA", matchres::NOMATCH, 0, -1),
    283 		(`^test[[:print:]]+$`, "test\t", matchres::NOMATCH, 0, -1),
    284 		(`^test[[:punct:]]+$`, "testA", matchres::NOMATCH, 0, -1),
    285 		(`^test[[:punct:]]+$`, "test!", matchres::MATCH, 0, -1),
    286 		(`^test[[:space:]]+$`, "test ", matchres::MATCH, 0, -1),
    287 		(`^test[[:upper:]]+$`, "testa", matchres::NOMATCH, 0, -1),
    288 		(`^test[[:upper:]]+$`, "testA", matchres::MATCH, 0, -1),
    289 		(`^test[[:xdigit:]]+$`, "testCAFE", matchres::MATCH, 0, -1),
    290 		// [:alpha:] etc. plus extra characters
    291 		(`^test[[:digit:]][[:alpha:]]$`, "test1a", matchres::MATCH, 0, -1),
    292 		(`^test[[:digit:]][[:alpha:]]$`, "testa1", matchres::NOMATCH, 0, -1),
    293 		(`^test[[:alnum:]!]+$`, "testa!1", matchres::MATCH, 0, -1),
    294 		(`^test[@[:alnum:]!]+$`, "testa!@1", matchres::MATCH, 0, -1),
    295 		// Escaped characters such as \+
    296 		(`^a\+b$`, "a+b", matchres::MATCH, 0, -1),
    297 		(`^a\?b$`, "a?b", matchres::MATCH, 0, -1),
    298 		(`^a\*b$`, "a*b", matchres::MATCH, 0, -1),
    299 		(`^a\^b$`, "a^b", matchres::MATCH, 0, -1),
    300 		(`^a\$b$`, "a$b", matchres::MATCH, 0, -1),
    301 		(`^a\[b$`, "a[b", matchres::MATCH, 0, -1),
    302 		(`^a\]b$`, "a]b", matchres::MATCH, 0, -1),
    303 		(`^a\(b$`, "a(b", matchres::MATCH, 0, -1),
    304 		(`^a\)b$`, "a)b", matchres::MATCH, 0, -1),
    305 		(`^a\|b$`, "a|b", matchres::MATCH, 0, -1),
    306 		(`^a\.b$`, "a.b", matchres::MATCH, 0, -1),
    307 		(`^a\\b$`, "a\\b", matchres::MATCH, 0, -1),
    308 		(`^x(abc)\{,2\}$`, "xabc{,2}", matchres::MATCH, 0, -1),
    309 		(`^x(abc)\{,2\}$`, "xabcabc{,2}", matchres::NOMATCH, 0, -1),
    310 		// {m,n}
    311 		(`^x(abc){2}$`, "xabcabc", matchres::MATCH, 0, -1),
    312 		(`^x(abc){3}$`, "xabcabc", matchres::NOMATCH, 0, -1),
    313 		(`^x(abc){1,2}$`, "xabc", matchres::MATCH, 0, -1),
    314 		(`^x(abc){1,2}$`, "xabcabc", matchres::MATCH, 0, -1),
    315 		(`^x(abc){1,2}$`, "xabcabcabc", matchres::NOMATCH, 0, -1),
    316 		(`^x(abc){,2}$`, "xabc", matchres::MATCH, 0, -1),
    317 		(`^x(abc){,2}$`, "xabcabc", matchres::MATCH, 0, -1),
    318 		(`^x(abc){,2}`, "xabcabcabc", matchres::MATCH, 0, 7),
    319 		(`^x(abc){,2}$`, "xabcabcabc", matchres::NOMATCH, 0, -1),
    320 		(`^x(abc){1,}$`, "xabc", matchres::MATCH, 0, -1),
    321 		(`^x(abc){1,}$`, "xabcabc", matchres::MATCH, 0, -1),
    322 		(`^x(abc){3,}$`, "xabcabc", matchres::NOMATCH, 0, -1),
    323 		(`^x(abc){3,}$`, "xabcabcabc", matchres::MATCH, 0, -1),
    324 		(`^x(abc){2,2}$`, "xabcabc", matchres::MATCH, 0, -1),
    325 		(`^x(abc){2,2}$`, "xabc", matchres::NOMATCH, 0, -1),
    326 		(`^x(abc){2,2}$`, "xabcabcabc", matchres::NOMATCH, 0, -1),
    327 		(`^x(abc){-1,2}$`, "xabcabcabc", matchres::ERROR, 0, -1),
    328 		(`^x(abc){x,2}$`, "xabcabcabc", matchres::ERROR, 0, -1),
    329 		(`^x(abc){0,-2}$`, "xabcabcabc", matchres::ERROR, 0, -1),
    330 		// various
    331 		(
    332 			`^.(1024)?(face)*(1024)*ca*(f+e?cafe)(babe)+$`,
    333 			"X1024facefacecaaaaafffcafebabebabe",
    334 			matchres::MATCH, 0, -1,
    335 		),
    336 		(
    337 			`.(1024)?(face)*(1024)*ca*(f+e?cafe)(babe)+`,
    338 			"X1024facefacecaaaaafffcafebabebabe",
    339 			matchres::MATCH, 0, -1,
    340 		),
    341 		(
    342 			`^.(1024)?(face)*(1024)*ca*(f+e?cafe)(babe)+$`,
    343 			"1024facefacecaaaaafffcafebabebabe",
    344 			matchres::NOMATCH, 0, 0,
    345 		),
    346 		(
    347 			`.(1024)?(face)*(1024)*ca*(f+e?cafe)(babe)+`,
    348 			"1024facefacecaaaaafffcafebabebabe",
    349 			matchres::MATCH, 3, -1,
    350 		),
    351 		(
    352 			`^([a-zA-Z]{1,2}[[:digit:]]{1,2})[[:space:]]*([[:digit:]][a-zA-Z]{2})$`,
    353 			"M15 4QN",
    354 			matchres::MATCH, 0, -1
    355 		),
    356 		(`^[^-a]`, "-bcd", matchres::NOMATCH, 0, 0),
    357 		(`^[-a]`, "-bcd", matchres::MATCH, 0, 1),
    358 		(`[^ac-]`, "bde", matchres::MATCH, 0, 1),
    359 		(`[-ac]`, "foo-de", matchres::MATCH, 3, 4),
    360 		(`[-ac]`, "def", matchres::NOMATCH, 0, 0),
    361 		(`foo[-ac]bar`, "foo-bar", matchres::MATCH, 0, 7),
    362 		(`[ac-]$`, "bde-", matchres::MATCH, 3, 4),
    363 		(`^[A-Za-z_-]+$`, "foo", matchres::MATCH, 0, 3),
    364 		// tests from perl
    365 		(`abc`, "abc", matchres::MATCH, 0, -1),
    366 		(`abc`, "xbc", matchres::NOMATCH, 0, 0),
    367 		(`abc`, "axc", matchres::NOMATCH, 0, 0),
    368 		(`abc`, "abx", matchres::NOMATCH, 0, 0),
    369 		(`abc`, "xabcy", matchres::MATCH, 1, 4),
    370 		(`abc`, "ababc", matchres::MATCH, 2, -1),
    371 		(`ab*c`, "abc", matchres::MATCH, 0, -1),
    372 		(`ab*bc`, "abc", matchres::MATCH, 0, -1),
    373 		(`ab*bc`, "abbc", matchres::MATCH, 0, -1),
    374 		(`ab*bc`, "abbbbc", matchres::MATCH, 0, -1),
    375 		(`ab{0,}bc`, "abbbbc", matchres::MATCH, 0, -1),
    376 		(`ab+bc`, "abbc", matchres::MATCH, 0, -1),
    377 		(`ab+bc`, "abc", matchres::NOMATCH, 0, 0),
    378 		(`ab+bc`, "abq", matchres::NOMATCH, 0, 0),
    379 		(`ab{1,}bc`, "abq", matchres::NOMATCH, 0, 0),
    380 		(`ab+bc`, "abbbbc", matchres::MATCH, 0, -1),
    381 		(`ab{1,}bc`, "abbbbc", matchres::MATCH, 0, -1),
    382 		(`ab{1,3}bc`, "abbbbc", matchres::MATCH, 0, -1),
    383 		(`ab{3,4}bc`, "abbbbc", matchres::MATCH, 0, -1),
    384 		(`ab{4,5}bc`, "abbbbc", matchres::NOMATCH, 0, 0),
    385 		(`ab?bc`, "abbc", matchres::MATCH, 0, -1),
    386 		(`ab?bc`, "abc", matchres::MATCH, 0, -1),
    387 		(`ab{0,1}bc`, "abc", matchres::MATCH, 0, -1),
    388 		(`ab?bc`, "abbbbc", matchres::NOMATCH, 0, 0),
    389 		(`ab?c`, "abc", matchres::MATCH, 0, -1),
    390 		(`ab{0,1}c`, "abc", matchres::MATCH, 0, -1),
    391 		(`^abc$`, "abc", matchres::MATCH, 0, -1),
    392 		(`^abc$`, "abcc", matchres::NOMATCH, 0, 0),
    393 		(`^abc`, "abcc", matchres::MATCH, 0, 3),
    394 		(`^abc$`, "aabc", matchres::NOMATCH, 0, 0),
    395 		(`abc$`, "aabc", matchres::MATCH, 1, -1),
    396 		(`^`, "abc", matchres::MATCH, 0, 0),
    397 		(`$`, "abc", matchres::MATCH, 3, 3),
    398 		(`a.c`, "abc", matchres::MATCH, 0, -1),
    399 		(`a.c`, "axc", matchres::MATCH, 0, -1),
    400 		(`a.*c`, "axyzc", matchres::MATCH, 0, -1),
    401 		(`a.*c`, "axyzd", matchres::NOMATCH, 0, 0),
    402 		(`a[bc]d`, "abc", matchres::NOMATCH, 0, 0),
    403 		(`a[bc]d`, "abd", matchres::MATCH, 0, -1),
    404 		(`a[b-d]e`, "abd", matchres::NOMATCH, 0, 0),
    405 		(`a[b-d]e`, "ace", matchres::MATCH, 0, -1),
    406 		(`a[b-d]`, "aac", matchres::MATCH, 1, -1),
    407 		(`a[-b]`, "a-", matchres::MATCH, 0, -1),
    408 		(`a[b-]`, "a-", matchres::MATCH, 0, -1),
    409 		(`a[b-a]`, "-", matchres::ERROR, 0, 0),
    410 		(`a[]b`, "-", matchres::ERROR, 0, 0),
    411 		(`a[`, "-", matchres::ERROR, 0, 0),
    412 		(`a]`, "a]", matchres::MATCH, 0, -1),
    413 		(`a[]]b`, "a]b", matchres::MATCH, 0, -1),
    414 		(`a[^bc]d`, "aed", matchres::MATCH, 0, -1),
    415 		(`a[^bc]d`, "abd", matchres::NOMATCH, 0, 0),
    416 		(`a[^-b]c`, "adc", matchres::MATCH, 0, -1),
    417 		(`a[^-b]c`, "a-c", matchres::NOMATCH, 0, 0),
    418 		(`a[^]b]c`, "a]c", matchres::NOMATCH, 0, 0),
    419 		(`a[^]b]c`, "adc", matchres::MATCH, 0, -1),
    420 		(`()ef`, "def", matchres::MATCH, 1, -1),
    421 		(`*a`, "-", matchres::ERROR, 0, 0),
    422 		(`(*)b`, "-", matchres::ERROR, 0, 0),
    423 		(`$b`, "b", matchres::ERROR, 0, 0),
    424 		(`a\`, "-", matchres::ERROR, 0, 0),
    425 		(`a\(b`, "a(b", matchres::MATCH, 0, -1),
    426 		(`a\(*b`, "ab", matchres::MATCH, 0, -1),
    427 		(`a\(*b`, "a((b", matchres::MATCH, 0, -1),
    428 		(`a\\b`, `a\b`, matchres::MATCH, 0, -1),
    429 		(`abc)`, "-", matchres::ERROR, 0, 0),
    430 		(`(abc`, "-", matchres::ERROR, 0, 0),
    431 		(`(a)b(c)`, "abc", matchres::MATCH, 0, -1),
    432 		(`a+b+c`, "aabbabc", matchres::MATCH, 4, -1),
    433 		(`a{1,}b{1,}c`, "aabbabc", matchres::MATCH, 4, -1),
    434 		(`a**`, "-", matchres::ERROR, 0, 0),
    435 		(`)(`, "-", matchres::ERROR, 0, 0),
    436 		(`[^ab]*`, "cde", matchres::MATCH, 0, -1),
    437 		(`abc`, "", matchres::NOMATCH, 0, 0),
    438 		(`a*`, "", matchres::MATCH, 0, -1),
    439 		(`([abc])*d`, "abbbcd", matchres::MATCH, 0, -1),
    440 		(`([abc])*bcd`, "abcd", matchres::MATCH, 0, -1),
    441 		(`abcd*efg`, "abcdefg", matchres::MATCH, 0, -1),
    442 		(`ab*`, "xabyabbbz", matchres::MATCH, 1, 3),
    443 		(`ab*`, "xayabbbz", matchres::MATCH, 1, 2),
    444 		(`(ab|cd)e`, "abcde", matchres::MATCH, 2, -1),
    445 		(`[abhgefdc]ij`, "hij", matchres::MATCH, 0, -1),
    446 		(`^(ab|cd)e`, "abcde", matchres::NOMATCH, 0, 0),
    447 		(`(abc|)ef`, "abcdef", matchres::MATCH, 4, -1),
    448 		(`(a|b)c*d`, "abcd", matchres::MATCH, 1, -1),
    449 		(`(ab|ab*)bc`, "abc", matchres::MATCH, 0, -1),
    450 		(`a([bc]*)c*`, "abc", matchres::MATCH, 0, -1),
    451 		(`a([bc]*)(c*d)`, "abcd", matchres::MATCH, 0, -1),
    452 		(`a([bc]+)(c*d)`, "abcd", matchres::MATCH, 0, -1),
    453 		(`a([bc]*)(c+d)`, "abcd", matchres::MATCH, 0, -1),
    454 		(`a[bcd]*dcdcde`, "adcdcde", matchres::MATCH, 0, -1),
    455 		(`a[bcd]+dcdcde`, "adcdcde", matchres::NOMATCH, 0, 0),
    456 		(`(ab|a)b*c`, "abc", matchres::MATCH, 0, -1),
    457 		(`[a-zA-Z_][a-zA-Z0-9_]*`, "alpha", matchres::MATCH, 0, -1),
    458 		(`^a(bc+|b[eh])g|.h$`, "abh", matchres::MATCH, 0, -1),
    459 		(`multiple words of text`, "uh-uh", matchres::NOMATCH, 0, 0),
    460 		(`multiple words`, "multiple words, yeah", matchres::MATCH, 0, 14),
    461 		(`(.*)c(.*)`, "abcde", matchres::MATCH, 0, -1),
    462 		(`\((.*), (.*)\)`, "(a, b)", matchres::MATCH, 0, -1),
    463 		(`[k]`, "ab", matchres::NOMATCH, 0, 0),
    464 		(`a[-]?c`, "ac", matchres::MATCH, 0, -1),
    465 		(`.*d`, "abc\nabd", matchres::MATCH, 0, -1),
    466 		(`(`, "", matchres::ERROR, 0, 0),
    467 		(`(x?)?`, "x", matchres::MATCH, 0, -1),
    468 		(`^*`, "", matchres::ERROR, 0, 0),
    469 		// Submatch handling
    470 		(`(a|ab)(c|bcd)(d*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    471 		(`(a|ab)(bcd|c)(d*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    472 		(`(ab|a)(c|bcd)(d*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    473 		(`(ab|a)(bcd|c)(d*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    474 		(`(a*)(b|abc)(c*)`, "abc", matchres::MATCH, 0, -1), // POSIX: (0,3)(0,1)(1,2)(2,3)
    475 		(`(a*)(abc|b)(c*)`, "abc", matchres::MATCH, 0, -1), // POSIX: (0,3)(0,1)(1,2)(2,3)
    476 		(`(a*)(b|abc)(c*)`, "abc", matchres::MATCH, 0, -1), // POSIX: (0,3)(0,1)(1,2)(2,3)
    477 		(`(a*)(abc|b)(c*)`, "abc", matchres::MATCH, 0, -1), // POSIX: (0,3)(0,1)(1,2)(2,3)
    478 		(`(a|ab)(c|bcd)(d|.*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    479 		(`(a|ab)(bcd|c)(d|.*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    480 		(`(ab|a)(c|bcd)(d|.*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    481 		(`(ab|a)(bcd|c)(d|.*)`, "abcd", matchres::MATCH, 0, -1), // POSIX: (0,4)(0,2)(2,3)(3,4)
    482 		// TODO: whole-expression alternation
    483 		// (`ab|cd`, "abc", matchres::MATCH, 0, -1),
    484 		// (`ab|cd`, "abcd", matchres::MATCH, 0, -1),
    485 		// TODO: multiple alternation
    486 		// (`a|b|c|d|e`, "e", matchres::MATCH, 0, -1),
    487 		// (`(a|b|c|d|e)f`, "ef", matchres::MATCH, 0, -1),
    488 		// TODO: nested capture groups
    489 		(`((a))`, "abc", matchres::ERROR, 0, -1),
    490 		// (`((a))`, "abc", matchres::MATCH, 0, -1),
    491 		// (`((a)(b)c)(d)`, "abcd", matchres::MATCH, 0, -1),
    492 		// (`(bc+d$|ef*g.|h?i(j|k))`, "effgz", matchres::MATCH, 0, -1),
    493 		// (`(bc+d$|ef*g.|h?i(j|k))`, "ij", matchres::MATCH, 0, -1),
    494 		// (`(bc+d$|ef*g.|h?i(j|k))`, "effg", matchres::NOMATCH, 0, 0),
    495 		// (`(bc+d$|ef*g.|h?i(j|k))`, "bcdd", matchres::NOMATCH, 0, 0),
    496 		// (`(bc+d$|ef*g.|h?i(j|k))`, "reffgz", matchres::MATCH, 0, -1),
    497 		// (`((((((((((a))))))))))`, "a", matchres::MATCH, 0, -1),
    498 		// (`(((((((((a)))))))))`, "a", matchres::MATCH, 0, -1),
    499 		// (`(([a-z]+):)?([a-z]+)$`, "smil", matchres::MATCH, 0, -1),
    500 		// (`^((a)c)?(ab)$`, "ab", matchres::MATCH, 0, -1),
    501 		// TODO: multiple simultaneous capture groups
    502 		// (`(a+|b)*`, "ab", matchres::MATCH, 0, -1),
    503 		// (`(a+|b){0,}`, "ab", matchres::MATCH, 0, -1),
    504 		// (`(a+|b)+`, "ab", matchres::MATCH, 0, -1),
    505 		// (`(a+|b){1,}`, "ab", matchres::MATCH, 0, -1),
    506 		// (`(a+|b)?`, "ab", matchres::MATCH, 0, -1),
    507 		// (`(a+|b){0,1}`, "ab", matchres::MATCH, 0, -1),
    508 		// NOTE: character sequences not currently supported
    509 		// (`\0`, "\0", matchres::MATCH, 0, -1),
    510 		// (`[\0a]`, "\0", matchres::MATCH, 0, -1),
    511 		// (`[a\0]`, "\0", matchres::MATCH, 0, -1),
    512 		// (`[^a\0]`, "\0", matchres::NOMATCH, 0, 0),
    513 		// NOTE: octal sequences not currently supported
    514 		// (`[\1]`, "\1", matchres::MATCH, 0, -1),
    515 		// (`\09`, "\0(separate-me)9", matchres::MATCH, 0, -1),
    516 		// (`\141`, "a", matchres::MATCH, 0, -1),
    517 		// (`[\41]`, "!", matchres::MATCH, 0, -1),
    518 		// NOTE: hex sequences not currently supported
    519 		// (`\xff`, "\377", matchres::MATCH, 0, -1),
    520 		// NOTE: non-greedy matching not currently supported
    521 		// (`a.+?c`, "abcabc", matchres::MATCH, 0, -1),
    522 		// (`.*?\S *:`, "xx:", matchres::MATCH, 0, -1),
    523 		// (`a[ ]*?\ (\d+).*`, "a   10", matchres::MATCH, 0, -1),
    524 		// (`a[ ]*?\ (\d+).*`, "a    10", matchres::MATCH, 0, -1),
    525 		// (`"(\\"|[^"])*?"`, `"\""`, matchres::MATCH, 0, -1),
    526 		// (`^.*?$`, "one\ntwo\nthree\n", matchres::NOMATCH, 0, 0),
    527 		// (`a[^>]*?b`, "a>b", matchres::NOMATCH, 0, 0),
    528 		// (`^a*?$`, "foo", matchres::NOMATCH, 0, 0),
    529 		// (`^([ab]*?)(?=(b)?)c`, "abc", matchres::MATCH, 0, -1),
    530 		// (`^([ab]*?)(?!(b))c`, "abc", matchres::MATCH, 0, -1),
    531 		// (`^([ab]*?)(?<!(a))c`, "abc", matchres::MATCH, 0, -1),
    532 	];
    533 
    534 	for (let i = 0z; i < len(cases); i += 1) {
    535 		const expr = cases[i].0;
    536 		const string = cases[i].1;
    537 		const should_match = cases[i].2;
    538 		const start = cases[i].3;
    539 		const end = if (cases[i].4 == -1) {
    540 			yield len(string): int;
    541 		} else {
    542 			yield cases[i].4;
    543 		};
    544 		run_find_case(expr, string, should_match, start, end);
    545 	};
    546 
    547 	const submatch_cases = [
    548 		// literals
    549 		(`aaa ([^ ]*) (...)`, "aaa bbb ccc", matchres::MATCH, 3z,
    550 			["aaa bbb ccc", "bbb", "ccc"]),
    551 	];
    552 
    553 	for (let i = 0z; i < len(submatch_cases); i += 1) {
    554 		const expr = submatch_cases[i].0;
    555 		const string = submatch_cases[i].1;
    556 		const should_match = submatch_cases[i].2;
    557 		const count = submatch_cases[i].3;
    558 		const targets = submatch_cases[i].4;
    559 		run_submatch_case(expr, string, should_match, count, targets);
    560 	};
    561 };
    562 
    563 @test fn findall() void = {
    564 	const cases = [
    565 		(`ab.`, "hello abc and abあ test abq thanks", matchres::MATCH, 3z,
    566 			["abc", "abあ", "abq"]),
    567 	];
    568 
    569 	for (let i = 0z; i < len(cases); i += 1) {
    570 		const expr = cases[i].0;
    571 		const string = cases[i].1;
    572 		const should_match = cases[i].2;
    573 		const count = cases[i].3;
    574 		const targets = cases[i].4;
    575 		run_findall_case(expr, string, should_match, count, targets);
    576 	};
    577 };