diff options
Diffstat (limited to 'src/io')
-rw-r--r-- | src/io/cxxstdio.cpp | 26 | ||||
-rw-r--r-- | src/io/cxxstdio_enums.cpp | 26 | ||||
-rw-r--r-- | src/io/cxxstdio_enums.hpp | 77 | ||||
-rw-r--r-- | src/io/dir.hpp | 2 | ||||
-rw-r--r-- | src/io/extract.cpp | 220 | ||||
-rw-r--r-- | src/io/extract.hpp | 234 | ||||
-rw-r--r-- | src/io/extract_test.cpp | 452 | ||||
-rw-r--r-- | src/io/fd.cpp | 5 | ||||
-rw-r--r-- | src/io/fd.hpp | 2 | ||||
-rw-r--r-- | src/io/fwd.hpp | 13 | ||||
-rw-r--r-- | src/io/line.cpp | 107 | ||||
-rw-r--r-- | src/io/line.hpp | 46 | ||||
-rw-r--r-- | src/io/line_test.cpp | 66 | ||||
-rw-r--r-- | src/io/read.cpp | 47 | ||||
-rw-r--r-- | src/io/read.hpp | 14 | ||||
-rw-r--r-- | src/io/read_test.cpp | 67 | ||||
-rw-r--r-- | src/io/span.cpp | 113 | ||||
-rw-r--r-- | src/io/span.hpp | 92 | ||||
-rw-r--r-- | src/io/tty.cpp | 27 | ||||
-rw-r--r-- | src/io/write.cpp | 2 | ||||
-rw-r--r-- | src/io/write.hpp | 2 |
21 files changed, 1361 insertions, 279 deletions
diff --git a/src/io/cxxstdio.cpp b/src/io/cxxstdio.cpp deleted file mode 100644 index ca4e880..0000000 --- a/src/io/cxxstdio.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "cxxstdio.hpp" -// cxxstdio.cpp - pass C++ types through printf -// -// Copyright © 2013 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 "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/io/cxxstdio_enums.cpp b/src/io/cxxstdio_enums.cpp deleted file mode 100644 index 216da1d..0000000 --- a/src/io/cxxstdio_enums.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "cxxstdio_enums.hpp" -// cxxstdio_enums.cpp - Opt-in integer formatting support for enums. -// -// 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 "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/io/cxxstdio_enums.hpp b/src/io/cxxstdio_enums.hpp deleted file mode 100644 index 05cdcae..0000000 --- a/src/io/cxxstdio_enums.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once -// cxxstdio_enums.hpp - Opt-in integer formatting support for enums. -// -// 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 <cstdint> - -#include "../generic/enum.hpp" - - -namespace tmwa -{ -namespace e -{ -enum class BF : uint16_t; -enum class EPOS : uint16_t; -enum class MapCell : uint8_t; -enum class Option : uint16_t; - -inline -auto decay_for_printf(BF v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(EPOS v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(MapCell v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(Option v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -} - -namespace magic -{ -enum class SPELLARG : uint8_t; - -inline -auto decay_for_printf(SPELLARG v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -} - -enum class BL : uint8_t; -enum class ByteCode : uint8_t; -enum class ItemLook : uint16_t; -enum class MS : uint8_t; -enum class SP : uint16_t; -enum class SkillID : uint16_t; -enum class StatusChange : uint16_t; - -inline -auto decay_for_printf(BL v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(ByteCode v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(ItemLook v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(MS v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(SP v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(SkillID v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(StatusChange v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -} // namespace tmwa diff --git a/src/io/dir.hpp b/src/io/dir.hpp index 071f309..f6fedbf 100644 --- a/src/io/dir.hpp +++ b/src/io/dir.hpp @@ -20,8 +20,6 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - #include "fd.hpp" diff --git a/src/io/extract.cpp b/src/io/extract.cpp new file mode 100644 index 0000000..fce4dab --- /dev/null +++ b/src/io/extract.cpp @@ -0,0 +1,220 @@ +#include "extract.hpp" +// extract.cpp - a simple, hierarchical, tokenizer +// +// Copyright © 2013-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 <algorithm> + +#include "../strings/astring.hpp" +#include "../strings/xstring.hpp" +#include "../strings/vstring.hpp" + +#include "../poison.hpp" + + +// TODO also pass an io::LineSpan around. +namespace tmwa +{ +bool impl_extract(XString str, XString *rv) +{ + *rv = str; + return true; +} + +bool impl_extract(XString str, RString *rv) +{ + *rv = str; + return true; +} + +bool impl_extract(XString str, AString *rv) +{ + *rv = str; + return true; +} + +bool impl_extract(XString str, std::chrono::nanoseconds *ns) +{ + std::chrono::nanoseconds::rep rep; + if (extract(str, &rep)) + { + *ns = std::chrono::nanoseconds(rep); + return true; + } + if (str.endswith("ns"_s)) + { + if (extract(str.xrslice_h("ns"_s.size()), &rep)) + { + *ns = std::chrono::nanoseconds(rep); + return true; + } + return false; + } + std::chrono::microseconds bigger; + if (extract(str, &bigger)) + { + *ns = bigger; + return *ns == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::microseconds *us) +{ + std::chrono::microseconds::rep rep; + if (extract(str, &rep)) + { + *us = std::chrono::microseconds(rep); + return true; + } + if (str.endswith("us"_s)) + { + if (extract(str.xrslice_h("us"_s.size()), &rep)) + { + *us = std::chrono::microseconds(rep); + return true; + } + return false; + } + std::chrono::milliseconds bigger; + if (extract(str, &bigger)) + { + *us = bigger; + return *us == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::milliseconds *ms) +{ + std::chrono::milliseconds::rep rep; + if (extract(str, &rep)) + { + *ms = std::chrono::milliseconds(rep); + return true; + } + if (str.endswith("ms"_s)) + { + if (extract(str.xrslice_h("ms"_s.size()), &rep)) + { + *ms = std::chrono::milliseconds(rep); + return true; + } + return false; + } + std::chrono::seconds bigger; + if (extract(str, &bigger)) + { + *ms = bigger; + return *ms == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::seconds *s) +{ + std::chrono::seconds::rep rep; + if (extract(str, &rep)) + { + *s = std::chrono::seconds(rep); + return true; + } + if (str.endswith("s"_s)) + { + if (extract(str.xrslice_h("s"_s.size()), &rep)) + { + *s = std::chrono::seconds(rep); + return true; + } + return false; + } + std::chrono::minutes bigger; + if (extract(str, &bigger)) + { + *s = bigger; + return *s == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::minutes *min) +{ + std::chrono::minutes::rep rep; + if (extract(str, &rep)) + { + *min = std::chrono::minutes(rep); + return true; + } + if (str.endswith("min"_s)) + { + if (extract(str.xrslice_h("min"_s.size()), &rep)) + { + *min = std::chrono::minutes(rep); + return true; + } + return false; + } + std::chrono::hours bigger; + if (extract(str, &bigger)) + { + *min = bigger; + return *min == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::hours *h) +{ + std::chrono::hours::rep rep; + if (extract(str, &rep)) + { + *h = std::chrono::hours(rep); + return true; + } + if (str.endswith("h"_s)) + { + if (extract(str.xrslice_h("h"_s.size()), &rep)) + { + *h = std::chrono::hours(rep); + return true; + } + return false; + } + std::chrono::duration<int, std::ratio<60*60*24>> bigger; + if (extract(str, &bigger)) + { + *h = bigger; + return *h == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d) +{ + std::chrono::duration<int, std::ratio<60*60*24>>::rep rep; + if (extract(str, &rep)) + { + *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep); + return true; + } + if (str.endswith("d"_s)) + { + if (extract(str.xrslice_h("d"_s.size()), &rep)) + { + *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep); + return true; + } + return false; + } + return false; +} +} // namespace tmwa diff --git a/src/io/extract.hpp b/src/io/extract.hpp new file mode 100644 index 0000000..897f50e --- /dev/null +++ b/src/io/extract.hpp @@ -0,0 +1,234 @@ +#pragma once +// extract.hpp - a simple, hierarchical, tokenizer +// +// Copyright © 2012-2013 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 <cerrno> +#include <cstdlib> + +#include <algorithm> +#include <chrono> +#include <vector> + +#include "../ints/wrap.hpp" + +#include "../strings/xstring.hpp" + +#include "../compat/time_t.hpp" + +#include "../generic/enum.hpp" + + +namespace tmwa +{ +template<class T> +bool extract(XString str, T t); + +template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && !std::is_same<T, bool>::value>::type> +bool impl_extract(XString str, T *iv) +{ + if (!str || str.size() > 20) + return false; + if (!((str.front() == '-' && std::is_signed<T>::value) + || ('0' <= str.front() && str.front() <= '9'))) + return false; + // needs a NUL, but can't always be given one. TODO VString? + char buf[20 + 1]; + std::copy(str.begin(), str.end(), buf); + buf[str.size()] = '\0'; + + char *end; + errno = 0; + if (std::is_signed<T>::value) + { + long long v = strtoll(buf, &end, 10); + if (errno || *end) + return false; + *iv = v; + return *iv == v; + } + else + { + unsigned long long v = strtoull(buf, &end, 10); + if (errno || *end) + return false; + *iv = v; + return *iv == v; + } +} + +inline +bool impl_extract(XString str, TimeT *tv) +{ + return extract(str, &tv->value); +} + +template<class T, typename=typename std::enable_if<std::is_enum<T>::value>::type> +bool extract_as_int(XString str, T *iv) +{ + typedef typename underlying_type<T>::type U; + U v; + // defer to integer version + if (!extract(str, &v)) + return false; + // TODO check bounds using enum min/max as in SSCANF + *iv = static_cast<T>(v); + return true; +} + +bool impl_extract(XString str, XString *rv); +bool impl_extract(XString str, RString *rv); +bool impl_extract(XString str, AString *rv); + +template<uint8_t N> +bool impl_extract(XString str, VString<N> *out) +{ + if (str.size() > N) + return false; + *out = str; + return true; +} + +inline +bool impl_extract(XString str, LString exact) +{ + return str == exact; +} + +template<class T> +class LStripper +{ +public: + T impl; +}; + +template<class T> +LStripper<T> lstripping(T v) +{ + return {v}; +} + +template<class T> +bool impl_extract(XString str, LStripper<T> out) +{ + return extract(str.lstrip(), out.impl); +} + +// basically just a std::tuple +// but it provides its data members publically +template<char split, int n, class... T> +class Record; +template<char split, int n> +class Record<split, n> +{ +}; +template<char split, int n, class F, class... R> +class Record<split, n, F, R...> +{ +public: + F frist; + Record<split, n - 1, R...> rest; +public: + Record(F f, R... r) + : frist(f), rest(r...) + {} +}; +template<char split, class... T> +Record<split, sizeof...(T), T...> record(T... t) +{ + return Record<split, sizeof...(T), T...>(t...); +} +template<char split, int n, class... T> +Record<split, n, T...> record(T... t) +{ + static_assert(0 < n && n < sizeof...(T), "don't be silly"); + return Record<split, n, T...>(t...); +} + +template<char split, int n> +bool impl_extract(XString str, Record<split, n>) +{ + return !str; +} + +template<char split, int n, class F, class... R> +bool impl_extract(XString str, Record<split, n, F, R...> rec) +{ + XString::iterator s = std::find(str.begin(), str.end(), split); + XString::iterator s2 = s; + if (s2 != str.end()) + ++s2; + XString head = str.xislice_h(s); + XString tail = str.xislice_t(s2); + if (s == str.end()) + return (extract(head, rec.frist) && n <= 1) + || (!head && n <= 0); + + return (extract(head, rec.frist) || n <= 0) + && extract(tail, rec.rest); +} + +template<char split, class T> +struct VRecord +{ + std::vector<T> *arr; +}; + +template<char split, class T> +VRecord<split, T> vrec(std::vector<T> *arr) +{ + return {arr}; +} + +template<char split, class T> +bool impl_extract(XString str, VRecord<split, T> rec) +{ + if (!str) + return true; + XString::iterator s = std::find(str.begin(), str.end(), split); + rec.arr->emplace_back(); + if (s == str.end()) + return extract(str, &rec.arr->back()); + return extract(str.xislice_h(s), &rec.arr->back()) + && extract(str.xislice_t(s + 1), rec); +} + +template<class R> +bool impl_extract(XString str, Wrapped<R> *w) +{ + return extract(str, &w->_value); +} + +bool impl_extract(XString str, std::chrono::nanoseconds *ns); +bool impl_extract(XString str, std::chrono::microseconds *us); +bool impl_extract(XString str, std::chrono::milliseconds *ms); +bool impl_extract(XString str, std::chrono::seconds *s); +bool impl_extract(XString str, std::chrono::minutes *min); +bool impl_extract(XString str, std::chrono::hours *h); +bool impl_extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d); + +// this must come after all non-`namespace tmwa` `impl_extract`s. +// In particular, the ones for `*int*` and `std::chrono::*` +template<class T> +bool extract(XString str, T t) +{ + return impl_extract(str, t); +} +} // namespace tmwa diff --git a/src/io/extract_test.cpp b/src/io/extract_test.cpp new file mode 100644 index 0000000..ee4cb08 --- /dev/null +++ b/src/io/extract_test.cpp @@ -0,0 +1,452 @@ +#include "extract.hpp" +// extract_test.cpp - Testsuite for a simple, hierarchical, tokenizer +// +// Copyright © 2013 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 "../strings/xstring.hpp" + +#include "../net/timer.t.hpp" + +#include "../mmo/strs.hpp" + +#include "../high/extract_mmo.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +TEST(extract, record_int) +{ + int x, y, z; + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(" "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(""_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + + EXPECT_FALSE(extract("1 2 3 4 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract("1"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(" "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(""_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + + EXPECT_FALSE(extract("1 2 3 4 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_FALSE(extract("1 2 3 4"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 3"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(3, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 2"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(2, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_TRUE(extract("1"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(1, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(" "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; + EXPECT_FALSE(extract(""_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(0, z); + x = y = z = 0; +} + +TEST(extract, record_str) +{ + XString x, y, z; + x = y = z = ""_s; + EXPECT_FALSE(extract("1 2 3 4 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1 2 3 4"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 3 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 3"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1 2"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1 "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1"_s, record<' '>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract(" "_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(""_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract(""_s, record<' '>(&x, &y, &z))); + EXPECT_EQ(""_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + + EXPECT_FALSE(extract("1 2 3 4 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1 2 3 4"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 3 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 3"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1"_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract(" "_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(""_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract(""_s, record<' ', 2>(&x, &y, &z))); + EXPECT_EQ(""_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + + EXPECT_FALSE(extract("1 2 3 4 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_FALSE(extract("1 2 3 4"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 3 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 3"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ("3"_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 2"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ("2"_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1 "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract("1"_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ("1"_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract(" "_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(""_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; + EXPECT_TRUE(extract(""_s, record<' ', 1>(&x, &y, &z))); + EXPECT_EQ(""_s, x); + EXPECT_EQ(""_s, y); + EXPECT_EQ(""_s, z); + x = y = z = ""_s; +} + +TEST(extract, mapname) +{ + MapName map; + EXPECT_TRUE(extract("abc"_s, &map)); + EXPECT_EQ(map, "abc"_s); + EXPECT_TRUE(extract("abc.gat"_s, &map)); + EXPECT_EQ(map, "abc"_s); + EXPECT_TRUE(extract("abcdefghijklmno"_s, &map)); + EXPECT_EQ(map, "abcdefghijklmno"_s); + EXPECT_TRUE(extract("abcdefghijklmno.gat"_s, &map)); + EXPECT_EQ(map, "abcdefghijklmno"_s); +} + +TEST(extract, chrono) +{ + std::chrono::nanoseconds ns; + std::chrono::microseconds us; + std::chrono::milliseconds ms; + std::chrono::seconds s; + std::chrono::minutes min; + std::chrono::hours h; + std::chrono::duration<int, std::ratio<60*60*24>> d; + + EXPECT_TRUE(extract("1"_s, &ns)); + EXPECT_EQ(ns, 1_ns); + EXPECT_TRUE(extract("3ns"_s, &ns)); + EXPECT_EQ(ns, 3_ns); + EXPECT_TRUE(extract("4us"_s, &ns)); + EXPECT_EQ(ns, 4_us); + EXPECT_TRUE(extract("5ms"_s, &ns)); + EXPECT_EQ(ns, 5_ms); + EXPECT_TRUE(extract("6s"_s, &ns)); + EXPECT_EQ(ns, 6_s); + EXPECT_TRUE(extract("7min"_s, &ns)); + EXPECT_EQ(ns, 7_min); + EXPECT_TRUE(extract("8h"_s, &ns)); + EXPECT_EQ(ns, 8_h); + EXPECT_TRUE(extract("9d"_s, &ns)); + EXPECT_EQ(ns, 9_d); + + EXPECT_TRUE(extract("1"_s, &us)); + EXPECT_EQ(us, 1_us); + EXPECT_TRUE(extract("4us"_s, &us)); + EXPECT_EQ(us, 4_us); + EXPECT_TRUE(extract("5ms"_s, &us)); + EXPECT_EQ(us, 5_ms); + EXPECT_TRUE(extract("6s"_s, &us)); + EXPECT_EQ(us, 6_s); + EXPECT_TRUE(extract("7min"_s, &us)); + EXPECT_EQ(us, 7_min); + EXPECT_TRUE(extract("8h"_s, &us)); + EXPECT_EQ(us, 8_h); + EXPECT_TRUE(extract("9d"_s, &us)); + EXPECT_EQ(us, 9_d); + + EXPECT_TRUE(extract("1"_s, &ms)); + EXPECT_EQ(ms, 1_ms); + EXPECT_TRUE(extract("5ms"_s, &ms)); + EXPECT_EQ(ms, 5_ms); + EXPECT_TRUE(extract("6s"_s, &ms)); + EXPECT_EQ(ms, 6_s); + EXPECT_TRUE(extract("7min"_s, &ms)); + EXPECT_EQ(ms, 7_min); + EXPECT_TRUE(extract("8h"_s, &ms)); + EXPECT_EQ(ms, 8_h); + EXPECT_TRUE(extract("9d"_s, &ms)); + EXPECT_EQ(ms, 9_d); + + EXPECT_TRUE(extract("1"_s, &s)); + EXPECT_EQ(s, 1_s); + EXPECT_TRUE(extract("6s"_s, &s)); + EXPECT_EQ(s, 6_s); + EXPECT_TRUE(extract("7min"_s, &s)); + EXPECT_EQ(s, 7_min); + EXPECT_TRUE(extract("8h"_s, &s)); + EXPECT_EQ(s, 8_h); + EXPECT_TRUE(extract("9d"_s, &s)); + EXPECT_EQ(s, 9_d); + + EXPECT_TRUE(extract("1"_s, &min)); + EXPECT_EQ(min, 1_min); + EXPECT_TRUE(extract("7min"_s, &min)); + EXPECT_EQ(min, 7_min); + EXPECT_TRUE(extract("8h"_s, &min)); + EXPECT_EQ(min, 8_h); + EXPECT_TRUE(extract("9d"_s, &min)); + EXPECT_EQ(min, 9_d); + + EXPECT_TRUE(extract("1"_s, &h)); + EXPECT_EQ(h, 1_h); + EXPECT_TRUE(extract("8h"_s, &h)); + EXPECT_EQ(h, 8_h); + EXPECT_TRUE(extract("9d"_s, &h)); + EXPECT_EQ(h, 9_d); + + EXPECT_TRUE(extract("1"_s, &d)); + EXPECT_EQ(d, 1_d); + EXPECT_TRUE(extract("9d"_s, &d)); + EXPECT_EQ(d, 9_d); +} +} // namespace tmwa diff --git a/src/io/fd.cpp b/src/io/fd.cpp index c0b44e8..bb0bbd5 100644 --- a/src/io/fd.cpp +++ b/src/io/fd.cpp @@ -102,6 +102,11 @@ namespace io int FD::close() { + if (fd == -1) + { + errno = EBADF; + return -1; + } return ::close(fd); } int FD::shutdown(int how) diff --git a/src/io/fd.hpp b/src/io/fd.hpp index d04d5bf..03a8b44 100644 --- a/src/io/fd.hpp +++ b/src/io/fd.hpp @@ -23,8 +23,6 @@ #include <sys/select.h> #include <sys/socket.h> -#include "../strings/fwd.hpp" - #include "../diagnostics.hpp" diff --git a/src/io/fwd.hpp b/src/io/fwd.hpp index deeb08c..3b9452b 100644 --- a/src/io/fwd.hpp +++ b/src/io/fwd.hpp @@ -20,6 +20,12 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +// io/fwd.hpp is rank 4 + namespace tmwa { @@ -28,5 +34,12 @@ namespace io class ReadFile; class WriteFile; class AppendFile; + class LineReader; + class LineCharReader; + class Line; + class LineChar; + class LineSpan; + template<class T> + class Spanned; } // 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 8244c5e..c94eeb9 100644 --- a/src/io/line.hpp +++ b/src/io/line.hpp @@ -21,55 +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; - 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; - 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); } - }; - class LineReader { protected: @@ -83,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(); @@ -97,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 edf60bd..582ee81 100644 --- a/src/io/line_test.cpp +++ b/src/io/line_test.cpp @@ -23,6 +23,8 @@ #include "../strings/astring.hpp" #include "../strings/zstring.hpp" +#include "../tests/fdhack.hpp" + #include "../poison.hpp" @@ -57,6 +59,7 @@ TEST(io, line1) } TEST(io, line2) { + QuietFd q; io::LineReader lr("<string2>"_s, string_pipe("Hello\nWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -73,6 +76,7 @@ TEST(io, line2) } TEST(io, line3) { + QuietFd q; io::LineReader lr("<string3>"_s, string_pipe("Hello\rWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -89,6 +93,7 @@ TEST(io, line3) } TEST(io, line4) { + QuietFd q; io::LineReader lr("<string4>"_s, string_pipe("Hello\r\nWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -105,6 +110,7 @@ TEST(io, line4) } TEST(io, line5) { + QuietFd q; io::LineReader lr("<string5>"_s, string_pipe("Hello\n\rWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -124,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) { @@ -175,6 +195,7 @@ TEST(io, linechar1) } TEST(io, linechar2) { + QuietFd q; io::LineCharReader lr("<stringchar2>"_s, string_pipe("Hi\nWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -223,6 +244,7 @@ TEST(io, linechar2) } TEST(io, linechar3) { + QuietFd q; io::LineCharReader lr("<stringchar3>"_s, string_pipe("Hi\rWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -271,6 +293,7 @@ TEST(io, linechar3) } TEST(io, linechar4) { + QuietFd q; io::LineCharReader lr("<stringchar4>"_s, string_pipe("Hi\r\nWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -319,6 +342,7 @@ TEST(io, linechar4) } TEST(io, linechar5) { + QuietFd q; io::LineCharReader lr("<stringchar5>"_s, string_pipe("Hi\n\rWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -372,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) { @@ -402,17 +462,17 @@ TEST(io, linespan) } while (span.end.ch() != 'r'); - EXPECT_EQ(span.begin.message_str("note"_s, "foo"_s), + EXPECT_EQ(span.begin.note_str("foo"_s), "<span>:1:5: note: foo\n" "Hello,\n" " ^\n"_s ); - EXPECT_EQ(span.end.message_str("warning"_s, "bar"_s), + EXPECT_EQ(span.end.warning_str("bar"_s), "<span>:2:3: warning: bar\n" "World!\n" " ^\n"_s ); - EXPECT_EQ(span.message_str("error"_s, "qux"_s), + EXPECT_EQ(span.error_str("qux"_s), "<span>:1:5: error: qux\n" "Hello,\n" " ^~ ...\n" diff --git a/src/io/read.cpp b/src/io/read.cpp index 3ae5246..32974d6 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) // @@ -25,7 +25,7 @@ #include "../strings/zstring.hpp" #include "../strings/literal.hpp" -#include "../io/cxxstdio.hpp" +#include "cxxstdio.hpp" #include "../poison.hpp" @@ -36,6 +36,8 @@ namespace io { ReadFile::ReadFile(FD f) : fd(f), start(0), end(0) + // only for debug-sanity + , buf{} { } ReadFile::ReadFile(ZString name) @@ -46,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(); @@ -54,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()) @@ -82,6 +118,7 @@ namespace io } bool ReadFile::getline(AString& line) { + bool was_real_file = fd != FD(); MString tmp; char c; bool anything = false; @@ -115,15 +152,17 @@ namespace io else FPRINTF(stderr, "warning: file contains bare CR\n"_fmt); } - else if (!happy && anything) + else if (!happy && anything && was_real_file) + { FPRINTF(stderr, "warning: file does not contain a trailing newline\n"_fmt); + } line = AString(tmp); return anything; } 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 1ec26ca..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,7 +20,7 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" +#include "../strings/rstring.hpp" #include "dir.hpp" #include "fd.hpp" @@ -29,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 8fe84b7..22c67c8 100644 --- a/src/io/read_test.cpp +++ b/src/io/read_test.cpp @@ -24,6 +24,8 @@ #include "../strings/zstring.hpp" #include "../strings/literal.hpp" +#include "../tests/fdhack.hpp" + #include "../poison.hpp" @@ -47,6 +49,7 @@ io::FD string_pipe(ZString sz) TEST(io, read1) { + QuietFd q; io::ReadFile rf(string_pipe("Hello"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -63,6 +66,7 @@ TEST(io, read2) } TEST(io, read3) { + QuietFd q; io::ReadFile rf(string_pipe("Hello\r"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -71,6 +75,7 @@ TEST(io, read3) } TEST(io, read4) { + QuietFd q; io::ReadFile rf(string_pipe("Hello\r\n"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -79,6 +84,7 @@ TEST(io, read4) } TEST(io, read5) { + QuietFd q; io::ReadFile rf(string_pipe("Hello\n\r"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -87,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..6d116c7 --- /dev/null +++ b/src/io/span.cpp @@ -0,0 +1,113 @@ +#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 +{ + io::LineSpan Line::to_span() const + { + io::LineSpan rv; + rv.begin.text = this->text; + rv.begin.filename = this->filename; + rv.begin.line = this->line; + rv.begin.column = 1; + rv.end.text = this->text; + rv.end.filename = this->filename; + rv.end.line = this->line; + rv.end.column = this->text.size(); + return rv; + } + + 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..9962b7c --- /dev/null +++ b/src/io/span.hpp @@ -0,0 +1,92 @@ +#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); } + + LineSpan to_span() const; + }; + + // 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/io/tty.cpp b/src/io/tty.cpp deleted file mode 100644 index c498740..0000000 --- a/src/io/tty.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "tty.hpp" -// io/tty.cpp - terminal escape sequences -// -// 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 "../poison.hpp" - - -namespace tmwa -{ -/* Nothing to see here, move along */ -} // namespace tmwa diff --git a/src/io/write.cpp b/src/io/write.cpp index 5359c7a..a98954b 100644 --- a/src/io/write.cpp +++ b/src/io/write.cpp @@ -37,6 +37,8 @@ namespace io { WriteFile::WriteFile(FD f, bool linebuffered) : fd(f), lb(linebuffered), buflen(0) + // only for debug-sanity + , buf{} {} WriteFile::WriteFile(ZString name, bool linebuffered) : fd(FD::open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)), lb(linebuffered), buflen(0) diff --git a/src/io/write.hpp b/src/io/write.hpp index 1ab05f3..d7d3699 100644 --- a/src/io/write.hpp +++ b/src/io/write.hpp @@ -22,8 +22,6 @@ #include <cstdarg> -#include "../strings/fwd.hpp" - #include "dir.hpp" #include "fd.hpp" |