Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C++
Article

Simulating Coroutines in Unmanaged C++

Rate me:
Please Sign up or sign in to vote.
2.00/5 (1 vote)
27 Aug 2020MIT4 min read 4.6K   65   5   4
Simulation of coroutines in unmanaged C++ under VS 2010
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:

Image 1

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++
// 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:

C++
Lformat SAREK_IN.txt >SAREK_OUT.txt

History

  • 26th August, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralNot that ideal Pin
Chad3F1-Sep-20 10:19
Chad3F1-Sep-20 10:19 
QuestionComments Pin
David O'Neil30-Aug-20 14:37
professionalDavid O'Neil30-Aug-20 14:37 
GeneralMessage Closed Pin
30-Aug-20 2:41
Chelsi Ved30-Aug-20 2:41 
QuestionUnder Windows try using Fibers instead of setjmp/longjmp Pin
lordvictory28-Aug-20 7:08
lordvictory28-Aug-20 7:08 
GeneralReally C, not C++ Pin
Greg Utas27-Aug-20 2:00
professionalGreg Utas27-Aug-20 2:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.