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)?...@@ -74,9 +74,9 @@ SingleAssignExpr <- Expr (AssignOp Expr)?
7474
75Expr <- BoolOrExpr75Expr <- BoolOrExpr
7676
77BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*77BoolOrExpr <- BoolAndExpr (OrOp BoolAndExpr)*
7878
79BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*79BoolAndExpr <- CompareExpr (AndOp CompareExpr)*
8080
81CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?81CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
8282
...@@ -253,7 +253,15 @@ AssignOp...@@ -253,7 +253,15 @@ AssignOp
253 / MINUSPERCENTEQUAL253 / MINUSPERCENTEQUAL
254 / EQUAL254 / EQUAL
255255
256CompareOp256OrOp <- 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
257 <- EQUALEQUAL265 <- EQUALEQUAL
258 / EXCLAMATIONMARKEQUAL266 / EXCLAMATIONMARKEQUAL
259 / LARROW267 / LARROW
...@@ -261,19 +269,25 @@ CompareOp...@@ -261,19 +269,25 @@ CompareOp
261 / LARROWEQUAL269 / LARROWEQUAL
262 / RARROWEQUAL270 / RARROWEQUAL
263271
264BitwiseOp272BitwiseOp <- pre_op_white BitwiseOpTok post_op_white
273 / !pre_op_white BitwiseOpTok !post_op_white
274BitwiseOpTok
265 <- AMPERSAND275 <- AMPERSAND
266 / CARET276 / CARET
267 / PIPE277 / PIPE
268 / KEYWORD_orelse278 / KEYWORD_orelse
269 / KEYWORD_catch Payload?279 / KEYWORD_catch Payload?
270280
271BitShiftOp281BitShiftOp <- pre_op_white BitShiftOpTok post_op_white
282 / !pre_op_white BitShiftOpTok !post_op_white
283BitShiftOpTok
272 <- LARROW2284 <- LARROW2
273 / RARROW2285 / RARROW2
274 / LARROW2PIPE286 / LARROW2PIPE
275287
276AdditionOp288AdditionOp <- pre_op_white AdditionOpTok post_op_white
289 / !pre_op_white AdditionOpTok !post_op_white
290AdditionOpTok
277 <- PLUS291 <- PLUS
278 / MINUS292 / MINUS
279 / PLUS2293 / PLUS2
...@@ -282,7 +296,9 @@ AdditionOp...@@ -282,7 +296,9 @@ AdditionOp
282 / PLUSPIPE296 / PLUSPIPE
283 / MINUSPIPE297 / MINUSPIPE
284298
285MultiplyOp299MultiplyOp <- pre_op_white MultiplyOpTok post_op_white
300 / !pre_op_white MultiplyOpTok !post_op_white
301MultiplyOpTok
286 <- PIPE2302 <- PIPE2
287 / ASTERISK303 / ASTERISK
288 / SLASH304 / SLASH
...@@ -438,6 +454,8 @@ line_comment...@@ -438,6 +454,8 @@ line_comment
438line_string <- '\\\\' non_control_utf8* newline454line_string <- '\\\\' non_control_utf8* newline
439newline <- "\n" / "\r\n" / eof455newline <- "\n" / "\r\n" / eof
440skip <- ([ \n\t\r] / line_comment)*456skip <- ([ \n\t\r] / line_comment)*
457pre_op_white <- ([ \n\t\r] / line_comment)+
458post_op_white <- [ \n\t\r] skip
441459
442CHAR_LITERAL <- skip ['] char_char* [']460CHAR_LITERAL <- skip ['] char_char* [']
443461
lib/std/zig/parser_fuzz.zig+17-2
...@@ -1,20 +1,35 @@...@@ -1,20 +1,35 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const Allocator = std.mem.Allocator;
2const Smith = std.testing.Smith;3const Smith = std.testing.Smith;
34
4const oracle = @import("parser_generated_oracle.zig");5const oracle = @import("parser_generated_oracle.zig");
56
6test "fuzz std.zig.Ast.parse() against generated oracle" {7test "fuzz std.zig.Ast.parse() against generated oracle" {
7 try std.testing.fuzz({}, checkAgainstOracle, .{});8 try std.testing.fuzz({}, fuzzAgainstOracle, .{});
8}9}
910
10fn checkAgainstOracle(_: void, smith: *Smith) !void {11fn fuzzAgainstOracle(_: void, smith: *Smith) !void {
11 var buffer: [1 << 14]u8 = undefined;12 var buffer: [1 << 14]u8 = undefined;
12 const len = smith.slice(buffer[0 .. buffer.len - 1]);13 const len = smith.slice(buffer[0 .. buffer.len - 1]);
13 buffer[len] = 0;14 buffer[len] = 0;
14 const source = buffer[0..len :0];15 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 {
16 var fba_buf: [1 << 18]u8 = undefined;30 var fba_buf: [1 << 18]u8 = undefined;
17 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);31 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
32
18 const ast = try std.zig.Ast.parse(fba.allocator(), source, .zig);33 const ast = try std.zig.Ast.parse(fba.allocator(), source, .zig);
1934
20 errdefer logBadSource(source, ast);35 errdefer logBadSource(source, ast);
lib/std/zig/parser_generated_oracle.zig+191-36
...@@ -509,7 +509,7 @@ const Parser = struct {...@@ -509,7 +509,7 @@ const Parser = struct {
509 if (p.parseBoolAndExpr() and blk_1: {509 if (p.parseBoolAndExpr() and blk_1: {
510 while (blk_3: {510 while (blk_3: {
511 const pos_3 = p.i;511 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;
513 p.i = pos_3;513 p.i = pos_3;
514 break :blk_3 false;514 break :blk_3 false;
515 }) {}515 }) {}
...@@ -525,7 +525,7 @@ const Parser = struct {...@@ -525,7 +525,7 @@ const Parser = struct {
525 if (p.parseCompareExpr() and blk_1: {525 if (p.parseCompareExpr() and blk_1: {
526 while (blk_3: {526 while (blk_3: {
527 const pos_3 = p.i;527 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;
529 p.i = pos_3;529 p.i = pos_3;
530 break :blk_3 false;530 break :blk_3 false;
531 }) {}531 }) {}
...@@ -612,40 +612,6 @@ const Parser = struct {...@@ -612,40 +612,6 @@ const Parser = struct {
612 break :blk_0 false;612 break :blk_0 false;
613 };613 };
614 }614 }
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 }
649 pub fn parsePrefixExpr(p: *Parser) bool {615 pub fn parsePrefixExpr(p: *Parser) bool {
650 return blk_0: {616 return blk_0: {
651 const pos_0 = p.i;617 const pos_0 = p.i;
...@@ -1461,7 +1427,67 @@ const Parser = struct {...@@ -1461,7 +1427,67 @@ const Parser = struct {
1461 break :blk_0 false;1427 break :blk_0 false;
1462 };1428 };
1463 }1429 }
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 }
1464 pub fn parseCompareOp(p: *Parser) bool {1470 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 {
1465 return blk_0: {1491 return blk_0: {
1466 const pos_0 = p.i;1492 const pos_0 = p.i;
1467 if (p.parseEQUALEQUAL()) break :blk_0 true;1493 if (p.parseEQUALEQUAL()) break :blk_0 true;
...@@ -1480,6 +1506,26 @@ const Parser = struct {...@@ -1480,6 +1506,26 @@ const Parser = struct {
1480 };1506 };
1481 }1507 }
1482 pub fn parseBitwiseOp(p: *Parser) bool {1508 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 {
1483 return blk_0: {1529 return blk_0: {
1484 const pos_0 = p.i;1530 const pos_0 = p.i;
1485 if (p.parseAMPERSAND()) break :blk_0 true;1531 if (p.parseAMPERSAND()) break :blk_0 true;
...@@ -1496,6 +1542,26 @@ const Parser = struct {...@@ -1496,6 +1542,26 @@ const Parser = struct {
1496 };1542 };
1497 }1543 }
1498 pub fn parseBitShiftOp(p: *Parser) bool {1544 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 {
1499 return blk_0: {1565 return blk_0: {
1500 const pos_0 = p.i;1566 const pos_0 = p.i;
1501 if (p.parseLARROW2()) break :blk_0 true;1567 if (p.parseLARROW2()) break :blk_0 true;
...@@ -1508,6 +1574,26 @@ const Parser = struct {...@@ -1508,6 +1574,26 @@ const Parser = struct {
1508 };1574 };
1509 }1575 }
1510 pub fn parseAdditionOp(p: *Parser) bool {1576 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 {
1511 return blk_0: {1597 return blk_0: {
1512 const pos_0 = p.i;1598 const pos_0 = p.i;
1513 if (p.parsePLUS()) break :blk_0 true;1599 if (p.parsePLUS()) break :blk_0 true;
...@@ -1528,6 +1614,26 @@ const Parser = struct {...@@ -1528,6 +1614,26 @@ const Parser = struct {
1528 };1614 };
1529 }1615 }
1530 pub fn parseMultiplyOp(p: *Parser) bool {1616 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 {
1531 return blk_0: {1637 return blk_0: {
1532 const pos_0 = p.i;1638 const pos_0 = p.i;
1533 if (p.parsePIPE2()) break :blk_0 true;1639 if (p.parsePIPE2()) break :blk_0 true;
...@@ -2378,6 +2484,55 @@ const Parser = struct {...@@ -2378,6 +2484,55 @@ const Parser = struct {
2378 break :blk_0 false;2484 break :blk_0 false;
2379 };2485 };
2380 }2486 }
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 }
2381 pub fn parseCHAR_LITERAL(p: *Parser) bool {2536 pub fn parseCHAR_LITERAL(p: *Parser) bool {
2382 return blk_0: {2537 return blk_0: {
2383 const pos_0 = p.i;2538 const pos_0 = p.i;