fix ... in F_ARGS opcode assembly

This commit is contained in:
Martin Michelsen
2023-12-11 13:56:00 -08:00
parent bc017578e3
commit d9cdf9804f
+214 -206
View File
@@ -1895,225 +1895,233 @@ std::string assemble_quest_script(const std::string& text) {
if (line_tokens.size() < 2) {
throw runtime_error(string_printf("(line %zu) arguments required for %s", line_num, opcode_def->name));
}
auto args = split_context(line_tokens[1], ',');
if (args.size() != opcode_def->args.size()) {
throw runtime_error(string_printf("(line %zu) incorrect argument count for %s", line_num, opcode_def->name));
}
strip_trailing_whitespace(line_tokens[1]);
strip_leading_whitespace(line_tokens[1]);
for (size_t z = 0; z < args.size(); z++) {
using Type = QuestScriptOpcodeDefinition::Argument::Type;
if (starts_with(line_tokens[1], "...")) {
if (!(opcode_def->flags & F_ARGS)) {
throw runtime_error(string_printf("(line %zu) \'...\' can only be used with F_ARGS opcodes", line_num));
}
string& arg = args[z];
const auto& arg_def = opcode_def->args[z];
strip_trailing_whitespace(arg);
strip_leading_whitespace(arg);
} else { // Not "..."
auto args = split_context(line_tokens[1], ',');
if (args.size() != opcode_def->args.size()) {
throw runtime_error(string_printf("(line %zu) incorrect argument count for %s", line_num, opcode_def->name));
}
try {
auto parse_reg = +[](const string& name) -> uint8_t {
if ((name[0] != 'r') && (name[0] != 'f')) {
throw runtime_error("a register is required");
}
size_t reg_num = stoull(name.substr(1), nullptr, 0);
if (reg_num > 0xFF) {
throw runtime_error("invalid register number");
}
return reg_num;
};
auto add_cstr = [&](const string& text) -> void {
switch (quest_version) {
case Version::DC_NTE:
code_w.write(tt_utf8_to_sjis(text));
code_w.put_u8(0);
break;
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3:
code_w.write(quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sjis(text));
code_w.put_u8(0);
break;
case Version::PC_V2:
case Version::BB_V4:
code_w.write(tt_utf8_to_utf16(text));
code_w.put_u16(0);
break;
default:
throw logic_error("invalid game version");
}
};
for (size_t z = 0; z < args.size(); z++) {
using Type = QuestScriptOpcodeDefinition::Argument::Type;
if (opcode_def->flags & F_ARGS) {
auto label_it = labels_by_name.find(arg);
if (starts_with(line_tokens[1], "...")) {
// Args were specified by preceding arg_push calls; nothing to do here
} else if (arg.empty()) {
throw runtime_error("argument is empty");
} else if (label_it != labels_by_name.end()) {
code_w.put_u8(0x4B); // arg_pushw
code_w.put_u16l(label_it->second->index);
} else if ((arg[0] == 'r') || (arg[0] == 'f')) {
// If the corresponding argument is a REG or REG_SET_FIXED, push
// the register number, not the register's value, since it's an
// out-param
if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) {
code_w.put_u8(0x4A); // arg_pushb
code_w.put_u8(parse_reg(arg));
} else if (
(arg_def.type == Type::REG_SET_FIXED) ||
(arg_def.type == Type::REG32_SET_FIXED)) {
auto tokens = split(arg, '-');
uint8_t start_reg;
if (tokens.size() == 1) {
start_reg = parse_reg(tokens[0]);
} else if (tokens.size() == 2) {
start_reg = parse_reg(tokens[0]);
if (static_cast<size_t>(parse_reg(tokens[1]) - start_reg + 1) != arg_def.count) {
throw runtime_error("incorrect number of registers used");
}
} else {
throw runtime_error("invalid fixed register set syntax");
}
code_w.put_u8(0x4A); // arg_pushb
code_w.put_u8(start_reg);
} else {
code_w.put_u8(0x48); // arg_pushr
code_w.put_u8(parse_reg(arg));
string& arg = args[z];
const auto& arg_def = opcode_def->args[z];
strip_trailing_whitespace(arg);
strip_leading_whitespace(arg);
try {
auto parse_reg = +[](const string& name) -> uint8_t {
if ((name[0] != 'r') && (name[0] != 'f')) {
throw runtime_error("a register is required");
}
} else if ((arg[0] == '@') && ((arg[1] == 'r') || (arg[1] == 'f'))) {
code_w.put_u8(0x4C); // arg_pusha
code_w.put_u8(parse_reg(arg.substr(1)));
} else if ((arg[0] == '@') && labels_by_name.count(arg.substr(1))) {
code_w.put_u8(0x4D); // arg_pusho
code_w.put_u16(labels_by_name.at(arg.substr(1))->index);
} else {
bool write_as_str = false;
try {
size_t end_offset;
uint64_t value = stoll(arg, &end_offset, 0);
if (end_offset != arg.size()) {
write_as_str = true;
} else if (value > 0xFFFF) {
code_w.put_u8(0x49); // arg_pushl
code_w.put_u32l(value);
} else if (value > 0xFF) {
code_w.put_u8(0x4B); // arg_pushw
code_w.put_u16l(value);
} else {
size_t reg_num = stoull(name.substr(1), nullptr, 0);
if (reg_num > 0xFF) {
throw runtime_error("invalid register number");
}
return reg_num;
};
auto add_cstr = [&](const string& text) -> void {
switch (quest_version) {
case Version::DC_NTE:
code_w.write(tt_utf8_to_sjis(text));
code_w.put_u8(0);
break;
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3:
code_w.write(quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sjis(text));
code_w.put_u8(0);
break;
case Version::PC_V2:
case Version::BB_V4:
code_w.write(tt_utf8_to_utf16(text));
code_w.put_u16(0);
break;
default:
throw logic_error("invalid game version");
}
};
if (opcode_def->flags & F_ARGS) {
auto label_it = labels_by_name.find(arg);
if (arg.empty()) {
throw runtime_error("argument is empty");
} else if (label_it != labels_by_name.end()) {
code_w.put_u8(0x4B); // arg_pushw
code_w.put_u16l(label_it->second->index);
} else if ((arg[0] == 'r') || (arg[0] == 'f')) {
// If the corresponding argument is a REG or REG_SET_FIXED, push
// the register number, not the register's value, since it's an
// out-param
if ((arg_def.type == Type::REG) || (arg_def.type == Type::REG32)) {
code_w.put_u8(0x4A); // arg_pushb
code_w.put_u8(value);
}
} catch (const exception&) {
write_as_str = true;
}
if (write_as_str) {
if (arg[0] == '\"') {
code_w.put_u8(0x4E); // arg_pushs
add_cstr(parse_data_string(arg));
} else {
throw runtime_error("invalid argument syntax");
}
}
}
} else { // Not F_ARGS
auto add_label = [&](const string& name, bool is32) -> void {
if (!labels_by_name.count(name)) {
throw runtime_error("label not defined: " + name);
}
if (is32) {
code_w.put_u32(labels_by_name.at(name)->index);
} else {
code_w.put_u16(labels_by_name.at(name)->index);
}
};
auto add_reg = [&](const string& name, bool is32) -> void {
if (is32) {
code_w.put_u32l(parse_reg(name));
} else {
code_w.put_u8(parse_reg(name));
}
};
auto split_set = [&](const string& text) -> vector<string> {
if (!starts_with(text, "[") || !ends_with(text, "]")) {
throw runtime_error("incorrect syntax for set-valued argument");
}
auto values = split(text.substr(1, text.size() - 2), ',');
if (values.size() > 0xFF) {
throw runtime_error("too many labels in set-valued argument");
}
return values;
};
switch (arg_def.type) {
case Type::LABEL16:
case Type::LABEL32:
add_label(arg, arg_def.type == Type::LABEL32);
break;
case Type::LABEL16_SET: {
auto label_names = split_set(arg);
code_w.put_u8(label_names.size());
for (auto name : label_names) {
strip_trailing_whitespace(name);
strip_leading_whitespace(name);
add_label(name, false);
}
break;
}
case Type::REG:
case Type::REG32:
add_reg(arg, arg_def.type == Type::REG32);
break;
case Type::REG_SET_FIXED:
case Type::REG32_SET_FIXED: {
auto tokens = split(arg, '-');
if (tokens.size() == 1) {
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
} else if (tokens.size() == 2) {
if (static_cast<size_t>(parse_reg(tokens[1]) - parse_reg(tokens[0]) + 1) != arg_def.count) {
throw runtime_error("incorrect number of registers used");
code_w.put_u8(parse_reg(arg));
} else if (
(arg_def.type == Type::REG_SET_FIXED) ||
(arg_def.type == Type::REG32_SET_FIXED)) {
auto tokens = split(arg, '-');
uint8_t start_reg;
if (tokens.size() == 1) {
start_reg = parse_reg(tokens[0]);
} else if (tokens.size() == 2) {
start_reg = parse_reg(tokens[0]);
if (static_cast<size_t>(parse_reg(tokens[1]) - start_reg + 1) != arg_def.count) {
throw runtime_error("incorrect number of registers used");
}
} else {
throw runtime_error("invalid fixed register set syntax");
}
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
code_w.put_u8(0x4A); // arg_pushb
code_w.put_u8(start_reg);
} else {
throw runtime_error("invalid fixed register set syntax");
code_w.put_u8(0x48); // arg_pushr
code_w.put_u8(parse_reg(arg));
}
break;
}
case Type::REG_SET: {
auto regs = split_set(arg);
code_w.put_u8(regs.size());
for (auto reg : regs) {
strip_trailing_whitespace(reg);
strip_leading_whitespace(reg);
add_reg(reg, false);
} else if ((arg[0] == '@') && ((arg[1] == 'r') || (arg[1] == 'f'))) {
code_w.put_u8(0x4C); // arg_pusha
code_w.put_u8(parse_reg(arg.substr(1)));
} else if ((arg[0] == '@') && labels_by_name.count(arg.substr(1))) {
code_w.put_u8(0x4D); // arg_pusho
code_w.put_u16(labels_by_name.at(arg.substr(1))->index);
} else {
bool write_as_str = false;
try {
size_t end_offset;
uint64_t value = stoll(arg, &end_offset, 0);
if (end_offset != arg.size()) {
write_as_str = true;
} else if (value > 0xFFFF) {
code_w.put_u8(0x49); // arg_pushl
code_w.put_u32l(value);
} else if (value > 0xFF) {
code_w.put_u8(0x4B); // arg_pushw
code_w.put_u16l(value);
} else {
code_w.put_u8(0x4A); // arg_pushb
code_w.put_u8(value);
}
} catch (const exception&) {
write_as_str = true;
}
if (write_as_str) {
if (arg[0] == '\"') {
code_w.put_u8(0x4E); // arg_pushs
add_cstr(parse_data_string(arg));
} else {
throw runtime_error("invalid argument syntax");
}
}
break;
}
case Type::INT8:
code_w.put_u8(stol(arg, nullptr, 0));
break;
case Type::INT16:
code_w.put_u16l(stol(arg, nullptr, 0));
break;
case Type::INT32:
code_w.put_u32l(stol(arg, nullptr, 0));
break;
case Type::FLOAT32:
code_w.put_u32l(stof(arg, nullptr));
break;
case Type::CSTRING:
add_cstr(parse_data_string(arg));
break;
default:
throw logic_error("unknown argument type");
} else { // Not F_ARGS
auto add_label = [&](const string& name, bool is32) -> void {
if (!labels_by_name.count(name)) {
throw runtime_error("label not defined: " + name);
}
if (is32) {
code_w.put_u32(labels_by_name.at(name)->index);
} else {
code_w.put_u16(labels_by_name.at(name)->index);
}
};
auto add_reg = [&](const string& name, bool is32) -> void {
if (is32) {
code_w.put_u32l(parse_reg(name));
} else {
code_w.put_u8(parse_reg(name));
}
};
auto split_set = [&](const string& text) -> vector<string> {
if (!starts_with(text, "[") || !ends_with(text, "]")) {
throw runtime_error("incorrect syntax for set-valued argument");
}
auto values = split(text.substr(1, text.size() - 2), ',');
if (values.size() > 0xFF) {
throw runtime_error("too many labels in set-valued argument");
}
return values;
};
switch (arg_def.type) {
case Type::LABEL16:
case Type::LABEL32:
add_label(arg, arg_def.type == Type::LABEL32);
break;
case Type::LABEL16_SET: {
auto label_names = split_set(arg);
code_w.put_u8(label_names.size());
for (auto name : label_names) {
strip_trailing_whitespace(name);
strip_leading_whitespace(name);
add_label(name, false);
}
break;
}
case Type::REG:
case Type::REG32:
add_reg(arg, arg_def.type == Type::REG32);
break;
case Type::REG_SET_FIXED:
case Type::REG32_SET_FIXED: {
auto tokens = split(arg, '-');
if (tokens.size() == 1) {
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
} else if (tokens.size() == 2) {
if (static_cast<size_t>(parse_reg(tokens[1]) - parse_reg(tokens[0]) + 1) != arg_def.count) {
throw runtime_error("incorrect number of registers used");
}
add_reg(tokens[0], arg_def.type == Type::REG32_SET_FIXED);
} else {
throw runtime_error("invalid fixed register set syntax");
}
break;
}
case Type::REG_SET: {
auto regs = split_set(arg);
code_w.put_u8(regs.size());
for (auto reg : regs) {
strip_trailing_whitespace(reg);
strip_leading_whitespace(reg);
add_reg(reg, false);
}
break;
}
case Type::INT8:
code_w.put_u8(stol(arg, nullptr, 0));
break;
case Type::INT16:
code_w.put_u16l(stol(arg, nullptr, 0));
break;
case Type::INT32:
code_w.put_u32l(stol(arg, nullptr, 0));
break;
case Type::FLOAT32:
code_w.put_u32l(stof(arg, nullptr));
break;
case Type::CSTRING:
add_cstr(parse_data_string(arg));
break;
default:
throw logic_error("unknown argument type");
}
}
} catch (const exception& e) {
throw runtime_error(string_printf("(arg %zu) %s", z + 1, e.what()));
}
} catch (const exception& e) {
throw runtime_error(string_printf("(arg %zu) %s", z + 1, e.what()));
}
}