From 6800761863dd45b6055768febc6ace6a20120dc7 Mon Sep 17 00:00:00 2001 From: Ben Longbons Date: Sun, 19 Oct 2014 22:22:08 -0700 Subject: New ast module for for npc parsing Will eventually put most/all parsers there. --- src/ast/fwd.hpp | 27 ++ src/ast/npc.cpp | 594 ++++++++++++++++++++++++++++++++++++++++++ src/ast/npc.hpp | 135 ++++++++++ src/ast/npc_test.cpp | 569 ++++++++++++++++++++++++++++++++++++++++ src/ast/script.cpp | 71 +++++ src/ast/script.hpp | 94 +++++++ src/compat/result.hpp | 78 ++++++ src/compat/result_test.cpp | 79 ++++++ src/io/line.hpp | 13 + src/io/line_test.cpp | 16 +- src/io/read.cpp | 2 + src/io/read_test.cpp | 6 + src/io/write.cpp | 2 + src/map/clif.cpp | 34 +++ src/map/clif.hpp | 2 + src/mmo/extract.cpp | 179 +++++++++++++ src/mmo/extract.hpp | 11 +- src/mmo/extract_test.cpp | 88 +++++++ src/sexpr/lexer_test.cpp | 10 +- src/sexpr/parser_test.cpp | 3 + src/strings/rstring.cpp | 19 +- src/strings/strings2_test.cpp | 9 + 22 files changed, 2025 insertions(+), 16 deletions(-) create mode 100644 src/ast/fwd.hpp create mode 100644 src/ast/npc.cpp create mode 100644 src/ast/npc.hpp create mode 100644 src/ast/npc_test.cpp create mode 100644 src/ast/script.cpp create mode 100644 src/ast/script.hpp create mode 100644 src/compat/result.hpp create mode 100644 src/compat/result_test.cpp (limited to 'src') 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 +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../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 +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../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 parse_warp(io::LineSpan span, std::vector>>>& 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 parse_shop(io::LineSpan span, std::vector>>>& 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 parse_monster(io::LineSpan span, std::vector>>>& 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::value, "delay1 is milliseconds"); + static_assert(std::is_same::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 parse_mapflag(io::LineSpan span, std::vector>>>& 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 parse_script_function(io::LineSpan span, std::vector>>>& 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 parse_script_none(io::LineSpan span, std::vector>>>& 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 parse_script_map_none(io::LineSpan span, std::vector>>>& 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 parse_script_map(io::LineSpan span, std::vector>>>& 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> parse_script_any(io::LineSpan span, std::vector>>>& bits, io::LineCharReader& lr) + { + std::unique_ptr