Skip to content

Commit

Permalink
Reimplement generic buffered reader
Browse files Browse the repository at this point in the history
Wraps any reader in a generic way.
  • Loading branch information
AlexMax committed Dec 9, 2023
1 parent 0572141 commit 5e362f9
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
project(lexio LANGUAGES CXX)

set(LEXIO_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/lexio/bufreader.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/lexio/bytes.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/lexio/core.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/lexio/lexio.hpp"
Expand Down
79 changes: 79 additions & 0 deletions include/lexio/bufreader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// Copyright 2023 Lexi Mayfield
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#pragma once

#include "./core.hpp"

namespace LexIO
{

template <typename T>
class GenericBufReader
{
T m_wrapped;
uint8_t *m_buffer = nullptr;
size_t m_allocSize = 0;
size_t m_size = 0;

public:
GenericBufReader(T &&wrapped) : m_wrapped(wrapped) {}

size_t LexRead(uint8_t *outDest, const size_t count)
{
BufferView data = LexFillBuffer(count);
std::copy(data.first, data.first + data.second, outDest);
LexConsumeBuffer(data.second);
return data.second;
}

BufferView LexFillBuffer(const size_t count)
{
if (count <= m_size)
{
// We already have enough data buffered.
return BufferView{m_buffer, m_size};
}

if (count > m_allocSize)
{
// Reallocate our buffer with any existing data.
uint8_t *buffer = ::new uint8_t[count];
std::copy(&m_buffer[0], &m_buffer[m_size], buffer);
::delete[] m_buffer;
m_buffer = buffer;
m_allocSize = count;
}

// Read into the buffer.
const size_t wanted = count - m_size;
const size_t actual = LexIO::Read<T>(&m_buffer[m_size], wanted, m_wrapped);
m_size += actual;
return BufferView{m_buffer, m_size};
}

void LexConsumeBuffer(const size_t count)
{
if (count > m_size)
{
throw new std::runtime_error("can't consume more bytes than buffer size");
}
std::copy(&m_buffer[count], &m_buffer[m_size], &m_buffer[0]);
m_size -= count;
}
};

} // namespace LexIO
1 change: 1 addition & 0 deletions include/lexio/lexio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "./core.hpp"

#include "./bufreader.hpp"
#include "./bytes.hpp"
#include "./ref.hpp"
#include "./serialize.hpp"
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ include(CTest)
include(Catch)

set(TEST_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/test_bufreader.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test_bytes.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test_container.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test_core.cpp"
Expand Down
177 changes: 177 additions & 0 deletions tests/test_bufreader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//
// Copyright 2023 Lexi Mayfield
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "./test.h"
#include "catch2/catch_all.hpp"

//******************************************************************************

template <typename T, std::size_t N>
constexpr std::size_t CountOf(T const (&)[N]) noexcept
{
return N;
}

constexpr const uint8_t TEXT_BUFFER[] = "The quick brown fox\njumps over the lazy dog.\n";
constexpr const size_t TEXT_BUFFER_SIZE = CountOf(TEXT_BUFFER) - 1;

static LexIO::VectorStream GetStream()
{
LexIO::VectorStream rvo;
rvo.LexWrite(&TEXT_BUFFER[0], TEXT_BUFFER_SIZE);
rvo.LexSeek(LexIO::SeekPos(0, LexIO::seek::start));
return rvo;
}

TEST_CASE("FillBuffer, single call")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

auto test = LexIO::FillBuffer(bufReader, 8);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[7] == 'c');
REQUIRE(test.second == 8);
}

TEST_CASE("FillBuffer, multiple calls")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

// Buffer initial four bytes.
auto test = LexIO::FillBuffer(bufReader, 4);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[3] == ' ');
REQUIRE(test.second == 4);

// Buffer less than what we had before, should read nothing.
test = LexIO::FillBuffer(bufReader, 2);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[3] == ' ');
REQUIRE(test.second == 4);

// Buffer more than what we had before.
test = LexIO::FillBuffer(bufReader, 8);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[3] == ' ');
REQUIRE(test.first[4] == 'q');
REQUIRE(test.first[7] == 'c');
REQUIRE(test.second == 8);
}

TEST_CASE("FillBuffer, EOF")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

// Buffer everything.
auto test = LexIO::FillBuffer(bufReader, 64);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[TEXT_BUFFER_SIZE - 1] == '\n');
REQUIRE(test.second == TEXT_BUFFER_SIZE);

// Buffer more than everything.
test = LexIO::FillBuffer(bufReader, 96);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[TEXT_BUFFER_SIZE - 1] == '\n');
REQUIRE(test.second == TEXT_BUFFER_SIZE);
}

TEST_CASE("FillBuffer, EOF with initial buffer")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

auto test = LexIO::FillBuffer(bufReader, 4);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[3] == ' ');
REQUIRE(test.second == 4);

// Buffer everything.
test = LexIO::FillBuffer(bufReader, 64);
REQUIRE(test.first[0] == 'T');
REQUIRE(test.first[3] == ' ');
REQUIRE(test.first[4] == 'q');
REQUIRE(test.first[TEXT_BUFFER_SIZE - 1] == '\n');
REQUIRE(test.second == TEXT_BUFFER_SIZE);
}

TEST_CASE("FillBuffer, zero sized read")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

auto test = LexIO::FillBuffer(bufReader, 0);
REQUIRE(test.second == 0);
}

TEST_CASE("ConsumeBuffer, single call")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

// Fill, then consume whole buffer.
LexIO::FillBuffer(bufReader, 8);
REQUIRE_NOTHROW(LexIO::ConsumeBuffer(bufReader, 8));

auto test = LexIO::GetBuffer(bufReader);
REQUIRE(test.second == 0);

// Subsequent read should pick up where we left off.
test = LexIO::FillBuffer(bufReader, 8);
REQUIRE(test.first[0] == 'k');
REQUIRE(test.first[7] == ' ');
REQUIRE(test.second == 8);
}

TEST_CASE("ConsumeBuffer, multiple calls")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

// Consume half the buffer.
LexIO::FillBuffer(bufReader, 8);
REQUIRE_NOTHROW(LexIO::ConsumeBuffer(bufReader, 4));
auto test = LexIO::GetBuffer(bufReader);
REQUIRE(test.first[0] == 'q');
REQUIRE(test.first[3] == 'c');
REQUIRE(test.second == 4);

// Consume the other half of the buffer.
REQUIRE_NOTHROW(LexIO::ConsumeBuffer(bufReader, 4));
test = LexIO::GetBuffer(bufReader);
REQUIRE(test.second == 0);
}

TEST_CASE("ConsumeBuffer, EOF")
{
auto stream = GetStream();
auto bufReader = LexIO::GenericBufReader(std::move(stream));

// Fill to EOF, consume part of it.
auto test = LexIO::FillBuffer(bufReader, 64);
REQUIRE_NOTHROW(LexIO::ConsumeBuffer(bufReader, 4));
test = LexIO::GetBuffer(bufReader);
REQUIRE(test.first[0] == 'q');
REQUIRE(test.first[3] == 'c');
REQUIRE(test.second == TEXT_BUFFER_SIZE - 4);

// Consume the rest of it.
REQUIRE_NOTHROW(LexIO::ConsumeBuffer(bufReader, TEXT_BUFFER_SIZE - 4));
test = LexIO::GetBuffer(bufReader);
REQUIRE(test.second == 0);
}

0 comments on commit 5e362f9

Please sign in to comment.