Jez Higgins

Freelance software grandad
software created
extended or repaired


Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations

Hire me
Contact

Older posts are available in the archive or through tags.

Feed

The Forest Road Reader, No 2.69 : STinC++ - edit, a barely walking skeleton

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.

The Buffer

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.

The Editor

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.

(.) i insert text before line (text follows)

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.

The Command Interface

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.

The Merest Suggestion of Walking Skeleton

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.

Source Code

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.

Endnotes

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.


1. And, happily, to some future afternoon.
2. One of the unusual aspects of this project is that we’re starting with detailed specs.
3. I dislike referring to the "non-production" code I write as part of a TDD cycle as test code, but I’m not sure that scaffolding is much better. Suggestions gratefully received.

Tagged code, and software-tools-in-c++


Jez Higgins

Freelance software grandad
software created
extended or repaired

Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations

Hire me
Contact

Older posts are available in the archive or through tags.

Feed