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 ++++++-- 8 files changed, 496 insertions(+), 77 deletions(-) create mode 100644 src/ast/item.cpp create mode 100644 src/ast/item.hpp create mode 100644 src/ast/item_test.cpp (limited to 'src/ast') 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 -- cgit v1.2.3-60-g2f50