#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#endif
static CMPP_NORETURN void fatalv(char const *zFmt, va_list);
static CMPP_NORETURN void fatal(char const *zFmt, ...);
static void cmpp_free(void *p);
static void * cmpp_realloc(void * p, unsigned n);
#if 0#endif
static void FILE_close(FILE *p);
static FILE * FILE_open(char const *zName, const char * zMode);
static void FILE_slurp(FILE *pFile, unsigned char **pOut,
unsigned * nOut);
static void db_affirm_rc(int rc, const char * zMsg);
static char * db_str_finish(sqlite3_str *s, int * n);
static sqlite3_str * db_str_new(void);
static void db_finalize(sqlite3_stmt *pStmt);
static int db_step(sqlite3_stmt *pStmt);
static void db_bind_int(sqlite3_stmt *pStmt, int col, int val);
#if 0#endif
static void db_bind_text(sqlite3_stmt *pStmt, int col, const char * zStr);
static void db_bind_textn(sqlite3_stmt *pStmt, int col, const char * zStr, int len);
#if 0#endif
static void db_free(void *m);
static void db_define_add(const char * zKey);
static int db_define_has(const char * zName);
static void db_define_rm(const char * zKey);
static void db_including_add(const char * zKey, const char * zSrc, int srcLine);
static void db_include_dir_add(const char * zKey);
static char * db_include_search(const char * zKey);
static void db_include_rm(const char * zKey);
static void db_prepare(sqlite3_stmt **pStmt, const char * zSql, ...);
static void cmpp_process_file(const char * zName);
static unsigned count_lines(unsigned char const * zFrom,
unsigned char const *zTo);
struct FileWrapper {
char const *zName;
FILE * pFile;
unsigned char * zContent;
unsigned nContent;
};
typedef struct FileWrapper FileWrapper;
#define FileWrapper_empty_m {0,0,0,0}
static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
static void FileWrapper_close(FileWrapper * p);
static void FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
static void FileWrapper_slurp(FileWrapper * p);
static void g_stderr(char const *zFmt, ...);
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#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;
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;
unsigned lineNo;
unsigned char const * zBegin;
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;
typedef struct CmppLevel CmppLevel;
struct CmppLevel {
unsigned short flags;
unsigned short skipLevel;
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 {
CmppLevel_Max = 10,
CmppArgs_Max = 10,
CmppLevel_F_ELIDE = 0x01,
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);
struct CmppTokenizer {
const char * zName;
unsigned const char * zBegin;
unsigned const char * zEnd;
unsigned const char * zAnchor;
unsigned const char * zPos;
unsigned int lineNo;
CmppParseState pstate;
CmppToken token;
struct {
unsigned ndx;
CmppLevel stack[CmppLevel_Max];
} level;
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, \
TS_Start, \
CmppToken_empty_m, \
{0U,{CmppLevel_empty_m}}, \
{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 CmppLevel_push(CmppTokenizer * const t);
static void CmppLevel_pop(CmppTokenizer * const t);
static CmppLevel * CmppLevel_get(CmppTokenizer * const t);
static struct Global {
const char * zArgv0;
const char * zDelim;
unsigned short nDelim;
int doDebug;
sqlite3 * db;
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,
(unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1,
0,
0,
FileWrapper_empty_m,
{
0, 0, 0,
0, 0, 0,
0
}
};
#if 0#endif
#if 0#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#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#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#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);
}
static void udf_file_exists(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zName;
(void)(argc);
zName = (const char*)sqlite3_value_text(argv[0]);
if( zName==0 ) return;
sqlite3_result_int(context, 0==access(zName, 0));
}
static void cmpp_initdb(void){
int rc;
char * zErr = 0;
const char * zSchema =
"CREATE TABLE def("
"k TEXT PRIMARY KEY NOT NULL"
") WITHOUT ROWID;"
"CREATE TABLE incl("
"file TEXT PRIMARY KEY NOT NULL,"
"srcFile TEXT DEFAULT NULL,"
"srcLine INTEGER DEFAULT 0"
") WITHOUT ROWID;"
"CREATE TABLE inclpath("
"seq INTEGER UNIQUE, "
"dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
")"
;
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.");
}
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;
}
}
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){
cmpp_t_out(t, zStart, (unsigned)(z - zStart));
}
assert(isDelim==0 || isDelim==g.nDelim);
tok->lineNo = t->lineNo += count_lines(zStart, z);
if(isDelim){
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;
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){
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 ){
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)' ';
}
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 ){
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{
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);
}
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);
}
static void cmpp_kwd_noop(CmppKeyword const * pKw, CmppTokenizer *t){
if(t || pKw){}
}
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)");
}
}
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] );
}
}
}
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");
}
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 ;
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#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);
}
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);
}
CT_pstate(t) = TS_Else;
}
static void cmpp_kwd_endif(CmppKeyword const * pKw, CmppTokenizer *t){
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);
}
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)){
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);
}
}
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
}
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#endif
CmppKeyword aKeywords[] = {
{"//", 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){
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