diff options
author | asynts <asynts@gmail.com> | 2020-08-05 12:14:44 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-08-06 10:33:16 +0200 |
commit | 5bfa7749c35d79475e39ac41d11cdd72e5d0da19 (patch) | |
tree | 2d42182416093067ad3a27c27bf9a13bbd8f155c /AK | |
parent | 75cde94c6a72be4f83a739a0a14258ca5f9a17dd (diff) | |
download | serenity-5bfa7749c35d79475e39ac41d11cdd72e5d0da19.zip |
AK: Add InputStream abstraction and InputMemoryStream implementation.
Diffstat (limited to 'AK')
-rw-r--r-- | AK/Forward.h | 4 | ||||
-rw-r--r-- | AK/Stream.h | 169 | ||||
-rw-r--r-- | AK/String.h | 23 | ||||
-rw-r--r-- | AK/Tests/TestMemoryStream.cpp | 111 |
4 files changed, 307 insertions, 0 deletions
diff --git a/AK/Forward.h b/AK/Forward.h index f42d36c9fd..47e8638699 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -48,6 +48,8 @@ class URL; class FlyString; class Utf32View; class Utf8View; +class InputStream; +class InputMemoryStream; template<typename T> class Span; @@ -129,6 +131,8 @@ using AK::Function; using AK::HashMap; using AK::HashTable; using AK::InlineLinkedList; +using AK::InputMemoryStream; +using AK::InputStream; using AK::IPv4Address; using AK::JsonArray; using AK::JsonObject; diff --git a/AK/Stream.h b/AK/Stream.h new file mode 100644 index 0000000000..6875578683 --- /dev/null +++ b/AK/Stream.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/Concepts.h> +#include <AK/Forward.h> +#include <AK/Span.h> +#include <AK/StdLibExtras.h> + +namespace AK::Detail { + +class Stream { +public: + virtual ~Stream() + { + ASSERT(!error() && !fatal()); + } + + bool error() const { return m_error; } + bool fatal() const { return m_fatal; } + + bool handle_error() { return exchange(m_error, false); } + + operator bool() const { return !m_error && !m_fatal; } + +protected: + mutable bool m_error { false }; + mutable bool m_fatal { false }; +}; + +} + +namespace AK { + +class InputStream : public AK::Detail::Stream { +public: + virtual size_t read(Bytes) = 0; + virtual bool read_or_error(Bytes) = 0; + virtual bool eof() const = 0; + virtual bool discard_or_error(size_t count) = 0; +}; + +// clang-format off + +template<Concepts::Integral Integral> +InputStream& operator>>(InputStream& stream, Integral& value) +{ + stream.read_or_error({ &value, sizeof(value) }); + return stream; +} + +#ifndef KERNEL +template<Concepts::FloatingPoint FloatingPoint> +InputStream& operator>>(InputStream& stream, FloatingPoint& value) +{ + stream.read_or_error({ &value, sizeof(value) }); + return stream; +} +#endif + +// clang-format on + +inline InputStream& operator>>(InputStream& stream, bool& value) +{ + stream.read_or_error({ &value, sizeof(value) }); + return stream; +} + +inline InputStream& operator>>(InputStream& stream, Bytes bytes) +{ + stream.read_or_error(bytes); + return stream; +} + +class InputMemoryStream final : public InputStream { + friend InputMemoryStream& operator>>(InputMemoryStream& stream, String& string); + +public: + InputMemoryStream(ReadonlyBytes bytes) + : m_bytes(bytes) + { + } + + bool eof() const override { return m_offset >= m_bytes.size(); } + + size_t read(Bytes bytes) override + { + const auto count = min(bytes.size(), remaining()); + __builtin_memcpy(bytes.data(), m_bytes.data() + m_offset, count); + m_offset += count; + return count; + } + + bool read_or_error(Bytes bytes) override + { + if (remaining() < bytes.size()) { + m_error = true; + return false; + } + + __builtin_memcpy(bytes.data(), m_bytes.data() + m_offset, bytes.size()); + m_offset += bytes.size(); + return true; + } + + bool discard_or_error(size_t count) override + { + if (remaining() < count) { + m_error = true; + return false; + } + + m_offset += count; + return true; + } + + void seek(size_t offset) + { + ASSERT(offset < m_bytes.size()); + m_offset = offset; + } + + u8 peek_or_error() const + { + if (remaining() == 0) { + m_error = true; + return 0; + } + + return m_bytes[m_offset]; + } + + ReadonlyBytes bytes() const { return m_bytes; } + size_t offset() const { return m_offset; } + size_t remaining() const { return m_bytes.size() - m_offset; } + +private: + ReadonlyBytes m_bytes; + size_t m_offset { 0 }; +}; + +} + +using AK::InputMemoryStream; +using AK::InputStream; diff --git a/AK/String.h b/AK/String.h index e1c5effef7..061d697b09 100644 --- a/AK/String.h +++ b/AK/String.h @@ -28,6 +28,7 @@ #include <AK/Forward.h> #include <AK/RefPtr.h> +#include <AK/Stream.h> #include <AK/StringImpl.h> #include <AK/StringUtils.h> #include <AK/Traits.h> @@ -275,6 +276,28 @@ bool operator<=(const char*, const String&); String escape_html_entities(const StringView& html); +inline InputMemoryStream& operator>>(InputMemoryStream& stream, String& string) +{ + // FIXME: There was some talking about a generic lexer class? + + const auto start = stream.offset(); + + while (!stream.eof() && stream.m_bytes[stream.m_offset]) { + ++stream.m_offset; + } + + if (stream.eof()) { + stream.m_error = true; + stream.m_offset = start; + string = nullptr; + } else { + string = String { stream.bytes().slice(start, stream.offset() - start) }; + ++stream.m_offset; + } + + return stream; +} + } using AK::CaseInsensitiveStringTraits; diff --git a/AK/Tests/TestMemoryStream.cpp b/AK/Tests/TestMemoryStream.cpp new file mode 100644 index 0000000000..2234ae5078 --- /dev/null +++ b/AK/Tests/TestMemoryStream.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/TestSuite.h> + +#include <AK/Stream.h> + +bool compare(ReadonlyBytes lhs, ReadonlyBytes rhs) +{ + if (lhs.size() != rhs.size()) + return false; + + for (size_t idx = 0; idx < lhs.size(); ++idx) { + if (lhs[idx] != rhs[idx]) + return false; + } + + return true; +} + +TEST_CASE(read_an_integer) +{ + u32 expected = 0x01020304, actual; + + InputMemoryStream stream { { &expected, sizeof(expected) } }; + stream >> actual; + + EXPECT(!stream.error() && !stream.fatal() && stream.eof()); + EXPECT_EQ(expected, actual); +} + +TEST_CASE(recoverable_error) +{ + u32 expected = 0x01020304, actual = 0; + u64 to_large_value = 0; + + InputMemoryStream stream { { &expected, sizeof(expected) } }; + + EXPECT(!stream.error() && !stream.fatal() && !stream.eof()); + stream >> to_large_value; + EXPECT(stream.error() && !stream.fatal() && !stream.eof()); + + EXPECT(stream.handle_error()); + EXPECT(!stream.error() && !stream.fatal() && !stream.eof()); + + stream >> actual; + EXPECT(!stream.error() && !stream.fatal() && stream.eof()); + EXPECT_EQ(expected, actual); +} + +TEST_CASE(chain_stream_operator) +{ + u8 expected[] { 0, 1, 2, 3 }, actual[4]; + + InputMemoryStream stream { { expected, sizeof(expected) } }; + + stream >> actual[0] >> actual[1] >> actual[2] >> actual[3]; + EXPECT(!stream.error() && !stream.fatal() && stream.eof()); + + EXPECT(compare({ expected, sizeof(expected) }, { actual, sizeof(actual) })); +} + +TEST_CASE(seeking_slicing_offset) +{ + u8 input[] { 0, 1, 2, 3, 4, 5, 6, 7 }, + expected0[] { 0, 1, 2, 3 }, + expected1[] { 4, 5, 6, 7 }, + expected2[] { 1, 2, 3, 4 }, + actual0[4], actual1[4], actual2[4]; + + InputMemoryStream stream { { input, sizeof(input) } }; + + stream >> Bytes { actual0, sizeof(actual0) }; + EXPECT(!stream.error() && !stream.fatal() && !stream.eof()); + EXPECT(compare({ expected0, sizeof(expected0) }, { actual0, sizeof(actual0) })); + + stream.seek(4); + stream >> Bytes { actual1, sizeof(actual1) }; + EXPECT(!stream.error() && !stream.fatal() && stream.eof()); + EXPECT(compare({ expected1, sizeof(expected1) }, { actual1, sizeof(actual1) })); + + stream.seek(1); + stream >> Bytes { actual2, sizeof(actual2) }; + EXPECT(!stream.error() && !stream.fatal() && !stream.eof()); + EXPECT(compare({ expected2, sizeof(expected2) }, { actual2, sizeof(actual2) })); +} + +TEST_MAIN(MemoryStream) |