This article presents a simulation of coroutines in unmanaged C++ (Win32 console application) under Visual Studio 2010 using the data type, macro and function defined in the header file <setjmp.h>.
Introduction
Subroutines (functions that return no values) are always discussed in the context of a master/slave relationship: a calling program (master) invokes the subroutine (slave), which executes from beginning to end and returns to the caller. Subroutines are so subservient that they are not even allowed to remember their own local data between successive calls, unless static variables are used. Coroutines replace this master/slave structure with a set of cooperating modules with no identifiable master. Consider the following problem statement by R.W. Floyd (in “The Paradigms of Programming.” Communications of the ACM, Vol. 22, No. 8, August 1979, p. 458):
Read lines from a text file, until a completely blank line is found. Eliminate redundant blanks between the words. Print the text, thirty characters to a line, without breaking words between lines.
According to Floyd, novice programmers take an unreasonably long time to solve this problem using typical programming languages. Even though both input and output are naturally expressed using levels of iteration, the input and output iterations do not mesh, which can make controlling the input and output an “undisciplined mess.”
A variant of Floyd’s problem is to read lines from a text file until detecting the end of the file. Redundant blanks between adjacent words are to be eliminated. The text must be printed on an output file, with a maximum of m characters to a line, and without breaking words between lines. As an example, consider the following input file (column numbers and lines of dashes are used for descriptive purposes, and are not part of the file):
11111111112222222222333333333344444444445555555555666
12345678901234567890123456789012345678901234567890123456789012
--------------------------------------------------------------
44
He [Sarek] was delighted to discover how very much like
him they [computer people] were ... All they
cared about was the art of their
work, and doing it right. It was hard not
to admire such dedication and love of
computing for its own sake.
The programmers were the first
Earth people he came to understand as being
really human.
Diane Duane.- "Spock's World."
--------------------------------------------------------------
The first line of the file is an integer (44 in the example) specifying the maximum number of characters to a line on the output file. The remaining lines contain the text to be formatted. There can be an arbitrary number of blank spaces between adjacent words, and the text can be broken arbitrarily by new-line characters. The only assumption is that there are no blank lines in the input text.
This problem can easily be solved using a programming language that supports either coroutine linkage or parallel processing. It can also be solved under a multitasking operating system, using suitable programming language extensions to define and manipulate tasks. The input and output computations are naturally expressed in terms of iteration, and they are meshed by means of cooperating modules. A possible logical organization of a program to solve this problem is shown below:
The main function initiates the computation by activating coroutine PrintWord
, which is responsible for creating the formatted output file. In order to do its job, PrintWord
must get words from the input file, and activates coroutine GetWord
to start assembling those words. In turn, GetWord
must obtain individual characters to assemble words, and activates coroutinee GetChar
to start getting characters from the input file. Each time it gets a character, GetChar
passes it to GetWord
in a shared global variable (say inChar
). GetWord
then stores the character in a buffer (say wordBuff
) representing a word. When a natural break (a blank character) between words is found, GetWord
informs PrintWord
about the new word in the buffer. When activated, PrintWord
prints the contents of wordBuff
into the output file (observing the restriction on the line length), followed by a single blank. The computation terminates when GetChar
detects the end of the input file. To signal the end of the input file, GetChar
may set a flag or send a special character to GetWord
, which in turn may set up an empty WordBuff
to PrintWord
.
According to the foregoing description, the result of processing the sample input file would be as shown below (again, column numbers and lines of dashes are used for descriptive purposes, and are not part of the output file).
11111111112222222222333333333344444
12345678901234567890123456789012345678901234
--------------------------------------------
He [Sarek] was delighted to discover how
very much like him they [computer people]
were ... All they cared about was the art of
their work, and doing it right. It was hard
not to admire such dedication and love of
computing for its own sake. The programmers
were the first Earth people he came to
understand as being really human. Diane
Duane.- "Spock's World."
--------------------------------------------
The C/C++ programming languages provide somewhat cumbersome support for coroutines by means of the data type jmp_buf
, the macro setjmp
, and the function longjmp
(all defined in the standard header <setjmp.h>
) which implement a form of non-local goto.
The above considerations allow writing the following line-formatting program.
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#define InLineLength 82
#define WordBuffLength 80
typedef void coroutine;
FILE *fp; char inLine[ InLineLength ], inChar, wordBuff[ WordBuffLength ]; int wbPos, outLineLength, endOfFile = 0; jmp_buf env0, env1, env2, env3, env4;
coroutine PrintWord();
coroutine GetWord();
coroutine GetChar();
void main( int argc, char *argv[] )
{
int i;
if ( argc != 2 )
{
printf( "You must specify a file name\n" );
printf( "as in: lformat0 <FileName>\n" );
}
else if ( (fp = fopen( argv[ 1 ], "r" )) == NULL )
{
printf( "File '%s' not found\n", argv[ 1 ] );
}
else {
fscanf( fp, "%d\n", &outLineLength );
i = setjmp( env0 ); if ( !i )
{
PrintWord(); }
fclose( fp );
}
}
coroutine PrintWord()
{
static int col, i, j;
printf( "\n" );
col = 0;
j = setjmp( env3 ); if ( !j )
{
GetWord(); }
while ( !endOfFile )
{
if ( col == 0 );
else
if ( col + wbPos + 1 < outLineLength )
{
printf( " " );
++col;
}
else {
printf( "\n" );
col = 0;
}
for ( i = 0; i < wbPos; ++i )
{
printf( "%c", wordBuff[ i ] );
++col;
}
j = setjmp( env3 ); if ( !j )
{
longjmp( env4, 1 ); }
}
printf( "\n" );
longjmp( env0, 1 ); }
coroutine GetWord()
{
static int i, firstCall = 1;
while ( 1 )
{
wbPos = 0;
do {
i = setjmp( env1 ); if ( !i )
{
if ( firstCall )
{
firstCall = 0;
GetChar(); }
else {
longjmp( env2, 1 ); }
}
if ( endOfFile )
{
longjmp( env3, 1 ); }
} while ( inChar == ' ' );
do {
if ( wbPos < outLineLength - 1 )
{
wordBuff[ wbPos++ ] = inChar;
}
i = setjmp( env1 ); if ( !i )
{
longjmp( env2, 1 ); }
if ( endOfFile )
{
longjmp( env3, 1 ); }
} while ( inChar != ' ' );
i = setjmp( env4 ); if ( !i )
{
longjmp( env3, 1 ); }
}
}
coroutine GetChar()
{
static int i, lp = 0, len = 0;
while ( !endOfFile && !feof( fp ) )
{
if ( lp == len )
{
if ( fgets( inLine, InLineLength, fp ) != NULL )
{
lp = 0;
len = strlen( inLine );
}
else endOfFile = 1;
}
if ( !endOfFile )
{
inChar = inLine[ lp++ ];
while ( inChar != '\n' )
{
i = setjmp( env2 ); if ( !i )
{
longjmp( env1, 1 ); }
inChar = inLine[ lp++ ];
}
inChar = ' ';
i = setjmp( env2 ); if ( !i )
{
longjmp( env1, 1 ); }
}
}
endOfFile = 1;
longjmp( env1, 1 ); }
Using the Code
As stated, the program was compiled in unmanaged C++, that is as a Win32 console application. The two warnings in function main
indicate that functions fopen
and fscanf
may be unsafe and that to disable deprecation, the flag _CRT_SECURE_NO_WARNINGS
must be used. To do so, right-click on the project name in bold and select Properties. Then on the left panel in the window that appears, expand the entry C/C++ and select Preprocessor. On the right panel of the next window that appears, click on the down arrow to the right of the bold Preprocessor Definitions and then click on <Edit...>. In the top panel of the next window, under _CONSOLE
, add the line _CRT_SECURE_NO_WARNINGS
. Finally, click on Apply, wait for the change to be applied, and then click on OK.
To run the program, save the input file (SAREK_IN.txt or whatever file you would like to process) in the Debug directory of the console application. Then, in the Visual Studio Tools menu, open a Command Prompt, change to the Debug directory and type:
Lformat SAREK_IN.txt
The command prompt window will display the result of the formatting of the input file. You can use redirection to create an output file by typing, for example:
Lformat SAREK_IN.txt >SAREK_OUT.txt
History
- 26th August, 2020: Initial version
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.