summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/generic/fwd.hpp2
-rw-r--r--src/generic/random.hpp4
-rw-r--r--src/generic/random2.hpp4
-rw-r--r--src/login/login.hpp4
-rw-r--r--src/map/battle.hpp4
-rw-r--r--src/map/clif.hpp4
-rw-r--r--src/map/fwd.hpp3
-rw-r--r--src/map/itemdb.cpp2
-rw-r--r--src/map/itemdb.hpp2
-rw-r--r--src/map/magic-expr.cpp1
-rw-r--r--src/map/magic-interpreter.hpp6
-rw-r--r--src/map/magic-stmt.cpp1
-rw-r--r--src/map/magic-v2.cpp1
-rw-r--r--src/map/map.cpp2
-rw-r--r--src/map/map.hpp7
-rw-r--r--src/map/mob.hpp4
-rw-r--r--src/map/npc.cpp3
-rw-r--r--src/map/npc.hpp1
-rw-r--r--src/map/pc.cpp2
-rw-r--r--src/map/pc.hpp4
-rw-r--r--src/map/script-buffer.cpp30
-rw-r--r--src/map/script-buffer.hpp42
-rw-r--r--src/map/script-call-internal.hpp97
-rw-r--r--src/map/script-call-internal.tcc76
-rw-r--r--src/map/script-call.cpp1042
-rw-r--r--src/map/script-call.hpp67
-rw-r--r--src/map/script-call.t.hpp45
-rw-r--r--src/map/script-fun.cpp (renamed from src/map/script.cpp)2200
-rw-r--r--src/map/script-fun.hpp38
-rw-r--r--src/map/script-parse-internal.hpp69
-rw-r--r--src/map/script-parse.cpp840
-rw-r--r--src/map/script-parse.hpp42
-rw-r--r--src/map/script-persist.cpp30
-rw-r--r--src/map/script-persist.hpp (renamed from src/map/script.hpp)94
-rw-r--r--src/map/script-persist.py (renamed from src/map/script.py)0
-rw-r--r--src/map/script-startup-internal.hpp44
-rw-r--r--src/map/script-startup.cpp265
-rw-r--r--src/map/script-startup.hpp35
-rw-r--r--src/map/skill.hpp3
39 files changed, 2809 insertions, 2311 deletions
diff --git a/src/generic/fwd.hpp b/src/generic/fwd.hpp
index cec6bd4..9c389b1 100644
--- a/src/generic/fwd.hpp
+++ b/src/generic/fwd.hpp
@@ -33,4 +33,6 @@ template<class K, class V>
class DMap;
template<class K, class V>
class UPMap;
+
+class InternPool;
} // namespace tmwa
diff --git a/src/generic/random.hpp b/src/generic/random.hpp
index 5d67236..897ad43 100644
--- a/src/generic/random.hpp
+++ b/src/generic/random.hpp
@@ -18,10 +18,10 @@
// 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 "fwd.hpp"
-
#include "random.t.hpp"
+#include "fwd.hpp"
+
#include <random>
diff --git a/src/generic/random2.hpp b/src/generic/random2.hpp
index 23d165c..3d481f4 100644
--- a/src/generic/random2.hpp
+++ b/src/generic/random2.hpp
@@ -18,10 +18,10 @@
// 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 "fwd.hpp"
-
#include "random.hpp"
+#include "fwd.hpp"
+
#include <algorithm>
#include "../compat/iter.hpp"
diff --git a/src/login/login.hpp b/src/login/login.hpp
index 92f3c76..5900440 100644
--- a/src/login/login.hpp
+++ b/src/login/login.hpp
@@ -18,10 +18,10 @@
// 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 "fwd.hpp"
-
#include "login.t.hpp"
+#include "fwd.hpp"
+
namespace tmwa
{
diff --git a/src/map/battle.hpp b/src/map/battle.hpp
index 83d94fc..5f47b70 100644
--- a/src/map/battle.hpp
+++ b/src/map/battle.hpp
@@ -20,10 +20,10 @@
// 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 "fwd.hpp"
-
#include "battle.t.hpp"
+#include "fwd.hpp"
+
#include "../strings/fwd.hpp"
#include "../generic/fwd.hpp"
diff --git a/src/map/clif.hpp b/src/map/clif.hpp
index ae23b7b..da8aa20 100644
--- a/src/map/clif.hpp
+++ b/src/map/clif.hpp
@@ -20,10 +20,10 @@
// 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 "fwd.hpp"
-
#include "clif.t.hpp"
+#include "fwd.hpp"
+
#include <functional>
#include "../strings/fwd.hpp"
diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp
index b73d36a..da5763d 100644
--- a/src/map/fwd.hpp
+++ b/src/map/fwd.hpp
@@ -43,7 +43,10 @@ struct NpcEvent;
struct item_data;
enum class SP : uint16_t;
+
+struct ScriptBuffer;
struct ScriptLabel;
+struct ScriptState;
namespace magic
{
diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp
index 50cc5c4..b0c8c02 100644
--- a/src/map/itemdb.cpp
+++ b/src/map/itemdb.cpp
@@ -35,6 +35,8 @@
#include "../mmo/extract.hpp"
#include "../mmo/extract_enums.hpp"
+#include "script-parse.hpp"
+
#include "../poison.hpp"
diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp
index 25b4dad..1fdca2e 100644
--- a/src/map/itemdb.hpp
+++ b/src/map/itemdb.hpp
@@ -26,7 +26,7 @@
#include "../mmo/mmo.hpp"
#include "map.t.hpp"
-#include "script.hpp"
+#include "script-buffer.hpp"
namespace tmwa
diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp
index 4ea83b6..a1196fa 100644
--- a/src/map/magic-expr.cpp
+++ b/src/map/magic-expr.cpp
@@ -42,6 +42,7 @@
#include "magic-interpreter-base.hpp"
#include "npc.hpp"
#include "pc.hpp"
+#include "script-call.hpp"
#include "../poison.hpp"
diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp
index 3bb600c..7cdf7bf 100644
--- a/src/map/magic-interpreter.hpp
+++ b/src/map/magic-interpreter.hpp
@@ -19,10 +19,10 @@
// 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 "fwd.hpp"
-
#include "magic-interpreter.t.hpp"
+#include "fwd.hpp"
+
#include <cassert>
#include <memory>
@@ -40,7 +40,7 @@
#include "../mmo/utils.hpp"
#include "map.hpp"
-#include "script.hpp"
+#include "script-buffer.hpp"
#include "skill.t.hpp"
diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp
index 1786839..f33f663 100644
--- a/src/map/magic-stmt.cpp
+++ b/src/map/magic-stmt.cpp
@@ -43,6 +43,7 @@
#include "mob.hpp"
#include "npc.hpp"
#include "pc.hpp"
+#include "script-call.hpp"
#include "skill.hpp"
#include "../poison.hpp"
diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp
index b299279..8a84a2d 100644
--- a/src/map/magic-v2.cpp
+++ b/src/map/magic-v2.cpp
@@ -41,6 +41,7 @@
#include "magic-interpreter.hpp"
#include "magic-interpreter-base.hpp"
#include "magic-stmt.hpp"
+#include "script-parse.hpp"
#include "../poison.hpp"
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 4a25029..5e9db39 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -72,7 +72,7 @@
#include "npc.hpp"
#include "party.hpp"
#include "pc.hpp"
-#include "script.hpp"
+#include "script-startup.hpp"
#include "skill.hpp"
#include "storage.hpp"
#include "trade.hpp"
diff --git a/src/map/map.hpp b/src/map/map.hpp
index a87c0fe..aeb8821 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -20,10 +20,10 @@
// 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 "fwd.hpp"
-
#include "map.t.hpp"
+#include "fwd.hpp"
+
#include <chrono>
#include <functional>
#include <list>
@@ -48,7 +48,8 @@
#include "clif.t.hpp"
#include "mapflag.hpp"
#include "mob.t.hpp"
-#include "script.hpp" // change to script.t.hpp
+#include "script-buffer.hpp"
+#include "script-persist.hpp"
#include "skill.t.hpp"
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index d0cc07a..1f2aead 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -20,10 +20,10 @@
// 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 "fwd.hpp"
-
#include "mob.t.hpp"
+#include "fwd.hpp"
+
#include "../generic/fwd.hpp"
#include "../generic/enum.hpp"
#include "../generic/random.t.hpp"
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 95a157b..22aac75 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -54,7 +54,8 @@
#include "map.hpp"
#include "mob.hpp"
#include "pc.hpp"
-#include "script.hpp"
+#include "script-call.hpp"
+#include "script-parse.hpp"
#include "skill.hpp"
#include "../poison.hpp"
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index 3870a39..30b0178 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -35,6 +35,7 @@
#include "../proto2/fwd.hpp"
#include "map.hpp"
+#include "script-call.t.hpp"
namespace tmwa
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 7f4c367..ac78672 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -57,7 +57,7 @@
#include "npc.hpp"
#include "party.hpp"
#include "path.hpp"
-#include "script.hpp"
+#include "script-call.hpp"
#include "skill.hpp"
#include "storage.hpp"
#include "trade.hpp"
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index 8a06076..605915e 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -20,10 +20,10 @@
// 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 "fwd.hpp"
-
#include "pc.t.hpp"
+#include "fwd.hpp"
+
#include "../strings/fwd.hpp"
#include "../generic/dumb_ptr.hpp"
diff --git a/src/map/script-buffer.cpp b/src/map/script-buffer.cpp
new file mode 100644
index 0000000..f3a639a
--- /dev/null
+++ b/src/map/script-buffer.cpp
@@ -0,0 +1,30 @@
+#include "script-buffer.hpp"
+// script-buffer.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 "../poison.hpp"
+
+
+namespace tmwa
+{
+} // namespace tmwa
diff --git a/src/map/script-buffer.hpp b/src/map/script-buffer.hpp
new file mode 100644
index 0000000..def23e2
--- /dev/null
+++ b/src/map/script-buffer.hpp
@@ -0,0 +1,42 @@
+#pragma once
+// script-buffer.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "fwd.hpp"
+
+#include <memory>
+
+
+namespace tmwa
+{
+class ScriptBuffer;
+} // namespace tmwa
+
+namespace std
+{
+template<>
+struct default_delete<const tmwa::ScriptBuffer>
+{
+ default_delete() {}
+ default_delete(default_delete<tmwa::ScriptBuffer>) {}
+ void operator()(const tmwa::ScriptBuffer *sd);
+};
+} // namespace std
diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp
new file mode 100644
index 0000000..72e1e46
--- /dev/null
+++ b/src/map/script-call-internal.hpp
@@ -0,0 +1,97 @@
+#pragma once
+// script-call-internal.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "script-call.hpp"
+#include "fwd.hpp"
+
+#include "../mmo/ids.hpp"
+
+#include "script-persist.hpp"
+
+
+namespace tmwa
+{
+enum class VariableCode : uint8_t
+{
+ PARAM,
+ VARIABLE,
+};
+
+struct script_stack
+{
+ std::vector<struct script_data> stack_datav;
+};
+
+enum class ScriptEndState;
+// future improvements coming!
+class ScriptState
+{
+public:
+ struct script_stack *stack;
+ int start, end;
+ ScriptEndState state;
+ BlockId rid, oid;
+ ScriptPointer scriptp, new_scriptp;
+ int defsp, new_defsp;
+};
+
+void run_func(ScriptState *st);
+
+enum class ScriptEndState
+{
+ ZERO,
+ STOP,
+ END,
+ RERUNLINE,
+ GOTO,
+ RETFUNC,
+};
+
+dumb_ptr<map_session_data> script_rid2sd(ScriptState *st);
+void get_val(dumb_ptr<map_session_data> sd, struct script_data *data);
+__attribute__((deprecated))
+void get_val(ScriptState *st, struct script_data *data);
+struct script_data get_val2(ScriptState *st, SIR reg);
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd);
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id);
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd);
+__attribute__((warn_unused_result))
+RString conv_str(ScriptState *st, struct script_data *data);
+__attribute__((warn_unused_result))
+int conv_num(ScriptState *st, struct script_data *data);
+__attribute__((warn_unused_result))
+const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data);
+
+template<class T>
+void push_int(struct script_stack *stack, int val);
+template<class T>
+void push_reg(struct script_stack *stack, SIR reg);
+template<class T>
+void push_script(struct script_stack *stack, const ScriptBuffer *code);
+template<class T>
+void push_str(struct script_stack *stack, RString str);
+
+void push_copy(struct script_stack *stack, int pos_);
+void pop_stack(struct script_stack *stack, int start, int end);
+} // namespace tmwa
+
+#include "script-call-internal.tcc"
diff --git a/src/map/script-call-internal.tcc b/src/map/script-call-internal.tcc
new file mode 100644
index 0000000..cc5c2a4
--- /dev/null
+++ b/src/map/script-call-internal.tcc
@@ -0,0 +1,76 @@
+// script-call-internal.tcc - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "script-persist.hpp"
+
+
+namespace tmwa
+{
+template<class D>
+bool first_type_is_any()
+{
+ return false;
+}
+
+template<class D, class F, class... R>
+constexpr
+bool first_type_is_any()
+{
+ return std::is_same<D, F>::value || first_type_is_any<D, R...>();
+}
+
+
+template<class T>
+void push_int(struct script_stack *stack, int val)
+{
+ static_assert(first_type_is_any<T, ScriptDataPos, ScriptDataInt, ScriptDataArg, ScriptDataFuncRef>(), "not int type");
+
+ script_data nsd = T{.numi= val};
+ stack->stack_datav.push_back(nsd);
+}
+
+template<class T>
+void push_reg(struct script_stack *stack, SIR reg)
+{
+ static_assert(first_type_is_any<T, ScriptDataParam, ScriptDataVariable>(), "not reg type");
+
+ script_data nsd = T{.reg= reg};
+ stack->stack_datav.push_back(nsd);
+}
+
+template<class T>
+void push_script(struct script_stack *stack, const ScriptBuffer *code)
+{
+ static_assert(first_type_is_any<T, ScriptDataRetInfo>(), "not scriptbuf type");
+
+ script_data nsd = T{.script= code};
+ stack->stack_datav.push_back(nsd);
+}
+
+template<class T>
+void push_str(struct script_stack *stack, RString str)
+{
+ static_assert(first_type_is_any<T, ScriptDataStr>(), "not str type");
+
+ script_data nsd = T{.str= str};
+ stack->stack_datav.push_back(nsd);
+}
+} // namespace tmwa
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
new file mode 100644
index 0000000..f889e4b
--- /dev/null
+++ b/src/map/script-call.cpp
@@ -0,0 +1,1042 @@
+#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 <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 "../generic/intern-pool.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/cxxstdio_enums.hpp"
+
+#include "battle.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
+{
+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<map_session_data> script_rid2sd(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+ if (!sd)
+ {
+ PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt);
+ }
+ return sd;
+}
+
+/*==========================================
+ * 変数の読み取り
+ *------------------------------------------
+ */
+void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
+{
+ MATCH (*data)
+ {
+ CASE (const ScriptDataParam&, u)
+ {
+ if (sd == nullptr)
+ PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
+ int numi = 0;
+ if (sd)
+ numi = pc_readparam(sd, u.reg.sp());
+ *data = ScriptDataInt{numi};
+ }
+ CASE (const ScriptDataVariable&, u)
+ {
+ ZString name_ = variable_names.outtern(u.reg.base());
+ VarName name = stringish<VarName>(name_);
+ char prefix = name.front();
+ char postfix = name.back();
+
+ if (prefix != '$')
+ {
+ if (sd == nullptr)
+ PRINTF("get_val error name?:%s\n"_fmt, name);
+ }
+ if (postfix == '$')
+ {
+ RString str;
+ if (prefix == '@')
+ {
+ if (sd)
+ str = pc_readregstr(sd, u.reg);
+ }
+ else if (prefix == '$')
+ {
+ RString *s = mapregstr_db.search(u.reg);
+ if (s)
+ str = *s;
+ }
+ else
+ {
+ PRINTF("script: get_val: illegal scope string variable.\n"_fmt);
+ str = "!!ERROR!!"_s;
+ }
+ *data = ScriptDataStr{str};
+ }
+ else
+ {
+ int numi = 0;
+ if (prefix == '@')
+ {
+ if (sd)
+ numi = pc_readreg(sd, u.reg);
+ }
+ else if (prefix == '$')
+ {
+ numi = mapreg_db.get(u.reg);
+ }
+ else if (prefix == '#')
+ {
+ if (name[1] == '#')
+ {
+ if (sd)
+ numi = pc_readaccountreg2(sd, name);
+ }
+ else
+ {
+ if (sd)
+ numi = pc_readaccountreg(sd, name);
+ }
+ }
+ else
+ {
+ if (sd)
+ numi = pc_readglobalreg(sd, name);
+ }
+ *data = ScriptDataInt{numi};
+ }
+ }
+ }
+}
+
+void get_val(ScriptState *st, struct script_data *data)
+{
+ dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr;
+ get_val(sd, data);
+}
+
+/*==========================================
+ * 変数の読み取り2
+ *------------------------------------------
+ */
+struct script_data get_val2(ScriptState *st, SIR reg)
+{
+ struct script_data dat = ScriptDataVariable{reg};
+ get_val(st, &dat);
+ return dat;
+}
+
+/*==========================================
+ * 変数設定用
+ *------------------------------------------
+ */
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd)
+{
+ if (type == VariableCode::PARAM)
+ {
+ int val = vd.get_if<ScriptDataInt>()->numi;
+ pc_setparam(sd, reg.sp(), val);
+ return;
+ }
+ assert (type == VariableCode::VARIABLE);
+
+ ZString name_ = variable_names.outtern(reg.base());
+ VarName name = stringish<VarName>(name_);
+ char prefix = name.front();
+ char postfix = name.back();
+
+ if (postfix == '$')
+ {
+ RString str = vd.get_if<ScriptDataStr>()->str;
+ if (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<ScriptDataInt>()->numi;
+ if (prefix == '@')
+ {
+ pc_setreg(sd, reg, val);
+ }
+ else if (prefix == '$')
+ {
+ mapreg_setreg(reg, val);
+ }
+ else if (prefix == '#')
+ {
+ if (name[1] == '#')
+ pc_setaccountreg2(sd, name, val);
+ else
+ pc_setaccountreg(sd, name, val);
+ }
+ else
+ {
+ pc_setglobalreg(sd, name, val);
+ }
+ }
+}
+
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id)
+{
+ struct script_data vd = ScriptDataInt{id};
+ set_reg(sd, type, reg, vd);
+}
+
+void set_reg(dumb_ptr<map_session_data> 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<ScriptDataRetInfo>());
+ if (auto *u = data->get_if<ScriptDataInt>())
+ {
+ AString buf = STRPRINTF("%d"_fmt, u->numi);
+ *data = ScriptDataStr{buf};
+ }
+ return data->get_if<ScriptDataStr>()->str;
+}
+
+/*==========================================
+ * 数値へ変換
+ *------------------------------------------
+ */
+int conv_num(ScriptState *st, struct script_data *data)
+{
+ int rv = 0;
+ get_val(st, data);
+ assert (!data->is<ScriptDataRetInfo>());
+ MATCH (*data)
+ {
+ default:
+ abort();
+ CASE (const ScriptDataStr&, u)
+ {
+ RString p = u.str;
+ rv = atoi(p.c_str());
+ }
+ CASE (const ScriptDataInt&, u)
+ {
+ return u.numi;
+ }
+ CASE (const ScriptDataPos&, u)
+ {
+ return u.numi;
+ }
+ }
+ *data = ScriptDataInt{rv};
+ return rv;
+}
+
+const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data)
+{
+ get_val(st, data);
+ return data->get_if<ScriptDataRetInfo>()->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 (static_cast<uint8_t>(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<uint8_t>(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<ScriptDataInt>())
+ rv = u->numi;
+ st->stack->stack_datav.pop_back();
+ return rv;
+}
+
+static
+bool isstr(struct script_data& c)
+{
+ return c.is<ScriptDataStr>();
+}
+
+/*==========================================
+ * 加算演算子
+ *------------------------------------------
+ */
+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<ScriptDataInt>()->numi += back.get_if<ScriptDataInt>()->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<ScriptDataInt>(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<ScriptDataInt>(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<ScriptDataStr>()->str, d2.get_if<ScriptDataStr>()->str);
+ }
+ else if (!(isstr(d1) || isstr(d2)))
+ {
+ // ii => op_2num
+ op_2num(st, op, d1.get_if<ScriptDataInt>()->numi, d2.get_if<ScriptDataInt>()->numi);
+ }
+ else
+ {
+ // si,is => error
+ PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt);
+ push_int<ScriptDataInt>(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<ScriptDataInt>(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<ScriptDataArg>())
+ {
+ start_sp--;
+ if (start_sp == 0)
+ {
+ if (battle_config.error_log)
+ PRINTF("function not found\n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+ }
+ // the func is before the arg
+ start_sp--;
+ st->start = start_sp;
+ st->end = end_sp;
+
+ if (!st->stack->stack_datav[st->start].is<ScriptDataFuncRef>())
+ {
+ PRINTF("run_func: not function and command! \n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+ size_t func = st->stack->stack_datav[st->start].get_if<ScriptDataFuncRef>()->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 (d)
+ {
+ CASE (const ScriptDataInt&, u)
+ {
+ PRINTF(" int(%d)"_fmt, u.numi);
+ }
+ CASE (const ScriptDataRetInfo&, u)
+ {
+ PRINTF(" retinfo(%p)"_fmt, static_cast<const void *>(u.script));
+ }
+ CASE (const ScriptDataParam&, u)
+ {
+ PRINTF(" param(%d)"_fmt, u.reg.sp());
+ }
+ CASE (const ScriptDataVariable&, u)
+ {
+ PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index());
+ }
+ CASE (const ScriptDataArg&, u)
+ {
+ (void)u;
+ PRINTF(" arg"_fmt);
+ }
+ CASE (const ScriptDataPos&, u)
+ {
+ (void)u;
+ PRINTF(" pos(%d)"_fmt, u.numi);
+ }
+ CASE (const ScriptDataStr&, u)
+ {
+ (void)u;
+ PRINTF(" str(%s)"_fmt, u.str);
+ }
+ CASE (const ScriptDataFuncRef&, u)
+ {
+ (void)u;
+ PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name);
+ }
+ }
+ }
+ PRINTF("\n"_fmt);
+ }
+ builtin_functions[func].func(st);
+
+ 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<ScriptDataRetInfo>())
+ {
+ PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+ assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet
+ st->scriptp.code = 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;
+ }
+}
+
+// pretend it's external so this can be called in the debugger
+void dump_script(const ScriptBuffer *script);
+void dump_script(const ScriptBuffer *script)
+{
+ ScriptPointer scriptp(script, 0);
+ while (scriptp.pos < reinterpret_cast<const std::vector<ByteCode> *>(script)->size())
+ {
+ PRINTF("%6zu: "_fmt, scriptp.pos);
+ switch (ByteCode c = get_com(&scriptp))
+ {
+ case ByteCode::EOL:
+ PRINTF("EOL\n"_fmt); // extra newline between functions
+ break;
+ case ByteCode::INT:
+ // synthesized!
+ PRINTF("INT %d"_fmt, get_num(&scriptp));
+ break;
+
+ case ByteCode::POS:
+ case ByteCode::VARIABLE:
+ case ByteCode::FUNC_REF:
+ case ByteCode::PARAM:
+ {
+ int arg = 0;
+ arg |= static_cast<uint8_t>(scriptp.pop()) << 0;
+ arg |= static_cast<uint8_t>(scriptp.pop()) << 8;
+ arg |= static_cast<uint8_t>(scriptp.pop()) << 16;
+ switch(c)
+ {
+ case ByteCode::POS:
+ PRINTF("POS %d"_fmt, arg);
+ break;
+ case ByteCode::VARIABLE:
+ PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg));
+ break;
+ case ByteCode::FUNC_REF:
+ PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name);
+ break;
+ case ByteCode::PARAM:
+ PRINTF("PARAM SP::#%d (sorry)"_fmt, arg);
+ break;
+ }
+ }
+ break;
+ case ByteCode::ARG:
+ PRINTF("ARG"_fmt);
+ break;
+ case ByteCode::STR:
+ PRINTF("STR \"%s\""_fmt, scriptp.pops());
+ break;
+ case ByteCode::FUNC:
+ PRINTF("FUNC"_fmt);
+ break;
+
+ case ByteCode::ADD:
+ PRINTF("ADD"_fmt);
+ break;
+ case ByteCode::SUB:
+ PRINTF("SUB"_fmt);
+ break;
+ case ByteCode::MUL:
+ PRINTF("MUL"_fmt);
+ break;
+ case ByteCode::DIV:
+ PRINTF("DIV"_fmt);
+ break;
+ case ByteCode::MOD:
+ PRINTF("MOD"_fmt);
+ break;
+ case ByteCode::EQ:
+ PRINTF("EQ"_fmt);
+ break;
+ case ByteCode::NE:
+ PRINTF("NE"_fmt);
+ break;
+ case ByteCode::GT:
+ PRINTF("GT"_fmt);
+ break;
+ case ByteCode::GE:
+ PRINTF("GE"_fmt);
+ break;
+ case ByteCode::LT:
+ PRINTF("LT"_fmt);
+ break;
+ case ByteCode::LE:
+ PRINTF("LE"_fmt);
+ break;
+ case ByteCode::AND:
+ PRINTF("AND"_fmt);
+ break;
+ case ByteCode::OR:
+ PRINTF("OR"_fmt);
+ break;
+ case ByteCode::XOR:
+ PRINTF("XOR"_fmt);
+ break;
+ case ByteCode::LAND:
+ PRINTF("LAND"_fmt);
+ break;
+ case ByteCode::LOR:
+ PRINTF("LOR"_fmt);
+ break;
+ case ByteCode::R_SHIFT:
+ PRINTF("R_SHIFT"_fmt);
+ break;
+ case ByteCode::L_SHIFT:
+ PRINTF("L_SHIFT"_fmt);
+ break;
+ case ByteCode::NEG:
+ PRINTF("NEG"_fmt);
+ break;
+ case ByteCode::NOT:
+ PRINTF("NOT"_fmt);
+ break;
+ case ByteCode::LNOT:
+ PRINTF("LNOT"_fmt);
+ break;
+
+ case ByteCode::NOP:
+ PRINTF("NOP"_fmt);
+ break;
+
+ default:
+ PRINTF("??? %d"_fmt, c);
+ break;
+ }
+ PRINTF("\n"_fmt);
+ }
+}
+
+/*==========================================
+ * スクリプトの実行メイン部分
+ *------------------------------------------
+ */
+static
+void run_script_main(ScriptState *st, const ScriptBuffer *rootscript)
+{
+ int cmdcount = script_config.check_cmdcount;
+ int gotocount = script_config.check_gotocount;
+ struct script_stack *stack = st->stack;
+
+ 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<ScriptDataInt>(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<uint8_t>(st->scriptp.pop()) << 0;
+ arg |= static_cast<uint8_t>(st->scriptp.pop()) << 8;
+ arg |= static_cast<uint8_t>(st->scriptp.pop()) << 16;
+ switch(c)
+ {
+ case ByteCode::POS:
+ push_int<ScriptDataPos>(stack, arg);
+ break;
+ case ByteCode::VARIABLE:
+ push_reg<ScriptDataVariable>(stack, SIR::from(arg));
+ break;
+ case ByteCode::FUNC_REF:
+ push_int<ScriptDataFuncRef>(stack, arg);
+ break;
+ case ByteCode::PARAM:
+ SP arg_sp = static_cast<SP>(arg);
+ push_reg<ScriptDataParam>(stack, SIR::from(arg_sp));
+ break;
+ }
+ }
+ break;
+ case ByteCode::ARG:
+ push_int<ScriptDataArg>(stack, 0);
+ break;
+ case ByteCode::STR:
+ push_str<ScriptDataStr>(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 (gotocount > 0 && (--gotocount) <= 0)
+ {
+ PRINTF("run_script: infinity loop !\n"_fmt);
+ 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);
+ st->state = ScriptEndState::END;
+ break;
+ }
+ if (cmdcount > 0 && (--cmdcount) <= 0)
+ {
+ PRINTF("run_script: infinity loop !\n"_fmt);
+ st->state = ScriptEndState::END;
+ }
+ }
+ switch (st->state)
+ {
+ case ScriptEndState::STOP:
+ break;
+ case ScriptEndState::END:
+ {
+ dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+ st->scriptp.code = nullptr;
+ st->scriptp.pos = -1;
+ if (sd && sd->npc_id == st->oid)
+ npc_event_dequeue(sd);
+ }
+ break;
+ case ScriptEndState::RERUNLINE:
+ st->scriptp.pos = rerun_pos;
+ break;
+ }
+
+ if (st->state != ScriptEndState::END)
+ {
+ // 再開するためにスタック情報を保存
+ dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+ if (sd)
+ {
+ sd->npc_stackbuf = stack->stack_datav;
+ sd->npc_script = st->scriptp.code;
+ // sd->npc_pos is set later ... ???
+ sd->npc_scriptroot = 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<argrec_t> args)
+{
+ struct script_stack stack;
+ ScriptState st;
+ dumb_ptr<map_session_data> sd = map_id2sd(rid);
+ const ScriptBuffer *rootscript = sp.code;
+ int i;
+ if (sp.code == nullptr || sp.pos >> 24)
+ return -1;
+
+ if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == 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;
+ 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<map_session_data> 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<map_session_data> 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<map_session_data> 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<ScriptDataInt>())
+ 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<map_session_data> 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<ScriptDataStr>())
+ // 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 tmwa
diff --git a/src/map/script-call.hpp b/src/map/script-call.hpp
new file mode 100644
index 0000000..da9d03f
--- /dev/null
+++ b/src/map/script-call.hpp
@@ -0,0 +1,67 @@
+#pragma once
+// script-call.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "script-call.t.hpp"
+
+#include "fwd.hpp"
+
+#include "../range/fwd.hpp"
+
+#include "../generic/fwd.hpp"
+
+#include "../mmo/fwd.hpp"
+
+
+namespace tmwa
+{
+enum class ByteCode : uint8_t;
+
+// implemented in script-parse.cpp because reasons
+struct ScriptPointer
+{
+ const ScriptBuffer *code;
+ size_t pos;
+
+ ScriptPointer()
+ : code()
+ , pos()
+ {}
+
+ ScriptPointer(const ScriptBuffer *c, size_t p)
+ : code(c)
+ , pos(p)
+ {}
+
+ ByteCode peek() const;
+ ByteCode pop();
+ ZString pops();
+};
+
+int run_script_l(ScriptPointer, BlockId, BlockId, Slice<argrec_t> args);
+int run_script(ScriptPointer, BlockId, BlockId);
+
+void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val);
+void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val);
+
+int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e);
+ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e);
+} // namespace tmwa
diff --git a/src/map/script-call.t.hpp b/src/map/script-call.t.hpp
new file mode 100644
index 0000000..04a6a80
--- /dev/null
+++ b/src/map/script-call.t.hpp
@@ -0,0 +1,45 @@
+#pragma once
+// script-call.t.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "fwd.hpp"
+
+#include "../strings/zstring.hpp"
+
+
+namespace tmwa
+{
+struct argrec_t
+{
+ ZString name;
+ union _aru
+ {
+ int i;
+ ZString s;
+
+ _aru(int n) : i(n) {}
+ _aru(ZString z) : s(z) {}
+ } v;
+
+ argrec_t(ZString n, int i) : name(n), v(i) {}
+ argrec_t(ZString n, ZString z) : name(n), v(z) {}
+};
+} // namespace tmwa
diff --git a/src/map/script.cpp b/src/map/script-fun.cpp
index 61233b8..5caf060 100644
--- a/src/map/script.cpp
+++ b/src/map/script-fun.cpp
@@ -1,5 +1,5 @@
-#include "script.hpp"
-// script.cpp - EAthena script frontend, engine, and library.
+#include "script-fun.hpp"
+// script-fun.cpp - EAthena script frontend, engine, and library.
//
// Copyright © ????-2004 Athena Dev Teams
// Copyright © 2004-2011 The Mana World Development Team
@@ -22,40 +22,18 @@
// 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 <cassert>
-#include <cmath>
-#include <cstdlib>
-#include <ctime>
-
-#include <algorithm>
-#include <set>
-
#include "../compat/fun.hpp"
-#include "../strings/mstring.hpp"
-#include "../strings/rstring.hpp"
-#include "../strings/astring.hpp"
-#include "../strings/zstring.hpp"
-#include "../strings/xstring.hpp"
-#include "../strings/literal.hpp"
-
#include "../generic/db.hpp"
+#include "../generic/dumb_ptr.hpp"
#include "../generic/intern-pool.hpp"
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
-#include "../io/lock.hpp"
-#include "../io/read.hpp"
-#include "../io/write.hpp"
-#include "../net/socket.hpp"
#include "../net/timer.hpp"
#include "../mmo/core.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/human_time_diff.hpp"
-#include "../mmo/utils.hpp"
#include "atcommand.hpp"
#include "battle.hpp"
@@ -69,6 +47,9 @@
#include "npc.hpp"
#include "party.hpp"
#include "pc.hpp"
+#include "script-call-internal.hpp"
+#include "script-parse-internal.hpp"
+#include "script-persist.hpp"
#include "skill.hpp"
#include "storage.hpp"
@@ -77,128 +58,6 @@
namespace tmwa
{
-constexpr bool DEBUG_DISP = false;
-constexpr bool DEBUG_RUN = false;
-
-enum class VariableCode : uint8_t
-{
- PARAM,
- VARIABLE,
-};
-
-enum class StringCode : uint8_t
-{
- NOP, POS, INT, PARAM, FUNC,
- VARIABLE,
-};
-
-enum class ByteCode : uint8_t
-{
- // types and specials
- // Note that 'INT' is synthetic, and does not occur in the data stream
- NOP, POS, INT, PARAM, FUNC, STR, ARG,
- VARIABLE, EOL,
-
- // unary and binary operators
- LOR, LAND, LE, LT, GE, GT, EQ, NE,
- XOR, OR, AND, ADD, SUB, MUL, DIV, MOD,
- NEG, LNOT, NOT, R_SHIFT, L_SHIFT,
-
- // additions
- // needed because FUNC is used for the actual call
- FUNC_REF,
-};
-
-struct str_data_t
-{
- StringCode type;
- RString strs;
- int backpatch;
- int label_;
- int val;
-};
-
-class ScriptBuffer
-{
- typedef ZString::iterator ZSit;
-
- std::vector<ByteCode> script_buf;
-public:
- // construction methods used only by script.cpp
- 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 used only by script.cpp
- 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
-{
-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;
-}
-
-struct script_stack
-{
- std::vector<struct script_data> stack_datav;
-};
-
-enum class ScriptEndState;
-// future improvements coming!
-class ScriptState
-{
-public:
- struct script_stack *stack;
- int start, end;
- ScriptEndState state;
- BlockId rid, oid;
- ScriptPointer scriptp, new_scriptp;
- int defsp, new_defsp;
-};
-
-static
-Map<RString, str_data_t> str_datam;
-static
-str_data_t LABEL_NEXTLINE_;
-
-static
-DMap<SIR, int> mapreg_db;
-static
-Map<SIR, RString> mapregstr_db;
-static
-int mapreg_dirty = -1;
-AString mapreg_txt = "save/mapreg.txt"_s;
-constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s;
-
-Map<ScriptLabel, int> scriptlabel_db;
-static
-std::set<ScriptLabel> probable_labels;
-UPMap<RString, const ScriptBuffer> userfunc_db;
-
static
Array<LString, 11> pos_str //=
{{
@@ -215,1166 +74,6 @@ Array<LString, 11> pos_str //=
"Not Equipped"_s,
}};
-static
-struct Script_Config
-{
- 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;
- static const
- int check_cmdcount = 8192;
- static const
- int check_gotocount = 512;
-} script_config;
-
-static
-int parse_cmd_if = 0;
-static
-str_data_t *parse_cmdp;
-
-static
-void run_func(ScriptState *st);
-
-static
-void mapreg_setreg(SIR num, int val);
-static
-void mapreg_setregstr(SIR num, XString str);
-
-struct BuiltinFunction
-{
- void (*func)(ScriptState *);
- LString name;
- LString arg;
- char ret;
-};
-// defined later
-extern BuiltinFunction builtin_functions[];
-
-static
-InternPool variable_names;
-
-
-template<class D>
-bool first_type_is_any()
-{
- return false;
-}
-
-template<class D, class F, class... R>
-constexpr
-bool first_type_is_any()
-{
- return std::is_same<D, F>::value || first_type_is_any<D, R...>();
-}
-
-
-static
-str_data_t *search_strp(XString p)
-{
- return str_datam.search(p);
-}
-
-static
-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;
- }
-}
-
-bool read_constdb(ZString filename)
-{
- io::ReadFile in(filename);
- if (!in.is_open())
- {
- PRINTF("can't read %s\n"_fmt, filename);
- return false;
- }
-
- bool rv = true;
- AString line_;
- while (in.getline(line_))
- {
- // is_comment only works for whole-line comments
- // that could change once the Z dependency is dropped ...
- LString comment = "//"_s;
- XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip();
- if (!line)
- continue;
- // "%m[A-Za-z0-9_] %i %i"
-
- // TODO promote either qsplit() or asplit()
- auto _it = std::find(line.begin(), line.end(), ' ');
- auto name = line.xislice_h(_it);
- auto _rest = line.xislice_t(_it);
- while (_rest.startswith(' '))
- _rest = _rest.xslice_t(1);
- auto _it2 = std::find(_rest.begin(), _rest.end(), ' ');
- auto val_ = _rest.xislice_h(_it2);
- auto type_ = _rest.xislice_t(_it2);
- while (type_.startswith(' '))
- type_ = type_.xslice_t(1);
- // yes, the above actually DTRT even for underlength input
-
- int val;
- int type = 0;
- // Note for future archeaologists: this code is indented correctly
- if (std::find_if_not(name.begin(), name.end(),
- [](char c)
- {
- return ('0' <= c && c <= '9')
- || ('A' <= c && c <= 'Z')
- || ('a' <= c && c <= 'z')
- || (c == '_');
- }) != name.end()
- || !extract(val_, &val)
- || (!extract(type_, &type) && type_))
- {
- PRINTF("Bad const line: %s\n"_fmt, line_);
- rv = false;
- continue;
- }
- str_data_t *n = add_strp(name);
- n->type = type ? StringCode::PARAM : StringCode::INT;
- n->val = val;
- }
- return rv;
-}
-
-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);
-}
-
-//
-// 実行系
-//
-enum class ScriptEndState
-{
- ZERO,
- STOP,
- END,
- RERUNLINE,
- GOTO,
- RETFUNC,
-};
-
-/*==========================================
- * ridからsdへの解決
- *------------------------------------------
- */
-static
-dumb_ptr<map_session_data> script_rid2sd(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
- if (!sd)
- {
- PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt);
- }
- return sd;
-}
-
-/*==========================================
- * 変数の読み取り
- *------------------------------------------
- */
-static
-void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
-{
- MATCH (*data)
- {
- CASE (const ScriptDataParam&, u)
- {
- if (sd == nullptr)
- PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
- int numi = 0;
- if (sd)
- numi = pc_readparam(sd, u.reg.sp());
- *data = ScriptDataInt{numi};
- }
- CASE (const ScriptDataVariable&, u)
- {
- ZString name_ = variable_names.outtern(u.reg.base());
- VarName name = stringish<VarName>(name_);
- char prefix = name.front();
- char postfix = name.back();
-
- if (prefix != '$')
- {
- if (sd == nullptr)
- PRINTF("get_val error name?:%s\n"_fmt, name);
- }
- if (postfix == '$')
- {
- RString str;
- if (prefix == '@')
- {
- if (sd)
- str = pc_readregstr(sd, u.reg);
- }
- else if (prefix == '$')
- {
- RString *s = mapregstr_db.search(u.reg);
- if (s)
- str = *s;
- }
- else
- {
- PRINTF("script: get_val: illegal scope string variable.\n"_fmt);
- str = "!!ERROR!!"_s;
- }
- *data = ScriptDataStr{str};
- }
- else
- {
- int numi = 0;
- if (prefix == '@')
- {
- if (sd)
- numi = pc_readreg(sd, u.reg);
- }
- else if (prefix == '$')
- {
- numi = mapreg_db.get(u.reg);
- }
- else if (prefix == '#')
- {
- if (name[1] == '#')
- {
- if (sd)
- numi = pc_readaccountreg2(sd, name);
- }
- else
- {
- if (sd)
- numi = pc_readaccountreg(sd, name);
- }
- }
- else
- {
- if (sd)
- numi = pc_readglobalreg(sd, name);
- }
- *data = ScriptDataInt{numi};
- }
- }
- }
-}
-
-static __attribute__((deprecated))
-void get_val(ScriptState *st, struct script_data *data)
-{
- dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr;
- get_val(sd, data);
-}
-
-/*==========================================
- * 変数の読み取り2
- *------------------------------------------
- */
-static
-struct script_data get_val2(ScriptState *st, SIR reg)
-{
- struct script_data dat = ScriptDataVariable{reg};
- get_val(st, &dat);
- return dat;
-}
-
-/*==========================================
- * 変数設定用
- *------------------------------------------
- */
-static
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd)
-{
- if (type == VariableCode::PARAM)
- {
- int val = vd.get_if<ScriptDataInt>()->numi;
- pc_setparam(sd, reg.sp(), val);
- return;
- }
- assert (type == VariableCode::VARIABLE);
-
- ZString name_ = variable_names.outtern(reg.base());
- VarName name = stringish<VarName>(name_);
- char prefix = name.front();
- char postfix = name.back();
-
- if (postfix == '$')
- {
- RString str = vd.get_if<ScriptDataStr>()->str;
- if (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<ScriptDataInt>()->numi;
- if (prefix == '@')
- {
- pc_setreg(sd, reg, val);
- }
- else if (prefix == '$')
- {
- mapreg_setreg(reg, val);
- }
- else if (prefix == '#')
- {
- if (name[1] == '#')
- pc_setaccountreg2(sd, name, val);
- else
- pc_setaccountreg(sd, name, val);
- }
- else
- {
- pc_setglobalreg(sd, name, val);
- }
- }
-}
-
-static
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id)
-{
- struct script_data vd = ScriptDataInt{id};
- set_reg(sd, type, reg, vd);
-}
-
-static
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd)
-{
- struct script_data vd = ScriptDataStr{zd};
- set_reg(sd, type, reg, vd);
-}
-
-/*==========================================
- * 文字列への変換
- *------------------------------------------
- */
-static __attribute__((warn_unused_result))
-RString conv_str(ScriptState *st, struct script_data *data)
-{
- get_val(st, data);
- assert (!data->is<ScriptDataRetInfo>());
- if (auto *u = data->get_if<ScriptDataInt>())
- {
- AString buf = STRPRINTF("%d"_fmt, u->numi);
- *data = ScriptDataStr{buf};
- }
- return data->get_if<ScriptDataStr>()->str;
-}
-
-/*==========================================
- * 数値へ変換
- *------------------------------------------
- */
-static __attribute__((warn_unused_result))
-int conv_num(ScriptState *st, struct script_data *data)
-{
- int rv = 0;
- get_val(st, data);
- assert (!data->is<ScriptDataRetInfo>());
- MATCH (*data)
- {
- default:
- abort();
- CASE (const ScriptDataStr&, u)
- {
- RString p = u.str;
- rv = atoi(p.c_str());
- }
- CASE (const ScriptDataInt&, u)
- {
- return u.numi;
- }
- CASE (const ScriptDataPos&, u)
- {
- return u.numi;
- }
- }
- *data = ScriptDataInt{rv};
- return rv;
-}
-
-static __attribute__((warn_unused_result))
-const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data)
-{
- get_val(st, data);
- return data->get_if<ScriptDataRetInfo>()->script;
-}
-
-
-template<class T>
-static
-void push_int(struct script_stack *stack, int val)
-{
- static_assert(first_type_is_any<T, ScriptDataPos, ScriptDataInt, ScriptDataArg, ScriptDataFuncRef>(), "not int type");
-
- script_data nsd = T{.numi= val};
- stack->stack_datav.push_back(nsd);
-}
-
-template<class T>
-static
-void push_reg(struct script_stack *stack, SIR reg)
-{
- static_assert(first_type_is_any<T, ScriptDataParam, ScriptDataVariable>(), "not reg type");
-
- script_data nsd = T{.reg= reg};
- stack->stack_datav.push_back(nsd);
-}
-
-template<class T>
-static
-void push_script(struct script_stack *stack, const ScriptBuffer *code)
-{
- static_assert(first_type_is_any<T, ScriptDataRetInfo>(), "not scriptbuf type");
-
- script_data nsd = T{.script= code};
- stack->stack_datav.push_back(nsd);
-}
-
-template<class T>
-static
-void push_str(struct script_stack *stack, RString str)
-{
- static_assert(first_type_is_any<T, ScriptDataStr>(), "not str type");
-
- script_data nsd = T{.str= str};
- stack->stack_datav.push_back(nsd);
-}
-
-static
-void push_copy(struct script_stack *stack, int pos_)
-{
- script_data csd = stack->stack_datav[pos_];
- stack->stack_datav.push_back(csd);
-}
-
-static
-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);
-}
-
-
#define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)])
#define HARG(n) (st->end > st->start + 2 + (n))
@@ -4178,857 +2877,6 @@ void builtin_mapexit(ScriptState *)
}
-//
-// 実行部main
-//
-/*==========================================
- * コマンドの読み取り
- *------------------------------------------
- */
-static
-ByteCode get_com(ScriptPointer *script)
-{
- if (static_cast<uint8_t>(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<uint8_t>(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<ScriptDataInt>())
- rv = u->numi;
- st->stack->stack_datav.pop_back();
- return rv;
-}
-
-static
-bool isstr(struct script_data& c)
-{
- return c.is<ScriptDataStr>();
-}
-
-/*==========================================
- * 加算演算子
- *------------------------------------------
- */
-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<ScriptDataInt>()->numi += back.get_if<ScriptDataInt>()->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<ScriptDataInt>(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<ScriptDataInt>(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<ScriptDataStr>()->str, d2.get_if<ScriptDataStr>()->str);
- }
- else if (!(isstr(d1) || isstr(d2)))
- {
- // ii => op_2num
- op_2num(st, op, d1.get_if<ScriptDataInt>()->numi, d2.get_if<ScriptDataInt>()->numi);
- }
- else
- {
- // si,is => error
- PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt);
- push_int<ScriptDataInt>(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<ScriptDataInt>(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<ScriptDataArg>())
- {
- start_sp--;
- if (start_sp == 0)
- {
- if (battle_config.error_log)
- PRINTF("function not found\n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
- }
- // the func is before the arg
- start_sp--;
- st->start = start_sp;
- st->end = end_sp;
-
- if (!st->stack->stack_datav[st->start].is<ScriptDataFuncRef>())
- {
- PRINTF("run_func: not function and command! \n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
- size_t func = st->stack->stack_datav[st->start].get_if<ScriptDataFuncRef>()->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 (d)
- {
- CASE (const ScriptDataInt&, u)
- {
- PRINTF(" int(%d)"_fmt, u.numi);
- }
- CASE (const ScriptDataRetInfo&, u)
- {
- PRINTF(" retinfo(%p)"_fmt, static_cast<const void *>(u.script));
- }
- CASE (const ScriptDataParam&, u)
- {
- PRINTF(" param(%d)"_fmt, u.reg.sp());
- }
- CASE (const ScriptDataVariable&, u)
- {
- PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index());
- }
- CASE (const ScriptDataArg&, u)
- {
- (void)u;
- PRINTF(" arg"_fmt);
- }
- CASE (const ScriptDataPos&, u)
- {
- (void)u;
- PRINTF(" pos(%d)"_fmt, u.numi);
- }
- CASE (const ScriptDataStr&, u)
- {
- (void)u;
- PRINTF(" str(%s)"_fmt, u.str);
- }
- CASE (const ScriptDataFuncRef&, u)
- {
- (void)u;
- PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name);
- }
- }
- }
- PRINTF("\n"_fmt);
- }
- builtin_functions[func].func(st);
-
- 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<ScriptDataRetInfo>())
- {
- PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
- assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet
- st->scriptp.code = 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;
- }
-}
-
-// pretend it's external so this can be called in the debugger
-void dump_script(const ScriptBuffer *script);
-void dump_script(const ScriptBuffer *script)
-{
- ScriptPointer scriptp(script, 0);
- while (scriptp.pos < reinterpret_cast<const std::vector<ByteCode> *>(script)->size())
- {
- PRINTF("%6zu: "_fmt, scriptp.pos);
- switch (ByteCode c = get_com(&scriptp))
- {
- case ByteCode::EOL:
- PRINTF("EOL\n"_fmt); // extra newline between functions
- break;
- case ByteCode::INT:
- // synthesized!
- PRINTF("INT %d"_fmt, get_num(&scriptp));
- break;
-
- case ByteCode::POS:
- case ByteCode::VARIABLE:
- case ByteCode::FUNC_REF:
- case ByteCode::PARAM:
- {
- int arg = 0;
- arg |= static_cast<uint8_t>(scriptp.pop()) << 0;
- arg |= static_cast<uint8_t>(scriptp.pop()) << 8;
- arg |= static_cast<uint8_t>(scriptp.pop()) << 16;
- switch(c)
- {
- case ByteCode::POS:
- PRINTF("POS %d"_fmt, arg);
- break;
- case ByteCode::VARIABLE:
- PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg));
- break;
- case ByteCode::FUNC_REF:
- PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name);
- break;
- case ByteCode::PARAM:
- PRINTF("PARAM SP::#%d (sorry)"_fmt, arg);
- break;
- }
- }
- break;
- case ByteCode::ARG:
- PRINTF("ARG"_fmt);
- break;
- case ByteCode::STR:
- PRINTF("STR \"%s\""_fmt, scriptp.pops());
- break;
- case ByteCode::FUNC:
- PRINTF("FUNC"_fmt);
- break;
-
- case ByteCode::ADD:
- PRINTF("ADD"_fmt);
- break;
- case ByteCode::SUB:
- PRINTF("SUB"_fmt);
- break;
- case ByteCode::MUL:
- PRINTF("MUL"_fmt);
- break;
- case ByteCode::DIV:
- PRINTF("DIV"_fmt);
- break;
- case ByteCode::MOD:
- PRINTF("MOD"_fmt);
- break;
- case ByteCode::EQ:
- PRINTF("EQ"_fmt);
- break;
- case ByteCode::NE:
- PRINTF("NE"_fmt);
- break;
- case ByteCode::GT:
- PRINTF("GT"_fmt);
- break;
- case ByteCode::GE:
- PRINTF("GE"_fmt);
- break;
- case ByteCode::LT:
- PRINTF("LT"_fmt);
- break;
- case ByteCode::LE:
- PRINTF("LE"_fmt);
- break;
- case ByteCode::AND:
- PRINTF("AND"_fmt);
- break;
- case ByteCode::OR:
- PRINTF("OR"_fmt);
- break;
- case ByteCode::XOR:
- PRINTF("XOR"_fmt);
- break;
- case ByteCode::LAND:
- PRINTF("LAND"_fmt);
- break;
- case ByteCode::LOR:
- PRINTF("LOR"_fmt);
- break;
- case ByteCode::R_SHIFT:
- PRINTF("R_SHIFT"_fmt);
- break;
- case ByteCode::L_SHIFT:
- PRINTF("L_SHIFT"_fmt);
- break;
- case ByteCode::NEG:
- PRINTF("NEG"_fmt);
- break;
- case ByteCode::NOT:
- PRINTF("NOT"_fmt);
- break;
- case ByteCode::LNOT:
- PRINTF("LNOT"_fmt);
- break;
-
- case ByteCode::NOP:
- PRINTF("NOP"_fmt);
- break;
-
- default:
- PRINTF("??? %d"_fmt, c);
- break;
- }
- PRINTF("\n"_fmt);
- }
-}
-
-/*==========================================
- * スクリプトの実行メイン部分
- *------------------------------------------
- */
-static
-void run_script_main(ScriptState *st, const ScriptBuffer *rootscript)
-{
- int cmdcount = script_config.check_cmdcount;
- int gotocount = script_config.check_gotocount;
- struct script_stack *stack = st->stack;
-
- 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<ScriptDataInt>(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<uint8_t>(st->scriptp.pop()) << 0;
- arg |= static_cast<uint8_t>(st->scriptp.pop()) << 8;
- arg |= static_cast<uint8_t>(st->scriptp.pop()) << 16;
- switch(c)
- {
- case ByteCode::POS:
- push_int<ScriptDataPos>(stack, arg);
- break;
- case ByteCode::VARIABLE:
- push_reg<ScriptDataVariable>(stack, SIR::from(arg));
- break;
- case ByteCode::FUNC_REF:
- push_int<ScriptDataFuncRef>(stack, arg);
- break;
- case ByteCode::PARAM:
- SP arg_sp = static_cast<SP>(arg);
- push_reg<ScriptDataParam>(stack, SIR::from(arg_sp));
- break;
- }
- }
- break;
- case ByteCode::ARG:
- push_int<ScriptDataArg>(stack, 0);
- break;
- case ByteCode::STR:
- push_str<ScriptDataStr>(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 (gotocount > 0 && (--gotocount) <= 0)
- {
- PRINTF("run_script: infinity loop !\n"_fmt);
- 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);
- st->state = ScriptEndState::END;
- break;
- }
- if (cmdcount > 0 && (--cmdcount) <= 0)
- {
- PRINTF("run_script: infinity loop !\n"_fmt);
- st->state = ScriptEndState::END;
- }
- }
- switch (st->state)
- {
- case ScriptEndState::STOP:
- break;
- case ScriptEndState::END:
- {
- dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
- st->scriptp.code = nullptr;
- st->scriptp.pos = -1;
- if (sd && sd->npc_id == st->oid)
- npc_event_dequeue(sd);
- }
- break;
- case ScriptEndState::RERUNLINE:
- st->scriptp.pos = rerun_pos;
- break;
- }
-
- if (st->state != ScriptEndState::END)
- {
- // 再開するためにスタック情報を保存
- dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
- if (sd)
- {
- sd->npc_stackbuf = stack->stack_datav;
- sd->npc_script = st->scriptp.code;
- // sd->npc_pos is set later ... ???
- sd->npc_scriptroot = 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<argrec_t> args)
-{
- struct script_stack stack;
- ScriptState st;
- dumb_ptr<map_session_data> sd = map_id2sd(rid);
- const ScriptBuffer *rootscript = sp.code;
- int i;
- if (sp.code == nullptr || sp.pos >> 24)
- return -1;
-
- if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == 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;
- 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 mapreg_setreg(SIR reg, int val)
-{
- mapreg_db.put(reg, val);
-
- mapreg_dirty = 1;
-}
-
-/*==========================================
- * 文字列型マップ変数の変更
- *------------------------------------------
- */
-void mapreg_setregstr(SIR reg, XString str)
-{
- if (!str)
- mapregstr_db.erase(reg);
- else
- mapregstr_db.insert(reg, str);
-
- mapreg_dirty = 1;
-}
-
-/*==========================================
- * 永続的マップ変数の読み込み
- *------------------------------------------
- */
-static
-void script_load_mapreg(void)
-{
- io::ReadFile in(mapreg_txt);
-
- if (!in.is_open())
- return;
-
- AString line;
- while (in.getline(line))
- {
- XString buf1, buf2;
- int index = 0;
- if (extract(line,
- record<'\t'>(
- record<','>(&buf1, &index),
- &buf2))
- || extract(line,
- record<'\t'>(
- record<','>(&buf1),
- &buf2)))
- {
- int s = variable_names.intern(buf1);
- SIR key = SIR::from(s, index);
- if (buf1.back() == '$')
- {
- mapregstr_db.insert(key, buf2);
- }
- else
- {
- int v;
- if (!extract(buf2, &v))
- goto borken;
- mapreg_db.put(key, v);
- }
- }
- else
- {
- borken:
- PRINTF("%s: %s broken data !\n"_fmt, mapreg_txt, AString(buf1));
- continue;
- }
- }
- mapreg_dirty = 0;
-}
-
-/*==========================================
- * 永続的マップ変数の書き込み
- *------------------------------------------
- */
-static
-void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp)
-{
- int num = key.base(), i = key.index();
- ZString name = variable_names.outtern(num);
- if (name[1] != '@')
- {
- if (i == 0)
- FPRINTF(fp, "%s\t%d\n"_fmt, name, data);
- else
- FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data);
- }
-}
-
-static
-void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp)
-{
- int num = key.base(), i = key.index();
- ZString name = variable_names.outtern(num);
- if (name[1] != '@')
- {
- if (i == 0)
- FPRINTF(fp, "%s\t%s\n"_fmt, name, data);
- else
- FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data);
- }
-}
-
-static
-void script_save_mapreg(void)
-{
- io::WriteLock fp(mapreg_txt);
- if (!fp.is_open())
- return;
- for (auto& pair : mapreg_db)
- script_save_mapreg_intsub(pair.first, pair.second, fp);
- for (auto& pair : mapregstr_db)
- script_save_mapreg_strsub(pair.first, pair.second, fp);
- mapreg_dirty = 0;
-}
-
-static
-void script_autosave_mapreg(TimerData *, tick_t)
-{
- if (mapreg_dirty)
- script_save_mapreg();
-}
-
-void do_final_script(void)
-{
- if (mapreg_dirty >= 0)
- script_save_mapreg();
-
- mapreg_db.clear();
- mapregstr_db.clear();
- scriptlabel_db.clear();
- userfunc_db.clear();
-
- str_datam.clear();
-}
-
-/*==========================================
- * 初期化
- *------------------------------------------
- */
-void do_init_script(void)
-{
- script_load_mapreg();
-
- Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL,
- script_autosave_mapreg,
- MAPREG_AUTOSAVE_INTERVAL
- ).detach();
-}
-
#define BUILTIN(func, args, ret) \
{builtin_##func, #func ## _s, args, ret}
@@ -5152,40 +3000,4 @@ BuiltinFunction builtin_functions[] =
BUILTIN(mapexit, ""_s, '\0'),
{nullptr, ""_s, ""_s, '\0'},
};
-
-void set_script_var_i(dumb_ptr<map_session_data> 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<map_session_data> 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<map_session_data> 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<ScriptDataInt>())
- 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<map_session_data> 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<ScriptDataStr>())
- // 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 tmwa
diff --git a/src/map/script-fun.hpp b/src/map/script-fun.hpp
new file mode 100644
index 0000000..e9f64f9
--- /dev/null
+++ b/src/map/script-fun.hpp
@@ -0,0 +1,38 @@
+#pragma once
+// script-fun.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "fwd.hpp"
+
+#include "../strings/literal.hpp"
+
+namespace tmwa
+{
+struct BuiltinFunction
+{
+ void (*func)(ScriptState *);
+ LString name;
+ LString arg;
+ char ret;
+};
+
+extern BuiltinFunction builtin_functions[];
+} // namespace tmwa
diff --git a/src/map/script-parse-internal.hpp b/src/map/script-parse-internal.hpp
new file mode 100644
index 0000000..f01317a
--- /dev/null
+++ b/src/map/script-parse-internal.hpp
@@ -0,0 +1,69 @@
+#pragma once
+// script-parse-internal.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "script-parse.hpp"
+#include "fwd.hpp"
+
+#include "../strings/rstring.hpp"
+
+
+namespace tmwa
+{
+enum class StringCode : uint8_t
+{
+ NOP, POS, INT, PARAM, FUNC,
+ VARIABLE,
+};
+enum class ByteCode : uint8_t
+{
+ // types and specials
+ // Note that 'INT' is synthetic, and does not occur in the data stream
+ NOP, POS, INT, PARAM, FUNC, STR, ARG,
+ VARIABLE, EOL,
+
+ // unary and binary operators
+ LOR, LAND, LE, LT, GE, GT, EQ, NE,
+ XOR, OR, AND, ADD, SUB, MUL, DIV, MOD,
+ NEG, LNOT, NOT, R_SHIFT, L_SHIFT,
+
+ // additions
+ // needed because FUNC is used for the actual call
+ FUNC_REF,
+};
+
+struct str_data_t
+{
+ StringCode type;
+ RString strs;
+ int backpatch;
+ int label_;
+ int val;
+};
+
+extern
+Map<RString, str_data_t> str_datam;
+extern
+InternPool variable_names;
+
+str_data_t *search_strp(XString p);
+str_data_t *add_strp(XString p);
+} // namespace tmwa
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
diff --git a/src/map/script-parse.hpp b/src/map/script-parse.hpp
new file mode 100644
index 0000000..6f536f8
--- /dev/null
+++ b/src/map/script-parse.hpp
@@ -0,0 +1,42 @@
+#pragma once
+// script-parse.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "fwd.hpp"
+
+#include <memory>
+
+#include "../strings/fwd.hpp"
+
+#include "../generic/fwd.hpp"
+
+
+namespace tmwa
+{
+std::unique_ptr<const ScriptBuffer> parse_script(ZString, int, bool implicit_end);
+
+extern
+Map<ScriptLabel, int> scriptlabel_db;
+extern
+UPMap<RString, const ScriptBuffer> userfunc_db;
+
+extern int script_errors;
+} // namespace tmwa
diff --git a/src/map/script-persist.cpp b/src/map/script-persist.cpp
new file mode 100644
index 0000000..9397d42
--- /dev/null
+++ b/src/map/script-persist.cpp
@@ -0,0 +1,30 @@
+#include "script-persist.hpp"
+// script-persist.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 "../poison.hpp"
+
+
+namespace tmwa
+{
+} // namespace tmwa
diff --git a/src/map/script.hpp b/src/map/script-persist.hpp
index 19dbcd0..35cd2d0 100644
--- a/src/map/script.hpp
+++ b/src/map/script-persist.hpp
@@ -1,5 +1,5 @@
#pragma once
-// script.hpp - EAthena script frontend, engine, and library.
+// script-persist.hpp - EAthena script frontend, engine, and library.
//
// Copyright © ????-2004 Athena Dev Teams
// Copyright © 2004-2011 The Mana World Development Team
@@ -22,63 +22,13 @@
#include "fwd.hpp"
-#include <cstdint>
-
-#include <memory>
-#include <vector>
-
-#include "../range/fwd.hpp"
-
-#include "../strings/zstring.hpp"
-
-#include "../generic/fwd.hpp"
+#include "../strings/rstring.hpp"
#include "../sexpr/variant.hpp"
-#include "../mmo/ids.hpp"
-
-
-namespace tmwa
-{
-enum class ByteCode : uint8_t;
-
-class ScriptBuffer;
-} // namespace tmwa
-
-namespace std
-{
-template<>
-struct default_delete<const tmwa::ScriptBuffer>
-{
- default_delete() {}
- default_delete(default_delete<tmwa::ScriptBuffer>) {}
- void operator()(const tmwa::ScriptBuffer *sd);
-};
-} // namespace std
namespace tmwa
{
-struct ScriptPointer
-{
- const ScriptBuffer *code;
- size_t pos;
-
- ScriptPointer()
- : code()
- , pos()
- {}
-
- ScriptPointer(const ScriptBuffer *c, size_t p)
- : code(c)
- , pos(p)
- {}
-
- ByteCode peek() const;
- ByteCode pop();
- ZString pops();
-};
-
-// internal
class SIR
{
uint32_t impl;
@@ -166,44 +116,4 @@ struct script_data : ScriptDataVariantBase
script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {}
script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {}
};
-
-std::unique_ptr<const ScriptBuffer> parse_script(ZString, int, bool implicit_end);
-
-struct argrec_t
-{
- ZString name;
- union _aru
- {
- int i;
- ZString s;
-
- _aru(int n) : i(n) {}
- _aru(ZString z) : s(z) {}
- } v;
-
- argrec_t(ZString n, int i) : name(n), v(i) {}
- argrec_t(ZString n, ZString z) : name(n), v(z) {}
-};
-int run_script_l(ScriptPointer, BlockId, BlockId, Slice<argrec_t> args);
-int run_script(ScriptPointer, BlockId, BlockId);
-
-extern
-Map<ScriptLabel, int> scriptlabel_db;
-extern
-UPMap<RString, const ScriptBuffer> userfunc_db;
-
-void do_init_script(void);
-void do_final_script(void);
-
-extern AString mapreg_txt;
-
-extern int script_errors;
-
-bool read_constdb(ZString filename);
-
-void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val);
-void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val);
-
-int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e);
-ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e);
} // namespace tmwa
diff --git a/src/map/script.py b/src/map/script-persist.py
index a5010cd..a5010cd 100644
--- a/src/map/script.py
+++ b/src/map/script-persist.py
diff --git a/src/map/script-startup-internal.hpp b/src/map/script-startup-internal.hpp
new file mode 100644
index 0000000..e4d6a8f
--- /dev/null
+++ b/src/map/script-startup-internal.hpp
@@ -0,0 +1,44 @@
+#pragma once
+// script-startup-internal.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "script-startup.hpp"
+#include "fwd.hpp"
+
+#include "../generic/fwd.hpp"
+
+#include "../strings/fwd.hpp"
+
+#include "script-persist.hpp"
+
+
+namespace tmwa
+{
+extern
+DMap<SIR, int> mapreg_db;
+extern
+Map<SIR, RString> mapregstr_db;
+extern
+int mapreg_dirty;
+
+void mapreg_setreg(SIR reg, int val);
+void mapreg_setregstr(SIR reg, XString str);
+} // namespace tmwa
diff --git a/src/map/script-startup.cpp b/src/map/script-startup.cpp
new file mode 100644
index 0000000..1aa63aa
--- /dev/null
+++ b/src/map/script-startup.cpp
@@ -0,0 +1,265 @@
+#include "script-startup-internal.hpp"
+// script-startup.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 "../strings/zstring.hpp"
+
+#include "../generic/db.hpp"
+#include "../generic/intern-pool.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/read.hpp"
+#include "../io/lock.hpp"
+
+#include "../net/timer.hpp"
+
+#include "../mmo/extract.hpp"
+
+#include "map.hpp"
+#include "script-parse-internal.hpp"
+#include "script-persist.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+DMap<SIR, int> mapreg_db;
+Map<SIR, RString> mapregstr_db;
+int mapreg_dirty = -1;
+AString mapreg_txt = "save/mapreg.txt"_s;
+constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s;
+
+bool read_constdb(ZString filename)
+{
+ io::ReadFile in(filename);
+ if (!in.is_open())
+ {
+ PRINTF("can't read %s\n"_fmt, filename);
+ return false;
+ }
+
+ bool rv = true;
+ AString line_;
+ while (in.getline(line_))
+ {
+ // is_comment only works for whole-line comments
+ // that could change once the Z dependency is dropped ...
+ LString comment = "//"_s;
+ XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip();
+ if (!line)
+ continue;
+ // "%m[A-Za-z0-9_] %i %i"
+
+ // TODO promote either qsplit() or asplit()
+ auto _it = std::find(line.begin(), line.end(), ' ');
+ auto name = line.xislice_h(_it);
+ auto _rest = line.xislice_t(_it);
+ while (_rest.startswith(' '))
+ _rest = _rest.xslice_t(1);
+ auto _it2 = std::find(_rest.begin(), _rest.end(), ' ');
+ auto val_ = _rest.xislice_h(_it2);
+ auto type_ = _rest.xislice_t(_it2);
+ while (type_.startswith(' '))
+ type_ = type_.xslice_t(1);
+ // yes, the above actually DTRT even for underlength input
+
+ int val;
+ int type = 0;
+ // Note for future archeaologists: this code is indented correctly
+ if (std::find_if_not(name.begin(), name.end(),
+ [](char c)
+ {
+ return ('0' <= c && c <= '9')
+ || ('A' <= c && c <= 'Z')
+ || ('a' <= c && c <= 'z')
+ || (c == '_');
+ }) != name.end()
+ || !extract(val_, &val)
+ || (!extract(type_, &type) && type_))
+ {
+ PRINTF("Bad const line: %s\n"_fmt, line_);
+ rv = false;
+ continue;
+ }
+ str_data_t *n = add_strp(name);
+ n->type = type ? StringCode::PARAM : StringCode::INT;
+ n->val = val;
+ }
+ return rv;
+}
+
+/*==========================================
+ * マップ変数の変更
+ *------------------------------------------
+ */
+void mapreg_setreg(SIR reg, int val)
+{
+ mapreg_db.put(reg, val);
+
+ mapreg_dirty = 1;
+}
+
+/*==========================================
+ * 文字列型マップ変数の変更
+ *------------------------------------------
+ */
+void mapreg_setregstr(SIR reg, XString str)
+{
+ if (!str)
+ mapregstr_db.erase(reg);
+ else
+ mapregstr_db.insert(reg, str);
+
+ mapreg_dirty = 1;
+}
+
+/*==========================================
+ * 永続的マップ変数の読み込み
+ *------------------------------------------
+ */
+static
+void script_load_mapreg(void)
+{
+ io::ReadFile in(mapreg_txt);
+
+ if (!in.is_open())
+ return;
+
+ AString line;
+ while (in.getline(line))
+ {
+ XString buf1, buf2;
+ int index = 0;
+ if (extract(line,
+ record<'\t'>(
+ record<','>(&buf1, &index),
+ &buf2))
+ || extract(line,
+ record<'\t'>(
+ record<','>(&buf1),
+ &buf2)))
+ {
+ int s = variable_names.intern(buf1);
+ SIR key = SIR::from(s, index);
+ if (buf1.back() == '$')
+ {
+ mapregstr_db.insert(key, buf2);
+ }
+ else
+ {
+ int v;
+ if (!extract(buf2, &v))
+ goto borken;
+ mapreg_db.put(key, v);
+ }
+ }
+ else
+ {
+ borken:
+ PRINTF("%s: %s broken data !\n"_fmt, mapreg_txt, AString(buf1));
+ continue;
+ }
+ }
+ mapreg_dirty = 0;
+}
+
+/*==========================================
+ * 永続的マップ変数の書き込み
+ *------------------------------------------
+ */
+static
+void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp)
+{
+ int num = key.base(), i = key.index();
+ ZString name = variable_names.outtern(num);
+ if (name[1] != '@')
+ {
+ if (i == 0)
+ FPRINTF(fp, "%s\t%d\n"_fmt, name, data);
+ else
+ FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data);
+ }
+}
+
+static
+void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp)
+{
+ int num = key.base(), i = key.index();
+ ZString name = variable_names.outtern(num);
+ if (name[1] != '@')
+ {
+ if (i == 0)
+ FPRINTF(fp, "%s\t%s\n"_fmt, name, data);
+ else
+ FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data);
+ }
+}
+
+static
+void script_save_mapreg(void)
+{
+ io::WriteLock fp(mapreg_txt);
+ if (!fp.is_open())
+ return;
+ for (auto& pair : mapreg_db)
+ script_save_mapreg_intsub(pair.first, pair.second, fp);
+ for (auto& pair : mapregstr_db)
+ script_save_mapreg_strsub(pair.first, pair.second, fp);
+ mapreg_dirty = 0;
+}
+
+static
+void script_autosave_mapreg(TimerData *, tick_t)
+{
+ if (mapreg_dirty)
+ script_save_mapreg();
+}
+
+void do_final_script(void)
+{
+ if (mapreg_dirty >= 0)
+ script_save_mapreg();
+
+ mapreg_db.clear();
+ mapregstr_db.clear();
+ scriptlabel_db.clear();
+ userfunc_db.clear();
+
+ str_datam.clear();
+}
+
+/*==========================================
+ * 初期化
+ *------------------------------------------
+ */
+void do_init_script(void)
+{
+ script_load_mapreg();
+
+ Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL,
+ script_autosave_mapreg,
+ MAPREG_AUTOSAVE_INTERVAL
+ ).detach();
+}
+} // namespace tmwa
diff --git a/src/map/script-startup.hpp b/src/map/script-startup.hpp
new file mode 100644
index 0000000..8a6b50d
--- /dev/null
+++ b/src/map/script-startup.hpp
@@ -0,0 +1,35 @@
+#pragma once
+// script-startup.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// 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 "fwd.hpp"
+
+#include "../strings/fwd.hpp"
+
+namespace tmwa
+{
+void do_init_script(void);
+void do_final_script(void);
+
+extern AString mapreg_txt;
+
+bool read_constdb(ZString filename);
+} // namespace tmwa
diff --git a/src/map/skill.hpp b/src/map/skill.hpp
index ec353ce..5d23bae 100644
--- a/src/map/skill.hpp
+++ b/src/map/skill.hpp
@@ -20,9 +20,10 @@
// 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 "skill.t.hpp"
+
#include "fwd.hpp"
-#include "skill.t.hpp"
#include "skill-pools.hpp"
#include "../strings/fwd.hpp"