/* WCSHDR * generate UNICODE, ANSI & NEUTRAL typedefs and prototypes from a master file * * string% = string{W,A,} * LPTSTR% = {LPWSTR,LPSTR,LPTSTR} * TCHAR% = {WCHAR,CHAR,TCHAR} * LPTCH% = {LPWCH,LPCH,LPTCH} * If whitespace follows the symbol, a space is appended as required to * prevent shortening, and thus screwwing layout. * * History: * 04-Mar-1991 IanJa Wrote it. * 19-Mar-1991 IanJa Not all fgets() implementations append '\0' upon EOF. * 29-Mar-1991 IanJa Workaround NT fgets bug ("\r\n" not collapsed to "\n"), * & Command line, Usage and Version numbers added. * 13-May-1991 IanJa All neutrality achieved by #define - no neutral structs * 14-May-1991 IanJa Minor improvements to version display, help * 21-May-1991 IanJa Realloc() pbs->pStart when required * 27-May-1991 GregoryW bug fix, add LPTSTRID, LPTSTRNULL * 13-Jun-1991 IanJa Convert #define's too. Eg: #define Fn%(a) FnEx%(0, a) * 19-Jun-1991 IanJa improve #define treatment & simplify main loop * 12-Aug-1991 IanJa fix multi-line #defines, NEAR & FAR typedefs * 12-Aug-1991 IanJa fix braceless typedefs with %s; add LPTSTR2 * 13-Aug-1991 IanJa add braceless typedefs #defines * 21-Aug-1991 IanJa fix string% substitutions for #defines * 21-Aug-1991 IanJa add BCHAR% -> BYTE or WCHAR as per BodinD request * 26-Aug-1991 IanJa init pbs->iType (NT-mode bug fix) * 26-Aug-1991 IanJa workaround NT fgets bug (CR LF not collapsed to NL) * 17-Nov-1992 v-griffk map #defines to typedef's on structs * for debugger support * 08-Sep-1993 IanJa add pLastParen for complex function typedefs such as * typedef BOOL ( CALLBACK * FOO% ) (BLAH% blah) ; * 24-Feb-1994 IanJa add CONV_FLUSH for blocks starting #if, #endif etc. * #if (WINVER > 0x400) * foo%(void); * #endif * 11-Nov-1994 RaymondC propagate ;internal-ness to trailers */ char *Version = "WCSHDR v1.20 1994-11-11:"; #include #include #include #include #define TRUE 1 #define FALSE 0 #define INITIAL_STORE_SIZE 2048 #define EXTRA_STORE_SIZE 1024 #define FN_NAME_SIZE 100 #define CONV_NONE 0 #define CONV_FN_PROTO 1 #define CONV_TYPEDEF 2 #define CONV_DEFINE 3 #define CONV_FLUSH 4 #define ASSERT(pbs, exp) if (!(exp)) AssertFail(__FILE__, __LINE__, pbs, #exp); typedef int BOOL; typedef char *PSZ; typedef struct { char *pStart; // 1st char in store char *pLastLine; // 1st char of last line in store int line; // number of lines read char *pEnd; /* '\0' at end of store */ size_t cbSize; size_t cbFree; int iType; // FnPrototype, Typedef, #define or none int nParen; // nesting index: ( & { increment; ) & } decrement char *p1stParen; // Pointer to first '(' or '{' in current block. char *pLastParen; // Pointer to last '(' or '{' in current block. char *pSymNam; // copy of function name, null-terminated int cbSymNam; // bytes available for fn name char *pszInternal; // "" if external or "\t// ;internal" if internal } BLOCKSTORE, *PBLOCKSTORE; void ArgProcess(int argc, PSZ argv[]); void Usage(void); void InitBS(PBLOCKSTORE); void SetInternalnessBS(PBLOCKSTORE); BOOL ReadLineBS(PBLOCKSTORE); void WriteBS(PBLOCKSTORE); void WriteAllTypesBS(PBLOCKSTORE, int); int ConversionRequiredBS(PBLOCKSTORE); void GetSymNameBS(PBLOCKSTORE, int); BOOL WriteRedefinedTypeNamesBS(PBLOCKSTORE); void WriteConvertBS(PBLOCKSTORE, int, BOOL); void EmptyBS(PBLOCKSTORE); void error_exit(PBLOCKSTORE pbs, int exitval); void PrintSubstitute(PBLOCKSTORE, PSZ, PSZ, int, BOOL); void AssertFail(PSZ pszfnam, int lineno, PBLOCKSTORE pbs, PSZ pszExp); #define NEUT 0 #define ANSI 1 #define UNIC 2 /* * Command line flags */ int fDebug = FALSE; void _CRTAPI1 main( int argc, PSZ argv[]) { /* * block store. * lines from input are saved in here until we know * enough about how to process them. */ BLOCKSTORE bs; int BlockType; ArgProcess(argc, argv); /* * buffer is empty */ InitBS(&bs); if (fDebug) { fprintf(stderr, "About to start main loop\n"); } while (ReadLineBS(&bs)) { /* * if line is blank then we have a complete block not requiring * any conversion. */ if (bs.pLastLine[strspn(bs.pLastLine, " \t\r")] == '\n') { WriteBS(&bs); EmptyBS(&bs); continue; } if ((BlockType = ConversionRequiredBS(&bs)) != 0) { WriteAllTypesBS(&bs, BlockType); } } /* * Flush last BlockStore */ WriteBS(&bs); } void WriteAllTypesBS(PBLOCKSTORE pbs, int BlockType) { if (fDebug) { fprintf(stderr, "WriteAllTypes(%lx, %d)\n", pbs, BlockType); } switch (BlockType) { case CONV_NONE: /* * No conversion required, keep accumulating block. */ return; case CONV_DEFINE: case CONV_FN_PROTO: SetInternalnessBS(pbs); GetSymNameBS(pbs, BlockType); WriteConvertBS(pbs, ANSI, TRUE); WriteConvertBS(pbs, UNIC, TRUE); ASSERT(pbs, pbs->pszInternal); /* * UNICODE defn. */ fprintf(stdout, "#ifdef UNICODE%s\n#define %s %sW%s\n", pbs->pszInternal, pbs->pSymNam, pbs->pSymNam, pbs->pszInternal); /* * ANSI defn. */ fprintf(stdout, "#else%s\n#define %s %sA%s\n", pbs->pszInternal, pbs->pSymNam, pbs->pSymNam, pbs->pszInternal); fprintf(stdout, "#endif // !UNICODE%s\n", pbs->pszInternal); /* * Neutral defn. */ break; case CONV_TYPEDEF: SetInternalnessBS(pbs); WriteConvertBS(pbs, ANSI, FALSE); WriteConvertBS(pbs, UNIC, FALSE); WriteRedefinedTypeNamesBS(pbs); break; case CONV_FLUSH: WriteBS(pbs); EmptyBS(pbs); break; default: fprintf(stderr, "Don't understand block"); error_exit(pbs, 2); } EmptyBS(pbs); } BOOL ReadLineBS(PBLOCKSTORE pbs) { int cbLine; if (fDebug) { fprintf(stderr, "ReadLineBS(%lx)\n", pbs); } /* * Not all implementations of fgets() put a '\0' in the buffer upon EOF. * This will cause ReadLineBS() to leave the BlockStore untouched when * it returns FALSE. * We must ensure that BlockStore contents are valid whenever this routine * is called. InitBS() and EmptyBS() must set contents to '\0' !! */ if (fgets(pbs->pEnd, pbs->cbFree, stdin) == NULL) { return FALSE; } cbLine = strlen(pbs->pEnd); if (fDebug) { fprintf(stderr, "read %d characters: \"%s\"\n", cbLine, pbs->pEnd); } pbs->pLastLine = pbs->pEnd; pbs->pEnd += cbLine; pbs->cbFree -= cbLine; pbs->line++; if (pbs->cbFree <= 1) { PSZ p; p = realloc(pbs->pStart, pbs->cbSize + EXTRA_STORE_SIZE); /* * Fatal Errror if allocation failed */ ASSERT(pbs, p != NULL); if (p == NULL) { fprintf(stderr, "Reallocate BlockStore to %d bytes failed", pbs->cbSize + EXTRA_STORE_SIZE); error_exit(pbs, 2); } /* * adjust the pointers and counts */ pbs->pLastLine = p + (pbs->pLastLine - pbs->pStart); pbs->pEnd = p + (pbs->pEnd - pbs->pStart); pbs->cbSize += EXTRA_STORE_SIZE; pbs->cbFree += EXTRA_STORE_SIZE; pbs->pStart = p; } return TRUE; } void WriteBS(PBLOCKSTORE pbs) { if (fDebug) { fprintf(stderr, "WriteBS(%lx)\n", pbs); } fputs(pbs->pStart, stdout); } /* * Each time a new line is read in, this function is called to determine * whether a complete block has been accumulated for conversion and output. */ int ConversionRequiredBS(PBLOCKSTORE pbs) { PSZ p; if (fDebug) { fprintf(stderr, "ConversionRequiredBS(%lx)\n", pbs); } if (pbs->iType == CONV_NONE) { if (strncmp(pbs->pStart, "#define", 7) == 0) { /* * The block starts with #define */ pbs->iType = CONV_DEFINE; } else if (pbs->pStart[0] == '#') { /* * The block starts with #if, #else, #endif etc. */ return CONV_FLUSH; } } if (pbs->iType != CONV_DEFINE) { /* * Scan this line for parentheses and braces to identify * a complete Function Prototype or Structure definition. * NOTE: comments containing unbalanced parentheses or braces * will screw this up! */ for (p = pbs->pLastLine; p <= pbs->pEnd; p++) { if ((*p == '(') || (*p == '{')) { pbs->pLastParen = p; if (pbs->p1stParen == NULL) { pbs->p1stParen = p; } pbs->nParen++; } else if ((*p == ')') || (*p == '}')) { pbs->nParen--; } if ((*p == ';') && (pbs->nParen == 0)) { /* * We have a function prototype or a typedef * (Balanced brackets and a semi-colon) */ if (pbs->p1stParen && *(pbs->p1stParen) == '(') { pbs->iType = CONV_FN_PROTO; } else { pbs->iType = CONV_TYPEDEF; } goto CheckPercents; } } /* * Not a #define, nor a complete Typedef or Function prototype. */ if (fDebug) { fprintf(stderr, " CONV_NONE (incomplete fn.proto/typedef)\n"); } return CONV_NONE; } else if (pbs->iType == CONV_DEFINE) { /* * We know the block is a #define - we must detect the end * (it can extend for more than one line using backslashes) */ if ((p = strrchr(pbs->pStart, '\\')) != NULL) { /* * There is a backslash on the line: if is it the last * non-whitespace character on the line, then this #define * is continuing on to the next line. */ p++; p += strspn(p, " \t\r\n"); if (*p == '\0') { /* * No conversion required *yet*. Continue accumulating * the multi-line #define statement. */ if (fDebug) { fprintf(stderr, " CONV_NONE (incomplete #define)\n"); } return CONV_NONE; // ...yet } } } CheckPercents: /* * We have a complete block of known type pbs->iType. We will need * to convert this block if it contains any %'s, so search for '%' */ p = pbs->pStart; while ((p = strchr(p, '%')) != NULL) { if (!isalnum(p[1])) { if (fDebug) { fprintf(stderr, " return %d (%% found)\n", pbs->iType); } return pbs->iType; } /* * We found a %, but it followed by an alphanumeric character, * so can't require wcshdr.exe substitution. Look for more '%'s */ p++; } if (fDebug) { fprintf(stderr, " CONV_FLUSH (no %%'s)\n"); } return CONV_FLUSH; } BOOL GetDefinedNameBS(PBLOCKSTORE pbs) { PSZ pPercent = pbs->p1stParen - 1; PSZ pStartNam; if (fDebug) { fprintf(stderr, "GetDefinedNameBS(%lx)\n", pbs); } /* * Scan forwards for name (starting from beyond the "#define") */ pStartNam = pbs->pStart + 7; while (isspace(*pStartNam)) { pStartNam++; } /* * Scan forwards for '%', starting at beginning of literal name */ for (pPercent = pStartNam; *pPercent; pPercent++) { if (*pPercent == '%') { /* * Make sure we have enough space to store the literal name */ if ((pPercent - pStartNam) > pbs->cbSymNam) { fprintf(stderr, "REALLOCATE DEFINED NAME BUFFER!"); error_exit(pbs, 2); } /* * store the literal name */ *pPercent = '\0'; strcpy(pbs->pSymNam, pStartNam); *pPercent = '%'; return TRUE; } } /* * didn't find percent! */ fprintf(stderr, "DEFINED NAME ???"); error_exit(pbs, 2); } BOOL GetFnNameBS(PBLOCKSTORE pbs) { PSZ pPercent = pbs->pLastParen - 1; PSZ pStartNam; if (fDebug) { fprintf(stderr, "GetFnNameBS(%lx)\n", pbs); } /* * Scan backwards for '%' */ while (*pPercent != '%') { if (--pPercent <= pbs->pStart) { fprintf(stderr, "FUNCTION NAME ???"); error_exit(pbs, 2); } } /* * Scan back for start of function name */ for (pStartNam = pPercent - 1; pStartNam >= pbs->pStart; pStartNam--) { if (!isalnum(*pStartNam) && *pStartNam != '_') break; } pStartNam++; /* * Make sure we have enough space to store the function name */ if ((pPercent - pStartNam) > pbs->cbSymNam) { fprintf(stderr, "REALLOCATE FN NAME BUFFER!"); error_exit(pbs, 2); } /* * store the function name */ *pPercent = '\0'; strcpy(pbs->pSymNam, pStartNam); *pPercent = '%'; return TRUE; } void GetSymNameBS(PBLOCKSTORE pbs, int iType) { if (iType == CONV_DEFINE) { GetDefinedNameBS(pbs); } else { GetFnNameBS(pbs); } } BOOL WriteRedefinedTypeNamesBS(PBLOCKSTORE pbs) { PSZ pFirstName = NULL; PSZ pToken; PSZ pPercent; BOOL fSkipFirst; if (fDebug) { fprintf(stderr, "WriteRedefinedTypeNamesBS(%lx)\n", pbs); } ASSERT(pbs, pbs->pszInternal); if (pbs->p1stParen && (*(pbs->p1stParen) == '{')) { /* * Scan backwards for the closing brace */ for (pToken = pbs->pEnd; *pToken != '}'; pToken--) { if (pToken <= pbs->pStart) { /* * No closing brace found!? */ fprintf(stderr, "CLOSING BRACE ???"); error_exit(pbs, 2); } } pToken++; fSkipFirst = FALSE; } else { /* * skip past "typedef" */ pToken = pbs->pStart + 7; /* * Skip the first name */ fSkipFirst = TRUE; } /* * UNICODE pass */ fprintf(stdout, "#ifdef UNICODE%s\n", pbs->pszInternal); while (pToken = strtok(pToken, ",; \t*\n\r")) { if (fDebug) { fprintf(stderr, "token: \"%s\"\n", pToken); } /* * Write out the #define for UNICODE, excluding "NEAR" & "FAR" */ if ( (_stricmp(pToken, "NEAR") == 0) || (_stricmp(pToken, "FAR") == 0)) { goto NextUnicodeToken; } if (fSkipFirst) { fSkipFirst = FALSE; goto NextUnicodeToken; } else if (pFirstName == NULL) { pFirstName = pToken; } pPercent = pToken + strlen(pToken) - 1; if (*pPercent == '%') { fprintf(stdout, "typedef "); PrintSubstitute(pbs, pToken, pPercent, UNIC, FALSE); fputs(" ", stdout); PrintSubstitute(pbs, pToken, pPercent, NEUT, FALSE); fprintf(stdout, ";%s\n", pbs->pszInternal); } NextUnicodeToken: pToken = NULL; } if (pFirstName == NULL) { fprintf(stderr, "TYPE NAME ???"); error_exit(pbs, 2); } fprintf(stdout, "#else%s\n", pbs->pszInternal); if (fDebug) { fprintf(stderr, "FirstName = %s\n", pFirstName); } /* * ANSI pass */ pToken = pFirstName; while ((pToken += strspn(pToken, "%,; \t*\n\r")) < pbs->pEnd) { /* * Write out the #define for ANSI, excluding "NEAR" and "FAR" */ if ( (_stricmp(pToken, "NEAR") == 0) || (_stricmp(pToken, "FAR") == 0)) { goto NextAnsiToken; } pPercent = pToken + strlen(pToken) - 1; if (*pPercent == '%') { fprintf(stdout, "typedef "); PrintSubstitute(pbs, pToken, pPercent, ANSI, FALSE); fputs(" ", stdout); PrintSubstitute(pbs, pToken, pPercent, NEUT, FALSE); fprintf(stdout, ";%s\n", pbs->pszInternal); } NextAnsiToken: while (*pToken++) { ; } } fprintf(stdout, "#endif // UNICODE%s\n", pbs->pszInternal); return TRUE; } void WriteConvertBS(PBLOCKSTORE pbs, int Type, int fVertAlign) { PSZ p = pbs->pStart; PSZ pPercent; if (fDebug) { fprintf(stderr, "WriteConvertBS(%lx, %d, %d)\n", pbs, Type, fVertAlign); } while ((pPercent = strchr(p, '%')) != NULL) { if (isalnum(pPercent[1])) { goto ContinueSearch; } /* * print the substitution */ PrintSubstitute(pbs, p, pPercent, Type, fVertAlign); /* * Advance beyond the % */ ContinueSearch: p = pPercent+1; } /* * Print remainder of store */ fputs(p, stdout); } void EmptyBS(PBLOCKSTORE pbs) { if (fDebug) { fprintf(stderr, "EmptyBS(%lx)\n", pbs); } pbs->pEnd = pbs->pStart; pbs->pLastLine = pbs->pStart; pbs->cbFree = pbs->cbSize; if (pbs->pStart) { *(pbs->pStart) = '\0'; } pbs->iType = CONV_NONE; pbs->p1stParen = NULL; pbs->pLastParen = NULL; pbs->nParen = 0; if (pbs->pSymNam) { *(pbs->pSymNam) = '\0'; } } void InitBS(PBLOCKSTORE pbs) { pbs->line = 0; pbs->pStart = malloc(INITIAL_STORE_SIZE); ASSERT(pbs, pbs->pStart != NULL); pbs->pLastLine = pbs->pStart; pbs->pEnd = pbs->pStart; *(pbs->pStart) = '\0'; pbs->iType = CONV_NONE; pbs->p1stParen = NULL; pbs->pLastParen = NULL; pbs->nParen = 0; pbs->pszInternal = 0; pbs->cbSize = INITIAL_STORE_SIZE; pbs->cbFree = INITIAL_STORE_SIZE; pbs->pSymNam = malloc(FN_NAME_SIZE); ASSERT(pbs, pbs->pSymNam != NULL); pbs->cbSymNam = FN_NAME_SIZE; *(pbs->pSymNam) = '\0'; } void SetInternalnessBS(PBLOCKSTORE pbs) { if (strstr(pbs->pStart, ";internal")) { pbs->pszInternal = "\t// ;internal"; } else { pbs->pszInternal = ""; } } void AssertFail( PSZ pszfnam, int lineno, PBLOCKSTORE pbs, PSZ pszExp) { fprintf(stderr, "ASSERT failed: file %s, line %d:\n", pszfnam, lineno); fprintf(stderr, "input line %d: \"%s\"\n", pbs->line, pszExp); } void ArgProcess( int argc, PSZ argv[]) { int ArgIndex; PSZ pszArg; for (ArgIndex = 1; ArgIndex < argc; ArgIndex++) { pszArg = argv[ArgIndex]; if ((*pszArg == '-') || (*pszArg == '/')) { switch (pszArg[1]) { case '?': fprintf(stderr, "%s\n", Version); Usage(); exit(0); case 'd': case 'D': fDebug = TRUE; break; default: fprintf(stderr, "%s Invalid switch: %s\n", Version, pszArg); Usage(); exit(1); } } } } void Usage(void) { fprintf(stderr, "usage: WCSHDR [-?] display this message\n"); fprintf(stderr, " [-d] debug (to stderr)\n"); fprintf(stderr, " reads stdin, writes to stdout\n"); } void error_exit(PBLOCKSTORE pbs, int exitval) { fprintf(stderr, " (line %d)\n", pbs->line); exit(exitval); } /* * Substitutions performed on strings ending '%' * */ typedef struct { int cchTemplate; PSZ pszTemplate; PSZ apszSub[3]; } SUBSTR, *PSUBSTR; /* * Strings that are replaced: * BCHAR% * TCHAR% * LPTCH% * LPTSTR% * LPTSTR2% * LPTSTRID% * LPTSTRNULL% * % * * "%" MUST comes last (before the null terminator) * * The other strings must be ordered from sensibly: * if FRED% came before BIGFRED% in Substrs[], then the Substitute() * procedure would match input BIGFRED% to FRED%, not BIGFRED%. The * simplest way to avoid this is to arrange strings in descending lengths. */ SUBSTR Substrs[] = { { 10, "LPTSTRNULL%", "LPTSTRNULL", "LPSTRNULL", "LPWSTRNULL" }, { 8, "LPTSTRID%", "LPTSTRID", "LPSTRID", "LPWSTRID" }, { 7, "LPTSTR2%", "LPTSTR2", "LPSTR2", "LPWSTR2" }, { 7, "LPCTSTR%", "LPCTSTR", "LPCSTR", "LPCWSTR" }, { 6, "LPTSTR%", "LPTSTR", "LPSTR", "LPWSTR" }, { 5, "TCHAR%", "TCHAR", "CHAR", "WCHAR" }, { 5, "BCHAR%", "BCHAR", "BYTE", "WCHAR" }, { 5, "LPTCH%", "LPTCH", "LPCH", "LPWCH" }, { 0, "%", "", "A", "W" }, { 0, NULL, NULL, NULL, NULL } }; PSZ special_pad[] = { " ", // Neutral " ", // ANSI " " // UNICODE }; PSZ normal_pad[] = { " ", // Neutral "", // ANSI "" // UNICODE }; void PrintSubstitute( PBLOCKSTORE pbs, // just for error reporting PSZ pStart, // where to start substitution PSZ pPercent, // ptr to '%' at end of input string int Type, // NEUT, ANSI or UNIC BOOL fVertAlign) // attempt to maintain vertical alignment? { PSUBSTR pSub; char chTmp; PSZ pChangedPart = NULL; if (fDebug) { fprintf(stderr, "PrintSubstitute(%lx, %lx, %lx, %d, %d)\n", pbs, pStart, pPercent, Type, fVertAlign); } for (pSub = Substrs; pSub->pszTemplate; pSub++) { int cch = pSub->cchTemplate; if ((pPercent - cch) < pStart) { continue; } if (strncmp(pPercent - cch, pSub->pszTemplate, cch+1) == 0) { pChangedPart = pPercent-cch; /* * print out unaltered bit */ chTmp = *pChangedPart; *pChangedPart = '\0'; fputs(pStart, stdout); *pChangedPart = chTmp; /* * print out replacement bit */ fputs(pSub->apszSub[Type], stdout); break; } } if (pChangedPart == NULL) { /* * NO match was found in Substrs[] !!! */ fprintf(stderr, "Can't substitute"); error_exit(pbs, 2); } /* * preserve alignment if required. * (not for function prototypes, and only if whitespace follows */ if (!fVertAlign && ((pPercent[1] == ' ') || (pPercent[1] == '\t'))) { if (pChangedPart != pPercent) { fputs(special_pad[Type], stdout); } else { fputs(normal_pad[Type], stdout); } } }