diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 1b63cc6a0f7ea2305c8ce1f394eccb6136ffd877..be76c4982d1a2bd0284978ff6a57477097b570ca 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -168,11 +168,31 @@ pub inline fn rawFree(a: Allocator, memory: []u8, alignment: Alignment, ret_addr /// Returns a pointer to undefined memory. /// Call `destroy` with the result to free the memory. pub fn create(a: Allocator, comptime T: type) Error!*T { + return a.createAdvancedWithRetAddr(T, null, @returnAddress()); +} + +pub fn alignedCreate( + self: Allocator, + comptime T: type, + /// null means naturally aligned + comptime alignment: ?Alignment, +) Error!*align(if (alignment) |a| a.toByteUnits() else @alignOf(T)) T { + return self.createAdvancedWithRetAddr(T, alignment, @returnAddress()); +} + +pub inline fn createAdvancedWithRetAddr( + self: Allocator, + comptime T: type, + /// null means naturally aligned + comptime alignment: ?Alignment, + return_address: usize, +) Error!*align(if (alignment) |a| a.toByteUnits() else @alignOf(T)) T { if (@sizeOf(T) == 0) { const ptr = comptime std.mem.alignBackward(usize, math.maxInt(usize), @alignOf(T)); return @ptrFromInt(ptr); } - const ptr: *T = @ptrCast(try a.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress())); + const a: Alignment = alignment orelse comptime .of(T); + const ptr: *align(a.toByteUnits()) T = @ptrCast(try self.allocBytesAligned(a, @sizeOf(T), return_address)); return ptr; } diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 4ce1af8fdee40685a819298333d4248ddb9abfa0..2c2881a382379a2b3997c13a294312d23089a9a6 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -634,7 +634,7 @@ pub const Wip = struct { const err_loc = std.zig.findLineColumn(source, err_span.main); try eb.addRootErrorMessage(.{ - .msg = try eb.addString(err.msg.get(zoir)), + .msg = try eb.addString(err.msg.get(&zoir)), .src_loc = try eb.addSourceLocation(.{ .src_path = try eb.addString(src_path), .span_start = err_span.start, @@ -664,7 +664,7 @@ pub const Wip = struct { // This line can cause `wip.extra.items` to be resized. const note_index = @backingInt(try eb.addErrorMessage(.{ - .msg = try eb.addString(note.msg.get(zoir)), + .msg = try eb.addString(note.msg.get(&zoir)), .src_loc = try eb.addSourceLocation(.{ .src_path = try eb.addString(src_path), .span_start = note_span.start, diff --git a/lib/std/zig/Zoir.zig b/lib/std/zig/Zoir.zig index d29315aa5cb91ab12167ee39a3022f87505cda11..e7c2376d865d652c9eb2e38b28341e224443679d 100644 --- a/lib/std/zig/Zoir.zig +++ b/lib/std/zig/Zoir.zig @@ -42,7 +42,7 @@ pub const Header = extern struct { } }; -pub fn hasCompileErrors(zoir: Zoir) bool { +pub fn hasCompileErrors(zoir: *const Zoir) bool { if (zoir.compile_errors.len > 0) { assert(zoir.nodes.len == 0); assert(zoir.extra.len == 0); @@ -54,7 +54,7 @@ pub fn hasCompileErrors(zoir: Zoir) bool { } } -pub fn deinit(zoir: Zoir, gpa: Allocator) void { +pub fn deinit(zoir: *const Zoir, gpa: Allocator) void { var nodes = zoir.nodes; nodes.deinit(gpa); @@ -105,7 +105,7 @@ pub const Node = union(enum) { root = 0, _, - pub fn get(idx: Index, zoir: Zoir) Node { + pub fn get(idx: Index, zoir: *const Zoir) Node { const repr = zoir.nodes.get(@backingInt(idx)); return switch (repr.tag) { .true => .true, @@ -151,7 +151,7 @@ pub const Node = union(enum) { }; } - pub fn getAstNode(idx: Index, zoir: Zoir) std.zig.Ast.Node.Index { + pub fn getAstNode(idx: Index, zoir: *const Zoir) std.zig.Ast.Node.Index { return zoir.nodes.items(.ast_node)[@backingInt(idx)]; } @@ -227,7 +227,7 @@ pub const Node = union(enum) { pub const NullTerminatedString = enum(u32) { _, - pub fn get(nts: NullTerminatedString, zoir: Zoir) [:0]const u8 { + pub fn get(nts: NullTerminatedString, zoir: *const Zoir) [:0]const u8 { const idx = std.mem.findScalar(u8, zoir.string_bytes[@backingInt(nts)..], 0).?; return zoir.string_bytes[@backingInt(nts)..][0..idx :0]; } @@ -244,7 +244,7 @@ pub const CompileError = extern struct { first_note: u32, note_count: u32, - pub fn getNotes(err: CompileError, zoir: Zoir) []const Note { + pub fn getNotes(err: CompileError, zoir: *const Zoir) []const Note { return zoir.error_notes[err.first_note..][0..err.note_count]; } diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 97ab7c5ab707b537246eae1d2ea714bbc1ae21b7..33723d62f6cd301c05fec0c4d97c72393e565174 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -490,7 +490,7 @@ fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) error{ OutOfMemory, } /// Estimates the size of a string node without parsing it. -pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize { +pub fn strLitSizeHint(tree: *const Ast, node: Ast.Node.Index) usize { switch (tree.nodeTag(node)) { // Parsed string literals are typically around the size of the raw strings. .string_literal => { @@ -515,7 +515,7 @@ pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize { /// Parses the given node as a string literal. pub fn parseStrLit( - tree: Ast, + tree: *const Ast, node: Ast.Node.Index, writer: *Writer, ) Writer.Error!std.zig.string_literal.Result { @@ -559,12 +559,12 @@ fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) error{ OutOfMemory, Bad const gpa = zg.gpa; const string_bytes = &zg.string_bytes; const str_index: u32 = @intCast(zg.string_bytes.items.len); - const size_hint = strLitSizeHint(zg.tree, str_node); + const size_hint = strLitSizeHint(&zg.tree, str_node); try string_bytes.ensureUnusedCapacity(gpa, size_hint); const result = r: { var aw: Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes); defer zg.string_bytes = aw.toArrayList(); - break :r parseStrLit(zg.tree, str_node, &aw.writer) catch |err| switch (err) { + break :r parseStrLit(&zg.tree, str_node, &aw.writer) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, }; }; diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 16294f3df6d763df2587b48fbb99e3b6615d478f..3f4eadddaabe3e442e112d3a76c02e9fcbe0439c 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1,14 +1,13 @@ -//! The simplest way to parse ZON at runtime is to use `fromSlice`/`fromSliceAlloc`. +//! Use `fromSlice`/`fromSlice` to parse a ZON string into a Zig value. Human readable errors +//! are written to an out parameter. //! -//! Note that if you need to parse ZON at compile time, you may use `@import`. +//! If your data is already available in tree form, consider `fromZoir`/`fromZoir`. //! -//! Parsing from individual Zoir nodes is also available: -//! * `fromZoir`/`fromZoirAlloc` -//! * `fromZoirNode`/`fromZoirNodeAlloc` -//! -//! For lower level control over parsing, see `std.zig.Zoir`. +//! 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; @@ -19,443 +18,474 @@ 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 = {}; -/// Configuration for the runtime parser. -pub const Options = struct { - /// If true, unknown fields do not error. - ignore_unknown_fields: bool = false, - /// If true, the parser cleans up partially parsed values on error. This requires some extra - /// bookkeeping, so you may want to turn it off if you don't need this feature (e.g. because - /// you're using arena allocation.) - free_on_error: bool = true, -}; +/// Errors encountered while parsing ZON. See `log` and `fatal` for reporting errors to the user. +pub const Diagnostics = struct { + errors: []const Error, -pub const Error = union(enum) { - zoir: Zoir.CompileError, - type_check: Error.TypeCheckFailure, - - pub const Note = union(enum) { - zoir: Zoir.CompileError.Note, - type_check: TypeCheckFailure.Note, + /// Log the failure with `std.log.err`. + pub fn log(self: *const Diagnostics, path: []const u8) void { + std.log.err("{f}", .{self.fmt(path)}); + } - pub const Iterator = struct { - index: usize = 0, - err: Error, - diag: *const Diagnostics, + 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; - pub fn next(self: *@This()) ?Note { - switch (self.err) { - .zoir => |err| { - if (self.index >= err.note_count) return null; - const note = err.getNotes(self.diag.zoir)[self.index]; - self.index += 1; - return .{ .zoir = note }; - }, - .type_check => |err| { - if (self.index >= err.getNoteCount()) return null; - const note = err.getNote(self.index); - self.index += 1; - return .{ .type_check = note }; - }, - } - } + const MyType = struct { + foo: u32, + bar: u32, }; - fn formatMessage(self: []const u8, w: *std.Io.Writer) std.Io.Writer.Error!void { - // Just writes the string for now, but we're keeping this behind a formatter so we have - // the option to extend it in the future to print more advanced messages (like `Error` - // does) without breaking the API. - try w.writeAll(self); - } - - pub fn fmtMessage(self: Note, diag: *const Diagnostics) std.fmt.Alt([]const u8, Note.formatMessage) { - return .{ .data = switch (self) { - .zoir => |note| note.msg.get(diag.zoir), - .type_check => |note| note.msg, - } }; - } - - pub fn getLocation(self: Note, diag: *const Diagnostics) Ast.Location { - switch (self) { - .zoir => |note| return zoirErrorLocation(diag.ast, note.token, note.node_or_offset), - .type_check => |note| return diag.ast.tokenLocation(note.offset, note.token), - } - } - }; - - pub const Iterator = struct { - index: usize = 0, - diag: *const Diagnostics, - - pub fn next(self: *@This()) ?Error { - if (self.index < self.diag.zoir.compile_errors.len) { - const result: Error = .{ .zoir = self.diag.zoir.compile_errors[self.index] }; - self.index += 1; - return result; - } - - if (self.diag.type_check) |err| { - if (self.index == self.diag.zoir.compile_errors.len) { - const result: Error = .{ .type_check = err }; - self.index += 1; - return result; - } - } - - return null; - } - }; - - const TypeCheckFailure = struct { - const Note = struct { - token: Ast.TokenIndex, - offset: u32, - msg: []const u8, - owned: bool, - - fn deinit(self: @This(), gpa: Allocator) void { - if (self.owned) gpa.free(self.msg); - } + 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 }; + }, }; - - message: []const u8, - owned: bool, - token: Ast.TokenIndex, - offset: u32, - note: ?@This().Note, - - fn deinit(self: @This(), gpa: Allocator) void { - if (self.note) |note| note.deinit(gpa); - if (self.owned) gpa.free(self.message); - } - - fn getNoteCount(self: @This()) usize { - return @intFromBool(self.note != null); - } - - fn getNote(self: @This(), index: usize) @This().Note { - assert(index == 0); - return self.note.?; - } - }; - - const FormatMessage = struct { - err: Error, - diag: *const Diagnostics, - }; - - fn formatMessage(self: FormatMessage, w: *std.Io.Writer) std.Io.Writer.Error!void { - switch (self.err) { - .zoir => |err| try w.writeAll(err.msg.get(self.diag.zoir)), - .type_check => |tc| try w.writeAll(tc.message), - } + _ = parsed; } - pub fn fmtMessage(self: @This(), diag: *const Diagnostics) std.fmt.Alt(FormatMessage, formatMessage) { - return .{ .data = .{ - .err = self, - .diag = diag, - } }; + /// 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)}); } - pub fn getLocation(self: @This(), diag: *const Diagnostics) Ast.Location { - return switch (self) { - .zoir => |err| return zoirErrorLocation( - diag.ast, - err.token, - err.node_or_offset, - ), - .type_check => |err| return diag.ast.tokenLocation(err.offset, err.token), + 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; } - pub fn iterateNotes(self: @This(), diag: *const Diagnostics) Note.Iterator { - return .{ .err = self, .diag = diag }; + /// 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 zoirErrorLocation(ast: Ast, maybe_token: Ast.OptionalTokenIndex, node_or_offset: u32) Ast.Location { + fn astLoc( + ast: *const Ast, + maybe_token: Ast.OptionalTokenIndex, + node_or_offset: u32, + ) Ast.Location { if (maybe_token.unwrap()) |token| { - var location = ast.tokenLocation(0, token); - location.column += node_or_offset; - return location; + 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); } } -}; -/// Information about the success or failure of a parse. -pub const Diagnostics = struct { - ast: Ast = .{ - .source = "", - .tokens = .empty, - .nodes = .empty, - .extra_data = &.{}, - .mode = .zon, - .errors = &.{}, - }, - zoir: Zoir = .{ - .nodes = .empty, - .extra = &.{}, - .limbs = &.{}, - .string_bytes = &.{}, - .compile_errors = &.{}, - .error_notes = &.{}, - }, - type_check: ?Error.TypeCheckFailure = null, - - fn assertEmpty(self: Diagnostics) void { - assert(self.ast.tokens.len == 0); - assert(self.zoir.nodes.len == 0); - assert(self.type_check == null); - } - - pub fn deinit(self: *Diagnostics, gpa: Allocator) void { - self.ast.deinit(gpa); - self.zoir.deinit(gpa); - if (self.type_check) |tc| tc.deinit(gpa); - self.* = undefined; - } - - pub fn iterateErrors(self: *const Diagnostics) Error.Iterator { - return .{ .diag = self }; - } - - pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void { - var errors = self.iterateErrors(); - while (errors.next()) |err| { - const loc = err.getLocation(self); - const msg = err.fmtMessage(self); - try w.print("{d}:{d}: error: {f}\n", .{ loc.line + 1, loc.column + 1, msg }); - - var notes = err.iterateNotes(self); - while (notes.next()) |note| { - const note_loc = note.getLocation(self); - const note_msg = note.fmtMessage(self); - try w.print("{d}:{d}: note: {f}\n", .{ - note_loc.line + 1, - note_loc.column + 1, - note_msg, + 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) |e| { + try w.print("{s}:", .{self.path}); + try w.print("{d}:{d}: error: {s}\n", .{ + e.loc.line + 1, + e.loc.column + 1, + e.msg, }); + + for (e.notes) |note| { + try w.print("{s}:", .{self.path}); + try w.print("{d}:{d}: note: {s}\n", .{ + 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, + }; + } + }; }; -/// Parses the given slice as ZON. -/// -/// Returns `error.OutOfMemory` on allocation failure, or `error.ParseZon` error if the ZON is -/// invalid or can not be deserialized into type `T`. -/// -/// When the parser returns `error.ParseZon`, it will also store a human readable explanation in -/// `diag` if non null. If diag is not null, it must be initialized to `.{}`. -/// -/// Asserts at compile time that the result type doesn't contain pointers. As such, the result -/// doesn't need to be freed. -/// -/// An allocator is still required for temporary allocations made during parsing. -pub fn fromSlice( - T: type, +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, - diag: ?*Diagnostics, + /// 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 }!T { +) error{ OutOfMemory, ParseZon }!void { comptime assert(!requiresAllocator(T)); - return fromSliceAlloc(T, gpa, source, diag, options); + try fromSliceInner(T, value, true, options); } -/// Like `fromSlice`, but the result may contain pointers. To automatically free the result, see -/// `free`. -pub fn fromSliceAlloc( - /// The type to deserialize into. May not be or contain any of the following types: - /// * Any comptime-only type, except in a comptime field - /// * `type` - /// * `void`, except as a union payload - /// * `noreturn` - /// * An error set/error union - /// * A many-pointer or C-pointer - /// * An opaque type, including `anyopaque` - /// * An async frame type, including `anyframe` and `anyframe->T` - /// * A function - /// - /// All other types are valid. Unsupported types will fail at compile time. +fn fromSliceInner( T: type, - gpa: Allocator, - source: [:0]const u8, - diag: ?*Diagnostics, + value: *T, + initialized: bool, options: Options, -) error{ OutOfMemory, ParseZon }!T { - if (diag) |s| s.assertEmpty(); +) error{ OutOfMemory, ParseZon }!void { + var errors: std.ArrayList(Diagnostics.Error) = .empty; + defer options.diagnostics.* = .{ .errors = errors.items }; - var ast = try std.zig.Ast.parse(gpa, source, .{ .mode = .zon }); - defer if (diag == null) ast.deinit(gpa); - if (diag) |s| s.ast = ast; + var ast = try std.zig.Ast.parse(options.gpa, options.source, .{ .mode = .zon }); + defer ast.deinit(options.gpa); - // If there's no diagnostics, Zoir exists for the lifetime of this function. If there is a - // diagnostics, ownership is transferred to diagnostics. - var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); - defer if (diag == null) zoir.deinit(gpa); + var zoir = try ZonGen.generate(options.gpa, ast, .{ .parse_str_lits = false }); + defer zoir.deinit(options.gpa); - if (diag) |s| s.* = .{}; - return fromZoirAlloc(T, gpa, ast, zoir, diag, options); + 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, - ast: Ast, - zoir: Zoir, - diag: ?*Diagnostics, - options: Options, -) error{ParseZon}!T { +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)); - var buf: [0]u8 = .{}; - var failing_allocator = std.heap.FixedBufferAllocator.init(&buf); - return fromZoirAlloc( - T, - failing_allocator.allocator(), - ast, - zoir, - diag, - options, - ) catch |err| switch (err) { - error.OutOfMemory => unreachable, // Checked by comptime assertion above - else => |e| return e, - }; + return fromZoir(T, options); } -/// Like `fromSliceAlloc`, but operates on `Zoir` instead of ZON source. -pub fn fromZoirAlloc( +/// Like `updateFromSlice`, but operates on `Zoir` instead of ZON source. +pub fn updateFromZoir( T: type, - gpa: Allocator, - ast: Ast, - zoir: Zoir, - diag: ?*Diagnostics, - options: Options, -) error{ OutOfMemory, ParseZon }!T { - return fromZoirNodeAlloc(T, gpa, ast, zoir, .root, diag, options); + value: *T, + options: FromZoirOptions, +) error{ OutOfMemory, ParseZon }!void { + return fromZoirInner(T, value, true, options); } -/// Like `fromZoir`, but the parse starts at `node` instead of root. -pub fn fromZoirNode( +/// Like `updateFromSliceNoAlloc`, but operates on `Zoir` instead of ZON source. +pub fn updateFromZoirNoAlloc( T: type, - ast: Ast, - zoir: Zoir, - node: Zoir.Node.Index, - diag: ?*Diagnostics, - options: Options, -) error{ParseZon}!T { + value: *T, + options: FromZoirOptions, +) error{ OutOfMemory, ParseZon }!void { comptime assert(!requiresAllocator(T)); - var buf: [0]u8 = .{}; - var failing_allocator = std.heap.FixedBufferAllocator.init(&buf); - return fromZoirNodeAlloc( - T, - failing_allocator.allocator(), - ast, - zoir, - node, - diag, - options, - ) catch |err| switch (err) { - error.OutOfMemory => unreachable, // Checked by comptime assertion above - else => |e| return e, - }; + return updateFromZoir(T, value, options); } -/// Like `fromZoirAlloc`, but the parse starts at `node` instead of root. -pub fn fromZoirNodeAlloc( +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, + .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, - gpa: Allocator, - ast: Ast, - zoir: Zoir, - node: Zoir.Node.Index, - diag: ?*Diagnostics, - options: Options, -) error{ OutOfMemory, ParseZon }!T { + value: *T, + initialized: bool, + options: InnerOptions, +) error{ OutOfMemory, ParseZon }!void { comptime assert(canParseType(T)); - if (diag) |s| { - s.assertEmpty(); - s.ast = ast; - s.zoir = zoir; - } - - if (zoir.hasCompileErrors()) { + 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 = .{ - .gpa = gpa, - .ast = ast, - .zoir = zoir, - .options = options, - .diag = diag, + .arena = options.arena, + .ast = options.ast, + .zoir = options.zoir, + .ignore_unknown_fields = options.ignore_unknown_fields, + .errors = options.errors, }; - return parser.parseExpr(T, node); -} - -/// Frees ZON values. -/// -/// Provided for convenience, you may also free these values on your own using the same allocator -/// passed into the parser. -/// -/// Asserts at comptime that sufficient information is available via the type system to free this -/// value. Untagged unions, for example, will fail this assert. -pub fn free(gpa: Allocator, value: anytype) void { - const Value = @TypeOf(value); - - _ = valid_types; - switch (@typeInfo(Value)) { - .bool, .int, .float, .@"enum" => {}, - .pointer => |pointer| { - switch (pointer.size) { - .one => { - free(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| { - free(gpa, item); - } - gpa.free(value); - }, - .many, .c => comptime unreachable, - } - }, - .array => { - freeArray(gpa, @TypeOf(value), &value); - }, - .vector => |vector| { - const array: [vector.len]vector.child = value; - freeArray(gpa, @TypeOf(array), &array); - }, - .@"struct" => |@"struct"| inline for (@"struct".field_names) |field_name| { - free(gpa, @field(value, field_name)); - }, - .@"union" => |@"union"| if (@"union".tag_type == null) { - if (comptime requiresAllocator(Value)) unreachable; - } else switch (value) { - inline else => |_, tag| { - free(gpa, @field(value, @tagName(tag))); - }, - }, - .optional => if (value) |some| { - free(gpa, some); - }, - .void => {}, - else => comptime unreachable, - } -} - -fn freeArray(gpa: Allocator, comptime A: type, array: *const A) void { - for (array) |elem| free(gpa, elem); + try parser.parseExprInto(options.node, value, initialized); } fn requiresAllocator(T: type) bool { @@ -480,62 +510,93 @@ fn requiresAllocator(T: type) bool { } const Parser = struct { - gpa: Allocator, - ast: Ast, - zoir: Zoir, - diag: ?*Diagnostics, - options: Options, + arena: Allocator, + ast: *const Ast, + zoir: *const Zoir, + errors: *std.ArrayList(Diagnostics.Error), + ignore_unknown_fields: bool, const ParseExprError = error{ ParseZon, OutOfMemory }; - fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprError!T { - return self.parseExprInner(T, node) catch |err| switch (err) { - error.WrongType => return self.failExpectedType(T, node), + 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 parseExprInner( + fn parseExprIntoInner( self: *@This(), - T: type, node: Zoir.Node.Index, - ) ParseExprInnerError!T { - if (T == Zoir.Node.Index) { - return node; + out: anytype, + initialized: bool, + ) ParseExprInnerError!void { + if (@TypeOf(out.*) == Zoir.Node.Index) { + out.* = node; + return; } - switch (@typeInfo(T)) { + switch (@typeInfo(@TypeOf(out.*))) { .optional => |optional| if (node.get(self.zoir) == .null) { - return null; + out.* = null; + } else if (initialized and out.* != null) { + try self.parseExprIntoInner(node, &out.*.?, true); } else { - return try self.parseExprInner(optional.child, node); + var some: optional.child = undefined; + try self.parseExprIntoInner(node, &some, false); + out.* = some; }, - .bool => return self.parseBool(node), - .int => return self.parseInt(T, node), - .float => return self.parseFloat(T, node), - .@"enum" => return self.parseEnumLiteral(T, node), + .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 => { - const result = try self.gpa.create(pointer.child); - errdefer self.gpa.destroy(result); - result.* = try self.parseExprInner(pointer.child, node); - return result; + // 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; }, - .slice => return self.parseSlicePointer(T, node), else => comptime unreachable, }, - .array => return self.parseArray(T, node), - .vector => |vector| { - const A = [vector.len]vector.child; - return try self.parseArray(A, node); - }, + .array => try self.parseArrayInto(node, out), + .vector => try self.parseVectorInto(node, out), .@"struct" => |@"struct"| if (@"struct".is_tuple) - return self.parseTuple(T, node) + try self.parseTupleInto(node, out) else - return self.parseStruct(T, node), - .@"union" => return self.parseUnion(T, node), + try self.parseStructInto(node, out, initialized), + .@"union" => try self.parseUnionInto(node, out, initialized), else => comptime unreachable, } @@ -545,7 +606,7 @@ const Parser = struct { /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just /// referred to by the type of container. fn failExpectedType( - self: @This(), + self: Parser, T: type, node: Zoir.Node.Index, ) error{ ParseZon, OutOfMemory } { @@ -554,7 +615,7 @@ const Parser = struct { } fn failExpectedTypeInner( - self: @This(), + self: Parser, T: type, opt: bool, node: Zoir.Node.Index, @@ -624,7 +685,7 @@ const Parser = struct { } } - fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { + fn parseBool(self: Parser, node: Zoir.Node.Index) !bool { switch (node.get(self.zoir)) { .true => return true, .false => return false, @@ -632,7 +693,7 @@ const Parser = struct { } } - fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T { + 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 @@ -649,7 +710,7 @@ const Parser = struct { } } - fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T { + 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), @@ -664,7 +725,7 @@ const Parser = struct { } } - fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T { + 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 @@ -684,7 +745,7 @@ const Parser = struct { } } - fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T { + 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), @@ -693,13 +754,13 @@ const Parser = struct { } } - fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T { + 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.gpa); + 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; @@ -728,7 +789,7 @@ const Parser = struct { } } - fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T { + 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 @@ -738,68 +799,68 @@ const Parser = struct { } // Allocate the slice - const slice = try self.gpa.allocWithOptions( + const slice = try self.arena.allocWithOptions( pointer.child, nodes.len, .fromByteUnitsOptional(pointer.attrs.@"align"), pointer.sentinel(), ); - errdefer self.gpa.free(slice); // Parse the elements and return the slice for (slice, 0..) |*elem, i| { - errdefer if (self.options.free_on_error) { - for (slice[0..i]) |item| { - free(self.gpa, item); - } - }; - elem.* = try self.parseExpr(pointer.child, nodes.at(@intCast(i))); + try self.parseExprInto(nodes.at(@intCast(i)), elem, false); } return slice; } - fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T { + 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_info = @typeInfo(T).array; + const array = @typeInfo(@TypeOf(out.*)).array; // Check if the size matches - if (nodes.len < array_info.len) { + if (nodes.len < array.len) { return self.failNodeFmt( node, "expected {} array elements; found {}", - .{ array_info.len, nodes.len }, + .{ array.len, nodes.len }, ); - } else if (nodes.len > array_info.len) { + } else if (nodes.len > array.len) { return self.failNodeFmt( - nodes.at(array_info.len), + nodes.at(array.len), "index {} outside of array of length {}", - .{ array_info.len, array_info.len }, + .{ array.len, array.len }, ); } // Parse the elements and return the array - var result: T = undefined; - for (&result, 0..) |*elem, i| { - // If we fail to parse this field, free all fields before it - errdefer if (self.options.free_on_error) { - for (result[0..i]) |item| { - free(self.gpa, item); - } - }; + for (out, 0..) |*elem, i| { + try self.parseExprInto(nodes.at(@intCast(i)), elem, false); + } - elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i))); + if (array.sentinel()) |s| { + out[array.len] = s; } - if (array_info.sentinel()) |s| result[result.len] = s; - return result; } - fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T { + 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, @@ -807,7 +868,7 @@ const Parser = struct { else => return error.WrongType, }; - const info = @typeInfo(T).@"struct"; + 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. @@ -821,29 +882,12 @@ const Parser = struct { }; // Parse the struct - var result: T = undefined; var field_found: [info.field_names.len]bool = @splat(false); - - // If we fail partway through, free all already initialized fields - var initialized: usize = 0; - errdefer if (self.options.free_on_error and info.field_names.len > 0) { - for (fields.names[0..initialized]) |name_runtime| { - switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) { - inline 0...(info.field_names.len - 1) => |name_index| { - const name = info.field_names[name_index]; - free(self.gpa, @field(result, name)); - }, - else => unreachable, // Can't be out of bounds - } - } - }; - - // Fill in the fields we found for (0..fields.names.len) |i| { const name = fields.names[i].get(self.zoir); const field_index = field_indices.get(name) orelse { - if (self.options.ignore_unknown_fields) continue; - return self.failUnexpected(T, "field", node, i, name); + 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); @@ -857,46 +901,43 @@ const Parser = struct { switch (field_index) { inline 0...(info.field_names.len - 1) => |j| { if (info.field_attrs[j].@"comptime") unreachable; - - @field(result, info.field_names[j]) = try self.parseExpr( - info.field_types[j], + try self.parseExprInto( fields.vals.at(@intCast(i)), + &@field(out, info.field_names[j]), + initialized, ); }, else => unreachable, // Can't be out of bounds } - - initialized += 1; } // Fill in any missing default fields - 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(result, info.field_names[i]) = default; - } else { - return self.failNodeFmt( - node, - "missing required field {s}", - .{info.field_names[i]}, - ); + 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]}, + ); + } } } } - - return result; } - fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T { + 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, }; - var result: T = undefined; - const info = @typeInfo(T).@"struct"; + const info = @typeInfo(@TypeOf(out.*)).@"struct"; if (nodes.len > info.field_names.len) { return self.failNodeFmt( @@ -910,38 +951,36 @@ const Parser = struct { // Check if we're out of bounds if (i >= nodes.len) { if (info.field_attrs[i].defaultValue(info.field_types[i])) |default| { - @field(result, info.field_names[i]) = default; + @field(out.*, info.field_names[i]) = default; } else { return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); } } else { - // If we fail to parse this field, free all fields before it - errdefer if (self.options.free_on_error) { - inline for (0..i) |j| { - if (j >= i) break; - free(self.gpa, result[j]); - } - }; - if (info.field_attrs[i].@"comptime") { return self.failComptimeField(node, i); } else { - result[i] = try self.parseExpr(info.field_types[i], nodes.at(i)); + try self.parseExprInto(nodes.at(i), &out[i], false); } } } - - return result; } - fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T { - const @"union" = @typeInfo(T).@"union"; + 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; + 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 }; } @@ -961,7 +1000,13 @@ const Parser = struct { 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(T, "field", node, null, field_name_str); + return self.failUnexpected( + @TypeOf(out.*), + "field", + node, + null, + field_name_str, + ); }; // Initialize the union from the given field. @@ -972,7 +1017,7 @@ const Parser = struct { return self.failNode(node, "expected union"); // Instantiate the union - return @unionInit(T, @"union".field_names[i], {}); + out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], {}); }, else => unreachable, // Can't be out of bounds } @@ -987,15 +1032,32 @@ const Parser = struct { 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(T, "field", node, 0, field_name_str); + 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 value = try self.parseExpr(@"union".field_types[i], field_val); - return @unionInit(T, @"union".field_names[i], value); + 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 @@ -1006,41 +1068,37 @@ const Parser = struct { } fn failTokenFmt( - self: @This(), + 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, null); + return self.failTokenFmtNote(token, offset, fmt, args, &.{}); } fn failTokenFmtNote( - self: @This(), + self: Parser, token: Ast.TokenIndex, offset: u32, comptime fmt: []const u8, args: anytype, - note: ?Error.TypeCheckFailure.Note, + notes: []Diagnostics.Note, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); comptime assert(args.len > 0); - if (self.diag) |s| s.type_check = .{ - .token = token, - .offset = offset, - .message = std.fmt.allocPrint(self.gpa, fmt, args) catch |err| { - if (note) |n| n.deinit(self.gpa); - return err; - }, - .owned = true, - .note = note, - }; + 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: @This(), + self: Parser, node: Zoir.Node.Index, comptime fmt: []const u8, args: anytype, @@ -1051,32 +1109,31 @@ const Parser = struct { } fn failToken( - self: @This(), - failure: Error.TypeCheckFailure, - ) error{ParseZon} { + self: Parser, + failure: Diagnostics.Error, + ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); - if (self.diag) |s| s.type_check = failure; + try self.errors.append(self.arena, failure); return error.ParseZon; } fn failNode( - self: @This(), + self: Parser, node: Zoir.Node.Index, - message: []const u8, - ) error{ParseZon} { + msg: []const u8, + ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = self.ast.nodeMainToken(node.getAstNode(self.zoir)); - return self.failToken(.{ - .token = token, - .offset = 0, - .message = message, - .owned = false, - .note = null, - }); + return self.failToken(.init(self.ast, .{ + .token = .fromToken(token), + .node_or_offset = 0, + .msg = msg, + .notes = &.{}, + })); } fn failCannotRepresent( - self: @This(), + self: Parser, T: type, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon } { @@ -1085,7 +1142,7 @@ const Parser = struct { } fn failUnexpected( - self: @This(), + self: Parser, T: type, item_kind: []const u8, node: Zoir.Node.Index, @@ -1093,8 +1150,6 @@ const Parser = struct { name: []const u8, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); - if (self.diag == null) return error.ParseZon; - const gpa = self.gpa; const token = if (field) |f| b: { var buf: [2]Ast.Node.Index = undefined; const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; @@ -1103,38 +1158,35 @@ const Parser = struct { } else self.ast.nodeMainToken(node.getAstNode(self.zoir)); switch (@typeInfo(T)) { inline .@"struct", .@"union", .@"enum" => |info| { - const note: Error.TypeCheckFailure.Note = if (info.field_names.len == 0) b: { - break :b .{ - .token = token, - .offset = 0, + 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", - .owned = false, - }; - } else b: { - const msg = "supported: "; - var buf: std.ArrayList(u8) = try .initCapacity(gpa, 64); - defer buf.deinit(gpa); - try buf.appendSlice(gpa, msg); + })); + } 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) try buf.appendSlice(gpa, ", "); - try buf.print(gpa, "'{f}'", .{std.zig.fmtIdFlags(field_name, .{ + 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; } - break :b .{ - .token = token, - .offset = 0, - .msg = try buf.toOwnedSlice(gpa), - .owned = true, - }; - }; + 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 }, - note, + notes.items, ); }, else => comptime unreachable, @@ -1145,12 +1197,11 @@ const Parser = struct { // 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: @This(), + self: Parser, node: Zoir.Node.Index, field: usize, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); - if (self.diag == null) return error.ParseZon; 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: { @@ -1161,13 +1212,12 @@ const Parser = struct { const value_node = array_init.ast.elements[field]; break :b self.ast.firstToken(value_node); }; - return self.failToken(.{ - .token = token, - .offset = 0, - .message = "cannot initialize comptime field", - .owned = false, - .note = null, - }); + return self.failToken(.init(self.ast, .{ + .token = .fromToken(token), + .node_or_offset = 0, + .msg = "cannot initialize comptime field", + .notes = &.{}, + })); } }; @@ -1329,106 +1379,198 @@ test "std.zon requiresAllocator" { test "std.zon ast errors" { const gpa = std.testing.allocator; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); + 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, - fromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &diag, .{}), + fromSliceNoAlloc(struct {}, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x = 1 .y = 2}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{f}", .{diag}); + 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), fromSlice(u8, gpa, + try std.testing.expectEqual(@as(u8, 10), fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = \\// comment \\10 // comment \\// comment - , null, .{})); + , + .diagnostics = &diagnostics, + })); { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = \\//! comment \\10 // comment \\// comment - , &diag, .{})); + , + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: expected expression, found 'a document comment'\n", - "{f}", - .{diag}, - ); + \\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 failing_allocator = std.testing.FailingAllocator.init(gpa, .{ - .fail_index = 0, - .resize_fail_index = 0, - }); - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.OutOfMemory, fromSliceAlloc( - []const u8, - failing_allocator.allocator(), - "\"foo\"", - &diag, - .{}, - )); - try std.testing.expectFmt("", "{f}", .{diag}); + 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 fromSliceAlloc syntax error" { +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, - fromSlice(u8, std.testing.allocator, ".{", null, .{}), + 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 fromSlice(?u32, gpa, "null", null, .{}); + const none = try fromSliceNoAlloc(?u32, .{ + .gpa = gpa, + .arena = arena, + .source = "null", + .diagnostics = &diagnostics, + }); try std.testing.expect(none == null); - const some = try fromSlice(?u32, gpa, "1", 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 fromSliceAlloc(?[]const u8, gpa, "null", null, .{}); + const none = try fromSlice(?[]const u8, .{ + .gpa = gpa, + .arena = arena, + .source = "null", + .diagnostics = &diagnostics, + }); try std.testing.expect(none == null); - const some = try fromSliceAlloc(?[]const u8, gpa, "\"foo\"", null, .{}); - defer free(gpa, some); + 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 fromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{}); + 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 fromSlice(Tagged, gpa, ".{.@\"y y\" = true}", null, .{}); + 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 fromSlice(Tagged, gpa, ".z", null, .{}); + 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 fromSlice(Tagged, gpa, ".@\"z z\"", null, .{}); + 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 fromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{}); + 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 fromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{}); + const untagged_y = try fromSliceNoAlloc(Untagged, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.@\"y y\" = true}", + .diagnostics = &diagnostics, + }); try std.testing.expect(untagged_y.@"y y"); } @@ -1436,106 +1578,149 @@ test "std.zon unions" { { const Union = union(enum) { bar: []const u8, baz: bool }; - const noalloc = try fromSliceAlloc(Union, gpa, ".{.baz = false}", null, .{}); + 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 fromSliceAlloc(Union, gpa, ".{.bar = \"qux\"}", null, .{}); - defer free(gpa, alloc); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(Union, gpa, ".{.z=2.5}", &diag, .{}), + fromSlice(Union, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.z=2.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:4: error: unexpected field 'z' - \\1:4: note: supported: 'x', 'y' + \\input.zon:1:4: error: unexpected field 'z' + \\input.zon:1:4: note: supported: 'x', 'y' \\ - , - "{f}", - .{diag}, - ); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Explicit void field { const Union = union(enum) { x: void }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(Union, gpa, ".{.x=1}", &diag, .{}), + fromSlice(Union, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x=1}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{f}", .{diag}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(Union, gpa, ".{.x = 1.5, .y = true}", &diag, .{}), + fromSlice(Union, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x = 1.5, .y = true}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:2: error: expected union\n", "{f}", .{diag}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(Union, gpa, ".{}", &diag, .{}), + fromSlice(Union, .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:2: error: expected union\n", "{f}", .{diag}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Union, gpa, ".x", &diag, .{})); - try std.testing.expectFmt("1:2: error: expected union\n", "{f}", .{diag}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Union, gpa, ".y", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(Union, .{ + .gpa = gpa, + .arena = arena, + .source = ".y", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - \\1:2: error: unexpected field 'y' - \\1:2: note: supported: 'x' + \\input.zon:1:2: error: unexpected field 'y' + \\input.zon:1:2: note: supported: 'x' \\ - , - "{f}", - .{diag}, - ); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Non void field for enum literal coercion { const Union = union(enum) { x: f32 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Union, gpa, ".x", &diag, .{})); - try std.testing.expectFmt("1:2: error: expected union\n", "{f}", .{diag}); + 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) { @@ -1544,16 +1729,36 @@ test "std.zon structs" { const Vec2 = struct { x: f32, y: f32 }; const Vec3 = struct { x: f32, y: f32, z: f32 }; - const zero = try fromSlice(Vec0, gpa, ".{}", null, .{}); + const zero = try fromSliceNoAlloc(Vec0, .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(Vec0{}, zero); - const one = try fromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{}); + 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 fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", null, .{}); + 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 fromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", null, .{}); + 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); } @@ -1561,57 +1766,62 @@ test "std.zon structs" { { const Foo = struct { bar: []const u8, baz: []const []const u8 }; - const parsed = try fromSliceAlloc( - Foo, - gpa, - ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", - null, - .{}, - ); - defer free(gpa, parsed); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &diag, .{}), + fromSliceNoAlloc(Vec2, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x=1.5, .z=2.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:12: error: unexpected field 'z' - \\1:12: note: supported: 'x', 'y' + \\input.zon:1:12: error: unexpected field 'z' + \\input.zon:1:12: note: supported: 'x', 'y' \\ - , - "{f}", - .{diag}, - ); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Duplicate field { const Vec2 = struct { x: f32, y: f32 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5, .x=3.5}", &diag, .{}), + fromSliceNoAlloc(Vec2, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x=1.5, .x=2.5, .x=3.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:4: error: duplicate struct field name - \\1:12: note: duplicate name here + \\input.zon:1:4: error: duplicate struct field name + \\input.zon:1:12: note: duplicate name here \\ - , "{f}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Ignore unknown fields { const Vec2 = struct { x: f32, y: f32 = 2.0 }; - const parsed = try fromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", null, .{ + 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); } @@ -1619,70 +1829,102 @@ test "std.zon structs" { // Unknown field when struct has no fields (regression test) { const Vec2 = struct {}; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &diag, .{}), + fromSliceNoAlloc(Vec2, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x=1.5, .z=2.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:4: error: unexpected field 'x' - \\1:4: note: none expected + \\input.zon:1:4: error: unexpected field 'x' + \\input.zon:1:4: note: none expected \\ - , "{f}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Missing field { const Vec2 = struct { x: f32, y: f32 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Vec2, gpa, ".{.x=1.5}", &diag, .{}), + fromSliceNoAlloc(Vec2, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.x=1.5}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:2: error: missing required field y\n", "{f}", .{diag}); + 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 fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); + 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 fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &diag, .{}); + 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( - \\1:18: error: cannot initialize comptime field + \\input.zon:1:18: error: cannot initialize comptime field \\ - , "{f}", .{diag}); + , "{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 fromSlice(Vec0, gpa, ".{ .x = .x }", null, .{}); + 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 fromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{}); + 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); } @@ -1690,79 +1932,92 @@ test "std.zon structs" { { // Structs { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSlice(struct {}, gpa, "Empty{}", &diag, .{}); + const parsed = fromSliceNoAlloc(struct {}, .{ + .gpa = gpa, + .arena = arena, + .source = "Empty{}", + .diagnostics = &diagnostics, + }); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( - \\1:1: error: types are not available in ZON - \\1:1: note: replace the type with '.' + \\input.zon:1:1: error: types are not available in ZON + \\input.zon:1:1: note: replace the type with '.' \\ - , "{f}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Arrays { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSlice([3]u8, gpa, "[3]u8{1, 2, 3}", &diag, .{}); + 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( - \\1:1: error: types are not available in ZON - \\1:1: note: replace the type with '.' + \\input.zon:1:1: error: types are not available in ZON + \\input.zon:1:1: note: replace the type with '.' \\ - , "{f}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Slices { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSliceAlloc([]u8, gpa, "[]u8{1, 2, 3}", &diag, .{}); + 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( - \\1:1: error: types are not available in ZON - \\1:1: note: replace the type with '.' + \\input.zon:1:1: error: types are not available in ZON + \\input.zon:1:1: note: replace the type with '.' \\ - , "{f}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Tuples { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSlice( - struct { u8, u8, u8 }, - gpa, - "Tuple{1, 2, 3}", - &diag, - .{}, - ); + 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( - \\1:1: error: types are not available in ZON - \\1:1: note: replace the type with '.' + \\input.zon:1:1: error: types are not available in ZON + \\input.zon:1:1: note: replace the type with '.' \\ - , "{f}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Nested { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSlice(struct {}, gpa, ".{ .x = Tuple{1, 2, 3} }", &diag, .{}); + 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( - \\1:9: error: types are not available in ZON - \\1:9: note: replace the type with '.' + \\input.zon:1:9: error: types are not available in ZON + \\input.zon:1:9: note: replace the type with '.' \\ - , "{f}", .{diag}); + , "{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) { @@ -1771,152 +2026,262 @@ test "std.zon tuples" { const Tuple2 = struct { f32, bool }; const Tuple3 = struct { f32, bool, u8 }; - const zero = try fromSlice(Tuple0, gpa, ".{}", null, .{}); + const zero = try fromSliceNoAlloc(Tuple0, .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(Tuple0{}, zero); - const one = try fromSlice(Tuple1, gpa, ".{1.2}", null, .{}); + 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 fromSlice(Tuple2, gpa, ".{1.2, true}", null, .{}); + 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 fromSlice(Tuple3, gpa, ".{1.2, false, 3}", null, .{}); + 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 fromSliceAlloc(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{}); - defer free(gpa, parsed); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Tuple, gpa, ".{0.5, true, 123}", &diag, .{}), + fromSliceNoAlloc(Tuple, .{ + .gpa = gpa, + .arena = arena, + .source = ".{0.5, true, 123}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{f}", .{diag}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Tuple, gpa, ".{0.5}", &diag, .{}), + fromSliceNoAlloc(Tuple, .{ + .gpa = gpa, + .arena = arena, + .source = ".{0.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:2: error: missing tuple field with index 1\n", - "{f}", - .{diag}, - ); + \\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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Tuple, gpa, ".{.foo = 10.0}", &diag, .{}), + fromSliceNoAlloc(Tuple, .{ + .gpa = gpa, + .arena = arena, + .source = ".{.foo = 10.0}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:2: error: expected tuple\n", "{f}", .{diag}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Struct, gpa, ".{10.0}", &diag, .{}), + fromSliceNoAlloc(Struct, .{ + .gpa = gpa, + .arena = arena, + .source = ".{10.0}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:2: error: expected struct\n", "{f}", .{diag}); + 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 fromSlice(Vec2, gpa, ".{ 1.2 }", null, .{}); + 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 }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const parsed = fromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &diag, .{}); + 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( - \\1:9: error: cannot initialize comptime field + \\input.zon:1:9: error: cannot initialize comptime field \\ - , "{f}", .{diag}); + , "{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 fromSlice([0]u8, gpa, ".{}", null, .{}); + 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 fromSlice([1]u8, gpa, ".{'a'}", null, .{}); + 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 fromSlice([2]u8, gpa, ".{'a', 'b'}", null, .{}); + 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 fromSlice([2]u8, gpa, ".{'a', 'b',}", null, .{}); + 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 fromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + 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 fromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + 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 fromSliceAlloc([]const u8, gpa, ".{}", null, .{}); - defer free(gpa, zero); + 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 fromSliceAlloc([]u8, gpa, ".{'a'}", null, .{}); - defer free(gpa, one); + const one = try fromSlice([]u8, .{ + .gpa = gpa, + .arena = arena, + .source = ".{'a'}", + .diagnostics = &diagnostics, + }); try std.testing.expectEqualSlices(u8, &.{'a'}, one); - const two = try fromSliceAlloc([]const u8, gpa, ".{'a', 'b'}", null, .{}); - defer free(gpa, two); + 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 fromSliceAlloc([]const u8, gpa, ".{'a', 'b',}", null, .{}); - defer free(gpa, two_comma); + 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 fromSliceAlloc([]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); - defer free(gpa, three); + 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 fromSliceAlloc([:'z']const u8, gpa, ".{'a', 'b', 'c'}", null, .{}); - defer free(gpa, sentinel); + 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); } @@ -1926,16 +2291,24 @@ test "std.zon arrays and slices" { { // Arrays { - const parsed = try fromSliceAlloc([1][]const u8, gpa, ".{\"abc\"}", null, .{}); - defer free(gpa, parsed); + 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 fromSliceAlloc([]const []const u8, gpa, ".{\"abc\"}", null, .{}); - defer free(gpa, parsed); + 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); } @@ -1945,7 +2318,12 @@ test "std.zon arrays and slices" { { // Arrays { - const sentinel = try fromSlice([1:2]u8, gpa, ".{1}", null, .{}); + 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]); @@ -1953,8 +2331,12 @@ test "std.zon arrays and slices" { // Slice literals { - const sentinel = try fromSliceAlloc([:2]align(4) u8, gpa, ".{1}", null, .{}); - defer free(gpa, sentinel); + 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]); @@ -1963,86 +2345,106 @@ test "std.zon arrays and slices" { // Expect 0 find 3 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &diag, .{}), + fromSliceNoAlloc([0]u8, .{ + .gpa = gpa, + .arena = arena, + .source = ".{'a', 'b', 'c'}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:3: error: index 0 outside of array of length 0\n", - "{f}", - .{diag}, - ); + \\input.zon:1:3: error: index 0 outside of array of length 0 + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Expect 1 find 2 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([1]u8, gpa, ".{'a', 'b'}", &diag, .{}), + fromSliceNoAlloc([1]u8, .{ + .gpa = gpa, + .arena = arena, + .source = ".{'a', 'b'}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:8: error: index 1 outside of array of length 1\n", - "{f}", - .{diag}, - ); + \\input.zon:1:8: error: index 1 outside of array of length 1 + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Expect 2 find 1 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([2]u8, gpa, ".{'a'}", &diag, .{}), + fromSliceNoAlloc([2]u8, .{ + .gpa = gpa, + .arena = arena, + .source = ".{'a'}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:2: error: expected 2 array elements; found 1\n", - "{f}", - .{diag}, - ); + \\input.zon:1:2: error: expected 2 array elements; found 1 + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Expect 3 find 0 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([3]u8, gpa, ".{}", &diag, .{}), + fromSliceNoAlloc([3]u8, .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:2: error: expected 3 array elements; found 0\n", - "{f}", - .{diag}, - ); + \\input.zon:1:2: error: expected 3 array elements; found 0 + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Wrong inner type { // Array { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &diag, .{}), + fromSliceNoAlloc([3]bool, .{ + .gpa = gpa, + .arena = arena, + .source = ".{'a', 'b', 'c'}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:3: error: expected type 'bool' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Slice { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]bool, gpa, ".{'a', 'b', 'c'}", &diag, .{}), + fromSlice([]bool, .{ + .gpa = gpa, + .arena = arena, + .source = ".{'a', 'b', 'c'}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:3: error: expected type 'bool' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } } @@ -2050,87 +2452,109 @@ test "std.zon arrays and slices" { { // Array { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([3]u8, gpa, "'a'", &diag, .{}), + fromSliceNoAlloc([3]u8, .{ + .gpa = gpa, + .arena = arena, + .source = "'a'", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Slice { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]u8, gpa, "'a'", &diag, .{}), + fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "'a'", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + 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) { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]u8, gpa, " &.{'a', 'b', 'c'}", &diag, .{}), + fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = " &.{'a', 'b', 'c'}", .diagnostics = &diagnostics }), ); try std.testing.expectFmt( - "1:3: error: pointers are not available in ZON\n", - "{f}", - .{diag}, - ); + \\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 fromSliceAlloc([]const u8, gpa, "\"abc\"", null, .{}); - defer free(gpa, parsed); + 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 fromSliceAlloc([]const u8, gpa, "\"ab\\nc\"", null, .{}); - defer free(gpa, parsed); + 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 fromSliceAlloc([]const u8, gpa, "\"ab\\x00c\"", null, .{}); - defer free(gpa, parsed); + 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 { { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]u8, gpa, "\"abcd\"", &diag, .{}), + fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "\"abcd\"", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]u8, gpa, "\\\\abcd", &diag, .{}), + fromSlice([]u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\abcd", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } } @@ -2141,50 +2565,58 @@ test "std.zon string literal" { defer ast.deinit(gpa); var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([4:0]u8, gpa, "\"abcd\"", &diag, .{}), + fromSliceNoAlloc([4:0]u8, .{ + .gpa = gpa, + .arena = arena, + .source = "\"abcd\"", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice([4:0]u8, gpa, "\\\\abcd", &diag, .{}), + fromSliceNoAlloc([4:0]u8, .{ + .gpa = gpa, + .arena = arena, + .source = "\\\\abcd", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + 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 fromSliceAlloc( - [:0]const u8, - gpa, - "\"abc\"", - null, - .{}, - ); - defer free(gpa, parsed); + 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 fromSliceAlloc( - [:0]const u8, - gpa, - "\\\\abc", - null, - .{}, - ); - defer free(gpa, parsed); + 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]); } @@ -2193,102 +2625,121 @@ test "std.zon string literal" { // Other value terminated slices { { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([:1]const u8, gpa, "\"foo\"", &diag, .{}), + fromSlice([:1]const u8, .{ .gpa = gpa, .arena = arena, .source = "\"foo\"", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([:1]const u8, gpa, "\\\\foo", &diag, .{}), + fromSlice([:1]const u8, .{ .gpa = gpa, .arena = arena, .source = "\\\\foo", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } } // Expecting string literal, getting something else { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]const u8, gpa, "true", &diag, .{}), + fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = "true", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected string\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected string + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Expecting string literal, getting an incompatible tuple { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]const u8, gpa, ".{false}", &diag, .{}), + fromSlice([]const u8, .{ .gpa = gpa, .arena = arena, .source = ".{false}", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:3: error: expected type 'u8'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:3: error: expected type 'u8' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Invalid string literal { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]const i8, gpa, "\"\\a\"", &diag, .{}), + fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\"\\a\"", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:3: error: invalid escape character: 'a'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:3: error: invalid escape character: 'a' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Slice wrong child type { { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]const i8, gpa, "\"a\"", &diag, .{}), + fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\"a\"", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]const i8, gpa, "\\\\a", &diag, .{}), + fromSlice([]const i8, .{ .gpa = gpa, .arena = arena, .source = "\\\\a", .diagnostics = &diagnostics }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } } // Bad alignment { { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]align(2) const u8, gpa, "\"abc\"", &diag, .{}), + fromSlice([]align(2) const u8, .{ + .gpa = gpa, + .arena = arena, + .source = "\"abc\"", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc([]align(2) const u8, gpa, "\\\\abc", &diag, .{}), + fromSlice([]align(2) const u8, .{ + .gpa = gpa, + .arena = arena, + .source = "\\\\abc", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } } @@ -2301,7 +2752,10 @@ test "std.zon string literal" { message2: String, message3: String, }; - const parsed = try fromSliceAlloc(S, gpa, + const parsed = try fromSlice(S, .{ + .gpa = gpa, + .arena = arena, + .source = \\.{ \\ .message = \\ \\hello, world! @@ -2318,8 +2772,9 @@ test "std.zon string literal" { \\ \\ \\ \\and this. \\} - , null, .{}); - defer free(gpa, parsed); + , + .diagnostics = &diagnostics, + }); try std.testing.expectEqualStrings( "hello, world!\nthis is a multiline string!\n\n...", parsed.message, @@ -2332,6 +2787,10 @@ test "std.zon string literal" { 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, @@ -2341,104 +2800,155 @@ test "std.zon enum literals" { }; // Tags that exist - try std.testing.expectEqual(Enum.foo, try fromSlice(Enum, gpa, ".foo", null, .{})); - try std.testing.expectEqual(Enum.bar, try fromSlice(Enum, gpa, ".bar", null, .{})); - try std.testing.expectEqual(Enum.baz, try fromSlice(Enum, gpa, ".baz", null, .{})); + 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 fromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}), + try fromSliceNoAlloc(Enum, .{ + .gpa = gpa, + .arena = arena, + .source = ".@\"ab\\nc\"", + .diagnostics = &diagnostics, + }), ); // Bad tag { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Enum, gpa, ".qux", &diag, .{}), + fromSliceNoAlloc(Enum, .{ + .gpa = gpa, + .arena = arena, + .source = ".qux", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:2: error: unexpected enum literal 'qux' - \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' + \\input.zon:1:2: error: unexpected enum literal 'qux' + \\input.zon:1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' \\ - , - "{f}", - .{diag}, - ); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Bad tag that's too long for parser { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Enum, gpa, ".@\"foobarbaz\"", &diag, .{}), + fromSliceNoAlloc(Enum, .{ + .gpa = gpa, + .arena = arena, + .source = ".@\"foobarbaz\"", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:2: error: unexpected enum literal 'foobarbaz' - \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' + \\input.zon:1:2: error: unexpected enum literal 'foobarbaz' + \\input.zon:1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' \\ - , - "{f}", - .{diag}, - ); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Bad type { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Enum, gpa, "true", &diag, .{}), + fromSliceNoAlloc(Enum, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected enum literal\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected enum literal + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Test embedded nulls in an identifier { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Enum, gpa, ".@\"\\x00\"", &diag, .{}), + fromSliceNoAlloc(Enum, .{ + .gpa = gpa, + .arena = arena, + .source = ".@\"\\x00\"", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:2: error: identifier cannot contain null bytes\n", - "{f}", - .{diag}, - ); + \\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 fromSlice(bool, gpa, "true", null, .{})); - try std.testing.expectEqual(false, try fromSlice(bool, gpa, "false", null, .{})); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(bool, gpa, " foo", &diag, .{}), + fromSliceNoAlloc(bool, .{ + .gpa = gpa, + .arena = arena, + .source = " foo", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - \\1:2: error: invalid expression - \\1:2: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' - \\1:2: note: precede identifier with '.' for an enum literal + \\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}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(bool, gpa, "123", &diag, .{})); - try std.testing.expectFmt("1:1: error: expected type 'bool'\n", "{f}", .{diag}); + 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")}); } } @@ -2466,493 +2976,782 @@ test "std.zon intFromFloatExact" { 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 fromSlice(u8, gpa, "10", null, .{})); - try std.testing.expectEqual(@as(i16, 24), try fromSlice(i16, gpa, "24", null, .{})); - try std.testing.expectEqual(@as(i14, -4), try fromSlice(i14, gpa, "-4", null, .{})); - try std.testing.expectEqual(@as(i32, -123), try fromSlice(i32, gpa, "-123", null, .{})); + 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 fromSlice(i8, gpa, "127", null, .{})); - try std.testing.expectEqual(@as(i8, -128), try fromSlice(i8, gpa, "-128", null, .{})); + 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 fromSlice(u8, gpa, "'a'", null, .{})); - try std.testing.expectEqual(@as(u8, 'z'), try fromSlice(u8, gpa, "'z'", null, .{})); + 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 fromSlice(u65, gpa, "36893488147419103231", null, .{}), + try fromSliceNoAlloc(u65, .{ + .gpa = gpa, + .arena = arena, + .source = "36893488147419103231", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(u65, 36893488147419103231), - try fromSlice(u65, gpa, "368934_881_474191032_31", null, .{}), + try fromSliceNoAlloc(u65, .{ + .gpa = gpa, + .arena = arena, + .source = "368934_881_474191032_31", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(u128, 340282366920938463463374607431768211455), - try fromSlice(u128, gpa, "340282366920938463463374607431768211455", null, .{}), + try fromSliceNoAlloc(u128, .{ + .gpa = gpa, + .arena = arena, + .source = "340282366920938463463374607431768211455", + .diagnostics = &diagnostics, + }), ); // Test big integer limits try std.testing.expectEqual( @as(i66, 36893488147419103231), - try fromSlice(i66, gpa, "36893488147419103231", null, .{}), + try fromSliceNoAlloc(i66, .{ + .gpa = gpa, + .arena = arena, + .source = "36893488147419103231", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(i66, -36893488147419103232), - try fromSlice(i66, gpa, "-36893488147419103232", null, .{}), + try fromSliceNoAlloc(i66, .{ + .gpa = gpa, + .arena = arena, + .source = "-36893488147419103232", + .diagnostics = &diagnostics, + }), ); { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice( - i66, - gpa, - "36893488147419103232", - &diag, - .{}, - )); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i66, .{ + .gpa = gpa, + .arena = arena, + .source = "36893488147419103232", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'i66' cannot represent value\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: type 'i66' cannot represent value + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice( - i66, - gpa, - "-36893488147419103233", - &diag, - .{}, - )); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i66, .{ + .gpa = gpa, + .arena = arena, + .source = "-36893488147419103233", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'i66' cannot represent value\n", - "{f}", - .{diag}, - ); + \\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 fromSlice(i8, gpa, "-1.0", null, .{})); - try std.testing.expectEqual(@as(i8, 123), try fromSlice(i8, gpa, "123.0", null, .{})); + 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 fromSlice(i16, gpa, "0xff", null, .{})); - try std.testing.expectEqual(@as(i16, -0xff), try fromSlice(i16, gpa, "-0xff", null, .{})); - try std.testing.expectEqual(@as(i16, 0o77), try fromSlice(i16, gpa, "0o77", null, .{})); - try std.testing.expectEqual(@as(i16, -0o77), try fromSlice(i16, gpa, "-0o77", null, .{})); - try std.testing.expectEqual(@as(i16, 0b11), try fromSlice(i16, gpa, "0b11", null, .{})); - try std.testing.expectEqual(@as(i16, -0b11), try fromSlice(i16, gpa, "-0b11", null, .{})); + 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 fromSlice( - u65, - gpa, - "0x1ffffffffffffffff", - null, - .{}, - )); - try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( - i66, - gpa, - "0x1ffffffffffffffff", - null, - .{}, - )); - try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( - i66, - gpa, - "-0x1ffffffffffffffff", - null, - .{}, - )); - try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( - u65, - gpa, - "0o3777777777777777777777", - null, - .{}, - )); - try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( - i66, - gpa, - "0o3777777777777777777777", - null, - .{}, - )); - try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( - i66, - gpa, - "-0o3777777777777777777777", - null, - .{}, - )); - try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( - u65, - gpa, - "0b11111111111111111111111111111111111111111111111111111111111111111", - null, - .{}, - )); - try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( - i66, - gpa, - "0b11111111111111111111111111111111111111111111111111111111111111111", - null, - .{}, - )); - try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( - i66, - gpa, - "-0b11111111111111111111111111111111111111111111111111111111111111111", - null, - .{}, - )); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "32a32", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = "32a32", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:3: error: invalid digit 'a' for decimal base\n", - "{f}", - .{diag}, - ); + \\input.zon:1:3: error: invalid digit 'a' for decimal base + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing to parse as int { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "true", &diag, .{})); - try std.testing.expectFmt("1:1: error: expected type 'u8'\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "256", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = "256", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'u8' cannot represent value\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: type 'u8' cannot represent value + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because a negative int is out of range { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-129", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ + .gpa = gpa, + .arena = arena, + .source = "-129", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'i8' cannot represent value\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: type 'i8' cannot represent value + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because an unsigned int is negative { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = "-1", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'u8' cannot represent value\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: type 'u8' cannot represent value + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because a float is non-whole { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "1.5", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = "1.5", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'u8' cannot represent value\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: type 'u8' cannot represent value + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Failing because a float is negative { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1.0", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = "-1.0", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: type 'u8' cannot represent value\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: type 'u8' cannot represent value + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Negative integer zero { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-0", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ + .gpa = gpa, + .arena = arena, + .source = "-0", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - \\1:2: error: integer literal '-0' is ambiguous - \\1:2: note: use '0' for an integer zero - \\1:2: note: use '-0.0' for a floating-point signed zero + \\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}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Negative integer zero casted to float { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-0", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-0", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - \\1:2: error: integer literal '-0' is ambiguous - \\1:2: note: use '0' for an integer zero - \\1:2: note: use '-0.0' for a floating-point signed zero + \\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}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } // Negative float 0 is allowed try std.testing.expect( - std.math.isNegativeZero(try fromSlice(f32, gpa, "-0.0", null, .{})), + std.math.isNegativeZero(try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-0.0", + .diagnostics = &diagnostics, + })), ); - try std.testing.expect(std.math.isPositiveZero(try fromSlice(f32, gpa, "0.0", null, .{}))); + try std.testing.expect(std.math.isPositiveZero(try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "0.0", + .diagnostics = &diagnostics, + }))); // Double negation is not allowed { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "--2", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i8, .{ + .gpa = gpa, + .arena = arena, + .source = "--2", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: expected number or 'inf' after '-'\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: expected number or 'inf' after '-' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(f32, gpa, "--2.0", &diag, .{}), + fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "--2.0", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:1: error: expected number or 'inf' after '-'\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: expected number or 'inf' after '-' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Invalid int literal { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0xg", &diag, .{})); - try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "0123", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(u8, .{ + .gpa = gpa, + .arena = arena, + .source = "0123", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - \\1:1: error: number '0123' has leading zero - \\1:1: note: use '0o' prefix for octal literals + \\input.zon:1:1: error: number '0123' has leading zero + \\input.zon:1:1: note: use '0o' prefix for octal literals \\ - , "{f}", .{diag}); + , "{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; { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-'a'", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-'a'", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: expected number or 'inf' after '-'\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: expected number or 'inf' after '-' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i16, gpa, "-'a'", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(i16, .{ + .gpa = gpa, + .arena = arena, + .source = "-'a'", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: expected number or 'inf' after '-'\n", - "{f}", - .{diag}, - ); + \\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 fromSlice(f16, gpa, "0.5", null, .{})); + 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 fromSlice(f32, gpa, "123.456", null, .{}), + try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "123.456", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(f64, -123.456), - try fromSlice(f64, gpa, "-123.456", null, .{}), + try fromSliceNoAlloc(f64, .{ + .gpa = gpa, + .arena = arena, + .source = "-123.456", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectEqual(@as(f128, 42.5), try fromSlice(f128, gpa, "42.5", null, .{})); + 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 fromSlice(f16, gpa, "5.0", null, .{})); - try std.testing.expectEqual(@as(f16, 5.0), try fromSlice(f16, gpa, "5", null, .{})); - try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102.0", null, .{})); - try std.testing.expectEqual(@as(f32, -102), try fromSlice(f32, gpa, "-102", null, .{})); + 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 fromSlice(f32, gpa, "'a'", null, .{})); - try std.testing.expectEqual(@as(f32, 'z'), try fromSlice(f32, gpa, "'z'", null, .{})); + 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 fromSlice(f32, gpa, "36893488147419103231", null, .{}), + try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "36893488147419103231", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(f32, -36893488147419103231.0), - try fromSlice(f32, gpa, "-36893488147419103231", null, .{}), + try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-36893488147419103231", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice( - f128, - gpa, - "0x1ffffffffffffffff", - null, - .{}, - )); - try std.testing.expectEqual(@as(f32, @floatFromInt(0x1ffffffffffffffff)), try fromSlice( - f32, - gpa, - "0x1ffffffffffffffff", - null, - .{}, - )); + 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 fromSlice(f32, gpa, "12_3.0E+77", null, .{}), + 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 fromSlice(f32, gpa, "0x103.70p-5", null, .{}), + try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "0x103.70p-5", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(f32, -0x103.70), - try fromSlice(f32, gpa, "-0x103.70", null, .{}), + try fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-0x103.70", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @as(f32, 0x1234_5678.9ABC_CDEFp-10), - try fromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), + 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 fromSlice(f32, gpa, "inf", null, .{}))); - try std.testing.expect(std.math.isNegativeInf(try fromSlice(f32, gpa, "-inf", null, .{}))); - try std.testing.expect(std.math.isNan(try fromSlice(f32, gpa, "nan", null, .{}))); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-nan", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-nan", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: expected number or 'inf' after '-'\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: expected number or 'inf' after '-' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // nan as int not allowed { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &diag, .{})); - try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &diag, .{})); - try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "inf", &diag, .{})); - try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-inf", &diag, .{})); - try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "foo", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "foo", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - \\1:1: error: invalid expression - \\1:1: note: ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan' - \\1:1: note: precede identifier with '.' for an enum literal + \\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}", .{diag}); + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-foo", &diag, .{})); + try std.testing.expectError(error.ParseZon, fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "-foo", + .diagnostics = &diagnostics, + })); try std.testing.expectFmt( - "1:1: error: expected number or 'inf' after '-'\n", - "{f}", - .{diag}, - ); + \\input.zon:1:1: error: expected number or 'inf' after '-' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Non float as float { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(f32, gpa, "\"foo\"", &diag, .{}), + fromSliceNoAlloc(f32, .{ + .gpa = gpa, + .arena = arena, + .source = "\"foo\"", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{f}", .{diag}); + 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 { @@ -2960,13 +3759,18 @@ test "std.zon free on error" { y: []const u8, z: bool, }; - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{ + .gpa = gpa, + .arena = arena, + .source = \\.{ \\ .x = "hello", \\ .y = "world", \\ .z = "fail", \\} - , null, .{})); + , + .diagnostics = &diagnostics, + })); } // Test freeing partially allocated tuples @@ -2976,358 +3780,418 @@ test "std.zon free on error" { []const u8, bool, }; - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, fromSlice(Struct, .{ + .gpa = gpa, + .arena = arena, + .source = \\.{ \\ "hello", \\ "world", \\ "fail", \\} - , null, .{})); + , + .diagnostics = &diagnostics, + })); } // Test freeing structs with missing fields - { - const Struct = struct { - x: []const u8, - y: bool, - }; - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Struct, std.testing.allocator, - \\.{ - \\ .x = "hello", - \\} - , null, .{})); - } + 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, fromSliceAlloc( - [3][]const u8, - std.testing.allocator, - \\.{ - \\ "hello", - \\ false, - \\ false, - \\} + try std.testing.expectError(error.ParseZon, fromSlice([3][]const u8, .{ + .gpa = gpa, + .arena = arena, + .source = + \\.{ + \\ "hello", + \\ false, + \\ false, + \\} , - null, - .{}, - )); - } + .diagnostics = &diagnostics, + })); // Test freeing partially allocated slices - { - try std.testing.expectError(error.ParseZon, fromSliceAlloc( - [][]const u8, - std.testing.allocator, - \\.{ - \\ "hello", - \\ "world", - \\ false, - \\} + try std.testing.expectError(error.ParseZon, fromSlice([][]const u8, .{ + .gpa = gpa, + .arena = arena, + .source = + \\.{ + \\ "hello", + \\ "world", + \\ false, + \\} , - null, - .{}, - )); - } + .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 fromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", null, .{})).x, + (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 fromSliceAlloc( - union { x: []const u8 }, - std.testing.allocator, - ".{ .x = \"foo\" }", - null, - .{}, - ); - defer free(std.testing.allocator, result.x); + const result = try fromSlice(union { x: []const u8 }, .{ + .gpa = gpa, + .arena = arena, + .source = ".{ .x = \"foo\" }", + .diagnostics = &diagnostics, + }); try std.testing.expectEqualStrings("foo", result.x); } - - // However, if it's possible we could get an error requiring we free the value, but the value - // cannot be freed (e.g. untagged unions) then we need to turn off `free_on_error` for it to - // compile. - { - const S = struct { - union { x: []const u8 }, - bool, - }; - const result = try fromSliceAlloc( - S, - std.testing.allocator, - ".{ .{ .x = \"foo\" }, true }", - null, - .{ .free_on_error = false }, - ); - defer free(std.testing.allocator, result[0].x); - try std.testing.expectEqualStrings("foo", result[0].x); - try std.testing.expect(result[1]); - } - - // Again but for structs. - { - const S = struct { - a: union { x: []const u8 }, - b: bool, - }; - const result = try fromSliceAlloc( - S, - std.testing.allocator, - ".{ .a = .{ .x = \"foo\" }, .b = true }", - null, - .{ - .free_on_error = false, - }, - ); - defer free(std.testing.allocator, result.a.x); - try std.testing.expectEqualStrings("foo", result.a.x); - try std.testing.expect(result.b); - } - - // Again but for arrays. - { - const S = [2]union { x: []const u8 }; - const result = try fromSliceAlloc( - S, - std.testing.allocator, - ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", - null, - .{ - .free_on_error = false, - }, - ); - defer free(std.testing.allocator, result[0].x); - defer free(std.testing.allocator, result[1].x); - try std.testing.expectEqualStrings("foo", result[0].x); - try std.testing.expectEqualStrings("bar", result[1].x); - } - - // Again but for slices. - { - const S = []union { x: []const u8 }; - const result = try fromSliceAlloc( - S, - std.testing.allocator, - ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", - null, - .{ - .free_on_error = false, - }, - ); - defer std.testing.allocator.free(result); - defer free(std.testing.allocator, result[0].x); - defer free(std.testing.allocator, result[1].x); - try std.testing.expectEqualStrings("foo", result[0].x); - try std.testing.expectEqualStrings("bar", result[1].x); - } } test "std.zon vector" { - const builtin = @import("builtin"); + 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 fromSlice(@Vector(0, bool), gpa, ".{}", null, .{}), + try fromSliceNoAlloc(@Vector(0, bool), .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @Vector(3, bool){ true, false, true }, - try fromSlice(@Vector(3, bool), gpa, ".{true, false, true}", null, .{}), + try fromSliceNoAlloc(@Vector(3, bool), .{ + .gpa = gpa, + .arena = arena, + .source = ".{true, false, true}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @Vector(0, f32){}, - try fromSlice(@Vector(0, f32), gpa, ".{}", null, .{}), + 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 fromSlice(@Vector(3, f32), gpa, ".{1.5, 2.5, 3.5}", null, .{}), + 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 fromSlice(@Vector(0, u8), gpa, ".{}", null, .{}), + try fromSliceNoAlloc(@Vector(0, u8), .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectEqual( @Vector(3, u8){ 2, 4, 6 }, - try fromSlice(@Vector(3, u8), gpa, ".{2, 4, 6}", null, .{}), + try fromSliceNoAlloc(@Vector(3, u8), .{ + .gpa = gpa, + .arena = arena, + .source = ".{2, 4, 6}", + .diagnostics = &diagnostics, + }), ); { try std.testing.expectEqual( @Vector(0, *const u8){}, - try fromSliceAlloc(@Vector(0, *const u8), gpa, ".{}", null, .{}), + try fromSlice(@Vector(0, *const u8), .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }), ); - const pointers = try fromSliceAlloc(@Vector(3, *const u8), gpa, ".{2, 4, 6}", null, .{}); - defer free(gpa, pointers); + 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 fromSliceAlloc(@Vector(0, ?*const u8), gpa, ".{}", null, .{}), + try fromSlice(@Vector(0, ?*const u8), .{ + .gpa = gpa, + .arena = arena, + .source = ".{}", + .diagnostics = &diagnostics, + }), ); - const pointers = try fromSliceAlloc(@Vector(3, ?*const u8), gpa, ".{2, null, 6}", null, .{}); - defer free(gpa, pointers); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(@Vector(2, f32), gpa, ".{0.5}", &diag, .{}), + fromSliceNoAlloc(@Vector(2, f32), .{ + .gpa = gpa, + .arena = arena, + .source = ".{0.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:2: error: expected 2 array elements; found 1\n", - "{f}", - .{diag}, - ); + \\input.zon:1:2: error: expected 2 array elements; found 1 + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Too many fields { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(@Vector(2, f32), gpa, ".{0.5, 1.5, 2.5}", &diag, .{}), + fromSliceNoAlloc(@Vector(2, f32), .{ + .gpa = gpa, + .arena = arena, + .source = ".{0.5, 1.5, 2.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:13: error: index 2 outside of array of length 2\n", - "{f}", - .{diag}, - ); + \\input.zon:1:13: error: index 2 outside of array of length 2 + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Wrong type fields { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(@Vector(3, f32), gpa, ".{0.5, true, 2.5}", &diag, .{}), + fromSliceNoAlloc(@Vector(3, f32), .{ + .gpa = gpa, + .arena = arena, + .source = ".{0.5, true, 2.5}", + .diagnostics = &diagnostics, + }), ); try std.testing.expectFmt( - "1:8: error: expected type 'f32'\n", - "{f}", - .{diag}, - ); + \\input.zon:1:8: error: expected type 'f32' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } // Wrong type { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(@Vector(3, u8), gpa, "true", &diag, .{}), + fromSliceNoAlloc(@Vector(3, u8), .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected type '@Vector(3, u8)'\n", "{f}", .{diag}); + 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 { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(@Vector(3, *u8), gpa, ".{1, true, 3}", &diag, .{}), + fromSlice(@Vector(3, *u8), .{ + .gpa = gpa, + .arena = arena, + .source = ".{1, true, 3}", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:6: error: expected type 'u8'\n", "{f}", .{diag}); + 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 fromSliceAlloc(*u32, gpa, "10", null, .{}); - defer free(gpa, 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 fromSliceAlloc(**u32, gpa, "10", null, .{}); - defer free(gpa, 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 fromSliceAlloc(***u32, gpa, "10", null, .{}); - defer free(gpa, 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 fromSliceAlloc(?*u32, gpa, "10", null, .{}); - defer free(gpa, some); + const some = try fromSlice(?*u32, .{ + .gpa = gpa, + .arena = arena, + .source = "10", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(@as(u32, 10), some.?.*); - const none = try fromSliceAlloc(?*u32, gpa, "null", null, .{}); - defer free(gpa, none); + const none = try fromSlice(?*u32, .{ + .gpa = gpa, + .arena = arena, + .source = "null", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(null, none); } { - const some = try fromSliceAlloc(*?u32, gpa, "10", null, .{}); - defer free(gpa, some); + const some = try fromSlice(*?u32, .{ + .gpa = gpa, + .arena = arena, + .source = "10", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(@as(u32, 10), some.*.?); - const none = try fromSliceAlloc(*?u32, gpa, "null", null, .{}); - defer free(gpa, none); + const none = try fromSlice(*?u32, .{ + .gpa = gpa, + .arena = arena, + .source = "null", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(null, none.*); } { - const some = try fromSliceAlloc(?**u32, gpa, "10", null, .{}); - defer free(gpa, some); + const some = try fromSlice(?**u32, .{ + .gpa = gpa, + .arena = arena, + .source = "10", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(@as(u32, 10), some.?.*.*); - const none = try fromSliceAlloc(?**u32, gpa, "null", null, .{}); - defer free(gpa, none); + const none = try fromSlice(?**u32, .{ + .gpa = gpa, + .arena = arena, + .source = "null", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(null, none); } { - const some = try fromSliceAlloc(*?*u32, gpa, "10", null, .{}); - defer free(gpa, some); + const some = try fromSlice(*?*u32, .{ + .gpa = gpa, + .arena = arena, + .source = "10", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(@as(u32, 10), some.*.?.*); - const none = try fromSliceAlloc(*?*u32, gpa, "null", null, .{}); - defer free(gpa, none); + const none = try fromSlice(*?*u32, .{ + .gpa = gpa, + .arena = arena, + .source = "null", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(null, none.*); } { - const some = try fromSliceAlloc(**?u32, gpa, "10", null, .{}); - defer free(gpa, some); + const some = try fromSlice(**?u32, .{ + .gpa = gpa, + .arena = arena, + .source = "10", + .diagnostics = &diagnostics, + }); try std.testing.expectEqual(@as(u32, 10), some.*.*.?); - const none = try fromSliceAlloc(**?u32, gpa, "null", null, .{}); - defer free(gpa, none); + 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 fromSliceAlloc(*[3]u8, gpa, ".{ 1, 2, 3 }", null, .{}); - defer free(gpa, result); + 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.*); } @@ -3349,7 +4213,10 @@ test "std.zon add pointers" { .f2 = &null, }; - const found = try fromSliceAlloc(?*Outer, gpa, + const found = try fromSlice(?*Outer, .{ + .gpa = gpa, + .arena = arena, + .source = \\.{ \\ .f1 = .{ \\ .f1 = null, @@ -3357,146 +4224,229 @@ test "std.zon add pointers" { \\ }, \\ .f2 = null, \\} - , null, .{}); - defer free(gpa, found); + , + .diagnostics = &diagnostics, + }); try std.testing.expectEqualDeep(expected, found.?.*); } // Test that optional types are flattened correctly in errors { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const u8, gpa, "true", &diag, .{}), + fromSlice(*const ?*const u8, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected type '?u8'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected type '?u8' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const f32, gpa, "true", &diag, .{}), + fromSlice(*const ?*const f32, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected type '?f32'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected type '?f32' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const @Vector(3, u8), gpa, "true", &diag, .{}), + fromSlice(*const ?*const @Vector(3, u8), .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected type '?@Vector(3, u8)'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected type '?@Vector(3, u8)' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const bool, gpa, "10", &diag, .{}), + fromSlice(*const ?*const bool, .{ + .gpa = gpa, + .arena = arena, + .source = "10", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected type '?bool'\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected type '?bool' + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const struct { a: i32 }, gpa, "true", &diag, .{}), + fromSlice(*const ?*const struct { a: i32 }, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional struct\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional struct + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const struct { i32 }, gpa, "true", &diag, .{}), + fromSlice(*const ?*const struct { i32 }, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional tuple\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional tuple + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const union { x: void }, gpa, "true", &diag, .{}), + fromSlice(*const ?*const union { x: void }, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional union\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional union + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const [3]u8, gpa, "true", &diag, .{}), + fromSlice(*const ?*const [3]u8, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(?[3]u8, gpa, "true", &diag, .{}), + fromSlice(?[3]u8, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const []u8, gpa, "true", &diag, .{}), + fromSlice(*const ?*const []u8, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(?[]u8, gpa, "true", &diag, .{}), + fromSlice(?[]u8, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional array\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional array + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const []const u8, gpa, "true", &diag, .{}), + fromSlice(*const ?*const []const u8, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional string\n", "{f}", .{diag}); + try std.testing.expectFmt( + \\input.zon:1:1: error: expected optional string + \\ + , "{f}", .{diagnostics.fmt("input.zon")}); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSliceAlloc(*const ?*const enum { foo }, gpa, "true", &diag, .{}), + fromSlice(*const ?*const enum { foo }, .{ + .gpa = gpa, + .arena = arena, + .source = "true", + .diagnostics = &diagnostics, + }), ); - try std.testing.expectFmt("1:1: error: expected optional enum literal\n", "{f}", .{diag}); + 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 { @@ -3504,27 +4454,55 @@ test "std.zon stop on node" { y: f32, }; - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const result = try fromSlice(Vec2, gpa, ".{ .x = 1.5, .y = 2.5 }", &diag, .{}); + 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(diag.zoir)); + try std.testing.expectEqual(Zoir.Node{ .float_literal = 1.5 }, result.x.get(&zoir)); } { - var diag: Diagnostics = .{}; - defer diag.deinit(gpa); - const result = try fromSlice(Zoir.Node.Index, gpa, "1.23", &diag, .{}); - try std.testing.expectEqual(Zoir.Node{ .float_literal = 1.23 }, result.get(diag.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 fromSlice([3]u8, gpa, ".{ 1, 2, 3 }", null, .{}), + try fromSliceNoAlloc([3]u8, .{ + .gpa = gpa, + .arena = arena, + .source = ".{ 1, 2, 3 }", + .diagnostics = &diagnostics, + }), ); const Nested = struct { u8, u8, struct { u8, u8 } }; @@ -3537,34 +4515,442 @@ test "std.zon no alloc" { try std.testing.expectEqual( Nested{ 1, 2, .{ 3, 4 } }, - try fromZoir(Nested, ast, zoir, null, .{}), + try fromZoirNoAlloc(Nested, .{ + .arena = arena, + .ast = &ast, + .zoir = &zoir, + .diagnostics = &diagnostics, + }), ); +} - try std.testing.expectEqual( - Nested{ 1, 2, .{ 3, 4 } }, - try fromZoirNode(Nested, ast, zoir, .root, null, .{}), - ); +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 errors without diagnostics" { +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 Enum = enum { - foo, - bar, - baz, + 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, }; - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Enum, gpa, ".nothing", null, .{})); - const Struct = struct { - name: []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 }, }; - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Struct, gpa, ".{ .name = \"Alice\", .age = 25 }", null, .{})); - const Union = union(enum) { - x, - y: 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 }, }; - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Union, gpa, ".a", null, .{})); - try std.testing.expectError(error.ParseZon, fromSliceAlloc(Union, gpa, ".{ .b = 8 }", null, .{})); + + // 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 { + _ = Diagnostics; } diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index e2ee5fc06e0567575ad560856fa515eef0e7c083..ec9aa66bf17933741673e2afa4a432d4ec43dc83 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -69,7 +69,7 @@ fn lowerExprAnonResTy(self: *LowerZon, node: Zoir.Node.Index) CompileError!Inter const gpa = comp.gpa; const io = comp.io; const ip = &pt.zcu.intern_pool; - switch (node.get(self.zoir.*)) { + switch (node.get(self.zoir)) { .true => return .bool_true, .false => return .bool_false, .null => return .null_value, @@ -99,7 +99,7 @@ fn lowerExprAnonResTy(self: *LowerZon, node: Zoir.Node.Index) CompileError!Inter gpa, io, pt.tid, - val.get(self.zoir.*), + val.get(self.zoir), .no_embedded_nulls, ), }), @@ -172,7 +172,7 @@ fn lowerExprAnonResTy(self: *LowerZon, node: Zoir.Node.Index) CompileError!Inter gpa, io, pt.tid, - zoir_name.get(self.zoir.*), + zoir_name.get(self.zoir), .no_embedded_nulls, ); field_ty.* = ip.typeOf(field_val); @@ -310,7 +310,7 @@ fn checkTypeInner( fn nodeSrc(self: *LowerZon, node: Zoir.Node.Index) LazySrcLoc { return .{ .baseline = .{ .inst = self.tracked_inst, .node = .main }, - .offset = .{ .node_abs = node.getAstNode(self.zoir.*) }, + .offset = .{ .node_abs = node.getAstNode(self.zoir) }, }; } @@ -363,7 +363,7 @@ fn lowerExprKnownResTyInner( .optional => return pt.intern(.{ .opt = .{ .ty = res_ty.toIntern(), - .val = if (node.get(self.zoir.*) == .null) b: { + .val = if (node.get(self.zoir) == .null) b: { break :b .none; } else b: { const child_type = res_ty.optionalChild(zcu); @@ -418,7 +418,7 @@ fn lowerExprKnownResTyInner( } fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { - return switch (node.get(self.zoir.*)) { + return switch (node.get(self.zoir)) { .true => .bool_true, .false => .bool_false, else => return error.WrongType, @@ -431,7 +431,7 @@ fn lowerInt( res_ty: Type, ) !InternPool.Index { @setFloatMode(.strict); - return switch (node.get(self.zoir.*)) { + return switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| { const rhs: i32 = val; @@ -555,7 +555,7 @@ fn lowerFloat( res_ty: Type, ) !InternPool.Index { @setFloatMode(.strict); - const value = switch (node.get(self.zoir.*)) { + const value = switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128, .nearest_even)[0]), @@ -592,7 +592,7 @@ fn lowerFloat( } fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { - switch (node.get(self.zoir.*)) { + switch (node.get(self.zoir)) { .null => return .null_value, else => return error.WrongType, } @@ -600,7 +600,7 @@ fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const array_info = res_ty.arrayInfo(self.sema.pt.zcu); - const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir.*)) { + 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, @@ -633,13 +633,13 @@ fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I const io = comp.io; const ip = &pt.zcu.intern_pool; try self.sema.ensureLayoutResolved(res_ty, self.import_src, .init); - switch (node.get(self.zoir.*)) { + switch (node.get(self.zoir)) { .enum_literal => |field_name| { const field_name_interned = try ip.getOrPutString( gpa, io, self.sema.pt.tid, - field_name.get(self.zoir.*), + field_name.get(self.zoir), .no_embedded_nulls, ); const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse { @@ -648,7 +648,7 @@ fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I "enum {f} has no member named '{f}'", .{ res_ty.fmt(self.sema.pt.zcu), - std.zig.fmtId(field_name.get(self.zoir.*)), + std.zig.fmtId(field_name.get(self.zoir)), }, ); }; @@ -667,13 +667,13 @@ fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { const gpa = comp.gpa; const io = comp.io; const ip = &pt.zcu.intern_pool; - switch (node.get(self.zoir.*)) { + switch (node.get(self.zoir)) { .enum_literal => |field_name| { const field_name_interned = try ip.getOrPutString( gpa, io, self.sema.pt.tid, - field_name.get(self.zoir.*), + field_name.get(self.zoir), .no_embedded_nulls, ); return self.sema.pt.intern(.{ .enum_literal = field_name_interned }); @@ -696,7 +696,7 @@ fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; - const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir.*)) { + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, @@ -720,7 +720,7 @@ fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. "index {} outside tuple of length {}", .{ elems.len, - elem_nodes.at(@intCast(i)).getAstNode(self.zoir.*), + elem_nodes.at(@intCast(i)).getAstNode(self.zoir), }, ); } @@ -760,7 +760,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool try self.sema.ensureStructDefaultsResolved(res_ty, self.import_src); const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?; - const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.zoir.*)) { + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.zoir)) { .struct_literal => |fields| fields, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, else => return error.WrongType, @@ -780,7 +780,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool gpa, io, self.sema.pt.tid, - fields.names[i].get(self.zoir.*), + fields.names[i].get(self.zoir), .no_embedded_nulls, ); const field_node = fields.vals.at(@intCast(i)); @@ -844,7 +844,7 @@ fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const string_alignment = ptr_info.flags.alignment == .none or ptr_info.flags.alignment == .@"1"; const string_sentinel = ptr_info.sentinel == .none or ptr_info.sentinel == .zero_u8; if (string_alignment and ptr_info.child == .u8_type and string_sentinel) { - switch (node.get(self.zoir.*)) { + switch (node.get(self.zoir)) { .string_literal => |val| { const ip_str = try ip.getOrPutString(gpa, io, self.sema.pt.tid, val, .maybe_embedded_nulls); const str_ref = try self.sema.addStrLit(ip_str, val.len); @@ -860,7 +860,7 @@ fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. } // Slice literals - const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir.*)) { + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, @@ -927,19 +927,19 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const union_info = pt.zcu.typeToUnion(res_ty).?; const enum_tag_info = ip.loadEnumType(union_info.enum_tag_type); - const field_name, const maybe_field_node = switch (node.get(self.zoir.*)) { + const field_name, const maybe_field_node = switch (node.get(self.zoir)) { .enum_literal => |name| b: { const field_name = try ip.getOrPutString( gpa, io, self.sema.pt.tid, - name.get(self.zoir.*), + name.get(self.zoir), .no_embedded_nulls, ); break :b .{ field_name, null }; }, .struct_literal => b: { - const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.zoir.*)) { + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.zoir)) { .struct_literal => |fields| fields, else => return self.fail(node, "expected type '{f}'", .{res_ty.fmt(self.sema.pt.zcu)}), }; @@ -950,7 +950,7 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. gpa, io, self.sema.pt.tid, - fields.names[0].get(self.zoir.*), + fields.names[0].get(self.zoir), .no_embedded_nulls, ); break :b .{ field_name, fields.vals.at(0) }; @@ -986,7 +986,7 @@ fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const vector_info = ip.indexToKey(res_ty.toIntern()).vector_type; - const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir.*)) { + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, else => return error.WrongType, diff --git a/src/print_zoir.zig b/src/print_zoir.zig index 93b4553b91ef13048989775d0c67b73574fd161c..412e7db4e085e5e7b58eed9bb819f6b2f4b615a3 100644 --- a/src/print_zoir.zig +++ b/src/print_zoir.zig @@ -54,7 +54,7 @@ const PrintZon = struct { } fn renderNode(pz: *PrintZon, node: Zoir.Node.Index) Error!void { - const zoir = pz.zoir; + const zoir = &pz.zoir; try pz.w.print("%{d} = ", .{@backingInt(node)}); switch (node.get(zoir)) { .true => try pz.w.writeAll("true"),