summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ast/item.cpp160
-rw-r--r--src/ast/item.hpp80
-rw-r--r--src/ast/item_test.cpp150
-rw-r--r--src/ast/npc.cpp53
-rw-r--r--src/ast/npc.hpp10
-rw-r--r--src/ast/npc_test.cpp82
-rw-r--r--src/ast/script.cpp14
-rw-r--r--src/ast/script.hpp24
-rw-r--r--src/io/fwd.hpp2
-rw-r--r--src/io/line.cpp107
-rw-r--r--src/io/line.hpp59
-rw-r--r--src/io/line_test.cpp50
-rw-r--r--src/io/read.cpp38
-rw-r--r--src/io/read.hpp14
-rw-r--r--src/io/read_test.cpp61
-rw-r--r--src/io/span.cpp99
-rw-r--r--src/io/span.hpp90
-rw-r--r--src/main-gdb-head.py2
-rw-r--r--src/sexpr/lexer.hpp9
-rw-r--r--src/sexpr/lexer_test.cpp22
-rw-r--r--src/sexpr/parser_test.cpp22
21 files changed, 905 insertions, 243 deletions
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 <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 "../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<Spanned<RString>> 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<ast::script::ScriptBody> 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<std::unique_ptr<ItemOrComment>> parse_item(io::LineCharReader& lr)
+ {
+ std::unique_ptr<ItemOrComment> rv = nullptr;
+ Spanned<RString> first = TRY_UNWRAP(lex_nonscript(lr, true), return Ok(std::move(rv)));
+ if (first.data.startswith("//"_s))
+ {
+ Comment comment;
+ comment.span = first.span;
+ comment.comment = first.data;
+ rv = make_unique<Comment>(std::move(comment));
+ return Ok(std::move(rv));
+ }
+ 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<Item>(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 <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 <memory>
+
+#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<ItemNameId> id;
+ Spanned<ItemName> name;
+ Spanned<ItemName> jname;
+ Spanned<ItemType> type;
+ Spanned<int> buy_price;
+ Spanned<int> sell_price;
+ Spanned<int> weight;
+ Spanned<int> atk;
+ Spanned<int> def;
+ Spanned<int> range;
+ Spanned<int> magic_bonus;
+ Spanned<RString> slot_unused;
+ Spanned<SEX> gender;
+ Spanned<EPOS> loc;
+ Spanned<int> wlv;
+ Spanned<int> elv;
+ Spanned<ItemLook> view;
+ ast::script::ScriptBody use_script;
+ ast::script::ScriptBody equip_script;
+ };
+
+ Result<std::unique_ptr<ItemOrComment>> parse_item(io::LineCharReader& lr);
+} // namespace item
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/item_test.cpp b/src/ast/item_test.cpp
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 <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 "../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, "<string>"_s, input);
+ auto res = parse_item(lr);
+ EXPECT_EQ(res.get_success(), Some(std::unique_ptr<ItemOrComment>(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, "<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<Comment *>(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, "<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<Item *>(top.get());
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->id.span, 1,1, 1,1);
+ EXPECT_EQ(p->id.data, wrap<ItemNameId>(1));
+ EXPECT_SPAN(p->name.span, 1,3, 1,6);
+ EXPECT_EQ(p->name.data, stringish<ItemName>("abc "_s));
+ EXPECT_SPAN(p->jname.span, 1,10, 1,12);
+ EXPECT_EQ(p->jname.data, stringish<ItemName>("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<Spanned<RString>> 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<RString> 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<RString> 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<RString> 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<RString> bit;
- bit.span = span;
- bit.data = RString(accum);
- return Some(std::move(bit));
+ return Some(respan(span, RString(accum)));
}
Result<std::unique_ptr<TopLevel>> 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<std::unique_ptr<TopLevel>> 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 <gtest/gtest.h>
+#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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
auto res = parse_top(lr);
EXPECT_EQ(res.get_success(), Some(std::unique_ptr<TopLevel>(nullptr)));
}
}
- TEST(ast, comment)
+ TEST(npcast, comment)
{
QuietFd q;
LString inputs[] =
@@ -83,10 +69,12 @@ namespace parse
};
for (auto input : inputs)
{
- io::LineCharReader lr("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<Comment *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<Warp *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<Shop *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<Monster *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<MapFlag *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<ScriptFunction *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<ScriptNone *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<ScriptMapNone *>(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("<string>"_s, string_pipe(input));
+ io::LineCharReader lr(io::from_string, "<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<ScriptMap *>(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 <http://www.gnu.org/licenses/>.
+#include "../io/line.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
-namespace script
+namespace ast
{
-namespace parse
+namespace script
{
- Result<ScriptBody> parse_script_body(io::LineCharReader& lr)
+ Result<ScriptBody> 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<ScriptBody> 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<ScriptBody> 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 <http://www.gnu.org/licenses/>.
#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<int>(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<int>(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<class T>
- 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, "<string1text>"_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, "<stringchar1text>"_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 <b.r.longbons@gmail.com>
+// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// 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 <b.r.longbons@gmail.com>
+// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// 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 <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 "../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 <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 "../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<class T>
+ struct Spanned
+ {
+ T data;
+ LineSpan span;
+ };
+
+ template<class T>
+ Spanned<T> respan(LineSpan span, T data)
+ {
+ return Spanned<T>{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 = '<heap 0x%x>' % addr
+ s = '(%s)<heap/stack 0x%x>' % (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("<lexer-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s));
+ sexpr::Lexer lexer(io::from_string, "<lexer-test1>"_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("<lexer-bad>"_s, string_pipe("(\n"_s));
+ sexpr::Lexer lexer(io::from_string, "<lexer-bad>"_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("<lexer-bad>"_s, string_pipe(bad));
+ sexpr::Lexer lexer(io::from_string, "<lexer-bad>"_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("<parser-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s));
+ sexpr::Lexer lexer(io::from_string, "<parser-test1>"_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("<parser-test1>"_s, string_pipe("(foo)(bar)\n"_s));
+ sexpr::Lexer lexer(io::from_string, "<parser-test1>"_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("<parse-bad>"_s, string_pipe(bad));
+ sexpr::Lexer lexer(io::from_string, "<parse-bad>"_s, bad);
EXPECT_FALSE(sexpr::parse(lexer, s));
EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
}