#include "script-call-internal.hpp" // script-call.cpp - EAthena script frontend, engine, and library. // // Copyright © ????-2004 Athena Dev Teams // Copyright © 2004-2011 The Mana World Development Team // Copyright © 2011 Chuck Miller // Copyright © 2011-2014 Ben Longbons // Copyright © 2013 wushin // // This file is part of The Mana World (Athena server) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "../generic/intern-pool.hpp" #include "../io/cxxstdio.hpp" #include "../mmo/cxxstdio_enums.hpp" #include "../high/core.hpp" #include "battle.hpp" #include "battle_conf.hpp" #include "globals.hpp" #include "map.hpp" #include "npc.hpp" #include "pc.hpp" #include "script-fun.hpp" #include "script-parse-internal.hpp" #include "script-persist.hpp" #include "script-startup-internal.hpp" #include "../poison.hpp" namespace tmwa { namespace map { constexpr bool DEBUG_RUN = false; static struct ScriptConfigRun { static const int check_cmdcount = 8192; static const int check_gotocount = 512; } script_config; /*========================================== * ridからsdへの解決 *------------------------------------------ */ dumb_ptr script_rid2sd(ScriptState *st) { dumb_ptr sd = map_id2sd(st->rid); if (!sd) { PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt); st->state = ScriptEndState::END; return nullptr; } return sd; } /*========================================== * 変数の読み取り * Reading variables *------------------------------------------ */ void get_val(dumb_ptr sd, struct script_data *data) { MATCH_BEGIN (*data) { MATCH_CASE (const ScriptDataParam&, u) { int numi = 0; if (sd) numi = pc_readparam(sd, u.reg.sp()); *data = ScriptDataInt{numi}; } MATCH_CASE (const ScriptDataVariable&, u) { ZString name_ = variable_names.outtern(u.reg.base()); VarName name = stringish(name_); char prefix = name.front(); char postfix = name.back(); if (postfix == '$') { RString str; if (prefix == '@' || (prefix == '.' && name[1] != '@')) { if (sd) str = pc_readregstr(sd, u.reg); // sd can also be a nd: they're handled the same way } else if (prefix == '$') { Option> s_ = mapregstr_db.search(u.reg); OMATCH_BEGIN_SOME (s, s_) { str = *s; } OMATCH_END (); } else { PRINTF("script: get_val: illegal scope string variable.\n"_fmt); str = "!!ERROR!!"_s; } *data = ScriptDataStr{str}; } else { int numi = 0; if (prefix == '@' || (prefix == '.' && name[1] != '@')) { if (sd) numi = pc_readreg(sd, u.reg); // sd can also be a nd: they're handled the same way } else if (prefix == '$') { numi = mapreg_db.get(u.reg); } else if (prefix == '#') { if (name[1] == '#') { if (sd) numi = pc_readaccountreg2(sd->is_player(), name); } else { if (sd) numi = pc_readaccountreg(sd->is_player(), name); } } else { if (sd) numi = pc_readglobalreg(sd->is_player(), name); } *data = ScriptDataInt{numi}; } } } MATCH_END (); } void get_val(ScriptState *st, struct script_data *data) { dumb_ptr bl = nullptr; MATCH_BEGIN (*data) { MATCH_CASE (const ScriptDataParam&, u) { (void)u; // XXX travis complains if we don't use u bl = map_id2bl(st->rid); } MATCH_CASE (const ScriptDataVariable&, u) { ZString name_ = variable_names.outtern(u.reg.base()); VarName name = stringish(name_); char prefix = name.front(); if (prefix == '.' && name[1] == '@') { if (name.back() == '$') { Option> s = st->regstrm.search(u.reg); ZString val = s.map([](P s_) -> ZString { return *s_; }).copy_or(""_s); *data = ScriptDataStr{val}; } else *data = ScriptDataInt{st->regm.get(u.reg)}; return; } if (prefix == '.' && st->oid) bl = map_id2bl(st->oid); else if (prefix != '$' && st->rid) bl = map_id2bl(st->rid); } } MATCH_END (); get_val(bl, data); } /*========================================== * 変数の読み取り2 * Reading Variables2 *------------------------------------------ */ struct script_data get_val2(ScriptState *st, SIR reg) { struct script_data dat = ScriptDataVariable{reg}; get_val(st, &dat); return dat; } /*========================================== * 変数設定用 * For setting variables *------------------------------------------ */ void set_reg(dumb_ptr sd, VariableCode type, SIR reg, struct script_data vd) { if (type == VariableCode::PARAM) { int val = vd.get_if()->numi; pc_setparam(sd, reg.sp(), val); return; } assert (type == VariableCode::VARIABLE); ZString name_ = variable_names.outtern(reg.base()); VarName name = stringish(name_); char prefix = name.front(); char postfix = name.back(); if (postfix == '$') { RString str = vd.get_if()->str; if (prefix == '@' || prefix == '.') { pc_setregstr(sd, reg, str); } else if (prefix == '$') { mapreg_setregstr(reg, str); } else { PRINTF("script: set_reg: illegal scope string variable !"_fmt); } } else { int val = vd.get_if()->numi; if (prefix == '@' || prefix == '.') { pc_setreg(sd, reg, val); } else if (prefix == '$') { mapreg_setreg(reg, val); } else if (prefix == '#') { if (name[1] == '#') pc_setaccountreg2(sd->is_player(), name, val); else pc_setaccountreg(sd->is_player(), name, val); } else { pc_setglobalreg(sd->is_player(), name, val); } } } void set_scope_reg(ScriptState *st, SIR reg, struct script_data *vd) { ZString name = variable_names.outtern(reg.base()); if (name.back() == '$') { if (auto *u = vd->get_if()) { if (!u->str) { st->regstrm.erase(reg); return; } st->regstrm.insert(reg, u->str); } else st->regstrm.insert(reg, conv_str(st, vd)); } else if (auto *u = vd->get_if()) st->regm.put(reg, u->numi); else st->regm.put(reg, conv_num(st, vd)); } void set_reg(dumb_ptr sd, VariableCode type, SIR reg, int id) { struct script_data vd = ScriptDataInt{id}; set_reg(sd, type, reg, vd); } void set_reg(dumb_ptr sd, VariableCode type, SIR reg, RString zd) { struct script_data vd = ScriptDataStr{zd}; set_reg(sd, type, reg, vd); } /*========================================== * 文字列への変換 *------------------------------------------ */ RString conv_str(ScriptState *st, struct script_data *data) { get_val(st, data); assert (!data->is()); if (auto *u = data->get_if()) { AString buf = STRPRINTF("%d"_fmt, u->numi); *data = ScriptDataStr{buf}; } return data->get_if()->str; } /*========================================== * 数値へ変換 *------------------------------------------ */ int conv_num(ScriptState *st, struct script_data *data) { int rv = 0; get_val(st, data); assert (!data->is()); MATCH_BEGIN (*data) { MATCH_DEFAULT () { abort(); } MATCH_CASE (const ScriptDataStr&, u) { RString p = u.str; rv = atoi(p.c_str()); } MATCH_CASE (const ScriptDataInt&, u) { return u.numi; } MATCH_CASE (const ScriptDataPos&, u) { return u.numi; } } MATCH_END () *data = ScriptDataInt{rv}; return rv; } Borrowed conv_script(ScriptState *st, struct script_data *data) { get_val(st, data); return data->get_if()->script; } void push_copy(struct script_stack *stack, int pos_) { script_data csd = stack->stack_datav[pos_]; stack->stack_datav.push_back(csd); } void pop_stack(struct script_stack *stack, int start, int end) { auto it = stack->stack_datav.begin(); stack->stack_datav.erase(it + start, it + end); } // // 実行部main // /*========================================== * コマンドの読み取り *------------------------------------------ */ static ByteCode get_com(ScriptPointer *script) { if (script == nullptr) return ByteCode::NOP; if (static_cast(script->peek()) >= 0x80) { // synthetic! Does not advance pos yet. return ByteCode::INT; } return script->pop(); } /*========================================== * 数値の所得 *------------------------------------------ */ static int get_num(ScriptPointer *scr) { int i = 0; int j = 0; uint8_t val; do { val = static_cast(scr->pop()); i += (val & 0x7f) << j; j += 6; } while (val >= 0xc0); return i; } /*========================================== * スタックから値を取り出す *------------------------------------------ */ static int pop_val(ScriptState *st) { if (st->stack->stack_datav.empty()) return 0; script_data& back = st->stack->stack_datav.back(); get_val(st, &back); int rv = 0; if (auto *u = back.get_if()) rv = u->numi; st->stack->stack_datav.pop_back(); return rv; } static bool isstr(struct script_data& c) { return c.is(); } /*========================================== * 加算演算子 *------------------------------------------ */ static void op_add(ScriptState *st) { get_val(st, &st->stack->stack_datav.back()); script_data back = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); script_data& back1 = st->stack->stack_datav.back(); get_val(st, &back1); if (!(isstr(back) || isstr(back1))) { back1.get_if()->numi += back.get_if()->numi; } else { RString sb = conv_str(st, &back); RString sb1 = conv_str(st, &back1); MString buf; buf += sb1; buf += sb; back1 = ScriptDataStr{.str= AString(buf)}; } } /*========================================== * 二項演算子(文字列) *------------------------------------------ */ static void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2) { int a = 0; switch (op) { case ByteCode::EQ: a = s1 == s2; break; case ByteCode::NE: a = s1 != s2; break; case ByteCode::GT: a = s1 > s2; break; case ByteCode::GE: a = s1 >= s2; break; case ByteCode::LT: a = s1 < s2; break; case ByteCode::LE: a = s1 <= s2; break; default: PRINTF("illegal string operater\n"_fmt); break; } push_int(st->stack, a); } /*========================================== * 二項演算子(数値) *------------------------------------------ */ static void op_2num(ScriptState *st, ByteCode op, int i1, int i2) { switch (op) { case ByteCode::SUB: i1 -= i2; break; case ByteCode::MUL: i1 *= i2; break; case ByteCode::DIV: i1 /= i2; break; case ByteCode::MOD: i1 %= i2; break; case ByteCode::AND: i1 &= i2; break; case ByteCode::OR: i1 |= i2; break; case ByteCode::XOR: i1 ^= i2; break; case ByteCode::LAND: i1 = i1 && i2; break; case ByteCode::LOR: i1 = i1 || i2; break; case ByteCode::EQ: i1 = i1 == i2; break; case ByteCode::NE: i1 = i1 != i2; break; case ByteCode::GT: i1 = i1 > i2; break; case ByteCode::GE: i1 = i1 >= i2; break; case ByteCode::LT: i1 = i1 < i2; break; case ByteCode::LE: i1 = i1 <= i2; break; case ByteCode::R_SHIFT: i1 = i1 >> i2; break; case ByteCode::L_SHIFT: i1 = i1 << i2; break; } push_int(st->stack, i1); } /*========================================== * 二項演算子 *------------------------------------------ */ static void op_2(ScriptState *st, ByteCode op) { // pop_val has unfortunate implications here script_data d2 = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); get_val(st, &d2); script_data d1 = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); get_val(st, &d1); if (isstr(d1) && isstr(d2)) { // ss => op_2str op_2str(st, op, d1.get_if()->str, d2.get_if()->str); } else if (!(isstr(d1) || isstr(d2))) { // ii => op_2num op_2num(st, op, d1.get_if()->numi, d2.get_if()->numi); } else { // si,is => error PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt); push_int(st->stack, 0); } } /*========================================== * 単項演算子 *------------------------------------------ */ static void op_1num(ScriptState *st, ByteCode op) { int i1; i1 = pop_val(st); switch (op) { case ByteCode::NEG: i1 = -i1; break; case ByteCode::NOT: i1 = ~i1; break; case ByteCode::LNOT: i1 = !i1; break; } push_int(st->stack, i1); } /*========================================== * 関数の実行 *------------------------------------------ */ void run_func(ScriptState *st) { size_t end_sp = st->stack->stack_datav.size(); size_t start_sp = end_sp - 1; while (!st->stack->stack_datav[start_sp].is()) { start_sp--; if (start_sp == 0) { dumb_ptr nd = map_id_is_npc(st->oid); PRINTF("run_func: function not found! @ %s\n"_fmt, nd ? nd->name : NpcName()); abort(); } } // the func is before the arg start_sp--; st->start = start_sp; st->end = end_sp; if (!st->stack->stack_datav[st->start].is()) { dumb_ptr nd = map_id_is_npc(st->oid); PRINTF("run_func: not a function or statement! @ %s\n"_fmt, nd ? nd->name : NpcName()); abort(); } size_t func = st->stack->stack_datav[st->start].get_if()->numi; if (DEBUG_RUN && battle_config.etc_log) { PRINTF("run_func : %s\n"_fmt, builtin_functions[func].name); PRINTF("stack dump :"_fmt); for (script_data& d : st->stack->stack_datav) { MATCH_BEGIN (d) { MATCH_CASE (const ScriptDataInt&, u) { PRINTF(" int(%d)"_fmt, u.numi); } MATCH_CASE (const ScriptDataRetInfo&, u) { PRINTF(" retinfo(%p)"_fmt, static_cast(&*u.script)); } MATCH_CASE (const ScriptDataParam&, u) { PRINTF(" param(%d)"_fmt, u.reg.sp()); } MATCH_CASE (const ScriptDataVariable&, u) { PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index()); } MATCH_CASE (const ScriptDataArg&, u) { (void)u; PRINTF(" arg"_fmt); } MATCH_CASE (const ScriptDataPos&, u) { (void)u; PRINTF(" pos(%d)"_fmt, u.numi); } MATCH_CASE (const ScriptDataStr&, u) { (void)u; PRINTF(" str(%s)"_fmt, u.str); } MATCH_CASE (const ScriptDataFuncRef&, u) { (void)u; PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name); } } MATCH_END (); } PRINTF("\n"_fmt); } builtin_functions[func].func(st); if (builtin_functions[func].ret != '\0') { // this builtin is expected to return a value script_data &back = st->stack->stack_datav.back(); if (builtin_functions[func].ret == 'i' && !back.is()) { PRINTF("script-call:run_func: expected %s to push an integer but none was found. aborting script execution.\n"_fmt, builtin_functions[func].name); st->state = ScriptEndState::END; } else if (builtin_functions[func].ret == 's' && !back.is()) { PRINTF("script-call:run_func: expected %s to push a string but none was found. aborting script execution.\n"_fmt, builtin_functions[func].name); st->state = ScriptEndState::END; } else if (builtin_functions[func].ret == 'v' && !back.is() && !back.is() && !back.is() && !back.is()) { PRINTF("script-call:run_func: expected %s to push a value but none was found. aborting script execution.\n"_fmt, builtin_functions[func].name); st->state = ScriptEndState::END; } else if (builtin_functions[func].ret == 'r' && !back.is()) { PRINTF("script-call:run_func: expected %s to push a variable reference but none was found. aborting script execution.\n"_fmt, builtin_functions[func].name); st->state = ScriptEndState::END; } else if (builtin_functions[func].ret == 'l' && !back.is()) { PRINTF("script-call:run_func: expected %s to push a label pos but none was found. aborting script execution.\n"_fmt, builtin_functions[func].name); st->state = ScriptEndState::END; } } pop_stack(st->stack, start_sp, end_sp); if (st->state == ScriptEndState::RETFUNC) { // ユーザー定義関数からの復帰 int olddefsp = st->defsp; pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除 if (st->defsp < 4 || !st->stack->stack_datav[st->defsp - 1].is()) { dumb_ptr nd = map_id_is_npc(st->oid); PRINTF("run_func: return without callfunc or callsub! @ %s\n"_fmt, nd ? nd->name : NpcName()); } assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet st->scriptp.code = Some(conv_script(st, &st->stack->stack_datav[olddefsp - 1])); // スクリプトを復元 st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元 st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元 // Number of arguments. int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得 //assert (i == 0); pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 st->state = ScriptEndState::GOTO; } } /*========================================== * スクリプトの実行メイン部分 *------------------------------------------ */ static void run_script_main(ScriptState *st, Borrowed rootscript) { if (st == nullptr) return; int cmdcount = script_config.check_cmdcount; int gotocount = script_config.check_gotocount; struct script_stack *stack = st->stack; if (stack == nullptr) return; st->defsp = stack->stack_datav.size(); int rerun_pos = st->scriptp.pos; st->state = ScriptEndState::ZERO; while (st->state == ScriptEndState::ZERO) { switch (ByteCode c = get_com(&st->scriptp)) { case ByteCode::EOL: if (stack->stack_datav.size() != st->defsp) { if (true) PRINTF("stack.sp (%zu) != default (%d)\n"_fmt, stack->stack_datav.size(), st->defsp); abort(); } rerun_pos = st->scriptp.pos; break; case ByteCode::INT: // synthesized! push_int(stack, get_num(&st->scriptp)); break; case ByteCode::POS: case ByteCode::VARIABLE: case ByteCode::FUNC_REF: case ByteCode::PARAM: // Note that these 3 have *very* different meanings, // despite being encoded similarly. { int arg = 0; arg |= static_cast(st->scriptp.pop()) << 0; arg |= static_cast(st->scriptp.pop()) << 8; arg |= static_cast(st->scriptp.pop()) << 16; switch(c) { case ByteCode::POS: push_int(stack, arg); break; case ByteCode::VARIABLE: push_reg(stack, SIR::from(arg)); break; case ByteCode::FUNC_REF: push_int(stack, arg); break; case ByteCode::PARAM: SP arg_sp = static_cast(arg); push_reg(stack, SIR::from(arg_sp)); break; } } break; case ByteCode::ARG: push_int(stack, 0); break; case ByteCode::STR: push_str(stack, st->scriptp.pops()); break; case ByteCode::FUNC: run_func(st); if (st->state == ScriptEndState::GOTO) { rerun_pos = st->scriptp.pos; st->state = ScriptEndState::ZERO; if (st->freeloop != 1 && gotocount > 0 && (--gotocount) <= 0) { dumb_ptr nd = map_id_is_npc(st->oid); PRINTF("run_script: infinity loop! @ %s\n"_fmt, nd ? nd->name : NpcName()); st->state = ScriptEndState::END; } } break; case ByteCode::ADD: op_add(st); break; case ByteCode::SUB: case ByteCode::MUL: case ByteCode::DIV: case ByteCode::MOD: case ByteCode::EQ: case ByteCode::NE: case ByteCode::GT: case ByteCode::GE: case ByteCode::LT: case ByteCode::LE: case ByteCode::AND: case ByteCode::OR: case ByteCode::XOR: case ByteCode::LAND: case ByteCode::LOR: case ByteCode::R_SHIFT: case ByteCode::L_SHIFT: op_2(st, c); break; case ByteCode::NEG: case ByteCode::NOT: case ByteCode::LNOT: op_1num(st, c); break; case ByteCode::NOP: st->state = ScriptEndState::END; break; default: if (battle_config.error_log) { PRINTF("unknown command : %d @ %zu\n"_fmt, c, st->scriptp.pos); if (st->oid) { dumb_ptr nd = map_id_is_npc(st->oid); PRINTF("NPC => %s\n"_fmt, nd->name); } if (st->rid) { dumb_ptr sd = script_rid2sd(st); if (sd) PRINTF("PC => %s\n"_fmt, sd->status_key.name.to__actual()); } } abort(); } if (st->freeloop != 1 && cmdcount > 0 && (--cmdcount) <= 0) { dumb_ptr nd = map_id_is_npc(st->oid); PRINTF("run_script: infinity loop! @ %s\n"_fmt, nd ? nd->name : NpcName()); st->state = ScriptEndState::END; } } dumb_ptr sd = map_id2sd(st->rid); switch (st->state) { case ScriptEndState::STOP: if (sd && sd->npc_id == st->oid) sd->state.npc_dialog_mes = 0; break; case ScriptEndState::END: st->scriptp.code = None; st->scriptp.pos = -1; if (sd && sd->npc_id == st->oid) { sd->state.npc_dialog_mes = 0; npc_event_dequeue(sd); } break; case ScriptEndState::RERUNLINE: if (sd && sd->npc_id == st->oid) sd->state.npc_dialog_mes = 0; st->scriptp.pos = rerun_pos; break; } if (st->state != ScriptEndState::END) { // 再開するためにスタック情報を保存 if (sd) { sd->npc_stackbuf = stack->stack_datav; sd->npc_script = st->scriptp.code; // sd->npc_pos is set later ... ??? sd->npc_scriptroot = Some(rootscript); } } } /*========================================== * スクリプトの実行 *------------------------------------------ */ int run_script(ScriptPointer sp, BlockId rid, BlockId oid) { return run_script_l(sp, rid, oid, nullptr); } int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, Slice args) { struct script_stack stack; ScriptState st; dumb_ptr bl = map_id2bl(rid); dumb_ptr sd = bl? bl->is_player(): nullptr; if (oid) { dumb_ptr oid_bl = map_id2bl(oid); if (oid_bl) { if (oid_bl->bl_type == BL::NPC) { dumb_ptr nd = oid_bl->is_npc(); if(nd->npc_subtype == NpcSubtype::SCRIPT) { dumb_ptr nds = nd->is_script(); if (nds->scr.parent) { dumb_ptr parent = map_id2bl(nds->scr.parent)->is_npc()->is_script(); assert(parent->bl_type == BL::NPC && parent->npc_subtype == NpcSubtype::SCRIPT); sp = ScriptPointer(borrow(*parent->scr.script), sp.pos); } } } } } P rootscript = TRY_UNWRAP(sp.code, return -1); int i; if (sp.pos >> 24) return -1; if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == Some(rootscript)) { // 前回のスタックを復帰 sp.code = sd->npc_script; stack.stack_datav = std::move(sd->npc_stackbuf); } st.stack = &stack; st.scriptp = sp; st.rid = rid; st.oid = oid; st.freeloop = 0; st.is_true = 0; for (i = 0; i < args.size(); i++) { if (args[i].name.back() == '$') pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s); else pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i); } run_script_main(&st, rootscript); stack.stack_datav.clear(); return st.scriptp.pos; } void set_script_var_i(dumb_ptr sd, VarName var, int e, int val) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); set_reg(sd, VariableCode::VARIABLE, reg, val); } void set_script_var_s(dumb_ptr sd, VarName var, int e, XString val) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); set_reg(sd, VariableCode::VARIABLE, reg, val); } int get_script_var_i(dumb_ptr sd, VarName var, int e) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); struct script_data dat = ScriptDataVariable{.reg= reg}; get_val(sd, &dat); if (auto *u = dat.get_if()) return u->numi; PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt); return 0; } ZString get_script_var_s(dumb_ptr sd, VarName var, int e) { size_t k = variable_names.intern(var); SIR reg = SIR::from(k, e); struct script_data dat = ScriptDataVariable{.reg= reg}; get_val(sd, &dat); if (auto *u = dat.get_if()) // this is almost certainly a memory leak after CONSTSTR removal return u->str; PRINTF("Warning: you lied about the type and I can't fix it!"_fmt); return ZString(); } } // namespace map } // namespace tmwa