summaryrefslogtreecommitdiff
path: root/src/ast
diff options
context:
space:
mode:
authorBen Longbons <b.r.longbons@gmail.com>2014-10-29 14:56:55 -0700
committerBen Longbons <b.r.longbons@gmail.com>2014-10-29 14:56:55 -0700
commit1a00fe4ea75924bfe594c4d92073cc95eaa2f32d (patch)
treea6e8992e0821a998236a4dfb5d6f6194a0b7ce97 /src/ast
parent469991120bcf550b6e2124203103876b6b7be918 (diff)
downloadtmwa-1a00fe4ea75924bfe594c4d92073cc95eaa2f32d.tar.gz
tmwa-1a00fe4ea75924bfe594c4d92073cc95eaa2f32d.tar.bz2
tmwa-1a00fe4ea75924bfe594c4d92073cc95eaa2f32d.tar.xz
tmwa-1a00fe4ea75924bfe594c4d92073cc95eaa2f32d.zip
Item AST
Diffstat (limited to 'src/ast')
-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
8 files changed, 496 insertions, 77 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