99babefe93a15594b1763b727db055251ebcce5d tdreszer Wed Aug 1 13:06:31 2012 -0700 Adding dsPrint module for printing into a dyString based stack of buffers and then optionally printing to stdout. diff --git src/lib/dsPrint.c src/lib/dsPrint.c new file mode 100644 index 0000000..05d51fe --- /dev/null +++ src/lib/dsPrint.c @@ -0,0 +1,328 @@ +// dsPrint - Dynamic string Stack based Printing. +// +// This module relies upon a dyString based stack of buffers which accumulate output +// for later printing. As a stack, only the top buffer can be filled and flushed out +// (dsPrintFlush). When flushing, the top buffer's content is appended to the next +// lower buffer, unless explicitly requested otherwise (dsPrintDirectly). +// If the stack is empty, dsPrintf will be equivalent to printf. +// +// Output goes to STDOUT unless a single alternative out file is registered. + +#include "common.h" +#include "dsPrint.h" + +// The stack of dyString buffers is only accessible locally +static struct dyString *dsStack = NULL; +#define dsAssertToken(token) assert((int)(long long)dsStack == token) + +// This module defaults to STDOUT but an alternative can be registered +static FILE *dsOut = NULL; +#define dsInitializeOut() {if (dsOut == NULL) dsOut = stdout;} +#define dsAssertUnregisteredOut() assert(dsOut == stdout || dsOut == NULL) +#define dsAssertRegisteredOut(out) assert(dsOut == out) + + +int dsPrintOpen(int initialBufSize) +// Allocate dynamic string and puts at top of stack +// returns token to be used for later stack accounting asserts +{ +if (initialBufSize <= 0) + initialBufSize = 2048; // presume large +struct dyString *ds = dyStringNew(initialBufSize); +slAddHead(&dsStack,ds); +return (int)(long long)ds; +} + + +int dsPrintf(char *format, ...) +// Prints into end of the top ds buffer, and return resulting string length +// If there is no current buffer, this acts like a simple printf and returns -1 +{ +int len = -1; // caller could assert returned length > 0 to ensure dsPrint is open! +va_list args; +va_start(args, format); +if (dsStack != NULL) + { + dyStringVaPrintf(dsStack, format, args); + len = dyStringLen(dsStack); + } +else + { + dsInitializeOut() + vfprintf(dsOut,format, args); + } +va_end(args); +return len; +} + + +int dsPrintDirectly(int token,FILE *file) +// Prints the contents of the top buffer directly to a file. +// Will leave the filled buffer at top of stack +// Returns the length printed. +{ +dsAssertToken(token); + +int len = dyStringLen(dsStack); +if (len != 0) + fprintf(file,"%s",dyStringContents(dsStack)); + fflush(file); +return len; +} + + +int dsPrintFlush(int token) +// Flushes the top buffer to the next lower one and empties top buffer. +// If there is no lower buffer then the content is printed to STDOUT (or registered out). +// Returns the length flushed. +{ +dsAssertToken(token); + +int len = dyStringLen(dsStack); +if (len != 0) + { + if (dsStack->next == NULL) + { + dsInitializeOut(); + fprintf(dsOut,"%s",dyStringContents(dsStack)); + fflush(dsOut); + } + else + dyStringAppend(dsStack->next,dyStringContents(dsStack)); + dyStringClear(dsStack); + } +return len; +} + + +int dsPrintClose(int token) +// Abandons the top buffer and its content, freeing memory. +// Returns the length abandoned. +{ +dsAssertToken(token); +int len = dyStringLen(dsStack); +struct dyString *ds = slPopHead(&dsStack); +dyStringFree(&ds); +return len; +} + +int dsPrintFlushAndClose(int token) +// Flushes the top buffer to the next lower one and frees the top buffer. +// If there is no lower buffer then the content is printed to STDOUT (or registered out). +// Returns the length flushed. +{ +int len = dsPrintFlush(token); +dsPrintClose(token); +return len; +} + + +char * dsPrintContent(int token) +// returns the content of the current buffer as a string, but does not flush or free it +// NOTE: returned pointer is not cloned memory and will be changed by successive dsPrint calls +{ +dsAssertToken(token); +return dyStringContents(dsStack); +} + + +int dsPrintEmpty(int token) +// Empties the top buffer in stack (abandoning content), but leave buffer in place +// Returns the length abandoned. +{ +dsAssertToken(token); +int len = dyStringLen(dsStack); +dyStringClear(dsStack); +return len; +} + + +static int dsPrintStackIxFromToken(int token) +// Return the stack member corresponding to the token. +// This ix can be used to walk up the stack from a given point +{ +struct dyString *ds = dsStack; +int ix = 0; + +for (; ds != NULL; ds = ds->next, ++ix) + { + if ((int)(long long)ds == token) + return ix; + } +return -1; +} + + +static struct dyString *dsPrintStackMemberFromIx(int ix) +// Return the stack member corresponding to the ix. +// By walking stack ix's, you can walk up or down the stack +{ +if (ix < 0) + return NULL; +return slElementFromIx(dsStack,ix); +} + + +int dsPrintSize(int token) +// Returns the curent length of the buffer starting at the level corresponding to token. +// Unlike other cases, the token does not have to correspond to the top of the stack! +{ +//dsAssertToken(token); +//return dyStringLen(dsStack); +int ix = dsPrintStackIxFromToken(token); +assert(ix >= 0); + +int len = 0; +struct dyString *ds = NULL; +for (;ix>0;ix--) + { + ds = dsPrintStackMemberFromIx(ix); + assert(ds != NULL); + len += dyStringLen(ds); + } +len += dyStringLen(dsStack); +return len; +} + + +int dsPrintStackDepth() +// Returns the current dsPrint buffer stack depth +{ +return slCount(dsStack); +} + + +void dsPrintRegisterOut(FILE *out) +// Registers an alternative to STDOUT. After registering, all dsPrint out goes to this file. +// NOTE: There is no stack. Register/Unregister serially. +{ +dsAssertUnregisteredOut(); +dsOut = out; +} + + +void dsPrintUnregisterOut(FILE *out) +// Unregisters the alternative to STDOUT. After unregistering all dsPrint out goes to STDOUT. +// NOTE: There is no stack. Register/Unregister serially. +{ +dsAssertRegisteredOut(out); +dsOut = stdout; +} + + +int dsPrintSizeAll() +// returns combined current size of all buffers +{ +int len = 0; +if (dsStack != NULL) + { + struct dyString *ds = dsStack; + for (;ds != NULL; ds = ds->next) + len += dyStringLen(ds); + } +return len; +} + + +int dsPrintFlushAndCloseAll() +// flushes, frees and closes all stacked buffers +// returns length flushed +{ +int len = 0; +if (dsStack != NULL) + { + while (dsStack != NULL) + { + int token = (int)(long long)dsStack; + len = dsPrintFlushAndClose(token); // Only the last length is needed! + } + } +return len; +} + + +char *dsPrintCannibalizeAndClose(int token) +// Closes the top stack buffer returning content. Returned string should be freed. +{ +dsAssertToken(token); +struct dyString *ds = slPopHead(&dsStack); +return dyStringCannibalize(&ds); +} + + +char *dsPrintCannibalizeAndCloseAll() +// Closes all stack buffers and returns a single string that is the full content. +// Returned string should be freed. +{ +while (dsStack != NULL && dsStack->next != NULL) + { + int token = (int)(long long)dsStack; + dsPrintFlushAndClose(token); // Flushes each to the next lower buffer + } +if (dsStack == NULL) + return NULL; +else + return dyStringCannibalize(&dsStack); +} + +/* +void dsPrintTest() +// Tests of dsPrint functions +{ +printf("Plain printf\n"); +dsPrintf("1 dsPrintf unopened\n"); + +int token1 = dsPrintOpen(0); +dsPrintf("2 dsOpen:1\n"); +dsPrintFlush(token1); +dsPrintf("3^10 1:%d after flush\n",dsPrintStackDepth()); +int token2 = dsPrintOpen(256); +dsPrintf("4 dsOpen:2 len1:%d len2:%d lenAll:%d\n", + dsPrintSize(token1),dsPrintSize(token2),dsPrintSizeAll()); +dsPrintRegisterOut(stderr); +dsPrintFlush(token2); +dsPrintUnregisterOut(stderr); +dsPrintf("5 2:%d After flush len1:%d len2:%d lenAll:%d\n", + dsPrintStackDepth(),dsPrintSize(token1),dsPrintSize(token2),dsPrintSizeAll()); +dsPrintFlushAndClose(token2); +dsPrintf("6 1:%d After flushAndClose:2 len1:%d\n",dsPrintStackDepth(),dsPrintSize(token1)); +token2 = dsPrintOpen(256); +dsPrintf("7 dsOpen:2 reopen:2 WILL ABANDON CONTENT\n"); +char *content = cloneString(dsPrintContent(token2)); +dsPrintAbandonContent(token2); +dsPrintf("8x7 2:%d len1:%d len2:%d lenAll:%d isEmpty2:%s\n", + dsPrintStackDepth(),dsPrintSize(token1),dsPrintSize(token2),dsPrintSizeAll(), + (dsPrintIsEmpty(token2) ? "EMPTY":"NOT_EMPTY")); +strSwapChar(content,'\n','~'); // Replace newline before printing. +dsPrintf("9 2:%d No flush yet len1:%d len2:%d lenAll:%d prev7:[%s]\n", + dsPrintStackDepth(),dsPrintSize(token1),dsPrintSize(token2),dsPrintSizeAll(),content); +freez(&content); +int token3 = dsPrintOpen(256); +dsPrintPuts("10 Open:3 Line doubled and out of turn due to dsPrintDirectly()"); +dsPrintDirectly(token3,stdout); +dsPrintf("11 3:%d Just prior to closing all. len1:%d len2:%d len3:%d lenAll:%d", + dsPrintStackDepth(), + dsPrintSize(token1),dsPrintSize(token2),dsPrintSize(token3),dsPrintSizeAll()); +int token4 = dsPrintOpen(256); +dsPrintf("12 Open:4 Opened stack:%d WILL ABANDON\n",dsPrintStackDepth()); +dsPrintAbandon(token4); +dsPrintPutc('\n'); +dsPrintPuts("13 Puts: Added last '\\n' with dsPrintPutc(), this with dsPrintPuts()."); +dsPrintf("14 3:%d Last line! Expect 7 & 12 missing, 10 dupped. lenAll:%d Bye.\n", + dsPrintStackDepth(),dsPrintSizeAll()); +dsPrintFlushAndCloseAll(); +assert(dsStack == NULL); +int ix = 0; +for (;ix<20;ix++) // tested to 1000 + { + dsPrintOpen(32); // Don't even care about tokens! + if (ix == 0) + dsPrintf("CannibalizeAndCloseAll():"); + dsPrintf(" %d",dsPrintStackDepth()); + } +content = dsPrintCannibalizeAndCloseAll(); +printf("\n[%s]\n",content); +freez(&content); +} +*/ +