class line_buffer {
};
Freelance software grandad
software created
extended or repaired
Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations
STinC++ rewrites the programs in Software Tools in Pascal using C++
As promised, I spent the afternoon programming. I did some futzing around beforehand and afterwards, obviously, but I did write some code. Then I went to Pembrokeshire for the weekend and one thing and another, so it’s taken a few days to get back to the keyboard.
As we type lines of text into our editor, that text needs to be stored somewhere. Somewhere is rather vague, so let’s use the established term of art.
class line_buffer {
};
How many lines are there in the buffer? Best be able to ask.
class line_buffer {
public:
size_t size() const;
};
I chose size
here, rather than say count
or length
because the standard library uses size
.
The most basic edit function there is to add a line into the buffer. No need to worry about changing or removing yet, let’s just get some text in there.
class line_buffer {
public:
void insert(size_t index, std::string_view line);
size_t size() const;
};
Again, insert
aligns with the names used in the standard library.
Having put a line in the buffer, I’d really like to be to have a look at it. You know, just to be sure.
class line_buffer {
public:
void insert(size_t index, std::string_view line);
std::string_view operator[](size_t index) const;
size_t size() const;
};
If I give the complete class declaration, you’ll know what all three lines of the implementation are.
class line_buffer {
public:
void insert(size_t index, std::string_view line);
std::string_view operator[](size_t index) const;
size_t size() const;
private:
std::vector<std::string> lines_;
};
It’s hardly what you call feature complete, but it’s does a small thing. It’s a step, a little uncertain step, forward.
One of the ideas that comes up over and over in Kernighan and Plauger’s description of edit
is the current line, aka dot
. Commands operate, in the absence of other instructions, on the current line, on dot
. Commands can also update dot
.
This dot
thing is obviously pretty important. I’ve put line_buffer
in charge of looking after the lines of text. I didn’t want it to have to worry about dot
too. So, I threw a new class around line_buffer
.
class edit_buffer {
private:
line_buffer buffer_;
];
Let’s find out what dot
is …
class edit_buffer {
public:
size_t dot() const;
private:
line_buffer buffer_;
};
While line_buffer
is concerned with the mechanics of holding onto some text, and shuffling it around,
I want to build out the members of edit_buffer
to match, or at least more closely align with, the various commands we need to implement. Again, I started with inserting some text, which is the i
command.
|
So, we’ll have a place in the buffer where we want to add our new line, and the line itself.
class edit_buffer {
public:
void insert_before(size_t index, std::string_view line);
size_t dot() const;
private:
line_buffer buffer_;
};
Inserting text changes two elements of our program state, the text in the buffer and the current line, so insert_before
implementation became the most complex code I’d written to that point.
void edit_buffer::insert_before(size_t index, std::string_view line) {
buffer_.insert(index, line);
dot_ = index+1;
}
edit
(like ed
on which it’s modelled) indexes from 1. dot
is only ever 0 when buffer is entirely empty. I initially started building out line_buffer
and edit_buffer
to index from 1 rather than from 0, but the cognitive clash with years, nay decades, of zero-indexing was just too much to deal with even in the handful of lines I’d written. I switched to zero-indexing, resolving to leave the translation to indexing from 1 to a higher level of the code[1].
Laziness and indecision prevented me from adding anything to edit_buffer
to actually get hold of the lines we might have inserted. Instead, I contented myself with being able to see the dot
and the length of the edit_buffer
change as lines were inserted.
class edit_buffer {
public:
void insert_before(size_t index, std::string_view line);
size_t dot() const;
size_t last() const;
private:
line_buffer buffer_;
};
last
gives our other special index, $
, the last line in the buffer.
Onwards and, as they say, upwards. edit
is an interactive program, in as much as it reads input from the console, and pops any output back to the console. We have, in our journey through Software Tools in Pascal written an armful of programs that do this. We know exactly what their entry point function looks like.
class editor {
public:
void process(std::istream& in, std::ostream& out);
};
Yes, process
is an awful name for a function, but I couldn’t think of a better one (nor may I yet).
We’ve got a decent idea about the shape of the function implementation too.
void editor::process(std::istream& in, std::ostream& out)
{
while(in.peek() != eof)
{
auto line = stiX::getline(in);
// transform line in some way
}
}
When we fire up our editor, we’ll read the input a line at a time, and try and do something with it.
But what? I had edit_buffer
and line_buffer
, and the two tiny elements of functionality they provided
inserting a line
telling us the current value of dot
I read back through the program description[2].
What’s this?
(.) =p print line number The trailing p, which is optional, causes the last affected line to be printed. Dot is set to the last affected line, except for f, w, and =, for which it is unchanged. |
Well, I think we manage that to hook that up without too much difficulty …
void editor::process(std::istream& in, std::ostream& out) {
while(in.peek() != eof) {
auto line = stiX::getline(in);
if (line == "=")
out << buffer_.dot() << "\n";
else
out << "?\n";
}
}
This is pretty low energy stuff, I’ll grant you. My gentle afternoon’s, well work is strong word, nurdle around had, by now, resulted in less than a dozen lines of "implementation" and perhaps two or three times that in TDD scaffolding[3]. But I had a feel for the shape of the code, where the future work would sit, and time to form ideas about which bits would be more awkward and which wouldn’t. That’s a good place to end up.
I was about to knock off and walk the dogs, when I had a sudden thought, sat back down and bashed out two last lines.
int main(int argc, char const* argv[]) {
auto editor = stiX::editor();
editor.process(std::cin, std::cout);
}
Compile, link, and …
You’d struggle to build a less substantial walking skeleton, but I’d argue that’s a good thing. There’s the merest scrap of end-to-end functionality, but it’s there in a real program I can run and type commands into.
I’ve worked in organisations where we’d do a spontaneous show-and-tell at this point, which I suppose is what this whole article is.
The full source code, with tests and what-not, for edit, indeed the whole project, is available in the stiX GitHub repository. Forgive me if I suggest that you might find the history more interesting that the naked source.
This whole endeavour relies on Software Tools in Pascal and Software Tools, both by Brian W Kernighan and PJ Plauger. I love these books and commend them to you. They are both still in print, but new copies are, frankly, ridiculously expensive. Happily, there are plenty of second-hand copies around, or you can borrow them from The Internet Archive using the links above.
For this project I’ve been using JetBrain’s CLion, which I liked enough to buy and keep renewing a license for. I use it all the time now.
Freelance software grandad
software created
extended or repaired
Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations