authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-07-17 01:34:34-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-09-26 12:35:38-07:00
log9ef02702cebc0de136384f0b7dc9d19aff13f27b
tree1ec9dc016911a4059df088a7094b0e20b7bd931f
parentdb34b1f9ab306ab0568ad6e7409c0c577fb1e0e6

Add `updateFrom*` variants to parse functions


1 files changed, 421 insertions(+), 40 deletions(-)

lib/std/zon/parse.zig+421-40
......@@ -6,6 +6,8 @@
66//! * `fromZoir`/`fromZoirAlloc`
77//! * `fromZoirNode`/`fromZoirNodeAlloc`
88//!
9//! To update an existing values, see the `updateFrom*` variants.
10//!
911//! For lower level control over parsing, see `std.zig.Zoir`.
1012
1113const std = @import("std");
......@@ -23,14 +25,39 @@ const ArrayList = std.ArrayList;
2325/// Rename when adding or removing support for a type.
2426const valid_types = {};
2527
26/// Configuration for the runtime parser.
27pub const Options = struct {
28/// Deprecated.
29pub const Options = FromOptions;
30
31/// Configuration for the `from*` functions.
32pub const FromOptions = struct {
2833 /// If true, unknown fields do not error.
2934 ignore_unknown_fields: bool = false,
3035 /// If true, the parser cleans up partially parsed values on error. This requires some extra
3136 /// bookkeeping, so you may want to turn it off if you don't need this feature (e.g. because
3237 /// you're using arena allocation.)
3338 free_on_error: bool = true,
39
40 fn toParserOptions(self: FromOptions) Parser.Options {
41 return .{
42 .ignore_unknown_fields = self.ignore_unknown_fields,
43 .ignore_missing_fields = false,
44 .free_on_error = self.free_on_error,
45 };
46 }
47};
48
49/// Configuration for the `updateFrom*` variants.
50pub const UpdateFromOptions = struct {
51 /// If true, unknown fields do not error.
52 ignore_unknown_fields: bool = false,
53
54 fn toParserOptions(self: UpdateFromOptions) Parser.Options {
55 return .{
56 .ignore_unknown_fields = self.ignore_unknown_fields,
57 .ignore_missing_fields = true,
58 .free_on_error = false,
59 };
60 }
3461};
3562
3663pub const Error = union(enum) {
......@@ -260,50 +287,92 @@ pub const Diagnostics = struct {
260287///
261288/// An allocator is still required for temporary allocations made during parsing.
262289pub fn fromSlice(
290 /// The type to deserialize into. Only the following types are supported, unsupported types will
291 /// result in a compiler error:
292 ///
293 /// * Optionals of supported types
294 /// * Booleans
295 /// * Integers except for `comptime_int`
296 /// * Floats except for `comptime_float`
297 /// * Enums
298 /// * Single item pointers to supported types
299 /// * Slices of supported types
300 /// * Arrays of supported types
301 /// * Vectors of supported types
302 /// * Structures of supported types
303 /// * Tuples of supported types
304 /// * Unions
263305 T: type,
264306 gpa: Allocator,
265307 source: [:0]const u8,
266308 diag: ?*Diagnostics,
267 options: Options,
309 options: FromOptions,
268310) error{ OutOfMemory, ParseZon }!T {
269311 comptime assert(!requiresAllocator(T));
270312 return fromSliceAlloc(T, gpa, source, diag, options);
271313}
272314
315/// Similar to `fromSlice`, but updates an existing value `current`. Fields not specified by ZON are
316/// left at their current values. Pointers are replaced with new values not mutated in place.
317pub fn updateFromSlice(
318 T: type,
319 gpa: Allocator,
320 current: *T,
321 source: [:0]const u8,
322 diag: ?*Diagnostics,
323 options: UpdateFromOptions,
324) error{ OutOfMemory, ParseZon }!void {
325 comptime assert(!requiresAllocator(T));
326 try updateFromSliceAlloc(T, gpa, current, source, diag, options);
327}
328
273329/// Like `fromSlice`, but the result may contain pointers. To automatically free the result, see
274330/// `free`.
275331pub fn fromSliceAlloc(
276 /// The type to deserialize into. May not be or contain any of the following types:
277 /// * Any comptime-only type, except in a comptime field
278 /// * `type`
279 /// * `void`, except as a union payload
280 /// * `noreturn`
281 /// * An error set/error union
282 /// * A many-pointer or C-pointer
283 /// * An opaque type, including `anyopaque`
284 /// * An async frame type, including `anyframe` and `anyframe->T`
285 /// * A function
286 ///
287 /// All other types are valid. Unsupported types will fail at compile time.
288332 T: type,
289333 gpa: Allocator,
290334 source: [:0]const u8,
291335 diag: ?*Diagnostics,
292 options: Options,
336 options: FromOptions,
293337) error{ OutOfMemory, ParseZon }!T {
338 var result: T = undefined;
339 try parseSliceAlloc(T, &result, gpa, source, diag, options.toParserOptions());
340 return result;
341}
342
343/// Like updateFromSlice`, but the result may contain pointers. Requires an arena for cleanup.
344pub fn updateFromSliceAlloc(
345 T: type,
346 arena: Allocator,
347 current: *T,
348 source: [:0]const u8,
349 diag: ?*Diagnostics,
350 options: UpdateFromOptions,
351) error{ OutOfMemory, ParseZon }!void {
352 try parseSliceAlloc(T, current, arena, source, diag, options.toParserOptions());
353}
354
355fn parseSliceAlloc(
356 T: type,
357 current: *T,
358 arena: Allocator,
359 source: [:0]const u8,
360 diag: ?*Diagnostics,
361 options: Parser.Options,
362) error{ OutOfMemory, ParseZon }!void {
294363 if (diag) |s| s.assertEmpty();
295364
296 var ast = try std.zig.Ast.parse(gpa, source, .{ .mode = .zon });
297 defer if (diag == null) ast.deinit(gpa);
365 var ast = try std.zig.Ast.parse(arena, source, .{ .mode = .zon });
366 defer if (diag == null) ast.deinit(arena);
298367 if (diag) |s| s.ast = ast;
299368
300369 // If there's no diagnostics, Zoir exists for the lifetime of this function. If there is a
301370 // diagnostics, ownership is transferred to diagnostics.
302 var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
303 defer if (diag == null) zoir.deinit(gpa);
371 var zoir = try ZonGen.generate(arena, ast, .{ .parse_str_lits = false });
372 defer if (diag == null) zoir.deinit(arena);
304373
305374 if (diag) |s| s.* = .{};
306 return fromZoirAlloc(T, gpa, ast, zoir, diag, options);
375 try parseZoirAlloc(T, arena, current, ast, zoir, diag, options);
307376}
308377
309378/// Like `fromSlice`, but operates on `Zoir` instead of ZON source.
......@@ -312,7 +381,7 @@ pub fn fromZoir(
312381 ast: Ast,
313382 zoir: Zoir,
314383 diag: ?*Diagnostics,
315 options: Options,
384 options: FromOptions,
316385) error{ParseZon}!T {
317386 comptime assert(!requiresAllocator(T));
318387 var buf: [0]u8 = .{};
......@@ -330,6 +399,32 @@ pub fn fromZoir(
330399 };
331400}
332401
402/// Like `updateSlice`, but operates on `Zoir` instead of ZON source.
403pub fn updateFromZoir(
404 T: type,
405 current: *T,
406 ast: Ast,
407 zoir: Zoir,
408 diag: ?*Diagnostics,
409 options: UpdateFromOptions,
410) error{ParseZon}!void {
411 comptime assert(!requiresAllocator(T));
412 var buf: [0]u8 = .{};
413 var failing_allocator = std.heap.FixedBufferAllocator.init(&buf);
414 updateFromZoirAlloc(
415 T,
416 failing_allocator.allocator(),
417 current,
418 ast,
419 zoir,
420 diag,
421 options,
422 ) catch |err| switch (err) {
423 error.OutOfMemory => unreachable, // Checked by comptime assertion above
424 else => |e| return e,
425 };
426}
427
333428/// Like `fromSliceAlloc`, but operates on `Zoir` instead of ZON source.
334429pub fn fromZoirAlloc(
335430 T: type,
......@@ -337,11 +432,36 @@ pub fn fromZoirAlloc(
337432 ast: Ast,
338433 zoir: Zoir,
339434 diag: ?*Diagnostics,
340 options: Options,
435 options: FromOptions,
341436) error{ OutOfMemory, ParseZon }!T {
342437 return fromZoirNodeAlloc(T, gpa, ast, zoir, .root, diag, options);
343438}
344439
440/// Like updateSliceAlloc`, but operates on `Zoir` instead of ZON source.
441pub fn updateFromZoirAlloc(
442 T: type,
443 arena: Allocator,
444 current: *T,
445 ast: Ast,
446 zoir: Zoir,
447 diag: ?*Diagnostics,
448 options: UpdateFromOptions,
449) error{ OutOfMemory, ParseZon }!void {
450 try parseZoirAlloc(T, arena, current, ast, zoir, diag, options.toParserOptions());
451}
452
453fn parseZoirAlloc(
454 T: type,
455 gpa: Allocator,
456 current: *T,
457 ast: Ast,
458 zoir: Zoir,
459 diag: ?*Diagnostics,
460 options: Parser.Options,
461) error{ OutOfMemory, ParseZon }!void {
462 try parseZoirNodeAlloc(T, gpa, current, ast, zoir, .root, diag, options);
463}
464
345465/// Like `fromZoir`, but the parse starts at `node` instead of root.
346466pub fn fromZoirNode(
347467 T: type,
......@@ -349,7 +469,7 @@ pub fn fromZoirNode(
349469 zoir: Zoir,
350470 node: Zoir.Node.Index,
351471 diag: ?*Diagnostics,
352 options: Options,
472 options: FromOptions,
353473) error{ParseZon}!T {
354474 comptime assert(!requiresAllocator(T));
355475 var buf: [0]u8 = .{};
......@@ -368,6 +488,34 @@ pub fn fromZoirNode(
368488 };
369489}
370490
491/// Like `updateZoir`, but the parse starts at `node` instead of root.
492pub fn updateFromZoirNode(
493 T: type,
494 current: *T,
495 ast: Ast,
496 zoir: Zoir,
497 node: Zoir.Node.Index,
498 diag: ?*Diagnostics,
499 options: UpdateFromOptions,
500) error{ParseZon}!void {
501 comptime assert(!requiresAllocator(T));
502 var buf: [0]u8 = .{};
503 var failing_allocator = std.heap.FixedBufferAllocator.init(&buf);
504 updateFromZoirNodeAlloc(
505 T,
506 failing_allocator.allocator(),
507 current,
508 ast,
509 zoir,
510 node,
511 diag,
512 options,
513 ) catch |err| switch (err) {
514 error.OutOfMemory => unreachable, // Checked by comptime assertion above
515 else => |e| return e,
516 };
517}
518
371519/// Like `fromZoirAlloc`, but the parse starts at `node` instead of root.
372520pub fn fromZoirNodeAlloc(
373521 T: type,
......@@ -376,8 +524,37 @@ pub fn fromZoirNodeAlloc(
376524 zoir: Zoir,
377525 node: Zoir.Node.Index,
378526 diag: ?*Diagnostics,
379 options: Options,
527 options: FromOptions,
380528) error{ OutOfMemory, ParseZon }!T {
529 var out: T = undefined;
530 try parseZoirNodeAlloc(T, gpa, &out, ast, zoir, node, diag, options.toParserOptions());
531 return out;
532}
533
534/// Like `updateZoirNodeAlloc`, but the parse starts at `node` instead of root.
535pub fn updateFromZoirNodeAlloc(
536 T: type,
537 arena: Allocator,
538 current: *T,
539 ast: Ast,
540 zoir: Zoir,
541 node: Zoir.Node.Index,
542 diag: ?*Diagnostics,
543 options: UpdateFromOptions,
544) error{ OutOfMemory, ParseZon }!void {
545 return parseZoirNodeAlloc(T, arena, current, ast, zoir, node, diag, options.toParserOptions());
546}
547
548fn parseZoirNodeAlloc(
549 T: type,
550 gpa: Allocator,
551 out: *T,
552 ast: Ast,
553 zoir: Zoir,
554 node: Zoir.Node.Index,
555 diag: ?*Diagnostics,
556 options: Parser.Options,
557) error{ OutOfMemory, ParseZon }!void {
381558 comptime assert(canParseType(T));
382559
383560 if (diag) |s| {
......@@ -398,9 +575,7 @@ pub fn fromZoirNodeAlloc(
398575 .diag = diag,
399576 };
400577
401 var out: T = undefined;
402 try parser.parseExprInto(node, &out);
403 return out;
578 try parser.parseExprInto(node, out);
404579}
405580
406581/// Frees ZON values.
......@@ -486,7 +661,14 @@ const Parser = struct {
486661 ast: Ast,
487662 zoir: Zoir,
488663 diag: ?*Diagnostics,
489 options: Options,
664 options: Parser.Options,
665 pointer_depth: usize = 0,
666
667 pub const Options = struct {
668 ignore_unknown_fields: bool,
669 free_on_error: bool,
670 ignore_missing_fields: bool,
671 };
490672
491673 const ParseExprError = error{ ParseZon, OutOfMemory };
492674
......@@ -529,6 +711,8 @@ const Parser = struct {
529711 );
530712 errdefer self.gpa.free(slice);
531713 const ptr = &slice[0];
714 self.pointer_depth += 1;
715 defer self.pointer_depth -= 1;
532716 try self.parseExprInnerInto(node, ptr);
533717 out.* = ptr;
534718 },
......@@ -883,17 +1067,19 @@ const Parser = struct {
8831067 }
8841068
8851069 // Fill in any missing default fields
886 inline for (field_found, 0..) |found, i| {
887 if (!found) {
888 const field_attrs = info.field_attrs[i];
889 if (field_attrs.defaultValue(info.field_types[i])) |default| {
890 @field(out, info.field_names[i]) = default;
891 } else {
892 return self.failNodeFmt(
893 node,
894 "missing required field {s}",
895 .{info.field_names[i]},
896 );
1070 if (!self.options.ignore_missing_fields or self.pointer_depth > 0) {
1071 inline for (field_found, 0..) |found, i| {
1072 if (!found) {
1073 const field_attrs = info.field_attrs[i];
1074 if (field_attrs.defaultValue(info.field_types[i])) |default| {
1075 @field(out, info.field_names[i]) = default;
1076 } else {
1077 return self.failNodeFmt(
1078 node,
1079 "missing required field {s}",
1080 .{info.field_names[i]},
1081 );
1082 }
8971083 }
8981084 }
8991085 }
......@@ -3598,3 +3784,198 @@ test "std.zon aligned pointers" {
35983784 defer free(gpa, found);
35993785 try std.testing.expectEqualDeep(expected.inner, found.inner);
36003786}
3787
3788test "std.zon update semantics" {
3789 // Test that the update logic works as expected
3790
3791 var arena_allocator: std.heap.ArenaAllocator = .init(std.testing.allocator);
3792 defer arena_allocator.deinit();
3793 const arena = arena_allocator.allocator();
3794
3795 const Vector = struct { x: f32, y: f32, z: f32 };
3796 const MyStruct = struct {
3797 foo: u32 = 123,
3798 bar: bool,
3799 baz: struct {
3800 a: [2]u32,
3801 b: enum { c, d } = .d,
3802 },
3803 ptr: *Vector,
3804 optional: ?u32 = null,
3805 str: []const u8 = "hello, world",
3806 };
3807
3808 var v: Vector = .{ .x = 1, .y = 2, .z = 3 };
3809 var expected: MyStruct = .{
3810 .foo = 10,
3811 .bar = true,
3812 .baz = .{ .a = .{ 1, 2 }, .b = .c },
3813 .ptr = &v,
3814 };
3815 var found = expected;
3816
3817 try updateFromSliceAlloc(
3818 MyStruct,
3819 arena,
3820 &found,
3821 ".{}",
3822 null,
3823 .{},
3824 );
3825 try std.testing.expectEqual(expected, found);
3826
3827 try updateFromSliceAlloc(
3828 MyStruct,
3829 arena,
3830 &found,
3831 ".{ .bar = false }",
3832 null,
3833 .{},
3834 );
3835 expected.bar = false;
3836 try std.testing.expectEqual(expected, found);
3837
3838 try updateFromSliceAlloc(
3839 MyStruct,
3840 arena,
3841 &found,
3842 ".{ .baz = .{ .a = .{ 2, 4 } } }",
3843 null,
3844 .{},
3845 );
3846 expected.baz.a[0] = 2;
3847 expected.baz.a[1] = 4;
3848 try std.testing.expectEqual(expected, found);
3849
3850 try updateFromSliceAlloc(
3851 MyStruct,
3852 arena,
3853 &found,
3854 ".{ .foo = 11, .baz = .{ .b = .d } }",
3855 null,
3856 .{},
3857 );
3858 expected.foo = 11;
3859 expected.baz.b = .d;
3860 try std.testing.expectEqual(expected, found);
3861
3862 try updateFromSliceAlloc(
3863 MyStruct,
3864 arena,
3865 &found,
3866 ".{ .optional = 10 }",
3867 null,
3868 .{},
3869 );
3870 expected.optional = 10;
3871 try std.testing.expectEqual(expected, found);
3872
3873 try updateFromSliceAlloc(
3874 MyStruct,
3875 arena,
3876 &found,
3877 ".{ .optional = null }",
3878 null,
3879 .{},
3880 );
3881 expected.optional = null;
3882 try std.testing.expectEqual(expected, found);
3883
3884 try updateFromSliceAlloc(
3885 MyStruct,
3886 arena,
3887 &found,
3888 ".{ .ptr = .{ .x = 10, .y = 20, .z = 30 } }",
3889 null,
3890 .{},
3891 );
3892 expected.ptr.x = 10;
3893 expected.ptr.y = 20;
3894 expected.ptr.z = 30;
3895 try std.testing.expectEqualDeep(expected, found);
3896
3897 try updateFromSliceAlloc(
3898 MyStruct,
3899 arena,
3900 &found,
3901 ".{ .str = \"foo\" }",
3902 null,
3903 .{},
3904 );
3905 expected.str = "foo";
3906 try std.testing.expectEqualDeep(expected, found);
3907}
3908
3909test "std.zon variants" {
3910 // Just a smoke test, we don't need to test each variant thoroughly because they all call into
3911 // the same code.
3912
3913 var arena_allocator: std.heap.ArenaAllocator = .init(std.testing.allocator);
3914 defer arena_allocator.deinit();
3915 const arena = arena_allocator.allocator();
3916
3917 const Struct = struct { a: u32, b: u32 };
3918 const start: Struct = .{ .a = 10, .b = 20 };
3919 const end: Struct = .{ .a = 100, .b = 20 };
3920
3921 // Update
3922 {
3923 const ast = try std.zig.Ast.parse(arena, ".{ .a = 100 }", .{ .mode = .zon });
3924 const zoir = try ZonGen.generate(arena, ast, .{ .parse_str_lits = false });
3925
3926 var curr = start;
3927 try updateFromSlice(Struct, arena, &curr, ".{ .a = 100 }", null, .{});
3928 try std.testing.expectEqual(end, curr);
3929
3930 curr = start;
3931 try updateFromSliceAlloc(Struct, arena, &curr, ".{ .a = 100 }", null, .{});
3932 try std.testing.expectEqual(end, curr);
3933
3934 curr = start;
3935 try updateFromZoir(Struct, &curr, ast, zoir, null, .{});
3936 try std.testing.expectEqual(end, curr);
3937
3938 curr = start;
3939 try updateFromZoirAlloc(Struct, arena, &curr, ast, zoir, null, .{});
3940 try std.testing.expectEqual(end, curr);
3941
3942 curr = start;
3943 try updateFromZoirNode(Struct, &curr, ast, zoir, .root, null, .{});
3944 try std.testing.expectEqual(end, curr);
3945
3946 curr = start;
3947 try updateFromZoirNodeAlloc(Struct, arena, &curr, ast, zoir, .root, null, .{});
3948 try std.testing.expectEqual(end, curr);
3949 }
3950
3951 // From
3952 {
3953 const ast = try std.zig.Ast.parse(arena, ".{ .a = 100, .b = 20 }", .{ .mode = .zon });
3954 const zoir = try ZonGen.generate(arena, ast, .{ .parse_str_lits = false });
3955
3956 try std.testing.expectEqual(
3957 end,
3958 try fromSlice(Struct, arena, ".{ .a = 100, .b = 20 }", null, .{}),
3959 );
3960 try std.testing.expectEqual(
3961 end,
3962 try fromSliceAlloc(Struct, arena, ".{ .a = 100, .b = 20 }", null, .{}),
3963 );
3964 try std.testing.expectEqual(
3965 try fromZoir(Struct, ast, zoir, null, .{}),
3966 end,
3967 );
3968 try std.testing.expectEqual(
3969 try fromZoirAlloc(Struct, arena, ast, zoir, null, .{}),
3970 end,
3971 );
3972 try std.testing.expectEqual(
3973 try fromZoirNode(Struct, ast, zoir, .root, null, .{}),
3974 end,
3975 );
3976 try std.testing.expectEqual(
3977 try fromZoirNodeAlloc(Struct, arena, ast, zoir, .root, null, .{}),
3978 end,
3979 );
3980 }
3981}