Skip to content

Instantly share code, notes, and snippets.

@eschen42
Last active August 5, 2021 17:04
Show Gist options
  • Save eschen42/fa0cd48139ba2ab4779caed65fcd676a to your computer and use it in GitHub Desktop.
Save eschen42/fa0cd48139ba2ab4779caed65fcd676a to your computer and use it in GitHub Desktop.
Macros to declare C coroutines with syntax similar to Icon co-expressions
#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
// 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