authorgravatar for mrpaul@aestheticwisdom.comMr. Paul <mrpaul@aestheticwisdom.com> 2021-09-20 15:32:34+07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-09-20 19:54:57-04:00
log380ca2685548ac916c825842033671ac8a97f577
tree0678aff3d664557bdb5ee2c4fbe9b840de55c529
parentabc30f79489b68f6dc0ee4b408c63a8e783215d1

docgen: re-enable syntax checking for code blocks

In a previous commit (f4d3d29), syntax checking for code blocks with the `syntax` type was disabled due to a change in astgen now checking the existence of identifiers. The change in astgen caused some code samples in the language reference to cause compilation errors. This commit updates the code samples in the language reference and re-enables syntax checking. Some code samples have been changed to unchecked syntax blocks using `{#syntax_block#}` when suitable.

2 files changed, 149 insertions(+), 105 deletions(-)

doc/docgen.zig+1-3
......@@ -1222,9 +1222,7 @@ fn genHtml(
12221222
12231223 try printSourceBlock(allocator, tokenizer, out, syntax_block);
12241224
1225 // TODO: remove code.just_check_syntax after updating code samples
1226 // that have stopped working due to a change in the compiler.
1227 if (!do_code_tests or code.just_check_syntax) {
1225 if (!do_code_tests) {
12281226 continue;
12291227 }
12301228
doc/langref.html.in+148-102
......@@ -4771,6 +4771,8 @@ test "parse u64" {
47714771 {#header_open|catch#}
47724772 <p>If you want to provide a default value, you can use the {#syntax#}catch{#endsyntax#} binary operator:</p>
47734773 {#code_begin|syntax#}
4774const parseU64 = @import("error_union_parsing_u64.zig").parseU64;
4775
47744776fn doAThing(str: []u8) void {
47754777 const number = parseU64(str, 10) catch 13;
47764778 _ = number; // ...
......@@ -4786,6 +4788,8 @@ fn doAThing(str: []u8) void {
47864788 <p>Let's say you wanted to return the error if you got one, otherwise continue with the
47874789 function logic:</p>
47884790 {#code_begin|syntax#}
4791const parseU64 = @import("error_union_parsing_u64.zig").parseU64;
4792
47894793fn doAThing(str: []u8) !void {
47904794 const number = parseU64(str, 10) catch |err| return err;
47914795 _ = number; // ...
......@@ -4795,6 +4799,8 @@ fn doAThing(str: []u8) !void {
47954799 There is a shortcut for this. The {#syntax#}try{#endsyntax#} expression:
47964800 </p>
47974801 {#code_begin|syntax#}
4802const parseU64 = @import("error_union_parsing_u64.zig").parseU64;
4803
47984804fn doAThing(str: []u8) !void {
47994805 const number = try parseU64(str, 10);
48004806 _ = number; // ...
......@@ -4810,7 +4816,7 @@ fn doAThing(str: []u8) !void {
48104816 Maybe you know with complete certainty that an expression will never be an error.
48114817 In this case you can do this:
48124818 </p>
4813 {#code_begin|syntax#}const number = parseU64("1234", 10) catch unreachable;{#code_end#}
4819 {#syntax#}const number = parseU64("1234", 10) catch unreachable;{#endsyntax#}
48144820 <p>
48154821 Here we know for sure that "1234" will parse successfully. So we put the
48164822 {#syntax#}unreachable{#endsyntax#} value on the right hand side. {#syntax#}unreachable{#endsyntax#} generates
......@@ -4822,7 +4828,7 @@ fn doAThing(str: []u8) !void {
48224828 Finally, you may want to take a different action for every situation. For that, we combine
48234829 the {#link|if#} and {#link|switch#} expression:
48244830 </p>
4825 {#code_begin|syntax#}
4831 {#syntax_block|zig|handle_all_error_scenarios.zig#}
48264832fn doAThing(str: []u8) void {
48274833 if (parseU64(str, 10)) |number| {
48284834 doSomethingWithNumber(number);
......@@ -4834,7 +4840,7 @@ fn doAThing(str: []u8) void {
48344840 error.InvalidChar => unreachable,
48354841 }
48364842}
4837 {#code_end#}
4843 {#end_syntax_block#}
48384844 {#header_open|errdefer#}
48394845 <p>
48404846 The other component to error handling is defer statements.
......@@ -4845,7 +4851,7 @@ fn doAThing(str: []u8) void {
48454851 <p>
48464852 Example:
48474853 </p>
4848 {#code_begin|syntax#}
4854 {#syntax_block|zig|errdefer_example.zig#}
48494855fn createFoo(param: i32) !Foo {
48504856 const foo = try tryToAllocateFoo();
48514857 // now we have allocated foo. we need to free it if the function fails.
......@@ -4863,7 +4869,7 @@ fn createFoo(param: i32) !Foo {
48634869 // but the defer will run!
48644870 return foo;
48654871}
4866 {#code_end#}
4872 {#end_syntax_block#}
48674873 <p>
48684874 The neat thing about this is that you get robust error handling without
48694875 the verbosity and cognitive overhead of trying to make sure every exit path
......@@ -5132,12 +5138,12 @@ fn bang2() void {
51325138 For the case when no errors are returned, the cost is a single memory write operation, only in the first non-failable function in the call graph that calls a failable function, i.e. when a function returning {#syntax#}void{#endsyntax#} calls a function returning {#syntax#}error{#endsyntax#}.
51335139 This is to initialize this struct in the stack memory:
51345140 </p>
5135 {#code_begin|syntax#}
5141 {#syntax_block|zig|stack_trace_struct.zig#}
51365142pub const StackTrace = struct {
51375143 index: usize,
51385144 instruction_addresses: [N]usize,
51395145};
5140 {#code_end#}
5146 {#end_syntax_block#}
51415147 <p>
51425148 Here, N is the maximum function call depth as determined by call graph analysis. Recursion is ignored and counts for 2.
51435149 </p>
......@@ -5150,13 +5156,13 @@ pub const StackTrace = struct {
51505156 <p>
51515157 When generating the code for a function that returns an error, just before the {#syntax#}return{#endsyntax#} statement (only for the {#syntax#}return{#endsyntax#} statements that return errors), Zig generates a call to this function:
51525158 </p>
5153 {#code_begin|syntax#}
5159 {#syntax_block|zig|zig_return_error_fn.zig#}
51545160// marked as "no-inline" in LLVM IR
51555161fn __zig_return_error(stack_trace: *StackTrace) void {
51565162 stack_trace.instruction_addresses[stack_trace.index] = @returnAddress();
51575163 stack_trace.index = (stack_trace.index + 1) % N;
51585164}
5159 {#code_end#}
5165 {#end_syntax_block#}
51605166 <p>
51615167 The cost is 2 math operations plus some memory reads and writes. The memory accessed is constrained and should remain cached for the duration of the error return bubbling.
51625168 </p>
......@@ -5206,16 +5212,16 @@ const optional_int: ?i32 = 5678;
52065212 Task: call malloc, if the result is null, return null.
52075213 </p>
52085214 <p>C code</p>
5209 <pre><code class="cpp">// malloc prototype included for reference
5215 {#syntax_block|c|call_malloc_in_c.c#}// malloc prototype included for reference
52105216void *malloc(size_t size);
52115217
52125218struct Foo *do_a_thing(void) {
52135219 char *ptr = malloc(1234);
52145220 if (!ptr) return NULL;
52155221 // ...
5216}</code></pre>
5222}{#end_syntax_block#}
52175223 <p>Zig code</p>
5218 {#code_begin|syntax#}
5224 {#syntax_block|zig|call_malloc_from_zig.zig#}
52195225// malloc prototype included for reference
52205226extern fn malloc(size: size_t) ?*u8;
52215227
......@@ -5223,7 +5229,7 @@ fn doAThing() ?*Foo {
52235229 const ptr = malloc(1234) orelse return null;
52245230 _ = ptr; // ...
52255231}
5226 {#code_end#}
5232 {#end_syntax_block#}
52275233 <p>
52285234 Here, Zig is at least as convenient, if not more, than C. And, the type of "ptr"
52295235 is {#syntax#}*u8{#endsyntax#} <em>not</em> {#syntax#}?*u8{#endsyntax#}. The {#syntax#}orelse{#endsyntax#} keyword
......@@ -5233,7 +5239,7 @@ fn doAThing() ?*Foo {
52335239 <p>
52345240 The other form of checking against NULL you might see looks like this:
52355241 </p>
5236 <pre><code class="cpp">void do_a_thing(struct Foo *foo) {
5242 {#syntax_block|c|checking_null_in_c.c#}void do_a_thing(struct Foo *foo) {
52375243 // do some stuff
52385244
52395245 if (foo) {
......@@ -5241,11 +5247,14 @@ fn doAThing() ?*Foo {
52415247 }
52425248
52435249 // do some stuff
5244}</code></pre>
5250}{#end_syntax_block#}
52455251 <p>
52465252 In Zig you can accomplish the same thing:
52475253 </p>
5248 {#code_begin|syntax#}
5254 {#code_begin|syntax|checking_null_in_zig#}
5255const Foo = struct{};
5256fn doSomethingWithFoo(foo: *Foo) void { _ = foo; }
5257
52495258fn doAThing(optional_foo: ?*Foo) void {
52505259 // do some stuff
52515260
......@@ -6111,7 +6120,7 @@ test "perform fn" {
61116120 different code. In this example, the function {#syntax#}performFn{#endsyntax#} is generated three different times,
61126121 for the different values of {#syntax#}prefix_char{#endsyntax#} provided:
61136122 </p>
6114 {#code_begin|syntax#}
6123 {#syntax_block|zig|performFn_1#}
61156124// From the line:
61166125// expect(performFn('t', 1) == 6);
61176126fn performFn(start_value: i32) i32 {
......@@ -6120,8 +6129,8 @@ fn performFn(start_value: i32) i32 {
61206129 result = three(result);
61216130 return result;
61226131}
6123 {#code_end#}
6124 {#code_begin|syntax#}
6132 {#end_syntax_block#}
6133 {#syntax_block|zig|performFn_2#}
61256134// From the line:
61266135// expect(performFn('o', 0) == 1);
61276136fn performFn(start_value: i32) i32 {
......@@ -6129,15 +6138,15 @@ fn performFn(start_value: i32) i32 {
61296138 result = one(result);
61306139 return result;
61316140}
6132 {#code_end#}
6133 {#code_begin|syntax#}
6141 {#end_syntax_block#}
6142 {#syntax_block|zig|performFn_3#}
61346143// From the line:
61356144// expect(performFn('w', 99) == 99);
61366145fn performFn(start_value: i32) i32 {
61376146 var result: i32 = start_value;
61386147 return result;
61396148}
6140 {#code_end#}
6149 {#end_syntax_block#}
61416150 <p>
61426151 Note that this happens even in a debug build; in a release build these generated functions still
61436152 pass through rigorous LLVM optimizations. The important thing to note, however, is not that this
......@@ -6367,11 +6376,11 @@ const Node = struct {
63676376 it works fine.
63686377 </p>
63696378 {#header_close#}
6370 {#header_open|Case Study: printf in Zig#}
6379 {#header_open|Case Study: print in Zig#}
63716380 <p>
6372 Putting all of this together, let's see how {#syntax#}printf{#endsyntax#} works in Zig.
6381 Putting all of this together, let's see how {#syntax#}print{#endsyntax#} works in Zig.
63736382 </p>
6374 {#code_begin|exe|printf#}
6383 {#code_begin|exe|print#}
63756384const print = @import("std").debug.print;
63766385
63776386const a_number: i32 = 1234;
......@@ -6386,67 +6395,84 @@ pub fn main() void {
63866395 Let's crack open the implementation of this and see how it works:
63876396 </p>
63886397
6389 {#code_begin|syntax#}
6390/// Calls print and then flushes the buffer.
6391pub fn printf(self: *Writer, comptime format: []const u8, args: anytype) anyerror!void {
6392 const State = enum {
6393 start,
6394 open_brace,
6395 close_brace,
6396 };
6397
6398 comptime var start_index: usize = 0;
6399 comptime var state = State.start;
6400 comptime var next_arg: usize = 0;
6398 {#code_begin|syntax|poc_print_fn#}
6399const Writer = struct {
6400 /// Calls print and then flushes the buffer.
6401 pub fn print(self: *Writer, comptime format: []const u8, args: anytype) anyerror!void {
6402 const State = enum {
6403 start,
6404 open_brace,
6405 close_brace,
6406 };
64016407
6402 inline for (format) |c, i| {
6403 switch (state) {
6404 State.start => switch (c) {
6405 '{' => {
6406 if (start_index < i) try self.write(format[start_index..i]);
6407 state = State.open_brace;
6408 },
6409 '}' => {
6410 if (start_index < i) try self.write(format[start_index..i]);
6411 state = State.close_brace;
6412 },
6413 else => {},
6414 },
6415 State.open_brace => switch (c) {
6416 '{' => {
6417 state = State.start;
6418 start_index = i;
6408 comptime var start_index: usize = 0;
6409 comptime var state = State.start;
6410 comptime var next_arg: usize = 0;
6411
6412 inline for (format) |c, i| {
6413 switch (state) {
6414 State.start => switch (c) {
6415 '{' => {
6416 if (start_index < i) try self.write(format[start_index..i]);
6417 state = State.open_brace;
6418 },
6419 '}' => {
6420 if (start_index < i) try self.write(format[start_index..i]);
6421 state = State.close_brace;
6422 },
6423 else => {},
64196424 },
6420 '}' => {
6421 try self.printValue(args[next_arg]);
6422 next_arg += 1;
6423 state = State.start;
6424 start_index = i + 1;
6425 State.open_brace => switch (c) {
6426 '{' => {
6427 state = State.start;
6428 start_index = i;
6429 },
6430 '}' => {
6431 try self.printValue(args[next_arg]);
6432 next_arg += 1;
6433 state = State.start;
6434 start_index = i + 1;
6435 },
6436 's' => {
6437 continue;
6438 },
6439 else => @compileError("Unknown format character: " ++ [1]u8{c}),
64256440 },
6426 else => @compileError("Unknown format character: " ++ c),
6427 },
6428 State.close_brace => switch (c) {
6429 '}' => {
6430 state = State.start;
6431 start_index = i;
6441 State.close_brace => switch (c) {
6442 '}' => {
6443 state = State.start;
6444 start_index = i;
6445 },
6446 else => @compileError("Single '}' encountered in format string"),
64326447 },
6433 else => @compileError("Single '}' encountered in format string"),
6434 },
6448 }
64356449 }
6436 }
6437 comptime {
6438 if (args.len != next_arg) {
6439 @compileError("Unused arguments");
6450 comptime {
6451 if (args.len != next_arg) {
6452 @compileError("Unused arguments");
6453 }
6454 if (state != State.start) {
6455 @compileError("Incomplete format string: " ++ format);
6456 }
64406457 }
6441 if (state != State.Start) {
6442 @compileError("Incomplete format string: " ++ format);
6458 if (start_index < format.len) {
6459 try self.write(format[start_index..format.len]);
64436460 }
6461 try self.flush();
64446462 }
6445 if (start_index < format.len) {
6446 try self.write(format[start_index..format.len]);
6463
6464 fn write(self: *Writer, value: []const u8) !void {
6465 _ = self;
6466 _ = value;
64476467 }
6448 try self.flush();
6449}
6468 pub fn printValue(self: *Writer, value: anytype) !void {
6469 _ = self;
6470 _ = value;
6471 }
6472 fn flush(self: *Writer) !void {
6473 _ = self;
6474 }
6475};
64506476 {#code_end#}
64516477 <p>
64526478 This is a proof of concept implementation; the actual function in the standard library has more
......@@ -6459,8 +6485,8 @@ pub fn printf(self: *Writer, comptime format: []const u8, args: anytype) anyerro
64596485 When this function is analyzed from our example code above, Zig partially evaluates the function
64606486 and emits a function that actually looks like this:
64616487 </p>
6462 {#code_begin|syntax#}
6463pub fn printf(self: *Writer, arg0: i32, arg1: []const u8) !void {
6488 {#syntax_block|zig|Emitted print Function#}
6489pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
64646490 try self.write("here is a string: '");
64656491 try self.printValue(arg0);
64666492 try self.write("' here is a number: ");
......@@ -6468,28 +6494,46 @@ pub fn printf(self: *Writer, arg0: i32, arg1: []const u8) !void {
64686494 try self.write("\n");
64696495 try self.flush();
64706496}
6471 {#code_end#}
6497 {#end_syntax_block#}
64726498 <p>
64736499 {#syntax#}printValue{#endsyntax#} is a function that takes a parameter of any type, and does different things depending
64746500 on the type:
64756501 </p>
6476 {#code_begin|syntax#}
6477pub fn printValue(self: *Writer, value: anytype) !void {
6478 switch (@typeInfo(@TypeOf(value))) {
6479 .Int => {
6480 return self.printInt(T, value);
6481 },
6482 .Float => {
6483 return self.printFloat(T, value);
6484 },
6485 else => {
6486 @compileError("Unable to print type '" ++ @typeName(T) ++ "'");
6487 },
6502 {#code_begin|syntax|poc_printValue_fn#}
6503 const Writer = struct {
6504 pub fn printValue(self: *Writer, value: anytype) !void {
6505 switch (@typeInfo(@TypeOf(value))) {
6506 .Int => {
6507 return self.writeInt(value);
6508 },
6509 .Float => {
6510 return self.writeFloat(value);
6511 },
6512 .Pointer => {
6513 return self.write(value);
6514 },
6515 else => {
6516 @compileError("Unable to print type '" ++ @typeName(@TypeOf(value)) ++ "'");
6517 },
6518 }
64886519 }
6489}
6520
6521 fn write(self: *Writer, value: []const u8) !void {
6522 _ = self;
6523 _ = value;
6524 }
6525 fn writeInt(self: *Writer, value: anytype) !void {
6526 _ = self;
6527 _ = value;
6528 }
6529 fn writeFloat(self: *Writer, value: anytype) !void {
6530 _ = self;
6531 _ = value;
6532 }
6533};
64906534 {#code_end#}
64916535 <p>
6492 And now, what happens if we give too many arguments to {#syntax#}printf{#endsyntax#}?
6536 And now, what happens if we give too many arguments to {#syntax#}print{#endsyntax#}?
64936537 </p>
64946538 {#code_begin|test_err|Unused argument in 'here is a string: '{s}' here is a number: {}#}
64956539const print = @import("std").debug.print;
......@@ -6497,7 +6541,7 @@ const print = @import("std").debug.print;
64976541const a_number: i32 = 1234;
64986542const a_string = "foobar";
64996543
6500test "printf too many arguments" {
6544test "print too many arguments" {
65016545 print("here is a string: '{s}' here is a number: {}\n", .{
65026546 a_string,
65036547 a_number,
......@@ -6512,7 +6556,7 @@ test "printf too many arguments" {
65126556 Zig doesn't care whether the format argument is a string literal,
65136557 only that it is a compile-time known value that can be coerced to a {#syntax#}[]const u8{#endsyntax#}:
65146558 </p>
6515 {#code_begin|exe|printf#}
6559 {#code_begin|exe|print#}
65166560const print = @import("std").debug.print;
65176561
65186562const a_number: i32 = 1234;
......@@ -7401,9 +7445,11 @@ fn add(a: i32, b: i32) i32 {
74017445 {#syntax#}@call{#endsyntax#} allows more flexibility than normal function call syntax does. The
74027446 {#syntax#}CallOptions{#endsyntax#} struct is reproduced here:
74037447 </p>
7404 {#code_begin|syntax#}
7448 {#syntax_block|zig|builtin.CallOptions struct#}
74057449pub const CallOptions = struct {
74067450 modifier: Modifier = .auto,
7451
7452 /// Only valid when `Modifier` is `Modifier.async_kw`.
74077453 stack: ?[]align(std.Target.stack_align) u8 = null,
74087454
74097455 pub const Modifier = enum {
......@@ -7440,7 +7486,7 @@ pub const CallOptions = struct {
74407486 compile_time,
74417487 };
74427488};
7443 {#code_end#}
7489 {#end_syntax_block#}
74447490 {#header_close#}
74457491
74467492 {#header_open|@cDefine#}
......@@ -7554,7 +7600,7 @@ fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_v
75547600 This function performs a weak atomic compare exchange operation. It's the equivalent of this code,
75557601 except atomic:
75567602 </p>
7557 {#code_begin|syntax#}
7603 {#syntax_block|zig|cmpxchgWeakButNotAtomic#}
75587604fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T {
75597605 const old_value = ptr.*;
75607606 if (old_value == expected_value and usuallyTrueButSometimesFalse()) {
......@@ -7564,7 +7610,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
75647610 return old_value;
75657611 }
75667612}
7567 {#code_end#}
7613 {#end_syntax_block#}
75687614 <p>
75697615 If you are using cmpxchg in a loop, the sporadic failure will be no problem, and {#syntax#}cmpxchgWeak{#endsyntax#}
75707616 is the better choice, because it can be implemented more efficiently in machine instructions.
......@@ -10159,7 +10205,7 @@ pub fn main() void {
1015910205 This expression is evaluated at compile-time and is used to control
1016010206 preprocessor directives and include multiple <code class="file">.h</code> files:
1016110207 </p>
10162 {#code_begin|syntax#}
10208 {#syntax_block|zig|@cImport Expression#}
1016310209const builtin = @import("builtin");
1016410210
1016510211const c = @cImport({
......@@ -10173,7 +10219,7 @@ const c = @cImport({
1017310219 }
1017410220 @cInclude("soundio.h");
1017510221});
10176 {#code_end#}
10222 {#end_syntax_block#}
1017710223 {#see_also|@cImport|@cInclude|@cDefine|@cUndef|@import#}
1017810224 {#header_close#}
1017910225