3
\$\begingroup\$

This question is a spiritual successor to Console class that handles multiple buffers. I was mostly interested in implementing one of the suggestions of using an iterator range and fixing the interface for Terminal. That means most of the code will be absent. Also, while the code above is intended for a freestanding environment, mine is in a hosted one and uses the standard library. Stuff like std::iterator and std::copy are easy to implement, meaning that I can easily convert it once I start developing the full application.

Here's an example of improvements over the original code:

// Original
void Terminal::clrbuf(int buffer)
{
 for (int i = buffers[buffer].ybase * buffers[buffer].columns; 
 i < (buffers[buffer].ybase + buffers[buffer].rows) 
 * buffers[buffer].columns; ++i)
 {
 video_buffer[i] = make_vgaentry(' ', buffers[buffer].color);
 }
}
// Mine
void clear_buffer()
{
 for (auto& c : BufferRange{current_buffer()})
 {
 c = ' ';
 }
 current_buffer().xpos = 0;
 current_buffer().ypos = 0;
}
// Original
for (int i = buffers[current_buffer].ybase
 * buffers[current_buffer].columns; 
 i < (buffers[current_buffer].ybase + buffers[current_buffer].rows - 1) 
 * buffers[current_buffer].columns; i++)
{
 video_buffer[i] = video_buffer[i + buffers[current_buffer].columns];
}
 // Mine
auto range = BufferRange{*buffer};
auto start = range.begin();
// (buffer->ybase + Buffer::rows - 1) * Buffer::columns
auto end = range.end() - Buffer::columns;
for (auto it = start; it != end; ++it)
{
 *it = it[Buffer::columns];
}

My idea is to emphasize abstraction by hiding the details behind iterators while strengthening interopability with alogrithms. Example:

void puts(const char* s)
{
 std::copy(s, s + std::strlen(s),
 BufferWriteIterator{current_buffer()});
}

The issue is that BufferWriteIterator does all of the work (in adjusting the internals and scrolling). However, my goal was to make classes related to Buffer do as little work as possible and make it the responsibility of Terminal to do that. I'm not sure of a good solution however, because I don't want BufferWriteIterator to care about Terminal either (i.e, having it as a member variable). On the other hand, the code is designed to affect the current buffer and not other buffers, suggesting that the Terminal shouldn't be responsible for it either.

Is this a good approach?

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <iterator>
class Buffer
{
private:
 char buffer[1024];
public:
 int xpos;
 int ypos;
 // What line the buffer starts at
 int ybase;
 constexpr static int columns = 80;
 // This is how many lines each buffer gets.
 constexpr static int rows = 12;
 int current_pos()
 {
 return xpos + (ybase + ypos) * columns;
 }
 char& operator[](int idx)
 {
 assert(idx >= 0 && idx < 1024);
 return buffer[idx];
 }
};
struct BufferRange
{
 Buffer& buffer;
 char* begin() { return &buffer[buffer.ybase * Buffer::columns]; }
 char* end() { return &buffer[(buffer.ybase + Buffer::rows) * Buffer::columns]; }
};
class BufferWriteIterator
 : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
private:
 Buffer* buffer;
public:
 BufferWriteIterator()
 : buffer{nullptr}
 {}
 BufferWriteIterator(Buffer& b)
 : buffer{&b}
 {}
 BufferWriteIterator& operator=(char c)
 {
 if (c == '\n')
 {
 buffer->xpos = 0;
 buffer->ypos++;
 } else {
 (*buffer)[buffer->current_pos()] = c;
 buffer->xpos++;
 }
 if (buffer->xpos >= Buffer::columns)
 {
 buffer->xpos = 0;
 buffer->ypos++;
 }
 // Scroll
 if (buffer->ypos >= Buffer::rows) 
 {
 auto range = BufferRange{*buffer};
 auto start = range.begin();
 // (buffer->ybase + Buffer::rows - 1) * Buffer::columns
 auto end = range.end() - Buffer::columns;
 for (auto it = start; it != end; ++it)
 {
 *it = it[Buffer::columns];
 }
 start = end;
 end = range.end();
 for (auto it = start; it != end; ++it)
 {
 *it = ' ';
 }
 buffer->ypos = Buffer::rows - 1;
 }
 return *this;
 }
 BufferWriteIterator& operator*() {return *this;}
 BufferWriteIterator& operator++() {return *this;}
 BufferWriteIterator& operator++(int) {return *this;} 
};
class Terminal
{
private:
 Buffer buffers[2];
 int buffer_idx;
 Buffer& current_buffer()
 {
 return buffers[buffer_idx];
 }
public:
 Terminal()
 : buffers{}, buffer_idx{0}
 {
 buffers[0].ybase = 0;
 for (std::size_t i = 1; i < sizeof(buffers); ++i)
 {
 // Add 1 since there will be a horizontal
 // separator every 12th row
 buffers[i].ybase = Buffer::rows * i + 1;
 }
 }
 void clear_buffer()
 {
 for (auto& c : BufferRange{current_buffer()})
 {
 c = ' ';
 }
 current_buffer().xpos = 0;
 current_buffer().ypos = 0;
 }
 void puts(const char* s)
 {
 std::copy(s, s + std::strlen(s),
 BufferWriteIterator{current_buffer()});
 }
};
int main()
{
}
asked Oct 8, 2015 at 23:23
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.