summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Longbons <b.r.longbons@gmail.com>2014-10-19 22:22:08 -0700
committerBen Longbons <b.r.longbons@gmail.com>2014-10-26 14:21:48 -0700
commit6800761863dd45b6055768febc6ace6a20120dc7 (patch)
tree73b416ca6507d9bb4f950252d55ead8e8cda34b5
parent0edf563dfc14a2b9db33a92f0eced28950bdf1aa (diff)
downloadtmwa-6800761863dd45b6055768febc6ace6a20120dc7.tar.gz
tmwa-6800761863dd45b6055768febc6ace6a20120dc7.tar.bz2
tmwa-6800761863dd45b6055768febc6ace6a20120dc7.tar.xz
tmwa-6800761863dd45b6055768febc6ace6a20120dc7.zip
New ast module for for npc parsing
Will eventually put most/all parsers there.
-rw-r--r--src/ast/fwd.hpp27
-rw-r--r--src/ast/npc.cpp594
-rw-r--r--src/ast/npc.hpp135
-rw-r--r--src/ast/npc_test.cpp569
-rw-r--r--src/ast/script.cpp71
-rw-r--r--src/ast/script.hpp94
-rw-r--r--src/compat/result.hpp78
-rw-r--r--src/compat/result_test.cpp79
-rw-r--r--src/io/line.hpp13
-rw-r--r--src/io/line_test.cpp16
-rw-r--r--src/io/read.cpp2
-rw-r--r--src/io/read_test.cpp6
-rw-r--r--src/io/write.cpp2
-rw-r--r--src/map/clif.cpp34
-rw-r--r--src/map/clif.hpp2
-rw-r--r--src/mmo/extract.cpp179
-rw-r--r--src/mmo/extract.hpp11
-rw-r--r--src/mmo/extract_test.cpp88
-rw-r--r--src/sexpr/lexer_test.cpp10
-rw-r--r--src/sexpr/parser_test.cpp3
-rw-r--r--src/strings/rstring.cpp19
-rw-r--r--src/strings/strings2_test.cpp9
22 files changed, 2025 insertions, 16 deletions
diff --git a/src/ast/fwd.hpp b/src/ast/fwd.hpp
new file mode 100644
index 0000000..77328c9
--- /dev/null
+++ b/src/ast/fwd.hpp
@@ -0,0 +1,27 @@
+#pragma once
+// ast/fwd.hpp - list of type names for new independent tmwa ast
+//
+// Copyright © 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 "../sanity.hpp"
+
+
+namespace tmwa
+{
+// meh, add more when I feel like it
+} // namespace tmwa
diff --git a/src/ast/npc.cpp b/src/ast/npc.cpp
new file mode 100644
index 0000000..362943c
--- /dev/null
+++ b/src/ast/npc.cpp
@@ -0,0 +1,594 @@
+#include "npc.hpp"
+// ast/npc.cpp - Structure of non-player characters (including mobs).
+//
+// Copyright © 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 "../io/cxxstdio.hpp"
+
+#include "../mmo/extract.hpp"
+
+#include "../map/clif.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace npc
+{
+namespace parse
+{
+ // 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; })
+
+ static
+ Result<Warp> parse_warp(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 3)
+ {
+ return Err(bits[0].span.error_str("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));
+ }
+ if (bits[3].data.size() != 5)
+ {
+ return Err(bits[3].span.error_str("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);
+ TRY_EXTRACT(bits[3].data[0], warp.xs);
+ TRY_EXTRACT(bits[3].data[1], warp.ys);
+ 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);
+ return Ok(std::move(warp));
+ }
+ static
+ Result<Shop> parse_shop(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 4)
+ {
+ return Err(bits[0].span.error_str("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));
+ }
+ if (bits[3].data.size() < 2)
+ {
+ return Err(bits[3].span.error_str("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);
+ TRY_EXTRACT(bits[0].data[3], shop.d);
+ shop.key_span = bits[1].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], shop.name);
+ TRY_EXTRACT(bits[3].data[0], shop.npc_class);
+ shop.items.span = bits[3].span;
+ shop.items.span.begin = bits[3].data[1].span.begin;
+ for (size_t i = 1; i < bits[3].data.size(); ++i)
+ {
+ shop.items.data.emplace_back();
+ auto& item = shop.items.data.back();
+ auto& data = bits[3].data[i];
+ 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)))
+ 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;
+ item.data.value.span = item.span;
+ item.data.value.span.begin.column = item.data.name.span.begin.column + name.size() + 1;
+ if (name.endswith(' '))
+ {
+ item.data.name.span.warning("Shop item has useless space before the colon"_s);
+ name = name.rstrip();
+ }
+ if (name.is_digit10())
+ {
+ 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 Ok(std::move(shop));
+ }
+ static
+ Result<Monster> parse_monster(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ 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));
+ }
+ 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));
+ }
+ 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));
+ }
+
+ 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);
+ if (bits[0].data.size() >= 5)
+ {
+ TRY_EXTRACT(bits[0].data[3], mob.xs);
+ TRY_EXTRACT(bits[0].data[4], mob.ys);
+ }
+ else
+ {
+ mob.xs.data = 0;
+ mob.xs.span = bits[0].data[2].span;
+ mob.xs.span.end.column++;
+ mob.xs.span.begin.column = mob.xs.span.end.column;
+ mob.ys.data = 0;
+ mob.ys.span = mob.xs.span;
+ }
+ mob.key_span = bits[1].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], mob.name);
+ TRY_EXTRACT(bits[3].data[0], mob.mob_class);
+ TRY_EXTRACT(bits[3].data[1], mob.num);
+ if (bits[3].data.size() >= 4)
+ {
+ static_assert(std::is_same<decltype(mob.delay1.data)::period, std::chrono::milliseconds::period>::value, "delay1 is milliseconds");
+ static_assert(std::is_same<decltype(mob.delay2.data)::period, std::chrono::milliseconds::period>::value, "delay2 is milliseconds");
+ if (bits[3].data[2].data.is_digit10())
+ bits[3].data[2].span.warning("delay1 lacks units; defaulting to ms"_s);
+ if (bits[3].data[3].data.is_digit10())
+ bits[3].data[3].span.warning("delay2 lacks units; defaulting to ms"_s);
+ TRY_EXTRACT(bits[3].data[2], mob.delay1);
+ TRY_EXTRACT(bits[3].data[3], mob.delay2);
+ if (bits[3].data.size() >= 5)
+ {
+ TRY_EXTRACT(bits[3].data[4], mob.event);
+ }
+ else
+ {
+ mob.event.data = NpcEvent();
+ mob.event.span = bits[3].data[3].span;
+ mob.event.span.end.column++;
+ mob.event.span.begin.column = mob.event.span.end.column;
+ }
+ }
+ else
+ {
+ mob.delay1.data = std::chrono::milliseconds::zero();
+ mob.delay1.span = bits[3].data[1].span;
+ mob.delay1.span.end.column++;
+ mob.delay1.span.begin.column = mob.delay1.span.end.column;
+ mob.delay2.data = std::chrono::milliseconds::zero();
+ mob.delay2.span = mob.delay1.span;
+ mob.event.data = NpcEvent();
+ mob.event.span = mob.delay1.span;
+ }
+ return Ok(std::move(mob));
+ }
+ static
+ Result<MapFlag> parse_mapflag(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 3 && bits.size() != 4)
+ {
+ return Err(span.error_str("expect 3 or 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 1)
+ {
+ return Err(bits[0].span.error_str("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));
+ }
+ }
+
+ 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);
+ }
+ 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;
+ }
+ 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)
+ {
+ // ScriptFunction: function|script|Fun Name{code}
+ if (bits.size() != 3)
+ {
+ return Err(span.error_str("expect 3 |component|s"_s));
+ }
+ assert(bits[0].data.size() == 1);
+ assert(bits[0].data[0].data == "function"_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));
+ }
+
+ 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
+ script_function.body = TRY(script::parse::parse_script_body(lr));
+ 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)
+ {
+ // ScriptNone: -|script|script name|-1{code}
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ assert(bits[0].data.size() == 1);
+ assert(bits[0].data[0].data == "-"_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));
+ }
+ 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));
+ }
+
+ 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
+ script_none.body = TRY(script::parse::parse_script_body(lr));
+ 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)
+ {
+ // ScriptMapNone: m,x,y,d|script|script name|-1{code}
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 4)
+ {
+ return Err(bits[0].span.error_str("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));
+ }
+ 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));
+ }
+
+ 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
+ script_map_none.body = TRY(script::parse::parse_script_body(lr));
+ 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)
+ {
+ // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code}
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 4)
+ {
+ return Err(bits[0].span.error_str("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));
+ }
+ if (bits[3].data.size() != 3)
+ {
+ return Err(bits[3].span.error_str("expect 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
+ script_map.body = TRY(script::parse::parse_script_body(lr));
+ 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)
+ {
+ std::unique_ptr<Script> rv;
+ // 4 cases:
+ // ScriptFunction: function|script|Fun Name{code}
+ // ScriptNone: -|script|script name|-1{code}
+ // ScriptMapNone: m,x,y,d|script|script name|-1{code}
+ // 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)));
+ }
+ else if (bits[0].data[0].data == "-"_s)
+ {
+ rv = make_unique<ScriptNone>(TRY(parse_script_none(span, bits, lr)));
+ }
+ else if (bits.size() >= 4 && bits[3].data[0].data == "-1"_s)
+ {
+ rv = make_unique<ScriptMapNone>(TRY(parse_script_map_none(span, bits, lr)));
+ }
+ else
+ {
+ rv = make_unique<ScriptMap>(TRY(parse_script_map(span, bits, lr)));
+ }
+ return Ok(std::move(rv));
+ }
+
+ /// Try to extract a top-level token
+ /// Return None at EOL, or Some(span)
+ /// This will alternate betweeen returning words and separators
+ static
+ Option<Spanned<RString>> lex(io::LineCharReader& lr, bool first)
+ {
+ io::LineChar c;
+ // at start of line, skip whitespace
+ if (first)
+ {
+ while (lr.get(c) && (/*c.ch() == ' ' ||*/ c.ch() == '\n'))
+ {
+ lr.adv();
+ }
+ }
+ // at end of file, end of line, or start of script, signal end
+ if (!lr.get(c) || c.ch() == '\n' || c.ch() == '{')
+ {
+ return None;
+ }
+ // separators are simple
+ if (c.ch() == '|' || c.ch() == ',')
+ {
+ Spanned<RString> bit;
+ bit.span.begin = c;
+ bit.span.end = c;
+ bit.data = RString(VString<1>(c.ch()));
+ lr.adv();
+ return Some(bit);
+ }
+ io::LineSpan span;
+ MString accum;
+ accum += c.ch();
+ span.begin = c;
+ span.end = c;
+ lr.adv();
+ if (c.ch() != '/')
+ first = false;
+
+ // if one-char token followed by an end-of-line or separator, stop
+ if (!lr.get(c) || c.ch() == '\n' || c.ch() == '{' || c.ch() == ',' || c.ch() == '|')
+ {
+ Spanned<RString> bit;
+ bit.span = span;
+ bit.data = RString(accum);
+ return Some(bit);
+ }
+
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+
+ // if first token on line, can get comment
+ if (first && c.ch() == '/')
+ {
+ while (lr.get(c) && c.ch() != '\n' && c.ch() != '{')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ Spanned<RString> bit;
+ bit.span = span;
+ bit.data = RString(accum);
+ return Some(bit);
+ }
+ // otherwise, collect until an end of line or separator
+ while (lr.get(c) && c.ch() != '\n' && c.ch() != '{' && c.ch() != ',' && c.ch() != '|')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ Spanned<RString> bit;
+ bit.span = span;
+ bit.data = RString(accum);
+ return Some(std::move(bit));
+ }
+
+ Result<std::unique_ptr<TopLevel>> parse_top(io::LineCharReader& in)
+ {
+ std::unique_ptr<TopLevel> rv;
+ Spanned<std::vector<Spanned<std::vector<Spanned<RString>>>>> bits;
+
+ {
+ Spanned<RString> mayc = TRY_UNWRAP(lex(in, true),
+ {
+ io::LineChar c;
+ if (in.get(c) && c.ch() == '{')
+ {
+ return Err(c.error_str("Unexpected script open"_s));
+ }
+ return Ok(std::move(rv));
+ });
+ 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));
+ }
+
+ if (mayc.data == "|"_s || mayc.data == ","_s)
+ return Err(mayc.span.error_str("Unexpected separator"_s));
+ bits.span = mayc.span;
+ bits.data.emplace_back();
+ bits.data.back().span = mayc.span;
+ bits.data.back().data.push_back(mayc);
+ }
+
+ while (true)
+ {
+ Spanned<RString> sep = TRY_UNWRAP(lex(in, false),
+ break);
+ if (sep.data == "|"_s)
+ {
+ bits.data.emplace_back();
+ }
+ else if (sep.data != ","_s)
+ {
+ return Err(sep.span.error_str("Expected separator"_s));
+ }
+
+ Spanned<RString> word = TRY_UNWRAP(lex(in, false),
+ return Err(sep.span.error_str("Expected word after separator"_s)));
+ if (bits.data.back().data.empty())
+ bits.data.back().span = word.span;
+ else
+ bits.data.back().span.end = word.span.end;
+ bits.span.end = word.span.end;
+ bits.data.back().data.push_back(std::move(word));
+ }
+
+ if (bits.data.size() < 2)
+ return Err(bits.span.error_str("Expected a line with |s in it"_s));
+ for (auto& bit : bits.data)
+ {
+ if (bit.data.empty())
+ return Err(bit.span.error_str("Empty components are not cool"_s));
+ }
+ if (bits.data[1].data.size() != 1)
+ return Err(bits.data[1].span.error_str("Expected a single word in type position"_s));
+ Spanned<RString>& w2 = bits.data[1].data[0];
+ if (w2.data == "warp"_s)
+ {
+ rv = make_unique<Warp>(TRY(parse_warp(bits.span, bits.data)));
+ }
+ else if (w2.data == "shop"_s)
+ {
+ rv = make_unique<Shop>(TRY(parse_shop(bits.span, bits.data)));
+ }
+ else if (w2.data == "monster"_s)
+ {
+ rv = make_unique<Monster>(TRY(parse_monster(bits.span, bits.data)));
+ }
+ else if (w2.data == "mapflag"_s)
+ {
+ rv = make_unique<MapFlag>(TRY(parse_mapflag(bits.span, bits.data)));
+ }
+ else if (w2.data == "script"_s)
+ {
+ rv = TRY_MOVE(parse_script_any(bits.span, bits.data, in));
+ }
+ else
+ {
+ return Err(w2.span.error_str("Unknown type"_s));
+ }
+ return Ok(std::move(rv));
+ }
+} // namespace parse
+} // namespace npc
+} // namespace tmwa
diff --git a/src/ast/npc.hpp b/src/ast/npc.hpp
new file mode 100644
index 0000000..e39a704
--- /dev/null
+++ b/src/ast/npc.hpp
@@ -0,0 +1,135 @@
+#pragma once
+// ast/npc.hpp - Structure of non-player characters (including mobs).
+//
+// Copyright © 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 "../compat/result.hpp"
+
+#include "../mmo/ids.hpp"
+#include "../mmo/strs.hpp"
+
+#include "../map/map.hpp"
+
+#include "script.hpp"
+
+
+namespace tmwa
+{
+namespace npc
+{
+namespace parse
+{
+ using io::Spanned;
+
+ struct TopLevel
+ {
+ io::LineSpan span;
+
+ virtual ~TopLevel();
+ };
+ struct Comment : TopLevel
+ {
+ RString comment;
+ };
+ struct Warp : TopLevel
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ io::LineSpan key_span;
+ Spanned<NpcName> name;
+ Spanned<unsigned> xs, ys;
+ Spanned<MapName> to_m;
+ Spanned<unsigned> to_x, to_y;
+ };
+ struct ShopItem
+ {
+ Spanned<ItemName> name;
+ Spanned<int> value;
+ };
+ struct Shop : TopLevel
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<DIR> d;
+ io::LineSpan key_span;
+ Spanned<NpcName> name;
+ Spanned<Species> npc_class;
+ Spanned<std::vector<Spanned<ShopItem>>> items;
+ };
+ struct Monster : TopLevel
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<unsigned> xs, ys;
+ io::LineSpan key_span;
+ Spanned<MobName> name;
+ Spanned<Species> mob_class;
+ Spanned<unsigned> num;
+ Spanned<interval_t> delay1, delay2;
+ Spanned<NpcEvent> event;
+ };
+ struct MapFlag : TopLevel
+ {
+ 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
+ script::parse::ScriptBody body;
+ };
+ struct ScriptFunction : Script
+ {
+ io::LineSpan key1_span;
+ Spanned<RString> name;
+ };
+ struct ScriptNone : Script
+ {
+ io::LineSpan key1_span;
+ Spanned<NpcName> name;
+ io::LineSpan key4_span;
+ };
+ struct ScriptMapNone : Script
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<DIR> d;
+ Spanned<NpcName> name;
+ io::LineSpan key4_span;
+ };
+ struct ScriptMap : Script
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<DIR> d;
+ Spanned<NpcName> name;
+ Spanned<Species> npc_class;
+ Spanned<unsigned> xs, ys;
+ };
+ // other Script subclasses elsewhere? (for item and magic scripts)
+
+ Result<std::unique_ptr<TopLevel>> parse_top(io::LineCharReader& in);
+} // namespace parse
+} // namespace npc
+} // namespace tmwa
diff --git a/src/ast/npc_test.cpp b/src/ast/npc_test.cpp
new file mode 100644
index 0000000..2697351
--- /dev/null
+++ b/src/ast/npc_test.cpp
@@ -0,0 +1,569 @@
+#include "npc.hpp"
+// ast/npc_test.cpp - Testsuite for npc parser.
+//
+// Copyright © 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 <gtest/gtest.h>
+
+#include "../tests/fdhack.hpp"
+
+//#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace npc
+{
+namespace parse
+{
+ static
+ io::FD string_pipe(ZString sz)
+ {
+ io::FD rfd, wfd;
+ if (-1 == io::FD::pipe(rfd, wfd))
+ return io::FD();
+ if (sz.size() != wfd.write(sz.c_str(), sz.size()))
+ {
+ rfd.close();
+ wfd.close();
+ return io::FD();
+ }
+ wfd.close();
+ return rfd;
+ }
+
+#define EXPECT_SPAN(span, bl,bc, el,ec) \
+ ({ \
+ EXPECT_EQ((span).begin.line, bl); \
+ EXPECT_EQ((span).begin.column, bc); \
+ EXPECT_EQ((span).end.line, el); \
+ EXPECT_EQ((span).end.column, ec); \
+ })
+
+ TEST(ast, eof)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ ""_s,
+ "\n"_s,
+ "\n\n"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_EQ(res.get_success(), Some(std::unique_ptr<TopLevel>(nullptr)));
+ }
+ }
+ TEST(ast, comment)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ //23456789
+ "// hello"_s,
+ "// hello\n "_s,
+ "// hello\nabc"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ 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_TRUE(p);
+ if (p)
+ {
+ EXPECT_EQ(p->comment, "// hello"_s);
+ }
+ }
+ }
+ TEST(ast, warp)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 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,
+ // no optional fields in warp
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,47);
+ auto p = dynamic_cast<Warp *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ 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->key_span, 1,13, 1,16);
+ 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_SPAN(p->ys.span, 1,33, 1,33);
+ EXPECT_EQ(p->ys.data, 4);
+ 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_SPAN(p->to_y.span, 1,47, 1,47);
+ EXPECT_EQ(p->to_y.data, 6);
+ }
+ }
+ }
+ TEST(ast, shop)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3 4 5
+ //2345678901234567890123456789012345678901234567890123456789
+ "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :8"_s,
+ "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :8\n"_s,
+ "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :8{"_s,
+ // no optional fields in shop
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,54);
+ auto p = dynamic_cast<Shop *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ 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,18);
+ EXPECT_SPAN(p->name.span, 1,20, 1,30);
+ EXPECT_EQ(p->name.data, stringish<NpcName>("Flower Shop"_s));
+ EXPECT_SPAN(p->npc_class.span, 1,32, 1,32);
+ EXPECT_EQ(p->npc_class.data, wrap<Species>(4));
+ EXPECT_SPAN(p->items.span, 1,34, 1,54);
+ EXPECT_EQ(p->items.data.size(), 3);
+ EXPECT_SPAN(p->items.data[0].span, 1,34, 1,36);
+ EXPECT_SPAN(p->items.data[0].data.name.span, 1,34, 1,34);
+ EXPECT_EQ(p->items.data[0].data.name.data, stringish<ItemName>("5"_s));
+ EXPECT_SPAN(p->items.data[0].data.value.span, 1,36, 1,36);
+ EXPECT_EQ(p->items.data[0].data.value.data, 6);
+ EXPECT_SPAN(p->items.data[1].span, 1,38, 1,44);
+ EXPECT_SPAN(p->items.data[1].data.name.span, 1,38, 1,42);
+ EXPECT_EQ(p->items.data[1].data.name.data, stringish<ItemName>("Named"_s));
+ EXPECT_SPAN(p->items.data[1].data.value.span, 1,44, 1,44);
+ EXPECT_EQ(p->items.data[1].data.value.data, 7);
+ EXPECT_SPAN(p->items.data[2].span, 1,46, 1,54);
+ EXPECT_SPAN(p->items.data[2].data.name.span, 1,46, 1,52);
+ EXPECT_EQ(p->items.data[2].data.name.data, stringish<ItemName>("Spaced"_s));
+ EXPECT_SPAN(p->items.data[2].data.value.span, 1,54, 1,54);
+ EXPECT_EQ(p->items.data[2].data.value.data, 8);
+ }
+ }
+ }
+ TEST(ast, monster)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3 4 5 6
+ //23456789012345678901234567890123456789012345678901234567890123456789
+ "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event"_s,
+ "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event\n"_s,
+ "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event{"_s,
+ "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000"_s,
+ "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000\n"_s,
+ "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000{"_s,
+ "nap.gat,1,20304|monster|Feeping Creature|506,700008000"_s,
+ "nap.gat,1,20304|monster|Feeping Creature|506,700008000\n"_s,
+ "nap.gat,1,20304|monster|Feeping Creature|506,700008000{"_s,
+ };
+ for (auto input : inputs)
+ {
+ bool first = input.startswith('m');
+ bool second = input.startswith('M');
+ bool third = input.startswith('n');
+ assert(first + second + third == 1);
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,first?65:54);
+ auto p = dynamic_cast<Monster *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ if (first)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ }
+ else if (second)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s));
+ }
+ else
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("nap"_s));
+ }
+ EXPECT_SPAN(p->x.span, 1,9, 1,9);
+ EXPECT_EQ(p->x.data, 1);
+ if (!third)
+ {
+ EXPECT_SPAN(p->y.span, 1,11, 1,11);
+ EXPECT_EQ(p->y.data, 2);
+ EXPECT_SPAN(p->xs.span, 1,13, 1,13);
+ EXPECT_EQ(p->xs.data, 3);
+ EXPECT_SPAN(p->ys.span, 1,15, 1,15);
+ EXPECT_EQ(p->ys.data, 4);
+ }
+ else
+ {
+ EXPECT_SPAN(p->y.span, 1,11, 1,15);
+ EXPECT_EQ(p->y.data, 20304);
+ EXPECT_SPAN(p->xs.span, 1,16, 1,16);
+ EXPECT_EQ(p->xs.data, 0);
+ EXPECT_SPAN(p->ys.span, 1,16, 1,16);
+ EXPECT_EQ(p->ys.data, 0);
+ }
+ EXPECT_SPAN(p->key_span, 1,17, 1,23);
+ EXPECT_SPAN(p->name.span, 1,25, 1,40);
+ EXPECT_EQ(p->name.data, stringish<MobName>("Feeping Creature"_s));
+ if (!third)
+ {
+ EXPECT_SPAN(p->mob_class.span, 1,42, 1,42);
+ EXPECT_EQ(p->mob_class.data, wrap<Species>(5));
+ EXPECT_SPAN(p->num.span, 1,44, 1,44);
+ EXPECT_EQ(p->num.data, 6);
+ EXPECT_SPAN(p->delay1.span, 1,46, 1,49);
+ EXPECT_EQ(p->delay1.data, 7_s);
+ EXPECT_SPAN(p->delay2.span, 1,51, 1,54);
+ EXPECT_EQ(p->delay2.data, 8_s);
+ }
+ else
+ {
+ EXPECT_SPAN(p->mob_class.span, 1,42, 1,44);
+ EXPECT_EQ(p->mob_class.data, wrap<Species>(506));
+ EXPECT_SPAN(p->num.span, 1,46, 1,54);
+ EXPECT_EQ(p->num.data, 700008000);
+ EXPECT_SPAN(p->delay1.span, 1,55, 1,55);
+ EXPECT_EQ(p->delay1.data, 0_s);
+ EXPECT_SPAN(p->delay2.span, 1,55, 1,55);
+ EXPECT_EQ(p->delay2.data, 0_s);
+ }
+ if (first)
+ {
+ EXPECT_SPAN(p->event.span, 1,56, 1,65);
+ EXPECT_EQ(p->event.data.npc, stringish<NpcName>("Npc"_s));
+ EXPECT_EQ(p->event.data.label, stringish<ScriptLabel>("Event"_s));
+ }
+ else
+ {
+ EXPECT_SPAN(p->event.span, 1,55, 1,55);
+ EXPECT_EQ(p->event.data.npc, NpcName());
+ EXPECT_EQ(p->event.data.label, ScriptLabel());
+ }
+ }
+ }
+ }
+ TEST(ast, mapflag)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "map.gat|mapflag|flagname"_s,
+ "map.gat|mapflag|flagname\n"_s,
+ "map.gat|mapflag|flagname{"_s,
+ "Map.gat|mapflag|flagname|optval"_s,
+ "Map.gat|mapflag|flagname|optval\n"_s,
+ "Map.gat|mapflag|flagname|optval{"_s,
+ };
+ for (auto input : inputs)
+ {
+ bool second = input.startswith('M');
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,!second?24:31);
+ auto p = dynamic_cast<MapFlag *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ if (!second)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ }
+ else
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("Map"_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)
+ {
+ EXPECT_SPAN(p->opt_extra.span, 1,25, 1,25);
+ EXPECT_EQ(p->opt_extra.data, ""_s);
+ }
+ else
+ {
+ EXPECT_SPAN(p->opt_extra.span, 1,26, 1,31);
+ EXPECT_EQ(p->opt_extra.data, "optval"_s);
+ }
+ }
+ }
+ }
+
+ TEST(ast, scriptfun)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "function|script|Fun Name{end;}"_s,
+ // 123456
+ "function|script|Fun Name\n{end;}\n"_s,
+ // 1234567
+ "function|script|Fun Name\n \n {end;} "_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,24);
+ auto p = dynamic_cast<ScriptFunction *>(top.get());
+ 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(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);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(p->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(p->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(p->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+ TEST(ast, scriptnone)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "-|script|#config|-1{end;}"_s,
+ // 123456
+ "-|script|#config|-1\n{end;}\n"_s,
+ // 1234567
+ "-|script|#config|-1\n \n {end;} "_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,19);
+ auto p = dynamic_cast<ScriptNone *>(top.get());
+ 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(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);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(p->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(p->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(p->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+ TEST(ast, scriptmapnone)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "map.gat,1,2,3|script|Init|-1{end;}"_s,
+ "map.gat,1,2,3|script|Init|-1\n{end;}\n"_s,
+ "map.gat,1,2,3|script|Init|-1\n \n {end;} "_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,28);
+ auto p = dynamic_cast<ScriptMapNone *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ 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(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);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(p->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(p->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(p->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+ TEST(ast, scriptmap)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "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,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr("<string>"_s, string_pipe(input));
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.get_success().is_some());
+ auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
+ EXPECT_SPAN(top->span, 1,1, 1,31);
+ auto p = dynamic_cast<ScriptMap *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ 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(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 (input.endswith('}'))
+ {
+ EXPECT_SPAN(p->body.span, 1,32, 1,37);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(p->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(p->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(p->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+} // namespace parse
+} // namespace npc
+} // namespace tmwa
diff --git a/src/ast/script.cpp b/src/ast/script.cpp
new file mode 100644
index 0000000..cc67224
--- /dev/null
+++ b/src/ast/script.cpp
@@ -0,0 +1,71 @@
+#include "script.hpp"
+// ast/script.cpp - Structure of tmwa-script
+//
+// Copyright © 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 Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace script
+{
+namespace parse
+{
+ Result<ScriptBody> parse_script_body(io::LineCharReader& lr)
+ {
+ io::LineSpan span;
+ io::LineChar c;
+ while (true)
+ {
+ if (!lr.get(c))
+ {
+ return Err("error: unexpected EOF before '{' in parse_script_body"_s);
+ }
+ if (c.ch() == ' ' || c.ch() == '\n')
+ {
+ lr.adv();
+ continue;
+ }
+ break;
+ }
+ if (c.ch() != '{')
+ {
+ return Err(c.error_str("expected opening '{'"_s));
+ }
+
+ MString accum;
+ accum += c.ch();
+ span.begin = c;
+ lr.adv();
+ while (true)
+ {
+ if (!lr.get(c))
+ return Err(c.error_str("unexpected EOF before '}' in parse_script_body"_s));
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ if (c.ch() == '}')
+ {
+ return Ok(ScriptBody{RString(accum), std::move(span)});
+ }
+ }
+ }
+} // namespace parse
+} // namespace script
+} // namespace tmwa
diff --git a/src/ast/script.hpp b/src/ast/script.hpp
new file mode 100644
index 0000000..59e53f0
--- /dev/null
+++ b/src/ast/script.hpp
@@ -0,0 +1,94 @@
+#pragma once
+// ast/script.hpp - Structure of tmwa-script
+//
+// Copyright © 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 Affero 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../compat/result.hpp"
+
+#include "../io/line.hpp"
+
+
+namespace tmwa
+{
+namespace script
+{
+namespace parse
+{
+ using io::Spanned;
+
+ struct ScriptBody
+ {
+ RString braced_body;
+ io::LineSpan span;
+ };
+
+ Result<ScriptBody> parse_script_body(io::LineCharReader& lr);
+
+ /*
+ (-- First bare-body-chunk only allowed for npcs, items, magic, functions.
+ It is not allowed for events. Basically it's an implicit label at times.
+ Last normal-lines is only permitted on item and magic scripts. --)
+ { script-body }: "{" bare-body-chunk? body-chunk* normal-lines? "}"
+ body-chunk: (comment* labelname ":")+ bare-body-chunk
+ bare-body-chunk: normal-lines terminator-line
+ normal-lines: normal-line*
+ any-line: normal-line
+ any-line: terminator-line
+ normal-line: "if" "(" expr ")" any-line
+ normal-line: normal-command ((expr ",")* expr)? ";"
+ terminator-line: "menu" (expr, labelname)* expr, labelname ";"
+ terminator-line: "goto" labelname ";"
+ terminator-line: terminator ((expr ",")* expr)? ";"
+ terminator: "return"
+ terminator: "close"
+ terminator: "end"
+ terminator: "mapexit"
+ terminator: "shop"
+
+ expr: subexpr_-1
+ subexpr_N: ("+" | "-" | "!" | "~") subexpr_7
+ subexpr_N: simple-expr (op_M subexpr_M | "(" ((expr ",")+ expr)? ")")* if N < M; function call only if N < 8 and preceding simple-expr (op sub)* is a known function
+ op_0: "||"
+ op_1: "&&"
+ op_2: "=="
+ op_2: "!="
+ op_2: ">="
+ op_2: ">"
+ op_2: "<="
+ op_2: "<"
+ op_3: "^"
+ op_4: "|"
+ op_5: "&"
+ op_5: ">>"
+ op_5: "<<"
+ op_6: "+"
+ op_6: "-"
+ op_7: "*"
+ op_7: "/"
+ op_7: "%"
+ simple-expr: "(" expr ")"
+ simple-expr: integer
+ simple-expr: string
+ simple-expr: variable ("[" expr "]")?
+ simple-expr: function // no longer command/label though
+ */
+} // namespace parse
+} // namespace script
+} // namespace tmwa
diff --git a/src/compat/result.hpp b/src/compat/result.hpp
new file mode 100644
index 0000000..f03c026
--- /dev/null
+++ b/src/compat/result.hpp
@@ -0,0 +1,78 @@
+#pragma once
+// result.hpp - A possibly failed return value
+//
+// Copyright © 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/rstring.hpp"
+
+#include "option.hpp"
+
+namespace tmwa
+{
+ namespace result
+ {
+ enum ResultMagicFlag { magic_flag };
+
+ template<class T>
+ class Result
+ {
+ Option<T> success;
+ RString failure;
+ public:
+ Result(ResultMagicFlag, T v)
+ : success(Some(std::move(v))), failure()
+ {}
+ Result(ResultMagicFlag, RString msg)
+ : success(None), failure(std::move(msg))
+ {}
+
+ bool is_ok() { return success.is_some(); }
+ bool is_err() { return !is_ok(); }
+
+ Option<T>& get_success() { return success; }
+ RString& get_failure() { return failure; }
+ };
+
+ template<class T>
+ Result<T> Ok(T v)
+ {
+ return Result<T>(magic_flag, std::move(v));
+ }
+
+ struct Err
+ {
+ RString message;
+ Err(RString m) : message(std::move(m)) {}
+
+ template<class T>
+ operator Result<T>()
+ {
+ return Result<T>(magic_flag, message);
+ }
+ };
+ } // namespace result
+ using result::Result;
+ using result::Ok;
+ using result::Err;
+
+#define TRY(r) ({ auto _res = r; TRY_UNWRAP(_res.get_success(), return ::tmwa::Err(_res.get_failure())); })
+ // TODO the existence of this as a separate macro is a bug.
+#define TRY_MOVE(r) ({ auto _res = r; TRY_UNWRAP(std::move(_res.get_success()), return ::tmwa::Err(_res.get_failure())); })
+} // namespace tmwa
diff --git a/src/compat/result_test.cpp b/src/compat/result_test.cpp
new file mode 100644
index 0000000..0fcc181
--- /dev/null
+++ b/src/compat/result_test.cpp
@@ -0,0 +1,79 @@
+#include "result.hpp"
+// result_test.cpp - Testsuite for possibly failing return values
+//
+// Copyright © 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 <gtest/gtest.h>
+
+#include "../strings/literal.hpp"
+
+//#include "../poison.hpp"
+
+
+namespace tmwa
+{
+TEST(Result, inspect)
+{
+ struct Foo
+ {
+ int val;
+
+ Foo(int v) : val(v) {}
+ Foo(Foo&&) = default;
+ Foo(const Foo&) = delete;
+ Foo& operator = (Foo&&) = default;
+ Foo& operator = (const Foo&) = delete;
+
+ bool operator == (const Foo& other) const
+ {
+ return this->val == other.val;
+ }
+ };
+
+ Result<Foo> foo = Ok(Foo(1));
+ EXPECT_TRUE(foo.is_ok());
+ EXPECT_FALSE(foo.is_err());
+ EXPECT_EQ(foo.get_success(), Some(Foo(1)));
+ EXPECT_EQ(foo.get_failure(), ""_s);
+ foo = Err("oops"_s);
+ EXPECT_FALSE(foo.is_ok());
+ EXPECT_TRUE(foo.is_err());
+ EXPECT_EQ(foo.get_success(), None<Foo>());
+ EXPECT_EQ(foo.get_failure(), "oops"_s);
+}
+
+static
+Result<int> try_you(bool b)
+{
+ return b ? Ok(0) : Err("die"_s);
+}
+
+static
+Result<int> try_me(bool b)
+{
+ return Ok(TRY(try_you(b)) + 1);
+}
+
+TEST(Result, try)
+{
+ Result<int> t = try_me(true);
+ EXPECT_EQ(t.get_success(), Some(1));
+ Result<int> f = try_me(false);
+ EXPECT_EQ(f.get_failure(), "die"_s);
+}
+} // namespace tmwa
diff --git a/src/io/line.hpp b/src/io/line.hpp
index 8244c5e..5572e98 100644
--- a/src/io/line.hpp
+++ b/src/io/line.hpp
@@ -41,6 +41,9 @@ namespace io
uint16_t line, column;
AString message_str(ZString cat, ZString msg) const;
+ AString note_str(ZString msg) const { return message_str("note"_s, msg); }
+ AString warning_str(ZString msg) const { return message_str("warning"_s, msg); }
+ AString error_str(ZString msg) const { return message_str("error"_s, msg); }
void message(ZString cat, ZString msg) const;
void note(ZString msg) const { message("note"_s, msg); }
void warning(ZString msg) const { message("warning"_s, msg); }
@@ -64,12 +67,22 @@ namespace io
LineChar begin, end;
AString message_str(ZString cat, ZString msg) const;
+ AString note_str(ZString msg) const { return message_str("note"_s, msg); }
+ AString warning_str(ZString msg) const { return message_str("warning"_s, msg); }
+ AString error_str(ZString msg) const { return message_str("error"_s, msg); }
void message(ZString cat, ZString msg) const;
void note(ZString msg) const { message("note"_s, msg); }
void warning(ZString msg) const { message("warning"_s, msg); }
void error(ZString msg) const { message("error"_s, msg); }
};
+ template<class T>
+ struct Spanned
+ {
+ T data;
+ LineSpan span;
+ };
+
class LineReader
{
protected:
diff --git a/src/io/line_test.cpp b/src/io/line_test.cpp
index edf60bd..46e7b57 100644
--- a/src/io/line_test.cpp
+++ b/src/io/line_test.cpp
@@ -23,6 +23,8 @@
#include "../strings/astring.hpp"
#include "../strings/zstring.hpp"
+#include "../tests/fdhack.hpp"
+
#include "../poison.hpp"
@@ -57,6 +59,7 @@ TEST(io, line1)
}
TEST(io, line2)
{
+ QuietFd q;
io::LineReader lr("<string2>"_s, string_pipe("Hello\nWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -73,6 +76,7 @@ TEST(io, line2)
}
TEST(io, line3)
{
+ QuietFd q;
io::LineReader lr("<string3>"_s, string_pipe("Hello\rWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -89,6 +93,7 @@ TEST(io, line3)
}
TEST(io, line4)
{
+ QuietFd q;
io::LineReader lr("<string4>"_s, string_pipe("Hello\r\nWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -105,6 +110,7 @@ TEST(io, line4)
}
TEST(io, line5)
{
+ QuietFd q;
io::LineReader lr("<string5>"_s, string_pipe("Hello\n\rWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -175,6 +181,7 @@ TEST(io, linechar1)
}
TEST(io, linechar2)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar2>"_s, string_pipe("Hi\nWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -223,6 +230,7 @@ TEST(io, linechar2)
}
TEST(io, linechar3)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar3>"_s, string_pipe("Hi\rWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -271,6 +279,7 @@ TEST(io, linechar3)
}
TEST(io, linechar4)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar4>"_s, string_pipe("Hi\r\nWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -319,6 +328,7 @@ TEST(io, linechar4)
}
TEST(io, linechar5)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar5>"_s, string_pipe("Hi\n\rWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -402,17 +412,17 @@ TEST(io, linespan)
}
while (span.end.ch() != 'r');
- EXPECT_EQ(span.begin.message_str("note"_s, "foo"_s),
+ EXPECT_EQ(span.begin.note_str("foo"_s),
"<span>:1:5: note: foo\n"
"Hello,\n"
" ^\n"_s
);
- EXPECT_EQ(span.end.message_str("warning"_s, "bar"_s),
+ EXPECT_EQ(span.end.warning_str("bar"_s),
"<span>:2:3: warning: bar\n"
"World!\n"
" ^\n"_s
);
- EXPECT_EQ(span.message_str("error"_s, "qux"_s),
+ EXPECT_EQ(span.error_str("qux"_s),
"<span>:1:5: error: qux\n"
"Hello,\n"
" ^~ ...\n"
diff --git a/src/io/read.cpp b/src/io/read.cpp
index 3ae5246..f3ed293 100644
--- a/src/io/read.cpp
+++ b/src/io/read.cpp
@@ -36,6 +36,8 @@ namespace io
{
ReadFile::ReadFile(FD f)
: fd(f), start(0), end(0)
+ // only for debug-sanity
+ , buf{}
{
}
ReadFile::ReadFile(ZString name)
diff --git a/src/io/read_test.cpp b/src/io/read_test.cpp
index 8fe84b7..e655eb1 100644
--- a/src/io/read_test.cpp
+++ b/src/io/read_test.cpp
@@ -24,6 +24,8 @@
#include "../strings/zstring.hpp"
#include "../strings/literal.hpp"
+#include "../tests/fdhack.hpp"
+
#include "../poison.hpp"
@@ -47,6 +49,7 @@ io::FD string_pipe(ZString sz)
TEST(io, read1)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -63,6 +66,7 @@ TEST(io, read2)
}
TEST(io, read3)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello\r"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -71,6 +75,7 @@ TEST(io, read3)
}
TEST(io, read4)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello\r\n"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -79,6 +84,7 @@ TEST(io, read4)
}
TEST(io, read5)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello\n\r"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
diff --git a/src/io/write.cpp b/src/io/write.cpp
index 5359c7a..a98954b 100644
--- a/src/io/write.cpp
+++ b/src/io/write.cpp
@@ -37,6 +37,8 @@ namespace io
{
WriteFile::WriteFile(FD f, bool linebuffered)
: fd(f), lb(linebuffered), buflen(0)
+ // only for debug-sanity
+ , buf{}
{}
WriteFile::WriteFile(ZString name, bool linebuffered)
: fd(FD::open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)), lb(linebuffered), buflen(0)
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index b0cb541..a5b7278 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -5675,4 +5675,38 @@ void do_init_clif(void)
{
make_listen_port(map_port, SessionParsers{.func_parse= clif_parse, .func_delete= clif_delete});
}
+
+bool extract(XString str, DIR *d)
+{
+ unsigned di;
+ if (extract(str, &di) && di < 8)
+ {
+ *d = static_cast<DIR>(di);
+ return true;
+ }
+ const struct
+ {
+ LString str;
+ DIR d;
+ } dirs[] =
+ {
+ {"S"_s, DIR::S},
+ {"SW"_s, DIR::SW},
+ {"W"_s, DIR::W},
+ {"NW"_s, DIR::NW},
+ {"N"_s, DIR::N},
+ {"NE"_s, DIR::NE},
+ {"E"_s, DIR::E},
+ {"SE"_s, DIR::SE},
+ };
+ for (auto& pair : dirs)
+ {
+ if (str == pair.str)
+ {
+ *d = pair.d;
+ return true;
+ }
+ }
+ return false;
+}
} // namespace tmwa
diff --git a/src/map/clif.hpp b/src/map/clif.hpp
index 12ed633..9873592 100644
--- a/src/map/clif.hpp
+++ b/src/map/clif.hpp
@@ -189,4 +189,6 @@ int clif_GM_kick(dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> tsd,
int clif_foreachclient(std::function<void(dumb_ptr<map_session_data>)>);
void do_init_clif(void);
+
+bool extract(XString, DIR *);
} // namespace tmwa
diff --git a/src/mmo/extract.cpp b/src/mmo/extract.cpp
index a480984..9123237 100644
--- a/src/mmo/extract.cpp
+++ b/src/mmo/extract.cpp
@@ -30,6 +30,9 @@
#include "../poison.hpp"
+// TODO move this whole file to io/ or something.
+// It needs to be lower in the include hierarchy so it can be implemented
+// for library types. Also it should pass an io::LineSpan around.
namespace tmwa
{
bool extract(XString str, XString *rv)
@@ -38,6 +41,12 @@ bool extract(XString str, XString *rv)
return true;
}
+bool extract(XString str, RString *rv)
+{
+ *rv = str;
+ return true;
+}
+
bool extract(XString str, AString *rv)
{
*rv = str;
@@ -98,4 +107,174 @@ bool extract(XString str, CharName *out)
}
return false;
}
+
+bool extract(XString str, std::chrono::nanoseconds *ns)
+{
+ std::chrono::nanoseconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *ns = std::chrono::nanoseconds(rep);
+ return true;
+ }
+ if (str.endswith("ns"_s))
+ {
+ if (extract(str.xrslice_h("ns"_s.size()), &rep))
+ {
+ *ns = std::chrono::nanoseconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::microseconds bigger;
+ if (extract(str, &bigger))
+ {
+ *ns = bigger;
+ return *ns == bigger;
+ }
+ return false;
+}
+bool extract(XString str, std::chrono::microseconds *us)
+{
+ std::chrono::microseconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *us = std::chrono::microseconds(rep);
+ return true;
+ }
+ if (str.endswith("us"_s))
+ {
+ if (extract(str.xrslice_h("us"_s.size()), &rep))
+ {
+ *us = std::chrono::microseconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::milliseconds bigger;
+ if (extract(str, &bigger))
+ {
+ *us = bigger;
+ return *us == bigger;
+ }
+ return false;
+}
+bool extract(XString str, std::chrono::milliseconds *ms)
+{
+ std::chrono::milliseconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *ms = std::chrono::milliseconds(rep);
+ return true;
+ }
+ if (str.endswith("ms"_s))
+ {
+ if (extract(str.xrslice_h("ms"_s.size()), &rep))
+ {
+ *ms = std::chrono::milliseconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::seconds bigger;
+ if (extract(str, &bigger))
+ {
+ *ms = bigger;
+ return *ms == bigger;
+ }
+ return false;
+}
+bool extract(XString str, std::chrono::seconds *s)
+{
+ std::chrono::seconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *s = std::chrono::seconds(rep);
+ return true;
+ }
+ if (str.endswith("s"_s))
+ {
+ if (extract(str.xrslice_h("s"_s.size()), &rep))
+ {
+ *s = std::chrono::seconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::minutes bigger;
+ if (extract(str, &bigger))
+ {
+ *s = bigger;
+ return *s == bigger;
+ }
+ return false;
+}
+bool extract(XString str, std::chrono::minutes *min)
+{
+ std::chrono::minutes::rep rep;
+ if (extract(str, &rep))
+ {
+ *min = std::chrono::minutes(rep);
+ return true;
+ }
+ if (str.endswith("min"_s))
+ {
+ if (extract(str.xrslice_h("min"_s.size()), &rep))
+ {
+ *min = std::chrono::minutes(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::hours bigger;
+ if (extract(str, &bigger))
+ {
+ *min = bigger;
+ return *min == bigger;
+ }
+ return false;
+}
+bool extract(XString str, std::chrono::hours *h)
+{
+ std::chrono::hours::rep rep;
+ if (extract(str, &rep))
+ {
+ *h = std::chrono::hours(rep);
+ return true;
+ }
+ if (str.endswith("h"_s))
+ {
+ if (extract(str.xrslice_h("h"_s.size()), &rep))
+ {
+ *h = std::chrono::hours(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::duration<int, std::ratio<60*60*24>> bigger;
+ if (extract(str, &bigger))
+ {
+ *h = bigger;
+ return *h == bigger;
+ }
+ return false;
+}
+bool extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d)
+{
+ std::chrono::duration<int, std::ratio<60*60*24>>::rep rep;
+ if (extract(str, &rep))
+ {
+ *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep);
+ return true;
+ }
+ if (str.endswith("d"_s))
+ {
+ if (extract(str.xrslice_h("d"_s.size()), &rep))
+ {
+ *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep);
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
} // namespace tmwa
diff --git a/src/mmo/extract.hpp b/src/mmo/extract.hpp
index ed2eb78..152d1b7 100644
--- a/src/mmo/extract.hpp
+++ b/src/mmo/extract.hpp
@@ -24,6 +24,7 @@
#include <cstdlib>
#include <algorithm>
+#include <chrono>
#include <vector>
#include "../ints/wrap.hpp"
@@ -93,7 +94,7 @@ bool extract_as_int(XString str, T *iv)
}
bool extract(XString str, XString *rv);
-
+bool extract(XString str, RString *rv);
bool extract(XString str, AString *rv);
template<uint8_t N>
@@ -228,4 +229,12 @@ bool extract(XString str, Wrapped<R> *w)
{
return extract(str, &w->_value);
}
+
+bool extract(XString str, std::chrono::nanoseconds *ns);
+bool extract(XString str, std::chrono::microseconds *us);
+bool extract(XString str, std::chrono::milliseconds *ms);
+bool extract(XString str, std::chrono::seconds *s);
+bool extract(XString str, std::chrono::minutes *min);
+bool extract(XString str, std::chrono::hours *h);
+bool extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d);
} // namespace tmwa
diff --git a/src/mmo/extract_test.cpp b/src/mmo/extract_test.cpp
index e6dc7b2..9c203ac 100644
--- a/src/mmo/extract_test.cpp
+++ b/src/mmo/extract_test.cpp
@@ -357,4 +357,92 @@ TEST(extract, mapname)
EXPECT_TRUE(extract("abcdefghijklmno.gat"_s, &map));
EXPECT_EQ(map, "abcdefghijklmno"_s);
}
+
+TEST(extract, chrono)
+{
+ std::chrono::nanoseconds ns;
+ std::chrono::microseconds us;
+ std::chrono::milliseconds ms;
+ std::chrono::seconds s;
+ std::chrono::minutes min;
+ std::chrono::hours h;
+ std::chrono::duration<int, std::ratio<60*60*24>> d;
+
+ EXPECT_TRUE(extract("1"_s, &ns));
+ EXPECT_EQ(ns, 1_ns);
+ EXPECT_TRUE(extract("3ns"_s, &ns));
+ EXPECT_EQ(ns, 3_ns);
+ EXPECT_TRUE(extract("4us"_s, &ns));
+ EXPECT_EQ(ns, 4_us);
+ EXPECT_TRUE(extract("5ms"_s, &ns));
+ EXPECT_EQ(ns, 5_ms);
+ EXPECT_TRUE(extract("6s"_s, &ns));
+ EXPECT_EQ(ns, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &ns));
+ EXPECT_EQ(ns, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &ns));
+ EXPECT_EQ(ns, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &ns));
+ EXPECT_EQ(ns, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &us));
+ EXPECT_EQ(us, 1_us);
+ EXPECT_TRUE(extract("4us"_s, &us));
+ EXPECT_EQ(us, 4_us);
+ EXPECT_TRUE(extract("5ms"_s, &us));
+ EXPECT_EQ(us, 5_ms);
+ EXPECT_TRUE(extract("6s"_s, &us));
+ EXPECT_EQ(us, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &us));
+ EXPECT_EQ(us, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &us));
+ EXPECT_EQ(us, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &us));
+ EXPECT_EQ(us, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &ms));
+ EXPECT_EQ(ms, 1_ms);
+ EXPECT_TRUE(extract("5ms"_s, &ms));
+ EXPECT_EQ(ms, 5_ms);
+ EXPECT_TRUE(extract("6s"_s, &ms));
+ EXPECT_EQ(ms, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &ms));
+ EXPECT_EQ(ms, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &ms));
+ EXPECT_EQ(ms, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &ms));
+ EXPECT_EQ(ms, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &s));
+ EXPECT_EQ(s, 1_s);
+ EXPECT_TRUE(extract("6s"_s, &s));
+ EXPECT_EQ(s, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &s));
+ EXPECT_EQ(s, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &s));
+ EXPECT_EQ(s, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &s));
+ EXPECT_EQ(s, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &min));
+ EXPECT_EQ(min, 1_min);
+ EXPECT_TRUE(extract("7min"_s, &min));
+ EXPECT_EQ(min, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &min));
+ EXPECT_EQ(min, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &min));
+ EXPECT_EQ(min, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &h));
+ EXPECT_EQ(h, 1_h);
+ EXPECT_TRUE(extract("8h"_s, &h));
+ EXPECT_EQ(h, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &h));
+ EXPECT_EQ(h, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &d));
+ EXPECT_EQ(d, 1_d);
+ EXPECT_TRUE(extract("9d"_s, &d));
+ EXPECT_EQ(d, 9_d);
+}
} // namespace tmwa
diff --git a/src/sexpr/lexer_test.cpp b/src/sexpr/lexer_test.cpp
index fdb47f2..5904894 100644
--- a/src/sexpr/lexer_test.cpp
+++ b/src/sexpr/lexer_test.cpp
@@ -76,21 +76,21 @@ TEST(sexpr, lexer)
sexpr::Lexer lexer("<lexer-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s));
EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN);
EXPECT_EQ(lexer.val_string(), "foo"_s);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:2: error: test\n"
" foo( ) 123\"\" \n"
" ^~~\n"_s
);
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:5: error: test\n"
" foo( ) 123\"\" \n"
" ^\n"_s
);
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_CLOSE);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:7: error: test\n"
" foo( ) 123\"\" \n"
" ^\n"_s
@@ -98,7 +98,7 @@ TEST(sexpr, lexer)
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN);
EXPECT_EQ(lexer.val_string(), "123"_s);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:9: error: test\n"
" foo( ) 123\"\" \n"
" ^~~\n"_s
@@ -106,7 +106,7 @@ TEST(sexpr, lexer)
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_STRING);
EXPECT_EQ(lexer.val_string(), ""_s);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:12: error: test\n"
" foo( ) 123\"\" \n"
" ^~\n"_s
diff --git a/src/sexpr/parser_test.cpp b/src/sexpr/parser_test.cpp
index 846d425..8619c15 100644
--- a/src/sexpr/parser_test.cpp
+++ b/src/sexpr/parser_test.cpp
@@ -20,6 +20,8 @@
#include <gtest/gtest.h>
+#include "../tests/fdhack.hpp"
+
#include "../poison.hpp"
@@ -90,6 +92,7 @@ TEST(sexpr, parselist)
TEST(sexpr, parsebad)
{
+ QuietFd q;
for (LString bad : {
"(\n"_s,
")\n"_s,
diff --git a/src/strings/rstring.cpp b/src/strings/rstring.cpp
index e74d1d5..5675935 100644
--- a/src/strings/rstring.cpp
+++ b/src/strings/rstring.cpp
@@ -58,13 +58,18 @@ namespace strings
}
RString& RString::operator = (const RString& r)
{
- // order important for self-assign
- if (!r.maybe_end)
- r.u.owned->count++;
- if (!maybe_end && !u.owned->count--)
- ::operator delete(u.owned);
- u = r.u;
- maybe_end = r.maybe_end;
+ // this turns out to be a win
+ // certain callers end up needing to do self-assignment a *lot*,
+ // leading to pointless ++,--s
+ if (this->u.owned != r.u.owned)
+ {
+ if (!r.maybe_end)
+ r.u.owned->count++;
+ if (!maybe_end && !u.owned->count--)
+ ::operator delete(u.owned);
+ u = r.u;
+ maybe_end = r.maybe_end;
+ }
return *this;
}
RString& RString::operator = (RString&& r)
diff --git a/src/strings/strings2_test.cpp b/src/strings/strings2_test.cpp
index 8ac8482..8b91306 100644
--- a/src/strings/strings2_test.cpp
+++ b/src/strings/strings2_test.cpp
@@ -228,4 +228,13 @@ TEST(StringTests, rlong)
EXPECT_EQ(&*r.begin(), &*r3.begin());
EXPECT_EQ(&*a.begin(), &*a3.begin());
}
+
+TEST(StringTest, rself)
+{
+ // force dynamic allocation; valgrind will check for memory errors
+ RString r = XString("foo bar baz"_s);
+ RString r2 = r;
+ r = r;
+ r = r2;
+}
} // namespace tmwa