summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/io/line.cpp141
-rw-r--r--src/io/line.hpp92
-rw-r--r--src/io/line_test.cpp350
3 files changed, 583 insertions, 0 deletions
diff --git a/src/io/line.cpp b/src/io/line.cpp
new file mode 100644
index 0000000..09dd8fb
--- /dev/null
+++ b/src/io/line.cpp
@@ -0,0 +1,141 @@
+#include "line.hpp"
+// io/line.hpp - Input from files, line-by-line
+//
+// 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 <fcntl.h>
+#include <unistd.h>
+
+#include "../strings/fstring.hpp"
+#include "../strings/mstring.hpp"
+#include "../strings/zstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "../poison.hpp"
+
+
+namespace io
+{
+ FString Line::message_str(ZString cat, ZString msg)
+ {
+ MString out;
+ if (column)
+ out += STRPRINTF("%s:%u:%u: %s: %s\n",
+ filename, line, column, cat, msg);
+ else
+ out += STRPRINTF("%s:%u: %s: %s\n",
+ filename, line, cat, msg);
+ out += STRPRINTF("%s\n", text);
+ out += STRPRINTF("%*c\n", column, '^');
+ return FString(out);
+ }
+
+ void Line::message(ZString cat, ZString msg)
+ {
+ if (column)
+ FPRINTF(stderr, "%s:%u:%u: %s: %s\n",
+ filename, line, column, cat, msg);
+ else
+ FPRINTF(stderr, "%s:%u: %s: %s\n",
+ filename, line, cat, msg);
+ FPRINTF(stderr, "%s\n", text);
+ FPRINTF(stderr, "%*c\n", column, '^');
+ }
+
+ LineReader::LineReader(ZString name)
+ : filename(name), line(0), column(0), rf(name)
+ {}
+
+ LineReader::LineReader(ZString name, int fd)
+ : filename(name), line(0), column(0), rf(fd)
+ {}
+
+ bool LineReader::read_line(Line& l)
+ {
+ if (rf.getline(l.text))
+ {
+ l.filename = filename;
+ l.line = ++line;
+ l.column = 0; // whole line
+ return true;
+ }
+ return false;
+ }
+
+ bool LineReader::is_open()
+ {
+ return rf.is_open();
+ }
+
+ LineCharReader::LineCharReader(ZString name)
+ : LineReader(name)
+ {
+ column = 1; // not 0, not whole line
+ if (rf.is_open())
+ adv();
+ if (!line)
+ column = 0;
+ }
+ // sigh, copy-paste
+ // in just a couple months I can drop support for gcc 4.6 though
+ LineCharReader::LineCharReader(ZString name, int fd)
+ : LineReader(name, fd)
+ {
+ column = 1; // not 0, not whole line
+ if (rf.is_open())
+ adv();
+ if (!line)
+ column = 0;
+ }
+
+ bool LineCharReader::get(LineChar& c)
+ {
+ if (!column)
+ return false;
+
+ c.text = line_text;
+ c.filename = filename;
+ c.line = line;
+ c.column = column;
+ return true;
+ }
+
+ void LineCharReader::adv()
+ {
+ if (column - 1 == line_text.size())
+ {
+ Line tmp;
+ if (!read_line(tmp))
+ {
+ // eof
+ column = 0;
+ return;
+ }
+ line_text = tmp.text;
+ column = 1;
+ }
+ else
+ column++;
+ }
+
+ bool LineCharReader::is_open()
+ {
+ return line != 0;
+ }
+} // namespace io
diff --git a/src/io/line.hpp b/src/io/line.hpp
new file mode 100644
index 0000000..6dccfd9
--- /dev/null
+++ b/src/io/line.hpp
@@ -0,0 +1,92 @@
+#ifndef TMWA_IO_LINE_HPP
+#define TMWA_IO_LINE_HPP
+// io/line.hpp - Input from files, line-by-line
+//
+// 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 "../sanity.hpp"
+
+# include "../strings/fstring.hpp"
+# include "../strings/zstring.hpp"
+
+# include "read.hpp"
+
+
+namespace io
+{
+ struct Line
+ {
+ FString text;
+
+ FString filename;
+ // 1-based
+ uint16_t line, column;
+
+ FString message_str(ZString cat, ZString msg);
+ void message(ZString cat, ZString msg);
+ void note(ZString msg) { message("note", msg); }
+ void warning(ZString msg) { message("warning", msg); }
+ void error(ZString msg) { message("error", 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];
+ }
+ };
+
+ class LineReader
+ {
+ protected:
+ FString filename;
+ uint16_t line, column;
+ ReadFile rf;
+ public:
+ explicit
+ LineReader(ZString name);
+ LineReader(LineReader&&) = delete;
+ // needed for unit tests
+ LineReader(ZString name, int fd);
+
+ bool read_line(Line& l);
+ bool is_open();
+ };
+
+ class LineCharReader : private LineReader
+ {
+ FString line_text;
+ public:
+ explicit
+ LineCharReader(ZString name);
+ LineCharReader(LineCharReader&&) = delete;
+ LineCharReader(ZString name, int fd);
+
+ bool get(LineChar& c);
+ void adv();
+ bool ok();
+ bool is_open();
+ };
+} // namespace io
+
+#endif //TMWA_IO_LINE_HPP
diff --git a/src/io/line_test.cpp b/src/io/line_test.cpp
new file mode 100644
index 0000000..8df6bbc
--- /dev/null
+++ b/src/io/line_test.cpp
@@ -0,0 +1,350 @@
+#include "line.hpp"
+
+#include <gtest/gtest.h>
+
+#include "../strings/zstring.hpp"
+
+static
+int string_pipe(ZString sz)
+{
+ int pfd[2];
+ if (-1 == pipe(pfd))
+ return -1;
+ if (sz.size() != write(pfd[1], sz.c_str(), sz.size()))
+ {
+ close(pfd[0]);
+ close(pfd[1]);
+ return -1;
+ }
+ close(pfd[1]);
+ return pfd[0];
+}
+
+TEST(io, line1)
+{
+ io::LineReader lr("<string1>", string_pipe("Hello World\n"));
+ io::Line hi;
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "Hello World");
+ EXPECT_EQ(hi.filename, "<string1>");
+ EXPECT_EQ(hi.line, 1);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_FALSE(lr.read_line(hi));
+}
+TEST(io, line2)
+{
+ io::LineReader lr("<string2>", string_pipe("Hello\nWorld"));
+ io::Line hi;
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "Hello");
+ EXPECT_EQ(hi.filename, "<string2>");
+ EXPECT_EQ(hi.line, 1);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "World");
+ EXPECT_EQ(hi.filename, "<string2>");
+ EXPECT_EQ(hi.line, 2);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_FALSE(lr.read_line(hi));
+}
+TEST(io, line3)
+{
+ io::LineReader lr("<string3>", string_pipe("Hello\rWorld"));
+ io::Line hi;
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "Hello");
+ EXPECT_EQ(hi.filename, "<string3>");
+ EXPECT_EQ(hi.line, 1);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "World");
+ EXPECT_EQ(hi.filename, "<string3>");
+ EXPECT_EQ(hi.line, 2);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_FALSE(lr.read_line(hi));
+}
+TEST(io, line4)
+{
+ io::LineReader lr("<string4>", string_pipe("Hello\r\nWorld"));
+ io::Line hi;
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "Hello");
+ EXPECT_EQ(hi.filename, "<string4>");
+ EXPECT_EQ(hi.line, 1);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "World");
+ EXPECT_EQ(hi.filename, "<string4>");
+ EXPECT_EQ(hi.line, 2);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_FALSE(lr.read_line(hi));
+}
+TEST(io, line5)
+{
+ io::LineReader lr("<string5>", string_pipe("Hello\n\rWorld"));
+ io::Line hi;
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "Hello");
+ EXPECT_EQ(hi.filename, "<string5>");
+ EXPECT_EQ(hi.line, 1);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "");
+ EXPECT_EQ(hi.filename, "<string5>");
+ EXPECT_EQ(hi.line, 2);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "World");
+ EXPECT_EQ(hi.filename, "<string5>");
+ EXPECT_EQ(hi.line, 3);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_FALSE(lr.read_line(hi));
+}
+
+TEST(io, linechar1)
+{
+ io::LineCharReader lr("<stringchar1>", string_pipe("Hi Wu\n"));
+ io::LineChar c;
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'H');
+ EXPECT_EQ(c.text, "Hi Wu");
+ EXPECT_EQ(c.filename, "<stringchar1>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'i');
+ EXPECT_EQ(c.text, "Hi Wu");
+ EXPECT_EQ(c.filename, "<stringchar1>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), ' ');
+ EXPECT_EQ(c.text, "Hi Wu");
+ EXPECT_EQ(c.filename, "<stringchar1>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'W');
+ EXPECT_EQ(c.text, "Hi Wu");
+ EXPECT_EQ(c.filename, "<stringchar1>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 4);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'u');
+ EXPECT_EQ(c.text, "Hi Wu");
+ EXPECT_EQ(c.filename, "<stringchar1>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 5);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Hi Wu");
+ EXPECT_EQ(c.filename, "<stringchar1>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 6);
+ lr.adv();
+ EXPECT_FALSE(lr.get(c));
+}
+TEST(io, linechar2)
+{
+ io::LineCharReader lr("<stringchar2>", string_pipe("Hi\nWu"));
+ io::LineChar c;
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'H');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar2>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'i');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar2>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar2>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'W');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar2>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'u');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar2>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar2>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_FALSE(lr.get(c));
+}
+TEST(io, linechar3)
+{
+ io::LineCharReader lr("<stringchar3>", string_pipe("Hi\rWu"));
+ io::LineChar c;
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'H');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar3>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'i');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar3>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar3>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'W');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar3>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'u');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar3>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar3>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_FALSE(lr.get(c));
+}
+TEST(io, linechar4)
+{
+ io::LineCharReader lr("<stringchar4>", string_pipe("Hi\r\nWu"));
+ io::LineChar c;
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'H');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar4>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'i');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar4>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar4>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'W');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar4>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'u');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar4>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar4>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_FALSE(lr.get(c));
+}
+TEST(io, linechar5)
+{
+ io::LineCharReader lr("<stringchar5>", string_pipe("Hi\n\rWu"));
+ io::LineChar c;
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'H');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'i');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "Hi");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ EXPECT_EQ(c.line, 1);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.text, "");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'W');
+ EXPECT_EQ(c.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ 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.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ 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.text, "Wu");
+ EXPECT_EQ(c.filename, "<stringchar5>");
+ EXPECT_EQ(c.line, 3);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_FALSE(lr.get(c));
+}