Expects bytes to be the full bytes of a string literal token (e.g. including the "" or L"").
pub fn parseAcceleratorKeyString(bytes: SourceBytes, is_virt: bool, options: literals.StringParseOptions) (ParseAcceleratorKeyStringError || Allocator.Error)!u16
pub fn parseAcceleratorKeyString(bytes: SourceBytes, is_virt: bool, options: literals.StringParseOptions) (ParseAcceleratorKeyStringError || Allocator.Error)!u16 {
if (bytes.slice.len == 0) {
return error.EmptyAccelerator;
}
var parser = literals.IterativeStringParser.init(bytes, options);
var translator = AcceleratorKeyCodepointTranslator{
.string_type = parser.declared_string_type,
.output_code_page = options.output_code_page,
};
const first_codepoint = translator.translate(try parser.next()) orelse return error.EmptyAccelerator;
// 0 is treated as a terminator, so this is equivalent to an empty string
if (first_codepoint == 0) return error.EmptyAccelerator;
if (first_codepoint == '^') {
// Note: Emitting this warning unconditionally whenever ^ is the first character
// matches the Win32 RC behavior, but it's questionable whether or not
// the warning should be emitted for ^^ since that results in the ASCII
// character ^ being written to the .res.
if (is_virt and options.diagnostics != null) {
try options.diagnostics.?.diagnostics.append(.{
.err = .ascii_character_not_equivalent_to_virtual_key_code,
.type = .warning,
.code_page = bytes.code_page,
.token = options.diagnostics.?.token,
});
}
const c = translator.translate(try parser.next()) orelse return error.InvalidControlCharacter;
const third_codepoint = translator.translate(try parser.next());
// 0 is treated as a terminator, so a 0 in the third position is fine but
// anything else is too many codepoints for an accelerator
if (third_codepoint != null and third_codepoint.? != 0) return error.InvalidControlCharacter;
switch (c) {
'^' => return '^', // special case
'a'...'z', 'A'...'Z' => return std.ascii.toUpper(@intCast(c)) - 0x40,
// Note: The Windows RC compiler allows more than just A-Z, but what it allows
// seems to be tied to some sort of Unicode-aware 'is character' function or something.
// The full list of codepoints that trigger an out-of-range error can be found here:
// https://gist.github.com/squeek502/2e9d0a4728a83eed074ad9785a209fd0
// For codepoints >= 0x80 that don't trigger the error, the Windows RC compiler takes the
// codepoint and does the `- 0x40` transformation as if it were A-Z which couldn't lead
// to anything useable, so there's no point in emulating that behavior--erroring for
// all non-[a-zA-Z] makes much more sense and is what was probably intended by the
// Windows RC compiler.
else => return error.ControlCharacterOutOfRange,
}
@compileError("this should be unreachable");
}
const second_codepoint = translator.translate(try parser.next());
var result: u32 = initial_value: {
if (first_codepoint >= 0x10000) {
if (second_codepoint != null and second_codepoint.? != 0) return error.AcceleratorTooLong;
// No idea why it works this way, but this seems to match the Windows RC
// behavior for codepoints >= 0x10000
const low = @as(u16, @intCast(first_codepoint & 0x3FF)) + 0xDC00;
const extra = (first_codepoint - 0x10000) / 0x400;
break :initial_value low + extra * 0x100;
}
break :initial_value first_codepoint;
};
// 0 is treated as a terminator
if (second_codepoint != null and second_codepoint.? == 0) return @truncate(result);
const third_codepoint = translator.translate(try parser.next());
// 0 is treated as a terminator, so a 0 in the third position is fine but
// anything else is too many codepoints for an accelerator
if (third_codepoint != null and third_codepoint.? != 0) return error.AcceleratorTooLong;
if (second_codepoint) |c| {
if (c >= 0x10000) return error.AcceleratorTooLong;
result <<= 8;
result += c;
} else if (is_virt) {
switch (result) {
'a'...'z' => result -= 0x20, // toUpper
else => {},
}
}
return @truncate(result);
}