zig/lib/compiler/resinator/rc.zig
Ryan Liptak 289e9c3507 resinator: Sync with upstream
Note: This mostly matches resinator v0.1.0 rather than the latest master version, since the latest master version focuses on adding support for .res -> .obj conversion which is not necessary for the future planned relationship of zig and resinator (resinator will likely be moved out of the compiler and into the build system, a la translate-c).

So, ultimately the changes here consist mostly of bug fixes for obscure edge cases.
2025-01-17 23:10:41 -08:00

446 lines
13 KiB
Zig
Vendored

const std = @import("std");
const utils = @import("utils.zig");
const res = @import("res.zig");
const SourceBytes = @import("literals.zig").SourceBytes;
// https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files
pub const ResourceType = enum {
accelerators,
bitmap,
cursor,
dialog,
dialogex,
/// As far as I can tell, this is undocumented; the most I could find was this:
/// https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/91697
dlginclude,
/// Undocumented, basically works exactly like RCDATA
dlginit,
font,
html,
icon,
menu,
menuex,
messagetable,
plugplay, // Obsolete
rcdata,
stringtable,
/// Undocumented
toolbar,
user_defined,
versioninfo,
vxd, // Obsolete
// Types that are treated as a user-defined type when encountered, but have
// special meaning without the Visual Studio GUI. We match the Win32 RC compiler
// behavior by acting as if these keyword don't exist when compiling the .rc
// (thereby treating them as user-defined).
//textinclude, // A special resource that is interpreted by Visual C++.
//typelib, // A special resource that is used with the /TLBID and /TLBOUT linker options
// Types that can only be specified by numbers, they don't have keywords
cursor_num,
icon_num,
string_num,
anicursor_num,
aniicon_num,
fontdir_num,
manifest_num,
const map = std.StaticStringMapWithEql(
ResourceType,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "ACCELERATORS", .accelerators },
.{ "BITMAP", .bitmap },
.{ "CURSOR", .cursor },
.{ "DIALOG", .dialog },
.{ "DIALOGEX", .dialogex },
.{ "DLGINCLUDE", .dlginclude },
.{ "DLGINIT", .dlginit },
.{ "FONT", .font },
.{ "HTML", .html },
.{ "ICON", .icon },
.{ "MENU", .menu },
.{ "MENUEX", .menuex },
.{ "MESSAGETABLE", .messagetable },
.{ "PLUGPLAY", .plugplay },
.{ "RCDATA", .rcdata },
.{ "STRINGTABLE", .stringtable },
.{ "TOOLBAR", .toolbar },
.{ "VERSIONINFO", .versioninfo },
.{ "VXD", .vxd },
});
pub fn fromString(bytes: SourceBytes) ResourceType {
const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
if (maybe_ordinal) |ordinal| {
if (ordinal.ordinal >= 256) return .user_defined;
return fromRT(@enumFromInt(ordinal.ordinal));
}
return map.get(bytes.slice) orelse .user_defined;
}
// TODO: Some comptime validation that RT <-> ResourceType conversion is synced?
pub fn fromRT(rt: res.RT) ResourceType {
return switch (rt) {
.ACCELERATOR => .accelerators,
.ANICURSOR => .anicursor_num,
.ANIICON => .aniicon_num,
.BITMAP => .bitmap,
.CURSOR => .cursor_num,
.DIALOG => .dialog,
.DLGINCLUDE => .dlginclude,
.DLGINIT => .dlginit,
.FONT => .font,
.FONTDIR => .fontdir_num,
.GROUP_CURSOR => .cursor,
.GROUP_ICON => .icon,
.HTML => .html,
.ICON => .icon_num,
.MANIFEST => .manifest_num,
.MENU => .menu,
.MESSAGETABLE => .messagetable,
.PLUGPLAY => .plugplay,
.RCDATA => .rcdata,
.STRING => .string_num,
.TOOLBAR => .toolbar,
.VERSION => .versioninfo,
.VXD => .vxd,
_ => .user_defined,
};
}
pub fn canUseRawData(resource: ResourceType) bool {
return switch (resource) {
.user_defined,
.html,
.plugplay, // Obsolete
.rcdata,
.vxd, // Obsolete
.manifest_num,
.dlginit,
=> true,
else => false,
};
}
pub fn nameForErrorDisplay(resource: ResourceType) []const u8 {
return switch (resource) {
// zig fmt: off
.accelerators, .bitmap, .cursor, .dialog, .dialogex, .dlginclude, .dlginit, .font,
.html, .icon, .menu, .menuex, .messagetable, .plugplay, .rcdata, .stringtable,
.toolbar, .versioninfo, .vxd => @tagName(resource),
// zig fmt: on
.user_defined => "user-defined",
.cursor_num => std.fmt.comptimePrint("{d} (cursor)", .{@intFromEnum(res.RT.CURSOR)}),
.icon_num => std.fmt.comptimePrint("{d} (icon)", .{@intFromEnum(res.RT.ICON)}),
.string_num => std.fmt.comptimePrint("{d} (string)", .{@intFromEnum(res.RT.STRING)}),
.anicursor_num => std.fmt.comptimePrint("{d} (anicursor)", .{@intFromEnum(res.RT.ANICURSOR)}),
.aniicon_num => std.fmt.comptimePrint("{d} (aniicon)", .{@intFromEnum(res.RT.ANIICON)}),
.fontdir_num => std.fmt.comptimePrint("{d} (fontdir)", .{@intFromEnum(res.RT.FONTDIR)}),
.manifest_num => std.fmt.comptimePrint("{d} (manifest)", .{@intFromEnum(res.RT.MANIFEST)}),
};
}
};
/// https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable-resource#parameters
/// https://learn.microsoft.com/en-us/windows/win32/menurc/dialog-resource#parameters
/// https://learn.microsoft.com/en-us/windows/win32/menurc/dialogex-resource#parameters
pub const OptionalStatements = enum {
characteristics,
language,
version,
// DIALOG
caption,
class,
exstyle,
font,
menu,
style,
pub const map = std.StaticStringMapWithEql(
OptionalStatements,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CHARACTERISTICS", .characteristics },
.{ "LANGUAGE", .language },
.{ "VERSION", .version },
});
pub const dialog_map = std.StaticStringMapWithEql(
OptionalStatements,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CAPTION", .caption },
.{ "CLASS", .class },
.{ "EXSTYLE", .exstyle },
.{ "FONT", .font },
.{ "MENU", .menu },
.{ "STYLE", .style },
});
};
pub const Control = enum {
auto3state,
autocheckbox,
autoradiobutton,
checkbox,
combobox,
control,
ctext,
defpushbutton,
edittext,
hedit,
iedit,
groupbox,
icon,
listbox,
ltext,
pushbox,
pushbutton,
radiobutton,
rtext,
scrollbar,
state3,
userbutton,
pub const map = std.StaticStringMapWithEql(
Control,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "AUTO3STATE", .auto3state },
.{ "AUTOCHECKBOX", .autocheckbox },
.{ "AUTORADIOBUTTON", .autoradiobutton },
.{ "CHECKBOX", .checkbox },
.{ "COMBOBOX", .combobox },
.{ "CONTROL", .control },
.{ "CTEXT", .ctext },
.{ "DEFPUSHBUTTON", .defpushbutton },
.{ "EDITTEXT", .edittext },
.{ "HEDIT", .hedit },
.{ "IEDIT", .iedit },
.{ "GROUPBOX", .groupbox },
.{ "ICON", .icon },
.{ "LISTBOX", .listbox },
.{ "LTEXT", .ltext },
.{ "PUSHBOX", .pushbox },
.{ "PUSHBUTTON", .pushbutton },
.{ "RADIOBUTTON", .radiobutton },
.{ "RTEXT", .rtext },
.{ "SCROLLBAR", .scrollbar },
.{ "STATE3", .state3 },
.{ "USERBUTTON", .userbutton },
});
pub fn hasTextParam(control: Control) bool {
switch (control) {
.scrollbar, .listbox, .iedit, .hedit, .edittext, .combobox => return false,
else => return true,
}
}
};
pub const ControlClass = struct {
pub const map = std.StaticStringMapWithEql(
res.ControlClass,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BUTTON", .button },
.{ "EDIT", .edit },
.{ "STATIC", .static },
.{ "LISTBOX", .listbox },
.{ "SCROLLBAR", .scrollbar },
.{ "COMBOBOX", .combobox },
});
/// Like `map.get` but works on WTF16 strings, for use with parsed
/// string literals ("BUTTON", or even "\x42UTTON")
pub fn fromWideString(str: []const u16) ?res.ControlClass {
const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
return if (ascii.eqlIgnoreCaseW(str, utf16Literal("BUTTON")))
.button
else if (ascii.eqlIgnoreCaseW(str, utf16Literal("EDIT")))
.edit
else if (ascii.eqlIgnoreCaseW(str, utf16Literal("STATIC")))
.static
else if (ascii.eqlIgnoreCaseW(str, utf16Literal("LISTBOX")))
.listbox
else if (ascii.eqlIgnoreCaseW(str, utf16Literal("SCROLLBAR")))
.scrollbar
else if (ascii.eqlIgnoreCaseW(str, utf16Literal("COMBOBOX")))
.combobox
else
null;
}
};
const ascii = struct {
/// Compares ASCII values case-insensitively, non-ASCII values are compared directly
pub fn eqlIgnoreCaseW(a: []const u16, b: []const u16) bool {
if (a.len != b.len) return false;
for (a, b) |a_c, b_c| {
if (a_c < 128) {
if (std.ascii.toLower(@intCast(a_c)) != std.ascii.toLower(@intCast(b_c))) return false;
} else {
if (a_c != b_c) return false;
}
}
return true;
}
};
pub const MenuItem = enum {
menuitem,
popup,
pub const map = std.StaticStringMapWithEql(
MenuItem,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "MENUITEM", .menuitem },
.{ "POPUP", .popup },
});
pub fn isSeparator(bytes: []const u8) bool {
return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
}
pub const Option = enum {
checked,
grayed,
help,
inactive,
menubarbreak,
menubreak,
pub const map = std.StaticStringMapWithEql(
Option,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CHECKED", .checked },
.{ "GRAYED", .grayed },
.{ "HELP", .help },
.{ "INACTIVE", .inactive },
.{ "MENUBARBREAK", .menubarbreak },
.{ "MENUBREAK", .menubreak },
});
};
};
pub const ToolbarButton = enum {
button,
separator,
pub const map = std.StaticStringMapWithEql(
ToolbarButton,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BUTTON", .button },
.{ "SEPARATOR", .separator },
});
};
pub const VersionInfo = enum {
file_version,
product_version,
file_flags_mask,
file_flags,
file_os,
file_type,
file_subtype,
pub const map = std.StaticStringMapWithEql(
VersionInfo,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "FILEVERSION", .file_version },
.{ "PRODUCTVERSION", .product_version },
.{ "FILEFLAGSMASK", .file_flags_mask },
.{ "FILEFLAGS", .file_flags },
.{ "FILEOS", .file_os },
.{ "FILETYPE", .file_type },
.{ "FILESUBTYPE", .file_subtype },
});
};
pub const VersionBlock = enum {
block,
value,
pub const map = std.StaticStringMapWithEql(
VersionBlock,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BLOCK", .block },
.{ "VALUE", .value },
});
};
/// Keywords that are be the first token in a statement and (if so) dictate how the rest
/// of the statement is parsed.
pub const TopLevelKeywords = enum {
language,
version,
characteristics,
stringtable,
pub const map = std.StaticStringMapWithEql(
TopLevelKeywords,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "LANGUAGE", .language },
.{ "VERSION", .version },
.{ "CHARACTERISTICS", .characteristics },
.{ "STRINGTABLE", .stringtable },
});
};
pub const CommonResourceAttributes = enum {
preload,
loadoncall,
fixed,
moveable,
discardable,
pure,
impure,
shared,
nonshared,
pub const map = std.StaticStringMapWithEql(
CommonResourceAttributes,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "PRELOAD", .preload },
.{ "LOADONCALL", .loadoncall },
.{ "FIXED", .fixed },
.{ "MOVEABLE", .moveable },
.{ "DISCARDABLE", .discardable },
.{ "PURE", .pure },
.{ "IMPURE", .impure },
.{ "SHARED", .shared },
.{ "NONSHARED", .nonshared },
});
};
pub const AcceleratorTypeAndOptions = enum {
virtkey,
ascii,
noinvert,
alt,
shift,
control,
pub const map = std.StaticStringMapWithEql(
AcceleratorTypeAndOptions,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "VIRTKEY", .virtkey },
.{ "ASCII", .ascii },
.{ "NOINVERT", .noinvert },
.{ "ALT", .alt },
.{ "SHIFT", .shift },
.{ "CONTROL", .control },
});
};