Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Parser

parse.Parser
const Parser = struct

File

lib/std/zon/parse.zig:483

Code

const Parser = struct {
    gpa: Allocator,
    ast: Ast,
    zoir: Zoir,
    diag: ?*Diagnostics,
    options: Options,

    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),
            else => |e| return e,
        };
    }

    const ParseExprInnerError = error{ ParseZon, OutOfMemory, WrongType };

    fn parseExprInner(
        self: *@This(),
        T: type,
        node: Zoir.Node.Index,
    ) ParseExprInnerError!T {
        if (T == Zoir.Node.Index) {
            return node;
        }

        switch (@typeInfo(T)) {
            .optional => |optional| if (node.get(self.zoir) == .null) {
                return null;
            } else {
                return try self.parseExprInner(optional.child, node);
            },
            .bool => return self.parseBool(node),
            .int => return self.parseInt(T, node),
            .float => return self.parseFloat(T, node),
            .@"enum" => return self.parseEnumLiteral(T, 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;
                },
                .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);
            },
            .@"struct" => |@"struct"| if (@"struct".is_tuple)
                return self.parseTuple(T, node)
            else
                return self.parseStruct(T, node),
            .@"union" => return self.parseUnion(T, node),

            else => comptime unreachable,
        }
    }

    /// Prints a message of the form `expected T` where T is first converted to a ZON type. For
    /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just
    /// referred to by the type of container.
    fn failExpectedType(
        self: @This(),
        T: type,
        node: Zoir.Node.Index,
    ) error{ ParseZon, OutOfMemory } {
        @branchHint(.cold);
        return self.failExpectedTypeInner(T, false, node);
    }

    fn failExpectedTypeInner(
        self: @This(),
        T: type,
        opt: bool,
        node: Zoir.Node.Index,
    ) error{ ParseZon, OutOfMemory } {
        _ = valid_types;
        switch (@typeInfo(T)) {
            .@"struct" => |@"struct"| if (@"struct".is_tuple) {
                if (opt) {
                    return self.failNode(node, "expected optional tuple");
                } else {
                    return self.failNode(node, "expected tuple");
                }
            } else {
                if (opt) {
                    return self.failNode(node, "expected optional struct");
                } else {
                    return self.failNode(node, "expected struct");
                }
            },
            .@"union" => if (opt) {
                return self.failNode(node, "expected optional union");
            } else {
                return self.failNode(node, "expected union");
            },
            .array => if (opt) {
                return self.failNode(node, "expected optional array");
            } else {
                return self.failNode(node, "expected array");
            },
            .pointer => |pointer| switch (pointer.size) {
                .one => return self.failExpectedTypeInner(pointer.child, opt, node),
                .slice => {
                    if (pointer.child == u8 and
                        pointer.attrs.@"const" and
                        (pointer.sentinel() == null or pointer.sentinel() == 0) and
                        (pointer.attrs.@"align" == null or pointer.attrs.@"align" == 1))
                    {
                        if (opt) {
                            return self.failNode(node, "expected optional string");
                        } else {
                            return self.failNode(node, "expected string");
                        }
                    } else {
                        if (opt) {
                            return self.failNode(node, "expected optional array");
                        } else {
                            return self.failNode(node, "expected array");
                        }
                    }
                },
                else => comptime unreachable,
            },
            .vector, .bool, .int, .float => if (opt) {
                return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)});
            } else {
                return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)});
            },
            .@"enum" => if (opt) {
                return self.failNode(node, "expected optional enum literal");
            } else {
                return self.failNode(node, "expected enum literal");
            },
            .optional => |optional| {
                return self.failExpectedTypeInner(optional.child, true, node);
            },
            else => comptime unreachable,
        }
    }

    fn parseBool(self: @This(), node: Zoir.Node.Index) !bool {
        switch (node.get(self.zoir)) {
            .true => return true,
            .false => return false,
            else => return error.WrongType,
        }
    }

    fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T {
        switch (node.get(self.zoir)) {
            .int_literal => |int| switch (int) {
                .small => |val| return std.math.cast(T, val) orelse
                    self.failCannotRepresent(T, node),
                .big => |val| return val.toInt(T) catch
                    self.failCannotRepresent(T, node),
            },
            .float_literal => |val| return intFromFloatExact(T, val) orelse
                self.failCannotRepresent(T, node),

            .char_literal => |val| return std.math.cast(T, val) orelse
                self.failCannotRepresent(T, node),
            else => return error.WrongType,
        }
    }

    fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T {
        switch (node.get(self.zoir)) {
            .int_literal => |int| switch (int) {
                .small => |val| return @floatFromInt(val),
                .big => |val| return val.toFloat(T, .nearest_even)[0],
            },
            .float_literal => |val| return @floatCast(val),
            .pos_inf => return std.math.inf(T),
            .neg_inf => return -std.math.inf(T),
            .nan => return std.math.nan(T),
            .char_literal => |val| return @floatFromInt(val),
            else => return error.WrongType,
        }
    }

    fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T {
        switch (node.get(self.zoir)) {
            .enum_literal => |field_name| {
                // Create a comptime string map for the enum fields
                const enum_info = @typeInfo(T).@"enum";
                comptime var kvs_list: [enum_info.field_names.len]struct { []const u8, T } = undefined;
                inline for (enum_info.field_names, enum_info.field_values, 0..) |enum_field_name, enum_field_value, i| {
                    kvs_list[i] = .{ enum_field_name, @fromBackingInt(@intCast(enum_field_value)) };
                }
                const enum_tags = std.StaticStringMap(T).initComptime(kvs_list);

                // Get the tag if it exists
                const field_name_str = field_name.get(self.zoir);
                return enum_tags.get(field_name_str) orelse
                    self.failUnexpected(T, "enum literal", node, null, field_name_str);
            },
            else => return error.WrongType,
        }
    }

    fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T {
        switch (node.get(self.zoir)) {
            .string_literal => return self.parseString(T, node),
            .array_literal => |nodes| return self.parseSlice(T, nodes),
            .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }),
            else => return error.WrongType,
        }
    }

    fn parseString(self: *@This(), 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);
        try aw.ensureUnusedCapacity(size_hint);
        defer aw.deinit();
        const result = ZonGen.parseStrLit(self.ast, ast_node, &aw.writer) catch return error.OutOfMemory;
        switch (result) {
            .success => {},
            .failure => |err| {
                const token = self.ast.nodeMainToken(ast_node);
                const raw_string = self.ast.tokenSlice(token);
                return self.failTokenFmt(token, @intCast(err.offset()), "{f}", .{err.fmt(raw_string)});
            },
        }

        if (pointer.child != u8 or
            pointer.size != .slice or
            !pointer.attrs.@"const" or
            (pointer.sentinel() != null and pointer.sentinel() != 0) or
            (pointer.attrs.@"align" != null and pointer.attrs.@"align" != 1))
        {
            return error.WrongType;
        }

        if (pointer.sentinel() != null) {
            return aw.toOwnedSliceSentinel(0);
        } else {
            return aw.toOwnedSlice();
        }
    }

    fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T {
        const pointer = @typeInfo(T).pointer;

        // Make sure we're working with a slice
        switch (pointer.size) {
            .slice => {},
            .one, .many, .c => comptime unreachable,
        }

        // Allocate the slice
        const slice = try self.gpa.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)));
        }

        return slice;
    }

    fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T {
        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;

        // Check if the size matches
        if (nodes.len < array_info.len) {
            return self.failNodeFmt(
                node,
                "expected {} array elements; found {}",
                .{ array_info.len, nodes.len },
            );
        } else if (nodes.len > array_info.len) {
            return self.failNodeFmt(
                nodes.at(array_info.len),
                "index {} outside of array of length {}",
                .{ array_info.len, array_info.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);
                }
            };

            elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i)));
        }
        if (array_info.sentinel()) |s| result[result.len] = s;
        return result;
    }

    fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T {
        const repr = node.get(self.zoir);
        const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {
            .struct_literal => |nodes| nodes,
            .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } },
            else => return error.WrongType,
        };

        const info = @typeInfo(T).@"struct";

        // Build a map from field name to index.
        // The special value `comptime_field` indicates that this is actually a comptime field.
        const comptime_field = std.math.maxInt(usize);
        const field_indices: std.StaticStringMap(usize) = comptime b: {
            var kvs_list: [info.field_names.len]struct { []const u8, usize } = undefined;
            for (&kvs_list, info.field_names, info.field_attrs, 0..) |*kv, field_name, field_attrs, i| {
                kv.* = .{ field_name, if (field_attrs.@"comptime") comptime_field else i };
            }
            break :b .initComptime(kvs_list);
        };

        // Parse the struct
        var 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 (field_index == comptime_field) {
                return self.failComptimeField(node, i);
            }

            // Mark the field as found. Assert that the found array is not zero length to satisfy
            // the type checker (it can't be since we made it into an iteration of this loop.)
            if (field_found.len == 0) unreachable;
            field_found[field_index] = true;

            switch (field_index) {
                inline 0...(info.field_names.len - 1) => |j| {
                    if (info.field_attrs[j].@"comptime") unreachable;

                    @field(result, info.field_names[j]) = try self.parseExpr(
                        info.field_types[j],
                        fields.vals.at(@intCast(i)),
                    );
                },
                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]},
                    );
                }
            }
        }

        return result;
    }

    fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T {
        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";

        if (nodes.len > info.field_names.len) {
            return self.failNodeFmt(
                nodes.at(info.field_names.len),
                "index {} outside of tuple length {}",
                .{ info.field_names.len, info.field_names.len },
            );
        }

        inline for (0..info.field_names.len) |i| {
            // Check if we're out of bounds
            if (i >= nodes.len) {
                if (info.field_attrs[i].defaultValue(info.field_types[i])) |default| {
                    @field(result, 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));
                }
            }
        }

        return result;
    }

    fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T {
        const @"union" = @typeInfo(T).@"union";

        if (@"union".field_names.len == 0) comptime unreachable;

        // Gather info on the fields
        const field_indices = b: {
            comptime var kvs_list: [@"union".field_names.len]struct { []const u8, usize } = undefined;
            inline for (@"union".field_names, 0..) |field_name, i| {
                kvs_list[i] = .{ field_name, i };
            }
            break :b std.StaticStringMap(usize).initComptime(kvs_list);
        };

        // Parse the union
        switch (node.get(self.zoir)) {
            .enum_literal => |field_name| {
                // The union must be tagged for an enum literal to coerce to it
                if (@"union".tag_type == null) {
                    return error.WrongType;
                }

                // Get the index of the named field. We don't use `parseEnum` here as
                // the order of the enum and the order of the union might not match!
                const field_index = b: {
                    const field_name_str = field_name.get(self.zoir);
                    break :b field_indices.get(field_name_str) orelse
                        return self.failUnexpected(T, "field", node, null, field_name_str);
                };

                // Initialize the union from the given field.
                switch (field_index) {
                    inline 0...@"union".field_names.len - 1 => |i| {
                        // Fail if the field is not void
                        if (@"union".field_types[i] != void)
                            return self.failNode(node, "expected union");

                        // Instantiate the union
                        return @unionInit(T, @"union".field_names[i], {});
                    },
                    else => unreachable, // Can't be out of bounds
                }
            },
            .struct_literal => |struct_fields| {
                if (struct_fields.names.len != 1) {
                    return error.WrongType;
                }

                // Fill in the field we found
                const field_name = struct_fields.names[0];
                const field_name_str = field_name.get(self.zoir);
                const field_val = struct_fields.vals.at(0);
                const field_index = field_indices.get(field_name_str) orelse
                    return self.failUnexpected(T, "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);
                        }
                    },
                    else => unreachable, // Can't be out of bounds
                }
            },
            else => return error.WrongType,
        }
    }

    fn failTokenFmt(
        self: @This(),
        token: Ast.TokenIndex,
        offset: u32,
        comptime fmt: []const u8,
        args: anytype,
    ) error{ OutOfMemory, ParseZon } {
        @branchHint(.cold);
        return self.failTokenFmtNote(token, offset, fmt, args, null);
    }

    fn failTokenFmtNote(
        self: @This(),
        token: Ast.TokenIndex,
        offset: u32,
        comptime fmt: []const u8,
        args: anytype,
        note: ?Error.TypeCheckFailure.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,
        };
        return error.ParseZon;
    }

    fn failNodeFmt(
        self: @This(),
        node: Zoir.Node.Index,
        comptime fmt: []const u8,
        args: anytype,
    ) error{ OutOfMemory, ParseZon } {
        @branchHint(.cold);
        const token = self.ast.nodeMainToken(node.getAstNode(self.zoir));
        return self.failTokenFmt(token, 0, fmt, args);
    }

    fn failToken(
        self: @This(),
        failure: Error.TypeCheckFailure,
    ) error{ParseZon} {
        @branchHint(.cold);
        if (self.diag) |s| s.type_check = failure;
        return error.ParseZon;
    }

    fn failNode(
        self: @This(),
        node: Zoir.Node.Index,
        message: []const u8,
    ) error{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,
        });
    }

    fn failCannotRepresent(
        self: @This(),
        T: type,
        node: Zoir.Node.Index,
    ) error{ OutOfMemory, ParseZon } {
        @branchHint(.cold);
        return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)});
    }

    fn failUnexpected(
        self: @This(),
        T: type,
        item_kind: []const u8,
        node: Zoir.Node.Index,
        field: ?usize,
        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)).?;
            const field_node = struct_init.ast.fields[f];
            break :b self.ast.firstToken(field_node) - 2;
        } else self.ast.nodeMainToken(node.getAstNode(self.zoir));
        switch (@typeInfo(T)) {
            inline .@"struct", .@"union", .@"enum" => |info| {
                const note: Error.TypeCheckFailure.Note = if (info.field_names.len == 0) b: {
                    break :b .{
                        .token = token,
                        .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);
                    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, .{
                            .allow_primitive = true,
                            .allow_underscore = true,
                        })});
                    }
                    break :b .{
                        .token = token,
                        .offset = 0,
                        .msg = try buf.toOwnedSlice(gpa),
                        .owned = true,
                    };
                };
                return self.failTokenFmtNote(
                    token,
                    0,
                    "unexpected {s} '{s}'",
                    .{ item_kind, name },
                    note,
                );
            },
            else => comptime unreachable,
        }
    }

    // Technically we could do this if we were willing to do a deep equal to verify
    // the value matched, but doing so doesn't seem to support any real use cases
    // so isn't worth the complexity at the moment.
    fn failComptimeField(
        self: @This(),
        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: {
            const field_node = struct_init.ast.fields[field];
            break :b self.ast.firstToken(field_node);
        } else b: {
            const array_init = self.ast.fullArrayInit(&buf, ast_node).?;
            const value_node = array_init.ast.elements[field];
            break :b self.ast.firstToken(value_node);
        };
        return self.failToken(.{
            .token = token,
            .offset = 0,
            .message = "cannot initialize comptime field",
            .owned = false,
            .note = null,
        });
    }
}