function getarg(n : integer; var charstr : string; maxsize : integer) : boolean
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++
Chapter 2 of Software Tools in Pascal is entitled Filters, and thus far it’s been absolutely focused on processing streams of text. We’ve substituted tabs for spaces, messed around with printer pre-processing, compressed and expanded text files. Where on earth could we be going next? I’m going to bet you wouldn’t expect something that’s very decidedly not a filter in any shape or form.
PROGRAM |
|
USAGE |
|
FUNCTION |
|
EXAMPLE |
echo hello world! hello world! |
Weird huh?
In many ways, this is a little chill-out program - a light palate cleanser ahead of the big meaty one coming up next. It also allows Kernighan and Plauger to go off for a page on Pascal’s, or at least standard Pascal’s, weaknesses as a proper programming language. They don’t put it in quite those terms, but they bemoan its lack of provision for command line argument handling and, more seriously, the weaknesses of its type system, particularly its lack of dedicated string type. They work around the string type problem by using a fixed length array with an end-of-string sentinel, and “pay the price of wasted storage”. They are rather more coy on how they solved the command line argument problem, other than to say they’ve defined a primitive
function getarg(n : integer; var charstr : string; maxsize : integer) : boolean
which can grab the ith command line argument. echo
then is a few lines to demonstrate getarg
and their string type.
This is one of the few places where Software Tools in Pascal differs in any significant way from Software Tools. As far as I’m aware neither FORTRAN nor PL/1 had command line argument handling or a string type either, but the corresponding place in chapter 2 of Software Tools instead presents a trivial text encryption program. That program also uses a getarg
primitive of exactly the same sort, but it goes entirely unremarked.
C++ provides ready access to command line arguments and (setting aside the char*
legacy) offers a serviceable string type, and so echo
offers little challenge.
void echo(int argc, char const* argv[], std::ostream &out) {
auto arguments = make_arguments(argc, argv); (1)
join(
arguments,
std::ostream_iterator<std::string>(out)
); (2)
}
While char const* argv[]
has a certain retro charm, I like my data to be in a nice container that knows things like its own size. make_arguments
simply shuffles the contents of argv
into a std::vector<std::string>
. C++ can infer the type of the variable arguments
, hence I can declare it as auto
. At compile time, the compiler will cross out auto
and write in the std::vector<std::string>
for me.
And now we can pour those arguments, interspersed with spaces, down the out
stream. Job done. Sorry, pardon? join
you say?
In spite of having “Do The Simplest Thing That Could Possibly Work” taped to the side of my monitor (and I really do try to hold true to it), I chose to set that aside and indulge in a little standard library style nonsense.
template<typename InputIt, typename OutputIt, typename Separator = std::string>
OutputIt join(InputIt first, InputIt last, OutputIt dest, Separator sep = " ") {
if (first != last)
*dest++ = *first++;
while (first != last) {
*dest++ = sep;
*dest++ = *first++;
}
return dest;
}
You’re probably familiar with the general shape of join
- handle the initial value in the sequence, then loop through rest, prefixing them with the separator. The declaration and return type follow the general form of standard library. It is worth just taking a moment to consider just how much is going in
*dest++ = *first++;
I’m not sufficiently down in the details to be sure if there are four or five (or some other number) of separate operations occurring here. It’s something along the lines of
dereferencing the input iterator first, *first
, yields a value.
first++
advances the input iterator to the next value in the sequence
assigning to *dest
writes a value into the output iterator. In the case of echo
that’s to standard output, but it could equally be another container, a file, or almost anything else.
dest++
advances the write position.
After many years, many years of waiting, the standard library is finally catching up with the fact that most of the time the sequence we’re operating over is already neatly wrapped up in a container. Having to spell out mycontainer.begin()
and mycontainer.end()
is a bit redundant and it would be nice if we could just lob the whole shebang over in one go. In preparation of C++ 20 then, I put together a range version of join
. (I’m being overly flippant. This is the absolute least of what’s coming in ranges, which looks to be an important and exciting addition to the library.)
template<typename InputRange, typename OutputIt, typename Separator = std::string>
OutputIt join(InputRange&& range, OutputIt out, Separator sep = " ") {
return join(std::begin(range), std::end(range), out, sep);
}
This is, I believe, the more-or-less canonical form of a range adapter function. Jonathan Boccara discusses why passing the range with a forwarding reference, InputRange&&
, is the preferred form in Algorithms on Ranges. It’s a subtle business, and I’m happy to pass the heavy thinking off to some one else.
Source code for this program, indeed the whole project, is available in the stiX GitHub repository. echo
is program 5 of Chapter 2.
An old-school shell hack on a line printer describes hooking up an old printer to a Linux box as a proper old timey TTY - an actual teletypewriter. The screen you’re reading this on? It’s a pseudo-TTY - it’s just pretending to be a printer while this chap’s got the real deal just like Grandad used to have 🙂.
Freelance software grandad
software created
extended or repaired
Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations