+/**
+ * undef is also simple
+ */
+static bool ftepp_undef(ftepp_t *ftepp)
+{
+ (void)ftepp_next(ftepp);
+ if (!ftepp_skipspace(ftepp))
+ return false;
+
+ if (ftepp->output_on) {
+ switch (ftepp->token) {
+ case TOKEN_IDENT:
+ case TOKEN_TYPENAME:
+ case TOKEN_KEYWORD:
+ ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+ break;
+ default:
+ ftepp_error(ftepp, "expected macro name");
+ return false;
+ }
+ }
+
+ (void)ftepp_next(ftepp);
+ if (!ftepp_skipspace(ftepp))
+ return false;
+ /* relaxing this condition
+ if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+ ftepp_error(ftepp, "stray tokens after #ifdef");
+ return false;
+ }
+ */
+ return true;
+}
+
+/* Special unescape-string function which skips a leading quote
+ * and stops at a quote, not just at \0
+ */
+static void unescape(const char *str, char *out) {
+ ++str;
+ while (*str && *str != '"') {
+ if (*str == '\\') {
+ ++str;
+ switch (*str) {
+ case '\\': *out++ = *str; break;
+ case '"': *out++ = *str; break;
+ case 'a': *out++ = '\a'; break;
+ case 'b': *out++ = '\b'; break;
+ case 'r': *out++ = '\r'; break;
+ case 'n': *out++ = '\n'; break;
+ case 't': *out++ = '\t'; break;
+ case 'f': *out++ = '\f'; break;
+ case 'v': *out++ = '\v'; break;
+ default:
+ *out++ = '\\';
+ *out++ = *str;
+ break;
+ }
+ ++str;
+ continue;
+ }
+
+ *out++ = *str++;
+ }
+ *out = 0;
+}
+
+static char *ftepp_include_find_path(const char *file, const char *pathfile)
+{
+ fs_file_t *fp;
+ char *filename = NULL;
+ const char *last_slash;
+ size_t len;
+
+ if (!pathfile)
+ return NULL;
+
+ last_slash = strrchr(pathfile, '/');
+
+ if (last_slash) {
+ len = last_slash - pathfile;
+ memcpy(vec_add(filename, len), pathfile, len);
+ vec_push(filename, '/');
+ }
+
+ len = strlen(file);
+ memcpy(vec_add(filename, len+1), file, len);
+ vec_last(filename) = 0;
+
+ fp = fs_file_open(filename, "rb");
+ if (fp) {
+ fs_file_close(fp);
+ return filename;
+ }
+ vec_free(filename);
+ return NULL;
+}
+
+static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
+{
+ char *filename = NULL;
+
+ filename = ftepp_include_find_path(file, ftepp->includename);
+ if (!filename)
+ filename = ftepp_include_find_path(file, ftepp->itemname);
+ return filename;
+}
+
+static bool ftepp_directive_warning(ftepp_t *ftepp) {
+ char *message = NULL;
+
+ if (!ftepp_skipspace(ftepp))
+ return false;
+
+ /* handle the odd non string constant case so it works like C */
+ if (ftepp->token != TOKEN_STRINGCONST) {
+ bool store = false;
+ vec_append(message, 8, "#warning");
+ ftepp_next(ftepp);
+ while (ftepp->token != TOKEN_EOL) {
+ vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+ ftepp_next(ftepp);
+ }
+ vec_push(message, '\0');
+ if (ftepp->output_on)
+ store = ftepp_warn(ftepp, WARN_CPP, message);
+ else
+ store = false;
+ vec_free(message);
+ return store;
+ }
+
+ if (!ftepp->output_on)
+ return false;
+
+ unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+ return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
+}
+
+static void ftepp_directive_error(ftepp_t *ftepp) {
+ char *message = NULL;
+
+ if (!ftepp_skipspace(ftepp))
+ return;
+
+ /* handle the odd non string constant case so it works like C */
+ if (ftepp->token != TOKEN_STRINGCONST) {
+ vec_append(message, 6, "#error");
+ ftepp_next(ftepp);
+ while (ftepp->token != TOKEN_EOL) {
+ vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+ ftepp_next(ftepp);
+ }
+ vec_push(message, '\0');
+ if (ftepp->output_on)
+ ftepp_error(ftepp, message);
+ vec_free(message);
+ return;
+ }
+
+ if (!ftepp->output_on)
+ return;
+
+ unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+ ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
+}
+
+static void ftepp_directive_message(ftepp_t *ftepp) {
+ char *message = NULL;
+
+ if (!ftepp_skipspace(ftepp))
+ return;
+
+ /* handle the odd non string constant case so it works like C */
+ if (ftepp->token != TOKEN_STRINGCONST) {
+ vec_append(message, 8, "#message");
+ ftepp_next(ftepp);
+ while (ftepp->token != TOKEN_EOL) {
+ vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+ ftepp_next(ftepp);
+ }
+ vec_push(message, '\0');
+ if (ftepp->output_on)
+ con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message);
+ vec_free(message);
+ return;
+ }
+
+ if (!ftepp->output_on)
+ return;
+
+ unescape (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+ con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", ftepp_tokval(ftepp));
+}
+
+/**
+ * Include a file.
+ * FIXME: do we need/want a -I option?
+ * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
+ */
+static bool ftepp_include(ftepp_t *ftepp)
+{
+ lex_file *old_lexer = ftepp->lex;
+ lex_file *inlex;
+ lex_ctx_t ctx;
+ char lineno[128];
+ char *filename;
+ char *old_includename;
+
+ (void)ftepp_next(ftepp);
+ if (!ftepp_skipspace(ftepp))
+ return false;
+
+ if (ftepp->token != TOKEN_STRINGCONST) {
+ ftepp_error(ftepp, "expected filename to include");
+ return false;
+ }
+
+ if (!ftepp->output_on) {
+ ftepp_next(ftepp);
+ return true;
+ }
+
+ ctx = ftepp_ctx(ftepp);
+
+ unescape(ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+
+ ftepp_out(ftepp, "\n#pragma file(", false);
+ ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+ ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
+
+ filename = ftepp_include_find(ftepp, ftepp_tokval(ftepp));
+ if (!filename) {
+ ftepp_error(ftepp, "failed to open include file `%s`", ftepp_tokval(ftepp));
+ return false;
+ }
+ inlex = lex_open(filename);
+ if (!inlex) {
+ ftepp_error(ftepp, "open failed on include file `%s`", filename);
+ vec_free(filename);
+ return false;
+ }
+ ftepp->lex = inlex;
+ old_includename = ftepp->includename;
+ ftepp->includename = filename;
+ if (!ftepp_preprocess(ftepp)) {
+ vec_free(ftepp->includename);
+ ftepp->includename = old_includename;
+ lex_close(ftepp->lex);
+ ftepp->lex = old_lexer;
+ return false;
+ }
+ vec_free(ftepp->includename);
+ ftepp->includename = old_includename;
+ lex_close(ftepp->lex);
+ ftepp->lex = old_lexer;
+
+ ftepp_out(ftepp, "\n#pragma file(", false);
+ ftepp_out(ftepp, ctx.file, false);
+ util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
+ ftepp_out(ftepp, lineno, false);
+
+ /* skip the line */
+ (void)ftepp_next(ftepp);
+ if (!ftepp_skipspace(ftepp))
+ return false;
+ if (ftepp->token != TOKEN_EOL) {
+ ftepp_error(ftepp, "stray tokens after #include");
+ return false;
+ }
+ (void)ftepp_next(ftepp);
+
+ return true;
+}
+