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.

NameOrOrdinal

res.NameOrOrdinal
pub const NameOrOrdinal = union(enum)

File

Code

pub const NameOrOrdinal = union(enum) {
    // UTF-16 LE
    name: [:0]const u16,
    ordinal: u16,

    pub fn deinit(self: NameOrOrdinal, allocator: Allocator) void {
        switch (self) {
            .name => |name| {
                allocator.free(name);
            },
            .ordinal => {},
        }
    }

    /// Returns the full length of the amount of bytes that would be written by `write`
    /// (e.g. for an ordinal it will return the length including the 0xFFFF indicator)
    pub fn byteLen(self: NameOrOrdinal) usize {
        switch (self) {
            .name => |name| {
                // + 1 for 0-terminated
                return (name.len + 1) * @sizeOf(u16);
            },
            .ordinal => return 4,
        }
    }

    pub fn write(self: NameOrOrdinal, writer: *std.Io.Writer) !void {
        switch (self) {
            .name => |name| {
                try writer.writeAll(std.mem.sliceAsBytes(name[0 .. name.len + 1]));
            },
            .ordinal => |ordinal| {
                try writer.writeInt(u16, 0xffff, .little);
                try writer.writeInt(u16, ordinal, .little);
            },
        }
    }

    pub fn writeEmpty(writer: *std.Io.Writer) !void {
        try writer.writeInt(u16, 0, .little);
    }

    pub fn fromString(allocator: Allocator, bytes: SourceBytes) !NameOrOrdinal {
        if (maybeOrdinalFromString(bytes)) |ordinal| {
            return ordinal;
        }
        return nameFromString(allocator, bytes);
    }

    pub fn nameFromString(allocator: Allocator, bytes: SourceBytes) !NameOrOrdinal {
        // Names have a limit of 256 UTF-16 code units + null terminator
        var buf = try std.ArrayList(u16).initCapacity(allocator, @min(257, bytes.slice.len));
        errdefer buf.deinit(allocator);

        var i: usize = 0;
        while (bytes.code_page.codepointAt(i, bytes.slice)) |codepoint| : (i += codepoint.byte_len) {
            if (buf.items.len == 256) break;

            const c = codepoint.value;
            if (c == Codepoint.invalid) {
                try buf.append(allocator, std.mem.nativeToLittle(u16, '�'));
            } else if (c < 0x7F) {
                // ASCII chars in names are always converted to uppercase
                try buf.append(allocator, std.mem.nativeToLittle(u16, std.ascii.toUpper(@intCast(c))));
            } else if (c < 0x10000) {
                const short: u16 = @intCast(c);
                try buf.append(allocator, std.mem.nativeToLittle(u16, short));
            } else {
                const high = @as(u16, @intCast((c - 0x10000) >> 10)) + 0xD800;
                try buf.append(allocator, std.mem.nativeToLittle(u16, high));

                // Note: This can cut-off in the middle of a UTF-16 surrogate pair,
                //       i.e. it can make the string end with an unpaired high surrogate
                if (buf.items.len == 256) break;

                const low = @as(u16, @intCast(c & 0x3FF)) + 0xDC00;
                try buf.append(allocator, std.mem.nativeToLittle(u16, low));
            }
        }

        return NameOrOrdinal{ .name = try buf.toOwnedSliceSentinel(allocator, 0) };
    }

    /// Returns `null` if the bytes do not form a valid number.
    /// Does not allow non-ASCII digits (which the Win32 RC compiler does allow
    /// in base 10 numbers, see `maybeNonAsciiOrdinalFromString`).
    pub fn maybeOrdinalFromString(bytes: SourceBytes) ?NameOrOrdinal {
        var buf = bytes.slice;
        var radix: u8 = 10;
        if (buf.len > 2 and buf[0] == '0') {
            switch (buf[1]) {
                '0'...'9' => {},
                'x', 'X' => {
                    radix = 16;
                    buf = buf[2..];
                    // only the first 4 hex digits matter, anything else is ignored
                    // i.e. 0x12345 is treated as if it were 0x1234
                    buf.len = @min(buf.len, 4);
                },
                else => return null,
            }
        }

        var i: usize = 0;
        var result: u16 = 0;
        while (bytes.code_page.codepointAt(i, buf)) |codepoint| : (i += codepoint.byte_len) {
            const c = codepoint.value;
            const digit: u8 = switch (c) {
                0x00...0x7F => std.fmt.charToDigit(@intCast(c), radix) catch switch (radix) {
                    10 => return null,
                    // non-hex-digits are treated as a terminator rather than invalidating
                    // the number (note: if there are no valid hex digits then the result
                    // will be zero which is not treated as a valid number)
                    16 => break,
                    else => unreachable,
                },
                else => if (radix == 10) return null else break,
            };

            if (result != 0) {
                result *%= radix;
            }
            result +%= digit;
        }

        // Anything that resolves to zero is not interpretted as a number
        if (result == 0) return null;
        return NameOrOrdinal{ .ordinal = result };
    }

    /// The Win32 RC compiler uses `iswdigit` for digit detection for base 10
    /// numbers, which means that non-ASCII digits are 'accepted' but handled
    /// in a totally unintuitive manner, leading to arbitrary results.
    ///
    /// This function will return the value that such an ordinal 'would' have
    /// if it was run through the Win32 RC compiler. This allows us to disallow
    /// non-ASCII digits in number literals but still detect when the Win32
    /// RC compiler would have allowed them, so that a proper warning/error
    /// can be emitted.
    pub fn maybeNonAsciiOrdinalFromString(bytes: SourceBytes) ?NameOrOrdinal {
        const buf = bytes.slice;
        const radix = 10;
        if (buf.len > 2 and buf[0] == '0') {
            switch (buf[1]) {
                // We only care about base 10 numbers here
                'x', 'X' => return null,
                else => {},
            }
        }

        var i: usize = 0;
        var result: u16 = 0;
        while (bytes.code_page.codepointAt(i, buf)) |codepoint| : (i += codepoint.byte_len) {
            const c = codepoint.value;
            const digit: u16 = digit: {
                const is_digit = (c >= '0' and c <= '9') or isNonAsciiDigit(c);
                if (!is_digit) return null;
                break :digit @intCast(c - '0');
            };

            if (result != 0) {
                result *%= radix;
            }
            result +%= digit;
        }

        // Anything that resolves to zero is not interpretted as a number
        if (result == 0) return null;
        return NameOrOrdinal{ .ordinal = result };
    }

    pub fn predefinedResourceType(self: NameOrOrdinal) ?RT {
        switch (self) {
            .ordinal => |ordinal| {
                if (ordinal >= 256) return null;
                switch (@as(RT, @fromBackingInt(@intCast(ordinal)))) {
                    .ACCELERATOR,
                    .ANICURSOR,
                    .ANIICON,
                    .BITMAP,
                    .CURSOR,
                    .DIALOG,
                    .DLGINCLUDE,
                    .DLGINIT,
                    .FONT,
                    .FONTDIR,
                    .GROUP_CURSOR,
                    .GROUP_ICON,
                    .HTML,
                    .ICON,
                    .MANIFEST,
                    .MENU,
                    .MESSAGETABLE,
                    .PLUGPLAY,
                    .RCDATA,
                    .STRING,
                    .TOOLBAR,
                    .VERSION,
                    .VXD,
                    => |rt| return rt,
                    _ => return null,
                }
            },
            .name => return null,
        }
    }

    pub fn format(self: NameOrOrdinal, w: *std.Io.Writer) !void {
        switch (self) {
            .name => |name| {
                try w.print("{f}", .{std.unicode.fmtUtf16Le(name)});
            },
            .ordinal => |ordinal| {
                try w.print("{d}", .{ordinal});
            },
        }
    }

    fn formatResourceType(self: NameOrOrdinal, w: *std.Io.Writer) std.Io.Writer.Error!void {
        switch (self) {
            .name => |name| {
                try w.print("{f}", .{std.unicode.fmtUtf16Le(name)});
            },
            .ordinal => |ordinal| {
                if (std.enums.tagName(RT, @fromBackingInt(@intCast(ordinal)))) |predefined_type_name| {
                    try w.print("{s}", .{predefined_type_name});
                } else {
                    try w.print("{d}", .{ordinal});
                }
            },
        }
    }

    pub fn fmtResourceType(type_value: NameOrOrdinal) std.fmt.Alt(NameOrOrdinal, formatResourceType) {
        return .{ .data = type_value };
    }
}