Simulating Coroutines in Unmanaged C++





2.00/5 (1 vote)
Simulation of coroutines in unmanaged C++ under VS 2010
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.
// C:\Users\Jorge\Documents\Visual Studio 2010\Projects\C++ unmanaged\Lformat
// \Lformat.cpp : Defines the entry point for the console application.
//
// Simple line-formatting program implemented by simulating coroutines
// via setjmp and longjmp.
//
// Programmer: Jorge L. Orejel
//
// Last update: 08/26/2020 : Implementation in unmanaged C++ (Win32 console
// application) under Visual Studio 2010.
//
// Based on: Program lformat0.c (last modified on 04/06/2004, and
// originally coded on 10/26/1989).
#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; // File-pointer
char inLine[ InLineLength ], // Input-line buffer
inChar, // Current character from inLine
wordBuff[ WordBuffLength ]; // Buffer to assemble words
int wbPos, // Current position in wordBuff
outLineLength, // Length of output lines
endOfFile = 0; // Flag to signal end of input file
jmp_buf env0, env1, env2, env3, env4; // Environment variables
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 // argc >= 2
//
// warning C4996: 'fopen': This function or variable may be unsafe.
// Consider using fopen_s instead.
// To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
//
if ( (fp = fopen( argv[ 1 ], "r" )) == NULL )
{
printf( "File '%s' not found\n", argv[ 1 ] );
}
else // fp != NULL
{
//
// warning C4996: 'fscanf': This function or variable may be unsafe.
// Consider using fscanf_s instead.
// To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
//
fscanf( fp, "%d\n", &outLineLength );
i = setjmp( env0 ); // Save state of main.
if ( !i )
{
PrintWord(); // Call PrintWord for first time.
}
fclose( fp );
}
}// main
coroutine PrintWord()
{
static int col, i, j;
printf( "\n" );
col = 0;
j = setjmp( env3 ); // Save state of PrintWord.
if ( !j )
{
GetWord(); // Call GetWord for first time.
}
while ( !endOfFile )
{
if ( col == 0 );
else
if ( col + wbPos + 1 < outLineLength )
{
printf( " " );
++col;
}
else // col + wbPos + 1 >= outLineLength
{
printf( "\n" );
col = 0;
}
for ( i = 0; i < wbPos; ++i )
{
printf( "%c", wordBuff[ i ] );
++col;
}
j = setjmp( env3 ); // Save state of PrintWord.
if ( !j )
{
longjmp( env4, 1 ); // Resume GetWord.
}
}
printf( "\n" );
longjmp( env0, 1 ); // Resume main.
}// PrintWord
coroutine GetWord()
{
static int i, firstCall = 1;
while ( 1 )
{
wbPos = 0;
do {
i = setjmp( env1 ); // Save state of GetWord.
if ( !i )
{
if ( firstCall )
{
firstCall = 0;
GetChar(); // Call GetChar for first time.
}
else // !firstCall
{
longjmp( env2, 1 ); // Resume GetChar.
}
}
if ( endOfFile )
{
longjmp( env3, 1 ); // Resume PrintWord.
}
} while ( inChar == ' ' );
do {
if ( wbPos < outLineLength - 1 )
{
wordBuff[ wbPos++ ] = inChar;
}
i = setjmp( env1 ); // Save state of GetWord.
if ( !i )
{
longjmp( env2, 1 ); // Resume GetChar.
}
if ( endOfFile )
{
longjmp( env3, 1 ); // Resume PrintWord.
}
} while ( inChar != ' ' );
i = setjmp( env4 ); // Save state of GetWord.
if ( !i )
{
longjmp( env3, 1 ); // Resume GetChar.
}
}
}// GetWord
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 ); // Save state of GetChar.
if ( !i )
{
longjmp( env1, 1 ); // Resume GetWord.
}
inChar = inLine[ lp++ ];
}
inChar = ' ';
i = setjmp( env2 ); // Save state of GetChar.
if ( !i )
{
longjmp( env1, 1 ); // Resume GetWord.
}
}
}
endOfFile = 1;
longjmp( env1, 1 ); // Resume GetWord.
}// GetChar
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