| author | |
| committer | |
| log | 27765bb87f1591aac40c80dcd9759fc15d49eebf |
| tree | e9fd5095f87918b7e0eb77f88841dd2cd78c2fa4 |
| parent | e539f7bbebc8016ea05b9203c8be371db8b2e90d |
| signature |
This matches the behavior of Parse.zig
Original motivation: 95b95ea33e4b595d2fc6fbea850694b79f27fe553 files changed, 110 insertions(+), 4 deletions(-)
doc/langref/grammar.peg+8-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | Root <- ContainerMembers skip eof | 1 | Root <- initial_doc_comment? ContainerMembers skip eof |
| 2 | 2 | ||
| 3 | # *** Top level *** | 3 | # *** Top level *** |
| 4 | ContainerMembers <- container_doc_comment? ContainerDeclaration* (ContainerField COMMA)* (ContainerField / ContainerDeclaration*) | 4 | ContainerMembers <- container_doc_comment? ContainerDeclaration* (ContainerField COMMA)* (ContainerField / ContainerDeclaration*) |
| ... | @@ -446,14 +446,20 @@ string_char | ... | @@ -446,14 +446,20 @@ string_char |
| 446 | <- '\\"' | 446 | <- '\\"' |
| 447 | / !["] non_control_utf8 | 447 | / !["] non_control_utf8 |
| 448 | 448 | ||
| 449 | |||
| 450 | # We forbid same-line doc comments to disambiguate the mapping to e.g. struct | ||
| 451 | # fields for documentation generation tooling. However, this requires a special | ||
| 452 | # case in the grammar to handle a doc comment right at the beginning of a file. | ||
| 453 | initial_doc_comment <- (skip '///' non_control_utf8* newline)+ skip !'//!' !KEYWORD_test !KEYWORD_comptime !eof | ||
| 449 | container_doc_comment <- (skip '//!' non_control_utf8* newline)+ | 454 | container_doc_comment <- (skip '//!' non_control_utf8* newline)+ |
| 450 | doc_comment <- (skip '///' non_control_utf8* newline)+ | 455 | doc_comment <- skip_require_newline (skip '///' non_control_utf8* newline)+ |
| 451 | line_comment | 456 | line_comment |
| 452 | <- '//' ![!/] non_control_utf8* newline | 457 | <- '//' ![!/] non_control_utf8* newline |
| 453 | / '////' non_control_utf8* newline | 458 | / '////' non_control_utf8* newline |
| 454 | line_string <- '\\\\' non_control_utf8* newline | 459 | line_string <- '\\\\' non_control_utf8* newline |
| 455 | newline <- "\n" / "\r\n" / eof | 460 | newline <- "\n" / "\r\n" / eof |
| 456 | skip <- ([ \n\t\r] / line_comment)* | 461 | skip <- ([ \n\t\r] / line_comment)* |
| 462 | skip_require_newline <- [ \t\r]* ([\n] / line_comment) skip | ||
| 457 | pre_op_white <- ([ \n\t\r] / line_comment)+ | 463 | pre_op_white <- ([ \n\t\r] / line_comment)+ |
| 458 | post_op_white <- [ \n\t\r] skip | 464 | post_op_white <- [ \n\t\r] skip |
| 459 | 465 |
lib/std/zig/parser_fuzz.zig+12| ... | @@ -25,6 +25,7 @@ test "operator whitespace" { | ... | @@ -25,6 +25,7 @@ test "operator whitespace" { |
| 25 | \\} | 25 | \\} |
| 26 | ); | 26 | ); |
| 27 | } | 27 | } |
| 28 | |||
| 28 | // Found using AFL++ | 29 | // Found using AFL++ |
| 29 | test "doc comment or division operator" { | 30 | test "doc comment or division operator" { |
| 30 | try checkAgainstOracle("0=0///\n0"); | 31 | try checkAgainstOracle("0=0///\n0"); |
| ... | @@ -36,6 +37,17 @@ test "double ampersand" { | ... | @@ -36,6 +37,17 @@ test "double ampersand" { |
| 36 | try checkAgainstOracle("test{&&0;}"); // ok | 37 | try checkAgainstOracle("test{&&0;}"); // ok |
| 37 | } | 38 | } |
| 38 | 39 | ||
| 40 | // Found using AFL++ | ||
| 41 | test "newline required before doc comment not at start of file" { | ||
| 42 | try checkAgainstOracle("0,///\n0"); // error | ||
| 43 | try checkAgainstOracle("///\n0"); // ok | ||
| 44 | try checkAgainstOracle(" ///\n0"); // ok | ||
| 45 | try checkAgainstOracle("\n///\n0"); // ok | ||
| 46 | try checkAgainstOracle("///"); // error | ||
| 47 | try checkAgainstOracle("///\n//!"); | ||
| 48 | try checkAgainstOracle("///\ntest {}"); | ||
| 49 | } | ||
| 50 | |||
| 39 | fn checkAgainstOracle(source: [:0]const u8) !void { | 51 | fn checkAgainstOracle(source: [:0]const u8) !void { |
| 40 | var fba_buf: [1 << 18]u8 = undefined; | 52 | var fba_buf: [1 << 18]u8 = undefined; |
| 41 | var fba: std.heap.FixedBufferAllocator = .init(&fba_buf); | 53 | var fba: std.heap.FixedBufferAllocator = .init(&fba_buf); |
lib/std/zig/parser_generated_oracle.zig+90-2| ... | @@ -16,7 +16,7 @@ const Parser = struct { | ... | @@ -16,7 +16,7 @@ const Parser = struct { |
| 16 | pub fn parseRoot(p: *Parser) bool { | 16 | pub fn parseRoot(p: *Parser) bool { |
| 17 | return blk_0: { | 17 | return blk_0: { |
| 18 | const pos_0 = p.i; | 18 | const pos_0 = p.i; |
| 19 | if (p.parseContainerMembers() and p.parseskip() and p.parseeof()) break :blk_0 true; | 19 | if ((p.parseinitial_doc_comment() or true) and p.parseContainerMembers() and p.parseskip() and p.parseeof()) break :blk_0 true; |
| 20 | p.i = pos_0; | 20 | p.i = pos_0; |
| 21 | break :blk_0 false; | 21 | break :blk_0 false; |
| 22 | }; | 22 | }; |
| ... | @@ -2330,6 +2330,60 @@ const Parser = struct { | ... | @@ -2330,6 +2330,60 @@ const Parser = struct { |
| 2330 | break :blk_0 false; | 2330 | break :blk_0 false; |
| 2331 | }; | 2331 | }; |
| 2332 | } | 2332 | } |
| 2333 | pub fn parseinitial_doc_comment(p: *Parser) bool { | ||
| 2334 | return blk_0: { | ||
| 2335 | const pos_0 = p.i; | ||
| 2336 | if (blk_1: { | ||
| 2337 | var match_1 = false; | ||
| 2338 | while (blk_3: { | ||
| 2339 | const pos_3 = p.i; | ||
| 2340 | if (p.parseskip() and blk_4: { | ||
| 2341 | if (std.mem.startsWith(u8, p.source[p.i..], "///")) { | ||
| 2342 | p.i += 3; | ||
| 2343 | break :blk_4 true; | ||
| 2344 | } | ||
| 2345 | break :blk_4 false; | ||
| 2346 | } and blk_4: { | ||
| 2347 | while (p.parsenon_control_utf8()) {} | ||
| 2348 | break :blk_4 true; | ||
| 2349 | } and p.parsenewline()) break :blk_3 true; | ||
| 2350 | p.i = pos_3; | ||
| 2351 | break :blk_3 false; | ||
| 2352 | }) { | ||
| 2353 | match_1 = true; | ||
| 2354 | } | ||
| 2355 | break :blk_1 match_1; | ||
| 2356 | } and p.parseskip() and blk_1: { | ||
| 2357 | const pos_1 = p.i; | ||
| 2358 | const match_1 = blk_2: { | ||
| 2359 | if (std.mem.startsWith(u8, p.source[p.i..], "//!")) { | ||
| 2360 | p.i += 3; | ||
| 2361 | break :blk_2 true; | ||
| 2362 | } | ||
| 2363 | break :blk_2 false; | ||
| 2364 | }; | ||
| 2365 | p.i = pos_1; | ||
| 2366 | break :blk_1 !match_1; | ||
| 2367 | } and blk_1: { | ||
| 2368 | const pos_1 = p.i; | ||
| 2369 | const match_1 = p.parseKEYWORD_test(); | ||
| 2370 | p.i = pos_1; | ||
| 2371 | break :blk_1 !match_1; | ||
| 2372 | } and blk_1: { | ||
| 2373 | const pos_1 = p.i; | ||
| 2374 | const match_1 = p.parseKEYWORD_comptime(); | ||
| 2375 | p.i = pos_1; | ||
| 2376 | break :blk_1 !match_1; | ||
| 2377 | } and blk_1: { | ||
| 2378 | const pos_1 = p.i; | ||
| 2379 | const match_1 = p.parseeof(); | ||
| 2380 | p.i = pos_1; | ||
| 2381 | break :blk_1 !match_1; | ||
| 2382 | }) break :blk_0 true; | ||
| 2383 | p.i = pos_0; | ||
| 2384 | break :blk_0 false; | ||
| 2385 | }; | ||
| 2386 | } | ||
| 2333 | pub fn parsecontainer_doc_comment(p: *Parser) bool { | 2387 | pub fn parsecontainer_doc_comment(p: *Parser) bool { |
| 2334 | return blk_0: { | 2388 | return blk_0: { |
| 2335 | const pos_0 = p.i; | 2389 | const pos_0 = p.i; |
| ... | @@ -2361,7 +2415,7 @@ const Parser = struct { | ... | @@ -2361,7 +2415,7 @@ const Parser = struct { |
| 2361 | pub fn parsedoc_comment(p: *Parser) bool { | 2415 | pub fn parsedoc_comment(p: *Parser) bool { |
| 2362 | return blk_0: { | 2416 | return blk_0: { |
| 2363 | const pos_0 = p.i; | 2417 | const pos_0 = p.i; |
| 2364 | if (blk_1: { | 2418 | if (p.parseskip_require_newline() and blk_1: { |
| 2365 | var match_1 = false; | 2419 | var match_1 = false; |
| 2366 | while (blk_3: { | 2420 | while (blk_3: { |
| 2367 | const pos_3 = p.i; | 2421 | const pos_3 = p.i; |
| ... | @@ -2496,6 +2550,40 @@ const Parser = struct { | ... | @@ -2496,6 +2550,40 @@ const Parser = struct { |
| 2496 | break :blk_0 false; | 2550 | break :blk_0 false; |
| 2497 | }; | 2551 | }; |
| 2498 | } | 2552 | } |
| 2553 | pub fn parseskip_require_newline(p: *Parser) bool { | ||
| 2554 | return blk_0: { | ||
| 2555 | const pos_0 = p.i; | ||
| 2556 | if (blk_1: { | ||
| 2557 | while ((p.i < p.source.len and switch (p.source[p.i]) { | ||
| 2558 | ' '...' ', | ||
| 2559 | '\t'...'\t', | ||
| 2560 | '\r'...'\r', | ||
| 2561 | => blk_2: { | ||
| 2562 | p.i += 1; | ||
| 2563 | break :blk_2 true; | ||
| 2564 | }, | ||
| 2565 | else => false, | ||
| 2566 | })) {} | ||
| 2567 | break :blk_1 true; | ||
| 2568 | } and blk_2: { | ||
| 2569 | const pos_2 = p.i; | ||
| 2570 | if ((p.i < p.source.len and switch (p.source[p.i]) { | ||
| 2571 | '\n'...'\n', | ||
| 2572 | => blk_3: { | ||
| 2573 | p.i += 1; | ||
| 2574 | break :blk_3 true; | ||
| 2575 | }, | ||
| 2576 | else => false, | ||
| 2577 | })) break :blk_2 true; | ||
| 2578 | p.i = pos_2; | ||
| 2579 | if (p.parseline_comment()) break :blk_2 true; | ||
| 2580 | p.i = pos_2; | ||
| 2581 | break :blk_2 false; | ||
| 2582 | } and p.parseskip()) break :blk_0 true; | ||
| 2583 | p.i = pos_0; | ||
| 2584 | break :blk_0 false; | ||
| 2585 | }; | ||
| 2586 | } | ||
| 2499 | pub fn parsepre_op_white(p: *Parser) bool { | 2587 | pub fn parsepre_op_white(p: *Parser) bool { |
| 2500 | return blk_0: { | 2588 | return blk_0: { |
| 2501 | const pos_0 = p.i; | 2589 | const pos_0 = p.i; |