diff options
-rw-r--r-- | Makefile.in | 15 | ||||
-rw-r--r-- | src/ast/fwd.hpp | 12 | ||||
-rw-r--r-- | src/ast/item.cpp | 27 | ||||
-rw-r--r-- | src/ast/item.hpp | 24 | ||||
-rw-r--r-- | src/ast/item_test.cpp | 16 | ||||
-rw-r--r-- | src/ast/npc.cpp | 260 | ||||
-rw-r--r-- | src/ast/npc.hpp | 69 | ||||
-rw-r--r-- | src/ast/npc_test.cpp | 214 | ||||
-rw-r--r-- | src/ast/script.hpp | 4 | ||||
-rw-r--r-- | src/compat/result.hpp | 16 | ||||
-rw-r--r-- | src/io/read.cpp | 5 | ||||
-rw-r--r-- | src/main-gdb-head.py | 18 | ||||
-rw-r--r-- | src/map/atcommand.cpp | 20 | ||||
-rw-r--r-- | src/map/fwd.hpp | 1 | ||||
-rw-r--r-- | src/map/itemdb.cpp | 132 | ||||
-rw-r--r-- | src/map/magic-v2.cpp | 17 | ||||
-rw-r--r-- | src/map/map.cpp | 4 | ||||
-rw-r--r-- | src/map/npc-parse.cpp | 977 | ||||
-rw-r--r-- | src/map/npc-parse.hpp | 2 | ||||
-rw-r--r-- | src/map/script-parse.cpp | 6 | ||||
-rw-r--r-- | src/map/script-parse.hpp | 2 | ||||
-rw-r--r-- | src/strings/rstring.cpp | 2 | ||||
-rw-r--r-- | src/strings/rstring.hpp | 2 |
23 files changed, 1020 insertions, 825 deletions
diff --git a/Makefile.in b/Makefile.in index 9be1ad9..8254bc2 100644 --- a/Makefile.in +++ b/Makefile.in @@ -474,8 +474,8 @@ lib/%.${SO_LONG}-gdb.py: lib/%.${SO_LONG}: lib/%.${SO_LONG}-gdb.py $(MKDIR_FIRST) $l ${CXX} -shared -Wl,-soname=$*.${SO_SHORT} ${LDFLAGS} $(filter-out lib/%-gdb.py,$^) ${LDLIBS} -o $@ - $c ln -sf $*.${SO_LONG} lib/$*.${SO_SHORT} - $c ln -sf $*.${SO_SHORT} lib/$*.so + $c ln -sfT $*.${SO_LONG} lib/$*.${SO_SHORT} + $c ln -sfT $*.${SO_SHORT} lib/$*.so lib/%.a: $(MKDIR_FIRST) rm -f $@ @@ -510,9 +510,14 @@ test-gtest: $(patsubst bin/tests/%,stamp/run-%.stamp,${GTEST_BINARIES}) test: test-dtest test-dtest: $(patsubst bin/tests/%,stamp/run-%.stamp,${DTEST_BINARIES}) +$(patsubst bin/tests/%,stamp/run-%.stamp,${TEST_BINARIES} ${GTEST_BINARIES} ${DTEST_BINARIES}): stamp/symlink-test-lib-dir.stamp +stamp/symlink-test-lib-dir.stamp: + @mkdir -p bin + ln -sfT ../lib bin/lib + touch $@ + stamp/run-%.stamp: bin/tests/% $(MKDIR_FIRST) - ln -sf ../lib bin/lib ${TESTER} $< ${TEST_ARGS} touch $@ @@ -566,8 +571,8 @@ ifeq (${ENABLE_SHARED},yes) ${install_data} -t ${DESTDIR}${LIBDIR} \ $(patsubst %.so,%.${SO_LONG},${LIBRARIES}) for lib in $(patsubst lib/%.so,%,${LIBRARIES}); do \ - ln -sf $$lib.${SO_LONG} ${DESTDIR}${LIBDIR}/$$lib.${SO_SHORT}; \ - ln -sf $$lib.${SO_SHORT} ${DESTDIR}${LIBDIR}/$$lib.so; \ + ln -sfT $$lib.${SO_LONG} ${DESTDIR}${LIBDIR}/$$lib.${SO_SHORT}; \ + ln -sfT $$lib.${SO_SHORT} ${DESTDIR}${LIBDIR}/$$lib.so; \ done else ${install_data} -t ${DESTDIR}${LIBDIR} \ diff --git a/src/ast/fwd.hpp b/src/ast/fwd.hpp index 4ab4490..24bf545 100644 --- a/src/ast/fwd.hpp +++ b/src/ast/fwd.hpp @@ -23,11 +23,23 @@ #include "../compat/fwd.hpp" // rank 2 #include "../io/fwd.hpp" // rank 4 #include "../net/fwd.hpp" // rank 5 +#include "../sexpr/fwd.hpp" // rank 5 #include "../mmo/fwd.hpp" // rank 6 #include "../high/fwd.hpp" // rank 9 // ast/fwd.hpp is rank 10 namespace tmwa { +namespace ast +{ +namespace npc +{ +class Warp; +} // namespace npc +namespace script +{ +class ScriptBody; +} // namespace script +} // namespace ast // meh, add more when I feel like it } // namespace tmwa diff --git a/src/ast/item.cpp b/src/ast/item.cpp index 99417d8..bd4f295 100644 --- a/src/ast/item.cpp +++ b/src/ast/item.cpp @@ -18,8 +18,6 @@ // 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 "../compat/memory.hpp" - #include "../io/extract.hpp" #include "../io/line.hpp" @@ -36,9 +34,6 @@ namespace item { using io::respan; - // separate file because virtual - ItemOrComment::~ItemOrComment() {} - static void skip_comma_space(io::LineCharReader& lr) { @@ -107,6 +102,7 @@ namespace item opt.implicit_start = true; opt.implicit_end = true; opt.one_line = true; + opt.no_event = true; auto rv = ast::script::parse_script_body(lr, opt); if (rv.get_success().is_some()) { @@ -115,20 +111,19 @@ namespace item return rv; } -#define SPAN_EXTRACT(bitexpr, var) ({ auto bit = bitexpr; if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract"_s #var)); var.span = bit.span; }) +#define SPAN_EXTRACT(bitexpr, var) ({ auto bit = bitexpr; if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; }) #define EOL_ERROR(lr) ({ io::LineChar c; lr.get(c) ? Err(c.error_str("unexpected EOL"_s)) : Err("unexpected EOF before unexpected EOL"_s); }) - Result<std::unique_ptr<ItemOrComment>> parse_item(io::LineCharReader& lr) + Option<Result<ItemOrComment>> parse_item(io::LineCharReader& lr) { - std::unique_ptr<ItemOrComment> rv = nullptr; - Spanned<RString> first = TRY_UNWRAP(lex_nonscript(lr, true), return Ok(std::move(rv))); + Spanned<RString> first = TRY_UNWRAP(lex_nonscript(lr, true), return None); if (first.data.startswith("//"_s)) { Comment comment; - comment.span = first.span; comment.comment = first.data; - rv = make_unique<Comment>(std::move(comment)); - return Ok(std::move(rv)); + ItemOrComment rv = std::move(comment); + rv.span = first.span; + return Some(Ok(std::move(rv))); } Item item; SPAN_EXTRACT(first, item.id); @@ -150,10 +145,10 @@ namespace item SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.view); item.use_script = TRY(lex_script(lr)); item.equip_script = TRY(lex_script(lr)); - item.span.begin = item.id.span.begin; - item.span.end = item.equip_script.span.end; - rv = make_unique<Item>(std::move(item)); - return Ok(std::move(rv)); + ItemOrComment rv = std::move(item); + rv.span.begin = item.id.span.begin; + rv.span.end = item.equip_script.span.end; + return Some(Ok(std::move(rv))); } } // namespace item } // namespace ast diff --git a/src/ast/item.hpp b/src/ast/item.hpp index b54e55c..a8fe908 100644 --- a/src/ast/item.hpp +++ b/src/ast/item.hpp @@ -20,12 +20,12 @@ #include "fwd.hpp" -#include <memory> - #include "../compat/result.hpp" #include "../io/span.hpp" +#include "../sexpr/variant.hpp" + #include "../mmo/clif.t.hpp" #include "../mmo/ids.hpp" #include "../mmo/strs.hpp" @@ -41,17 +41,11 @@ namespace item { using io::Spanned; - struct ItemOrComment - { - io::LineSpan span; - - virtual ~ItemOrComment(); - }; - struct Comment : ItemOrComment + struct Comment { RString comment; }; - struct Item : ItemOrComment + struct Item { Spanned<ItemNameId> id; Spanned<ItemName> name; @@ -74,7 +68,15 @@ namespace item ast::script::ScriptBody equip_script; }; - Result<std::unique_ptr<ItemOrComment>> parse_item(io::LineCharReader& lr); + using ItemOrCommentBase = Variant<Comment, Item>; + struct ItemOrComment : ItemOrCommentBase + { + ItemOrComment(Comment o) : ItemOrCommentBase(std::move(o)) {} + ItemOrComment(Item o) : ItemOrCommentBase(std::move(o)) {} + io::LineSpan span; + }; + + Option<Result<ItemOrComment>> parse_item(io::LineCharReader& lr); } // namespace item } // namespace ast } // namespace tmwa diff --git a/src/ast/item_test.cpp b/src/ast/item_test.cpp index 3b5fd07..a77662e 100644 --- a/src/ast/item_test.cpp +++ b/src/ast/item_test.cpp @@ -24,7 +24,7 @@ #include "../tests/fdhack.hpp" -//#include "../poison.hpp" +#include "../poison.hpp" namespace tmwa @@ -54,7 +54,7 @@ namespace item { io::LineCharReader lr(io::from_string, "<string>"_s, input); auto res = parse_item(lr); - EXPECT_EQ(res.get_success(), Some(std::unique_ptr<ItemOrComment>(nullptr))); + EXPECT_TRUE(res.is_none()); } } TEST(itemast, comment) @@ -70,11 +70,11 @@ namespace item for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_item(lr); + auto res = TRY_UNWRAP(parse_item(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - EXPECT_SPAN(top->span, 1,1, 1,8); - auto p = dynamic_cast<Comment *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,8); + auto p = top.get_if<Comment>(); EXPECT_TRUE(p); if (p) { @@ -96,11 +96,11 @@ namespace item for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_item(lr); + auto res = TRY_UNWRAP(parse_item(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - EXPECT_SPAN(top->span, 1,1, 1,58); - auto p = dynamic_cast<Item *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,58); + auto p = top.get_if<Item>(); EXPECT_TRUE(p); if (p) { diff --git a/src/ast/npc.cpp b/src/ast/npc.cpp index ceb381d..6b75503 100644 --- a/src/ast/npc.cpp +++ b/src/ast/npc.cpp @@ -18,8 +18,6 @@ // 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 "../compat/memory.hpp" - #include "../io/cxxstdio.hpp" #include "../io/extract.hpp" #include "../io/line.hpp" @@ -39,10 +37,7 @@ namespace npc { using io::respan; - // separate file because virtual - TopLevel::~TopLevel() {} - -#define TRY_EXTRACT(bit, var) ({ if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract"_s #var)); var.span = bit.span; }) +#define TRY_EXTRACT(bit, var) ({ if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; }) static Result<Warp> parse_warp(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) @@ -53,28 +48,33 @@ namespace npc } if (bits[0].data.size() != 3) { - return Err(bits[0].span.error_str("expect 3 ,component,s"_s)); + return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s)); } assert(bits[1].data.size() == 1); assert(bits[1].data[0].data == "warp"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } if (bits[3].data.size() != 5) { - return Err(bits[3].span.error_str("expect 5 ,component,s"_s)); + return Err(bits[3].span.error_str("in |component 4| expect 5 ,component,s"_s)); } Warp warp; - warp.span = span; TRY_EXTRACT(bits[0].data[0], warp.m); TRY_EXTRACT(bits[0].data[1], warp.x); TRY_EXTRACT(bits[0].data[2], warp.y); warp.key_span = bits[1].data[0].span; TRY_EXTRACT(bits[2].data[0], warp.name); + if (bits[3].data[0].data == "-1"_s) + bits[3].data[0].data = "4294967295"_s; TRY_EXTRACT(bits[3].data[0], warp.xs); + warp.xs.data += 2; + if (bits[3].data[1].data == "-1"_s) + bits[3].data[1].data = "4294967295"_s; TRY_EXTRACT(bits[3].data[1], warp.ys); + warp.ys.data += 2; TRY_EXTRACT(bits[3].data[2], warp.to_m); TRY_EXTRACT(bits[3].data[3], warp.to_x); TRY_EXTRACT(bits[3].data[4], warp.to_y); @@ -89,21 +89,20 @@ namespace npc } if (bits[0].data.size() != 4) { - return Err(bits[0].span.error_str("expect 4 ,component,s"_s)); + return Err(bits[0].span.error_str("in |component 1| expect 4 ,component,s"_s)); } assert(bits[1].data.size() == 1); assert(bits[1].data[0].data == "shop"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } if (bits[3].data.size() < 2) { - return Err(bits[3].span.error_str("expect at least 2 ,component,s"_s)); + return Err(bits[3].span.error_str("in |component 4| expect at least 2 ,component,s"_s)); } Shop shop; - shop.span = span; TRY_EXTRACT(bits[0].data[0], shop.m); TRY_EXTRACT(bits[0].data[1], shop.x); TRY_EXTRACT(bits[0].data[2], shop.y); @@ -121,8 +120,8 @@ namespace npc assert(data.span.begin.line == data.span.end.line); item.span = data.span; - XString name; - if (!extract(data.data, record<':'>(&name, &item.data.value.data))) + XString name, value; + if (!extract(data.data, record<':'>(&name, &value))) return Err(data.span.error_str("Failed to split item:value"_s)); item.data.name.span = item.span; item.data.name.span.end.column = item.data.name.span.begin.column + name.size() - 1; @@ -138,7 +137,22 @@ namespace npc item.data.name.span.warning("Shop item is an id; should be a name"_s); } if (!extract(name, &item.data.name.data)) - return Err("item name problem (too long?)"_s); + { + return Err(item.data.name.span.error_str("item name problem (too long?)"_s)); + } + if (value.startswith('-')) + { + item.data.value.span.begin.warning("Shop value multiplier should use '*' instead of '-' now"_s); + value = value.xslice_t(1); + } + else if (value.startswith('*')) + { + value = value.xslice_t(1); + } + if (!extract(value, &item.data.value.data)) + { + return Err(item.data.value.span.error_str("invalid item value"_s)); + } } return Ok(std::move(shop)); } @@ -151,21 +165,20 @@ namespace npc } if (bits[0].data.size() != 3 && bits[0].data.size() != 5) { - return Err(bits[0].span.error_str("expect 3 or 5 ,component,s"_s)); + return Err(bits[0].span.error_str("in |component 1| expect 3 or 5 ,component,s"_s)); } assert(bits[1].data.size() == 1); assert(bits[1].data[0].data == "monster"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } if (bits[3].data.size() != 2 && bits[3].data.size() != 4 && bits[3].data.size() != 5) { - return Err(bits[3].span.error_str("expect 2, 4, or 5 ,component,s"_s)); + return Err(bits[3].span.error_str("in |component 4| expect 2, 4, or 5 ,component,s"_s)); } Monster mob; - mob.span = span; TRY_EXTRACT(bits[0].data[0], mob.m); TRY_EXTRACT(bits[0].data[1], mob.x); TRY_EXTRACT(bits[0].data[2], mob.y); @@ -231,42 +244,39 @@ namespace npc } if (bits[0].data.size() != 1) { - return Err(bits[0].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[0].span.error_str("in |component 1| expect 1 ,component,s"_s)); } assert(bits[1].data.size() == 1); assert(bits[1].data[0].data == "mapflag"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); - } - if (bits.size() >= 4) - { - if (bits[3].data.size() != 1) - { - return Err(bits[3].span.error_str("expect 1 ,component,s"_s)); - } + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } MapFlag mapflag; - mapflag.span = span; TRY_EXTRACT(bits[0].data[0], mapflag.m); mapflag.key_span = bits[1].data[0].span; TRY_EXTRACT(bits[2].data[0], mapflag.name); if (bits.size() >= 4) { - TRY_EXTRACT(bits[3].data[0], mapflag.opt_extra); + mapflag.vec_extra.span = bits[3].span; + for (auto& bit : bits[3].data) + { + mapflag.vec_extra.data.emplace_back(); + TRY_EXTRACT(bit, mapflag.vec_extra.data.back()); + } } else { - mapflag.opt_extra.data = ""_s; - mapflag.opt_extra.span = bits[2].span; - mapflag.opt_extra.span.end.column++; - mapflag.opt_extra.span.begin.column = mapflag.opt_extra.span.end.column; + mapflag.vec_extra.data = {}; + mapflag.vec_extra.span = bits[2].span; + mapflag.vec_extra.span.end.column++; + mapflag.vec_extra.span.begin.column = mapflag.vec_extra.span.end.column; } return Ok(std::move(mapflag)); } static - Result<ScriptFunction> parse_script_function(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) + Result<ScriptFunction> parse_script_function_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) { // ScriptFunction: function|script|Fun Name{code} if (bits.size() != 3) @@ -279,23 +289,17 @@ namespace npc assert(bits[1].data[0].data == "script"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } ScriptFunction script_function; - script_function.span = span; script_function.key1_span = bits[0].data[0].span; - script_function.key_span = bits[1].data[0].span; TRY_EXTRACT(bits[2].data[0], script_function.name); - // also expect '{' and parse real script - ast::script::ScriptOptions opt; - opt.implicit_start = true; - opt.default_label = "OnCall"_s; - script_function.body = TRY(ast::script::parse_script_body(lr, opt)); + // also expect '{' and parse real script (in caller) return Ok(std::move(script_function)); } static - Result<ScriptNone> parse_script_none(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) + Result<ScriptNone> parse_script_none_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) { // ScriptNone: -|script|script name|-1{code} if (bits.size() != 4) @@ -308,29 +312,23 @@ namespace npc assert(bits[1].data[0].data == "script"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } assert(bits[3].data[0].data == "-1"_s); if (bits[3].data.size() != 1) { - return Err(bits[2].span.error_str("last |component| should be just -1"_s)); + return Err(bits[3].span.error_str("in |component 4| should be just -1"_s)); } ScriptNone script_none; - script_none.span = span; script_none.key1_span = bits[0].data[0].span; - script_none.key_span = bits[1].data[0].span; TRY_EXTRACT(bits[2].data[0], script_none.name); script_none.key4_span = bits[3].data[0].span; - // also expect '{' and parse real script - ast::script::ScriptOptions opt; - opt.implicit_start = true; - opt.no_start = true; - script_none.body = TRY(ast::script::parse_script_body(lr, opt)); + // also expect '{' and parse real script (in caller) return Ok(std::move(script_none)); } static - Result<ScriptMapNone> parse_script_map_none(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) + Result<ScriptMapNone> parse_script_map_none_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) { // ScriptMapNone: m,x,y,d|script|script name|-1{code} if (bits.size() != 4) @@ -339,37 +337,31 @@ namespace npc } if (bits[0].data.size() != 4) { - return Err(bits[0].span.error_str("expect 3 ,component,s"_s)); + return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s)); } assert(bits[1].data.size() == 1); assert(bits[1].data[0].data == "script"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } if (bits[3].data.size() != 1 || bits[3].data[0].data != "-1"_s) { - return Err(bits[2].span.error_str("last |component| should be just -1"_s)); + return Err(bits[3].span.error_str("in |component 4| should be just -1"_s)); } ScriptMapNone script_map_none; - script_map_none.span = span; TRY_EXTRACT(bits[0].data[0], script_map_none.m); TRY_EXTRACT(bits[0].data[1], script_map_none.x); TRY_EXTRACT(bits[0].data[2], script_map_none.y); TRY_EXTRACT(bits[0].data[3], script_map_none.d); - script_map_none.key_span = bits[1].data[0].span; TRY_EXTRACT(bits[2].data[0], script_map_none.name); script_map_none.key4_span = bits[3].data[0].span; - // also expect '{' and parse real script - ast::script::ScriptOptions opt; - opt.implicit_start = true; - opt.no_start = true; - script_map_none.body = TRY(ast::script::parse_script_body(lr, opt)); + // also expect '{' and parse real script (in caller) return Ok(std::move(script_map_none)); } static - Result<ScriptMap> parse_script_map(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) + Result<ScriptMap> parse_script_map_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) { // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code} if (bits.size() != 4) @@ -378,41 +370,48 @@ namespace npc } if (bits[0].data.size() != 4) { - return Err(bits[0].span.error_str("expect 3 ,component,s"_s)); + return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s)); } assert(bits[1].data.size() == 1); assert(bits[1].data[0].data == "script"_s); if (bits[2].data.size() != 1) { - return Err(bits[2].span.error_str("expect 1 ,component,s"_s)); + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); } - if (bits[3].data.size() != 3) + if (bits[3].data.size() != 1 && bits[3].data.size() != 3) { - return Err(bits[3].span.error_str("expect 3 ,component,s"_s)); + return Err(bits[3].span.error_str("in |component 4| expect 1 or 3 ,component,s"_s)); } ScriptMap script_map; - script_map.span = span; TRY_EXTRACT(bits[0].data[0], script_map.m); TRY_EXTRACT(bits[0].data[1], script_map.x); TRY_EXTRACT(bits[0].data[2], script_map.y); TRY_EXTRACT(bits[0].data[3], script_map.d); - script_map.key_span = bits[1].data[0].span; TRY_EXTRACT(bits[2].data[0], script_map.name); TRY_EXTRACT(bits[3].data[0], script_map.npc_class); - TRY_EXTRACT(bits[3].data[1], script_map.xs); - TRY_EXTRACT(bits[3].data[2], script_map.ys); - // also expect '{' and parse real script - ast::script::ScriptOptions opt; - opt.implicit_start = true; - opt.default_label = "OnClick"_s; - script_map.body = TRY(ast::script::parse_script_body(lr, opt)); + if (bits[3].data.size() >= 3) + { + TRY_EXTRACT(bits[3].data[1], script_map.xs); + script_map.xs.data = script_map.xs.data * 2 + 1; + TRY_EXTRACT(bits[3].data[2], script_map.ys); + script_map.ys.data = script_map.ys.data * 2 + 1; + } + else + { + script_map.xs.data = 0; + script_map.xs.span = script_map.npc_class.span; + script_map.xs.span.end.column++; + script_map.xs.span.begin = script_map.xs.span.end; + script_map.ys.data = 0; + script_map.ys.span = script_map.xs.span; + } + // also expect '{' and parse real script (in caller) return Ok(std::move(script_map)); } static - Result<std::unique_ptr<Script>> parse_script_any(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) + Result<Script> parse_script_any(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) { - std::unique_ptr<Script> rv; // 4 cases: // ScriptFunction: function|script|Fun Name{code} // ScriptNone: -|script|script name|-1{code} @@ -420,21 +419,52 @@ namespace npc // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code} if (bits[0].data[0].data == "function"_s) { - rv = make_unique<ScriptFunction>(TRY(parse_script_function(span, bits, lr))); + Script rv = TRY(parse_script_function_head(span, bits)); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.default_label = "OnCall"_s; + opt.no_event = true; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); } else if (bits[0].data[0].data == "-"_s) { - rv = make_unique<ScriptNone>(TRY(parse_script_none(span, bits, lr))); + Script rv = TRY(parse_script_none_head(span, bits)); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.no_start = true; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); } else if (bits.size() >= 4 && bits[3].data[0].data == "-1"_s) { - rv = make_unique<ScriptMapNone>(TRY(parse_script_map_none(span, bits, lr))); + Script rv = TRY(parse_script_map_none_head(span, bits)); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.no_start = true; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); } else { - rv = make_unique<ScriptMap>(TRY(parse_script_map(span, bits, lr))); + ScriptMap script_map = TRY(parse_script_map_head(span, bits)); + bool no_touch = !script_map.xs.data && !script_map.ys.data; + Script rv = std::move(script_map); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.default_label = "OnClick"_s; + opt.no_touch = no_touch; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); } - return Ok(std::move(rv)); } /// Try to extract a top-level token @@ -460,10 +490,29 @@ namespace npc { return None; } - // separators are simple + // separators are simple ... NOT. + // Reasonably, we should only ever eat a single char here. + // Unfortunately, we aren't dealing with reasonable people here. if (c.ch() == '|' || c.ch() == ',') { lr.adv(); + while (true) + { + io::LineChar c2; + if (!lr.get(c2) || c2.ch() == '\n' || c2.ch() == '{') + { + c.warning("Separator at EOL"_s); + return None; + } + if (c2.ch() == ',' || c2.ch() == '|') + { + lr.adv(); + c = c2; + c.warning("Adjacent separators"_s); + continue; + } + break; + } return Some(respan({c, c}, RString(VString<1>(c.ch())))); } io::LineSpan span; @@ -488,7 +537,7 @@ namespace npc // if first token on line, can get comment if (first && c.ch() == '/') { - while (lr.get(c) && c.ch() != '\n' && c.ch() != '{') + while (lr.get(c) && c.ch() != '\n') { accum += c.ch(); span.end = c; @@ -506,11 +555,11 @@ namespace npc return Some(respan(span, RString(accum))); } - Result<std::unique_ptr<TopLevel>> parse_top(io::LineCharReader& in) + Option<Result<TopLevel>> parse_top(io::LineCharReader& in) { - std::unique_ptr<TopLevel> rv; Spanned<std::vector<Spanned<std::vector<Spanned<RString>>>>> bits; + // special logic for the first 'bit' { Spanned<RString> mayc = TRY_UNWRAP(lex(in, true), { @@ -519,15 +568,15 @@ namespace npc { return Err(c.error_str("Unexpected script open"_s)); } - return Ok(std::move(rv)); + return None; }); if (mayc.data.startswith("//"_s)) { Comment com; com.comment = std::move(mayc.data); - com.span = std::move(mayc.span); - rv = make_unique<Comment>(std::move(com)); - return Ok(std::move(rv)); + TopLevel rv = std::move(com); + rv.span = std::move(mayc.span); + return Some(Ok(std::move(rv))); } if (mayc.data == "|"_s || mayc.data == ","_s) @@ -573,29 +622,38 @@ namespace npc Spanned<RString>& w2 = bits.data[1].data[0]; if (w2.data == "warp"_s) { - rv = make_unique<Warp>(TRY(parse_warp(bits.span, bits.data))); + TopLevel rv = TRY(parse_warp(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); } else if (w2.data == "shop"_s) { - rv = make_unique<Shop>(TRY(parse_shop(bits.span, bits.data))); + TopLevel rv = TRY(parse_shop(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); } else if (w2.data == "monster"_s) { - rv = make_unique<Monster>(TRY(parse_monster(bits.span, bits.data))); + TopLevel rv = TRY(parse_monster(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); } else if (w2.data == "mapflag"_s) { - rv = make_unique<MapFlag>(TRY(parse_mapflag(bits.span, bits.data))); + TopLevel rv = TRY(parse_mapflag(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); } else if (w2.data == "script"_s) { - rv = TRY_MOVE(parse_script_any(bits.span, bits.data, in)); + TopLevel rv = TRY_MOVE(parse_script_any(bits.span, bits.data, in)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); } else { return Err(w2.span.error_str("Unknown type"_s)); } - return Ok(std::move(rv)); } } // namespace npc } // namespace ast diff --git a/src/ast/npc.hpp b/src/ast/npc.hpp index dd323b6..a51acd3 100644 --- a/src/ast/npc.hpp +++ b/src/ast/npc.hpp @@ -20,18 +20,18 @@ #include "fwd.hpp" -#include <memory> - #include "../compat/result.hpp" #include "../io/span.hpp" +#include "../net/timer.t.hpp" + +#include "../sexpr/variant.hpp" + #include "../mmo/clif.t.hpp" #include "../mmo/ids.hpp" #include "../mmo/strs.hpp" -#include "../net/timer.t.hpp" - #include "script.hpp" @@ -43,17 +43,11 @@ namespace npc { using io::Spanned; - struct TopLevel - { - io::LineSpan span; - - virtual ~TopLevel(); - }; - struct Comment : TopLevel + struct Comment { RString comment; }; - struct Warp : TopLevel + struct Warp { Spanned<MapName> m; Spanned<unsigned> x, y; @@ -66,9 +60,10 @@ namespace npc struct ShopItem { Spanned<ItemName> name; + bool value_multiply; Spanned<int> value; }; - struct Shop : TopLevel + struct Shop { Spanned<MapName> m; Spanned<unsigned> x, y; @@ -78,7 +73,7 @@ namespace npc Spanned<Species> npc_class; Spanned<std::vector<Spanned<ShopItem>>> items; }; - struct Monster : TopLevel + struct Monster { Spanned<MapName> m; Spanned<unsigned> x, y; @@ -90,32 +85,25 @@ namespace npc Spanned<interval_t> delay1, delay2; Spanned<NpcEvent> event; }; - struct MapFlag : TopLevel + struct MapFlag { Spanned<MapName> m; io::LineSpan key_span; - // TODO should this extract all the way? Spanned<RString> name; - Spanned<RString> opt_extra; - }; - struct Script : TopLevel - { - io::LineSpan key_span; - // see src/script/parser.hpp - ast::script::ScriptBody body; + Spanned<std::vector<Spanned<RString>>> vec_extra; }; - struct ScriptFunction : Script + struct ScriptFunction { io::LineSpan key1_span; Spanned<RString> name; }; - struct ScriptNone : Script + struct ScriptNone { io::LineSpan key1_span; Spanned<NpcName> name; io::LineSpan key4_span; }; - struct ScriptMapNone : Script + struct ScriptMapNone { Spanned<MapName> m; Spanned<unsigned> x, y; @@ -123,7 +111,7 @@ namespace npc Spanned<NpcName> name; io::LineSpan key4_span; }; - struct ScriptMap : Script + struct ScriptMap { Spanned<MapName> m; Spanned<unsigned> x, y; @@ -132,9 +120,32 @@ namespace npc Spanned<Species> npc_class; Spanned<unsigned> xs, ys; }; - // other Script subclasses elsewhere? (for item and magic scripts) + using ScriptBase = Variant<ScriptFunction, ScriptNone, ScriptMapNone, ScriptMap>; + struct Script : ScriptBase + { + Script() = default; + Script(ScriptFunction s) : ScriptBase(std::move(s)) {} + Script(ScriptNone s) : ScriptBase(std::move(s)) {} + Script(ScriptMapNone s) : ScriptBase(std::move(s)) {} + Script(ScriptMap s) : ScriptBase(std::move(s)) {} + + io::LineSpan key_span; + ast::script::ScriptBody body; + }; + using TopLevelBase = Variant<Comment, Warp, Shop, Monster, MapFlag, Script>; + struct TopLevel : TopLevelBase + { + TopLevel() = default; + TopLevel(Comment t) : TopLevelBase(std::move(t)) {} + TopLevel(Warp t) : TopLevelBase(std::move(t)) {} + TopLevel(Shop t) : TopLevelBase(std::move(t)) {} + TopLevel(Monster t) : TopLevelBase(std::move(t)) {} + TopLevel(MapFlag t) : TopLevelBase(std::move(t)) {} + TopLevel(Script t) : TopLevelBase(std::move(t)) {} + io::LineSpan span; + }; - Result<std::unique_ptr<TopLevel>> parse_top(io::LineCharReader& in); + Option<Result<TopLevel>> parse_top(io::LineCharReader& in); } // namespace npc } // namespace ast } // namespace tmwa diff --git a/src/ast/npc_test.cpp b/src/ast/npc_test.cpp index ea4bdf3..7cced35 100644 --- a/src/ast/npc_test.cpp +++ b/src/ast/npc_test.cpp @@ -24,7 +24,7 @@ #include "../tests/fdhack.hpp" -//#include "../poison.hpp" +#include "../poison.hpp" namespace tmwa @@ -54,7 +54,7 @@ namespace npc { io::LineCharReader lr(io::from_string, "<string>"_s, input); auto res = parse_top(lr); - EXPECT_EQ(res.get_success(), Some(std::unique_ptr<TopLevel>(nullptr))); + EXPECT_TRUE(res.is_none()); } } TEST(npcast, comment) @@ -70,13 +70,11 @@ namespace npc for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,8); - auto p = dynamic_cast<Comment *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,8); + auto p = top.get_if<Comment>(); EXPECT_TRUE(p); if (p) { @@ -91,21 +89,19 @@ namespace npc { // 1 2 3 4 //234567890123456789012345678901234567890123456789 - "map.gat,1,2|warp|To Other Map|3,4,other.gat,5,6"_s, - "map.gat,1,2|warp|To Other Map|3,4,other.gat,5,6\n"_s, - "map.gat,1,2|warp|To Other Map|3,4,other.gat,5,6{"_s, + "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8"_s, + "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8\n"_s, + "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8{"_s, // no optional fields in warp }; for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,47); - auto p = dynamic_cast<Warp *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,47); + auto p = top.get_if<Warp>(); EXPECT_TRUE(p); if (p) { @@ -119,15 +115,15 @@ namespace npc EXPECT_SPAN(p->name.span, 1,18, 1,29); EXPECT_EQ(p->name.data, stringish<NpcName>("To Other Map"_s)); EXPECT_SPAN(p->xs.span, 1,31, 1,31); - EXPECT_EQ(p->xs.data, 3); + EXPECT_EQ(p->xs.data, 5); EXPECT_SPAN(p->ys.span, 1,33, 1,33); - EXPECT_EQ(p->ys.data, 4); + EXPECT_EQ(p->ys.data, 6); EXPECT_SPAN(p->to_m.span, 1,35, 1,43); EXPECT_EQ(p->to_m.data, stringish<MapName>("other"_s)); EXPECT_SPAN(p->to_x.span, 1,45, 1,45); - EXPECT_EQ(p->to_x.data, 5); + EXPECT_EQ(p->to_x.data, 7); EXPECT_SPAN(p->to_y.span, 1,47, 1,47); - EXPECT_EQ(p->to_y.data, 6); + EXPECT_EQ(p->to_y.data, 8); } } } @@ -146,13 +142,11 @@ namespace npc for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,54); - auto p = dynamic_cast<Shop *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,54); + auto p = top.get_if<Shop>(); EXPECT_TRUE(p); if (p) { @@ -213,13 +207,11 @@ namespace npc bool third = input.startswith('n'); assert(first + second + third == 1); io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,first?65:54); - auto p = dynamic_cast<Monster *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,first?65:54); + auto p = top.get_if<Monster>(); EXPECT_TRUE(p); if (p) { @@ -309,42 +301,63 @@ namespace npc "Map.gat|mapflag|flagname|optval"_s, "Map.gat|mapflag|flagname|optval\n"_s, "Map.gat|mapflag|flagname|optval{"_s, + "nap.gat|mapflag|flagname|aa,b,c"_s, + "nap.gat|mapflag|flagname|aa,b,c\n"_s, + "nap.gat|mapflag|flagname|aa,b,c{"_s, }; for (auto input : inputs) { + bool first = input.startswith('m'); bool second = input.startswith('M'); + bool third = input.startswith('n'); + EXPECT_EQ(first + second + third, 1); io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,!second?24:31); - auto p = dynamic_cast<MapFlag *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,first?24:31); + auto p = top.get_if<MapFlag>(); EXPECT_TRUE(p); if (p) { EXPECT_SPAN(p->m.span, 1,1, 1,7); - if (!second) + if (first) { EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); } - else + if (second) { EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s)); } + if (third) + { + EXPECT_EQ(p->m.data, stringish<MapName>("nap"_s)); + } EXPECT_SPAN(p->key_span, 1,9, 1,15); EXPECT_SPAN(p->name.span, 1,17, 1,24); EXPECT_EQ(p->name.data, "flagname"_s); - if (!second) + if (first) { - EXPECT_SPAN(p->opt_extra.span, 1,25, 1,25); - EXPECT_EQ(p->opt_extra.data, ""_s); + EXPECT_SPAN(p->vec_extra.span, 1,25, 1,25); + EXPECT_EQ(p->vec_extra.data.size(), 0); } - else + if (second) { - EXPECT_SPAN(p->opt_extra.span, 1,26, 1,31); - EXPECT_EQ(p->opt_extra.data, "optval"_s); + EXPECT_SPAN(p->vec_extra.span, 1,26, 1,31); + EXPECT_EQ(p->vec_extra.data.size(), 1); + EXPECT_SPAN(p->vec_extra.data[0].span, 1,26, 1,31); + EXPECT_EQ(p->vec_extra.data[0].data, "optval"_s); + } + if (third) + { + EXPECT_SPAN(p->vec_extra.span, 1,26, 1,31); + EXPECT_EQ(p->vec_extra.data.size(), 3); + EXPECT_SPAN(p->vec_extra.data[0].span, 1,26, 1,27); + EXPECT_EQ(p->vec_extra.data[0].data, "aa"_s); + EXPECT_SPAN(p->vec_extra.data[1].span, 1,29, 1,29); + EXPECT_EQ(p->vec_extra.data[1].data, "b"_s); + EXPECT_SPAN(p->vec_extra.data[2].span, 1,31, 1,31); + EXPECT_EQ(p->vec_extra.data[2].data, "c"_s); } } } @@ -366,37 +379,37 @@ namespace npc for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,24); - auto p = dynamic_cast<ScriptFunction *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,24); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptFunction>(); EXPECT_TRUE(p); if (p) { EXPECT_SPAN(p->key1_span, 1,1, 1,8); - EXPECT_SPAN(p->key_span, 1,10, 1,15); + EXPECT_SPAN(script->key_span, 1,10, 1,15); EXPECT_SPAN(p->name.span, 1,17, 1,24); EXPECT_EQ(p->name.data, "Fun Name"_s); if (input.endswith('}')) { - EXPECT_SPAN(p->body.span, 1,25, 1,30); + EXPECT_SPAN(script->body.span, 1,25, 1,30); } else if (input.endswith('\n')) { - EXPECT_SPAN(p->body.span, 2,1, 2,6); + EXPECT_SPAN(script->body.span, 2,1, 2,6); } else if (input.endswith(' ')) { - EXPECT_SPAN(p->body.span, 3,2, 3,7); + EXPECT_SPAN(script->body.span, 3,2, 3,7); } else { FAIL(); } - EXPECT_EQ(p->body.braced_body, "{end;}"_s); + EXPECT_EQ(script->body.braced_body, "{end;}"_s); } } } @@ -416,38 +429,38 @@ namespace npc for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,19); - auto p = dynamic_cast<ScriptNone *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,19); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptNone>(); EXPECT_TRUE(p); if (p) { EXPECT_SPAN(p->key1_span, 1,1, 1,1); - EXPECT_SPAN(p->key_span, 1,3, 1,8); + EXPECT_SPAN(script->key_span, 1,3, 1,8); EXPECT_SPAN(p->name.span, 1,10, 1,16); EXPECT_EQ(p->name.data, stringish<NpcName>("#config"_s)); EXPECT_SPAN(p->key4_span, 1,18, 1,19); if (input.endswith('}')) { - EXPECT_SPAN(p->body.span, 1,20, 1,25); + EXPECT_SPAN(script->body.span, 1,20, 1,25); } else if (input.endswith('\n')) { - EXPECT_SPAN(p->body.span, 2,1, 2,6); + EXPECT_SPAN(script->body.span, 2,1, 2,6); } else if (input.endswith(' ')) { - EXPECT_SPAN(p->body.span, 3,2, 3,7); + EXPECT_SPAN(script->body.span, 3,2, 3,7); } else { FAIL(); } - EXPECT_EQ(p->body.braced_body, "{end;}"_s); + EXPECT_EQ(script->body.braced_body, "{end;}"_s); } } } @@ -465,13 +478,13 @@ namespace npc for (auto input : inputs) { io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,28); - auto p = dynamic_cast<ScriptMapNone *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,28); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptMapNone>(); EXPECT_TRUE(p); if (p) { @@ -483,27 +496,27 @@ namespace npc EXPECT_EQ(p->y.data, 2); EXPECT_SPAN(p->d.span, 1,13, 1,13); EXPECT_EQ(p->d.data, DIR::NW); - EXPECT_SPAN(p->key_span, 1,15, 1,20); + EXPECT_SPAN(script->key_span, 1,15, 1,20); EXPECT_SPAN(p->name.span, 1,22, 1,25); EXPECT_EQ(p->name.data, stringish<NpcName>("Init"_s)); EXPECT_SPAN(p->key4_span, 1,27, 1,28); if (input.endswith('}')) { - EXPECT_SPAN(p->body.span, 1,29, 1,34); + EXPECT_SPAN(script->body.span, 1,29, 1,34); } else if (input.endswith('\n')) { - EXPECT_SPAN(p->body.span, 2,1, 2,6); + EXPECT_SPAN(script->body.span, 2,1, 2,6); } else if (input.endswith(' ')) { - EXPECT_SPAN(p->body.span, 3,2, 3,7); + EXPECT_SPAN(script->body.span, 3,2, 3,7); } else { FAIL(); } - EXPECT_EQ(p->body.braced_body, "{end;}"_s); + EXPECT_EQ(script->body.braced_body, "{end;}"_s); } } } @@ -517,54 +530,77 @@ namespace npc "map.gat,1,2,3|script|Asdf|4,5,6{end;}"_s, "map.gat,1,2,3|script|Asdf|4,5,6\n{end;}\n"_s, "map.gat,1,2,3|script|Asdf|4,5,6\n \n {end;} "_s, + "Map.gat,1,2,3|script|Asdf|40506{end;}"_s, + "Map.gat,1,2,3|script|Asdf|40506\n{end;}\n"_s, + "Map.gat,1,2,3|script|Asdf|40506\n \n {end;} "_s, }; for (auto input : inputs) { + bool second = input.startswith('M'); io::LineCharReader lr(io::from_string, "<string>"_s, input); - auto res = parse_top(lr); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); EXPECT_TRUE(res.get_success().is_some()); auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); - if (!top) - FAIL(); - EXPECT_SPAN(top->span, 1,1, 1,31); - auto p = dynamic_cast<ScriptMap *>(top.get()); + EXPECT_SPAN(top.span, 1,1, 1,31); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptMap>(); EXPECT_TRUE(p); if (p) { EXPECT_SPAN(p->m.span, 1,1, 1,7); - EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + if (!second) + { + EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + } + else + { + EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s)); + } EXPECT_SPAN(p->x.span, 1,9, 1,9); EXPECT_EQ(p->x.data, 1); EXPECT_SPAN(p->y.span, 1,11, 1,11); EXPECT_EQ(p->y.data, 2); EXPECT_SPAN(p->d.span, 1,13, 1,13); EXPECT_EQ(p->d.data, DIR::NW); - EXPECT_SPAN(p->key_span, 1,15, 1,20); + EXPECT_SPAN(script->key_span, 1,15, 1,20); EXPECT_SPAN(p->name.span, 1,22, 1,25); EXPECT_EQ(p->name.data, stringish<NpcName>("Asdf"_s)); - EXPECT_SPAN(p->npc_class.span, 1,27, 1,27); - EXPECT_EQ(p->npc_class.data, wrap<Species>(4)); - EXPECT_SPAN(p->xs.span, 1,29, 1,29); - EXPECT_EQ(p->xs.data, 5); - EXPECT_SPAN(p->ys.span, 1,31, 1,31); - EXPECT_EQ(p->ys.data, 6); + if (!second) + { + EXPECT_SPAN(p->npc_class.span, 1,27, 1,27); + EXPECT_EQ(p->npc_class.data, wrap<Species>(4)); + EXPECT_SPAN(p->xs.span, 1,29, 1,29); + EXPECT_EQ(p->xs.data, 11); + EXPECT_SPAN(p->ys.span, 1,31, 1,31); + EXPECT_EQ(p->ys.data, 13); + } + else + { + EXPECT_SPAN(p->npc_class.span, 1,27, 1,31); + EXPECT_EQ(p->npc_class.data, wrap<Species>(40506)); + EXPECT_SPAN(p->xs.span, 1,32, 1,32); + EXPECT_EQ(p->xs.data, 0); + EXPECT_SPAN(p->ys.span, 1,32, 1,32); + EXPECT_EQ(p->ys.data, 0); + } if (input.endswith('}')) { - EXPECT_SPAN(p->body.span, 1,32, 1,37); + EXPECT_SPAN(script->body.span, 1,32, 1,37); } else if (input.endswith('\n')) { - EXPECT_SPAN(p->body.span, 2,1, 2,6); + EXPECT_SPAN(script->body.span, 2,1, 2,6); } else if (input.endswith(' ')) { - EXPECT_SPAN(p->body.span, 3,2, 3,7); + EXPECT_SPAN(script->body.span, 3,2, 3,7); } else { FAIL(); } - EXPECT_EQ(p->body.braced_body, "{end;}"_s); + EXPECT_EQ(script->body.braced_body, "{end;}"_s); } } } diff --git a/src/ast/script.hpp b/src/ast/script.hpp index da43f90..74b11e1 100644 --- a/src/ast/script.hpp +++ b/src/ast/script.hpp @@ -51,6 +51,10 @@ namespace script bool implicit_end = false; // forbid newlines anywhere between { and } bool one_line = false; + // forbid the OnTouch event + bool no_touch = false; + // forbid all events + bool no_event = false; }; Result<ScriptBody> parse_script_body(io::LineCharReader& lr, ScriptOptions opt); diff --git a/src/compat/result.hpp b/src/compat/result.hpp index f03c026..6adc552 100644 --- a/src/compat/result.hpp +++ b/src/compat/result.hpp @@ -66,7 +66,23 @@ namespace tmwa { return Result<T>(magic_flag, message); } + template<class T> + operator Option<Result<T>>() + { + return Some(Result<T>(magic_flag, message)); + } }; + + template<class T> + bool operator == (const Result<T>& l, const Result<T>& r) + { + return l.get_success() == r.get_success() && l.get_failure() == r.get_failure(); + } + template<class T> + bool operator != (const Result<T>& l, const Result<T>& r) + { + return !(l == r); + } } // namespace result using result::Result; using result::Ok; diff --git a/src/io/read.cpp b/src/io/read.cpp index d701c7f..32974d6 100644 --- a/src/io/read.cpp +++ b/src/io/read.cpp @@ -118,6 +118,7 @@ namespace io } bool ReadFile::getline(AString& line) { + bool was_real_file = fd != FD(); MString tmp; char c; bool anything = false; @@ -151,8 +152,10 @@ namespace io else FPRINTF(stderr, "warning: file contains bare CR\n"_fmt); } - else if (!happy && anything) + else if (!happy && anything && was_real_file) + { FPRINTF(stderr, "warning: file does not contain a trailing newline\n"_fmt); + } line = AString(tmp); return anything; } diff --git a/src/main-gdb-head.py b/src/main-gdb-head.py index 3a05917..a465c97 100644 --- a/src/main-gdb-head.py +++ b/src/main-gdb-head.py @@ -134,20 +134,30 @@ class PointerPrinter(object): def to_string(self): v = self._value - addr = int(v.cast(gdb.lookup_type('uintptr_t'))) + uptr = gdb.lookup_type('uintptr_t') + addr = int(v.cast(uptr)) if not addr: s = 'nullptr' else: try: sym, off, sec, lib = info_symbol(addr) - except: - s = '(%s)<heap/stack 0x%x>' % (v.type, addr) + except TypeError: + sp = gdb.parse_and_eval('$sp') + sp = int(sp.cast(uptr)) + LOTS = 8 * 1024 * 1024 + diff = addr - sp + if +diff >= 0 and +diff <= LOTS: + a = '<$sp+0x%x>' % +diff + elif -diff >= 0 and -diff <= LOTS: + a = '<$sp-0x%x>' % -diff + else: + a = '<heap 0x%x>' % addr + s = '(%s)%s' % (v.type, a) else: if off: s = '<%s+%d>' % off else: s = '<%s>' % sym - # TODO should I add (type *) ? return s diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index d3e215f..0b23ddd 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -57,6 +57,8 @@ #include "../high/mmo.hpp" #include "../high/utils.hpp" +#include "../ast/npc.hpp" + #include "battle.hpp" #include "chrif.hpp" #include "clif.hpp" @@ -4136,13 +4138,21 @@ ATCE atcommand_addwarp(Session *s, dumb_ptr<map_session_data> sd, if (!extract(message, record<' '>(&mapname, &x, &y))) return ATCE::USAGE; - AString w1 = STRPRINTF("%s,%d,%d"_fmt, sd->mapname_, sd->bl_x, sd->bl_y); AString w3 = STRPRINTF("%s%d%d%d%d"_fmt, mapname, sd->bl_x, sd->bl_y, x, y); - AString w4 = STRPRINTF("1,1,%s.gat,%d,%d"_fmt, mapname, x, y); - NpcName w3name = stringish<NpcName>(w3); - int ret = npc_parse_warp(w1, "warp"_s, w3name, w4); - if (ret) + + ast::npc::Warp warp; + warp.m.data = sd->mapname_; + warp.x.data = sd->bl_x; + warp.y.data = sd->bl_y; + warp.name.data = w3name; + warp.xs.data = 1+2; + warp.ys.data = 1+2; + warp.to_m.data = mapname; + warp.to_x.data = x; + warp.to_y.data = y; + + if (!npc_load_warp(warp)) // warp failed return ATCE::RANGE; diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp index f998b77..3e4f221 100644 --- a/src/map/fwd.hpp +++ b/src/map/fwd.hpp @@ -34,6 +34,7 @@ #include "../proto2/fwd.hpp" // rank 8 #include "../high/fwd.hpp" // rank 9 #include "../wire/fwd.hpp" // rank 9 +#include "../ast/fwd.hpp" // rank 10 // map/fwd.hpp is rank ∞ because it is an executable diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp index 2e3d0c5..2ce03ea 100644 --- a/src/map/itemdb.cpp +++ b/src/map/itemdb.cpp @@ -30,11 +30,13 @@ #include "../io/cxxstdio.hpp" #include "../io/extract.hpp" -#include "../io/read.hpp" +#include "../io/line.hpp" #include "../mmo/config_parse.hpp" #include "../mmo/extract_enums.hpp" +#include "../ast/item.hpp" + #include "script-parse.hpp" #include "../poison.hpp" @@ -149,99 +151,63 @@ int itemdb_isequip3(ItemNameId nameid) bool itemdb_readdb(ZString filename) { - bool rv = true; - - int ln = 0, lines = 0; + io::LineCharReader in(filename); + if (!in.is_open()) { - io::ReadFile in(filename); - - if (!in.is_open()) - { - PRINTF("can't read %s\n"_fmt, filename); - return false; - } + PRINTF("can't read %s\n"_fmt, filename); + return false; + } - lines = 0; + int ln = 0; - AString line; - while (in.getline(line)) + while (true) + { + auto res = TRY_UNWRAP(ast::item::parse_item(in), + { + PRINTF("read %s done (count=%d)\n"_fmt, filename, ln); + return true; + }); + if (res.get_failure()) + PRINTF("%s\n"_fmt, res.get_failure()); + ast::item::ItemOrComment ioc = TRY_UNWRAP(std::move(res.get_success()), return false); + + MATCH (ioc) { - lines++; - if (is_comment(line)) - continue; - // a line is 17 normal fields followed by 2 {} fields - // the fields are separated by ", *", but there may be , - // in the {}. - - auto it = std::find(line.begin(), line.end(), '{'); - XString main_part = line.xislice_h(it).rstrip(); - // According to the code, tail_part may be empty. See later. - ZString tail_part = line.xislice_t(it); - - XString unused_slot_count; - item_data idv {}; - if (!extract( - main_part, record<','>( - &idv.nameid, - lstripping(&idv.name), - lstripping(&idv.jname), - lstripping(&idv.type), - lstripping(&idv.value_buy), - lstripping(&idv.value_sell), - lstripping(&idv.weight), - lstripping(&idv.atk), - lstripping(&idv.def), - lstripping(&idv.range), - lstripping(&idv.magic_bonus), - lstripping(&unused_slot_count), - lstripping(&idv.sex), - lstripping(&idv.equip), - lstripping(&idv.wlv), - lstripping(&idv.elv), - lstripping(&idv.look) - ) - ) - ) + CASE(const ast::item::Comment&, c) { - PRINTF("%s:%d: error: bad item line: %s\n"_fmt, - filename, lines, line); - rv = false; - continue; + (void)c; } - - ln++; - - Borrowed<struct item_data> id = itemdb_search(idv.nameid); - *id = std::move(idv); - if (id->value_buy == 0 && id->value_sell == 0) + CASE(const ast::item::Item&, item) { + ln++; + + item_data idv {}; + idv.nameid = item.id.data; + idv.name = item.name.data; + idv.jname = item.jname.data; + idv.type = item.type.data; + idv.value_buy = item.buy_price.data ?: item.sell_price.data * 2; + idv.value_sell = item.sell_price.data ?: item.buy_price.data / 2; + idv.weight = item.weight.data; + idv.atk = item.atk.data; + idv.def = item.def.data; + idv.range = item.range.data; + idv.magic_bonus = item.magic_bonus.data; + idv.sex = item.gender.data; + idv.equip = item.loc.data; + idv.wlv = item.wlv.data; + idv.elv = item.elv.data; + idv.look = item.view.data; + + idv.use_script = compile_script(item.use_script, true); + idv.equip_script = compile_script(item.equip_script, true); + + Borrowed<struct item_data> id = itemdb_search(idv.nameid); + *id = std::move(idv); } - else if (id->value_buy == 0) - { - id->value_buy = id->value_sell * 2; - } - else if (id->value_sell == 0) - { - id->value_sell = id->value_buy / 2; - } - - id->use_script = nullptr; - id->equip_script = nullptr; - - if (!tail_part) - continue; - id->use_script = parse_script(tail_part, lines, true); - - tail_part = tail_part.xislice_t(std::find(tail_part.begin() + 1, tail_part.end(), '{')); - if (!tail_part) - continue; - id->equip_script = parse_script(tail_part, lines, true); } - PRINTF("read %s done (count=%d)\n"_fmt, filename, ln); } - - return rv; } /*========================================== diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp index eeeb989..26c1a19 100644 --- a/src/map/magic-v2.cpp +++ b/src/map/magic-v2.cpp @@ -36,6 +36,8 @@ #include "../sexpr/parser.hpp" +#include "../ast/script.hpp" + #include "itemdb.hpp" #include "magic-expr.hpp" #include "magic-interpreter.hpp" @@ -785,7 +787,20 @@ namespace magic_v2 if (s._list[1]._type != sexpr::STRING) return fail(s._list[1], "not string"_s); ZString body = s._list[1]._str; - std::unique_ptr<const ScriptBuffer> script = parse_script(body, s._list[1]._span.begin.line, true); + auto begin = s._list[1]._span.begin; + io::LineCharReader lr(io::from_string, begin.filename, body, begin.line, begin.column); + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.implicit_end = true; + opt.no_event = true; + auto code_res = ast::script::parse_script_body(lr, opt); + if (code_res.get_failure()) + { + PRINTF("%s\n"_fmt, code_res.get_failure()); + } + auto code = TRY_UNWRAP(code_res.get_success(), + return fail(s._list[1], "script does not compile"_s)); + std::unique_ptr<const ScriptBuffer> script = compile_script(code, true); if (!script) return fail(s._list[1], "script does not compile"_s); EffectScript e; diff --git a/src/map/map.cpp b/src/map/map.cpp index 9ae865f..b5f08ea 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -1295,7 +1295,7 @@ bool map_readmap(map_local *m, size_t num, MapName fn) int xs = m->xs = gat_v[0] | gat_v[1] << 8; int ys = m->ys = gat_v[2] | gat_v[3] << 8; - PRINTF("\rLoading Maps [%zu/%zu]: %-30s (%i, %i)"_fmt, + PRINTF("Loading Maps [%zu/%zu]: %-30s (%i, %i)\r"_fmt, num, maps_db.size(), fn, xs, ys); fflush(stdout); @@ -1348,7 +1348,7 @@ bool map_readallmap(void) } } - PRINTF("\rMaps Loaded: %-65zu\n"_fmt, maps_db.size()); + PRINTF("Maps Loaded: %-65zu\n"_fmt, maps_db.size()); if (maps_removed) { PRINTF("Cowardly refusing to keep going after removing %d maps.\n"_fmt, diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp index 81a5ba2..390f551 100644 --- a/src/map/npc-parse.cpp +++ b/src/map/npc-parse.cpp @@ -32,12 +32,14 @@ #include "../io/cxxstdio.hpp" #include "../io/extract.hpp" -#include "../io/read.hpp" +#include "../io/line.hpp" #include "../mmo/config_parse.hpp" #include "../high/extract_mmo.hpp" +#include "../ast/npc.hpp" + #include "battle.hpp" #include "clif.hpp" #include "itemdb.hpp" @@ -57,24 +59,12 @@ std::list<AString> npc_srcs; static int npc_warp, npc_shop, npc_script, npc_mob; -// -// 初期化関係 -// - -/*========================================== - * 読み込むnpcファイルのクリア - *------------------------------------------ - */ static void npc_clearsrcfile(void) { npc_srcs.clear(); } -/*========================================== - * 読み込むnpcファイルの追加 - *------------------------------------------ - */ void npc_addsrcfile(AString name) { if (name == "clear"_s) @@ -86,10 +76,6 @@ void npc_addsrcfile(AString name) npc_srcs.push_back(name); } -/*========================================== - * 読み込むnpcファイルの削除 - *------------------------------------------ - */ void npc_delsrcfile(XString name) { if (name == "all"_s) @@ -145,26 +131,19 @@ void register_npc_name(dumb_ptr<npc_data> nd) npcs_by_name.put(nd->name, nd); } -/*========================================== - * warp行解析 - *------------------------------------------ - */ -int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) +// extern for atcommand @addwarp +bool npc_load_warp(ast::npc::Warp& warp) { - int x, y, xs, ys, to_x, to_y; - int i, j; - MapName mapname, to_mapname; - dumb_ptr<npc_data_warp> nd; + MapName mapname = warp.m.data; + int x = warp.x.data, y = warp.y.data; - if (!extract(w1, record<','>(&mapname, &x, &y)) || - !extract(w4, record<','>(&xs, &ys, &to_mapname, &to_x, &to_y))) - { - PRINTF("bad warp line : %s\n"_fmt, w3); - return 1; - } + int xs = warp.xs.data, ys = warp.ys.data; + MapName to_mapname = warp.to_m.data; + int to_x = warp.to_x.data, to_y = warp.to_y.data; - P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return 1); + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); + dumb_ptr<npc_data_warp> nd; nd.new_(); nd->bl_id = npc_get_new_npc_id(); nd->n = map_addnpc(m, nd); @@ -175,7 +154,7 @@ int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) nd->bl_y = y; nd->dir = DIR::S; nd->flag = 0; - nd->name = w3; + nd->name = warp.name.data; if (!battle_config.warp_point_debug) nd->npc_class = WARP_CLASS; @@ -187,16 +166,14 @@ int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) nd->opt2 = Opt2::ZERO; nd->opt3 = Opt3::ZERO; nd->warp.name = to_mapname; - xs += 2; - ys += 2; nd->warp.x = to_x; nd->warp.y = to_y; nd->warp.xs = xs; nd->warp.ys = ys; - for (i = 0; i < ys; i++) + for (int i = 0; i < ys; i++) { - for (j = 0; j < xs; j++) + for (int j = 0; j < xs; j++) { int x_lo = x - xs / 2; int y_lo = y - ys / 2; @@ -216,74 +193,42 @@ int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) clif_spawnnpc(nd); register_npc_name(nd); - return 0; -} - -static -bool extract(XString xs, npc_item_list *itv) -{ - XString name_or_id; - if (!extract(xs, record<':'>(&name_or_id, &itv->value))) - return false; - - P<struct item_data> id = ((extract(name_or_id, &itv->nameid) && itv->nameid) - ? ({ - P<struct item_data> id_ = itemdb_search(itv->nameid); - id_; - }) - : ({ - P<struct item_data> id_ = TRY_UNWRAP(itemdb_searchname(name_or_id.rstrip()), return false); - itv->nameid = id_->nameid; - id_; - })); - - if (itv->value < 0) - { - itv->value = id->value_buy * abs(itv->value); - } return true; } -/*========================================== - * shop行解析 - *------------------------------------------ - */ static -int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a) +bool npc_load_shop(ast::npc::Shop& shop) { - int x, y; - DIR dir; - MapName mapname; + MapName mapname = shop.m.data; + int x = shop.x.data, y = shop.y.data; + DIR dir = shop.d.data; dumb_ptr<npc_data_shop> nd; - ZString::iterator w4comma; - Species npc_class; - - int dir_; // TODO use enum directly in extract - if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) - || dir_ < 0 || dir_ >= 8 - || (w4comma = std::find(w4a.begin(), w4a.end(), ',')) == w4a.end() - || !extract(w4a.xislice_h(w4comma), &npc_class)) - { - PRINTF("bad shop line : %s\n"_fmt, w3); - return 1; - } - dir = static_cast<DIR>(dir_); - P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return 1); + Species npc_class = shop.npc_class.data; - nd.new_(); - ZString w4b = w4a.xislice_t(w4comma + 1); - - if (!extract(w4b, vrec<','>(&nd->shop_items))) - { - PRINTF("bad shop items : %s\n"_fmt, w3); - PRINTF(" somewhere --> %s\n"_fmt, w4b); - nd->shop_items.clear(); - } + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); - if (nd->shop_items.empty()) - { - nd.delete_(); - return 1; + nd.new_(); + nd->shop_items.reserve(shop.items.data.size()); + for (auto& it : shop.items.data) + { + nd->shop_items.emplace_back(); + auto& back = nd->shop_items.back(); + P<item_data> id = ((extract(it.data.name.data, &back.nameid) && back.nameid) + ? ({ + P<item_data> id_ = TRY_UNWRAP(itemdb_exists(back.nameid), { it.data.name.span.error("No item with this numerical id"_s); return false; }); + id_; + }) + : ({ + P<item_data> id_ = TRY_UNWRAP(itemdb_searchname(XString(it.data.name.data)), { it.data.name.span.error("No item with this name"_s); return false; }); + back.nameid = id_->nameid; + id_; + })); + + if (it.data.value_multiply) + { + back.value = id->value_buy * back.value; + } + return true; } nd->bl_prev = nd->bl_next = nullptr; @@ -293,7 +238,7 @@ int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a) nd->bl_id = npc_get_new_npc_id(); nd->dir = dir; nd->flag = 0; - nd->name = w3; + nd->name = shop.name.data; nd->npc_class = npc_class; nd->speed = 200_ms; nd->option = Opt0::ZERO; @@ -309,164 +254,289 @@ int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a) clif_spawnnpc(nd); register_npc_name(nd); - return 0; + return true; } -/*========================================== - * NPCのラベルデータコンバート - *------------------------------------------ - */ static -void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr<npc_data_script> nd) +bool npc_load_monster(ast::npc::Monster& monster) { - nullpo_retv(nd); + MapName mapname = monster.m.data; + int x = monster.x.data, y = monster.y.data; + int xs = monster.xs.data, ys = monster.ys.data; - struct npc_label_list eln {}; - eln.name = lname; - eln.pos = pos; - nd->scr.label_listv.push_back(std::move(eln)); + Species mob_class = monster.mob_class.data; + int num = monster.num.data; + interval_t delay1 = monster.delay1.data; + interval_t delay2 = monster.delay2.data; + NpcEvent eventname = monster.event.data; + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); + + if (num > 1 && battle_config.mob_count_rate != 100) + { + num = num * battle_config.mob_count_rate / 100; + if (num < 1) + num = 1; + } + + for (int i = 0; i < num; i++) + { + dumb_ptr<mob_data> md; + md.new_(); + + md->bl_prev = nullptr; + md->bl_next = nullptr; + md->bl_m = m; + md->bl_x = x; + md->bl_y = y; + MobName expected = get_mob_db(mob_class).jname; + if (monster.name.data != expected) + { + monster.name.span.warning(STRPRINTF("Visible label/jname should match: %s"_fmt, expected)); + } + if (monster.name.data == ENGLISH_NAME) + md->name = get_mob_db(mob_class).name; + else if (monster.name.data == JAPANESE_NAME) + md->name = get_mob_db(mob_class).jname; + else + md->name = monster.name.data; + + md->n = i; + md->mob_class = mob_class; + md->bl_id = npc_get_new_npc_id(); + md->spawn.m = m; + md->spawn.x0 = x; + md->spawn.y0 = y; + md->spawn.xs = xs; + md->spawn.ys = ys; + md->spawn.delay1 = delay1; + md->spawn.delay2 = delay2; + + really_memzero_this(&md->state); + // md->timer = nullptr; + md->target_id = BlockId(); + md->attacked_id = BlockId(); + + md->lootitemv.clear(); + + md->npc_event = eventname; + + md->bl_type = BL::MOB; + map_addiddb(md); + mob_spawn(md->bl_id); + + npc_mob++; + } + + return true; } -/*========================================== - * script行解析 - *------------------------------------------ - */ static -int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, - XString first_line, io::ReadFile& fp, int *lines) +bool npc_load_mapflag(ast::npc::MapFlag& mapflag) { - int x, y; - DIR dir = DIR::S; - int xs = 0, ys = 0; // [Valaris] thanks to fov - Species npc_class; - MapName mapname; - std::unique_ptr<const ScriptBuffer> script = nullptr; - dumb_ptr<npc_data_script> nd; - int evflag = 0; + MapName mapname = mapflag.m.data; + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); - P<map_local> m = borrow(undefined_gat); - if (w1 == "-"_s) + MapFlag mf; + if (!extract(mapflag.name.data, &mf)) { - x = 0; - y = 0; - m = borrow(undefined_gat); + mapflag.name.span.error("No such mapflag"_s); + return false; } - else + + if (battle_config.pk_mode && mf == MapFlag::NOPVP) { - int dir_; // TODO use enum directly in extract - if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) - || dir_ < 0 || dir_ >= 8 - || (w2 == "script"_s && !w4.contains(','))) + if (mapflag.vec_extra.data.size()) { - PRINTF("bad script line : %s\n"_fmt, w3); - return 1; + mapflag.vec_extra.span.error("No extra argument expected for mapflag 'nopvp'"_s); + return false; } - dir = static_cast<DIR>(dir_); - m = map_mapname2mapid(mapname).copy_or(borrow(undefined_gat)); + m->flag.set(MapFlag::NOPVP, 1); + m->flag.set(MapFlag::PVP, 0); + return true; } - if (w2 == "script"_s) + MapName savemap; + int savex, savey; + + if (mf == MapFlag::NOSAVE) { - // may be empty - MString srcbuf; - srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); - // Note: it was a bug that this was missing. I think. - int startline = *lines; - - // while (!srcbuf.rstrip().endswith('}')) - while (true) + if (mapflag.vec_extra.data.size() == 3 + && extract(mapflag.vec_extra.data[0].data, &savemap) + && extract(mapflag.vec_extra.data[1].data, &savex) + && extract(mapflag.vec_extra.data[2].data, &savey) + && map_mapname2mapid(savemap).is_some()) { - auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); - if (it != srcbuf.rend() && *it == '}') - break; - - AString line; - if (!fp.getline(line)) - // eof - break; - (*lines)++; - if (!srcbuf) - { - // may be a no-op - srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); - // safe to execute more than once - // But will usually only happen once - startline = *lines; - } - else - srcbuf += line; - srcbuf += '\n'; + m->save.map_ = savemap; + m->save.x = savex; + m->save.y = savey; + } + else + { + mapflag.vec_extra.span.error("Unable to extract nosave savepoint"_s); + return false; + } + } + else if (mf == MapFlag::RESAVE) + { + if (mapflag.vec_extra.data.size() == 3 + && extract(mapflag.vec_extra.data[0].data, &savemap) + && extract(mapflag.vec_extra.data[1].data, &savex) + && extract(mapflag.vec_extra.data[2].data, &savey) + && map_mapname2mapid(savemap).is_some()) + { + m->resave.map_ = savemap; + m->resave.x = savex; + m->resave.y = savey; + } + else + { + mapflag.vec_extra.span.error("Unable to extract resave savepoint"_s); + return false; } - script = parse_script(AString(srcbuf), startline, false); - if (script == nullptr) - // script parse error? - return 1; } else { - assert(0 && "duplicate() is no longer supported!\n"_s); - return 0; + if (mapflag.vec_extra.data.size()) + { + mapflag.vec_extra.span.error("No extra argument expected for mapflag"_s); + return false; + } } + m->flag.set(mf, true); + + return true; +} + +static +void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr<npc_data_script> nd) +{ + nullpo_retv(nd); + + struct npc_label_list eln {}; + eln.name = lname; + eln.pos = pos; + nd->scr.label_listv.push_back(std::move(eln)); +} + +static +bool npc_load_script_function(ast::script::ScriptBody& body, ast::npc::ScriptFunction& script_function) +{ + std::unique_ptr<const ScriptBuffer> script = compile_script(body, false); + if (script == nullptr) + return false; + + userfunc_db.put(script_function.name.data, std::move(script)); + + return true; +} + +static +bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& script_none) +{ + std::unique_ptr<const ScriptBuffer> script = compile_script(body, false); + if (script == nullptr) + return false; + dumb_ptr<npc_data_script> nd; nd.new_(); - if (m == borrow(undefined_gat)) - { - } - else if (extract(w4, record<','>(&npc_class, &xs, &ys))) - { - if (xs >= 0) - xs = xs * 2 + 1; - if (ys >= 0) - ys = ys * 2 + 1; + nd->name = script_none.name.data; - if (npc_class != NEGATIVE_SPECIES) - { + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = borrow(undefined_gat); + nd->bl_x = 0; + nd->bl_y = 0; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = DIR::S; + nd->flag = 0; + nd->npc_class = NEGATIVE_SPECIES; + nd->speed = 200_ms; + nd->scr.script = std::move(script); + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; - for (int i = 0; i < ys; i++) - { - for (int j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - } + npc_script++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SCRIPT; - nd->scr.xs = xs; - nd->scr.ys = ys; - } - else - { - XString w4x = w4; - if (w4x.endswith(',')) - w4x = w4x.xrslice_h(1); - if (!extract(w4x, &npc_class)) - abort(); - nd->scr.xs = 0; - nd->scr.ys = 0; - } + register_npc_name(nd); - if (npc_class == NEGATIVE_SPECIES && m != borrow(undefined_gat)) - { - evflag = 1; - } + for (auto& pair : scriptlabel_db) + npc_convertlabel_db(pair.first, pair.second, nd); - if (w3.contains(':')) + for (npc_label_list& el : nd->scr.label_listv) { - assert(false && "feature removed"_s); - abort(); + ScriptLabel lname = el.name; + int pos = el.pos; + + if (lname.startswith("On"_s)) + { + struct event_data ev {}; + ev.nd = nd; + ev.pos = pos; + NpcEvent buf; + buf.npc = nd->name; + buf.label = lname; + ev_db.insert(buf, ev); + } } + for (npc_label_list& el : nd->scr.label_listv) { - nd->name = w3; + int t_ = 0; + ScriptLabel lname = el.name; + int pos = el.pos; + if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0) + { + interval_t t = static_cast<interval_t>(t_); + + npc_timerevent_list tel {}; + tel.timer = t; + tel.pos = pos; + + auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel, + [](const npc_timerevent_list& l, const npc_timerevent_list& r) + { + return l.timer < r.timer; + } + ); + assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer); + + nd->scr.timer_eventv.insert(it, std::move(tel)); + } } + // The counter starts stopped with 0 ticks, which is the first event, + // unless there is none, in which case begin == end. + nd->scr.timer = interval_t::zero(); + nd->scr.next_event = nd->scr.timer_eventv.begin(); + // nd->scr.timerid = nullptr; + + return true; +} + +static +bool npc_load_script_map_none(ast::script::ScriptBody& body, ast::npc::ScriptMapNone& script_map_none) +{ + MapName mapname = script_map_none.m.data; + int x = script_map_none.x.data, y = script_map_none.y.data; + DIR dir = script_map_none.d.data; + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), + { + script_map_none.m.span.error("No such map"_s); + return false; + }); + + std::unique_ptr<const ScriptBuffer> script = compile_script(body, false); + if (script == nullptr) + return false; + + dumb_ptr<npc_data_script> nd; + nd.new_(); + + nd->name = script_map_none.name.data; nd->bl_prev = nd->bl_next = nullptr; nd->bl_m = m; @@ -475,7 +545,7 @@ int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, nd->bl_id = npc_get_new_npc_id(); nd->dir = dir; nd->flag = 0; - nd->npc_class = npc_class; + nd->npc_class = NEGATIVE_SPECIES; nd->speed = 200_ms; nd->scr.script = std::move(script); nd->option = Opt0::ZERO; @@ -486,24 +556,20 @@ int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, npc_script++; nd->bl_type = BL::NPC; nd->npc_subtype = NpcSubtype::SCRIPT; - if (m != borrow(undefined_gat)) - { - nd->n = map_addnpc(m, nd); - map_addblock(nd); - if (evflag) - { - struct event_data ev {}; - ev.nd = nd; - ev.pos = 0; - NpcEvent npcev; - npcev.npc = nd->name; - npcev.label = ScriptLabel(); - ev_db.insert(npcev, ev); - } - else - clif_spawnnpc(nd); + nd->n = map_addnpc(m, nd); + map_addblock(nd); + + { + struct event_data ev {}; + ev.nd = nd; + ev.pos = 0; + NpcEvent npcev; + npcev.npc = nd->name; + npcev.label = ScriptLabel(); + ev_db.insert(npcev, ev); } + register_npc_name(nd); for (auto& pair : scriptlabel_db) @@ -526,8 +592,6 @@ int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, } } - //----------------------------------------- - // ラベルデータからタイマーイベント取り込み for (npc_label_list& el : nd->scr.label_listv) { int t_ = 0; @@ -558,185 +622,171 @@ int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, nd->scr.next_event = nd->scr.timer_eventv.begin(); // nd->scr.timerid = nullptr; - return 0; + return true; } -/*========================================== - * function行解析 - *------------------------------------------ - */ static -int npc_parse_function(XString, XString, XString w3, ZString, - XString first_line, io::ReadFile& fp, int *lines) +bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& script_map) { - MString srcbuf; - srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); - int startline = *lines; + MapName mapname = script_map.m.data; + int x = script_map.x.data, y = script_map.y.data; + DIR dir = script_map.d.data; + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), + { + script_map.m.span.error("No such map"_s); + return false; + }); + + std::unique_ptr<const ScriptBuffer> script = compile_script(body, false); + if (script == nullptr) + return false; + + + dumb_ptr<npc_data_script> nd; + nd.new_(); + + Species npc_class = script_map.npc_class.data; + int xs = script_map.xs.data, ys = script_map.ys.data; - while (true) { - auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); - if (it != srcbuf.rend() && *it == '}') - break; - - AString line; - if (!fp.getline(line)) - break; - (*lines)++; - if (!srcbuf) + for (int i = 0; i < ys; i++) { - srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); - startline = *lines; + for (int j = 0; j < xs; j++) + { + int x_lo = x - xs / 2; + int y_lo = y - ys / 2; + int xc = x_lo + j; + int yc = y_lo + i; + MapCell t = map_getcell(m, xc, yc); + if (bool(t & MapCell::UNWALKABLE)) + continue; + map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); + } } - else - srcbuf += line; - srcbuf += '\n'; - } - std::unique_ptr<const ScriptBuffer> script = parse_script(AString(srcbuf), startline, false); - if (script == nullptr) - { - // script parse error? - return 1; + + nd->scr.xs = xs; + nd->scr.ys = ys; } - userfunc_db.put(w3, std::move(script)); + nd->name = script_map.name.data; - return 0; -} + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + nd->npc_class = npc_class; + nd->speed = 200_ms; + nd->scr.script = std::move(script); + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; -/*========================================== - * mob行解析 - *------------------------------------------ - */ -static -int npc_parse_mob(XString w1, XString, MobName w3, ZString w4) -{ - int x, y, xs, ys, num; - Species mob_class; - int i; - MapName mapname; - NpcEvent eventname; - dumb_ptr<mob_data> md; - - xs = ys = 0; - int delay1_ = 0, delay2_ = 0; - if (!extract(w1, record<',', 3>(&mapname, &x, &y, &xs, &ys)) || - !extract(w4, record<',', 2>(&mob_class, &num, &delay1_, &delay2_, &eventname))) - { - PRINTF("bad monster line : %s\n"_fmt, w3); - return 1; - } - interval_t delay1 = std::chrono::milliseconds(delay1_); - interval_t delay2 = std::chrono::milliseconds(delay2_); + npc_script++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SCRIPT; - P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return 1); + nd->n = map_addnpc(m, nd); + map_addblock(nd); - if (num > 1 && battle_config.mob_count_rate != 100) - { - if ((num = num * battle_config.mob_count_rate / 100) < 1) - num = 1; - } + clif_spawnnpc(nd); - for (i = 0; i < num; i++) - { - md.new_(); + register_npc_name(nd); - md->bl_prev = nullptr; - md->bl_next = nullptr; - md->bl_m = m; - md->bl_x = x; - md->bl_y = y; - if (w3 == ENGLISH_NAME) - md->name = get_mob_db(mob_class).name; - else if (w3 == JAPANESE_NAME) - md->name = get_mob_db(mob_class).jname; - else - md->name = w3; + for (auto& pair : scriptlabel_db) + npc_convertlabel_db(pair.first, pair.second, nd); - md->n = i; - md->mob_class = mob_class; - md->bl_id = npc_get_new_npc_id(); - md->spawn.m = m; - md->spawn.x0 = x; - md->spawn.y0 = y; - md->spawn.xs = xs; - md->spawn.ys = ys; - md->spawn.delay1 = delay1; - md->spawn.delay2 = delay2; + for (npc_label_list& el : nd->scr.label_listv) + { + ScriptLabel lname = el.name; + int pos = el.pos; - really_memzero_this(&md->state); - // md->timer = nullptr; - md->target_id = BlockId(); - md->attacked_id = BlockId(); + if (lname.startswith("On"_s)) + { + struct event_data ev {}; + ev.nd = nd; + ev.pos = pos; + NpcEvent buf; + buf.npc = nd->name; + buf.label = lname; + ev_db.insert(buf, ev); + } + } - md->lootitemv.clear(); + for (npc_label_list& el : nd->scr.label_listv) + { + int t_ = 0; + ScriptLabel lname = el.name; + int pos = el.pos; + if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0) + { + interval_t t = static_cast<interval_t>(t_); - md->npc_event = eventname; + npc_timerevent_list tel {}; + tel.timer = t; + tel.pos = pos; - md->bl_type = BL::MOB; - map_addiddb(md); - mob_spawn(md->bl_id); + auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel, + [](const npc_timerevent_list& l, const npc_timerevent_list& r) + { + return l.timer < r.timer; + } + ); + assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer); - npc_mob++; + nd->scr.timer_eventv.insert(it, std::move(tel)); + } } + // The counter starts stopped with 0 ticks, which is the first event, + // unless there is none, in which case begin == end. + nd->scr.timer = interval_t::zero(); + nd->scr.next_event = nd->scr.timer_eventv.begin(); + // nd->scr.timerid = nullptr; - return 0; + return true; } -/*========================================== - * マップフラグ行の解析 - *------------------------------------------ - */ static -int npc_parse_mapflag(XString w1, XString, XString w3, ZString w4) +bool npc_load_script_any(ast::npc::Script *script) { - MapName mapname, savemap; - int savex, savey; - - mapname = stringish<MapName>(w1); - if (!mapname) - return 1; - - P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return 1); - - MapFlag mf; - if (!extract(w3, &mf)) - return 1; - - if (battle_config.pk_mode && mf == MapFlag::NOPVP) - { - m->flag.set(MapFlag::NOPVP, 1); - m->flag.set(MapFlag::PVP, 0); - return 0; - } - - if (mf == MapFlag::NOSAVE) + MATCH (*script) { - if (w4 == "SavePoint"_s) + CASE (ast::npc::ScriptFunction&, script_function) { - m->save.map_ = stringish<MapName>("SavePoint"_s); - m->save.x = -1; - m->save.y = -1; + return npc_load_script_function(script->body, script_function); } - else if (extract(w4, record<','>(&savemap, &savex, &savey))) + CASE (ast::npc::ScriptNone&, script_none) { - m->save.map_ = savemap; - m->save.x = savex; - m->save.y = savey; + return npc_load_script_none(script->body, script_none); } - } - if (mf == MapFlag::RESAVE) - { - if (extract(w4, record<','>(&savemap, &savex, &savey))) + CASE (ast::npc::ScriptMapNone&, script_map_none) { - m->resave.map_ = savemap; - m->resave.x = savex; - m->resave.y = savey; + auto& mapname = script_map_none.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_script_map_none(script->body, script_map_none); + } + CASE (ast::npc::ScriptMap&, script_map) + { + auto& mapname = script_map.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_script_map(script->body, script_map); } } - m->flag.set(mf, true); - - return 0; + abort(); } dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y, @@ -766,104 +816,103 @@ dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y, return retval; } -/*========================================== - * npc初期化 - *------------------------------------------ - */ -bool do_init_npc(void) +static +bool load_one_npc(io::LineCharReader& fp, bool& done) { - bool rv = true; + auto res = TRY_UNWRAP(ast::npc::parse_top(fp), { done = true; return true; }); + if (res.get_failure()) + PRINTF("%s\n"_fmt, res.get_failure()); + ast::npc::TopLevel tl = TRY_UNWRAP(std::move(res.get_success()), return false); - for (; !npc_srcs.empty(); npc_srcs.pop_front()) + MATCH (tl) { - AString nsl = npc_srcs.front(); - io::ReadFile fp(nsl); - if (!fp.is_open()) + CASE (ast::npc::Comment&, c) { - PRINTF("file not found : %s\n"_fmt, nsl); - rv = false; - continue; + return true; } - PRINTF("\rLoading NPCs [%d]: %-54s"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), - nsl); - int lines = 0; - AString zline; - while (fp.getline(zline)) + CASE (ast::npc::Warp&, warp) { - XString w1, w2, w3, w4x; - ZString w4z; - lines++; - - if (is_comment(zline)) - continue; - - if (!extract(zline, record<'|', 3>(&w1, &w2, &w3, &w4x)) || !w1 || !w2 || !w3) - { - FPRINTF(stderr, "%s:%d: Broken script line: %s\n"_fmt, nsl, lines, zline); - rv = false; - continue; - } - if (&*w4x.end() == &*zline.end()) - { - w4z = zline.xrslice_t(w4x.size()); - } - assert(bool(w4x) == bool(w4z)); - - if (w1 != "-"_s && w1 != "function"_s) - { - auto comma = std::find(w1.begin(), w1.end(), ','); - MapName mapname = stringish<MapName>(w1.xislice_h(comma)); - Option<P<map_local>> m = map_mapname2mapid(mapname); - if (m.is_none()) - { - // "mapname" is not assigned to this server - FPRINTF(stderr, "%s:%d: Map not found: %s\n"_fmt, nsl, lines, mapname); - rv = false; - continue; - } - } - if (w2 == "warp"_s) - { - NpcName npcname = stringish<NpcName>(w3); - rv &= !npc_parse_warp(w1, w2, npcname, w4z); - } - else if (w2 == "shop"_s) + auto& mapname = warp.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) { - NpcName npcname = stringish<NpcName>(w3); - rv &= !npc_parse_shop(w1, w2, npcname, w4z); + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; } - else if (w2 == "script"_s) - { - if (w1 == "function"_s) - { - rv &= !npc_parse_function(w1, w2, w3, w4z, - w4x, fp, &lines); - } - else - { - NpcName npcname = stringish<NpcName>(w3); - rv &= !npc_parse_script(w1, w2, npcname, w4z, - w4x, fp, &lines); - } - } - else if (w2 == "monster"_s) + return npc_load_warp(warp); + } + CASE (ast::npc::Shop&, shop) + { + auto& mapname = shop.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) { - MobName mobname = stringish<MobName>(w3); - rv &= !npc_parse_mob(w1, w2, mobname, w4z); + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; } - else if (w2 == "mapflag"_s) + return npc_load_shop(shop); + } + CASE (ast::npc::Monster&, monster) + { + auto& mapname = monster.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) { - rv &= !npc_parse_mapflag(w1, w2, w3, w4z); + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; } - else + return npc_load_monster(monster); + } + CASE (ast::npc::MapFlag&, mapflag) + { + auto& mapname = mapflag.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) { - PRINTF("odd script line: %s\n"_fmt, zline); - script_errors++; + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; } + return npc_load_mapflag(mapflag); } - fflush(stdout); + CASE (ast::npc::Script&, script) + { + return npc_load_script_any(&script); + } + } + abort(); +} + +static +bool load_npc_file(ZString nsl) +{ + io::LineCharReader fp(nsl); + if (!fp.is_open()) + { + PRINTF("file not found : %s\n"_fmt, nsl); + return false; + } + PRINTF("Loading NPCs [%d]: %-54s\r"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), + nsl); + + bool done = false; + while (!done) + { + if (!load_one_npc(fp, done)) + return false; + } + return true; +} + +bool do_init_npc(void) +{ + bool rv = true; + + for (; !npc_srcs.empty(); npc_srcs.pop_front()) + { + AString nsl = npc_srcs.front(); + rv &= load_npc_file(nsl); } - PRINTF("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt, + PRINTF("NPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s); if (script_errors) diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp index a9cf300..902a214 100644 --- a/src/map/npc-parse.hpp +++ b/src/map/npc-parse.hpp @@ -25,7 +25,7 @@ namespace tmwa { -int npc_parse_warp(XString w1, XString, NpcName w3, XString w4); +bool npc_load_warp(ast::npc::Warp& warp); /** * Spawns and installs a talk-only NPC diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp index 7956831..85e29a5 100644 --- a/src/map/script-parse.cpp +++ b/src/map/script-parse.cpp @@ -33,6 +33,8 @@ #include "../mmo/cxxstdio_enums.hpp" +#include "../ast/script.hpp" + #include "map.t.hpp" #include "script-buffer.hpp" #include "script-call.hpp" @@ -692,10 +694,10 @@ void add_builtin_functions(void) } } -std::unique_ptr<const ScriptBuffer> parse_script(ZString src, int line, bool implicit_end) +std::unique_ptr<const ScriptBuffer> compile_script(const ast::script::ScriptBody& body, bool implicit_end) { auto script_buf = make_unique<ScriptBuffer>(); - script_buf->parse_script(src, line, implicit_end); + script_buf->parse_script(body.braced_body, body.span.begin.line, implicit_end); return std::move(script_buf); } diff --git a/src/map/script-parse.hpp b/src/map/script-parse.hpp index d1f824f..b8b5012 100644 --- a/src/map/script-parse.hpp +++ b/src/map/script-parse.hpp @@ -27,7 +27,7 @@ namespace tmwa { -std::unique_ptr<const ScriptBuffer> parse_script(ZString, int, bool implicit_end); +std::unique_ptr<const ScriptBuffer> compile_script(const ast::script::ScriptBody& body, bool implicit_end); extern Map<ScriptLabel, int> scriptlabel_db; diff --git a/src/strings/rstring.cpp b/src/strings/rstring.cpp index 5675935..aaf0ba0 100644 --- a/src/strings/rstring.cpp +++ b/src/strings/rstring.cpp @@ -36,7 +36,7 @@ namespace tmwa { namespace strings { - RString::RString() + RString::RString() noexcept : u{.begin= ""}, maybe_end(u.begin) { } diff --git a/src/strings/rstring.hpp b/src/strings/rstring.hpp index ad44beb..62f74fa 100644 --- a/src/strings/rstring.hpp +++ b/src/strings/rstring.hpp @@ -48,7 +48,7 @@ namespace strings const char *maybe_end; public: - RString(); + RString() noexcept; RString(LString s); RString(const RString&); RString(RString&&); |