authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-06-15 11:50:05+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-08 11:48:58+02:00
log9303d4be1029dc42845aafeff38c811a08ac29b0
tree48d423edaad6d529ec6d5f221404f11ded2640e0
parent39023329229279b6eb0ddfd4879d15ad9644375e
signaturelock-open Commit is signed but in an unrecognized format.

grammar: match Parse.zig operator whitespace rules

Parse.zig requires whitespace to be either immediately before and after a binary operator or neither before nor after the operator.

3 files changed, 233 insertions(+), 45 deletions(-)

doc/langref/grammar.peg+25-7
......@@ -74,9 +74,9 @@ SingleAssignExpr <- Expr (AssignOp Expr)?
7474
7575Expr <- BoolOrExpr
7676
77BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
77BoolOrExpr <- BoolAndExpr (OrOp BoolAndExpr)*
7878
79BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
79BoolAndExpr <- CompareExpr (AndOp CompareExpr)*
8080
8181CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
8282
......@@ -253,7 +253,15 @@ AssignOp
253253 / MINUSPERCENTEQUAL
254254 / EQUAL
255255
256CompareOp
256OrOp <- pre_op_white KEYWORD_or post_op_white
257 / !pre_op_white KEYWORD_or !post_op_white
258
259AndOp <- pre_op_white KEYWORD_and post_op_white
260 / !pre_op_white KEYWORD_and !post_op_white
261
262CompareOp <- pre_op_white CompareOpTok post_op_white
263 / !pre_op_white CompareOpTok !post_op_white
264CompareOpTok
257265 <- EQUALEQUAL
258266 / EXCLAMATIONMARKEQUAL
259267 / LARROW
......@@ -261,19 +269,25 @@ CompareOp
261269 / LARROWEQUAL
262270 / RARROWEQUAL
263271
264BitwiseOp
272BitwiseOp <- pre_op_white BitwiseOpTok post_op_white
273 / !pre_op_white BitwiseOpTok !post_op_white
274BitwiseOpTok
265275 <- AMPERSAND
266276 / CARET
267277 / PIPE
268278 / KEYWORD_orelse
269279 / KEYWORD_catch Payload?
270280
271BitShiftOp
281BitShiftOp <- pre_op_white BitShiftOpTok post_op_white
282 / !pre_op_white BitShiftOpTok !post_op_white
283BitShiftOpTok
272284 <- LARROW2
273285 / RARROW2
274286 / LARROW2PIPE
275287
276AdditionOp
288AdditionOp <- pre_op_white AdditionOpTok post_op_white
289 / !pre_op_white AdditionOpTok !post_op_white
290AdditionOpTok
277291 <- PLUS
278292 / MINUS
279293 / PLUS2
......@@ -282,7 +296,9 @@ AdditionOp
282296 / PLUSPIPE
283297 / MINUSPIPE
284298
285MultiplyOp
299MultiplyOp <- pre_op_white MultiplyOpTok post_op_white
300 / !pre_op_white MultiplyOpTok !post_op_white
301MultiplyOpTok
286302 <- PIPE2
287303 / ASTERISK
288304 / SLASH
......@@ -438,6 +454,8 @@ line_comment
438454line_string <- '\\\\' non_control_utf8* newline
439455newline <- "\n" / "\r\n" / eof
440456skip <- ([ \n\t\r] / line_comment)*
457pre_op_white <- ([ \n\t\r] / line_comment)+
458post_op_white <- [ \n\t\r] skip
441459
442460CHAR_LITERAL <- skip ['] char_char* [']
443461
lib/std/zig/parser_fuzz.zig+17-2
......@@ -1,20 +1,35 @@
11const std = @import("../std.zig");
2const Allocator = std.mem.Allocator;
23const Smith = std.testing.Smith;
34
45const oracle = @import("parser_generated_oracle.zig");
56
67test "fuzz std.zig.Ast.parse() against generated oracle" {
7 try std.testing.fuzz({}, checkAgainstOracle, .{});
8 try std.testing.fuzz({}, fuzzAgainstOracle, .{});
89}
910
10fn checkAgainstOracle(_: void, smith: *Smith) !void {
11fn fuzzAgainstOracle(_: void, smith: *Smith) !void {
1112 var buffer: [1 << 14]u8 = undefined;
1213 const len = smith.slice(buffer[0 .. buffer.len - 1]);
1314 buffer[len] = 0;
1415 const source = buffer[0..len :0];
1516
17 try checkAgainstOracle(source);
18}
19
20// Found using AFL++
21test "operator whitespace" {
22 try checkAgainstOracle(
23 \\test {
24 \\ _!= 0;
25 \\}
26 );
27}
28
29fn checkAgainstOracle(source: [:0]const u8) !void {
1630 var fba_buf: [1 << 18]u8 = undefined;
1731 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
32
1833 const ast = try std.zig.Ast.parse(fba.allocator(), source, .zig);
1934
2035 errdefer logBadSource(source, ast);
lib/std/zig/parser_generated_oracle.zig+191-36
......@@ -509,7 +509,7 @@ const Parser = struct {
509509 if (p.parseBoolAndExpr() and blk_1: {
510510 while (blk_3: {
511511 const pos_3 = p.i;
512 if (p.parseKEYWORD_or() and p.parseBoolAndExpr()) break :blk_3 true;
512 if (p.parseOrOp() and p.parseBoolAndExpr()) break :blk_3 true;
513513 p.i = pos_3;
514514 break :blk_3 false;
515515 }) {}
......@@ -525,7 +525,7 @@ const Parser = struct {
525525 if (p.parseCompareExpr() and blk_1: {
526526 while (blk_3: {
527527 const pos_3 = p.i;
528 if (p.parseKEYWORD_and() and p.parseCompareExpr()) break :blk_3 true;
528 if (p.parseAndOp() and p.parseCompareExpr()) break :blk_3 true;
529529 p.i = pos_3;
530530 break :blk_3 false;
531531 }) {}
......@@ -612,40 +612,6 @@ const Parser = struct {
612612 break :blk_0 false;
613613 };
614614 }
615 pub fn parseBinOp(p: *Parser) bool {
616 return blk_0: {
617 const pos_0 = p.i;
618 if (p.parseExpr() and blk_1: {
619 while (blk_3: {
620 const pos_3 = p.i;
621 if (blk_4: {
622 var match_4 = false;
623 while (p.parsewhite()) {
624 match_4 = true;
625 }
626 break :blk_4 match_4;
627 } and blk_4: {
628 if (std.mem.startsWith(u8, p.source[p.i..], "or")) {
629 p.i += 2;
630 break :blk_4 true;
631 }
632 break :blk_4 false;
633 } and blk_4: {
634 var match_4 = false;
635 while (p.parsewhite()) {
636 match_4 = true;
637 }
638 break :blk_4 match_4;
639 } and p.parseBoolAndExpr()) break :blk_3 true;
640 p.i = pos_3;
641 break :blk_3 false;
642 }) {}
643 break :blk_1 true;
644 }) break :blk_0 true;
645 p.i = pos_0;
646 break :blk_0 false;
647 };
648 }
649615 pub fn parsePrefixExpr(p: *Parser) bool {
650616 return blk_0: {
651617 const pos_0 = p.i;
......@@ -1461,7 +1427,67 @@ const Parser = struct {
14611427 break :blk_0 false;
14621428 };
14631429 }
1430 pub fn parseOrOp(p: *Parser) bool {
1431 return blk_0: {
1432 const pos_0 = p.i;
1433 if (p.parsepre_op_white() and p.parseKEYWORD_or() and p.parsepost_op_white()) break :blk_0 true;
1434 p.i = pos_0;
1435 if (blk_1: {
1436 const pos_1 = p.i;
1437 const match_1 = p.parsepre_op_white();
1438 p.i = pos_1;
1439 break :blk_1 !match_1;
1440 } and p.parseKEYWORD_or() and blk_1: {
1441 const pos_1 = p.i;
1442 const match_1 = p.parsepost_op_white();
1443 p.i = pos_1;
1444 break :blk_1 !match_1;
1445 }) break :blk_0 true;
1446 p.i = pos_0;
1447 break :blk_0 false;
1448 };
1449 }
1450 pub fn parseAndOp(p: *Parser) bool {
1451 return blk_0: {
1452 const pos_0 = p.i;
1453 if (p.parsepre_op_white() and p.parseKEYWORD_and() and p.parsepost_op_white()) break :blk_0 true;
1454 p.i = pos_0;
1455 if (blk_1: {
1456 const pos_1 = p.i;
1457 const match_1 = p.parsepre_op_white();
1458 p.i = pos_1;
1459 break :blk_1 !match_1;
1460 } and p.parseKEYWORD_and() and blk_1: {
1461 const pos_1 = p.i;
1462 const match_1 = p.parsepost_op_white();
1463 p.i = pos_1;
1464 break :blk_1 !match_1;
1465 }) break :blk_0 true;
1466 p.i = pos_0;
1467 break :blk_0 false;
1468 };
1469 }
14641470 pub fn parseCompareOp(p: *Parser) bool {
1471 return blk_0: {
1472 const pos_0 = p.i;
1473 if (p.parsepre_op_white() and p.parseCompareOpTok() and p.parsepost_op_white()) break :blk_0 true;
1474 p.i = pos_0;
1475 if (blk_1: {
1476 const pos_1 = p.i;
1477 const match_1 = p.parsepre_op_white();
1478 p.i = pos_1;
1479 break :blk_1 !match_1;
1480 } and p.parseCompareOpTok() and blk_1: {
1481 const pos_1 = p.i;
1482 const match_1 = p.parsepost_op_white();
1483 p.i = pos_1;
1484 break :blk_1 !match_1;
1485 }) break :blk_0 true;
1486 p.i = pos_0;
1487 break :blk_0 false;
1488 };
1489 }
1490 pub fn parseCompareOpTok(p: *Parser) bool {
14651491 return blk_0: {
14661492 const pos_0 = p.i;
14671493 if (p.parseEQUALEQUAL()) break :blk_0 true;
......@@ -1480,6 +1506,26 @@ const Parser = struct {
14801506 };
14811507 }
14821508 pub fn parseBitwiseOp(p: *Parser) bool {
1509 return blk_0: {
1510 const pos_0 = p.i;
1511 if (p.parsepre_op_white() and p.parseBitwiseOpTok() and p.parsepost_op_white()) break :blk_0 true;
1512 p.i = pos_0;
1513 if (blk_1: {
1514 const pos_1 = p.i;
1515 const match_1 = p.parsepre_op_white();
1516 p.i = pos_1;
1517 break :blk_1 !match_1;
1518 } and p.parseBitwiseOpTok() and blk_1: {
1519 const pos_1 = p.i;
1520 const match_1 = p.parsepost_op_white();
1521 p.i = pos_1;
1522 break :blk_1 !match_1;
1523 }) break :blk_0 true;
1524 p.i = pos_0;
1525 break :blk_0 false;
1526 };
1527 }
1528 pub fn parseBitwiseOpTok(p: *Parser) bool {
14831529 return blk_0: {
14841530 const pos_0 = p.i;
14851531 if (p.parseAMPERSAND()) break :blk_0 true;
......@@ -1496,6 +1542,26 @@ const Parser = struct {
14961542 };
14971543 }
14981544 pub fn parseBitShiftOp(p: *Parser) bool {
1545 return blk_0: {
1546 const pos_0 = p.i;
1547 if (p.parsepre_op_white() and p.parseBitShiftOpTok() and p.parsepost_op_white()) break :blk_0 true;
1548 p.i = pos_0;
1549 if (blk_1: {
1550 const pos_1 = p.i;
1551 const match_1 = p.parsepre_op_white();
1552 p.i = pos_1;
1553 break :blk_1 !match_1;
1554 } and p.parseBitShiftOpTok() and blk_1: {
1555 const pos_1 = p.i;
1556 const match_1 = p.parsepost_op_white();
1557 p.i = pos_1;
1558 break :blk_1 !match_1;
1559 }) break :blk_0 true;
1560 p.i = pos_0;
1561 break :blk_0 false;
1562 };
1563 }
1564 pub fn parseBitShiftOpTok(p: *Parser) bool {
14991565 return blk_0: {
15001566 const pos_0 = p.i;
15011567 if (p.parseLARROW2()) break :blk_0 true;
......@@ -1508,6 +1574,26 @@ const Parser = struct {
15081574 };
15091575 }
15101576 pub fn parseAdditionOp(p: *Parser) bool {
1577 return blk_0: {
1578 const pos_0 = p.i;
1579 if (p.parsepre_op_white() and p.parseAdditionOpTok() and p.parsepost_op_white()) break :blk_0 true;
1580 p.i = pos_0;
1581 if (blk_1: {
1582 const pos_1 = p.i;
1583 const match_1 = p.parsepre_op_white();
1584 p.i = pos_1;
1585 break :blk_1 !match_1;
1586 } and p.parseAdditionOpTok() and blk_1: {
1587 const pos_1 = p.i;
1588 const match_1 = p.parsepost_op_white();
1589 p.i = pos_1;
1590 break :blk_1 !match_1;
1591 }) break :blk_0 true;
1592 p.i = pos_0;
1593 break :blk_0 false;
1594 };
1595 }
1596 pub fn parseAdditionOpTok(p: *Parser) bool {
15111597 return blk_0: {
15121598 const pos_0 = p.i;
15131599 if (p.parsePLUS()) break :blk_0 true;
......@@ -1528,6 +1614,26 @@ const Parser = struct {
15281614 };
15291615 }
15301616 pub fn parseMultiplyOp(p: *Parser) bool {
1617 return blk_0: {
1618 const pos_0 = p.i;
1619 if (p.parsepre_op_white() and p.parseMultiplyOpTok() and p.parsepost_op_white()) break :blk_0 true;
1620 p.i = pos_0;
1621 if (blk_1: {
1622 const pos_1 = p.i;
1623 const match_1 = p.parsepre_op_white();
1624 p.i = pos_1;
1625 break :blk_1 !match_1;
1626 } and p.parseMultiplyOpTok() and blk_1: {
1627 const pos_1 = p.i;
1628 const match_1 = p.parsepost_op_white();
1629 p.i = pos_1;
1630 break :blk_1 !match_1;
1631 }) break :blk_0 true;
1632 p.i = pos_0;
1633 break :blk_0 false;
1634 };
1635 }
1636 pub fn parseMultiplyOpTok(p: *Parser) bool {
15311637 return blk_0: {
15321638 const pos_0 = p.i;
15331639 if (p.parsePIPE2()) break :blk_0 true;
......@@ -2378,6 +2484,55 @@ const Parser = struct {
23782484 break :blk_0 false;
23792485 };
23802486 }
2487 pub fn parsepre_op_white(p: *Parser) bool {
2488 return blk_0: {
2489 const pos_0 = p.i;
2490 if (blk_1: {
2491 var match_1 = false;
2492 while (blk_3: {
2493 const pos_3 = p.i;
2494 if ((p.i < p.source.len and switch (p.source[p.i]) {
2495 ' '...' ',
2496 '\n'...'\n',
2497 '\t'...'\t',
2498 '\r'...'\r',
2499 => blk_4: {
2500 p.i += 1;
2501 break :blk_4 true;
2502 },
2503 else => false,
2504 })) break :blk_3 true;
2505 p.i = pos_3;
2506 if (p.parseline_comment()) break :blk_3 true;
2507 p.i = pos_3;
2508 break :blk_3 false;
2509 }) {
2510 match_1 = true;
2511 }
2512 break :blk_1 match_1;
2513 }) break :blk_0 true;
2514 p.i = pos_0;
2515 break :blk_0 false;
2516 };
2517 }
2518 pub fn parsepost_op_white(p: *Parser) bool {
2519 return blk_0: {
2520 const pos_0 = p.i;
2521 if ((p.i < p.source.len and switch (p.source[p.i]) {
2522 ' '...' ',
2523 '\n'...'\n',
2524 '\t'...'\t',
2525 '\r'...'\r',
2526 => blk_1: {
2527 p.i += 1;
2528 break :blk_1 true;
2529 },
2530 else => false,
2531 }) and p.parseskip()) break :blk_0 true;
2532 p.i = pos_0;
2533 break :blk_0 false;
2534 };
2535 }
23812536 pub fn parseCHAR_LITERAL(p: *Parser) bool {
23822537 return blk_0: {
23832538 const pos_0 = p.i;