#define CMPP_DEFAULT_DELIM "##"
#endif
+#ifndef CMPP_ATSIGN
+#define CMPP_ATSIGN (unsigned char)'@'
+#endif
+
#if 1
# define CMPP_NORETURN __attribute__((noreturn))
#else
static void * cmpp_malloc(unsigned n);
#endif
+static void check__oom2(void const *p, char const *zFile, int line){
+ if(!p) fatal("Alloc failed at %s:%d", zFile, line);
+}
+#define check__oom(P) check__oom2((P), __FILE__, __LINE__)
+
/*
** 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.
** 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.
*/
** 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);
+//static
+int db_define_has(const char * zName);
+
+/*
+** Returns true if the given key is already in the `#define` list, and
+** it has a truthy value (is not empty and not equal to '0'), else
+** false. Fails fatally on db error.
+**
+** nName is the length of zName, or <0 to use strlen() to figure
+** it out.
+*/
+static int db_define_get_bool(const char * zName, int nName);
+
+/*
+** Searches for a define where (k GLOB zName). If one is found, a copy
+** of it is assigned to *zVal (the caller must eventually db_free()
+** it)), *nVal (if nVal is not NULL) is assigned its strlen, and
+** returns non-0. If no match is found, 0 is returned and neither
+** *zVal nor *nVal are modified. If more than one result matches, a
+** fatal error is triggered.
+**
+** It is legal for *zVal to be NULL (and *nVal to be 0) if it returns
+** non-0. That just means that the key was defined with no value part.
+*/
+static int db_define_get(const char * zName, int nName, char **zVal, unsigned int *nVal);
+
/*
** Removes the given `#define` macro name from the list of
** macros. Fails fatally on error.
*/
static void cmpp_process_file(const char * zName);
+/*
+ ** If the first nKey bytes of zKey contain a chEq character then:
+ **
+ ** - Assigns *nVal to the strlen() of the part after that (may be 0).
+ ** - If zEq is not NULL, it is set to the position of the chEq.
+ ** - Returns the part one byte after that chEq.
+ **
+ ** Else returns NULL and does not modify *len or *zEq.
+ **
+ ** If nKey is negative then strlen() is used to calculate it.
+ */
+static char const * cmpp_val_part(char const *zKey,
+ int nKey,
+ char chEq,
+ unsigned * nVal,
+ char const **zEq);
+
/*
** Returns the number newline characters between the given starting
** point and inclusive ending point. Results are undefined if zFrom is
static void FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
/* Proxy for FILE_slurp(). */
static void FileWrapper_slurp(FileWrapper * p);
+/*
+** If p->zContent ends in \n or \r\n, that part is replaced with 0 and
+** p->nContent is adjusted. Returns true if it chomps, else false.
+*/
+int FileWrapper_chomp(FileWrapper * p);
/*
** 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);
- fflush(stdout);
- fflush(stderr);
- exit(1);
-}
-
-void fatal(char const *zFmt, ...){
- va_list va;
- va_start(va, zFmt);
- fatalv(zFmt, va);
- va_end(va);
-}
+ if(lvl<=g.flags.doDebug) g_stderr("%s @ %s():%d: ",g.zArgv0,__func__,__LINE__); \
+ if(lvl<=g.flags.doDebug) g_stderr pfexpr
void cmpp_free(void *p){
- free(p);
+ sqlite3_free(p);
}
void * cmpp_realloc(void * p, unsigned n){
- void * const rc = realloc(p, n);
+ void * const rc = sqlite3_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);
+ void * const rc = sqlite3_alloc(n);
if(!rc) fatal("malloc(%u) failed", n);
return rc;
}
FILE_slurp(p->pFile, &p->zContent, &p->nContent);
}
+int FileWrapper_chomp(FileWrapper * p){
+ if( p->nContent && '\n'==p->zContent[p->nContent-1] ){
+ p->zContent[--p->nContent] = 0;
+ if( p->nContent && '\r'==p->zContent[p->nContent-1] ){
+ p->zContent[--p->nContent] = 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
unsigned count_lines(unsigned char const * zFrom, unsigned char const *zTo){
unsigned ln = 0;
unsigned char const *zPos = zFrom;
*/
static CmppLevel * CmppLevel_get(CmppTokenizer * const t);
+#define CMPPLEVEL_GET
+/*
+** Policies for how to handle undefined @tokens@ when performing
+** content filtering.
+*/
+enum AtPolicy {
+ AT_invalid = -1,
+ /** Turn off @foo@ parsing. */
+ AT_OFF = 0,
+ /** Retain undefined @foo@ - emit it as-is. */
+ AT_RETAIN,
+ /** Elide undefined @foo@. */
+ AT_ELIDE,
+ /** Error for undefined @foo@. */
+ AT_ERROR,
+ AT_DEFAULT = AT_ERROR
+};
+typedef enum AtPolicy AtPolicy;
+
+static AtPolicy AtPolicy_fromStr(char const *z){
+ if( 0==strcmp(z, "retain") ) return AT_RETAIN;
+ if( 0==strcmp(z, "elide") ) return AT_ELIDE;
+ if( 0==strcmp(z, "error") ) return AT_ERROR;
+ if( 0==strcmp(z, "off") ) return AT_OFF;
+ return AT_invalid;
+}
+
/*
-** Global app state singleton. */
+** 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;
+ /* Current tokenizer (for error reporting purposes). */
+ CmppTokenizer const * tok;
/* Output channel. */
FileWrapper out;
+ struct {
+ /*
+ ** Bytes of the keyword delimiter/prefix. Owned
+ ** elsewhere.
+ */
+ const char * z;
+ /* Byte length of this->zDelim. */
+ unsigned short n;
+ /*
+ ** The @token@ delimiter.
+ **
+ ** Potential TODO is replace this with a pair of opener/closer
+ ** strings, e.g. "{{" and "}}".
+ */
+ const unsigned char chAt;
+ } delim;
struct {
sqlite3_stmt * defIns;
sqlite3_stmt * defDel;
sqlite3_stmt * defHas;
+ sqlite3_stmt * defGet;
+ sqlite3_stmt * defGetBool;
sqlite3_stmt * inclIns;
sqlite3_stmt * inclDel;
sqlite3_stmt * inclHas;
FILE * pFile;
int expandSql;
} sqlTrace;
+ struct {
+ AtPolicy atPolicy;
+ /* If true, enables certain debugging output. */
+ char doDebug;
+ /* If true, chomp() files read via -Fx=file. */
+ char chompF;
+ } flags;
} g = {
.zArgv0 = "?",
- .zDelim = CMPP_DEFAULT_DELIM,
- .nDelim = (unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1,
- .doDebug = 0,
.db = 0,
+ .tok = 0,
.out = FileWrapper_empty_m,
+ .delim = {
+ .z = CMPP_DEFAULT_DELIM,
+ .n = (unsigned short) sizeof(CMPP_DEFAULT_DELIM)-1,
+ .chAt = '@'
+ },
.stmt = {
.defIns = 0,
.defDel = 0,
.defHas = 0,
+ .defGet = 0,
+ .defGetBool = 0,
.inclIns = 0,
.inclDel = 0,
.inclHas = 0,
.sqlTrace = {
.pFile = 0,
.expandSql = 0
+ },
+ .flags = {
+ .atPolicy = AT_OFF,
+ .doDebug = 0,
+ .chompF = 0
}
};
}
#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){
fatal("fwrite() output failed with errno #%d", err);
}
}
-#endif
void g_stderrv(char const *zFmt, va_list va){
vfprintf(stderr, zFmt, va);
va_end(va);
}
+/*
+** Emits n bytes of z if CT_skip(t) is false.
+*/
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);
- }
- }
+ if(!CT_skip(t)) g_out(z, n);
}
void CmppLevel_push(CmppTokenizer * const t){
CmppLevel * p;
if(t->level.ndx+1 == (unsigned)CmppLevel_Max){
fatal("%sif nesting level is too deep. Max=%d\n",
- g.zDelim, CmppLevel_Max);
+ g.delim.z, CmppLevel_Max);
}
pPrev = &CT_level(t);
g_debug(3,("push from tokenizer level=%u flags=%04x\n", t->level.ndx, pPrev->flags));
void db_affirm_rc(int rc, const char * zMsg){
if(rc){
- fatal("Db error #%d %s: %s", rc, zMsg, sqlite3_errmsg(g.db));
+ fatal("Db error #%d %s: %s", rc, zMsg,
+ sqlite3_errmsg(g.db));
}
}
}
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()");
+ db_affirm_rc(sqlite3_bind_int(pStmt, col, val),
+ "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()");
+ db_affirm_rc(sqlite3_bind_null(pStmt, col),
+ "from db_bind_null()");
}
-#endif
void db_bind_textn(sqlite3_stmt *pStmt, int col,
const char * zStr, int n){
- int const rc = zStr
+ db_affirm_rc(
+ (zStr && n)
? sqlite3_bind_text(pStmt, col, zStr, n, SQLITE_TRANSIENT)
- : sqlite3_bind_null(pStmt, col);
- db_affirm_rc(rc,"from db_bind_textn()");
+ : sqlite3_bind_null(pStmt, col),
+ "from db_bind_textn()"
+ );
}
void db_bind_text(sqlite3_stmt *pStmt, int col,
sqlite3_free(m);
}
-void db_define_add(const char * zKey){
- int rc;
+static sqlite3_stmt * db__prep_add(void){
if(!g.stmt.defIns){
db_prepare(&g.stmt.defIns,
- "INSERT OR REPLACE INTO def(k) VALUES(?)");
+ "INSERT OR REPLACE INTO def(k,v) VALUES(?,?)");
+ }
+ return g.stmt.defIns;
+}
+
+void db_define_add(const char * zKey){
+ int rc;
+ char const * zEq = 0;
+ unsigned nVal = 0;
+ char const * zVal = cmpp_val_part(zKey, -1, '=', &nVal, &zEq);
+ sqlite3_stmt * const q = db__prep_add();
+ //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
+ if( zEq ){
+ db_bind_textn(q, 1, zKey, (zEq-zKey));
+ }else{
+ db_bind_text(q, 1, zKey);
}
- db_bind_text(g.stmt.defIns, 1, zKey);
- rc = db_step(g.stmt.defIns);
+ if( zEq ){
+ if( zVal && nVal ){
+ db_bind_text(q, 2, zVal);
+ }else{
+ db_bind_null(q, 2);
+ }
+ }else{
+ db_bind_int(q, 2, 1);
+ }
+ rc = db_step(q);
if(SQLITE_DONE != rc){
db_affirm_rc(rc, "Stepping INSERT on def");
}
- g_debug(2,("define: %s\n",zKey));
- sqlite3_reset(g.stmt.defIns);
+ g_debug(2,("define: %s%s%s\n",
+ zKey,
+ zVal ? " with value " : "",
+ zVal ? zVal : ""));
+ sqlite3_reset(q);
+}
+
+static void db_define_add_file(const char * zKey){
+ int rc;
+ char const * zEq = 0;
+ unsigned nVal = 0;
+ char const * zVal = cmpp_val_part(zKey, -1, '=', &nVal, &zEq);
+ if( !zVal || !nVal ){
+ fatal("Invalid filename: %s", zKey);
+ }
+ sqlite3_stmt * q = 0;
+ FileWrapper fw = FileWrapper_empty;
+ FileWrapper_open(&fw, zVal, "r");
+ FileWrapper_slurp(&fw);
+ q = db__prep_add();
+ //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
+ db_bind_textn(q, 1, zKey, (zEq-zKey));
+ if( g.flags.chompF ){
+ FileWrapper_chomp(&fw);
+ }
+ if( fw.nContent ){
+ db_affirm_rc(
+ sqlite3_bind_text(q, 2,
+ (char const *)fw.zContent,
+ (int)fw.nContent, sqlite3_free),
+ "binding file content");
+ fw.zContent = 0 /* transfered ownership */;
+ fw.nContent = 0;
+ }else{
+ db_affirm_rc( sqlite3_bind_null(q, 2),
+ "binding empty file content");
+ }
+ FileWrapper_close(&fw);
+ rc = db_step(q);
+ if(SQLITE_DONE != rc){
+ db_affirm_rc(rc, "stepping INSERT on def");
+ }
+ g_debug(2,("define: %s%s%s\n",
+ zKey,
+ zVal ? " with value " : "",
+ zVal ? zVal : ""));
+ sqlite3_clear_bindings(q);
+ sqlite3_reset(q);
}
int db_define_has(const char * zName){
int rc;
- if(!g.stmt.defHas){
- db_prepare(&g.stmt.defHas, "SELECT 1 FROM def WHERE k=?");
+ sqlite3_stmt * q = g.stmt.defHas;
+ if(!q){
+ db_prepare(&q, "SELECT 1 FROM def WHERE k GLOB ?");
+ g.stmt.defHas = q;
}
- db_bind_text(g.stmt.defHas, 1, zName);
- rc = db_step(g.stmt.defHas);
+ db_bind_text(q, 1, zName);
+ rc = db_step(q);
if(SQLITE_ROW == rc){
rc = 1;
}else{
rc = 0;
}
g_debug(1,("defined [%s] ?= %d\n",zName, rc));
- sqlite3_clear_bindings(g.stmt.defHas);
- sqlite3_reset(g.stmt.defHas);
+ sqlite3_clear_bindings(q);
+ sqlite3_reset(q);
+ return rc;
+}
+
+int db_define_get_bool(const char * zName, int nName){
+ sqlite3_stmt * q = g.stmt.defGetBool;
+ int rc = 0;
+ if(!q){
+ db_prepare(
+ &q,
+ "SELECT 1 FROM def WHERE k = ?1"
+ " AND v IS NOT NULL"
+ " AND '0'!=v AND ''!=v"
+ );
+ g.stmt.defGetBool = q;
+ }
+ if( nName<0 ) nName=(int)strlen(zName);
+ db_bind_textn(q, 1, zName, nName);
+ rc = db_step(q);
+ if(SQLITE_ROW == rc){
+ if( SQLITE_ROW==sqlite3_step(q) ){
+ fatal("Key is ambiguous: %s", zName);
+ }
+ rc = 1;
+ }else{
+ assert(SQLITE_DONE==rc);
+ rc = 0;
+ }
+ sqlite3_clear_bindings(q);
+ sqlite3_reset(q);
return rc;
}
+int db_define_get(const char * zName, int nName,
+ char **zVal, unsigned int *nVal){
+ sqlite3_stmt * q = g.stmt.defGet;
+ int rc = 0;
+ int n = 0;
+ if(!q){
+ db_prepare(&q, "SELECT k,v FROM def WHERE k GLOB ?");
+ g.stmt.defGet = q;
+ }
+ if( nName<0 ) nName=(int)strlen(zName);
+ db_bind_textn(q, 1, zName, nName);
+ rc = db_step(q);
+ if(SQLITE_ROW == rc){
+ const unsigned char * z = sqlite3_column_text(q, 1);
+ n = sqlite3_column_bytes(q,1);
+ if( nVal ) *nVal = (unsigned)n;
+ *zVal = n ? sqlite3_mprintf("%.*s", n, z) : 0;
+ if( n && z ) check__oom(*zVal);
+ if( SQLITE_ROW==sqlite3_step(q) ){
+ db_free(*zVal);
+ *zVal = 0;
+ fatal("Key is ambiguous: %.*s\n",
+ nName, zName);
+ }
+ rc = 1;
+ }else{
+ assert(SQLITE_DONE==rc);
+ rc = 0;
+ }
+ g_debug(1,("define [%.*s] ?= %d %.*s\n",
+ nName, zName, rc,
+ *zVal ? n : 0,
+ *zVal ? *zVal : "<NULL>"));
+ sqlite3_clear_bindings(q);
+ sqlite3_reset(q);
+ return rc;
+}
void db_define_rm(const char * zKey){
int rc;
int n = 0;
- //const char *zPos = zKey;
-#if 0
- if( !zKey ){
- if( g.db ){
- sqlite3_exec(g.db, "DELETE FROM def", 0, 0, 0);
- }
- return;
- }
-#endif
if(!g.stmt.defDel){
db_prepare(&g.stmt.defDel, "DELETE FROM def WHERE k GLOB ?");
}
- //for( ; *zPos && '='!=*zPos; ++n, ++zPos) {}
db_bind_text(g.stmt.defDel, 1, zKey);
rc = db_step(g.stmt.defDel);
if(SQLITE_DONE != rc){
static void cmpp_atexit(void){
#define FINI(M) if(g.stmt.M) sqlite3_finalize(g.stmt.M)
- FINI(defIns); FINI(defDel); FINI(defHas);
+ FINI(defIns); FINI(defDel);
+ FINI(defHas); FINI(defGet); FINI(defGetBool);
FINI(inclIns); FINI(inclDel); FINI(inclHas);
FINI(inclPathAdd); FINI(inclSearch);
#undef FINI
switch(t){
case SQLITE_TRACE_STMT:{
FILE * const fp = g.sqlTrace.pFile;
- char const * zSql = (char const *)x;
- char * zExp = g.sqlTrace.expandSql
- ? sqlite3_expanded_sql((sqlite3_stmt*)p)
- : 0;
- assert( fp );
- fprintf(fp, "SQL TRACE #%u: %s\n",
- ++counter, zExp ? zExp : zSql);
- sqlite3_free(zExp);
+ if( fp ){
+ char const * zSql = (char const *)x;
+ char * zExp = g.sqlTrace.expandSql
+ ? sqlite3_expanded_sql((sqlite3_stmt*)p)
+ : 0;
+ fprintf(fp, "SQL TRACE #%u: %s\n",
+ ++counter, zExp ? zExp : zSql);
+ sqlite3_free(zExp);
+ }
break;
}
}
return 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 */
+ "k TEXT PRIMARY KEY NOT NULL,"
+ "v TEXT DEFAULT NULL"
+ ") WITHOUT ROWID;"
"CREATE TABLE incl("
+ /* ^^^ files currently being included */
"file TEXT PRIMARY KEY NOT NULL,"
"srcFile TEXT DEFAULT NULL,"
"srcLine INTEGER DEFAULT 0"
") WITHOUT ROWID;"
- /* ^^^ files currently being included */
"CREATE TABLE inclpath("
+ /* ^^^ include path */
"seq INTEGER UNIQUE ON CONFLICT IGNORE, "
"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.");
- if( g.sqlTrace.pFile ){
- sqlite3_trace_v2(g.db, SQLITE_TRACE_STMT, cmpp__db_sq3TraceV2, 0);
- }
+ sqlite3_trace_v2(g.db, SQLITE_TRACE_STMT, cmpp__db_sq3TraceV2, 0);
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,
/*
** 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.
+** [zBegin,zEnd), returns g.delim.n if it is at the start of a line and
+** starts with g.delim.z, else returns 0.
*/
static unsigned short cmpp_is_delim(unsigned char const *zBegin,
unsigned char const *zEnd,
assert(zPos>=zBegin);
if(zPos>zBegin &&
('\n'!=*(zPos - 1)
- || ((unsigned)(zEnd - zPos) <= g.nDelim))){
+ || ((unsigned)(zEnd - zPos) <= g.delim.n))){
return 0;
- }else if(0==memcmp(zPos, g.zDelim, g.nDelim)){
- return g.nDelim;
+ }else if(0==memcmp(zPos, g.delim.z, g.delim.n)){
+ return g.delim.n;
}else{
return 0;
}
}
+static char const * cmpp_val_part(char const *zKey,
+ int nKey,
+ char chEq,
+ unsigned * len,
+ char const **zEq){
+ const char *zPos = zKey;
+ if( nKey<0 ) nKey = (int)strlen(zKey);
+ const char * const zEnd = zPos + nKey;
+ for( ; *zPos && zPos<zEnd ; ++zPos) {
+ if( chEq==*zPos ){
+ if( zEq ) *zEq = zPos;
+ ++zPos;
+ *len = (unsigned)(zEnd - zPos);
+ return zPos;
+ }
+ }
+ return 0;
+}
+
+static void cmpp_t_out_expand(CmppTokenizer * const t,
+ unsigned char const * zFrom,
+ unsigned int n){
+ unsigned char const *zLeft = zFrom;
+ unsigned char const * const zEnd = zFrom + n;
+ unsigned char const *z = AT_OFF==g.flags.atPolicy ? zEnd : zLeft;
+ unsigned char const chEol = (unsigned char)'\n';
+ int state = 0 /* 0==looking for opening @
+ ** 1==looking for closing @ */;
+#define tflush \
+ if(zLeft<z){ cmpp_t_out(t, zLeft, (z-zLeft)); } zLeft = z
+ for( ; z<zEnd; ++z ){
+ zLeft = z;
+ for( ;z<zEnd; ++z ){
+ if( chEol==*z ){
+ state = 0;
+ ++z /*ensure that we flush the EOL now*/;
+ tflush;
+ --z/*And make sure that zLeft does the right thing*/;
+ break;
+ }
+ if( g.delim.chAt==*z ){
+ if( 0==state ){
+ tflush;
+ state = 1;
+ }else{ /* process chAt...chAt */
+ char *zVal = 0;
+ unsigned int nVal = 0;
+ assert( 1==state );
+ assert( g.delim.chAt==*zLeft );
+ assert( zLeft<z );
+ if( z==zLeft+1 ){
+ tflush;
+ }else{
+ char const *zKey = (char const*)zLeft+1;
+ int const nKey = (z-zLeft-1);
+ if( db_define_get(zKey, nKey, &zVal, &nVal) ){
+ if( nVal ){
+ cmpp_t_out(t, (unsigned char const*)zVal, nVal);
+ }else{
+ /* Elide it */
+ }
+ zLeft = z+1/*skip closing g.delim.chAt*/;
+ db_free(zVal);
+ }else{
+ assert( !zVal );
+ /* No match. Emit it as-is. */
+ switch( g.flags.atPolicy ){
+ case AT_RETAIN:
+ tflush;
+ break;
+ case AT_ERROR:
+ fatal("Undefined key: %c%.*s%c",
+ g.delim.chAt, nKey, zKey, g.delim.chAt );
+ break;
+ case AT_ELIDE:
+ zLeft = z+1;
+ break;
+ case AT_invalid:
+ case AT_OFF:
+ fatal("Unhandled g.flags.atPolicy #%d!",
+ g.flags.atPolicy);
+ break;
+ }
+ }
+ state = 0;
+ }
+ }/* process chAt...chAt */
+ }/*g.delim.chAt*/
+ }/*per-line loop*/
+ }/*outer loop*/
+ tflush;
+#undef tflush
+ return;
+}
+
/*
** 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
}
if(z>zStart){
/* We passed up content */
- cmpp_t_out(t, zStart, (unsigned)(z - zStart));
+ //cmpp_t_out(t, zStart, (unsigned)(z - zStart));
+ cmpp_t_out_expand(t, zStart, (unsigned)(z - zStart));
}
- assert(isDelim==0 || isDelim==g.nDelim);
+ assert(isDelim==0 || isDelim==g.delim.n);
tok->lineNo = t->lineNo += count_lines(zStart, z);
if(isDelim){
/* Handle backslash-escaped newlines */
assert(TT_Line==tok->ttype);
if((unsigned)sizeof(t->args.lineBuf) < tokLen + 1){
- fatal("Keyword line is unreasonably long: %.*s",
- tokLen, tok->zBegin);
+ fatal("Keyword line #%u is unreasonably long: %.*s",
+ tok->lineNo, tokLen, tok->zBegin);
}else if(!tokLen){
fatal("Line #%u has no keyword after delimiter", tok->lineNo);
}
for( ++zz ; *zz && isspace(*zz); ++zz ){}
if(t->args.pKw->bTokenize){
for( ; *zz; prevChar = *zz, ++zz ){
- /* Split string into word-shaped tokens.
+ /* Split string into word-shaped tokens.
** TODO ?= quoted strings, for the sake of the
** #error keyword. */
if(isspace(*zz)){
if(*zz) t->args.argv[argc++] = zz;
}
tok->ttype = t->args.pKw->ttype;
- if(g.doDebug>1){
+ if(g.flags.doDebug>1){
for(i = 0; i < argc; ++i){
g_debug(0,("line %u arg #%d=%s\n",
tok->lineNo, i,
/* Impl. for #if, #ifnot, #elif, #elifnot. */
static void cmpp_kwd_if(CmppKeyword const * pKw, CmppTokenizer *t){
- int buul;
+ int buul = 0;
CmppParseState tmpState = TS_Start;
if(t->args.argc!=2){
cmpp_kwd__misuse(pKw, t, "Expecting exactly 1 argument");
cmpp_kwd__misuse(pKw, t, "Unexpected keyword token type");
break;
}
- buul = db_define_has((char const *)t->args.argv[1]);
+ char const * const zKey = (char const *)t->args.argv[1];
+ char const * zEq = 0;
+ unsigned nValPart = 0;
+ char const * zValPart = cmpp_val_part(zKey, -1, '=', &nValPart, &zEq);
+ if( zValPart ){
+ unsigned nVal = 0;
+ char * zVal = 0;
+ buul = db_define_get(zKey, (zEq-zKey), &zVal, &nVal);
+ if( nVal ){
+ /* FIXME? do this with a query */
+ g_debug(1,("if get-define %.*s=%.*s zValPart=%s\n",
+ (zEq-zKey), zKey,
+ nVal, zVal, zValPart));
+ buul = 0==sqlite3_strglob(zValPart,zVal);
+ }
+ db_free(zVal);
+ }else{
+ buul = db_define_get_bool(zKey, -1);
+ }
if(TT_IfNot==pKw->ttype || TT_ElifNot==pKw->ttype) buul = !buul;
if(buul){
CT_pstate(t) = tmpState = TS_IfPassed;
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);
+ g.delim.z, t->zName, lvlToken.lineNo);
}
}
return;
g_stderr("\t%.*s\n", n, z);
}
db_finalize(q);
+ }else if(M("@")){
+ g.flags.atPolicy = AT_RETAIN;
+ }else if(M("no-@")){
+ g.flags.atPolicy = AT_OFF;
+ }else if(M("chomp-F")){
+ g.flags.chompF = 1;
+ }else if(M("no-chomp-F")){
+ g.flags.chompF = 0;
}else{
- cmpp_kwd__misuse(pKw, t, "Unknown pragma");
+ cmpp_kwd__misuse(pKw, t, "Unknown pragma: %s", zArg);
}
#undef M
}
}else{
g_stderr("%s:%u: (no %.*s%s argument)\n",
t->zName, t->token.lineNo,
- g.nDelim, g.zDelim, pKw->zName);
+ g.delim.n, g.delim.z, pKw->zName);
}
}
}
void cmpp_process_file(const char * zName){
FileWrapper fw = FileWrapper_empty;
+ CmppTokenizer const * const oldTok = g.tok;
CmppTokenizer ct = CmppTokenizer_empty;
+ g.tok = &ct;
FileWrapper_open(&fw, zName, "r");
FileWrapper_slurp(&fw);
g_debug(1,("Read %u byte(s) from [%s]\n", fw.nContent, fw.zName));
fatal("Input ended inside an unterminated nested construct"
"opened at [%s] line %u", zName, lv->token.lineNo);
}
+ g.tok = oldTok;
+}
+
+
+void fatalv(char const *zFmt, va_list va){
+ fflush(stdout);
+ fputc('\n', stderr);
+ if( g.tok ){
+ fprintf(stderr,"%s: @%s:%d: ",
+ g.zArgv0,
+ (g.tok->zName && 0==strcmp("-",g.tok->zName))
+ ? "<stdin>"
+ : g.tok->zName,
+ g.tok->lineNo);
+ }
+ if(zFmt && *zFmt){
+ vfprintf(stderr, zFmt, va);
+ }
+ fputc('\n', stderr);
+ fflush(stderr);
+ exit(1);
+}
+
+void fatal(char const *zFmt, ...){
+ va_list va;
+ va_start(va, zFmt);
+ fatalv(zFmt, va);
+ va_end(va);
}
#undef CT_level
fprintf(fOut,
"Flags and filenames may be in any order and "
"they are processed in that order.\n"
- "\nFlags:\n\n");
-
-#define arg(F,D) fprintf(fOut," %s\n %s\n",F, D)
- arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)). "
- " Because arguments are processed in order, this should "
- " normally be given before -f.");
+ "\nFlags:\n");
+#define GAP " "
+#define arg(F,D) fprintf(fOut,"\n %s\n" GAP "%s\n",F, D)
+ arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n"
+ GAP "Because arguments are processed in order, this should\n"
+ GAP "normally be given before -f.");
arg("-f|--file FILE","Read input from FILE (default=- (stdin)).\n"
- " All non-flag arguments are assumed to be the input files.");
- arg("-DXYZ","Define XYZ to true");
- arg("-UXYZ","Undefine all matching glob XYZ");
- arg("-IXYZ","Add dir XYZ to include path");
+ " All non-flag arguments are assumed to be the input files.");
+ arg("-DXYZ[=value]","Define XYZ to the given value (default=1).");
+ arg("-UXYZ","Undefine all defines matching glob XYZ.");
+ arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path.");
+ arg("-FXYZ=filename",
+ "Define XYZ to the raw contents of the given file.\n"
+ GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include\n"
+ GAP "Maybe it should be. Or maybe we need a new flag for that.");
arg("-d|--delimiter VALUE", "Set keyword delimiter to VALUE "
- "(default=" CMPP_DEFAULT_DELIM ")");
- arg("--sql-trace", "Send a trace of all SQL to stderr");
- arg("--sql-trace-x", "Like --sql-trace but expand the SQL");
+ "(default=" CMPP_DEFAULT_DELIM ").");
+ arg("--@-policy retain|elide|error|off",
+ "Specifies how to handle @tokens@ (default=off).\n"
+ GAP "off = do not look for @tokens@\n"
+ GAP "retain = parse @tokens@ and retain any undefined ones\n"
+ GAP "elide = parse @tokens@ and elide any undefined ones\n"
+ GAP "error = parse @tokens@ and error for any undefined ones"
+ );
+ arg("-@", "Equivalent to --@-policy=error.");
+ arg("-no-@", "Equivalent to --@-policy=off (the default).");
+ arg("--sql-trace", "Send a trace of all SQL to stderr.");
+ arg("--sql-trace-x",
+ "Like --sql-trace but expand all bound values in the SQL.");
+ arg("--no-sql-trace", "Disable SQL tracing (default).");
+ arg("--chomp-F", "One trailing newline is trimmed from files "
+ "read via -FXYZ=filename.");
+ arg("--no-chomp-F", "Disable --chomp-F (default).");
#undef arg
- fputs("",fOut);
+#undef GAP
+ fputs("\n",fOut);
+}
+
+static void get_flag_val(int argc, char const * const * argv, int * ndx,
+ char const **zVal){
+ char const * zEq = strchr(argv[*ndx], '=');
+ if( zEq ){
+ *zVal = zEq+1;
+ return;
+ }
+ if(*ndx+1>=argc){
+ fatal("Missing value for flag '%s'", argv[*ndx]);
+ }
+ *zVal = argv[++*ndx];
+}
+
+static int arg_is_flag( char const *zFlag, char const *zArg,
+ char const **zValIfEqX ){
+ *zValIfEqX = 0;
+ if( 0==strcmp(zFlag, zArg) ) return 1;
+ char const * z = strchr(zArg,'=');
+ if( z && z>zArg ){
+ /* compare the part before the '=' */
+ if( 0==strncmp(zFlag, zArg, z-zArg) ){
+ *zValIfEqX = z+1;
+ return 1;
+ }
+ }
+ return 0;
}
int main(int argc, char const * const * argv){
int rc = 0;
int inclCount = 0;
int nFile = 0;
-#define M(X) (0==strcmp(X,zArg))
+ int ndxTrace = 0;
+ int expandMode = 0;
+ char const * zVal = 0;
+#define ARGVAL if( !zVal ) get_flag_val(argc, argv, &i, &zVal)
+#define M(X) arg_is_flag(X, zArg, &zVal)
#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]
+#define NOVAL if( zVal ) fatal("Unexpected value for %s", zArg)
g.zArgv0 = argv[0];
#define DOIT if(doIt)
*/
DOIT{
atexit(cmpp_atexit);
+ if( 1==ndxTrace ){
+ /* Ensure that we start with tracing in the early stage if
+ --sql-trace is the first arg, in order to log schema
+ setup. */
+ g.sqlTrace.pFile = stderr;
+ g.sqlTrace.expandSql = expandMode;
+ }
cmpp_initdb();
}
for(int i = 1; i < argc; ++i){
+ int negate = 0;
char const * zArg = argv[i];
+ //g_stderr("i=%d zArg=%s\n", i, zArg);
+ zVal = 0;
while('-'==*zArg) ++zArg;
if(zArg==argv[i]/*not a flag*/){
+ zVal = zArg;
goto do_infile;
- }ISFLAG2("?","help"){
+ }
+ if( 0==strncmp(zArg,"no-",3) ){
+ zArg += 3;
+ negate = 1;
+ }
+ ISFLAG2("?","help"){
+ NOVAL;
usage(0);
goto end;
}else if('D'==*zArg){
DOIT {
db_define_add(zArg);
}
+ }else if('F'==*zArg){
+ ++zArg;
+ if(!*zArg) fatal("Missing key for -F");
+ DOIT {
+ db_define_add_file(zArg);
+ }
}else if('U'==*zArg){
++zArg;
if(!*zArg) fatal("Missing key for -U");
ISFLAG2("o","outfile"){
ARGVAL;
DOIT {
- FileWrapper_open(&g.out, zArg, "w");
+ FileWrapper_open(&g.out, zVal, "w");
}
}
ISFLAG2("f","file"){
db_include_dir_add(".");
++inclCount;
}
- cmpp_process_file(zArg);
+ cmpp_process_file(zVal);
}
}
- ISFLAG2("d","delimiter"){
- ARGVAL;
- if( !doIt ){
- g.zDelim = zArg;
- g.nDelim = (unsigned short)strlen(zArg);
- if(!g.nDelim) fatal("Keyword delimiter may not be empty.");
+ ISFLAG("@"){
+ NOVAL;
+ DOIT {
+ assert( AT_DEFAULT!=AT_OFF );
+ g.flags.atPolicy = negate ? AT_OFF : AT_DEFAULT;
}
}
- ISFLAG("debug"){
+ ISFLAG("@-policy"){
+ AtPolicy aup;
+ ARGVAL;
+ aup = AtPolicy_fromStr(zVal);
+ if( AT_invalid==aup ){
+ fatal("Invalid --@-policy. Try one of retain|elide|error|off.");
+ }
DOIT {
- ++g.doDebug;
+ g.flags.atPolicy = aup;
}
}
+ ISFLAG("debug"){
+ NOVAL;
+ g.flags.doDebug += negate ? -1 : 1;
+ }
ISFLAG("sql-trace"){
- if( !doIt ){
- /* Needs to be set before the start of the second pass, when
- the db is inited. */
- g.sqlTrace.pFile = stderr;
- g.sqlTrace.expandSql = 0;
+ NOVAL;
+ /* Needs to be set before the start of the second pass, when
+ the db is inited. */
+ g.sqlTrace.expandSql = 0;
+ DOIT {
+ g.sqlTrace.pFile = negate ? (FILE*)0 : stderr;
+ }else if( !ndxTrace && !negate ){
+ ndxTrace = i;
+ expandMode = 0;
}
}
ISFLAG("sql-trace-x"){
+ NOVAL;
+ g.sqlTrace.expandSql = 1;
+ DOIT {
+ g.sqlTrace.pFile = negate ? (FILE*)0 : stderr;
+ }else if( !ndxTrace && !negate ){
+ ndxTrace = i;
+ expandMode = 1;
+ }
+ }
+ ISFLAG("chomp-F"){
+ NOVAL;
+ DOIT g.flags.chompF = !negate;
+ }
+ ISFLAG2("d","delimiter"){
+ ARGVAL;
if( !doIt ){
- g.sqlTrace.pFile = stderr;
- g.sqlTrace.expandSql = 1;
+ g.delim.z = zVal;
+ g.delim.n = (unsigned short)strlen(zVal);
+ if(!g.delim.n) fatal("Keyword delimiter may not be empty.");
}
}
else{