//! Use `fromSlice`/`fromSliceNoAlloc` to parse a ZON string into a Zig value. Human readable //! errors are written to an out parameter. //! //! If your data is already available in tree form, consider `fromZoir`/`fromZoirNoAlloc`. //! //! To parse into an existing value, see the `updateFrom*` variants. For lower level control over //! parsing, see `std.zig.ZonGen`. For importing ZON at compile time, use `@import`. const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const Ast = std.zig.Ast; const Zoir = std.zig.Zoir; const ZonGen = std.zig.ZonGen; const TokenIndex = std.zig.Ast.TokenIndex; const Base = std.zig.number_literal.Base; const StrLitErr = std.zig.string_literal.Error; const NumberLiteralError = std.zig.number_literal.Error; const assert = std.debug.assert; const ArrayList = std.ArrayList; const ArenaAllocator = std.heap.ArenaAllocator; /// Rename when adding or removing support for a type. const valid_types = {}; /// Errors encountered while parsing ZON. See `log` and `fatal` for reporting errors to the user. pub const Diagnostics = struct { errors: []const Error, /// Log the failure with `std.log.err`. pub fn log(self: *const Diagnostics, path: []const u8) void { std.log.err("{f}", .{self.fmt(path)}); } test log { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const MyType = struct { foo: u32, bar: u32, }; const parsed: MyType = fromSliceNoAlloc(MyType, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = 1, .bar = 2 }", .diagnostics = &diagnostics, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.ParseZon => b: { diagnostics.log("input.zon"); break :b .{ .foo = 0, .bar = 0 }; }, }; _ = parsed; } /// Log the failure with `std.log.err`, and then terminate the process with exit code 1. pub fn fatal(self: *const Diagnostics, path: []const u8) noreturn { std.process.fatal("{f}", .{self.fmt(path)}); } test fatal { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const MyType = struct { foo: u32, bar: u32, }; const parsed = fromSliceNoAlloc(MyType, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = 1, .bar = 2 }", .diagnostics = &diagnostics, }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.ParseZon => diagnostics.fatal("input.zon"), }; _ = parsed; } /// Formats a human readable description of any errors. pub fn fmt(self: *const Diagnostics, path: []const u8) Formatter { return .{ .errors = self.errors, .path = path, }; } fn astLoc( ast: *const Ast, maybe_token: Ast.OptionalTokenIndex, node_or_offset: u32, ) Ast.Location { if (maybe_token.unwrap()) |token| { var loc = ast.tokenLocation(0, token); loc.column += node_or_offset; return loc; } else { const ast_node: Ast.Node.Index = @fromBackingInt(@intCast(node_or_offset)); const token = ast.nodeMainToken(ast_node); return ast.tokenLocation(0, token); } } pub const Formatter = struct { path: []const u8, errors: []const Error, pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void { for (self.errors, 0..) |e, i| { if (i > 0) try w.writeByte('\n'); try w.print("{s}:{d}:{d}: error: {s}", .{ self.path, e.loc.line + 1, e.loc.column + 1, e.msg, }); for (e.notes) |note| { try w.print("\n{s}:{d}:{d}: note: {s}", .{ self.path, note.loc.line + 1, note.loc.column + 1, note.msg, }); } } } }; pub const Error = struct { msg: []const u8, loc: Ast.Location, token: Ast.OptionalTokenIndex, /// If `token == .none`, this is an `Ast.Node.Index`. /// Otherwise, this is a byte offset into `token`. node_or_offset: u32, notes: []Note, const Options = struct { msg: []const u8, token: Ast.OptionalTokenIndex, node_or_offset: u32, notes: []Note, }; fn init(ast: *const Ast, options: Error.Options) Error { return .{ .msg = options.msg, .loc = astLoc(ast, options.token, options.node_or_offset), .token = options.token, .node_or_offset = options.node_or_offset, .notes = options.notes, }; } }; pub const Note = struct { msg: []const u8, loc: Ast.Location, token: Ast.OptionalTokenIndex, /// If `token == .none`, this is an `Ast.Node.Index`. /// Otherwise, this is a byte offset into `token`. node_or_offset: u32, const Options = struct { msg: []const u8, token: Ast.OptionalTokenIndex, node_or_offset: u32, }; fn init(ast: *const Ast, options: Note.Options) @This() { return .{ .msg = options.msg, .loc = astLoc(ast, options.token, options.node_or_offset), .token = options.token, .node_or_offset = options.node_or_offset, }; } }; }; pub const Options = struct { /// Used for scratch allocations. gpa: Allocator, /// Used for allocating diagnostics and results. arena: Allocator, /// The ZON source to parse. source: [:0]const u8, /// Initialized by the callee with a human readable description of any parse errors that /// occured. diagnostics: *Diagnostics, /// If true, unknown fields do not error. ignore_unknown_fields: bool = false, }; /// Parses the given slice as ZON, writing any errors to `options.diagnostics`. pub fn fromSlice(T: type, options: Options) error{ OutOfMemory, ParseZon }!T { var value: T = undefined; try fromSliceInner(T, &value, false, options); return value; } test fromSlice { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); const TextureOptions = struct { const AddressMode = enum { clamp, reflect, wrap, zero }; address_mode: struct { u: AddressMode = .wrap, v: AddressMode = .wrap }, mipmaps: bool = true, premultiply: bool = true, }; const source = \\.{ \\ .address_mode = .{ .u = .clamp }, \\ .mipmaps = false, \\} ; var diagnostics: Diagnostics = undefined; const options = fromSlice(TextureOptions, .{ .gpa = gpa, .arena = arena, .source = source, .diagnostics = &diagnostics, }) catch |err| switch (err) { error.OutOfMemory => return err, error.ParseZon => { diagnostics.log("texture_options.zon"); return err; }, }; try std.testing.expectEqualDeep(TextureOptions{ .address_mode = .{ .u = .clamp, .v = .wrap }, .mipmaps = false, .premultiply = true, }, options); } /// Like `fromSlice` but the result type may not contain pointers, allowing it to outlive /// `options.arena`. pub fn fromSliceNoAlloc(T: type, options: Options) error{ OutOfMemory, ParseZon }!T { comptime assert(!requiresAllocator(T)); return fromSlice(T, options); } /// Similar to `fromSlice`, but updates the existing contents of `value` in place. Fields /// unspecified by ZON are left unchanged. /// /// Assumes that `value` points to a fully initialized value prior to this call. Slices and untagged /// unions are replaced in their entirey when specified. On error, `value` remains valid, but may be /// partially updated. pub fn updateFromSlice( T: type, value: *T, options: Options, ) error{ OutOfMemory, ParseZon }!void { try fromSliceInner(T, value, true, options); } test updateFromSlice { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); const MyTextEditorConfig = struct { indentation: enum { spaces, tabs }, columns: []const u16, dark_mode: enum { auto, dark, light }, theme: []const u8, }; const global_config = \\.{ \\ .indentation = .spaces, \\ .columns = .{ 100 }, \\ .dark_mode = .dark, \\} ; const project_config = \\.{ \\ .indentation = .tabs, \\} ; var config: MyTextEditorConfig = .{ .indentation = .spaces, .columns = &.{}, .dark_mode = .auto, .theme = "default", }; var diagnostics: Diagnostics = undefined; updateFromSlice(MyTextEditorConfig, &config, .{ .gpa = gpa, .arena = arena, .source = global_config, .diagnostics = &diagnostics, }) catch |err| switch (err) { error.OutOfMemory => return err, error.ParseZon => { diagnostics.log("global_config.zon"); return err; }, }; updateFromSlice(MyTextEditorConfig, &config, .{ .gpa = gpa, .arena = arena, .source = project_config, .diagnostics = &diagnostics, }) catch |err| switch (err) { error.OutOfMemory => return err, error.ParseZon => { diagnostics.log("project_config.zon"); return err; }, }; try std.testing.expectEqualDeep(MyTextEditorConfig{ .indentation = .tabs, .columns = &.{100}, .dark_mode = .dark, .theme = "default", }, config); } /// Like `updateFromSlice` but the `T` may not contain pointers, allowing `value` to outlive /// `options.arena`. pub fn updateFromSliceNoAlloc( T: type, value: *T, options: Options, ) error{ OutOfMemory, ParseZon }!void { comptime assert(!requiresAllocator(T)); try fromSliceInner(T, value, true, options); } fn fromSliceInner( T: type, value: *T, initialized: bool, options: Options, ) error{ OutOfMemory, ParseZon }!void { var errors: std.ArrayList(Diagnostics.Error) = .empty; defer options.diagnostics.* = .{ .errors = errors.items }; var ast = try std.zig.Ast.parse(options.gpa, options.source, .{ .mode = .zon }); defer ast.deinit(options.gpa); var zoir = try ZonGen.generate(options.gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(options.gpa); try inner(T, value, initialized, .{ .arena = options.arena, .ast = &ast, .zoir = &zoir, .node = .root, .errors = &errors, .ignore_unknown_fields = options.ignore_unknown_fields, }); } pub const FromZoirOptions = struct { /// Used for allocating diagnostics and results. arena: Allocator, /// The AST for this ZON value. ast: *const Ast, /// The zig object intermediate representation of this ZON value. zoir: *const Zoir, /// The node to start parsing at. node: Zoir.Node.Index = .root, /// Initialized by the callee with a human readable description of any parse errors that /// occured. diagnostics: *Diagnostics, /// If true, unknown fields do not error. ignore_unknown_fields: bool = false, }; /// Like `fromSlice`, but operates on `Zoir` instead of ZON source. pub fn fromZoir(T: type, options: FromZoirOptions) error{ OutOfMemory, ParseZon }!T { var value: T = undefined; try fromZoirInner(T, &value, false, options); return value; } /// Like `fromSliceNoAlloc`, but operates on `Zoir` instead of ZON source. pub fn fromZoirNoAlloc(T: type, options: FromZoirOptions) error{ OutOfMemory, ParseZon }!T { comptime assert(!requiresAllocator(T)); return fromZoir(T, options); } /// Like `updateFromSlice`, but operates on `Zoir` instead of ZON source. pub fn updateFromZoir( T: type, value: *T, options: FromZoirOptions, ) error{ OutOfMemory, ParseZon }!void { return fromZoirInner(T, value, true, options); } /// Like `updateFromSliceNoAlloc`, but operates on `Zoir` instead of ZON source. pub fn updateFromZoirNoAlloc( T: type, value: *T, options: FromZoirOptions, ) error{ OutOfMemory, ParseZon }!void { comptime assert(!requiresAllocator(T)); return updateFromZoir(T, value, options); } fn fromZoirInner( T: type, value: *T, initialized: bool, options: FromZoirOptions, ) error{ OutOfMemory, ParseZon }!void { var errors: std.ArrayList(Diagnostics.Error) = .empty; defer options.diagnostics.* = .{ .errors = errors.items }; return inner(T, value, initialized, .{ .arena = options.arena, .ast = options.ast, .zoir = options.zoir, .node = options.node, .errors = &errors, .ignore_unknown_fields = options.ignore_unknown_fields, }); } const InnerOptions = struct { arena: Allocator, ast: *const Ast, zoir: *const Zoir, node: Zoir.Node.Index = .root, errors: *std.ArrayList(Diagnostics.Error), ignore_unknown_fields: bool, }; fn inner( T: type, value: *T, initialized: bool, options: InnerOptions, ) error{ OutOfMemory, ParseZon }!void { comptime assert(canParseType(T)); if (options.zoir.hasCompileErrors()) { for (options.zoir.compile_errors) |e| { var notes: std.ArrayList(Diagnostics.Note) = .empty; for (e.getNotes(options.zoir)) |note| { try notes.append(options.arena, .init(options.ast, .{ .msg = try options.arena.dupe(u8, note.msg.get(options.zoir)), .token = note.token, .node_or_offset = note.node_or_offset, })); } try options.errors.append(options.arena, .init(options.ast, .{ .msg = try options.arena.dupe(u8, e.msg.get(options.zoir)), .token = e.token, .node_or_offset = e.node_or_offset, .notes = notes.items, })); } return error.ParseZon; } var parser: Parser = .{ .arena = options.arena, .ast = options.ast, .zoir = options.zoir, .ignore_unknown_fields = options.ignore_unknown_fields, .errors = options.errors, }; try parser.parseExprInto(options.node, value, initialized); } fn requiresAllocator(T: type) bool { _ = valid_types; return switch (@typeInfo(T)) { .pointer => true, .array => |array| return array.len > 0 and requiresAllocator(array.child), .@"struct" => |@"struct"| inline for (@"struct".field_types) |field_type| { if (requiresAllocator(field_type)) { break true; } } else false, .@"union" => |@"union"| inline for (@"union".field_types) |field_type| { if (requiresAllocator(field_type)) { break true; } } else false, .optional => |optional| requiresAllocator(optional.child), .vector => |vector| return vector.len > 0 and requiresAllocator(vector.child), else => false, }; } const Parser = struct { arena: Allocator, ast: *const Ast, zoir: *const Zoir, errors: *std.ArrayList(Diagnostics.Error), ignore_unknown_fields: bool, const ParseExprError = error{ ParseZon, OutOfMemory }; fn parseExprInto( self: *Parser, node: Zoir.Node.Index, out: anytype, initialized: bool, ) ParseExprError!void { return self.parseExprIntoInner(node, out, initialized) catch |err| switch (err) { error.WrongType => return self.failExpectedType(@TypeOf(out.*), node), else => |e| return e, }; } const ParseExprInnerError = error{ ParseZon, OutOfMemory, WrongType }; fn parseExprIntoInner( self: *@This(), node: Zoir.Node.Index, out: anytype, initialized: bool, ) ParseExprInnerError!void { if (@TypeOf(out.*) == Zoir.Node.Index) { out.* = node; return; } switch (@typeInfo(@TypeOf(out.*))) { .optional => |optional| if (node.get(self.zoir) == .null) { out.* = null; } else if (initialized and out.* != null) { try self.parseExprIntoInner(node, &out.*.?, true); } else { var some: optional.child = undefined; try self.parseExprIntoInner(node, &some, false); out.* = some; }, .bool => out.* = try self.parseBool(node), .int => out.* = try self.parseInt(@TypeOf(out.*), node), .float => out.* = try self.parseFloat(@TypeOf(out.*), node), .@"enum" => out.* = try self.parseEnumLiteral(@TypeOf(out.*), node), .pointer => |pointer| switch (pointer.size) { .one => { // Get a pointer to update const new = b: { // If the existing value is initialized and non const, use it if (initialized and !pointer.attrs.@"const") break :b out.*; // Otherwise, allocate new memory const new = try self.arena.alignedCreate( pointer.child, if (pointer.attrs.@"align") |a| .fromByteUnits(a) else null, ); // If the original value was initialized, dupe it into the new memory if (initialized) new.* = out.*.*; break :b new; }; try self.parseExprIntoInner( node, new, initialized, ); out.* = new; }, .slice => { // Slices are replaced wholesale since ZON doesn't have syntax for specifying // just a part of a slice. const new = b: { const new = try self.parseSlicePointer(@TypeOf(out.*), node); break :b new; }; out.* = new; }, else => comptime unreachable, }, .array => try self.parseArrayInto(node, out), .vector => try self.parseVectorInto(node, out), .@"struct" => |@"struct"| if (@"struct".is_tuple) try self.parseTupleInto(node, out) else try self.parseStructInto(node, out, initialized), .@"union" => try self.parseUnionInto(node, out, initialized), else => comptime unreachable, } } /// Prints a message of the form `expected T` where T is first converted to a ZON type. For /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just /// referred to by the type of container. fn failExpectedType( self: Parser, T: type, node: Zoir.Node.Index, ) error{ ParseZon, OutOfMemory } { @branchHint(.cold); return self.failExpectedTypeInner(T, false, node); } fn failExpectedTypeInner( self: Parser, T: type, opt: bool, node: Zoir.Node.Index, ) error{ ParseZon, OutOfMemory } { _ = valid_types; switch (@typeInfo(T)) { .@"struct" => |@"struct"| if (@"struct".is_tuple) { if (opt) { return self.failNode(node, "expected optional tuple"); } else { return self.failNode(node, "expected tuple"); } } else { if (opt) { return self.failNode(node, "expected optional struct"); } else { return self.failNode(node, "expected struct"); } }, .@"union" => if (opt) { return self.failNode(node, "expected optional union"); } else { return self.failNode(node, "expected union"); }, .array => if (opt) { return self.failNode(node, "expected optional array"); } else { return self.failNode(node, "expected array"); }, .pointer => |pointer| switch (pointer.size) { .one => return self.failExpectedTypeInner(pointer.child, opt, node), .slice => { if (pointer.child == u8 and pointer.attrs.@"const" and (pointer.sentinel() == null or pointer.sentinel() == 0) and (pointer.attrs.@"align" == null or pointer.attrs.@"align" == 1)) { if (opt) { return self.failNode(node, "expected optional string"); } else { return self.failNode(node, "expected string"); } } else { if (opt) { return self.failNode(node, "expected optional array"); } else { return self.failNode(node, "expected array"); } } }, else => comptime unreachable, }, .vector, .bool, .int, .float => if (opt) { return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)}); } else { return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}); }, .@"enum" => if (opt) { return self.failNode(node, "expected optional enum literal"); } else { return self.failNode(node, "expected enum literal"); }, .optional => |optional| { return self.failExpectedTypeInner(optional.child, true, node); }, else => comptime unreachable, } } fn parseBool(self: Parser, node: Zoir.Node.Index) !bool { switch (node.get(self.zoir)) { .true => return true, .false => return false, else => return error.WrongType, } } fn parseInt(self: Parser, T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), .big => |val| return val.toInt(T) catch self.failCannotRepresent(T, node), }, .float_literal => |val| return intFromFloatExact(T, val) orelse self.failCannotRepresent(T, node), .char_literal => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), else => return error.WrongType, } } fn parseFloat(self: Parser, T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return @floatFromInt(val), .big => |val| return val.toFloat(T, .nearest_even)[0], }, .float_literal => |val| return @floatCast(val), .pos_inf => return std.math.inf(T), .neg_inf => return -std.math.inf(T), .nan => return std.math.nan(T), .char_literal => |val| return @floatFromInt(val), else => return error.WrongType, } } fn parseEnumLiteral(self: Parser, T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .enum_literal => |field_name| { // Create a comptime string map for the enum fields const enum_info = @typeInfo(T).@"enum"; comptime var kvs_list: [enum_info.field_names.len]struct { []const u8, T } = undefined; inline for (enum_info.field_names, enum_info.field_values, 0..) |enum_field_name, enum_field_value, i| { kvs_list[i] = .{ enum_field_name, @fromBackingInt(@intCast(enum_field_value)) }; } const enum_tags = std.StaticStringMap(T).initComptime(kvs_list); // Get the tag if it exists const field_name_str = field_name.get(self.zoir); return enum_tags.get(field_name_str) orelse self.failUnexpected(T, "enum literal", node, null, field_name_str); }, else => return error.WrongType, } } fn parseSlicePointer(self: *Parser, T: type, node: Zoir.Node.Index) ParseExprInnerError!T { switch (node.get(self.zoir)) { .string_literal => return self.parseString(T, node), .array_literal => |nodes| return self.parseSlice(T, nodes), .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }), else => return error.WrongType, } } fn parseString(self: *Parser, T: type, node: Zoir.Node.Index) ParseExprInnerError!T { const ast_node = node.getAstNode(self.zoir); const pointer = @typeInfo(T).pointer; var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); if (pointer.sentinel() != null) size_hint += 1; var aw: std.Io.Writer.Allocating = .init(self.arena); try aw.ensureUnusedCapacity(size_hint); defer aw.deinit(); const result = ZonGen.parseStrLit(self.ast, ast_node, &aw.writer) catch return error.OutOfMemory; switch (result) { .success => {}, .failure => |err| { const token = self.ast.nodeMainToken(ast_node); const raw_string = self.ast.tokenSlice(token); return self.failTokenFmt(token, @intCast(err.offset()), "{f}", .{err.fmt(raw_string)}); }, } if (pointer.child != u8 or pointer.size != .slice or !pointer.attrs.@"const" or (pointer.sentinel() != null and pointer.sentinel() != 0) or (pointer.attrs.@"align" != null and pointer.attrs.@"align" != 1)) { return error.WrongType; } if (pointer.sentinel() != null) { return aw.toOwnedSliceSentinel(0); } else { return aw.toOwnedSlice(); } } fn parseSlice(self: *Parser, T: type, nodes: Zoir.Node.Index.Range) !T { const pointer = @typeInfo(T).pointer; // Make sure we're working with a slice switch (pointer.size) { .slice => {}, .one, .many, .c => comptime unreachable, } // Allocate the slice const slice = try self.arena.allocWithOptions( pointer.child, nodes.len, .fromByteUnitsOptional(pointer.attrs.@"align"), pointer.sentinel(), ); // Parse the elements and return the slice for (slice, 0..) |*elem, i| { try self.parseExprInto(nodes.at(@intCast(i)), elem, false); } return slice; } fn parseArrayInto(self: *Parser, node: Zoir.Node.Index, out: anytype) !void { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, }; const array = @typeInfo(@TypeOf(out.*)).array; // Check if the size matches if (nodes.len < array.len) { return self.failNodeFmt( node, "expected {} array elements; found {}", .{ array.len, nodes.len }, ); } else if (nodes.len > array.len) { return self.failNodeFmt( nodes.at(array.len), "index {} outside of array of length {}", .{ array.len, array.len }, ); } // Parse the elements and return the array for (out, 0..) |*elem, i| { try self.parseExprInto(nodes.at(@intCast(i)), elem, false); } if (array.sentinel()) |s| { out[array.len] = s; } } fn parseVectorInto(self: *Parser, node: Zoir.Node.Index, out: anytype) !void { const vector = @typeInfo(@TypeOf(out.*)).vector; var array: [vector.len]vector.child = undefined; try self.parseArrayInto(node, &array); out.* = array; } fn parseStructInto( self: *Parser, node: Zoir.Node.Index, out: anytype, initialized: bool, ) !void { const repr = node.get(self.zoir); const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { .struct_literal => |nodes| nodes, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, else => return error.WrongType, }; const info = @typeInfo(@TypeOf(out.*)).@"struct"; // Build a map from field name to index. // The special value `comptime_field` indicates that this is actually a comptime field. const comptime_field = std.math.maxInt(usize); const field_indices: std.StaticStringMap(usize) = comptime b: { var kvs_list: [info.field_names.len]struct { []const u8, usize } = undefined; for (&kvs_list, info.field_names, info.field_attrs, 0..) |*kv, field_name, field_attrs, i| { kv.* = .{ field_name, if (field_attrs.@"comptime") comptime_field else i }; } break :b .initComptime(kvs_list); }; // Parse the struct var field_found: [info.field_names.len]bool = @splat(false); for (0..fields.names.len) |i| { const name = fields.names[i].get(self.zoir); const field_index = field_indices.get(name) orelse { if (self.ignore_unknown_fields) continue; return self.failUnexpected(@TypeOf(out.*), "field", node, i, name); }; if (field_index == comptime_field) { return self.failComptimeField(node, i); } // Mark the field as found. Assert that the found array is not zero length to satisfy // the type checker (it can't be since we made it into an iteration of this loop.) if (field_found.len == 0) unreachable; field_found[field_index] = true; switch (field_index) { inline 0...(info.field_names.len - 1) => |j| { if (info.field_attrs[j].@"comptime") unreachable; try self.parseExprInto( fields.vals.at(@intCast(i)), &@field(out, info.field_names[j]), initialized, ); }, else => unreachable, // Can't be out of bounds } } // Fill in any missing default fields if (!initialized) { inline for (field_found, 0..) |found, i| { if (!found) { const field_attrs = info.field_attrs[i]; if (field_attrs.defaultValue(info.field_types[i])) |default| { @field(out, info.field_names[i]) = default; } else { return self.failNodeFmt( node, "missing required field {s}", .{info.field_names[i]}, ); } } } } } fn parseTupleInto(self: *Parser, node: Zoir.Node.Index, out: anytype) !void { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, }; const info = @typeInfo(@TypeOf(out.*)).@"struct"; if (nodes.len > info.field_names.len) { return self.failNodeFmt( nodes.at(info.field_names.len), "index {} outside of tuple length {}", .{ info.field_names.len, info.field_names.len }, ); } inline for (0..info.field_names.len) |i| { // Check if we're out of bounds if (i >= nodes.len) { if (info.field_attrs[i].defaultValue(info.field_types[i])) |default| { @field(out.*, info.field_names[i]) = default; } else { return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); } } else { if (info.field_attrs[i].@"comptime") { return self.failComptimeField(node, i); } else { try self.parseExprInto(nodes.at(i), &out[i], false); } } } } fn parseUnionInto( self: *Parser, node: Zoir.Node.Index, out: anytype, initialized: bool, ) !void { const @"union" = @typeInfo(@TypeOf(out.*)).@"union"; if (@"union".field_names.len == 0) comptime unreachable; // Gather info on the fields const field_indices = b: { comptime var kvs_list: [@"union".field_names.len]struct { []const u8, usize, } = undefined; inline for (@"union".field_names, 0..) |field_name, i| { kvs_list[i] = .{ field_name, i }; } break :b std.StaticStringMap(usize).initComptime(kvs_list); }; // Parse the union switch (node.get(self.zoir)) { .enum_literal => |field_name| { // The union must be tagged for an enum literal to coerce to it if (@"union".tag_type == null) { return error.WrongType; } // Get the index of the named field. We don't use `parseEnum` here as // the order of the enum and the order of the union might not match! const field_index = b: { const field_name_str = field_name.get(self.zoir); break :b field_indices.get(field_name_str) orelse return self.failUnexpected( @TypeOf(out.*), "field", node, null, field_name_str, ); }; // Initialize the union from the given field. switch (field_index) { inline 0...@"union".field_names.len - 1 => |i| { // Fail if the field is not void if (@"union".field_types[i] != void) return self.failNode(node, "expected union"); // Instantiate the union out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], {}); }, else => unreachable, // Can't be out of bounds } }, .struct_literal => |struct_fields| { if (struct_fields.names.len != 1) { return error.WrongType; } // Fill in the field we found const field_name = struct_fields.names[0]; const field_name_str = field_name.get(self.zoir); const field_val = struct_fields.vals.at(0); const field_index = field_indices.get(field_name_str) orelse return self.failUnexpected(@TypeOf(out.*), "field", node, 0, field_name_str); switch (field_index) { inline 0...@"union".field_names.len - 1 => |i| { if (@"union".field_types[i] == void) { return self.failNode(field_val, "expected type 'void'"); } else { const field_initialized = b: { if (!initialized) break :b false; if (@"union".tag_type == null) break :b false; const tag = @field(@"union".tag_type.?, @"union".field_names[i]); if (out.* != tag) break :b false; break :b true; }; if (!field_initialized) { out.* = @unionInit( @TypeOf(out.*), @"union".field_names[i], undefined, ); } try self.parseExprInto( field_val, &@field(out.*, @"union".field_names[i]), field_initialized, ); } }, else => unreachable, // Can't be out of bounds } }, else => return error.WrongType, } } fn failTokenFmt( self: Parser, token: Ast.TokenIndex, offset: u32, comptime fmt: []const u8, args: anytype, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); return self.failTokenFmtNote(token, offset, fmt, args, &.{}); } fn failTokenFmtNote( self: Parser, token: Ast.TokenIndex, offset: u32, comptime fmt: []const u8, args: anytype, notes: []Diagnostics.Note, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); comptime assert(args.len > 0); try self.errors.append(self.arena, .init(self.ast, .{ .token = .fromToken(token), .node_or_offset = offset, .msg = try std.fmt.allocPrint(self.arena, fmt, args), .notes = notes, })); return error.ParseZon; } fn failNodeFmt( self: Parser, node: Zoir.Node.Index, comptime fmt: []const u8, args: anytype, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = self.ast.nodeMainToken(node.getAstNode(self.zoir)); return self.failTokenFmt(token, 0, fmt, args); } fn failToken( self: Parser, failure: Diagnostics.Error, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); try self.errors.append(self.arena, failure); return error.ParseZon; } fn failNode( self: Parser, node: Zoir.Node.Index, msg: []const u8, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = self.ast.nodeMainToken(node.getAstNode(self.zoir)); return self.failToken(.init(self.ast, .{ .token = .fromToken(token), .node_or_offset = 0, .msg = msg, .notes = &.{}, })); } fn failCannotRepresent( self: Parser, T: type, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); } fn failUnexpected( self: Parser, T: type, item_kind: []const u8, node: Zoir.Node.Index, field: ?usize, name: []const u8, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = if (field) |f| b: { var buf: [2]Ast.Node.Index = undefined; const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; const field_node = struct_init.ast.fields[f]; break :b self.ast.firstToken(field_node) - 2; } else self.ast.nodeMainToken(node.getAstNode(self.zoir)); switch (@typeInfo(T)) { inline .@"struct", .@"union", .@"enum" => |info| { var notes: std.ArrayList(Diagnostics.Note) = .empty; if (info.field_names.len == 0) { try notes.append(self.arena, .init(self.ast, .{ .token = .fromToken(token), .node_or_offset = 0, .msg = "none expected", })); } else { var aw: std.Io.Writer.Allocating = .init(self.arena); aw.writer.writeAll("supported: ") catch return error.OutOfMemory; inline for (info.field_names, 0..) |field_name, i| { if (i != 0) aw.writer.writeAll(", ") catch return error.OutOfMemory; aw.writer.print("'{f}'", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true, .allow_underscore = true, })}) catch return error.OutOfMemory; } try notes.append(self.arena, .init(self.ast, .{ .token = .fromToken(token), .node_or_offset = 0, .msg = try aw.toOwnedSlice(), })); } return self.failTokenFmtNote( token, 0, "unexpected {s} '{s}'", .{ item_kind, name }, notes.items, ); }, else => comptime unreachable, } } // Technically we could do this if we were willing to do a deep equal to verify // the value matched, but doing so doesn't seem to support any real use cases // so isn't worth the complexity at the moment. fn failComptimeField( self: Parser, node: Zoir.Node.Index, field: usize, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const ast_node = node.getAstNode(self.zoir); var buf: [2]Ast.Node.Index = undefined; const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: { const field_node = struct_init.ast.fields[field]; break :b self.ast.firstToken(field_node); } else b: { const array_init = self.ast.fullArrayInit(&buf, ast_node).?; const value_node = array_init.ast.elements[field]; break :b self.ast.firstToken(value_node); }; return self.failToken(.init(self.ast, .{ .token = .fromToken(token), .node_or_offset = 0, .msg = "cannot initialize comptime field", .notes = &.{}, })); } }; fn intFromFloatExact(T: type, value: anytype) ?T { const max: @TypeOf(value) = @floatFromInt(std.math.maxInt(T)); const min: @TypeOf(value) = @floatFromInt(std.math.minInt(T)); if (value > max or value < min) { return null; } if (std.math.isNan(value) or std.math.trunc(value) != value) { return null; } return @intFromFloat(value); } fn canParseType(T: type) bool { comptime return canParseTypeInner(T, &.{}, false); } fn canParseTypeInner( T: type, /// Visited structs and unions, to avoid infinite recursion. /// Tracking more types is unnecessary, and a little complex due to optional nesting. visited: []const type, parent_is_optional: bool, ) bool { return switch (@typeInfo(T)) { .bool, .int, .float, .null, .@"enum", => true, .noreturn, .void, .type, .undefined, .error_union, .error_set, .@"fn", .frame, .@"anyframe", .@"opaque", .spirv, .comptime_int, .comptime_float, .enum_literal, => false, .pointer => |pointer| switch (pointer.size) { .one => canParseTypeInner(pointer.child, visited, parent_is_optional), .slice => canParseTypeInner(pointer.child, visited, false), .many, .c => false, }, .optional => |optional| if (parent_is_optional) false else canParseTypeInner(optional.child, visited, true), .array => |array| canParseTypeInner(array.child, visited, false), .vector => |vector| canParseTypeInner(vector.child, visited, false), .@"struct" => |@"struct"| { for (visited) |V| if (T == V) return true; const new_visited = visited ++ .{T}; for (@"struct".field_types, @"struct".field_attrs) |field_type, field_attrs| { if (!field_attrs.@"comptime" and !canParseTypeInner(field_type, new_visited, false)) { return false; } } return true; }, .@"union" => |@"union"| { for (visited) |V| if (T == V) return true; const new_visited = visited ++ .{T}; for (@"union".field_types) |field_type| { if (field_type != void and !canParseTypeInner(field_type, new_visited, false)) { return false; } } return true; }, }; } test "std.zon parse canParseType" { try std.testing.expect(!comptime canParseType(void)); try std.testing.expect(!comptime canParseType(struct { f: [*]u8 })); try std.testing.expect(!comptime canParseType(struct { error{foo} })); try std.testing.expect(!comptime canParseType(union(enum) { a: void, b: [*c]u8 })); try std.testing.expect(!comptime canParseType(@Vector(0, [*c]u8))); try std.testing.expect(!comptime canParseType(*?[*c]u8)); try std.testing.expect(comptime canParseType(enum(u8) { _ })); try std.testing.expect(comptime canParseType(union { foo: void })); try std.testing.expect(comptime canParseType(union(enum) { foo: void })); try std.testing.expect(!comptime canParseType(comptime_float)); try std.testing.expect(!comptime canParseType(comptime_int)); try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null })); try std.testing.expect(!comptime canParseType(@TypeOf(.foo))); try std.testing.expect(comptime canParseType(?u8)); try std.testing.expect(comptime canParseType(*?*u8)); try std.testing.expect(comptime canParseType(?struct { foo: ?struct { ?union(enum) { a: ?@Vector(0, ?*u8), }, ?struct { f: ?[]?u8, }, }, })); try std.testing.expect(!comptime canParseType(??u8)); try std.testing.expect(!comptime canParseType(?*?u8)); try std.testing.expect(!comptime canParseType(*?*?*u8)); try std.testing.expect(!comptime canParseType(struct { x: comptime_int = 2 })); try std.testing.expect(!comptime canParseType(struct { x: comptime_float = 2 })); try std.testing.expect(comptime canParseType(struct { comptime x: @TypeOf(.foo) = .foo })); try std.testing.expect(!comptime canParseType(struct { comptime_int })); const Recursive = struct { foo: ?*@This() }; try std.testing.expect(comptime canParseType(Recursive)); // Make sure we validate nested optional before we early out due to already having seen // a type recursion! try std.testing.expect(!comptime canParseType(struct { add_to_visited: ?u8, retrieve_from_visited: ??u8, })); } test "std.zon requiresAllocator" { try std.testing.expect(!requiresAllocator(u8)); try std.testing.expect(!requiresAllocator(f32)); try std.testing.expect(!requiresAllocator(enum { foo })); try std.testing.expect(!requiresAllocator(struct { f32 })); try std.testing.expect(!requiresAllocator(struct { x: f32 })); try std.testing.expect(!requiresAllocator([0][]const u8)); try std.testing.expect(!requiresAllocator([2]u8)); try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); try std.testing.expect(!requiresAllocator(?f32)); try std.testing.expect(!requiresAllocator(void)); try std.testing.expect(!requiresAllocator(@TypeOf(null))); try std.testing.expect(!requiresAllocator(@Vector(3, u8))); try std.testing.expect(!requiresAllocator(@Vector(0, *const u8))); try std.testing.expect(requiresAllocator([]u8)); try std.testing.expect(requiresAllocator(*struct { u8, u8 })); try std.testing.expect(requiresAllocator([1][]const u8)); try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(?[]u8)); try std.testing.expect(requiresAllocator(@Vector(3, *const u8))); } test "std.zon ast errors" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(struct {}, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1 .y = 2}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:13: error: expected ',' after initializer , "{f}", .{diagnostics.fmt("input.zon")}); } test "std.zon comments" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; try std.testing.expectEqual(@as(u8, 10), fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = \\// comment \\10 // comment \\// comment , .diagnostics = &diagnostics, })); { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = \\//! comment \\10 // comment \\// comment , .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected expression, found 'a document comment' , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon failure/oom formatting" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; try std.testing.expectError(error.OutOfMemory, fromSlice([]const u8, .{ .gpa = .failing, .arena = arena, .source = "\"foo\"", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\ , "{f}", .{diagnostics.fmt("input.zon")}); } test "std.zon fromSlice syntax error" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = ".{", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:3: error: expected expression, found 'EOF' , "{f}", .{diagnostics.fmt("input.zon")}); } test "std.zon optional" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Basic usage { const none = try fromSliceNoAlloc(?u32, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expect(none == null); const some = try fromSliceNoAlloc(?u32, .{ .gpa = gpa, .arena = arena, .source = "1", .diagnostics = &diagnostics, }); try std.testing.expect(some.? == 1); } // Deep free { const none = try fromSlice(?[]const u8, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expect(none == null); const some = try fromSlice(?[]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"foo\"", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings("foo", some.?); } } test "std.zon unions" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Unions { const Tagged = union(enum) { x: f32, @"y y": bool, z, @"z z" }; const Untagged = union { x: f32, @"y y": bool, z: void, @"z z": void }; const tagged_x = try fromSliceNoAlloc(Tagged, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.5}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x); const tagged_y = try fromSliceNoAlloc(Tagged, .{ .gpa = gpa, .arena = arena, .source = ".{.@\"y y\" = true}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y); const tagged_z_shorthand = try fromSliceNoAlloc(Tagged, .{ .gpa = gpa, .arena = arena, .source = ".z", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand); const tagged_zz_shorthand = try fromSliceNoAlloc(Tagged, .{ .gpa = gpa, .arena = arena, .source = ".@\"z z\"", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand); const untagged_x = try fromSliceNoAlloc(Untagged, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.5}", .diagnostics = &diagnostics, }); try std.testing.expect(untagged_x.x == 1.5); const untagged_y = try fromSliceNoAlloc(Untagged, .{ .gpa = gpa, .arena = arena, .source = ".{.@\"y y\" = true}", .diagnostics = &diagnostics, }); try std.testing.expect(untagged_y.@"y y"); } // Deep free { const Union = union(enum) { bar: []const u8, baz: bool }; const noalloc = try fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".{.baz = false}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Union{ .baz = false }, noalloc); const alloc = try fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".{.bar = \"qux\"}", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc); } // Unknown field { const Union = union { x: f32, y: f32 }; try std.testing.expectError( error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".{.z=2.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:4: error: unexpected field 'z' \\input.zon:1:4: note: supported: 'x', 'y' , "{f}", .{diagnostics.fmt("input.zon")}); } // Explicit void field { const Union = union(enum) { x: void }; try std.testing.expectError( error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".{.x=1}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:6: error: expected type 'void' , "{f}", .{diagnostics.fmt("input.zon")}); } // Extra field { const Union = union { x: f32, y: bool }; try std.testing.expectError( error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.5, .y = true}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected union , "{f}", .{diagnostics.fmt("input.zon")}); } // No fields { const Union = union { x: f32, y: bool }; try std.testing.expectError( error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected union , "{f}", .{diagnostics.fmt("input.zon")}); } // Enum literals cannot coerce into untagged unions { const Union = union { x: void }; try std.testing.expectError(error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".x", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:2: error: expected union , "{f}", .{diagnostics.fmt("input.zon")}); } // Unknown field for enum literal coercion { const Union = union(enum) { x: void }; try std.testing.expectError(error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".y", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:2: error: unexpected field 'y' \\input.zon:1:2: note: supported: 'x' , "{f}", .{diagnostics.fmt("input.zon")}); } // Non void field for enum literal coercion { const Union = union(enum) { x: f32 }; try std.testing.expectError(error.ParseZon, fromSlice(Union, .{ .gpa = gpa, .arena = arena, .source = ".x", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:2: error: expected union , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon structs" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Structs (various sizes tested since they're parsed differently) { const Vec0 = struct {}; const Vec1 = struct { x: f32 }; const Vec2 = struct { x: f32, y: f32 }; const Vec3 = struct { x: f32, y: f32, z: f32 }; const zero = try fromSliceNoAlloc(Vec0, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec0{}, zero); const one = try fromSliceNoAlloc(Vec1, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.2}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec1{ .x = 1.2 }, one); const two = try fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.2, .y = 3.4}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two); const three = try fromSliceNoAlloc(Vec3, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.2, .y = 3.4, .z = 5.6}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec3{ .x = 1.2, .y = 3.4, .z = 5.6 }, three); } // Deep free (structs and arrays) { const Foo = struct { bar: []const u8, baz: []const []const u8 }; const parsed = try fromSlice(Foo, .{ .gpa = gpa, .arena = arena, .source = ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); } // Unknown field { const Vec2 = struct { x: f32, y: f32 }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x=1.5, .z=2.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:12: error: unexpected field 'z' \\input.zon:1:12: note: supported: 'x', 'y' , "{f}", .{diagnostics.fmt("input.zon")}); } // Duplicate field { const Vec2 = struct { x: f32, y: f32 }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x=1.5, .x=2.5, .x=3.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:4: error: duplicate struct field name \\input.zon:1:12: note: duplicate name here , "{f}", .{diagnostics.fmt("input.zon")}); } // Ignore unknown fields { const Vec2 = struct { x: f32, y: f32 = 2.0 }; const parsed = try fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{ .x = 1.0, .z = 3.0 }", .ignore_unknown_fields = true, .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec2{ .x = 1.0, .y = 2.0 }, parsed); } // Unknown field when struct has no fields (regression test) { const Vec2 = struct {}; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x=1.5, .z=2.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:4: error: unexpected field 'x' \\input.zon:1:4: note: none expected , "{f}", .{diagnostics.fmt("input.zon")}); } // Missing field { const Vec2 = struct { x: f32, y: f32 }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x=1.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: missing required field y , "{f}", .{diagnostics.fmt("input.zon")}); } // Default field { const Vec2 = struct { x: f32, y: f32 = 1.5 }; const parsed = try fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.2}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); } // Comptime field { const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; const parsed = try fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.2}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); } // Comptime field assignment { const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; const parsed = fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{.x = 1.2, .y = 1.5}", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:18: error: cannot initialize comptime field , "{f}", .{diagnostics.fmt("input.zon")}); } // Enum field (regression test, we were previously getting the field name in an // incorrect way that broke for enum values) { const Vec0 = struct { x: enum { x } }; const parsed = try fromSliceNoAlloc(Vec0, .{ .gpa = gpa, .arena = arena, .source = ".{ .x = .x }", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec0{ .x = .x }, parsed); } // Enum field and struct field with @ { const Vec0 = struct { @"x x": enum { @"x x" } }; const parsed = try fromSliceNoAlloc(Vec0, .{ .gpa = gpa, .arena = arena, .source = ".{ .@\"x x\" = .@\"x x\" }", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed); } // Type expressions are not allowed { // Structs { const parsed = fromSliceNoAlloc(struct {}, .{ .gpa = gpa, .arena = arena, .source = "Empty{}", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:1: error: types are not available in ZON \\input.zon:1:1: note: replace the type with '.' , "{f}", .{diagnostics.fmt("input.zon")}); } // Arrays { const parsed = fromSliceNoAlloc([3]u8, .{ .gpa = gpa, .arena = arena, .source = "[3]u8{1, 2, 3}", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:1: error: types are not available in ZON \\input.zon:1:1: note: replace the type with '.' , "{f}", .{diagnostics.fmt("input.zon")}); } // Slices { const parsed = fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "[]u8{1, 2, 3}", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:1: error: types are not available in ZON \\input.zon:1:1: note: replace the type with '.' , "{f}", .{diagnostics.fmt("input.zon")}); } // Tuples { const parsed = fromSliceNoAlloc(struct { u8, u8, u8 }, .{ .gpa = gpa, .arena = arena, .source = "Tuple{1, 2, 3}", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:1: error: types are not available in ZON \\input.zon:1:1: note: replace the type with '.' , "{f}", .{diagnostics.fmt("input.zon")}); } // Nested { const parsed = fromSliceNoAlloc(struct {}, .{ .gpa = gpa, .arena = arena, .source = ".{ .x = Tuple{1, 2, 3} }", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:9: error: types are not available in ZON \\input.zon:1:9: note: replace the type with '.' , "{f}", .{diagnostics.fmt("input.zon")}); } } } test "std.zon tuples" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Structs (various sizes tested since they're parsed differently) { const Tuple0 = struct {}; const Tuple1 = struct { f32 }; const Tuple2 = struct { f32, bool }; const Tuple3 = struct { f32, bool, u8 }; const zero = try fromSliceNoAlloc(Tuple0, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Tuple0{}, zero); const one = try fromSliceNoAlloc(Tuple1, .{ .gpa = gpa, .arena = arena, .source = ".{1.2}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Tuple1{1.2}, one); const two = try fromSliceNoAlloc(Tuple2, .{ .gpa = gpa, .arena = arena, .source = ".{1.2, true}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Tuple2{ 1.2, true }, two); const three = try fromSliceNoAlloc(Tuple3, .{ .gpa = gpa, .arena = arena, .source = ".{1.2, false, 3}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Tuple3{ 1.2, false, 3 }, three); } // Deep free { const Tuple = struct { []const u8, []const u8 }; const parsed = try fromSlice(Tuple, .{ .gpa = gpa, .arena = arena, .source = ".{\"hello\", \"world\"}", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed); } // Extra field { const Tuple = struct { f32, bool }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Tuple, .{ .gpa = gpa, .arena = arena, .source = ".{0.5, true, 123}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:14: error: index 2 outside of tuple length 2 , "{f}", .{diagnostics.fmt("input.zon")}); } // Extra field { const Tuple = struct { f32, bool }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Tuple, .{ .gpa = gpa, .arena = arena, .source = ".{0.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: missing tuple field with index 1 , "{f}", .{diagnostics.fmt("input.zon")}); } // Tuple with unexpected field names { const Tuple = struct { f32 }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Tuple, .{ .gpa = gpa, .arena = arena, .source = ".{.foo = 10.0}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected tuple , "{f}", .{diagnostics.fmt("input.zon")}); } // Struct with missing field names { const Struct = struct { foo: f32 }; try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Struct, .{ .gpa = gpa, .arena = arena, .source = ".{10.0}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected struct , "{f}", .{diagnostics.fmt("input.zon")}); } // Comptime field { const Vec2 = struct { f32, comptime f32 = 1.5 }; const parsed = try fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{ 1.2 }", .diagnostics = &diagnostics, }); try std.testing.expectEqual(Vec2{ 1.2, 1.5 }, parsed); } // Comptime field assignment { const Vec2 = struct { f32, comptime f32 = 1.5 }; const parsed = fromSliceNoAlloc(Vec2, .{ .gpa = gpa, .arena = arena, .source = ".{ 1.2, 1.5}", .diagnostics = &diagnostics, }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\input.zon:1:9: error: cannot initialize comptime field , "{f}", .{diagnostics.fmt("input.zon")}); } } // Test sizes 0 to 3 since small sizes get parsed differently test "std.zon arrays and slices" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Literals { // Arrays { const zero = try fromSliceNoAlloc([0]u8, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero); const one = try fromSliceNoAlloc([1]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a'}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one); const two = try fromSliceNoAlloc([2]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b'}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two); const two_comma = try fromSliceNoAlloc([2]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b',}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma); const three = try fromSliceNoAlloc([3]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three); const sentinel = try fromSliceNoAlloc([3:'z']u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }); const expected_sentinel: [3:'z']u8 = .{ 'a', 'b', 'c' }; try std.testing.expectEqualSlices(u8, &expected_sentinel, &sentinel); } // Slice literals { const zero = try fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero); const one = try fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a'}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &.{'a'}, one); const two = try fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b'}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two); const two_comma = try fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b',}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma); const three = try fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three); const sentinel = try fromSlice([:'z']const u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }); const expected_sentinel: [:'z']const u8 = &.{ 'a', 'b', 'c' }; try std.testing.expectEqualSlices(u8, expected_sentinel, sentinel); } } // Deep free { // Arrays { const parsed = try fromSlice([1][]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{\"abc\"}", .diagnostics = &diagnostics, }); const expected: [1][]const u8 = .{"abc"}; try std.testing.expectEqualDeep(expected, parsed); } // Slice literals { const parsed = try fromSlice([]const []const u8, .{ .gpa = gpa, .arena = arena, .source = ".{\"abc\"}", .diagnostics = &diagnostics, }); const expected: []const []const u8 = &.{"abc"}; try std.testing.expectEqualDeep(expected, parsed); } } // Sentinels and alignment { // Arrays { const sentinel = try fromSliceNoAlloc([1:2]u8, .{ .gpa = gpa, .arena = arena, .source = ".{1}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(usize, 1), sentinel.len); try std.testing.expectEqual(@as(u8, 1), sentinel[0]); try std.testing.expectEqual(@as(u8, 2), sentinel[1]); } // Slice literals { const sentinel = try fromSlice([:2]align(4) u8, .{ .gpa = gpa, .arena = arena, .source = ".{1}", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(usize, 1), sentinel.len); try std.testing.expectEqual(@as(u8, 1), sentinel[0]); try std.testing.expectEqual(@as(u8, 2), sentinel[1]); } } // Expect 0 find 3 { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([0]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:3: error: index 0 outside of array of length 0 , "{f}", .{diagnostics.fmt("input.zon")}); } // Expect 1 find 2 { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([1]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b'}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:8: error: index 1 outside of array of length 1 , "{f}", .{diagnostics.fmt("input.zon")}); } // Expect 2 find 1 { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([2]u8, .{ .gpa = gpa, .arena = arena, .source = ".{'a'}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected 2 array elements; found 1 , "{f}", .{diagnostics.fmt("input.zon")}); } // Expect 3 find 0 { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([3]u8, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected 3 array elements; found 0 , "{f}", .{diagnostics.fmt("input.zon")}); } // Wrong inner type { // Array { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([3]bool, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:3: error: expected type 'bool' , "{f}", .{diagnostics.fmt("input.zon")}); } // Slice { try std.testing.expectError( error.ParseZon, fromSlice([]bool, .{ .gpa = gpa, .arena = arena, .source = ".{'a', 'b', 'c'}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:3: error: expected type 'bool' , "{f}", .{diagnostics.fmt("input.zon")}); } } // Complete wrong type { // Array { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([3]u8, .{ .gpa = gpa, .arena = arena, .source = "'a'", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } // Slice { try std.testing.expectError( error.ParseZon, fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "'a'", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } } // Address of is not allowed (indirection for slices in ZON is implicit) { try std.testing.expectError( error.ParseZon, fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = " &.{'a', 'b', 'c'}", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:3: error: pointers are not available in ZON , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon string literal" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Basic string literal { const parsed = try fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"abc\"", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed); } // String literal with escape characters { const parsed = try fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"ab\\nc\"", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed); } // String literal with embedded null { const parsed = try fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"ab\\x00c\"", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed); } // Passing string literal to a mutable slice { { try std.testing.expectError( error.ParseZon, fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "\"abcd\"", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\abcd", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } } // Passing string literal to a array { { var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .{ .mode = .zon }); defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([4:0]u8, .{ .gpa = gpa, .arena = arena, .source = "\"abcd\"", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc([4:0]u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\abcd", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } } // Zero terminated slices { { const parsed: [:0]const u8 = try fromSlice([:0]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"abc\"", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } { const parsed: [:0]const u8 = try fromSlice([:0]const u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\abc", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } } // Other value terminated slices { { try std.testing.expectError( error.ParseZon, fromSlice([:1]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"foo\"", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice([:1]const u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\foo", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } } // Expecting string literal, getting something else { try std.testing.expectError( error.ParseZon, fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected string , "{f}", .{diagnostics.fmt("input.zon")}); } // Expecting string literal, getting an incompatible tuple { try std.testing.expectError( error.ParseZon, fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{false}", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:3: error: expected type 'u8' , "{f}", .{diagnostics.fmt("input.zon")}); } // Invalid string literal { try std.testing.expectError( error.ParseZon, fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\"\\a\"", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:3: error: invalid escape character: 'a' , "{f}", .{diagnostics.fmt("input.zon")}); } // Slice wrong child type { { try std.testing.expectError( error.ParseZon, fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\"a\"", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\\\\a", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } } // Bad alignment { { try std.testing.expectError( error.ParseZon, fromSlice([]align(2) const u8, .{ .gpa = gpa, .arena = arena, .source = "\"abc\"", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice([]align(2) const u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\abc", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected array , "{f}", .{diagnostics.fmt("input.zon")}); } } // Multi line strings inline for (.{ []const u8, [:0]const u8 }) |String| { // Nested { const S = struct { message: String, message2: String, message3: String, }; const parsed = try fromSlice(S, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ .message = \\ \\hello, world! \\ \\ \\this is a multiline string! \\ \\ \\ \\... \\ \\ , \\ .message2 = \\ \\this too...sort of. \\ , \\ .message3 = \\ \\ \\ \\and this. \\} , .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings( "hello, world!\nthis is a multiline string!\n\n...", parsed.message, ); try std.testing.expectEqualStrings("this too...sort of.", parsed.message2); try std.testing.expectEqualStrings("\nand this.", parsed.message3); } } } test "std.zon enum literals" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const Enum = enum { foo, bar, baz, @"ab\nc", }; // Tags that exist try std.testing.expectEqual(Enum.foo, try fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".foo", .diagnostics = &diagnostics, })); try std.testing.expectEqual(Enum.bar, try fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".bar", .diagnostics = &diagnostics, })); try std.testing.expectEqual(Enum.baz, try fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".baz", .diagnostics = &diagnostics, })); try std.testing.expectEqual( Enum.@"ab\nc", try fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".@\"ab\\nc\"", .diagnostics = &diagnostics, }), ); // Bad tag { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".qux", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: unexpected enum literal 'qux' \\input.zon:1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' , "{f}", .{diagnostics.fmt("input.zon")}); } // Bad tag that's too long for parser { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".@\"foobarbaz\"", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: unexpected enum literal 'foobarbaz' \\input.zon:1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' , "{f}", .{diagnostics.fmt("input.zon")}); } // Bad type { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected enum literal , "{f}", .{diagnostics.fmt("input.zon")}); } // Test embedded nulls in an identifier { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(Enum, .{ .gpa = gpa, .arena = arena, .source = ".@\"\\x00\"", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: identifier cannot contain null bytes , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon parse bool" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Correct bools try std.testing.expectEqual(true, try fromSliceNoAlloc(bool, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, })); try std.testing.expectEqual(false, try fromSliceNoAlloc(bool, .{ .gpa = gpa, .arena = arena, .source = "false", .diagnostics = &diagnostics, })); // Errors { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(bool, .{ .gpa = gpa, .arena = arena, .source = " foo", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: invalid expression \\input.zon:1:2: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' \\input.zon:1:2: note: precede identifier with '.' for an enum literal , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(bool, .{ .gpa = gpa, .arena = arena, .source = "123", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'bool' , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon intFromFloatExact" { // Valid conversions try std.testing.expectEqual(@as(u8, 10), intFromFloatExact(u8, @as(f32, 10.0)).?); try std.testing.expectEqual(@as(i8, -123), intFromFloatExact(i8, @as(f64, @as(f64, -123.0))).?); try std.testing.expectEqual(@as(i16, 45), intFromFloatExact(i16, @as(f128, @as(f128, 45.0))).?); try std.testing.expectEqual(@as(u128, 67), intFromFloatExact(u128, @as(f128, @as(f128, 67.0))).?); // Out of range try std.testing.expectEqual(@as(?u4, null), intFromFloatExact(u4, @as(f32, 16.0))); try std.testing.expectEqual(@as(?i4, null), intFromFloatExact(i4, @as(f64, -17.0))); try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f128, -2.0))); // Not a whole number try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, @as(f32, 0.5))); try std.testing.expectEqual(@as(?i8, null), intFromFloatExact(i8, @as(f64, 0.01))); // Infinity and NaN try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.inf(f32))); try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, -std.math.inf(f32))); try std.testing.expectEqual(@as(?u8, null), intFromFloatExact(u8, std.math.nan(f32))); } test "std.zon parse int" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Test various numbers and types try std.testing.expectEqual(@as(u8, 10), try fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i16, 24), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "24", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i14, -4), try fromSliceNoAlloc(i14, .{ .gpa = gpa, .arena = arena, .source = "-4", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i32, -123), try fromSliceNoAlloc(i32, .{ .gpa = gpa, .arena = arena, .source = "-123", .diagnostics = &diagnostics, })); // Test limits try std.testing.expectEqual(@as(i8, 127), try fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "127", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i8, -128), try fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "-128", .diagnostics = &diagnostics, })); // Test characters try std.testing.expectEqual(@as(u8, 'a'), try fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "'a'", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(u8, 'z'), try fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "'z'", .diagnostics = &diagnostics, })); // Test big integers try std.testing.expectEqual( @as(u65, 36893488147419103231), try fromSliceNoAlloc(u65, .{ .gpa = gpa, .arena = arena, .source = "36893488147419103231", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(u65, 36893488147419103231), try fromSliceNoAlloc(u65, .{ .gpa = gpa, .arena = arena, .source = "368934_881_474191032_31", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(u128, 340282366920938463463374607431768211455), try fromSliceNoAlloc(u128, .{ .gpa = gpa, .arena = arena, .source = "340282366920938463463374607431768211455", .diagnostics = &diagnostics, }), ); // Test big integer limits try std.testing.expectEqual( @as(i66, 36893488147419103231), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "36893488147419103231", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(i66, -36893488147419103232), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "-36893488147419103232", .diagnostics = &diagnostics, }), ); { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "36893488147419103232", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'i66' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "-36893488147419103233", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'i66' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } // Test parsing whole number floats as integers try std.testing.expectEqual(@as(i8, -1), try fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "-1.0", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i8, 123), try fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "123.0", .diagnostics = &diagnostics, })); // Test non-decimal integers try std.testing.expectEqual(@as(i16, 0xff), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "0xff", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i16, -0xff), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "-0xff", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i16, 0o77), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "0o77", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i16, -0o77), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "-0o77", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i16, 0b11), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "0b11", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i16, -0b11), try fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "-0b11", .diagnostics = &diagnostics, })); // Test non-decimal big integers try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSliceNoAlloc(u65, .{ .gpa = gpa, .arena = arena, .source = "0x1ffffffffffffffff", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "0x1ffffffffffffffff", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "-0x1ffffffffffffffff", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSliceNoAlloc(u65, .{ .gpa = gpa, .arena = arena, .source = "0o3777777777777777777777", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "0o3777777777777777777777", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "-0o3777777777777777777777", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSliceNoAlloc(u65, .{ .gpa = gpa, .arena = arena, .source = "0b11111111111111111111111111111111111111111111111111111111111111111", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "0b11111111111111111111111111111111111111111111111111111111111111111", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSliceNoAlloc(i66, .{ .gpa = gpa, .arena = arena, .source = "-0b11111111111111111111111111111111111111111111111111111111111111111", .diagnostics = &diagnostics, })); // Number with invalid character in the middle { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "32a32", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:3: error: invalid digit 'a' for decimal base , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing to parse as int { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'u8' , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because an int is out of range { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "256", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'u8' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because a negative int is out of range { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "-129", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'i8' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because an unsigned int is negative { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "-1", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'u8' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because a float is non-whole { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "1.5", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'u8' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because a float is negative { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "-1.0", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: type 'u8' cannot represent value , "{f}", .{diagnostics.fmt("input.zon")}); } // Negative integer zero { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "-0", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:2: error: integer literal '-0' is ambiguous \\input.zon:1:2: note: use '0' for an integer zero \\input.zon:1:2: note: use '-0.0' for a floating-point signed zero , "{f}", .{diagnostics.fmt("input.zon")}); } // Negative integer zero casted to float { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-0", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:2: error: integer literal '-0' is ambiguous \\input.zon:1:2: note: use '0' for an integer zero \\input.zon:1:2: note: use '-0.0' for a floating-point signed zero , "{f}", .{diagnostics.fmt("input.zon")}); } // Negative float 0 is allowed try std.testing.expect( std.math.isNegativeZero(try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-0.0", .diagnostics = &diagnostics, })), ); try std.testing.expect(std.math.isPositiveZero(try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "0.0", .diagnostics = &diagnostics, }))); // Double negation is not allowed { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "--2", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected number or 'inf' after '-' , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "--2.0", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected number or 'inf' after '-' , "{f}", .{diagnostics.fmt("input.zon")}); } // Invalid int literal { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "0xg", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:3: error: invalid digit 'g' for hex base , "{f}", .{diagnostics.fmt("input.zon")}); } // Notes on invalid int literal { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ .gpa = gpa, .arena = arena, .source = "0123", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: number '0123' has leading zero \\input.zon:1:1: note: use '0o' prefix for octal literals , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon negative char" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-'a'", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected number or 'inf' after '-' , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i16, .{ .gpa = gpa, .arena = arena, .source = "-'a'", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected number or 'inf' after '-' , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon parse float" { if (builtin.cpu.arch == .x86) return error.SkipZigTest; const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Test decimals try std.testing.expectEqual(@as(f16, 0.5), try fromSliceNoAlloc(f16, .{ .gpa = gpa, .arena = arena, .source = "0.5", .diagnostics = &diagnostics, })); try std.testing.expectEqual( @as(f32, 123.456), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "123.456", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(f64, -123.456), try fromSliceNoAlloc(f64, .{ .gpa = gpa, .arena = arena, .source = "-123.456", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual(@as(f128, 42.5), try fromSliceNoAlloc(f128, .{ .gpa = gpa, .arena = arena, .source = "42.5", .diagnostics = &diagnostics, })); // Test whole numbers with and without decimals try std.testing.expectEqual(@as(f16, 5.0), try fromSliceNoAlloc(f16, .{ .gpa = gpa, .arena = arena, .source = "5.0", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(f16, 5.0), try fromSliceNoAlloc(f16, .{ .gpa = gpa, .arena = arena, .source = "5", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(f32, -102), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-102.0", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(f32, -102), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-102", .diagnostics = &diagnostics, })); // Test characters and negated characters try std.testing.expectEqual(@as(f32, 'a'), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "'a'", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(f32, 'z'), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "'z'", .diagnostics = &diagnostics, })); // Test big integers try std.testing.expectEqual( @as(f32, 36893488147419103231.0), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "36893488147419103231", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(f32, -36893488147419103231.0), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-36893488147419103231", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSliceNoAlloc(f128, .{ .gpa = gpa, .arena = arena, .source = "0x1ffffffffffffffff", .diagnostics = &diagnostics, })); try std.testing.expectEqual(@as(f32, @floatFromInt(0x1ffffffffffffffff)), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "0x1ffffffffffffffff", .diagnostics = &diagnostics, })); // Exponents, underscores try std.testing.expectEqual( @as(f32, 123.0E+77), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "12_3.0E+77", .diagnostics = &diagnostics, }), ); // Hexadecimal try std.testing.expectEqual( @as(f32, 0x103.70p-5), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "0x103.70p-5", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(f32, -0x103.70), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-0x103.70", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @as(f32, 0x1234_5678.9ABC_CDEFp-10), try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "0x1234_5678.9ABC_CDEFp-10", .diagnostics = &diagnostics, }), ); // inf, nan try std.testing.expect(std.math.isPositiveInf(try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "inf", .diagnostics = &diagnostics, }))); try std.testing.expect(std.math.isNegativeInf(try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-inf", .diagnostics = &diagnostics, }))); try std.testing.expect(std.math.isNan(try fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "nan", .diagnostics = &diagnostics, }))); // Negative nan not allowed { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-nan", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected number or 'inf' after '-' , "{f}", .{diagnostics.fmt("input.zon")}); } // nan as int not allowed { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "nan", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'i8' , "{f}", .{diagnostics.fmt("input.zon")}); } // nan as int not allowed { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "nan", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'i8' , "{f}", .{diagnostics.fmt("input.zon")}); } // inf as int not allowed { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "inf", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'i8' , "{f}", .{diagnostics.fmt("input.zon")}); } // -inf as int not allowed { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ .gpa = gpa, .arena = arena, .source = "-inf", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'i8' , "{f}", .{diagnostics.fmt("input.zon")}); } // Bad identifier as float { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "foo", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: invalid expression \\input.zon:1:1: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' \\input.zon:1:1: note: precede identifier with '.' for an enum literal , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "-foo", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:1: error: expected number or 'inf' after '-' , "{f}", .{diagnostics.fmt("input.zon")}); } // Non float as float { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(f32, .{ .gpa = gpa, .arena = arena, .source = "\"foo\"", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected type 'f32' , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon free on error" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Test freeing partially allocated structs { const Struct = struct { x: []const u8, y: []const u8, z: bool, }; try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ .x = "hello", \\ .y = "world", \\ .z = "fail", \\} , .diagnostics = &diagnostics, })); } // Test freeing partially allocated tuples { const Struct = struct { []const u8, []const u8, bool, }; try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ "hello", \\ "world", \\ "fail", \\} , .diagnostics = &diagnostics, })); } // Test freeing structs with missing fields const Struct = struct { x: []const u8, y: bool, }; try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ .x = "hello", \\} , .diagnostics = &diagnostics, })); // Test freeing partially allocated arrays try std.testing.expectError(error.ParseZon, fromSlice([3][]const u8, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ "hello", \\ false, \\ false, \\} , .diagnostics = &diagnostics, })); // Test freeing partially allocated slices try std.testing.expectError(error.ParseZon, fromSlice([][]const u8, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ "hello", \\ "world", \\ false, \\} , .diagnostics = &diagnostics, })); // We can parse types that can't be freed, as long as they contain no allocations, e.g. untagged // unions. try std.testing.expectEqual( @as(f32, 1.5), (try fromSliceNoAlloc(union { x: f32 }, .{ .gpa = gpa, .arena = arena, .source = ".{ .x = 1.5 }", .diagnostics = &diagnostics, })).x, ); // We can also parse types that can't be freed if it's impossible for an error to occur after // the allocation, as is the case here. { const result = try fromSlice(union { x: []const u8 }, .{ .gpa = gpa, .arena = arena, .source = ".{ .x = \"foo\" }", .diagnostics = &diagnostics, }); try std.testing.expectEqualStrings("foo", result.x); } } test "std.zon vector" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15330 if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .s390x) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25957 const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Passing cases try std.testing.expectEqual( @Vector(0, bool){}, try fromSliceNoAlloc(@Vector(0, bool), .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @Vector(3, bool){ true, false, true }, try fromSliceNoAlloc(@Vector(3, bool), .{ .gpa = gpa, .arena = arena, .source = ".{true, false, true}", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @Vector(0, f32){}, try fromSliceNoAlloc(@Vector(0, f32), .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @Vector(3, f32){ 1.5, 2.5, 3.5 }, try fromSliceNoAlloc(@Vector(3, f32), .{ .gpa = gpa, .arena = arena, .source = ".{1.5, 2.5, 3.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @Vector(0, u8){}, try fromSliceNoAlloc(@Vector(0, u8), .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( @Vector(3, u8){ 2, 4, 6 }, try fromSliceNoAlloc(@Vector(3, u8), .{ .gpa = gpa, .arena = arena, .source = ".{2, 4, 6}", .diagnostics = &diagnostics, }), ); { try std.testing.expectEqual( @Vector(0, *const u8){}, try fromSlice(@Vector(0, *const u8), .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); const pointers = try fromSlice(@Vector(3, *const u8), .{ .gpa = gpa, .arena = arena, .source = ".{2, 4, 6}", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(@Vector(3, *const u8){ &2, &4, &6 }, pointers); } { try std.testing.expectEqual( @Vector(0, ?*const u8){}, try fromSlice(@Vector(0, ?*const u8), .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }), ); const pointers = try fromSlice(@Vector(3, ?*const u8), .{ .gpa = gpa, .arena = arena, .source = ".{2, null, 6}", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(@Vector(3, ?*const u8){ &2, null, &6 }, pointers); } // Too few fields { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(@Vector(2, f32), .{ .gpa = gpa, .arena = arena, .source = ".{0.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:2: error: expected 2 array elements; found 1 , "{f}", .{diagnostics.fmt("input.zon")}); } // Too many fields { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(@Vector(2, f32), .{ .gpa = gpa, .arena = arena, .source = ".{0.5, 1.5, 2.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:13: error: index 2 outside of array of length 2 , "{f}", .{diagnostics.fmt("input.zon")}); } // Wrong type fields { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(@Vector(3, f32), .{ .gpa = gpa, .arena = arena, .source = ".{0.5, true, 2.5}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:8: error: expected type 'f32' , "{f}", .{diagnostics.fmt("input.zon")}); } // Wrong type { try std.testing.expectError( error.ParseZon, fromSliceNoAlloc(@Vector(3, u8), .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected type '@Vector(3, u8)' , "{f}", .{diagnostics.fmt("input.zon")}); } // Elements should get freed on error { try std.testing.expectError( error.ParseZon, fromSlice(@Vector(3, *u8), .{ .gpa = gpa, .arena = arena, .source = ".{1, true, 3}", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:6: error: expected type 'u8' , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon add pointers" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Primitive with varying levels of pointers { const result = try fromSlice(*u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), result.*); } { const result = try fromSlice(**u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), result.*.*); } { const result = try fromSlice(***u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), result.*.*.*); } // Primitive optional with varying levels of pointers { const some = try fromSlice(?*u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), some.?.*); const none = try fromSlice(?*u32, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expectEqual(null, none); } { const some = try fromSlice(*?u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), some.*.?); const none = try fromSlice(*?u32, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expectEqual(null, none.*); } { const some = try fromSlice(?**u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), some.?.*.*); const none = try fromSlice(?**u32, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expectEqual(null, none); } { const some = try fromSlice(*?*u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), some.*.?.*); const none = try fromSlice(*?*u32, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expectEqual(null, none.*); } { const some = try fromSlice(**?u32, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }); try std.testing.expectEqual(@as(u32, 10), some.*.*.?); const none = try fromSlice(**?u32, .{ .gpa = gpa, .arena = arena, .source = "null", .diagnostics = &diagnostics, }); try std.testing.expectEqual(null, none.*.*); } // Pointer to an array { const result = try fromSlice(*[3]u8, .{ .gpa = gpa, .arena = arena, .source = ".{ 1, 2, 3 }", .diagnostics = &diagnostics, }); try std.testing.expectEqual([3]u8{ 1, 2, 3 }, result.*); } // A complicated type with nested internal pointers and string allocations { const Inner = struct { f1: *const ?*const []const u8, f2: *const ?*const []const u8, }; const Outer = struct { f1: *const ?*const Inner, f2: *const ?*const Inner, }; const expected: Outer = .{ .f1 = &&.{ .f1 = &null, .f2 = &&"foo", }, .f2 = &null, }; const found = try fromSlice(?*Outer, .{ .gpa = gpa, .arena = arena, .source = \\.{ \\ .f1 = .{ \\ .f1 = null, \\ .f2 = "foo", \\ }, \\ .f2 = null, \\} , .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(expected, found.?.*); } // Test that optional types are flattened correctly in errors { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected type '?u8' , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const f32, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected type '?f32' , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const @Vector(3, u8), .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected type '?@Vector(3, u8)' , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const bool, .{ .gpa = gpa, .arena = arena, .source = "10", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected type '?bool' , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const struct { a: i32 }, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional struct , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const struct { i32 }, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional tuple , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const union { x: void }, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional union , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const [3]u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(?[3]u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const []u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(?[]u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional array , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const []const u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional string , "{f}", .{diagnostics.fmt("input.zon")}); } { try std.testing.expectError( error.ParseZon, fromSlice(*const ?*const enum { foo }, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics, }), ); try std.testing.expectFmt( \\input.zon:1:1: error: expected optional enum literal , "{f}", .{diagnostics.fmt("input.zon")}); } } test "std.zon stop on node" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; { const Vec2 = struct { x: Zoir.Node.Index, y: f32, }; var ast = try std.zig.Ast.parse(gpa, ".{ .x = 1.5, .y = 2.5 }", .{ .mode = .zon }); defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); const result = try fromZoirNoAlloc(Vec2, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }); try std.testing.expectEqual(result.y, 2.5); try std.testing.expectEqual(Zoir.Node{ .float_literal = 1.5 }, result.x.get(&zoir)); } { var ast = try std.zig.Ast.parse(gpa, "1.23", .{ .mode = .zon }); defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); const result = try fromZoirNoAlloc(Zoir.Node.Index, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }); try std.testing.expectEqual(Zoir.Node{ .float_literal = 1.23 }, result.get(&zoir)); } } test "std.zon no alloc" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; try std.testing.expectEqual( [3]u8{ 1, 2, 3 }, try fromSliceNoAlloc([3]u8, .{ .gpa = gpa, .arena = arena, .source = ".{ 1, 2, 3 }", .diagnostics = &diagnostics, }), ); const Nested = struct { u8, u8, struct { u8, u8 } }; var ast = try std.zig.Ast.parse(gpa, ".{ 1, 2, .{ 3, 4 } }", .{ .mode = .zon }); defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); try std.testing.expectEqual( Nested{ 1, 2, .{ 3, 4 } }, try fromZoirNoAlloc(Nested, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }), ); } test "std.zon aligned pointers" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const n: u8 align(8) = 10; const Foo = struct { inner: *align(8) const u8, }; const expected: Foo = .{ .inner = &n, }; const found = try fromSlice(Foo, .{ .gpa = gpa, .arena = arena, .source = ".{ .inner = 10 }", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(expected.inner, found.inner); } test "std.zon update basic" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const Vector = struct { x: f32, y: f32, z: f32 }; const MyStruct = struct { foo: u32 = 123, bar: bool, baz: struct { a: [2]u32, b: enum { c, d } = .d, }, ptr: *Vector, optional: ?u32 = null, str: []const u8, }; var v1: Vector = .{ .x = 1, .y = 2, .z = 3 }; var expected: MyStruct = .{ .foo = 10, .bar = true, .baz = .{ .a = .{ 1, 2 }, .b = .c }, .ptr = &v1, .str = "hello, world", }; var v2: Vector = .{ .x = 1, .y = 2, .z = 3 }; var found: MyStruct = .{ .foo = 10, .bar = true, .baz = .{ .a = .{ 1, 2 }, .b = .c }, .ptr = &v2, .str = "hello, world", }; try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{}", .diagnostics = &diagnostics, }); try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .bar = false }", .diagnostics = &diagnostics, }); expected.bar = false; try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .baz = .{ .a = .{ 2, 4 } } }", .diagnostics = &diagnostics, }); expected.baz.a[0] = 2; expected.baz.a[1] = 4; try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = 11, .baz = .{ .b = .d } }", .diagnostics = &diagnostics, }); expected.foo = 11; expected.baz.b = .d; try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .optional = 10 }", .diagnostics = &diagnostics, }); expected.optional = 10; try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .optional = null }", .diagnostics = &diagnostics, }); expected.optional = null; try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .ptr = .{ .x = 10, .y = 20, .z = 30 } }", .diagnostics = &diagnostics, }); expected.ptr.x = 10; expected.ptr.y = 20; expected.ptr.z = 30; try std.testing.expectEqualDeep(expected, found); try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .str = \"foo\" }", .diagnostics = &diagnostics, }); expected.str = "foo"; try std.testing.expectEqualDeep(expected, found); } test "std.zon update optionals" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const MyStruct = struct { foo: ?struct { bar: u32 = 1, baz: u32 = 2, qux: u32 }, }; // Updating an optional that starts out null should fill in any default fields we left off { var expected: MyStruct = .{ .foo = null }; var found = expected; try updateFromSliceNoAlloc(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .qux = 3 } }", .diagnostics = &diagnostics, }); expected.foo = .{ .qux = 3 }; try std.testing.expectEqual(expected, found); } // Updating an optional that starts out null should error if we leave off required fields. { var found: MyStruct = .{ .foo = null }; try std.testing.expectError(error.ParseZon, updateFromSliceNoAlloc(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{} }", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:12: error: missing required field qux , "{f}", .{diagnostics.fmt("input.zon")}); } // Updating an optional that starts out non-null should preserve any values we leave off. It's // also okay to leave off required fields since they were already set. { var expected: MyStruct = .{ .foo = .{ .bar = 10, .baz = 20, .qux = 30 }, }; var found = expected; try updateFromSliceNoAlloc(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .baz = 200 } }", .diagnostics = &diagnostics, }); expected.foo.?.baz = 200; try std.testing.expectEqual(expected, found); } } test "std.zon update optional pointers" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const MyStruct = struct { foo: ?*const struct { bar: u32 = 1, baz: u32 = 2, qux: u32 }, }; // Just a smoke test since its the combination of behavior we've already tested separately { var expected: MyStruct = .{ .foo = null }; var found = expected; try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .qux = 3 } }", .diagnostics = &diagnostics, }); expected.foo = &.{ .qux = 3 }; try std.testing.expectEqualDeep(expected, found); } } test "std.zon update pointers" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; // Updating a pointer should leave fields we don't specify unchanged { const MyStruct = struct { foo: *struct { bar: u32 = 10, baz: u32 = 20, qux: u32 }, }; var ptr: @typeInfo(@FieldType(MyStruct, "foo")).pointer.child = .{ .qux = 3 }; var expected: MyStruct = .{ .foo = &ptr }; var found = expected; try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .bar = 100 } }", .diagnostics = &diagnostics, }); expected.foo.bar = 100; try std.testing.expectEqualDeep(expected, found); } // Same thing, but for const pointers { const MyStruct = struct { foo: *const struct { bar: u32 = 10, baz: u32 = 20, qux: u32 }, }; var expected: MyStruct = .{ .foo = &.{ .qux = 3 } }; var found = expected; try updateFromSlice(MyStruct, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .bar = 100 } }", .diagnostics = &diagnostics, }); expected.foo = &.{ .bar = 100, .baz = 20, .qux = 3 }; try std.testing.expectEqualDeep(expected, found); } } test "std.zon update unions" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const MyUnion = union(enum) { none: void, foo: struct { bar: u32 = 10, baz: u32 = 20, qux: u32 }, }; // Updating a union to a new field should fill in any default sub-fields we left off { var expected: MyUnion = .none; var found = expected; try updateFromSliceNoAlloc(MyUnion, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .qux = 3 } }", .diagnostics = &diagnostics, }); expected = .{ .foo = .{ .qux = 3 } }; try std.testing.expectEqual(expected, found); } // Updating a union to a new field should error if we leave off required sub-fields { var found: MyUnion = .none; try std.testing.expectError(error.ParseZon, updateFromSliceNoAlloc(MyUnion, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{} }", .diagnostics = &diagnostics, })); try std.testing.expectFmt( \\input.zon:1:12: error: missing required field qux , "{f}", .{diagnostics.fmt("input.zon")}); } // Updating a union should preseve any sub-fields that we left off. It's also okay to leave off // required fields since they were already set. { var expected: MyUnion = .{ .foo = .{ .bar = 10, .baz = 20, .qux = 30 }, }; var found = expected; try updateFromSliceNoAlloc(MyUnion, &found, .{ .gpa = gpa, .arena = arena, .source = ".{ .foo = .{ .baz = 200 } }", .diagnostics = &diagnostics, }); expected.foo.baz = 200; try std.testing.expectEqual(expected, found); } } test "std.zon variants" { // Just a smoke test, we don't need to test each variant thoroughly because they all call into // the same code. const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; const Struct = struct { a: u32, b: u32 }; const start: Struct = .{ .a = 10, .b = 20 }; const end: Struct = .{ .a = 100, .b = 20 }; // Update { var ast = try std.zig.Ast.parse(gpa, ".{ .a = 100 }", .{ .mode = .zon }); defer ast.deinit(gpa); const zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); var curr = start; try updateFromSliceNoAlloc(Struct, &curr, .{ .gpa = gpa, .arena = arena, .source = ".{ .a = 100 }", .diagnostics = &diagnostics, }); try std.testing.expectEqual(end, curr); curr = start; try updateFromSlice(Struct, &curr, .{ .gpa = gpa, .arena = arena, .source = ".{ .a = 100 }", .diagnostics = &diagnostics, }); try std.testing.expectEqual(end, curr); curr = start; try updateFromZoirNoAlloc(Struct, &curr, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }); try std.testing.expectEqual(end, curr); curr = start; try updateFromZoir(Struct, &curr, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }); try std.testing.expectEqual(end, curr); } // From { var ast = try std.zig.Ast.parse(gpa, ".{ .a = 100, .b = 20 }", .{ .mode = .zon }); defer ast.deinit(gpa); const zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); try std.testing.expectEqual( end, try fromSliceNoAlloc(Struct, .{ .gpa = gpa, .arena = arena, .source = ".{ .a = 100, .b = 20 }", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( end, try fromSlice(Struct, .{ .gpa = gpa, .arena = arena, .source = ".{ .a = 100, .b = 20 }", .diagnostics = &diagnostics, }), ); try std.testing.expectEqual( try fromZoirNoAlloc(Struct, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }), end, ); try std.testing.expectEqual( try fromZoir(Struct, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .diagnostics = &diagnostics, }), end, ); } } test "std.zon parse specific node with zoir" { const gpa = std.testing.allocator; var arena_allocator: ArenaAllocator = .init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var diagnostics: Diagnostics = undefined; var ast = try std.zig.Ast.parse(gpa, ".{ .x = 1.5, .y = 2.5 }", .{ .mode = .zon }); defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); const root_node: std.zig.Zoir.Node = std.zig.Zoir.Node.Index.get(.root, &zoir); try std.testing.expect(root_node == .struct_literal); try std.testing.expectEqual(2, root_node.struct_literal.vals.len); const x_node = root_node.struct_literal.vals.at(0); const x_result = try fromZoirNoAlloc(f32, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .node = x_node, .diagnostics = &diagnostics, }); try std.testing.expectApproxEqAbs(x_result, 1.5, 0.1); const y_node = root_node.struct_literal.vals.at(1); const y_result = try fromZoirNoAlloc(f32, .{ .arena = arena, .ast = &ast, .zoir = &zoir, .node = y_node, .diagnostics = &diagnostics, }); try std.testing.expectApproxEqAbs(y_result, 2.5, 0.1); } test { _ = Diagnostics; }