summaryrefslogtreecommitdiff
path: root/src/map/script-parse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/map/script-parse.cpp')
-rw-r--r--src/map/script-parse.cpp840
1 files changed, 840 insertions, 0 deletions
diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp
new file mode 100644
index 0000000..0c93477
--- /dev/null
+++ b/src/map/script-parse.cpp
@@ -0,0 +1,840 @@
+#include "script-parse-internal.hpp"
+// script-parse.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 <b.r.longbons@gmail.com>
+// 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 <http://www.gnu.org/licenses/>.
+
+#include <set>
+
+#include "../generic/db.hpp"
+#include "../generic/intern-pool.hpp"
+
+#include "../strings/rstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/cxxstdio_enums.hpp"
+
+#include "map.t.hpp"
+#include "script-buffer.hpp"
+#include "script-call.hpp"
+#include "script-fun.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+constexpr bool DEBUG_DISP = false;
+
+class ScriptBuffer
+{
+ typedef ZString::iterator ZSit;
+
+ std::vector<ByteCode> script_buf;
+public:
+ // construction methods
+ void add_scriptc(ByteCode a);
+ void add_scriptb(uint8_t a);
+ void add_scripti(uint32_t a);
+ void add_scriptl(str_data_t *a);
+ void set_label(str_data_t *ld, int pos_);
+ ZSit parse_simpleexpr(ZSit p);
+ ZSit parse_subexpr(ZSit p, int limit);
+ ZSit parse_expr(ZSit p);
+ ZSit parse_line(ZSit p, bool *canstep);
+ void parse_script(ZString src, int line, bool implicit_end);
+
+ // consumption methods
+ ByteCode operator[](size_t i) const { return script_buf[i]; }
+ ZString get_str(size_t i) const
+ {
+ return ZString(strings::really_construct_from_a_pointer, reinterpret_cast<const char *>(&script_buf[i]), nullptr);
+ }
+};
+} // namespace tmwa
+
+void std::default_delete<const tmwa::ScriptBuffer>::operator()(const tmwa::ScriptBuffer *sd)
+{
+ really_delete1 sd;
+}
+
+namespace tmwa
+{
+// implemented for script-call.hpp because reasons
+ByteCode ScriptPointer::peek() const { return (*code)[pos]; }
+ByteCode ScriptPointer::pop() { return (*code)[pos++]; }
+ZString ScriptPointer::pops()
+{
+ ZString rv = code->get_str(pos);
+ pos += rv.size();
+ ++pos;
+ return rv;
+}
+
+Map<RString, str_data_t> str_datam;
+static
+str_data_t LABEL_NEXTLINE_;
+
+Map<ScriptLabel, int> scriptlabel_db;
+static
+std::set<ScriptLabel> probable_labels;
+UPMap<RString, const ScriptBuffer> userfunc_db;
+
+static
+struct ScriptConfigParse
+{
+ static const
+ int warn_func_no_comma = 1;
+ static const
+ int warn_cmd_no_comma = 1;
+ static const
+ int warn_func_mismatch_paramnum = 1;
+ static const
+ int warn_cmd_mismatch_paramnum = 1;
+} script_config;
+
+static
+int parse_cmd_if = 0;
+static
+str_data_t *parse_cmdp;
+
+InternPool variable_names;
+
+str_data_t *search_strp(XString p)
+{
+ return str_datam.search(p);
+}
+
+str_data_t *add_strp(XString p)
+{
+ if (str_data_t *rv = search_strp(p))
+ return rv;
+
+ RString p2 = p;
+ str_data_t *datum = str_datam.init(p2);
+ datum->type = StringCode::NOP;
+ datum->strs = p2;
+ datum->backpatch = -1;
+ datum->label_ = -1;
+ return datum;
+}
+
+/*==========================================
+ * スクリプトバッファに1バイト書き込む
+ *------------------------------------------
+ */
+void ScriptBuffer::add_scriptc(ByteCode a)
+{
+ script_buf.push_back(a);
+}
+
+/*==========================================
+ * スクリプトバッファにデータタイプを書き込む
+ *------------------------------------------
+ */
+void ScriptBuffer::add_scriptb(uint8_t a)
+{
+ add_scriptc(static_cast<ByteCode>(a));
+}
+
+/*==========================================
+ * スクリプトバッファに整数を書き込む
+ *------------------------------------------
+ */
+void ScriptBuffer::add_scripti(uint32_t a)
+{
+ while (a >= 0x40)
+ {
+ add_scriptb(a | 0xc0);
+ a = (a - 0x40) >> 6;
+ }
+ add_scriptb(a | 0x80);
+}
+
+/*==========================================
+ * スクリプトバッファにラベル/変数/関数を書き込む
+ *------------------------------------------
+ */
+// 最大16Mまで
+void ScriptBuffer::add_scriptl(str_data_t *ld)
+{
+ int backpatch = ld->backpatch;
+
+ switch (ld->type)
+ {
+ case StringCode::POS:
+ add_scriptc(ByteCode::POS);
+ add_scriptb(static_cast<uint8_t>(ld->label_));
+ add_scriptb(static_cast<uint8_t>(ld->label_ >> 8));
+ add_scriptb(static_cast<uint8_t>(ld->label_ >> 16));
+ break;
+ case StringCode::NOP:
+ // need to set backpatch, because it might become a label later
+ add_scriptc(ByteCode::VARIABLE);
+ ld->backpatch = script_buf.size();
+ add_scriptb(static_cast<uint8_t>(backpatch));
+ add_scriptb(static_cast<uint8_t>(backpatch >> 8));
+ add_scriptb(static_cast<uint8_t>(backpatch >> 16));
+ break;
+ case StringCode::INT:
+ add_scripti(ld->val);
+ break;
+ case StringCode::FUNC:
+ add_scriptc(ByteCode::FUNC_REF);
+ add_scriptb(static_cast<uint8_t>(ld->val));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 8));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 16));
+ break;
+ case StringCode::PARAM:
+ add_scriptc(ByteCode::PARAM);
+ add_scriptb(static_cast<uint8_t>(ld->val));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 8));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 16));
+ break;
+ default:
+ abort();
+ }
+}
+
+/*==========================================
+ * ラベルを解決する
+ *------------------------------------------
+ */
+void ScriptBuffer::set_label(str_data_t *ld, int pos_)
+{
+ int next;
+
+ ld->type = StringCode::POS;
+ ld->label_ = pos_;
+ for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next)
+ {
+ next = 0;
+ // woot! no longer endian-dependent!
+ next |= static_cast<uint8_t>(script_buf[i + 0]) << 0;
+ next |= static_cast<uint8_t>(script_buf[i + 1]) << 8;
+ next |= static_cast<uint8_t>(script_buf[i + 2]) << 16;
+ script_buf[i - 1] = ByteCode::POS;
+ script_buf[i] = static_cast<ByteCode>(pos_);
+ script_buf[i + 1] = static_cast<ByteCode>(pos_ >> 8);
+ script_buf[i + 2] = static_cast<ByteCode>(pos_ >> 16);
+ }
+}
+
+/*==========================================
+ * スペース/コメント読み飛ばし
+ *------------------------------------------
+ */
+static
+ZString::iterator skip_space(ZString::iterator p)
+{
+ while (1)
+ {
+ while (isspace(*p))
+ p++;
+ if (p[0] == '/' && p[1] == '/')
+ {
+ while (*p && *p != '\n')
+ p++;
+ }
+ else if (p[0] == '/' && p[1] == '*')
+ {
+ p++;
+ while (*p && (p[-1] != '*' || p[0] != '/'))
+ p++;
+ if (*p)
+ p++;
+ }
+ else
+ break;
+ }
+ return p;
+}
+
+/*==========================================
+ * 1単語スキップ
+ *------------------------------------------
+ */
+static
+ZString::iterator skip_word(ZString::iterator p)
+{
+ // prefix
+ if (*p == '$')
+ p++; // MAP鯖内共有変数用
+ if (*p == '@')
+ p++; // 一時的変数用(like weiss)
+ if (*p == '#')
+ p++; // account変数用
+ if (*p == '#')
+ p++; // ワールドaccount変数用
+
+ while (isalnum(*p) || *p == '_')
+ p++;
+
+ // postfix
+ if (*p == '$')
+ p++; // 文字列変数
+
+ return p;
+}
+
+// TODO: replace this whole mess with some sort of input stream that works
+// a line at a time.
+static
+ZString startptr;
+static
+int startline;
+
+int script_errors = 0;
+/*==========================================
+ * エラーメッセージ出力
+ *------------------------------------------
+ */
+static
+void disp_error_message(ZString mes, ZString::iterator pos_)
+{
+ script_errors++;
+
+ assert (startptr.begin() <= pos_ && pos_ <= startptr.end());
+
+ int line;
+ ZString::iterator p;
+
+ for (line = startline, p = startptr.begin(); p != startptr.end(); line++)
+ {
+ ZString::iterator linestart = p;
+ ZString::iterator lineend = std::find(p, startptr.end(), '\n');
+ if (pos_ < lineend)
+ {
+ PRINTF("\n%s\nline %d : "_fmt, mes, line);
+ for (int i = 0; linestart + i != lineend; i++)
+ {
+ if (linestart + i != pos_)
+ PRINTF("%c"_fmt, linestart[i]);
+ else
+ PRINTF("\'%c\'"_fmt, linestart[i]);
+ }
+ PRINTF("\a\n"_fmt);
+ return;
+ }
+ p = lineend + 1;
+ }
+}
+
+/*==========================================
+ * 項の解析
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p)
+{
+ p = skip_space(p);
+
+ if (*p == ';' || *p == ',')
+ {
+ disp_error_message("unexpected expr end"_s, p);
+ exit(1);
+ }
+ if (*p == '(')
+ {
+
+ p = parse_subexpr(p + 1, -1);
+ p = skip_space(p);
+ if ((*p++) != ')')
+ {
+ disp_error_message("unmatch ')'"_s, p);
+ exit(1);
+ }
+ }
+ else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1])))
+ {
+ char *np;
+ int i = strtoul(&*p, &np, 0);
+ add_scripti(i);
+ p += np - &*p;
+ }
+ else if (*p == '"')
+ {
+ add_scriptc(ByteCode::STR);
+ p++;
+ while (*p && *p != '"')
+ {
+ if (*p == '\\')
+ p++;
+ else if (*p == '\n')
+ {
+ disp_error_message("unexpected newline @ string"_s, p);
+ exit(1);
+ }
+ add_scriptb(*p++);
+ }
+ if (!*p)
+ {
+ disp_error_message("unexpected eof @ string"_s, p);
+ exit(1);
+ }
+ add_scriptb(0);
+ p++; //'"'
+ }
+ else
+ {
+ // label , register , function etc
+ ZString::iterator p2 = skip_word(p);
+ if (p2 == p)
+ {
+ disp_error_message("unexpected character"_s, p);
+ exit(1);
+ }
+ XString word(&*p, &*p2, nullptr);
+ if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s))
+ probable_labels.insert(stringish<ScriptLabel>(word));
+ if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s))
+ {
+ disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p);
+ }
+ str_data_t *ld = add_strp(word);
+
+ parse_cmdp = ld; // warn_*_mismatch_paramnumのために必要
+ // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s?
+ if (ld == search_strp("if"_s)) // warn_cmd_no_commaのために必要
+ parse_cmd_if++;
+ p = p2;
+
+ if (ld->type != StringCode::FUNC && *p == '[')
+ {
+ // array(name[i] => getelementofarray(name,i) )
+ add_scriptl(search_strp("getelementofarray"_s));
+ add_scriptc(ByteCode::ARG);
+ add_scriptl(ld);
+ p = parse_subexpr(p + 1, -1);
+ p = skip_space(p);
+ if (*p != ']')
+ {
+ disp_error_message("unmatch ']'"_s, p);
+ exit(1);
+ }
+ p++;
+ add_scriptc(ByteCode::FUNC);
+ }
+ else
+ add_scriptl(ld);
+
+ }
+
+ return p;
+}
+
+/*==========================================
+ * 式の解析
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit)
+{
+ ByteCode op;
+ int opl, len;
+
+ p = skip_space(p);
+
+ if (*p == '-')
+ {
+ ZString::iterator tmpp = skip_space(p + 1);
+ if (*tmpp == ';' || *tmpp == ',')
+ {
+ --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p);
+ add_scriptl(&LABEL_NEXTLINE_);
+ p++;
+ return p;
+ }
+ }
+ ZString::iterator tmpp = p;
+ if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!')
+ || (op = ByteCode::NOT, *p == '~'))
+ {
+ p = parse_subexpr(p + 1, 100);
+ add_scriptc(op);
+ }
+ else
+ p = parse_simpleexpr(p);
+ p = skip_space(p);
+ while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') ||
+ (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') ||
+ (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') ||
+ (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') ||
+ (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') ||
+ (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') ||
+ (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') ||
+ (op = ByteCode::AND, opl = 5, len = 1, *p == '&') ||
+ (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') ||
+ (op = ByteCode::OR, opl = 4, len = 1, *p == '|') ||
+ (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') ||
+ (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') ||
+ (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') ||
+ (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') ||
+ (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') ||
+ (op = ByteCode::GT, opl = 2, len = 1, *p == '>') ||
+ (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') ||
+ (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') ||
+ (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit)
+ {
+ p += len;
+ if (op == ByteCode::FUNC)
+ {
+ int i = 0;
+ str_data_t *funcp = parse_cmdp;
+ ZString::iterator plist[128];
+
+ if (funcp->type != StringCode::FUNC)
+ {
+ disp_error_message("expect function"_s, tmpp);
+ exit(0);
+ }
+
+ add_scriptc(ByteCode::ARG);
+ while (*p && *p != ')' && i < 128)
+ {
+ plist[i] = p;
+ p = parse_subexpr(p, -1);
+ p = skip_space(p);
+ if (*p == ',')
+ p++;
+ else if (*p != ')' && script_config.warn_func_no_comma)
+ {
+ disp_error_message("expect ',' or ')' at func params"_s,
+ p);
+ }
+ p = skip_space(p);
+ i++;
+ }
+ plist[i] = p;
+ if (*p != ')')
+ {
+ disp_error_message("func request '(' ')'"_s, p);
+ exit(1);
+ }
+ p++;
+
+ if (funcp->type == StringCode::FUNC
+ && script_config.warn_func_mismatch_paramnum)
+ {
+ ZString arg = builtin_functions[funcp->val].arg;
+ int j = 0;
+ // TODO handle ? and multiple * correctly
+ for (j = 0; arg[j]; j++)
+ if (arg[j] == '*' || arg[j] == '?')
+ break;
+ if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j))
+ {
+ disp_error_message("illegal number of parameters"_s,
+ plist[std::min(i, j)]);
+ }
+ if (!builtin_functions[funcp->val].ret)
+ {
+ disp_error_message("statement in function context"_s, tmpp);
+ }
+ }
+ }
+ else // not op == ByteCode::FUNC
+ {
+ p = parse_subexpr(p, opl);
+ }
+ add_scriptc(op);
+ p = skip_space(p);
+ }
+ return p; /* return first untreated operator */
+}
+
+/*==========================================
+ * 式の評価
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p)
+{
+ switch (*p)
+ {
+ case ')':
+ case ';':
+ case ':':
+ case '[':
+ case ']':
+ case '}':
+ disp_error_message("unexpected char"_s, p);
+ exit(1);
+ }
+ p = parse_subexpr(p, -1);
+ return p;
+}
+
+/*==========================================
+ * 行の解析
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step)
+{
+ int i = 0;
+ ZString::iterator plist[128];
+
+ p = skip_space(p);
+ if (*p == ';')
+ return p;
+
+ parse_cmd_if = 0; // warn_cmd_no_commaのために必要
+
+ // 最初は関数名
+ ZString::iterator p2 = p;
+ p = parse_simpleexpr(p);
+ p = skip_space(p);
+
+ str_data_t *cmd = parse_cmdp;
+ if (cmd->type != StringCode::FUNC)
+ {
+ disp_error_message("expect command"_s, p2);
+ }
+
+ {
+ // TODO should be LString, but no heterogenous lookup yet
+ static
+ std::set<ZString> terminators =
+ {
+ "goto"_s,
+ "return"_s,
+ "close"_s,
+ "menu"_s,
+ "end"_s,
+ "mapexit"_s,
+ "shop"_s,
+ };
+ *can_step = terminators.count(cmd->strs) == 0;
+ }
+
+ add_scriptc(ByteCode::ARG);
+ while (*p && *p != ';' && i < 128)
+ {
+ plist[i] = p;
+
+ p = parse_expr(p);
+ p = skip_space(p);
+ // 引数区切りの,処理
+ if (*p == ',')
+ p++;
+ else if (*p != ';' && script_config.warn_cmd_no_comma
+ && parse_cmd_if * 2 <= i)
+ {
+ disp_error_message("expect ',' or ';' at cmd params"_s, p);
+ }
+ p = skip_space(p);
+ i++;
+ }
+ plist[i] = p;
+ if (*(p++) != ';')
+ {
+ disp_error_message("need ';'"_s, p);
+ exit(1);
+ }
+ add_scriptc(ByteCode::FUNC);
+
+ if (cmd->type == StringCode::FUNC
+ && script_config.warn_cmd_mismatch_paramnum)
+ {
+ ZString arg = builtin_functions[cmd->val].arg;
+ int j = 0;
+ // TODO see above
+ for (j = 0; arg[j]; j++)
+ if (arg[j] == '*' || arg[j] == '?')
+ break;
+ if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j))
+ {
+ disp_error_message("illegal number of parameters"_s,
+ plist[std::min(i, j)]);
+ }
+ if (builtin_functions[cmd->val].ret)
+ {
+ disp_error_message("function in statement context"_s, p2);
+ }
+ }
+
+ return p;
+}
+
+/*==========================================
+ * 組み込み関数の追加
+ *------------------------------------------
+ */
+static
+void add_builtin_functions(void)
+{
+ for (int i = 0; builtin_functions[i].func; i++)
+ {
+ str_data_t *n = add_strp(builtin_functions[i].name);
+ n->type = StringCode::FUNC;
+ n->val = i;
+ }
+}
+
+std::unique_ptr<const ScriptBuffer> parse_script(ZString src, int line, bool implicit_end)
+{
+ auto script_buf = make_unique<ScriptBuffer>();
+ script_buf->parse_script(src, line, implicit_end);
+ return std::move(script_buf);
+}
+
+/*==========================================
+ * スクリプトの解析
+ *------------------------------------------
+ */
+void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end)
+{
+ static int first = 1;
+
+ if (first)
+ {
+ add_builtin_functions();
+ }
+ first = 0;
+ LABEL_NEXTLINE_.type = StringCode::NOP;
+ LABEL_NEXTLINE_.backpatch = -1;
+ LABEL_NEXTLINE_.label_ = -1;
+ for (auto& pair : str_datam)
+ {
+ str_data_t& dit = pair.second;
+ if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE)
+ {
+ dit.type = StringCode::NOP;
+ dit.backpatch = -1;
+ dit.label_ = -1;
+ }
+ }
+
+ // 外部用label dbの初期化
+ scriptlabel_db.clear();
+
+ // for error message
+ startptr = src;
+ startline = line;
+
+ bool can_step = true;
+
+ ZString::iterator p = src.begin();
+ p = skip_space(p);
+ if (*p != '{')
+ {
+ disp_error_message("not found '{'"_s, p);
+ abort();
+ }
+ for (p++; *p && *p != '}';)
+ {
+ p = skip_space(p);
+ if (*skip_space(skip_word(p)) == ':')
+ {
+ if (can_step)
+ {
+ --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p);
+ }
+ can_step = true;
+
+ ZString::iterator tmpp = skip_word(p);
+ XString str(&*p, &*tmpp, nullptr);
+ str_data_t *ld = add_strp(str);
+ bool e1 = ld->type != StringCode::NOP;
+ bool e2 = ld->type == StringCode::POS;
+ bool e3 = ld->label_ != -1;
+ assert (e1 == e2 && e2 == e3);
+ if (e3)
+ {
+ disp_error_message("dup label "_s, p);
+ exit(1);
+ }
+ set_label(ld, script_buf.size());
+ scriptlabel_db.insert(stringish<ScriptLabel>(str), script_buf.size());
+ p = tmpp + 1;
+ continue;
+ }
+
+ if (!can_step)
+ {
+ --script_errors; disp_error_message("deprecated: unreachable statement"_s, p);
+ }
+ // 他は全部一緒くた
+ p = parse_line(p, &can_step);
+ p = skip_space(p);
+ add_scriptc(ByteCode::EOL);
+
+ set_label(&LABEL_NEXTLINE_, script_buf.size());
+ LABEL_NEXTLINE_.type = StringCode::NOP;
+ LABEL_NEXTLINE_.backpatch = -1;
+ LABEL_NEXTLINE_.label_ = -1;
+ }
+
+ if (can_step && !implicit_end)
+ {
+ --script_errors; disp_error_message("deprecated: implicit end"_s, p);
+ }
+ add_scriptc(ByteCode::NOP);
+
+ // resolve the unknown labels
+ for (auto& pair : str_datam)
+ {
+ str_data_t& sit = pair.second;
+ if (sit.type == StringCode::NOP)
+ {
+ sit.type = StringCode::VARIABLE;
+ sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts.
+ size_t pool_index = variable_names.intern(sit.strs);
+ for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next)
+ {
+ next = 0;
+ next |= static_cast<uint8_t>(script_buf[j + 0]) << 0;
+ next |= static_cast<uint8_t>(script_buf[j + 1]) << 8;
+ next |= static_cast<uint8_t>(script_buf[j + 2]) << 16;
+ script_buf[j] = static_cast<ByteCode>(pool_index);
+ script_buf[j + 1] = static_cast<ByteCode>(pool_index >> 8);
+ script_buf[j + 2] = static_cast<ByteCode>(pool_index >> 16);
+ }
+ }
+ }
+
+ for (const auto& pair : scriptlabel_db)
+ {
+ ScriptLabel key = pair.first;
+ if (key.startswith("On"_s))
+ continue;
+ if (!(key.startswith("L_"_s) || key.startswith("S_"_s)))
+ PRINTF("Warning: ugly label: %s\n"_fmt, key);
+ else if (!probable_labels.count(key))
+ PRINTF("Warning: unused label: %s\n"_fmt, key);
+ }
+ for (ScriptLabel used : probable_labels)
+ {
+ if (!scriptlabel_db.search(used))
+ PRINTF("Warning: no such label: %s\n"_fmt, used);
+ }
+ probable_labels.clear();
+
+ if (!DEBUG_DISP)
+ return;
+ for (size_t i = 0; i < script_buf.size(); i++)
+ {
+ if ((i & 15) == 0)
+ PRINTF("%04zx : "_fmt, i);
+ PRINTF("%02x "_fmt, script_buf[i]);
+ if ((i & 15) == 15)
+ PRINTF("\n"_fmt);
+ }
+ PRINTF("\n"_fmt);
+}
+} // namespace tmwa