Last active
August 5, 2021 17:04
-
-
Save eschen42/fa0cd48139ba2ab4779caed65fcd676a to your computer and use it in GitHub Desktop.
Macros to declare C coroutines with syntax similar to Icon co-expressions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _COEXPR_H | |
// Here are C macros to approximate Icon co-expressions, see e.g., | |
// https://web.archive.org/web/20160407082917im_/http://www.cs.arizona.edu/icon/ftp/doc/tr87_6.pdf | |
// https://web.archive.org/web/20160407001546im_/http://www.cs.arizona.edu/icon/analyst/backiss/IA21.pdf | |
// These macros were (loosely) adapted from: | |
// https://web.archive.org/web/20210611030357im_/https://www.chiark.greenend.org.uk/~sgtatham/coroutine.h | |
// see e.g., | |
// https://web.archive.org/web/20210611030357im_/https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html | |
// Parameters accepted by the co-expression-like coroutine macros: | |
// - `rtype` is the type of values that the coroutine produces | |
// - `fname` is the name of the resulting coroutine-function | |
// - `ctag` is the context struct tag, see `ctyp` | |
// - `ctyp` is the context type: `typedef struct ctag { ... } ctyp;` | |
// - `cvar` is a declared instance of `ctyp` | |
// - `x` is the value to produce or transmit; must be of type `rtype` | |
// - `v` member of ctyp used to transmit values to the coroutine | |
// Within the coroutine-function body declared by COEXPR | |
// - `_c` is a `ctyp *` pointing to the context | |
// - `_fname` is a `char *` pointing to the string-ized `fname` | |
// COEXPR_CONTEXT_BEGIN begins the definition of the context type. | |
// It should appear in the global scope. | |
#define COEXPR_CONTEXT_BEGIN(ctag) typedef struct ctag { | |
// COEXPR_CONTEXT_END ends the definition of the context type. | |
// It should appear in the global scope. | |
#define COEXPR_CONTEXT_END(ctag, ctyp, rtyp) \ | |
rtyp _result; \ | |
int _transmitted; \ | |
int _failed; \ | |
int _line; \ | |
struct ctag * _refreshment; \ | |
} ctyp; | |
// Use COEXPR_INITIALIZER initialize five the invariant members. | |
#define COEXPR_INITIALIZER 0,0,0,0,0 | |
// COEXPR_CONTEXT produces a reference to the instantiation of the | |
// structure passed to the coroutine. | |
#define COEXPR_CONTEXT(fname) ctx_ ## fname | |
// CREATE declares an instance of the context type. | |
// As such, it allocates space on the stack. it does not end with | |
// a semicolon so that an initializer may be supplied. | |
#define CREATE(fname,ctyp) ctyp COEXPR_CONTEXT | |
// REFRESHABLE declares a refreshment instance of the context type. | |
// As such, it preserves the initial values supplied to CREATE. | |
#define REFRESHABLE(fname,ctyp) ctyp ctx_ ## fname ## _rfrsh = COEXPR_CONTEXT; \ | |
memcpy(&ctx_ ## fname ## _rfrsh, &COEXPR_CONTEXT, sizeof(COEXPR_CONTEXT)); \ | |
ctx_ ## fname ## _rfrsh._refreshment \ | |
= COEXPR_CONTEXT._refreshment \ | |
= &ctx_ ## fname ## _rfrsh; | |
// REFRESH refreshes a coexpr context from the refreshment copy. | |
#define REFRESH(fname) \ | |
memcpy(&COEXPR_CONTEXT, COEXPR_CONTEXT._refreshment, sizeof(COEXPR_CONTEXT)); | |
// COEXPR declares and initializes a co-experession-like coroutine. | |
#define COEXPR(rtype, fname, ctyp, dflt) \ | |
rtype fname (ctyp * _c) { \ | |
const rtype _default = dflt; \ | |
const char * _fname = #fname; \ | |
if (!_c || _c->_failed) return dflt; \ | |
switch(_c->_line) { \ | |
case 0: | |
// end COEXPR | |
// COEXPR_END ends the coexpression (fails, but produces the default value) | |
// - x should be ordinarily be ignored by the coroutine-activator | |
#define COEXPR_END \ | |
}; \ | |
FAIL \ | |
} | |
// end COEXPR | |
// FAIL sets the co-expression state to failed and returns meaningless default | |
#define FAIL \ | |
_c->_failed = 1; \ | |
return(_default); | |
// YIELD transmits `x_out` (of type `rtype`) to the activator, | |
// then it restores the context and assigns transmitted result to `x_out`. | |
// This is equivalent to `x_out @ x_in` in Icon. | |
// N.B. both invocations of the __LINE__ macro must appear on same line! | |
#define YIELD(x_out,x_in) \ | |
do { \ | |
_c->_result = x_out; _c->_transmitted = 1; \ | |
_c->_line=__LINE__; return (x_out); case __LINE__: \ | |
_c->_result = _c->_transmitted ? _c->_result : _default; \ | |
} while (0); \ | |
x_in = _c->_result; | |
// ACTIVATE activates the coroutine to produce a value | |
// By convention, the zeroth result is the meaningless default value. | |
#define ACTIVATE(fname) ACTIVATE_P(fname,&COEXPR_CONTEXT) | |
// Use the ACTIVATE_P form when not in the scope where CREATE was invoked. | |
// The additional argument is a pointer to the context. | |
#define ACTIVATE_P(fname,ctyp_pointer) ( \ | |
(ctyp_pointer)->_result = 0, \ | |
(ctyp_pointer)->_transmitted = 0, \ | |
(ctyp_pointer)->_result = fname(ctyp_pointer) \ | |
) | |
// SUCCESS activates the coroutine to produce a value | |
// By convention, the zeroth result is the meaningless default value. | |
#define SUCCESS(fname) (COEXPR_CONTEXT._failed == 0) | |
// TRANSMIT transmits a value to the coroutine when activating the latter | |
// By convention, a transmitted value is ignored during the first activation. | |
// (From p. 10 of Icon Technical Report 87-6.pdf, "On the first activation of | |
// a co-expression, the transmitted result is discarded, since there is | |
// nothing to receive it. On subsequent activations, the transmitted result | |
// becomes the result of the expression that activated the current co- | |
// expression." | |
#define TRANSMIT(fname,x) TRANSMIT_P(fname,x,&COEXPR_CONTEXT) | |
// Use the TRANSMIT_P form when not in the scope where CREATE was invoked. | |
// The additional argument is a pointer to the context. | |
#define TRANSMIT_P(fname,x,ctyp_pointer) ( \ | |
(ctyp_pointer)->_result = 0, \ | |
(ctyp_pointer)->_transmitted = (ctyp_pointer)->_line ? 1 : 0, \ | |
(ctyp_pointer)->_result = x, \ | |
(ctyp_pointer)->_result = fname(ctyp_pointer), \ | |
(ctyp_pointer)->_transmitted = 0, \ | |
(ctyp_pointer)->_result \ | |
) | |
// REFRESH and REFRESHABLE require string.h for the declaration of memcpy | |
#include <string.h> | |
#ifdef _COEXPRESSION_EXAMPLE | |
// Here is a working example: | |
#include <stdio.h> | |
/////////////////////////////////////////////////////////////// | |
// declare the context passed when activating the co-expression | |
/////////////////////////////////////////////////////////////// | |
COEXPR_CONTEXT_BEGIN(_context) | |
// typedef struct _context { | |
int init; | |
int max; | |
int i; | |
// rtyp _result; // added by COEXPR_CONTEXT_END | |
// int _transmitted; // added by COEXPR_CONTEXT_END | |
// int _line; // added by COEXPR_CONTEXT_END | |
// int _failed; // added by COEXPR_CONTEXT_END | |
// struct _context * _refreshment; // added by COEXPR_CONTEXT_END | |
// } context_type; | |
COEXPR_CONTEXT_END(_context, context_type, int) | |
#define new_context(init, max) { init, max, 0, COEXPR_INITIALIZER } | |
/////////////////////////////////////////////////////////////// | |
// define the co-expression | |
/////////////////////////////////////////////////////////////// | |
COEXPR(int, coexpr_eg, context_type, -1) { | |
int result; | |
if (_c->init > _c->max) { | |
printf(" < %s: _c->init is unexpectedly > _c->max\n", _fname); | |
} else { | |
for (_c->i = _c->init; _c->i < _c->max; _c->i++) { | |
// `YIELD(expr1,expr2)` is equivalent to Icon `expr1 @ expr2` | |
YIELD(_c->i, result); | |
if (_c->_transmitted) | |
printf(" < %s received %d\n", _fname, result); | |
} | |
} | |
FAIL | |
} | |
COEXPR_END | |
/////////////////////////////////////////////////////////////// | |
// demonstrate activation of (or transmission to) co-expression | |
/////////////////////////////////////////////////////////////// | |
// forward references to helper routines | |
int remote_activate(int (*coexpr_ptr) (context_type *), context_type *ctyp_pointer); | |
int remote_transmit(int (*coexpr_ptr) (context_type *), int x, context_type *ctyp_pointer); | |
int ipow(int base, int exp); | |
// entrypoint | |
int main(void) { | |
int result = 0; | |
{ | |
puts("Expected results:"); | |
puts(" Transmit discarded value to co-expression, expecting it to produce 12:"); | |
puts(" > TRANSMIT produced 12 (same as ACTIVATE on first activation)"); | |
puts(" Transmit 2 to the 12th power to the co-expression, " | |
"expecting it to produce 13"); | |
puts(" < coexpr_eg received 4096"); | |
puts(" > remote_transmit produced 13"); | |
puts(" Activate co-expression, expecting it to produce 14:"); | |
puts(" > ACTIVATE produced 14"); | |
puts(" Activate co-expression, expecting it to produce 15:"); | |
puts(" > remote_activate produced 15"); | |
puts(" Activate co-expression, expecting it to fail to produce a value:"); | |
puts(" > activation failed as expected"); | |
puts(" Refresh co-expression."); | |
puts(" Transmit discarded value to refreshed co-expression, expecting it to produce 12:"); | |
puts(" > TRANSMIT produced 12"); | |
puts(" Transmit 2 to the 12th power to the refreshed co-expression, " | |
"expecting it to produce 13"); | |
puts(" < coexpr_eg received 4096"); | |
puts(" > remote_transmit produced 13"); | |
puts(" Activate refreshed co-expression, expecting it to produce 14:"); | |
puts(" > ACTIVATE produced 14"); | |
puts("\nActual results:"); | |
CREATE(coexpr_eg, context_type) = new_context(12, 16); | |
REFRESHABLE(coexpr_eg, context_type); | |
printf(" Transmit discarded value to co-expression, expecting it to produce 12\n"); | |
// For first activation, result is same as `result = ACTIVATE(coexpr_eg);` | |
result = TRANSMIT(coexpr_eg, 1066); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > TRANSMIT produced %d (same as ACTIVATE on first activation)\n", result); | |
printf(" Transmit 2 to the %dth power to the co-expression," | |
" expecting it to produce %d\n", result, 1 + result); | |
result = remote_transmit(coexpr_eg, ipow(2, result), &COEXPR_CONTEXT); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > remote_transmit produced %d\n", result); | |
printf(" Activate co-expression, expecting it to produce %d\n", 1 + result); | |
result = ACTIVATE(coexpr_eg); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > ACTIVATE produced %d\n", result); | |
printf(" Activate co-expression, expecting it to produce %d\n", 1 + result); | |
result = remote_activate(coexpr_eg, &COEXPR_CONTEXT); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > remote_activate produced %d\n", result); | |
printf(" Activate co-expression, " | |
" expecting it to fail to produce a value\n"); | |
result = ACTIVATE(coexpr_eg); | |
if (SUCCESS(coexpr_eg)) { | |
printf(" > ACTIVATE **unexpectedly** produced %d\n", result); | |
return -1; | |
} else { | |
printf(" > activation failed as expected\n"); | |
} | |
printf(" Refresh co-expression\n"); | |
REFRESH(coexpr_eg); | |
printf(" Transmit discarded value to refreshed co-expression, expecting it to produce 12\n"); | |
// For first activation, result is same as `result = ACTIVATE(coexpr_eg);` | |
result = TRANSMIT(coexpr_eg, -44); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > TRANSMIT produced %d\n", result); | |
printf(" Transmit 2 to the %dth power to the refreshed co-expression," | |
" expecting it to produce %d\n", result, 1 + result); | |
result = remote_transmit(coexpr_eg, ipow(2, result), &COEXPR_CONTEXT); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > remote_transmit produced %d\n", result); | |
printf(" Activate refreshed co-expression, expecting it to produce %d\n", 1 + result); | |
result = ACTIVATE(coexpr_eg); | |
if (! SUCCESS(coexpr_eg)) return 0; | |
printf(" > ACTIVATE produced %d\n", result); | |
return 0; | |
} | |
} | |
// Demonstrate use of the ACTIVATE_P macro in a different scope from where | |
// the COEXPR was CREATEed. | |
int remote_activate(int (*coexpr_ptr) (context_type *), context_type *ctyp_pointer) { | |
return ACTIVATE_P((*coexpr_ptr), ctyp_pointer); | |
} | |
// Demonstrate use of the TRANSMIT_P macro in a different scope from where | |
// the COEXPR was CREATEed. | |
int remote_transmit(int (*coexpr_ptr) (context_type *), int x, context_type *ctyp_pointer) { | |
return TRANSMIT_P((*coexpr_ptr), x, ctyp_pointer); | |
} | |
// ipow - integer exponentiation | |
// ref: http://rosettacode.org/wiki/Exponentiation_operator#C | |
int ipow(int base, int exp) | |
{ | |
int pow = base; | |
int v = 1; | |
if (exp < 0) { | |
return (base*base != 1)? 0: (exp&1)? base : 1; | |
} | |
while(exp > 0 ) | |
{ | |
if (exp & 1) v *= pow; | |
pow *= pow; | |
exp >>= 1; | |
} | |
return v; | |
} | |
#endif // _COEXPRESSION_EXAMPLE | |
#define _COEXPR_H | |
#endif // _COEXPR_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ref: http://www.flexhex.com/docs/articles/alternate-streams.phtml | |
#include <windows.h> | |
#include <errhandlingapi.h> | |
#include <stdio.h> | |
#include "coexpr.h" | |
int iRetCode = EXIT_SUCCESS; | |
/////////////////////////////////////////////////////////////// | |
// declare the context passed when activating the co-expression | |
/////////////////////////////////////////////////////////////// | |
COEXPR_CONTEXT_BEGIN(_context) | |
// typedef struct _context { | |
char *szFromStream; | |
//BY_HANDLE_FILE_INFORMATION bhfi; | |
HANDLE hInFile; | |
BYTE buf[64*1024]; | |
DWORD dwBytesRead; | |
DWORDLONG expected_length; | |
DWORDLONG actual_length; | |
// int _transmitted; // added by COEXPR_CONTEXT_END | |
// int _line; // added by COEXPR_CONTEXT_END | |
// int _failed; // added by COEXPR_CONTEXT_END | |
// rtyp _result; // added by COEXPR_CONTEXT_END | |
// } context_type; | |
COEXPR_CONTEXT_END(_context, context_type, int) | |
#define new_context(ptr) { ptr, NULL, 0, 0, 0, 0, 0, 0, 0, 0 } | |
void print_context(context_type * p) { | |
printf("szFromStream = %s\n", p->szFromStream); | |
printf("hInFile = 0x%08x\n", p->hInFile); | |
// if (p->hInFile) printf("*hInFile = 0x%08x\n", * p->hInFile); | |
printf("dwBytesRead = %d\n", p->dwBytesRead); | |
printf("_transmitted = %d\n", p->_transmitted); | |
printf("_line = %d\n", p->_line); | |
printf("_failed = %d\n", p->_failed); | |
printf("_result = %d\n", p->_result); | |
} | |
/////////////////////////////////////////////////////////////// | |
// define the co-expression | |
/////////////////////////////////////////////////////////////// | |
COEXPR(int, coread_stream, context_type, -1) { | |
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information | |
/* | |
typedef struct _BY_HANDLE_FILE_INFORMATION { | |
DWORD dwFileAttributes; | |
FILETIME ftCreationTime; | |
FILETIME ftLastAccessTime; | |
FILETIME ftLastWriteTime; | |
DWORD dwVolumeSerialNumber; | |
DWORD nFileSizeHigh; | |
DWORD nFileSizeLow; | |
DWORD nNumberOfLinks; | |
DWORD nFileIndexHigh; | |
DWORD nFileIndexLow; | |
} BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION; | |
*/ | |
BY_HANDLE_FILE_INFORMATION bhfi; | |
//// printf("\nenter coread_stream\n---\n"); | |
//// print_context(_c); | |
_c->hInFile = | |
CreateFile(_c->szFromStream, GENERIC_READ, FILE_SHARE_READ, NULL, | |
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); | |
if (_c->hInFile == INVALID_HANDLE_VALUE) { | |
_c->_result = GetLastError(); | |
fprintf(stderr, "INVALID_HANDLE_VALUE, GetLastError returned %d\n", _c->_result); | |
FAIL; | |
} | |
if (!GetFileInformationByHandle(_c->hInFile, &bhfi)) { | |
_c->_result = GetLastError(); | |
fprintf(stderr, "GetFileInformationByHandle failed, GetLastError returned %d\n", _c->_result); | |
FAIL; | |
} | |
_c->expected_length = 0x100000000 * bhfi.nFileSizeHigh + bhfi.nFileSizeLow; | |
do { | |
/// char format_buf[20]; | |
ReadFile(_c->hInFile, _c->buf, sizeof(_c->buf), &_c->dwBytesRead, NULL); | |
//// fprintf(stderr, "%d bytes read\n", _c->dwBytesRead); | |
if (_c->dwBytesRead) { | |
int result; | |
_c->actual_length += _c->dwBytesRead; | |
/// sprintf(format_buf, "%%%ds", _c->dwBytesRead); | |
/// printf("read %s bytes\n", format_buf); | |
/// printf(format_buf, buf); | |
//// printf("\nbefore coread_stream YIELD\n---\n"); | |
//// print_context(_c); | |
if (_c->actual_length > _c->expected_length) { | |
fprintf(stderr, "Warning: more bytes read from %s than expected\n", _c->szFromStream); | |
fprintf(stderr, "expected 0x%hhx, actual 0x%hhx\n", _c->expected_length, _c->actual_length); | |
_c->dwBytesRead -= (DWORD) (_c->actual_length - _c->expected_length); | |
} | |
YIELD(_c->dwBytesRead, result); | |
//// printf("\nafter coread_stream YIELD\n---\n"); | |
//// print_context(_c); | |
} | |
} while (_c->dwBytesRead == sizeof(_c->buf) && _c->actual_length != _c->expected_length); | |
CloseHandle(_c->hInFile); | |
} | |
COEXPR_END | |
int main(int argc, char *argv[]) { | |
int bytes_read, total_bytes_read = 0; | |
if (argc != 2) { | |
fprintf(stderr, "usage: %s name_of_file_or_ads\n", argv[0]); | |
return iRetCode; | |
} | |
//return read_stream(argv[1]); | |
CREATE(coread_stream, context_type) = new_context(argv[1]); | |
do { | |
//// printf("\nbefore inner ACTIVATE\n---\n"); | |
//// print_context(&ctx_coread_stream); | |
bytes_read = ACTIVATE(coread_stream); | |
//// printf("\nafter inner ACTIVATE\n---\n"); | |
//// print_context(&ctx_coread_stream); | |
if (SUCCESS(coread_stream) && bytes_read > 0) { | |
total_bytes_read += bytes_read; | |
} else { | |
printf("activation failed, bytes_read = %d, total_bytes_read = %d\n", bytes_read, total_bytes_read); | |
break; | |
} | |
} while (bytes_read > 0); | |
printf("bytes_read = %d, total_bytes_read = %d\n", bytes_read, total_bytes_read); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment