12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532 |
- /*
- ** 2022-11-12:
- **
- ** The author disclaims copyright to this source code. In place of
- ** a legal notice, here is a blessing:
- **
- ** * May you do good and not evil.
- ** * May you find forgiveness for yourself and forgive others.
- ** * May you share freely, never taking more than you give.
- **
- ************************************************************************
- **
- ** The C-minus Preprocessor: a truly minimal C-like preprocessor.
- ** Why? Because C preprocessors _can_ process non-C code but generally make
- ** quite a mess of it. The purpose of this application is an extremely
- ** minimal preprocessor with only the most basic functionality of a C
- ** preprocessor, namely:
- **
- ** - Limited `#if`, where its one argument is a macro name which
- ** resolves to true if it's defined, false if it's not. Likewise,
- ** `#ifnot` is the inverse. Includes `#else` and `#elif` and
- ** `#elifnot`. Such chains are terminated with `#endif`.
- **
- ** - `#define` accepts one or more arguments, the names of
- ** macros. Each one is implicitly true.
- **
- ** - `#undef` undefine one or more macros.
- **
- ** - `#error` treats the rest of the line as a fatal error message.
- **
- ** - `#include` treats its argument as a filename token (NOT quoted,
- ** though support for quoting may be added later). Some effort is
- ** made to prevent recursive inclusion, but that support is both
- ** somewhat fragile and possibly completely unnecessary.
- **
- ** - `#pragma` is in place for adding "meta-commands", but it does not
- ** yet have any concrete list of documented commands.
- **
- * - `#stderr` outputs its file name, line number, and the remaininder
- ** of that line to stderr.
- **
- ** - `#//` acts as a single-line comment, noting that there must be as
- ** space after the `//` part because `//` is (despite appearances)
- ** parsed like a keyword.
- **
- ** Note that "#" above is symbolic. The keyword delimiter is
- ** configurable and defaults to "##". Define CMPP_DEFAULT_DELIM to a
- ** string when compiling to define the default at build-time.
- **
- ** This preprocessor does no expansion of content except within the
- ** bounds of its `#keywords`.
- **
- ** Design note: this code makes use of sqlite3. Though not _strictly_
- ** needed in order to implement it, this tool was specifically created
- ** for use with the sqlite3 project's own JavaScript code, so there's
- ** no reason not to make use of it to do some of the heavy lifting. It
- ** does not require any cutting-edge sqlite3 features and should be
- ** usable with any version which supports `WITHOUT ROWID`.
- **
- ** Author(s):
- **
- ** - Stephan Beal <https://wanderinghorse.net/home/stephan/>
- */
- #include <stdlib.h>
- #include <stdio.h>
- #include <errno.h>
- #include <string.h>
- #include <stdarg.h>
- #include <assert.h>
- #include <ctype.h>
- #include "sqlite3.h"
- #if defined(_WIN32) || defined(WIN32)
- # include <io.h>
- # include <fcntl.h>
- # ifndef access
- # define access(f,m) _access((f),(m))
- # endif
- #else
- # include <unistd.h>
- #endif
- #ifndef CMPP_DEFAULT_DELIM
- #define CMPP_DEFAULT_DELIM "##"
- #endif
- #if 1
- # define CMPP_NORETURN __attribute__((noreturn))
- #else
- # define CMPP_NORETURN
- #endif
- /* Fatally exits the app with the given printf-style message. */
- static CMPP_NORETURN void fatalv(char const *zFmt, va_list);
- static CMPP_NORETURN void fatal(char const *zFmt, ...);
- /** Proxy for free(), for symmetry with cmpp_realloc(). */
- static void cmpp_free(void *p);
- /** A realloc() proxy which dies fatally on allocation error. */
- static void * cmpp_realloc(void * p, unsigned n);
- #if 0
- /** A malloc() proxy which dies fatally on allocation error. */
- static void * cmpp_malloc(unsigned n);
- #endif
- /*
- ** If p is stdin or stderr then this is a no-op, else it is a
- ** proxy for fclose(). This is a no-op if p is NULL.
- */
- static void FILE_close(FILE *p);
- /*
- ** Works like fopen() but accepts the special name "-" to mean either
- ** stdin (if zMode indicates a real-only mode) or stdout. Fails
- ** fatally on error.
- */
- static FILE * FILE_open(char const *zName, const char * zMode);
- /*
- ** Reads the entire contents of the given file, allocating it in a
- ** buffer which gets assigned to `*pOut`. `*nOut` gets assigned the
- ** length of the output buffer. Fails fatally on error.
- */
- static void FILE_slurp(FILE *pFile, unsigned char **pOut,
- unsigned * nOut);
- /*
- ** Intended to be passed an sqlite3 result code. If it's non-0
- ** then it emits a fatal error message which contains both the
- ** given string and the sqlite3_errmsg() from the application's
- ** database instance.
- */
- static void db_affirm_rc(int rc, const char * zMsg);
- /*
- ** Proxy for sqlite3_str_finish() which fails fatally if that
- ** routine returns NULL.
- */
- static char * db_str_finish(sqlite3_str *s, int * n);
- /*
- ** Proxy for sqlite3_str_new() which fails fatally if that
- ** routine returns NULL.
- */
- static sqlite3_str * db_str_new(void);
- /* Proxy for sqlite3_finalize(). */
- static void db_finalize(sqlite3_stmt *pStmt);
- /*
- ** Proxy for sqlite3_step() which fails fatally if the result
- ** is anything other than SQLITE_ROW or SQLITE_DONE.
- */
- static int db_step(sqlite3_stmt *pStmt);
- /*
- ** Proxy for sqlite3_bind_int() which fails fatally on error.
- */
- static void db_bind_int(sqlite3_stmt *pStmt, int col, int val);
- #if 0
- /*
- ** Proxy for sqlite3_bind_null() which fails fatally on error.
- */
- static void db_bind_null(sqlite3_stmt *pStmt, int col);
- #endif
- /*
- ** Proxy for sqlite3_bind_text() which fails fatally on error.
- */
- static void db_bind_text(sqlite3_stmt *pStmt, int col, const char * zStr);
- /*
- ** Proxy for sqlite3_bind_text() which fails fatally on error.
- */
- static void db_bind_textn(sqlite3_stmt *pStmt, int col, const char * zStr, int len);
- #if 0
- /*
- ** Proxy for sqlite3_bind_text() which fails fatally on error. It uses
- ** sqlite3_str_vappendf() so supports all of its formatting options.
- */
- static void db_bind_textv(sqlite3_stmt *pStmt, int col, const char * zFmt, ...);
- #endif
- /*
- ** Proxy for sqlite3_free(), to be passed any memory which is allocated
- ** by sqlite3_malloc().
- */
- static void db_free(void *m);
- /*
- ** Adds the given `#define` macro name to the list of macros, ignoring
- ** any duplicates. Fails fatally on error.
- */
- static void db_define_add(const char * zKey);
- /*
- ** Returns true if the given key is already in the `#define` list,
- ** else false. Fails fatally on db error.
- */
- static int db_define_has(const char * zName);
- /*
- ** Removes the given `#define` macro name from the list of
- ** macros. Fails fatally on error.
- */
- static void db_define_rm(const char * zKey);
- /*
- ** Adds the given filename to the list of being-`#include`d files,
- ** using the given source file name and line number of error reporting
- ** purposes. If recursion is later detected.
- */
- static void db_including_add(const char * zKey, const char * zSrc, int srcLine);
- /*
- ** Adds the given dir to the list of includes. They are checked in the
- ** order they are added.
- */
- static void db_include_dir_add(const char * zKey);
- /*
- ** Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of
- ** the `#include` dirs (db_include_dir_add()). If no file match is
- ** found, NULL is returned. Memory must eventually be passed to
- ** db_free() to free it.
- */
- static char * db_include_search(const char * zKey);
- /*
- ** Removes the given key from the `#include` list.
- */
- static void db_include_rm(const char * zKey);
- /*
- ** A proxy for sqlite3_prepare() which fails fatally on error.
- */
- static void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...);
- /*
- ** Opens the given file and processes its contents as c-pp, sending
- ** all output to the global c-pp output channel. Fails fatally on
- ** error.
- */
- static void cmpp_process_file(const char * zName);
- /*
- ** Returns the number newline characters between the given starting
- ** point and inclusive ending point. Results are undefined if zFrom is
- ** greater than zTo.
- */
- static unsigned count_lines(unsigned char const * zFrom,
- unsigned char const *zTo);
- /*
- ** Wrapper around a FILE handle.
- */
- struct FileWrapper {
- /* File's name. */
- char const *zName;
- /* FILE handle. */
- FILE * pFile;
- /* Where FileWrapper_slurp() stores the file's contents. */
- unsigned char * zContent;
- /* Size of this->zContent, as set by FileWrapper_slurp(). */
- unsigned nContent;
- };
- typedef struct FileWrapper FileWrapper;
- #define FileWrapper_empty_m {0,0,0,0}
- static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
- /* Proxy for FILE_close(). */
- static void FileWrapper_close(FileWrapper * p);
- /* Proxy for FILE_open(). */
- static void FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
- /* Proxy for FILE_slurp(). */
- static void FileWrapper_slurp(FileWrapper * p);
- /*
- ** Outputs a printf()-formatted message to stderr.
- */
- static void g_stderr(char const *zFmt, ...);
- /*
- ** Outputs a printf()-formatted message to stderr.
- */
- static void g_stderrv(char const *zFmt, va_list);
- #define g_debug(lvl,pfexpr) \
- if(lvl<=g.doDebug) g_stderr("%s @ %s:%d: ",g.zArgv0,__FILE__,__LINE__); \
- if(lvl<=g.doDebug) g_stderr pfexpr
- void fatalv(char const *zFmt, va_list va){
- if(zFmt && *zFmt){
- vfprintf(stderr, zFmt, va);
- }
- fputc('\n', stderr);
- exit(1);
- }
- void fatal(char const *zFmt, ...){
- va_list va;
- va_start(va, zFmt);
- fatalv(zFmt, va);
- va_end(va);
- }
- void cmpp_free(void *p){
- free(p);
- }
- void * cmpp_realloc(void * p, unsigned n){
- void * const rc = realloc(p, n);
- if(!rc) fatal("realloc(P,%u) failed", n);
- return rc;
- }
- #if 0
- void * cmpp_malloc(unsigned n){
- void * const rc = malloc(n);
- if(!rc) fatal("malloc(%u) failed", n);
- return rc;
- }
- #endif
- FILE * FILE_open(char const *zName, const char * zMode){
- FILE * p;
- if('-'==zName[0] && 0==zName[1]){
- p = strstr(zMode,"w") ? stdout : stdin;
- }else{
- p = fopen(zName, zMode);
- if(!p) fatal("Cannot open file [%s] with mode [%s]", zName, zMode);
- }
- return p;
- }
- void FILE_close(FILE *p){
- if(p && p!=stdout && p!=stderr){
- fclose(p);
- }
- }
- void FILE_slurp(FILE *pFile, unsigned char **pOut,
- unsigned * nOut){
- unsigned char zBuf[1024 * 8];
- unsigned char * pDest = 0;
- unsigned nAlloc = 0;
- unsigned nOff = 0;
- /* Note that this needs to be able to work on non-seekable streams,
- ** thus we read in chunks instead of doing a single alloc and
- ** filling it in one go. */
- while( !feof(pFile) ){
- size_t const n = fread(zBuf, 1, sizeof(zBuf), pFile);
- if(n>0){
- if(nAlloc < nOff + n + 1){
- nAlloc = nOff + n + 1;
- pDest = cmpp_realloc(pDest, nAlloc);
- }
- memcpy(pDest + nOff, zBuf, n);
- nOff += n;
- }
- }
- if(pDest) pDest[nOff] = 0;
- *pOut = pDest;
- *nOut = nOff;
- }
- void FileWrapper_close(FileWrapper * p){
- if(p->pFile) FILE_close(p->pFile);
- if(p->zContent) cmpp_free(p->zContent);
- *p = FileWrapper_empty;
- }
- void FileWrapper_open(FileWrapper * p, const char * zName,
- const char * zMode){
- FileWrapper_close(p);
- p->pFile = FILE_open(zName, zMode);
- p->zName = zName;
- }
- void FileWrapper_slurp(FileWrapper * p){
- assert(!p->zContent);
- assert(p->pFile);
- FILE_slurp(p->pFile, &p->zContent, &p->nContent);
- }
- unsigned count_lines(unsigned char const * zFrom, unsigned char const *zTo){
- unsigned ln = 0;
- unsigned char const *zPos = zFrom;
- assert(zFrom && zTo);
- assert(zFrom <= zTo);
- for(; zPos < zTo; ++zPos){
- switch(*zPos){
- case (unsigned)'\n': ++ln; break;
- default: break;
- }
- }
- return ln;
- }
- enum CmppParseState {
- TS_Start = 1,
- TS_If,
- TS_IfPassed,
- TS_Else,
- TS_Error
- };
- typedef enum CmppParseState CmppParseState;
- enum CmppTokenType {
- TT_Invalid = 0,
- TT_Comment,
- TT_Define,
- TT_Elif,
- TT_ElifNot,
- TT_Else,
- TT_EndIf,
- TT_Error,
- TT_If,
- TT_IfNot,
- TT_Include,
- TT_Line,
- TT_Pragma,
- TT_Stderr,
- TT_Undef
- };
- typedef enum CmppTokenType CmppTokenType;
- struct CmppToken {
- CmppTokenType ttype;
- /* Line number of this token in the source file. */
- unsigned lineNo;
- /* Start of the token. */
- unsigned char const * zBegin;
- /* One-past-the-end byte of the token. */
- unsigned char const * zEnd;
- };
- typedef struct CmppToken CmppToken;
- #define CmppToken_empty_m {TT_Invalid,0,0,0}
- static const CmppToken CmppToken_empty = CmppToken_empty_m;
- /*
- ** CmppLevel represents one "level" of tokenization, starting at the
- ** top of the main input, incrementing once for each level of `#if`,
- ** and decrementing for each `#endif`.
- */
- typedef struct CmppLevel CmppLevel;
- struct CmppLevel {
- unsigned short flags;
- /*
- ** Used for controlling which parts of an if/elif/...endif chain
- ** should get output.
- */
- unsigned short skipLevel;
- /* The token which started this level (an 'if' or 'ifnot'). */
- CmppToken token;
- CmppParseState pstate;
- };
- #define CmppLevel_empty_m {0U,0U,CmppToken_empty_m,TS_Start}
- static const CmppLevel CmppLevel_empty = CmppLevel_empty_m;
- enum CmppLevel_Flags {
- /* Max depth of nested `#if` constructs in a single tokenizer. */
- CmppLevel_Max = 10,
- /* Max number of keyword arguments. */
- CmppArgs_Max = 10,
- /* Flag indicating that output for a CmpLevel should be elided. */
- CmppLevel_F_ELIDE = 0x01,
- /*
- ** Mask of CmppLevel::flags which are inherited when CmppLevel_push()
- ** is used.
- */
- CmppLevel_F_INHERIT_MASK = 0x01
- };
- typedef struct CmppTokenizer CmppTokenizer;
- typedef struct CmppKeyword CmppKeyword;
- typedef void (*cmpp_keyword_f)(CmppKeyword const * pKw, CmppTokenizer * t);
- struct CmppKeyword {
- const char *zName;
- unsigned nName;
- int bTokenize;
- CmppTokenType ttype;
- cmpp_keyword_f xCall;
- };
- static CmppKeyword const * CmppKeyword_search(const char *zName);
- static void cmpp_process_keyword(CmppTokenizer * const t);
- /*
- ** Tokenizer for c-pp input files.
- */
- struct CmppTokenizer {
- const char * zName; /* Input (file) name for error reporting */
- unsigned const char * zBegin; /* start of input */
- unsigned const char * zEnd; /* one-after-the-end of input */
- unsigned const char * zAnchor; /* start of input or end point of
- previous token */
- unsigned const char * zPos; /* current position */
- unsigned int lineNo; /* line # of current pos */
- CmppParseState pstate;
- CmppToken token; /* current token result */
- struct {
- unsigned ndx;
- CmppLevel stack[CmppLevel_Max];
- } level;
- /* Args for use in cmpp_keyword_f() impls. */
- struct {
- CmppKeyword const * pKw;
- int argc;
- const unsigned char * argv[CmppArgs_Max];
- unsigned char lineBuf[1024];
- } args;
- };
- #define CT_level(t) (t)->level.stack[(t)->level.ndx]
- #define CT_pstate(t) CT_level(t).pstate
- #define CT_skipLevel(t) CT_level(t).skipLevel
- #define CLvl_skip(lvl) ((lvl)->skipLevel || ((lvl)->flags & CmppLevel_F_ELIDE))
- #define CT_skip(t) CLvl_skip(&CT_level(t))
- #define CmppTokenizer_empty_m { \
- 0,0,0,0,0,1U/*lineNo*/, \
- TS_Start, \
- CmppToken_empty_m, \
- {/*level*/0U,{CmppLevel_empty_m}}, \
- {/*args*/0,0,{0},{0}} \
- }
- static const CmppTokenizer CmppTokenizer_empty = CmppTokenizer_empty_m;
- static void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n);
- /*static void cmpp_t_outf(CmppTokenizer * t, char const *zFmt, ...);*/
- /*
- ** Pushes a new level into the given tokenizer. Fails fatally if
- ** it's too deep.
- */
- static void CmppLevel_push(CmppTokenizer * const t);
- /*
- ** Pops a level from the tokenizer. Fails fatally if the top
- ** level is popped.
- */
- static void CmppLevel_pop(CmppTokenizer * const t);
- /*
- ** Returns the current level object.
- */
- static CmppLevel * CmppLevel_get(CmppTokenizer * const t);
- /*
- ** Global app state singleton. */
- static struct Global {
- /* main()'s argv[0]. */
- const char * zArgv0;
- /*
- ** Bytes of the keyword delimiter/prefix. Owned
- ** elsewhere.
- */
- const char * zDelim;
- /* Byte length of this->zDelim. */
- unsigned short nDelim;
- /* If true, enables certain debugging output. */
- int doDebug;
- /* App's db instance. */
- sqlite3 * db;
- /* Output channel. */
- FileWrapper out;
- struct {
- sqlite3_stmt * defIns;
- sqlite3_stmt * defDel;
- sqlite3_stmt * defHas;
- sqlite3_stmt * inclIns;
- sqlite3_stmt * inclDel;
- sqlite3_stmt * inclHas;
- sqlite3_stmt * inclPathAdd;
- sqlite3_stmt * inclSearch;
- } stmt;
- } g = {
- "?",
- CMPP_DEFAULT_DELIM/*zDelim*/,
- (unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1/*nDelim*/,
- 0/*doDebug*/,
- 0/*db*/,
- FileWrapper_empty_m/*out*/,
- {/*stmt*/
- 0/*defIns*/, 0/*defDel*/, 0/*defHas*/,
- 0/*inclIns*/, 0/*inclDel*/, 0/*inclHas*/,
- 0/*inclPathAdd*/
- }
- };
- #if 0
- /*
- ** Outputs a printf()-formatted message to c-pp's global output
- ** channel.
- */
- static void g_outf(char const *zFmt, ...);
- void g_outf(char const *zFmt, ...){
- va_list va;
- va_start(va, zFmt);
- vfprintf(g.out.pFile, zFmt, va);
- va_end(va);
- }
- #endif
- #if 0
- /* Outputs n bytes from z to c-pp's global output channel. */
- static void g_out(void const *z, unsigned int n);
- void g_out(void const *z, unsigned int n){
- if(1!=fwrite(z, n, 1, g.out.pFile)){
- int const err = errno;
- fatal("fwrite() output failed with errno #%d", err);
- }
- }
- #endif
- void g_stderrv(char const *zFmt, va_list va){
- vfprintf(stderr, zFmt, va);
- }
- void g_stderr(char const *zFmt, ...){
- va_list va;
- va_start(va, zFmt);
- g_stderrv(zFmt, va);
- va_end(va);
- }
- void cmpp_t_out(CmppTokenizer * t, void const *z, unsigned int n){
- g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t)));
- g_debug(3,("CT_skip() ?= %d\n",CT_skip(t)));
- if(!CT_skip(t)){
- if(1!=fwrite(z, n, 1, g.out.pFile)){
- int const err = errno;
- fatal("fwrite() output failed with errno #%d", err);
- }
- }
- }
- void CmppLevel_push(CmppTokenizer * const t){
- CmppLevel * pPrev;
- CmppLevel * p;
- if(t->level.ndx+1 == (unsigned)CmppLevel_Max){
- fatal("%sif nesting level is too deep. Max=%d\n",
- g.zDelim, CmppLevel_Max);
- }
- pPrev = &CT_level(t);
- g_debug(3,("push from tokenizer level=%u flags=%04x\n", t->level.ndx, pPrev->flags));
- p = &t->level.stack[++t->level.ndx];
- *p = CmppLevel_empty;
- p->token = t->token;
- p->flags = (CmppLevel_F_INHERIT_MASK & pPrev->flags);
- if(CLvl_skip(pPrev)) p->flags |= CmppLevel_F_ELIDE;
- g_debug(3,("push to tokenizer level=%u flags=%04x\n", t->level.ndx, p->flags));
- }
- void CmppLevel_pop(CmppTokenizer * const t){
- if(!t->level.ndx){
- fatal("Internal error: CmppLevel_pop() at the top of the stack");
- }
- g_debug(3,("pop from tokenizer level=%u, flags=%04x skipLevel?=%d\n", t->level.ndx,
- t->level.stack[t->level.ndx].flags, CT_skipLevel(t)));
- g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t)));
- g_debug(3,("CT_skip() ?= %d\n",CT_skip(t)));
- t->level.stack[t->level.ndx--] = CmppLevel_empty;
- g_debug(3,("pop to tokenizer level=%u, flags=%04x\n", t->level.ndx,
- t->level.stack[t->level.ndx].flags));
- g_debug(3,("CT_skipLevel() ?= %d\n",CT_skipLevel(t)));
- g_debug(3,("CT_skip() ?= %d\n",CT_skip(t)));
- }
- CmppLevel * CmppLevel_get(CmppTokenizer * const t){
- return &t->level.stack[t->level.ndx];
- }
- void db_affirm_rc(int rc, const char * zMsg){
- if(rc){
- fatal("Db error #%d %s: %s", rc, zMsg, sqlite3_errmsg(g.db));
- }
- }
- void db_finalize(sqlite3_stmt *pStmt){
- sqlite3_finalize(pStmt);
- }
- int db_step(sqlite3_stmt *pStmt){
- int const rc = sqlite3_step(pStmt);
- if(SQLITE_ROW!=rc && SQLITE_DONE!=rc){
- db_affirm_rc(rc, "from db_step()");
- }
- return rc;
- }
- static sqlite3_str * db_str_new(void){
- sqlite3_str * rc = sqlite3_str_new(g.db);
- if(!rc) fatal("Alloc failed for sqlite3_str_new()");
- return rc;
- }
- static char * db_str_finish(sqlite3_str *s, int * n){
- int const rc = sqlite3_str_errcode(s);
- if(rc) fatal("Error #%d from sqlite3_str_errcode()", rc);
- if(n) *n = sqlite3_str_length(s);
- char * z = sqlite3_str_finish(s);
- if(!z) fatal("Alloc failed for sqlite3_str_new()");
- return z;
- }
- void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...){
- int rc;
- sqlite3_str * str = db_str_new();
- char * z = 0;
- int n = 0;
- va_list va;
- if(!str) fatal("sqlite3_str_new() failed");
- va_start(va, zSql);
- sqlite3_str_vappendf(str, zSql, va);
- va_end(va);
- rc = sqlite3_str_errcode(str);
- if(rc) fatal("sqlite3_str_errcode() = %d", rc);
- z = db_str_finish(str, &n);
- rc = sqlite3_prepare_v2(g.db, z, n, pStmt, 0);
- if(rc) fatal("Error #%d (%s) preparing: %s",
- rc, sqlite3_errmsg(g.db), z);
- sqlite3_free(z);
- }
- void db_bind_int(sqlite3_stmt *pStmt, int col, int val){
- int const rc = sqlite3_bind_int(pStmt, col, val);
- db_affirm_rc(rc,"from db_bind_int()");
- }
- #if 0
- void db_bind_null(sqlite3_stmt *pStmt, int col){
- int const rc = sqlite3_bind_null(pStmt, col);
- db_affirm_rc(rc,"from db_bind_null()");
- }
- #endif
- void db_bind_textn(sqlite3_stmt *pStmt, int col,
- const char * zStr, int n){
- int const rc = zStr
- ? sqlite3_bind_text(pStmt, col, zStr, n, SQLITE_TRANSIENT)
- : sqlite3_bind_null(pStmt, col);
- db_affirm_rc(rc,"from db_bind_textn()");
- }
- void db_bind_text(sqlite3_stmt *pStmt, int col,
- const char * zStr){
- db_bind_textn(pStmt, col, zStr, -1);
- }
- #if 0
- void db_bind_textv(sqlite3_stmt *pStmt, int col,
- const char * zFmt, ...){
- int rc;
- sqlite3_str * str = db_str_new();
- int n = 0;
- char * z;
- va_list va;
- va_start(va,zFmt);
- sqlite3_str_vappendf(str, zFmt, va);
- va_end(va);
- z = db_str_finish(str, &n);
- rc = sqlite3_bind_text(pStmt, col, z, n, sqlite3_free);
- db_affirm_rc(rc,"from db_bind_textv()");
- }
- #endif
- void db_free(void *m){
- sqlite3_free(m);
- }
- void db_define_add(const char * zKey){
- int rc;
- if(!g.stmt.defIns){
- db_prepare(&g.stmt.defIns,
- "INSERT OR REPLACE INTO def(k) VALUES(?)");
- }
- db_bind_text(g.stmt.defIns, 1, zKey);
- rc = db_step(g.stmt.defIns);
- if(SQLITE_DONE != rc){
- db_affirm_rc(rc, "Stepping INSERT on def");
- }
- g_debug(2,("define: %s\n",zKey));
- sqlite3_reset(g.stmt.defIns);
- }
- int db_define_has(const char * zName){
- int rc;
- if(!g.stmt.defHas){
- db_prepare(&g.stmt.defHas, "SELECT 1 FROM def WHERE k=?");
- }
- db_bind_text(g.stmt.defHas, 1, zName);
- rc = db_step(g.stmt.defHas);
- if(SQLITE_ROW == rc){
- rc = 1;
- }else{
- assert(SQLITE_DONE==rc);
- rc = 0;
- }
- g_debug(1,("defined [%s] ?= %d\n",zName, rc));
- sqlite3_clear_bindings(g.stmt.defHas);
- sqlite3_reset(g.stmt.defHas);
- return rc;
- }
- void db_define_rm(const char * zKey){
- int rc;
- int n = 0;
- const char *zPos = zKey;
- if(!g.stmt.defDel){
- db_prepare(&g.stmt.defDel, "DELETE FROM def WHERE k=?");
- }
- for( ; *zPos && '='!=*zPos; ++n, ++zPos) {}
- db_bind_text(g.stmt.defDel, 1, zKey);
- rc = db_step(g.stmt.defDel);
- if(SQLITE_DONE != rc){
- db_affirm_rc(rc, "Stepping DELETE on def");
- }
- g_debug(2,("undefine: %.*s\n",n, zKey));
- sqlite3_clear_bindings(g.stmt.defDel);
- sqlite3_reset(g.stmt.defDel);
- }
- void db_including_add(const char * zKey, const char * zSrc, int srcLine){
- int rc;
- if(!g.stmt.inclIns){
- db_prepare(&g.stmt.inclIns,
- "INSERT OR FAIL INTO incl(file,srcFile,srcLine) VALUES(?,?,?)");
- }
- db_bind_text(g.stmt.inclIns, 1, zKey);
- db_bind_text(g.stmt.inclIns, 2, zSrc);
- db_bind_int(g.stmt.inclIns, 3, srcLine);
- rc = db_step(g.stmt.inclIns);
- if(SQLITE_DONE != rc){
- db_affirm_rc(rc, "Stepping INSERT on incl");
- }
- g_debug(2,("inclpath add [%s] from [%s]:%d\n", zKey, zSrc, srcLine));
- sqlite3_clear_bindings(g.stmt.inclIns);
- sqlite3_reset(g.stmt.inclIns);
- }
- void db_include_rm(const char * zKey){
- int rc;
- if(!g.stmt.inclDel){
- db_prepare(&g.stmt.inclDel, "DELETE FROM incl WHERE file=?");
- }
- db_bind_text(g.stmt.inclDel, 1, zKey);
- rc = db_step(g.stmt.inclDel);
- if(SQLITE_DONE != rc){
- db_affirm_rc(rc, "Stepping DELETE on incl");
- }
- g_debug(2,("inclpath rm [%s]\n", zKey));
- sqlite3_clear_bindings(g.stmt.inclDel);
- sqlite3_reset(g.stmt.inclDel);
- }
- char * db_include_search(const char * zKey){
- char * zName = 0;
- if(!g.stmt.inclSearch){
- db_prepare(&g.stmt.inclSearch,
- "SELECT ?1 fn WHERE fileExists(fn) "
- "UNION ALL SELECT * FROM ("
- "SELECT replace(dir||'/'||?1, '//','/') AS fn "
- "FROM inclpath WHERE fileExists(fn) ORDER BY seq"
- ")");
- }
- db_bind_text(g.stmt.inclSearch, 1, zKey);
- if(SQLITE_ROW==db_step(g.stmt.inclSearch)){
- const unsigned char * z = sqlite3_column_text(g.stmt.inclSearch, 0);
- zName = z ? sqlite3_mprintf("%s", z) : 0;
- if(!zName) fatal("Alloc failed");
- }
- sqlite3_clear_bindings(g.stmt.inclSearch);
- sqlite3_reset(g.stmt.inclSearch);
- return zName;
- }
- static int db_including_has(const char * zName){
- int rc;
- if(!g.stmt.inclHas){
- db_prepare(&g.stmt.inclHas, "SELECT 1 FROM incl WHERE file=?");
- }
- db_bind_text(g.stmt.inclHas, 1, zName);
- rc = db_step(g.stmt.inclHas);
- if(SQLITE_ROW == rc){
- rc = 1;
- }else{
- assert(SQLITE_DONE==rc);
- rc = 0;
- }
- g_debug(2,("inclpath has [%s] = %d\n",zName, rc));
- sqlite3_clear_bindings(g.stmt.inclHas);
- sqlite3_reset(g.stmt.inclHas);
- return rc;
- }
- #if 0
- /*
- ** Fails fatally if the `#include` list contains the given key.
- */
- static void db_including_check(const char * zKey);
- void db_including_check(const char * zName){
- if(db_including_has(zName)){
- fatal("Recursive include detected: %s\n", zName);
- }
- }
- #endif
- void db_include_dir_add(const char * zDir){
- static int seq = 0;
- int rc;
- if(!g.stmt.inclPathAdd){
- db_prepare(&g.stmt.inclPathAdd,
- "INSERT OR FAIL INTO inclpath(seq,dir) VALUES(?,?)");
- }
- db_bind_int(g.stmt.inclPathAdd, 1, ++seq);
- db_bind_text(g.stmt.inclPathAdd, 2, zDir);
- rc = db_step(g.stmt.inclPathAdd);
- if(SQLITE_DONE != rc){
- db_affirm_rc(rc, "Stepping INSERT on inclpath");
- }
- g_debug(2,("inclpath add #%d: %s\n",seq, zDir));
- sqlite3_clear_bindings(g.stmt.inclPathAdd);
- sqlite3_reset(g.stmt.inclPathAdd);
- }
- static void cmpp_atexit(void){
- #define FINI(M) if(g.stmt.M) sqlite3_finalize(g.stmt.M)
- FINI(defIns); FINI(defDel); FINI(defHas);
- FINI(inclIns); FINI(inclDel); FINI(inclHas);
- FINI(inclPathAdd); FINI(inclSearch);
- #undef FINI
- FileWrapper_close(&g.out);
- if(g.db) sqlite3_close(g.db);
- }
- /*
- ** sqlite3 UDF which returns true if its argument refers to an
- ** accessible file, else false.
- */
- static void udf_file_exists(
- sqlite3_context *context,
- int argc,
- sqlite3_value **argv
- ){
- const char *zName;
- (void)(argc); /* Unused parameter */
- zName = (const char*)sqlite3_value_text(argv[0]);
- if( zName==0 ) return;
- sqlite3_result_int(context, 0==access(zName, 0));
- }
- /* Initialize g.db, failing fatally on error. */
- static void cmpp_initdb(void){
- int rc;
- char * zErr = 0;
- const char * zSchema =
- "CREATE TABLE def("
- "k TEXT PRIMARY KEY NOT NULL"
- /*"v INTEGER DEFAULT 1"*/
- ") WITHOUT ROWID;"
- /* ^^^ defines */
- "CREATE TABLE incl("
- "file TEXT PRIMARY KEY NOT NULL,"
- "srcFile TEXT DEFAULT NULL,"
- "srcLine INTEGER DEFAULT 0"
- ") WITHOUT ROWID;"
- /* ^^^ files currently being included */
- "CREATE TABLE inclpath("
- "seq INTEGER UNIQUE, "
- "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
- ")"
- /* ^^^ include path */
- ;
- assert(0==g.db);
- if(g.db) return;
- rc = sqlite3_open_v2(":memory:", &g.db, SQLITE_OPEN_READWRITE, 0);
- if(rc) fatal("Error opening :memory: db.");
- rc = sqlite3_exec(g.db, zSchema, 0, 0, &zErr);
- if(rc) fatal("Error initializing database: %s", zErr);
- rc = sqlite3_create_function(g.db, "fileExists", 1,
- SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
- udf_file_exists, 0, 0);
- db_affirm_rc(rc, "UDF registration failed.");
- }
- /*
- ** For position zPos, which must be in the half-open range
- ** [zBegin,zEnd), returns g.nDelim if it is at the start of a line and
- ** starts with g.zDelim, else returns 0.
- */
- static unsigned short cmpp_is_delim(unsigned char const *zBegin,
- unsigned char const *zEnd,
- unsigned char const *zPos){
- assert(zEnd>zBegin);
- assert(zPos<zEnd);
- assert(zPos>=zBegin);
- if(zPos>zBegin &&
- ('\n'!=*(zPos - 1)
- || ((unsigned)(zEnd - zPos) <= g.nDelim))){
- return 0;
- }else if(0==memcmp(zPos, g.zDelim, g.nDelim)){
- return g.nDelim;
- }else{
- return 0;
- }
- }
- /*
- ** Scans t to the next keyword line, emitting all input before that
- ** which is _not_ a keyword line unless it's elided due to being
- ** inside a block which elides its content. Returns 0 if no keyword
- ** line was found, in which case the end of the input has been
- ** reached, else returns a truthy value and sets up t's state for use
- ** with cmpp_process_keyword(), which should then be called.
- */
- static int cmpp_next_keyword_line(CmppTokenizer * const t){
- unsigned char const * zStart;
- unsigned char const * z;
- CmppToken * const tok = &t->token;
- unsigned short isDelim = 0;
- assert(t->zBegin);
- assert(t->zEnd > t->zBegin);
- if(!t->zPos) t->zPos = t->zBegin;
- t->zAnchor = t->zPos;
- zStart = z = t->zPos;
- *tok = CmppToken_empty;
- while(z<t->zEnd
- && 0==(isDelim = cmpp_is_delim(t->zBegin, t->zEnd, z))){
- ++z;
- }
- if(z>zStart){
- /* We passed up content */
- cmpp_t_out(t, zStart, (unsigned)(z - zStart));
- }
- assert(isDelim==0 || isDelim==g.nDelim);
- tok->lineNo = t->lineNo += count_lines(zStart, z);
- if(isDelim){
- /* Handle backslash-escaped newlines */
- int isEsc = 0, atEol = 0;
- tok->zBegin = z+isDelim;
- for( ++z ; z<t->zEnd && 0==atEol; ++z ){
- switch((int)*z){
- case (int)'\\':
- isEsc = 0==isEsc; break;
- case (int)'\n':
- atEol = 0==isEsc;
- isEsc = 0;
- ++t->lineNo;
- break;
- default:
- break;
- }
- }
- tok->zEnd = atEol ? z-1 : z;
- /* Strip leading spaces */
- while(tok->zBegin < tok->zEnd && isspace((char)(*tok->zBegin))){
- ++tok->zBegin;
- }
- tok->ttype = TT_Line;
- g_debug(2,("Keyword @ line %u: [[[%.*s]]]\n",
- tok->lineNo,
- (int)(tok->zEnd-tok->zBegin), tok->zBegin));
- }
- t->zPos = z;
- if(isDelim){
- /* Split t->token into arguments for the line's keyword */
- int i, argc = 0, prevChar = 0;
- const unsigned tokLen = (unsigned)(tok->zEnd - tok->zBegin);
- unsigned char * zKwd;
- unsigned char * zEsc;
- unsigned char * zz;
- assert(TT_Line==tok->ttype);
- if((unsigned)sizeof(t->args.lineBuf) < tokLen + 1){
- fatal("Keyword line is unreasonably long: %.*s",
- tokLen, tok->zBegin);
- }else if(!tokLen){
- fatal("Line #%u has no keyword after delimiter", tok->lineNo);
- }
- g_debug(2,("token @ line %u len=%u [[[%.*s]]]\n",
- tok->lineNo, tokLen, tokLen, tok->zBegin));
- zKwd = &t->args.lineBuf[0];
- memcpy(zKwd, tok->zBegin, tokLen);
- memset(zKwd + tokLen, 0, sizeof(t->args.lineBuf) - tokLen);
- for( zEsc = 0, zz = zKwd; *zz; ++zz ){
- /* Convert backslash-escaped newlines to whitespace */
- switch((int)*zz){
- case (int)'\\':
- if(zEsc) zEsc = 0;
- else zEsc = zz;
- break;
- case (int)'\n':
- assert(zEsc && "Should not have an unescaped newline?");
- if(zEsc==zz-1){
- *zEsc = (unsigned char)' ';
- /* FIXME?: memmove() lnBuf content one byte to the left here
- ** to collapse backslash and newline into a single
- ** byte. Also consider collapsing all leading space on the
- ** next line. */
- }
- zEsc = 0;
- *zz = (unsigned char)' ';
- break;
- default:
- zEsc = 0;
- break;
- }
- }
- t->args.argv[argc++] = zKwd;
- for( zz = zKwd; *zz; ++zz ){
- if(isspace(*zz)){
- *zz = 0;
- break;
- }
- }
- t->args.pKw = CmppKeyword_search((char const *)zKwd);
- if(!t->args.pKw){
- fatal("Unknown keyword '%s' at line %u\n", (char const *)zKwd,
- tok->lineNo);
- }
- for( ++zz ; *zz && isspace(*zz); ++zz ){}
- if(t->args.pKw->bTokenize){
- for( ; *zz; prevChar = *zz, ++zz ){
- /* Split string into word-shaped tokens.
- ** TODO ?= quoted strings, for the sake of the
- ** #error keyword. */
- if(isspace(*zz)){
- assert(zz!=zKwd && "Leading space was stripped earlier.");
- *zz = 0;
- }else{
- if(argc == (int)CmppArgs_Max){
- fatal("Too many arguments @ line %u: %.*s",
- tok->lineNo, tokLen, tok->zBegin);
- }else if(zz>zKwd && !prevChar){
- t->args.argv[argc++] = zz;
- }
- }
- }
- }else{
- /* Treat rest of line as one token */
- if(*zz) t->args.argv[argc++] = zz;
- }
- tok->ttype = t->args.pKw->ttype;
- if(g.doDebug>1){
- for(i = 0; i < argc; ++i){
- g_debug(0,("line %u arg #%d=%s\n",
- tok->lineNo, i,
- (char const *)t->args.argv[i]));
- }
- }
- t->args.argc = argc;
- }else{
- t->args.pKw = 0;
- t->args.argc = 0;
- }
- return isDelim;
- }
- static void cmpp_kwd__err_prefix(CmppKeyword const * pKw, CmppTokenizer *t,
- char const *zPrefix){
- g_stderr("%s%s%s @ %s line %u: ",
- zPrefix ? zPrefix : "",
- zPrefix ? ": " : "",
- pKw->zName, t->zName, t->token.lineNo);
- }
- /* Internal error reporting helper for cmpp_keyword_f() impls. */
- static CMPP_NORETURN void cmpp_kwd__misuse(CmppKeyword const * pKw,
- CmppTokenizer *t,
- char const *zFmt, ...){
- va_list va;
- cmpp_kwd__err_prefix(pKw, t, "Fatal error");
- va_start(va, zFmt);
- fatalv(zFmt, va);
- va_end(va);
- }
- /* No-op cmpp_keyword_f() impl. */
- static void cmpp_kwd_noop(CmppKeyword const * pKw, CmppTokenizer *t){
- if(t || pKw){/*unused*/}
- }
- /* #error impl. */
- static void cmpp_kwd_error(CmppKeyword const * pKw, CmppTokenizer *t){
- if(CT_skip(t)) return;
- else{
- assert(t->args.argc < 3);
- const char *zBegin = t->args.argc>1
- ? (const char *)t->args.argv[1] : 0;
- cmpp_kwd__err_prefix(pKw, t, NULL);
- fatal("%s", zBegin ? zBegin : "(no additional info)");
- }
- }
- /* Impl. for #define, #undef */
- static void cmpp_kwd_define(CmppKeyword const * pKw, CmppTokenizer *t){
- if(CT_skip(t)) return;
- if(t->args.argc<2){
- cmpp_kwd__misuse(pKw, t, "Expecting one or more arguments");
- }else{
- int i = 1;
- void (*func)(const char *) = TT_Define==pKw->ttype
- ? db_define_add : db_define_rm;
- for( ; i < t->args.argc; ++i){
- func( (char const *)t->args.argv[i] );
- }
- }
- }
- /* Impl. for #if, #ifnot, #elif, #elifnot. */
- static void cmpp_kwd_if(CmppKeyword const * pKw, CmppTokenizer *t){
- int buul;
- CmppParseState tmpState = TS_Start;
- if(t->args.argc!=2){
- cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 argument");
- }
- /*g_debug(0,("%s %s level %u pstate=%d\n", pKw->zName,
- (char const *)t->args.argv[1],
- t->level.ndx, (int)CT_pstate(t)));*/
- switch(pKw->ttype){
- case TT_Elif:
- case TT_ElifNot:
- switch(CT_pstate(t)){
- case TS_If: break;
- case TS_IfPassed: CT_level(t).flags |= CmppLevel_F_ELIDE; return;
- default: goto misuse;
- }
- break;
- case TT_If:
- case TT_IfNot:
- CmppLevel_push(t);
- break;
- default:
- cmpp_kwd__misuse(pKw, t, "Unpexected keyword token type");
- break;
- }
- buul = db_define_has((char const *)t->args.argv[1]);
- if(TT_IfNot==pKw->ttype || TT_ElifNot==pKw->ttype) buul = !buul;
- if(buul){
- CT_pstate(t) = tmpState = TS_IfPassed;
- CT_skipLevel(t) = 0;
- }else{
- CT_pstate(t) = TS_If /* also for TT_IfNot, TT_Elif, TT_ElifNot */;
- CT_skipLevel(t) = 1;
- g_debug(3,("setting CT_skipLevel = 1 @ level %d\n", t->level.ndx));
- }
- if(TT_If==pKw->ttype || TT_IfNot==pKw->ttype){
- unsigned const lvlIf = t->level.ndx;
- CmppToken const lvlToken = CT_level(t).token;
- while(cmpp_next_keyword_line(t)){
- cmpp_process_keyword(t);
- if(lvlIf > t->level.ndx){
- assert(TT_EndIf == t->token.ttype);
- break;
- }
- #if 0
- if(TS_IfPassed==tmpState){
- tmpState = TS_Start;
- t->level.stack[lvlIf].flags |= CmppLevel_F_ELIDE;
- g_debug(1,("Setting ELIDE for TS_IfPassed @ lv %d (lvlIf=%d)\n", t->level.ndx, lvlIf));
- }
- #endif
- }
- if(lvlIf <= t->level.ndx){
- cmpp_kwd__err_prefix(pKw, t, NULL);
- fatal("Input ended inside an unterminated %sif "
- "opened at [%s] line %u",
- g.zDelim, t->zName, lvlToken.lineNo);
- }
- }
- return;
- misuse:
- cmpp_kwd__misuse(pKw, t, "'%s' used out of context",
- pKw->zName);
- }
- /* Impl. for #else. */
- static void cmpp_kwd_else(CmppKeyword const * pKw, CmppTokenizer *t){
- if(t->args.argc>1){
- cmpp_kwd__misuse(pKw, t, "Expecting no arguments");
- }
- switch(CT_pstate(t)){
- case TS_IfPassed: CT_skipLevel(t) = 1; break;
- case TS_If: CT_skipLevel(t) = 0; break;
- default:
- cmpp_kwd__misuse(pKw, t, "'%s' with no matching 'if'",
- pKw->zName);
- }
- /*g_debug(0,("else flags=0x%02x skipLevel=%u\n",
- CT_level(t).flags, CT_level(t).skipLevel));*/
- CT_pstate(t) = TS_Else;
- }
- /* Impl. for #endif. */
- static void cmpp_kwd_endif(CmppKeyword const * pKw, CmppTokenizer *t){
- /* Maintenance reminder: we ignore all arguments after the endif
- ** to allow for constructs like:
- **
- ** #endif // foo
- **
- ** in a manner which does not require a specific comment style */
- switch(CT_pstate(t)){
- case TS_Else:
- case TS_If:
- case TS_IfPassed:
- break;
- default:
- cmpp_kwd__misuse(pKw, t, "'%s' with no matching 'if'",
- pKw->zName);
- }
- CmppLevel_pop(t);
- }
- /* Impl. for #include. */
- static void cmpp_kwd_include(CmppKeyword const * pKw, CmppTokenizer *t){
- char const * zFile;
- char * zResolved;
- if(CT_skip(t)) return;
- else if(t->args.argc!=2){
- cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 filename argument");
- }
- zFile = (const char *)t->args.argv[1];
- if(db_including_has(zFile)){
- /* Note that different spellings of the same filename
- ** will elude this check, but that seems okay, as different
- ** spellings means that we're not re-running the exact same
- ** invocation. We might want some other form of multi-include
- ** protection, rather than this, however. There may well be
- ** sensible uses for recursion. */
- cmpp_kwd__err_prefix(pKw, t, NULL);
- fatal("Recursive include of file: %s", zFile);
- }
- zResolved = db_include_search(zFile);
- if(zResolved){
- db_including_add(zFile, t->zName, t->token.lineNo);
- cmpp_process_file(zResolved);
- db_include_rm(zFile);
- db_free(zResolved);
- }else{
- cmpp_kwd__err_prefix(pKw, t, NULL);
- fatal("file not found: %s", zFile);
- }
- }
- /* Impl. for #pragma. */
- static void cmpp_kwd_pragma(CmppKeyword const * pKw, CmppTokenizer *t){
- const char * zArg;
- if(CT_skip(t)) return;
- else if(t->args.argc!=2){
- cmpp_kwd__misuse(pKw, t, "Expecting one argument");
- }
- zArg = (const char *)t->args.argv[1];
- #define M(X) 0==strcmp(zArg,X)
- if(M("defines")){
- sqlite3_stmt * q = 0;
- db_prepare(&q, "SELECT k FROM def ORDER BY k");
- g_stderr("cmpp defines:\n");
- while(SQLITE_ROW==db_step(q)){
- int const n = sqlite3_column_bytes(q, 0);
- const char * z = (const char *)sqlite3_column_text(q, 0);
- g_stderr("\t%.*s\n", n, z);
- }
- db_finalize(q);
- }else{
- cmpp_kwd__misuse(pKw, t, "Unknown pragma");
- }
- #undef M
- }
- /* #stder impl. */
- static void cmpp_kwd_stderr(CmppKeyword const * pKw, CmppTokenizer *t){
- if(CT_skip(t)) return;
- else{
- const char *zBegin = t->args.argc>1
- ? (const char *)t->args.argv[1] : 0;
- if(zBegin){
- g_stderr("%s:%u: %s\n", t->zName, t->token.lineNo, zBegin);
- }else{
- g_stderr("%s:%u: (no %.*s%s argument)\n",
- t->zName, t->token.lineNo,
- g.nDelim, g.zDelim, pKw->zName);
- }
- }
- }
- #if 0
- /* Impl. for dummy placeholder. */
- static void cmpp_kwd_todo(CmppKeyword const * pKw, CmppTokenizer *t){
- if(t){/*unused*/}
- g_debug(0,("TODO: keyword handler for %s\n", pKw->zName));
- }
- #endif
- CmppKeyword aKeywords[] = {
- /* Keep these sorted by zName */
- {"//", 2, 0, TT_Comment, cmpp_kwd_noop},
- {"define", 6, 1, TT_Define, cmpp_kwd_define},
- {"elif", 4, 1, TT_Elif, cmpp_kwd_if},
- {"elifnot", 7, 1, TT_ElifNot, cmpp_kwd_if},
- {"else", 4, 1, TT_Else, cmpp_kwd_else},
- {"endif", 5, 0, TT_EndIf, cmpp_kwd_endif},
- {"error", 4, 0, TT_Error, cmpp_kwd_error},
- {"if", 2, 1, TT_If, cmpp_kwd_if},
- {"ifnot", 5, 1, TT_IfNot, cmpp_kwd_if},
- {"include", 7, 0, TT_Include, cmpp_kwd_include},
- {"pragma", 6, 1, TT_Pragma, cmpp_kwd_pragma},
- {"stderr", 6, 0, TT_Stderr, cmpp_kwd_stderr},
- {"undef", 5, 1, TT_Undef, cmpp_kwd_define},
- {0,0,TT_Invalid, 0}
- };
- static int cmp_CmppKeyword(const void *p1, const void *p2){
- char const * zName = (const char *)p1;
- CmppKeyword const * kw = (CmppKeyword const *)p2;
- return strcmp(zName, kw->zName);
- }
- CmppKeyword const * CmppKeyword_search(const char *zName){
- return (CmppKeyword const *)bsearch(zName, &aKeywords[0],
- sizeof(aKeywords)/sizeof(aKeywords[0]) - 1,
- sizeof(aKeywords[0]),
- cmp_CmppKeyword);
- }
- void cmpp_process_keyword(CmppTokenizer * const t){
- assert(t->args.pKw);
- assert(t->args.argc);
- t->args.pKw->xCall(t->args.pKw, t);
- t->args.pKw = 0;
- t->args.argc = 0;
- }
- void cmpp_process_file(const char * zName){
- FileWrapper fw = FileWrapper_empty;
- CmppTokenizer ct = CmppTokenizer_empty;
- FileWrapper_open(&fw, zName, "r");
- FileWrapper_slurp(&fw);
- g_debug(1,("Read %u byte(s) from [%s]\n", fw.nContent, fw.zName));
- ct.zName = zName;
- ct.zBegin = fw.zContent;
- ct.zEnd = fw.zContent + fw.nContent;
- while(cmpp_next_keyword_line(&ct)){
- cmpp_process_keyword(&ct);
- }
- FileWrapper_close(&fw);
- if(0!=ct.level.ndx){
- CmppLevel * const lv = CmppLevel_get(&ct);
- fatal("Input ended inside an unterminated nested construct"
- "opened at [%s] line %u", zName, lv->token.lineNo);
- }
- }
- static void usage(int isErr){
- FILE * const fOut = isErr ? stderr : stdout;
- fprintf(fOut,
- "Usage: %s [flags] [infile]\n"
- "Flags:\n",
- g.zArgv0);
- #define arg(F,D) fprintf(fOut," %s\n %s\n",F, D)
- arg("-f|--file FILE","Read input from FILE (default=- (stdin)).\n"
- " Alternately, the first non-flag argument is assumed to "
- "be the input file.");
- arg("-o|--outfile FILE","Send output to FILE (default=- (stdout))");
- arg("-DXYZ","Define XYZ to true");
- arg("-UXYZ","Undefine XYZ (equivalent to false)");
- arg("-IXYZ","Add dir XYZ to include path");
- arg("-d|--delimiter VALUE", "Set keyword delimiter to VALUE "
- "(default=" CMPP_DEFAULT_DELIM ")");
- #undef arg
- fputs("",fOut);
- }
- int main(int argc, char const * const * argv){
- int rc = 0;
- int i;
- int inclCount = 0;
- const char * zInfile = 0;
- #define M(X) (0==strcmp(X,zArg))
- #define ISFLAG(X) else if(M(X))
- #define ISFLAG2(X,Y) else if(M(X) || M(Y))
- #define ARGVAL \
- if(i+1>=argc) fatal("Missing value for flag '%s'", zArg); \
- zArg = argv[++i]
- g.zArgv0 = argv[0];
- atexit(cmpp_atexit);
- cmpp_initdb();
- for(i = 1; i < argc; ++i){
- char const * zArg = argv[i];
- while('-'==*zArg) ++zArg;
- if(M("?") || M("help")) {
- usage(0);
- goto end;
- }else if('D'==*zArg){
- ++zArg;
- if(!*zArg) fatal("Missing key for -D");
- db_define_add(zArg);
- }else if('U'==*zArg){
- ++zArg;
- if(!*zArg) fatal("Missing key for -U");
- db_define_rm(zArg);
- }else if('I'==*zArg){
- ++zArg;
- if(!*zArg) fatal("Missing directory for -I");
- db_include_dir_add(zArg);
- ++inclCount;
- }
- ISFLAG2("o","outfile"){
- ARGVAL;
- if(g.out.zName) fatal("Cannot use -o more than once.");
- g.out.zName = zArg;
- }
- ISFLAG2("f","file"){
- ARGVAL;
- do_infile:
- if(zInfile) fatal("Cannot use -i more than once.");
- zInfile = zArg;
- }
- ISFLAG2("d","delimiter"){
- ARGVAL;
- g.zDelim = zArg;
- g.nDelim = (unsigned short)strlen(zArg);
- if(!g.nDelim) fatal("Keyword delimiter may not be empty.");
- }
- ISFLAG("debug"){
- ++g.doDebug;
- }else if(!zInfile && '-'!=argv[i][0]){
- goto do_infile;
- }else{
- fatal("Unhandled flag: %s", argv[i]);
- }
- }
- if(!zInfile) zInfile = "-";
- if(!g.out.zName) g.out.zName = "-";
- if(!inclCount) db_include_dir_add(".");
- FileWrapper_open(&g.out, g.out.zName, "w");
- cmpp_process_file(zInfile);
- FileWrapper_close(&g.out);
- end:
- return rc ? EXIT_FAILURE : EXIT_SUCCESS;
- }
- #undef CT_level
- #undef CT_pstate
- #undef CT_skipLevel
- #undef CT_skip
- #undef CLvl_skip
|