From 1a00fe4ea75924bfe594c4d92073cc95eaa2f32d Mon Sep 17 00:00:00 2001 From: Ben Longbons Date: Wed, 29 Oct 2014 14:56:55 -0700 Subject: Item AST --- src/ast/item.cpp | 160 ++++++++++++++++++++++++++++++++++++++++++++++ src/ast/item.hpp | 80 +++++++++++++++++++++++ src/ast/item_test.cpp | 150 +++++++++++++++++++++++++++++++++++++++++++ src/ast/npc.cpp | 53 ++++++++------- src/ast/npc.hpp | 10 +-- src/ast/npc_test.cpp | 82 +++++++++++++----------- src/ast/script.cpp | 14 ++-- src/ast/script.hpp | 24 +++++-- src/io/fwd.hpp | 2 + src/io/line.cpp | 107 ++++++++++++------------------- src/io/line.hpp | 59 ++--------------- src/io/line_test.cpp | 50 +++++++++++++++ src/io/read.cpp | 38 ++++++++++- src/io/read.hpp | 14 +++- src/io/read_test.cpp | 61 ++++++++++++++++++ src/io/span.cpp | 99 ++++++++++++++++++++++++++++ src/io/span.hpp | 90 ++++++++++++++++++++++++++ src/main-gdb-head.py | 2 +- src/sexpr/lexer.hpp | 9 ++- src/sexpr/lexer_test.cpp | 22 +------ src/sexpr/parser_test.cpp | 22 +------ 21 files changed, 905 insertions(+), 243 deletions(-) create mode 100644 src/ast/item.cpp create mode 100644 src/ast/item.hpp create mode 100644 src/ast/item_test.cpp create mode 100644 src/io/span.cpp create mode 100644 src/io/span.hpp diff --git a/src/ast/item.cpp b/src/ast/item.cpp new file mode 100644 index 0000000..99417d8 --- /dev/null +++ b/src/ast/item.cpp @@ -0,0 +1,160 @@ +#include "item.hpp" +// ast/item.cpp - Structure of itemdb +// +// 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 "../compat/memory.hpp" + +#include "../io/extract.hpp" +#include "../io/line.hpp" + +#include "../mmo/extract_enums.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace item +{ + using io::respan; + + // separate file because virtual + ItemOrComment::~ItemOrComment() {} + + static + void skip_comma_space(io::LineCharReader& lr) + { + io::LineChar c; + if (lr.get(c) && c.ch() == ',') + { + lr.adv(); + while (lr.get(c) && c.ch() == ' ') + { + lr.adv(); + } + } + } + static + Option> lex_nonscript(io::LineCharReader& lr, bool first) + { + io::LineChar c; + if (first) + { + while (lr.get(c) && c.ch() == '\n') + { + lr.adv(); + } + } + if (!lr.get(c)) + { + return None; + } + io::LineSpan span; + MString accum; + accum += c.ch(); + span.begin = c; + span.end = c; + lr.adv(); + if (c.ch() != '/') + first = false; + + if (first && lr.get(c) && c.ch() == '/') + { + accum += c.ch(); + span.end = c; + lr.adv(); + while (lr.get(c) && c.ch() != '\n') + { + accum += c.ch(); + span.end = c; + lr.adv(); + } + return Some(respan(span, RString(accum))); + } + + while (lr.get(c) && c.ch() != ',' && c.ch() != '\n') + { + accum += c.ch(); + span.end = c; + lr.adv(); + } + skip_comma_space(lr); + return Some(respan(span, RString(accum))); + } + + static + Result lex_script(io::LineCharReader& lr) + { + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.implicit_end = true; + opt.one_line = true; + auto rv = ast::script::parse_script_body(lr, opt); + if (rv.get_success().is_some()) + { + skip_comma_space(lr); + } + 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 EOL_ERROR(lr) ({ io::LineChar c; lr.get(c) ? Err(c.error_str("unexpected EOL"_s)) : Err("unexpected EOF before unexpected EOL"_s); }) + Result> parse_item(io::LineCharReader& lr) + { + std::unique_ptr rv = nullptr; + Spanned first = TRY_UNWRAP(lex_nonscript(lr, true), return Ok(std::move(rv))); + if (first.data.startswith("//"_s)) + { + Comment comment; + comment.span = first.span; + comment.comment = first.data; + rv = make_unique(std::move(comment)); + return Ok(std::move(rv)); + } + Item item; + SPAN_EXTRACT(first, item.id); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.name); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.jname); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.type); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.buy_price); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.sell_price); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.weight); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.atk); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.def); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.range); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.magic_bonus); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.slot_unused); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.gender); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.loc); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.wlv); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.elv); + 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(std::move(item)); + return Ok(std::move(rv)); + } +} // namespace item +} // namespace ast +} // namespace tmwa diff --git a/src/ast/item.hpp b/src/ast/item.hpp new file mode 100644 index 0000000..b54e55c --- /dev/null +++ b/src/ast/item.hpp @@ -0,0 +1,80 @@ +#pragma once +// ast/item.hpp - Structure of tmwa itemdb +// +// 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 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 . + +#include "fwd.hpp" + +#include + +#include "../compat/result.hpp" + +#include "../io/span.hpp" + +#include "../mmo/clif.t.hpp" +#include "../mmo/ids.hpp" +#include "../mmo/strs.hpp" + +#include "script.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace item +{ + using io::Spanned; + + struct ItemOrComment + { + io::LineSpan span; + + virtual ~ItemOrComment(); + }; + struct Comment : ItemOrComment + { + RString comment; + }; + struct Item : ItemOrComment + { + Spanned id; + Spanned name; + Spanned jname; + Spanned type; + Spanned buy_price; + Spanned sell_price; + Spanned weight; + Spanned atk; + Spanned def; + Spanned range; + Spanned magic_bonus; + Spanned slot_unused; + Spanned gender; + Spanned loc; + Spanned wlv; + Spanned elv; + Spanned view; + ast::script::ScriptBody use_script; + ast::script::ScriptBody equip_script; + }; + + Result> 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 new file mode 100644 index 0000000..3b5fd07 --- /dev/null +++ b/src/ast/item_test.cpp @@ -0,0 +1,150 @@ +#include "item.hpp" +// ast/item_test.cpp - Testsuite for itemdb parser. +// +// 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 + +#include "../io/line.hpp" + +#include "../tests/fdhack.hpp" + +//#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace item +{ +#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(itemast, eof) + { + QuietFd q; + LString inputs[] = + { + ""_s, + "\n"_s, + "\n\n"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, ""_s, input); + auto res = parse_item(lr); + EXPECT_EQ(res.get_success(), Some(std::unique_ptr(nullptr))); + } + } + TEST(itemast, comment) + { + QuietFd q; + LString inputs[] = + { + //23456789 + "// hello"_s, + "// hello\n "_s, + "// hello\nabc"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, ""_s, input); + auto res = parse_item(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(top.get()); + EXPECT_TRUE(p); + if (p) + { + EXPECT_EQ(p->comment, "// hello"_s); + } + } + } + TEST(itemast, item) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 4 5 + //2345678901234567890123456789012345678901234567890123456789 + "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}"_s, + "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\n"_s, + "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\nabc"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, ""_s, input); + auto res = parse_item(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,58); + auto p = dynamic_cast(top.get()); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->id.span, 1,1, 1,1); + EXPECT_EQ(p->id.data, wrap(1)); + EXPECT_SPAN(p->name.span, 1,3, 1,6); + EXPECT_EQ(p->name.data, stringish("abc "_s)); + EXPECT_SPAN(p->jname.span, 1,10, 1,12); + EXPECT_EQ(p->jname.data, stringish("def"_s)); + EXPECT_SPAN(p->type.span, 1,14, 1,14); + EXPECT_EQ(p->type.data, ItemType::JUNK); + EXPECT_SPAN(p->buy_price.span, 1,16, 1,16); + EXPECT_EQ(p->buy_price.data, 4); + EXPECT_SPAN(p->sell_price.span, 1,18, 1,18); + EXPECT_EQ(p->sell_price.data, 5); + EXPECT_SPAN(p->weight.span, 1,20, 1,20); + EXPECT_EQ(p->weight.data, 6); + EXPECT_SPAN(p->atk.span, 1,22, 1,22); + EXPECT_EQ(p->atk.data, 7); + EXPECT_SPAN(p->def.span, 1,24, 1,24); + EXPECT_EQ(p->def.data, 8); + EXPECT_SPAN(p->range.span, 1,26, 1,26); + EXPECT_EQ(p->range.data, 9); + EXPECT_SPAN(p->magic_bonus.span, 1,28, 1,29); + EXPECT_EQ(p->magic_bonus.data, 10); + EXPECT_SPAN(p->slot_unused.span, 1,31, 1,32); + EXPECT_EQ(p->slot_unused.data, "xx"_s); + EXPECT_SPAN(p->gender.span, 1,34, 1,34); + EXPECT_EQ(p->gender.data, SEX::NEUTRAL); + EXPECT_SPAN(p->loc.span, 1,36, 1,37); + EXPECT_EQ(p->loc.data, EPOS::MISC1); + EXPECT_SPAN(p->wlv.span, 1,39, 1,40); + EXPECT_EQ(p->wlv.data, 12); + EXPECT_SPAN(p->elv.span, 1,42, 1,43); + EXPECT_EQ(p->elv.data, 13); + EXPECT_SPAN(p->view.span, 1,45, 1,46); + EXPECT_EQ(p->view.data, ItemLook::BOW); + EXPECT_SPAN(p->use_script.span, 1,49, 1,54); + EXPECT_EQ(p->use_script.braced_body, "{end;}"_s); + EXPECT_SPAN(p->equip_script.span, 1,57, 1,58); + EXPECT_EQ(p->equip_script.braced_body, "{}"_s); + } + } + } +} // namespace item +} // namespace ast +} // namespace tmwa diff --git a/src/ast/npc.cpp b/src/ast/npc.cpp index ca518d8..ceb381d 100644 --- a/src/ast/npc.cpp +++ b/src/ast/npc.cpp @@ -22,6 +22,7 @@ #include "../io/cxxstdio.hpp" #include "../io/extract.hpp" +#include "../io/line.hpp" #include "../mmo/extract_enums.hpp" @@ -32,10 +33,12 @@ namespace tmwa { -namespace npc +namespace ast { -namespace parse +namespace npc { + using io::respan; + // separate file because virtual TopLevel::~TopLevel() {} @@ -285,7 +288,10 @@ namespace parse 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)); + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.default_label = "OnCall"_s; + script_function.body = TRY(ast::script::parse_script_body(lr, opt)); return Ok(std::move(script_function)); } static @@ -317,7 +323,10 @@ namespace parse 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)); + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.no_start = true; + script_none.body = TRY(ast::script::parse_script_body(lr, opt)); return Ok(std::move(script_none)); } static @@ -353,7 +362,10 @@ namespace parse 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)); + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.no_start = true; + script_map_none.body = TRY(ast::script::parse_script_body(lr, opt)); return Ok(std::move(script_map_none)); } static @@ -391,7 +403,10 @@ namespace parse 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)); + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.default_label = "OnClick"_s; + script_map.body = TRY(ast::script::parse_script_body(lr, opt)); return Ok(std::move(script_map)); } static @@ -428,6 +443,9 @@ namespace parse static Option> lex(io::LineCharReader& lr, bool first) { + // you know, I just realized a lot of the if (.get()) checks are not + // actually going to fail, since LineCharReader guarantees the \n + // occurs before EOF io::LineChar c; // at start of line, skip whitespace if (first) @@ -445,12 +463,8 @@ namespace parse // separators are simple if (c.ch() == '|' || c.ch() == ',') { - Spanned bit; - bit.span.begin = c; - bit.span.end = c; - bit.data = RString(VString<1>(c.ch())); lr.adv(); - return Some(bit); + return Some(respan({c, c}, RString(VString<1>(c.ch())))); } io::LineSpan span; MString accum; @@ -464,10 +478,7 @@ namespace parse // 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 bit; - bit.span = span; - bit.data = RString(accum); - return Some(bit); + return Some(respan(span, RString(accum))); } accum += c.ch(); @@ -483,10 +494,7 @@ namespace parse span.end = c; lr.adv(); } - Spanned bit; - bit.span = span; - bit.data = RString(accum); - return Some(bit); + return Some(respan(span, RString(accum))); } // otherwise, collect until an end of line or separator while (lr.get(c) && c.ch() != '\n' && c.ch() != '{' && c.ch() != ',' && c.ch() != '|') @@ -495,10 +503,7 @@ namespace parse span.end = c; lr.adv(); } - Spanned bit; - bit.span = span; - bit.data = RString(accum); - return Some(std::move(bit)); + return Some(respan(span, RString(accum))); } Result> parse_top(io::LineCharReader& in) @@ -592,6 +597,6 @@ namespace parse } return Ok(std::move(rv)); } -} // namespace parse } // namespace npc +} // namespace ast } // namespace tmwa diff --git a/src/ast/npc.hpp b/src/ast/npc.hpp index 648b40b..dd323b6 100644 --- a/src/ast/npc.hpp +++ b/src/ast/npc.hpp @@ -24,6 +24,8 @@ #include "../compat/result.hpp" +#include "../io/span.hpp" + #include "../mmo/clif.t.hpp" #include "../mmo/ids.hpp" #include "../mmo/strs.hpp" @@ -35,9 +37,9 @@ namespace tmwa { -namespace npc +namespace ast { -namespace parse +namespace npc { using io::Spanned; @@ -100,7 +102,7 @@ namespace parse { io::LineSpan key_span; // see src/script/parser.hpp - script::parse::ScriptBody body; + ast::script::ScriptBody body; }; struct ScriptFunction : Script { @@ -133,6 +135,6 @@ namespace parse // other Script subclasses elsewhere? (for item and magic scripts) Result> parse_top(io::LineCharReader& in); -} // namespace parse } // namespace npc +} // namespace ast } // namespace tmwa diff --git a/src/ast/npc_test.cpp b/src/ast/npc_test.cpp index 2697351..ea4bdf3 100644 --- a/src/ast/npc_test.cpp +++ b/src/ast/npc_test.cpp @@ -20,6 +20,8 @@ #include +#include "../io/line.hpp" + #include "../tests/fdhack.hpp" //#include "../poison.hpp" @@ -27,26 +29,10 @@ namespace tmwa { -namespace npc +namespace ast { -namespace parse +namespace npc { - 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); \ @@ -55,7 +41,7 @@ namespace parse EXPECT_EQ((span).end.column, ec); \ }) - TEST(ast, eof) + TEST(npcast, eof) { QuietFd q; LString inputs[] = @@ -66,12 +52,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); EXPECT_EQ(res.get_success(), Some(std::unique_ptr(nullptr))); } } - TEST(ast, comment) + TEST(npcast, comment) { QuietFd q; LString inputs[] = @@ -83,10 +69,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -96,7 +84,7 @@ namespace parse } } } - TEST(ast, warp) + TEST(npcast, warp) { QuietFd q; LString inputs[] = @@ -110,10 +98,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -141,7 +131,7 @@ namespace parse } } } - TEST(ast, shop) + TEST(npcast, shop) { QuietFd q; LString inputs[] = @@ -155,10 +145,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -197,7 +189,7 @@ namespace parse } } } - TEST(ast, monster) + TEST(npcast, monster) { QuietFd q; LString inputs[] = @@ -220,10 +212,12 @@ namespace parse bool second = input.startswith('M'); bool third = input.startswith('n'); assert(first + second + third == 1); - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -302,7 +296,7 @@ namespace parse } } } - TEST(ast, mapflag) + TEST(npcast, mapflag) { QuietFd q; LString inputs[] = @@ -319,10 +313,12 @@ namespace parse for (auto input : inputs) { bool second = input.startswith('M'); - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -354,7 +350,7 @@ namespace parse } } - TEST(ast, scriptfun) + TEST(npcast, scriptfun) { QuietFd q; LString inputs[] = @@ -369,10 +365,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -402,7 +400,7 @@ namespace parse } } } - TEST(ast, scriptnone) + TEST(npcast, scriptnone) { QuietFd q; LString inputs[] = @@ -417,10 +415,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -451,7 +451,7 @@ namespace parse } } } - TEST(ast, scriptmapnone) + TEST(npcast, scriptmapnone) { QuietFd q; LString inputs[] = @@ -464,10 +464,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -505,7 +507,7 @@ namespace parse } } } - TEST(ast, scriptmap) + TEST(npcast, scriptmap) { QuietFd q; LString inputs[] = @@ -518,10 +520,12 @@ namespace parse }; for (auto input : inputs) { - io::LineCharReader lr(""_s, string_pipe(input)); + io::LineCharReader lr(io::from_string, ""_s, input); auto res = parse_top(lr); 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(top.get()); EXPECT_TRUE(p); @@ -564,6 +568,6 @@ namespace parse } } } -} // namespace parse } // namespace npc +} // namespace ast } // namespace tmwa diff --git a/src/ast/script.cpp b/src/ast/script.cpp index cc67224..ec958e1 100644 --- a/src/ast/script.cpp +++ b/src/ast/script.cpp @@ -18,16 +18,18 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#include "../io/line.hpp" + #include "../poison.hpp" namespace tmwa { -namespace script +namespace ast { -namespace parse +namespace script { - Result parse_script_body(io::LineCharReader& lr) + Result parse_script_body(io::LineCharReader& lr, ScriptOptions opt) { io::LineSpan span; io::LineChar c; @@ -37,7 +39,7 @@ namespace parse { return Err("error: unexpected EOF before '{' in parse_script_body"_s); } - if (c.ch() == ' ' || c.ch() == '\n') + if (c.ch() == ' ' || (!opt.one_line && c.ch() == '\n')) { lr.adv(); continue; @@ -57,6 +59,8 @@ namespace parse { if (!lr.get(c)) return Err(c.error_str("unexpected EOF before '}' in parse_script_body"_s)); + if (opt.one_line && c.ch() == '\n') + return Err(c.error_str("unexpected EOL before '}' in parse_script_body"_s)); accum += c.ch(); span.end = c; lr.adv(); @@ -66,6 +70,6 @@ namespace parse } } } -} // namespace parse } // namespace script +} // namespace ast } // namespace tmwa diff --git a/src/ast/script.hpp b/src/ast/script.hpp index 59e53f0..da43f90 100644 --- a/src/ast/script.hpp +++ b/src/ast/script.hpp @@ -22,14 +22,14 @@ #include "../compat/result.hpp" -#include "../io/line.hpp" +#include "../io/span.hpp" namespace tmwa { -namespace script +namespace ast { -namespace parse +namespace script { using io::Spanned; @@ -39,7 +39,21 @@ namespace parse io::LineSpan span; }; - Result parse_script_body(io::LineCharReader& lr); + struct ScriptOptions + { + // don't require a label at the beginning + bool implicit_start = false; + // label to generate at the beginning if not already present + RString default_label; + // beginning must be only 'end;' + bool no_start; + // don't requite an 'end;' at the end + bool implicit_end = false; + // forbid newlines anywhere between { and } + bool one_line = false; + }; + + Result parse_script_body(io::LineCharReader& lr, ScriptOptions opt); /* (-- First bare-body-chunk only allowed for npcs, items, magic, functions. @@ -89,6 +103,6 @@ namespace parse simple-expr: variable ("[" expr "]")? simple-expr: function // no longer command/label though */ -} // namespace parse } // namespace script +} // namespace ast } // namespace tmwa diff --git a/src/io/fwd.hpp b/src/io/fwd.hpp index 99268f4..5334fbd 100644 --- a/src/io/fwd.hpp +++ b/src/io/fwd.hpp @@ -34,5 +34,7 @@ namespace io class ReadFile; class WriteFile; class AppendFile; + class LineReader; + class LineCharReader; } // namespace io } // namespace tmwa diff --git a/src/io/line.cpp b/src/io/line.cpp index a1cdf42..5d7e792 100644 --- a/src/io/line.cpp +++ b/src/io/line.cpp @@ -19,9 +19,7 @@ // along with this program. If not, see . #include "../strings/astring.hpp" -#include "../strings/mstring.hpp" #include "../strings/zstring.hpp" -#include "../strings/xstring.hpp" #include "cxxstdio.hpp" @@ -32,71 +30,6 @@ namespace tmwa { namespace io { - AString Line::message_str(ZString cat, ZString msg) const - { - MString out; - if (column) - out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, - filename, line, column, cat, msg); - else - out += STRPRINTF("%s:%u: %s: %s\n"_fmt, - filename, line, cat, msg); - out += STRPRINTF("%s\n"_fmt, text); - out += STRPRINTF("%*c\n"_fmt, column, '^'); - return AString(out); - } - - void Line::message(ZString cat, ZString msg) const - { - if (column) - FPRINTF(stderr, "%s:%u:%u: %s: %s\n"_fmt, - filename, line, column, cat, msg); - else - FPRINTF(stderr, "%s:%u: %s: %s\n"_fmt, - filename, line, cat, msg); - FPRINTF(stderr, "%s\n"_fmt, text); - FPRINTF(stderr, "%*c\n"_fmt, column, '^'); - } - - AString LineSpan::message_str(ZString cat, ZString msg) const - { - assert (begin.column); - assert (end.column); - assert (begin.line < end.line || begin.column <= end.column); - - MString out; - if (begin.line == end.line) - { - out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, - begin.filename, begin.line, begin.column, cat, msg); - out += STRPRINTF("%s\n"_fmt, begin.text); - out += STRPRINTF("%*c"_fmt, begin.column, '^'); - for (unsigned c = begin.column; c != end.column; ++c) - out += '~'; - out += '\n'; - } - else - { - out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, - begin.filename, begin.line, begin.column, cat, msg); - out += STRPRINTF("%s\n"_fmt, begin.text); - out += STRPRINTF("%*c"_fmt, begin.column, '^'); - for (unsigned c = begin.column; c != begin.text.size(); ++c) - out += '~'; - out += " ...\n"_s; - out += STRPRINTF("%s\n"_fmt, end.text); - for (unsigned c = 0; c != end.column; ++c) - out += '~'; - out += '\n'; - } - return AString(out); - } - - void LineSpan::message(ZString cat, ZString msg) const - { - FPRINTF(stderr, "%s"_fmt, message_str(cat, msg)); - } - LineReader::LineReader(ZString name) : filename(name), line(0), column(0), rf(name) {} @@ -105,6 +38,14 @@ namespace io : filename(name), line(0), column(0), rf(fd) {} + LineReader::LineReader(read_file_from_string, ZString name, XString content, int startline, FD fd) + : filename(name), line(startline-1), column(0), rf(from_string, content, fd) + {} + + LineReader::LineReader(read_file_from_string, ZString name, LString content, int startline, FD fd) + : filename(name), line(startline-1), column(0), rf(from_string, content, fd) + {} + bool LineReader::read_line(Line& l) { AString text; @@ -145,6 +86,38 @@ namespace io column = 0; } + LineCharReader::LineCharReader(read_file_from_string, ZString name, XString content, int startline, int startcol, FD fd) + : LineReader(from_string, name, content, 1, fd) + { + column = 1; // not 0, not whole line + if (rf.is_open()) + adv(); + if (!line) + column = 0; + else + { + line = startline; + column = startcol; + line_text = STRPRINTF("%*s"_fmt, static_cast(column-1 + line_text.size()), line_text); + } + } + + LineCharReader::LineCharReader(read_file_from_string, ZString name, LString content, int startline, int startcol, FD fd) + : LineReader(from_string, name, content, 1, fd) + { + column = 1; // not 0, not whole line + if (rf.is_open()) + adv(); + if (!line) + column = 0; + else + { + line = startline; + column = startcol; + line_text = STRPRINTF("%*s"_fmt, static_cast(column-1 + line_text.size()), line_text); + } + } + bool LineCharReader::get(LineChar& c) { if (!column) diff --git a/src/io/line.hpp b/src/io/line.hpp index 5572e98..c94eeb9 100644 --- a/src/io/line.hpp +++ b/src/io/line.hpp @@ -21,68 +21,15 @@ #include "fwd.hpp" #include "../strings/rstring.hpp" -#include "../strings/zstring.hpp" -#include "../strings/literal.hpp" #include "read.hpp" +#include "span.hpp" namespace tmwa { namespace io { - // TODO split this out - struct Line - { - RString text; - - RString filename; - // 1-based - 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); } - void error(ZString msg) const { message("error"_s, msg); } - }; - - // psst, don't tell anyone - struct LineChar : Line - { - char ch() - { - size_t c = column - 1; - if (c == text.size()) - return '\n'; - return text[c]; - } - }; - - struct LineSpan - { - 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 - struct Spanned - { - T data; - LineSpan span; - }; - class LineReader { protected: @@ -96,6 +43,8 @@ namespace io LineReader& operator = (LineReader&&) = delete; // needed for unit tests LineReader(ZString name, FD fd); + LineReader(read_file_from_string, ZString name, XString content, int startline=1, FD fd=FD()); + LineReader(read_file_from_string, ZString name, LString content, int startline=1, FD fd=FD()); bool read_line(Line& l); bool is_open(); @@ -110,6 +59,8 @@ namespace io LineCharReader(LineCharReader&&) = delete; LineCharReader& operator = (LineCharReader&&) = delete; LineCharReader(ZString name, FD fd); + LineCharReader(read_file_from_string, ZString name, XString content, int startline=1, int startcol=1, FD fd=FD()); + LineCharReader(read_file_from_string, ZString name, LString content, int startline=1, int startcol=1, FD fd=FD()); bool get(LineChar& c); void adv(); diff --git a/src/io/line_test.cpp b/src/io/line_test.cpp index 46e7b57..582ee81 100644 --- a/src/io/line_test.cpp +++ b/src/io/line_test.cpp @@ -130,6 +130,20 @@ TEST(io, line5) EXPECT_EQ(hi.column, 0); EXPECT_FALSE(lr.read_line(hi)); } +TEST(io, line1text) +{ + io::LineReader lr(io::from_string, ""_s, "Hello\nWorld"_s, 2); + io::Line hi; + EXPECT_TRUE(lr.read_line(hi)); + EXPECT_EQ(hi.text, "Hello"_s); + EXPECT_EQ(hi.line, 2); + EXPECT_EQ(hi.column, 0); + EXPECT_TRUE(lr.read_line(hi)); + EXPECT_EQ(hi.text, "World"_s); + EXPECT_EQ(hi.line, 3); + EXPECT_EQ(hi.column, 0); + EXPECT_FALSE(lr.read_line(hi)); +} TEST(io, linechar1) { @@ -382,6 +396,42 @@ TEST(io, linechar5) lr.adv(); EXPECT_FALSE(lr.get(c)); } +TEST(io, linechar1text) +{ + io::LineCharReader lr(io::from_string, ""_s, "Hi\nWu\n"_s, 2, 3); + io::LineChar c; + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'H'); + EXPECT_EQ(c.line, 2); + EXPECT_EQ(c.column, 3); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'i'); + EXPECT_EQ(c.line, 2); + EXPECT_EQ(c.column, 4); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), '\n'); + EXPECT_EQ(c.line, 2); + EXPECT_EQ(c.column, 5); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'W'); + EXPECT_EQ(c.line, 3); + EXPECT_EQ(c.column, 1); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'u'); + EXPECT_EQ(c.line, 3); + EXPECT_EQ(c.column, 2); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), '\n'); + EXPECT_EQ(c.line, 3); + EXPECT_EQ(c.column, 3); + lr.adv(); + EXPECT_FALSE(lr.get(c)); +} TEST(io, linespan) { diff --git a/src/io/read.cpp b/src/io/read.cpp index 30620a1..d701c7f 100644 --- a/src/io/read.cpp +++ b/src/io/read.cpp @@ -1,7 +1,7 @@ #include "read.hpp" // io/read.cpp - Input from files // -// Copyright © 2013 Ben Longbons +// Copyright © 2013-2014 Ben Longbons // // This file is part of The Mana World (Athena server) // @@ -48,6 +48,32 @@ namespace io : fd(dir.open_fd(name, O_RDONLY | O_CLOEXEC)), start(0), end(0) { } + ReadFile::ReadFile(read_file_from_string, XString content, FD f) + : fd(f), start(0), end(), extra() + { + if (content.size() <= 4096) + { + end = content.size(); + auto z = std::copy(content.begin(), content.end(), buf); + // only for debug sanity + std::fill(z, std::end(buf), 0); + return; + } + auto base = content.base(); + if (!base) + { + extra = content; + end = content.size(); + return; + } + start = &*content.begin() - &*base->begin(); + end = &*content.end() - &*base->begin(); + extra = *base; + } + ReadFile::ReadFile(read_file_from_string, LString content, FD f) + : ReadFile(from_string, RString(content), f) + { + } ReadFile::~ReadFile() { fd.close(); @@ -56,6 +82,14 @@ namespace io bool ReadFile::get(char& c) { + if (extra) + { + c = extra[start]; + ++start; + if (start == end) + extra = ""_s; + return true; + } if (start == end) { if (fd == FD()) @@ -125,7 +159,7 @@ namespace io bool ReadFile::is_open() { - return fd != FD(); + return fd != FD() || start != end; } } // namespace io } // namespace tmwa diff --git a/src/io/read.hpp b/src/io/read.hpp index c1c4882..2e3611b 100644 --- a/src/io/read.hpp +++ b/src/io/read.hpp @@ -1,7 +1,7 @@ #pragma once // io/read.hpp - Input from files. // -// Copyright © 2013 Ben Longbons +// Copyright © 2013-2014 Ben Longbons // // This file is part of The Mana World (Athena server) // @@ -20,6 +20,8 @@ #include "fwd.hpp" +#include "../strings/rstring.hpp" + #include "dir.hpp" #include "fd.hpp" @@ -27,18 +29,28 @@ namespace tmwa { namespace io { + enum read_file_from_string + { + from_string, + }; + + // TODO - for internal warnings, it would be convenient if this class + // didn't exist at all, and instead everything was done with line info. class ReadFile { private: FD fd; unsigned short start, end; char buf[4096]; + RString extra; public: explicit ReadFile(FD fd); explicit ReadFile(ZString name); ReadFile(const DirFd& dir, ZString name); + ReadFile(read_file_from_string, XString content, FD fd=FD()); + ReadFile(read_file_from_string, LString content, FD fd=FD()); ReadFile& operator = (ReadFile&&) = delete; ReadFile(ReadFile&&) = delete; diff --git a/src/io/read_test.cpp b/src/io/read_test.cpp index e655eb1..22c67c8 100644 --- a/src/io/read_test.cpp +++ b/src/io/read_test.cpp @@ -93,4 +93,65 @@ TEST(io, read5) EXPECT_FALSE(hi); EXPECT_FALSE(rf.getline(hi)); } + +#define S15 "0123456789abcde"_s +#define S16 "0123456789abcdef"_s +#define S255 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S15 +#define S256 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 +#define S4095 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S255 +#define S4096 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 + +TEST(io, readstringr) +{ + LString tests[] = + { + S15, + S16, + S255, + S256, + S4095, + S4096, + S4096 S16, + }; + for (RString test : tests) + { + char buf[test.size() + 1]; + + io::ReadFile rf(io::from_string, test); + EXPECT_EQ(rf.get(buf, sizeof(buf)), test.size()); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + + io::ReadFile rf2(io::from_string, test, string_pipe("\na"_s)); + EXPECT_EQ(rf2.get(buf, sizeof(buf)), test.size() + 1); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + EXPECT_EQ('\n', buf[test.size()]); + } +} + +TEST(io, readstringx) +{ + LString tests[] = + { + S15, + S16, + S255, + S256, + S4095, + S4096, + S4096 S16, + }; + for (XString test : tests) + { + char buf[test.size() + 1]; + + io::ReadFile rf(io::from_string, test); + EXPECT_EQ(rf.get(buf, sizeof(buf)), test.size()); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + + io::ReadFile rf2(io::from_string, test, string_pipe("\na"_s)); + EXPECT_EQ(rf2.get(buf, sizeof(buf)), test.size() + 1); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + EXPECT_EQ('\n', buf[test.size()]); + } +} } // namespace tmwa diff --git a/src/io/span.cpp b/src/io/span.cpp new file mode 100644 index 0000000..f4752f0 --- /dev/null +++ b/src/io/span.cpp @@ -0,0 +1,99 @@ +#include "span.hpp" +// io/span.cpp - Tracking info about input +// +// 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 "../strings/astring.hpp" +#include "../strings/mstring.hpp" +#include "../strings/zstring.hpp" + +#include "cxxstdio.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace io +{ + AString Line::message_str(ZString cat, ZString msg) const + { + MString out; + if (column) + out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, + filename, line, column, cat, msg); + else + out += STRPRINTF("%s:%u: %s: %s\n"_fmt, + filename, line, cat, msg); + out += STRPRINTF("%s\n"_fmt, text); + out += STRPRINTF("%*c\n"_fmt, column, '^'); + return AString(out); + } + + void Line::message(ZString cat, ZString msg) const + { + if (column) + FPRINTF(stderr, "%s:%u:%u: %s: %s\n"_fmt, + filename, line, column, cat, msg); + else + FPRINTF(stderr, "%s:%u: %s: %s\n"_fmt, + filename, line, cat, msg); + FPRINTF(stderr, "%s\n"_fmt, text); + FPRINTF(stderr, "%*c\n"_fmt, column, '^'); + } + + AString LineSpan::message_str(ZString cat, ZString msg) const + { + assert (begin.column); + assert (end.column); + assert (begin.line < end.line || begin.column <= end.column); + + MString out; + if (begin.line == end.line) + { + out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, + begin.filename, begin.line, begin.column, cat, msg); + out += STRPRINTF("%s\n"_fmt, begin.text); + out += STRPRINTF("%*c"_fmt, begin.column, '^'); + for (unsigned c = begin.column; c != end.column; ++c) + out += '~'; + out += '\n'; + } + else + { + out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, + begin.filename, begin.line, begin.column, cat, msg); + out += STRPRINTF("%s\n"_fmt, begin.text); + out += STRPRINTF("%*c"_fmt, begin.column, '^'); + for (unsigned c = begin.column; c != begin.text.size(); ++c) + out += '~'; + out += " ...\n"_s; + out += STRPRINTF("%s\n"_fmt, end.text); + for (unsigned c = 0; c != end.column; ++c) + out += '~'; + out += '\n'; + } + return AString(out); + } + + void LineSpan::message(ZString cat, ZString msg) const + { + FPRINTF(stderr, "%s"_fmt, message_str(cat, msg)); + } +} // namespace io +} // namespace tmwa diff --git a/src/io/span.hpp b/src/io/span.hpp new file mode 100644 index 0000000..e474a7a --- /dev/null +++ b/src/io/span.hpp @@ -0,0 +1,90 @@ +#pragma once +// io/span.hpp - Tracking info about input +// +// 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 "fwd.hpp" + +#include "../strings/rstring.hpp" +#include "../strings/zstring.hpp" +#include "../strings/literal.hpp" + + +namespace tmwa +{ +namespace io +{ + // TODO split this out + struct Line + { + RString text; + + RString filename; + // 1-based + 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); } + void error(ZString msg) const { message("error"_s, msg); } + }; + + // psst, don't tell anyone + struct LineChar : Line + { + char ch() + { + size_t c = column - 1; + if (c == text.size()) + return '\n'; + return text[c]; + } + }; + + struct LineSpan + { + 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 + struct Spanned + { + T data; + LineSpan span; + }; + + template + Spanned respan(LineSpan span, T data) + { + return Spanned{std::move(data), std::move(span)}; + } +} // namespace io +} // namespace tmwa diff --git a/src/main-gdb-head.py b/src/main-gdb-head.py index 6ae204b..3a05917 100644 --- a/src/main-gdb-head.py +++ b/src/main-gdb-head.py @@ -141,7 +141,7 @@ class PointerPrinter(object): try: sym, off, sec, lib = info_symbol(addr) except: - s = '' % addr + s = '(%s)' % (v.type, addr) else: if off: s = '<%s+%d>' % off diff --git a/src/sexpr/lexer.hpp b/src/sexpr/lexer.hpp index 744d8c5..9b198a0 100644 --- a/src/sexpr/lexer.hpp +++ b/src/sexpr/lexer.hpp @@ -58,10 +58,13 @@ namespace sexpr Lexer(ZString filename) : _in(filename), _current(TOK_EOF), _span(), _depth() { adv(); } - // for unit tests - Lexer(ZString fake, io::FD fd) - : _in(fake, fd), _current(TOK_EOF), _span(), _depth() + Lexer(io::read_file_from_string, ZString name, XString str) + : _in(io::from_string, name, str), _current(TOK_EOF), _span(), _depth() { adv(); } + Lexer(io::read_file_from_string, ZString name, LString str) + : _in(io::from_string, name, str), _current(TOK_EOF), _span(), _depth() + { adv(); } + Lexeme peek() { return _current; } void adv() { _current = _adv(); } ZString val_string() { return _string; } diff --git a/src/sexpr/lexer_test.cpp b/src/sexpr/lexer_test.cpp index 5904894..d84312e 100644 --- a/src/sexpr/lexer_test.cpp +++ b/src/sexpr/lexer_test.cpp @@ -29,22 +29,6 @@ namespace tmwa { -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; -} - TEST(sexpr, escape) { EXPECT_EQ(sexpr::escape('\0'), "\\x00"_s); @@ -73,7 +57,7 @@ TEST(sexpr, escape) TEST(sexpr, lexer) { io::LineSpan span; - sexpr::Lexer lexer(""_s, string_pipe(" foo( ) 123\"\" \n"_s)); + sexpr::Lexer lexer(io::from_string, ""_s, " foo( ) 123\"\" \n"_s); EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN); EXPECT_EQ(lexer.val_string(), "foo"_s); EXPECT_EQ(lexer.span().error_str("test"_s), @@ -120,7 +104,7 @@ TEST(sexpr, lexbad) QuietFd q; { io::LineSpan span; - sexpr::Lexer lexer(""_s, string_pipe("(\n"_s)); + sexpr::Lexer lexer(io::from_string, ""_s, "(\n"_s); EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN); lexer.adv(); EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); @@ -135,7 +119,7 @@ TEST(sexpr, lexbad) }) { io::LineSpan span; - sexpr::Lexer lexer(""_s, string_pipe(bad)); + sexpr::Lexer lexer(io::from_string, ""_s, bad); EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); } } diff --git a/src/sexpr/parser_test.cpp b/src/sexpr/parser_test.cpp index 8619c15..bbaf5eb 100644 --- a/src/sexpr/parser_test.cpp +++ b/src/sexpr/parser_test.cpp @@ -27,27 +27,11 @@ namespace tmwa { -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; -} - TEST(sexpr, parser) { sexpr::SExpr s; io::LineSpan span; - sexpr::Lexer lexer(""_s, string_pipe(" foo( ) 123\"\" \n"_s)); + sexpr::Lexer lexer(io::from_string, ""_s, " foo( ) 123\"\" \n"_s); EXPECT_TRUE(sexpr::parse(lexer, s)); EXPECT_EQ(s._type, sexpr::TOKEN); @@ -72,7 +56,7 @@ TEST(sexpr, parser) TEST(sexpr, parselist) { sexpr::SExpr s; - sexpr::Lexer lexer(""_s, string_pipe("(foo)(bar)\n"_s)); + sexpr::Lexer lexer(io::from_string, ""_s, "(foo)(bar)\n"_s); EXPECT_TRUE(sexpr::parse(lexer, s)); EXPECT_EQ(s._type, sexpr::LIST); @@ -108,7 +92,7 @@ TEST(sexpr, parsebad) { sexpr::SExpr s; io::LineSpan span; - sexpr::Lexer lexer(""_s, string_pipe(bad)); + sexpr::Lexer lexer(io::from_string, ""_s, bad); EXPECT_FALSE(sexpr::parse(lexer, s)); EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); } -- cgit v1.2.3-60-g2f50