]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Merge branch 'master' into ftepp
authorWolfgang (Blub) Bumiller <blub@speed.at>
Sun, 18 Nov 2012 12:06:29 +0000 (13:06 +0100)
committerWolfgang (Blub) Bumiller <blub@speed.at>
Sun, 18 Nov 2012 12:06:29 +0000 (13:06 +0100)
Makefile
ftepp.c [new file with mode: 0644]
gmqcc.h
ir.c
lexer.c
lexer.h
main.c
opts.def
parser.c

index 5486dd13eaf68e5da1fca94b230d0fe904b8bc65..ad8c9411c7467b647c7e3ad2cb14de64bf07e0ff 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -28,7 +28,8 @@ OBJ     = \
           code.o      \
           ast.o       \
           ir.o        \
-          con.o
+          con.o       \
+          ftepp.o
 OBJ_C = main.o lexer.o parser.o
 OBJ_X = exec-standalone.o util.o con.o
 
diff --git a/ftepp.c b/ftepp.c
new file mode 100644 (file)
index 0000000..4318633
--- /dev/null
+++ b/ftepp.c
@@ -0,0 +1,1072 @@
+/*
+ * Copyright (C) 2012
+ *     Wolfgang Bumiller
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "gmqcc.h"
+#include "lexer.h"
+
+typedef struct {
+    bool on;
+    bool was_on;
+    bool had_else;
+} ppcondition;
+
+typedef struct {
+    int   token;
+    char *value;
+    /* a copy from the lexer */
+    union {
+        vector v;
+        int    i;
+        double f;
+        int    t; /* type */
+    } constval;
+} pptoken;
+
+typedef struct {
+    lex_ctx ctx;
+
+    char   *name;
+    char  **params;
+    /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
+    bool    has_params;
+
+    pptoken **output;
+} ppmacro;
+
+typedef struct {
+    lex_file    *lex;
+    int          token;
+    bool         newline;
+    unsigned int errors;
+
+    bool         output_on;
+    ppcondition *conditions;
+    ppmacro    **macros;
+
+    bool         to_string;
+    char        *output;
+    FILE        *output_file;
+} ftepp_t;
+
+#define ftepp_tokval(f) ((f)->lex->tok.value)
+#define ftepp_ctx(f)    ((f)->lex->tok.ctx)
+
+static void ftepp_errorat(ftepp_t *ftepp, lex_ctx ctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    ftepp->errors++;
+
+    va_start(ap, fmt);
+    con_vprintmsg(LVL_ERROR, ctx.file, ctx.line, "error", fmt, ap);
+    va_end(ap);
+}
+
+static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
+{
+    va_list ap;
+
+    ftepp->errors++;
+
+    va_start(ap, fmt);
+    con_vprintmsg(LVL_ERROR, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
+    va_end(ap);
+}
+
+static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
+{
+    va_list ap;
+    int lvl = LVL_WARNING;
+
+    if (!OPTS_WARN(warntype))
+        return false;
+
+    if (opts_werror) {
+           lvl = LVL_ERROR;
+        ftepp->errors++;
+    }
+
+    va_start(ap, fmt);
+    con_vprintmsg(lvl, ftepp->lex->tok.ctx.file, ftepp->lex->tok.ctx.line, "error", fmt, ap);
+    va_end(ap);
+    return opts_werror;
+}
+
+static pptoken *pptoken_make(ftepp_t *ftepp)
+{
+    pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
+    token->token = ftepp->token;
+#if 0
+    if (token->token == TOKEN_WHITE)
+        token->value = util_strdup(" ");
+    else
+#else
+        token->value = util_strdup(ftepp_tokval(ftepp));
+#endif
+    memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
+    return token;
+}
+
+static void pptoken_delete(pptoken *self)
+{
+    mem_d(self->value);
+    mem_d(self);
+}
+
+static ppmacro *ppmacro_new(lex_ctx ctx, const char *name)
+{
+    ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
+    memset(macro, 0, sizeof(*macro));
+    macro->name = util_strdup(name);
+    return macro;
+}
+
+static void ppmacro_delete(ppmacro *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->params); ++i)
+        mem_d(self->params[i]);
+    vec_free(self->params);
+    for (i = 0; i < vec_size(self->output); ++i)
+        pptoken_delete(self->output[i]);
+    vec_free(self->output);
+    mem_d(self->name);
+    mem_d(self);
+}
+
+static ftepp_t* ftepp_new()
+{
+    ftepp_t *ftepp;
+
+    ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
+    memset(ftepp, 0, sizeof(*ftepp));
+
+    ftepp->output_on = true;
+
+    return ftepp;
+}
+
+static void ftepp_delete(ftepp_t *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->macros); ++i)
+        ppmacro_delete(self->macros[i]);
+    vec_free(self->macros);
+    vec_free(self->conditions);
+    if (self->lex)
+        lex_close(self->lex);
+    if (self->output_file)
+        fclose(self->output_file);
+    mem_d(self);
+}
+
+static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
+{
+    if (ignore_cond || ftepp->output_on)
+    {
+        size_t len;
+        char  *data;
+        if (!ftepp->to_string) {
+            fprintf((ftepp->output_file ? ftepp->output_file : stdout), "%s", str);
+            return;
+        }
+        len = strlen(str);
+        data = vec_add(ftepp->output, len);
+        memcpy(data, str, len);
+    }
+}
+
+static void ftepp_update_output_condition(ftepp_t *ftepp)
+{
+    size_t i;
+    ftepp->output_on = true;
+    for (i = 0; i < vec_size(ftepp->conditions); ++i)
+        ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
+}
+
+static ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
+{
+    size_t i;
+    for (i = 0; i < vec_size(ftepp->macros); ++i) {
+        if (!strcmp(name, ftepp->macros[i]->name))
+            return ftepp->macros[i];
+    }
+    return NULL;
+}
+
+static void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
+{
+    size_t i;
+    for (i = 0; i < vec_size(ftepp->macros); ++i) {
+        if (!strcmp(name, ftepp->macros[i]->name)) {
+            vec_remove(ftepp->macros, i, 1);
+            return;
+        }
+    }
+}
+
+static inline int ftepp_next(ftepp_t *ftepp)
+{
+    return (ftepp->token = lex_do(ftepp->lex));
+}
+
+/* Important: this does not skip newlines! */
+static bool ftepp_skipspace(ftepp_t *ftepp)
+{
+    if (ftepp->token != TOKEN_WHITE)
+        return true;
+    while (ftepp_next(ftepp) == TOKEN_WHITE) {}
+    if (ftepp->token >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected end of preprocessor directive");
+        return false;
+    }
+    return true;
+}
+
+/* this one skips EOLs as well */
+static bool ftepp_skipallwhite(ftepp_t *ftepp)
+{
+    if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
+        return true;
+    do {
+        ftepp_next(ftepp);
+    } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
+    if (ftepp->token >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected end of preprocessor directive");
+        return false;
+    }
+    return true;
+}
+
+/**
+ * The huge macro parsing code...
+ */
+static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
+{
+    do {
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+        if (ftepp->token == ')')
+            break;
+        switch (ftepp->token) {
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                break;
+            default:
+                ftepp_error(ftepp, "unexpected token in parameter list");
+                return false;
+        }
+        vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+    } while (ftepp->token == ',');
+    if (ftepp->token != ')') {
+        ftepp_error(ftepp, "expected closing paren after macro parameter list");
+        return false;
+    }
+    ftepp_next(ftepp);
+    /* skipspace happens in ftepp_define */
+    return true;
+}
+
+static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
+{
+    pptoken *ptok;
+    while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
+        ptok = pptoken_make(ftepp);
+        vec_push(macro->output, ptok);
+        ftepp_next(ftepp);
+    }
+    /* recursive expansion can cause EOFs here */
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
+        return false;
+    }
+    return true;
+}
+
+static bool ftepp_define(ftepp_t *ftepp)
+{
+    ppmacro *macro;
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+            if (macro && ftepp->output_on) {
+                if (ftepp_warn(ftepp, WARN_PREPROCESSOR, "redefining `%s`", ftepp_tokval(ftepp)))
+                    return false;
+                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+            }
+            macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
+            break;
+        default:
+            ftepp_error(ftepp, "expected macro name");
+            return false;
+    }
+
+    (void)ftepp_next(ftepp);
+
+    if (ftepp->token == '(') {
+        macro->has_params = true;
+        if (!ftepp_define_params(ftepp, macro))
+            return false;
+    }
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    if (!ftepp_define_body(ftepp, macro))
+        return false;
+
+    if (ftepp->output_on)
+        vec_push(ftepp->macros, macro);
+    else {
+        ppmacro_delete(macro);
+    }
+    return true;
+}
+
+/**
+ * When a macro is used we have to handle parameters as well
+ * as special-concatenation via ## or stringification via #
+ *
+ * Note: parenthesis can nest, so FOO((a),b) is valid, but only
+ * this kind of parens. Curly braces or [] don't count towards the
+ * paren-level.
+ */
+typedef struct {
+    pptoken **tokens;
+} macroparam;
+
+static void macroparam_clean(macroparam *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->tokens); ++i)
+        pptoken_delete(self->tokens[i]);
+    vec_free(self->tokens);
+}
+
+/* need to leave the last token up */
+static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
+{
+    macroparam *params = NULL;
+    pptoken    *ptok;
+    macroparam  mp;
+    size_t      parens = 0;
+    size_t      i;
+
+    if (!ftepp_skipallwhite(ftepp))
+        return false;
+    while (ftepp->token != ')') {
+        mp.tokens = NULL;
+        if (!ftepp_skipallwhite(ftepp))
+            return false;
+        while (parens || ftepp->token != ',') {
+            if (ftepp->token == '(')
+                ++parens;
+            else if (ftepp->token == ')') {
+                if (!parens)
+                    break;
+                --parens;
+            }
+            ptok = pptoken_make(ftepp);
+            vec_push(mp.tokens, ptok);
+            if (ftepp_next(ftepp) >= TOKEN_EOF) {
+                ftepp_error(ftepp, "unexpected EOF in macro call");
+                goto on_error;
+            }
+        }
+        vec_push(params, mp);
+        mp.tokens = NULL;
+        if (ftepp->token == ')')
+            break;
+        if (ftepp->token != ',') {
+            ftepp_error(ftepp, "expected closing paren or comma in macro call");
+            goto on_error;
+        }
+        if (ftepp_next(ftepp) >= TOKEN_EOF) {
+            ftepp_error(ftepp, "unexpected EOF in macro call");
+            goto on_error;
+        }
+    }
+    /* need to leave that up
+    if (ftepp_next(ftepp) >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected EOF in macro call");
+        goto on_error;
+    }
+    */
+    *out_params = params;
+    return true;
+
+on_error:
+    if (mp.tokens)
+        macroparam_clean(&mp);
+    for (i = 0; i < vec_size(params); ++i)
+        macroparam_clean(&params[i]);
+    vec_free(params);
+    return false;
+}
+
+static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
+{
+    size_t i;
+    for (i = 0; i < vec_size(macro->params); ++i) {
+        if (!strcmp(macro->params[i], name)) {
+            *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool ftepp_preprocess(ftepp_t *ftepp);
+static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params)
+{
+    char     *old_string = ftepp->output;
+    bool      old_string_flag = ftepp->to_string;
+    lex_file *old_lexer = ftepp->lex;
+    bool retval = true;
+
+    size_t    o, pi, pv;
+    lex_file *inlex;
+
+    /* really ... */
+    if (!vec_size(macro->output))
+        return true;
+
+    ftepp->output    = NULL;
+    ftepp->to_string = true;
+    for (o = 0; o < vec_size(macro->output); ++o) {
+        pptoken *out = macro->output[o];
+        switch (out->token) {
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                if (!macro_params_find(macro, out->value, &pi)) {
+                    ftepp_out(ftepp, out->value, false);
+                    break;
+                } else {
+                    for (pv = 0; pv < vec_size(params[pi].tokens); ++pv) {
+                        out = params[pi].tokens[pv];
+                        if (out->token == TOKEN_EOL)
+                            ftepp_out(ftepp, "\n", false);
+                        else
+                            ftepp_out(ftepp, out->value, false);
+                    }
+                }
+                break;
+            case '#':
+                if (o + 1 < vec_size(macro->output) && macro->output[o+1]->token == '#') {
+                    /* raw concatenation */
+                    ++o;
+                    break;
+                }
+                ftepp_out(ftepp, "#", false);
+                break;
+            case TOKEN_EOL:
+                ftepp_out(ftepp, "\n", false);
+                break;
+            default:
+                ftepp_out(ftepp, out->value, false);
+                break;
+        }
+    }
+    vec_push(ftepp->output, 0);
+    /* Now run the preprocessor recursively on this string buffer */
+    /*
+    printf("__________\n%s\n=========\n", ftepp->output);
+    */
+    inlex = lex_open_string(ftepp->output, vec_size(ftepp->output)-1, ftepp->lex->name);
+    if (!inlex) {
+        ftepp_error(ftepp, "internal error: failed to instantiate lexer");
+        retval = false;
+        goto cleanup;
+    }
+    ftepp->output    = old_string;
+    ftepp->to_string = old_string_flag;
+    ftepp->lex = inlex;
+    if (!ftepp_preprocess(ftepp)) {
+        lex_close(ftepp->lex);
+        retval = false;
+        goto cleanup;
+    }
+
+cleanup:
+    ftepp->lex       = old_lexer;
+    ftepp->output    = old_string;
+    ftepp->to_string = old_string_flag;
+    return retval;
+}
+
+static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
+{
+    size_t     o;
+    macroparam *params = NULL;
+    bool        retval = true;
+
+    if (!macro->has_params) {
+        if (!ftepp_macro_expand(ftepp, macro, NULL))
+            return false;
+        ftepp_next(ftepp);
+        return true;
+    }
+    ftepp_next(ftepp);
+
+    if (!ftepp_skipallwhite(ftepp))
+        return false;
+
+    if (ftepp->token != '(') {
+        ftepp_error(ftepp, "expected macro parameters in parenthesis");
+        return false;
+    }
+
+    ftepp_next(ftepp);
+    if (!ftepp_macro_call_params(ftepp, &params))
+        return false;
+
+    if (vec_size(params) != vec_size(macro->params)) {
+        ftepp_error(ftepp, "macro %s expects %u paramteters, %u provided", macro->name,
+                    (unsigned int)vec_size(macro->params),
+                    (unsigned int)vec_size(params));
+        retval = false;
+        goto cleanup;
+    }
+
+    if (!ftepp_macro_expand(ftepp, macro, params))
+        retval = false;
+    ftepp_next(ftepp);
+
+cleanup:
+    for (o = 0; o < vec_size(params); ++o)
+        macroparam_clean(&params[o]);
+    vec_free(params);
+    return retval;
+}
+
+/**
+ * #if - the FTEQCC way:
+ *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
+ *    <numbers>    => True if the number is not 0
+ *    !<factor>    => True if the factor yields false
+ *    !!<factor>   => ERROR on 2 or more unary nots
+ *    <macro>      => becomes the macro's FIRST token regardless of parameters
+ *    <e> && <e>   => True if both expressions are true
+ *    <e> || <e>   => True if either expression is true
+ *    <string>     => False
+ *    <ident>      => False (remember for macros the <macro> rule applies instead)
+ * Unary + and - are weird and wrong in fteqcc so we don't allow them
+ * parenthesis in expressions are allowed
+ * parameter lists on macros are errors
+ * No mathematical calculations are executed
+ */
+static bool ftepp_if_expr(ftepp_t *ftepp, bool *out)
+{
+    ppmacro *macro;
+    bool     wasnot = false;
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    while (ftepp->token == '!') {
+        wasnot = true;
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+    }
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            if (!strcmp(ftepp_tokval(ftepp), "defined")) {
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != '(') {
+                    ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
+                    return false;
+                }
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != TOKEN_IDENT &&
+                    ftepp->token != TOKEN_TYPENAME &&
+                    ftepp->token != TOKEN_KEYWORD)
+                {
+                    ftepp_error(ftepp, "defined() used on an unexpected token type");
+                    return false;
+                }
+                macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+                *out = !!macro;
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != ')') {
+                    ftepp_error(ftepp, "expected closing paren");
+                    return false;
+                }
+                break;
+            }
+
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+            if (!macro || !vec_size(macro->output)) {
+                *out = false;
+            } else {
+                /* This does not expand recursively! */
+                switch (macro->output[0]->token) {
+                    case TOKEN_INTCONST:
+                        *out = !!(macro->output[0]->constval.f);
+                        break;
+                    case TOKEN_FLOATCONST:
+                        *out = !!(macro->output[0]->constval.f);
+                        break;
+                    default:
+                        *out = false;
+                        break;
+                }
+            }
+            break;
+        case TOKEN_STRINGCONST:
+            *out = false;
+            break;
+        case TOKEN_INTCONST:
+            *out = !!(ftepp->lex->tok.constval.i);
+            break;
+        case TOKEN_FLOATCONST:
+            *out = !!(ftepp->lex->tok.constval.f);
+            break;
+
+        case '(':
+            ftepp_next(ftepp);
+            if (!ftepp_if_expr(ftepp, out))
+                return false;
+            if (ftepp->token != ')') {
+                ftepp_error(ftepp, "expected closing paren in #if expression");
+                return false;
+            }
+            break;
+
+        default:
+            ftepp_error(ftepp, "junk in #if");
+            return false;
+    }
+    if (wasnot)
+        *out = !*out;
+
+    ftepp->lex->flags.noops = false;
+    ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    ftepp->lex->flags.noops = true;
+
+    if (ftepp->token == ')')
+        return true;
+
+    if (ftepp->token != TOKEN_OPERATOR)
+        return true;
+
+    if (!strcmp(ftepp_tokval(ftepp), "&&") ||
+        !strcmp(ftepp_tokval(ftepp), "||"))
+    {
+        bool next = false;
+        char opc  = ftepp_tokval(ftepp)[0];
+
+        ftepp_next(ftepp);
+        if (!ftepp_if_expr(ftepp, &next))
+            return false;
+
+        if (opc == '&')
+            *out = *out && next;
+        else
+            *out = *out || next;
+        return true;
+    }
+    else {
+        ftepp_error(ftepp, "junk after #if");
+        return false;
+    }
+}
+
+static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
+{
+    bool result = false;
+
+    memset(cond, 0, sizeof(*cond));
+    (void)ftepp_next(ftepp);
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    if (ftepp->token == TOKEN_EOL) {
+        ftepp_error(ftepp, "expected expression for #if-directive");
+        return false;
+    }
+
+    if (!ftepp_if_expr(ftepp, &result))
+        return false;
+
+    cond->on = result;
+    return true;
+}
+
+/**
+ * ifdef is rather simple
+ */
+static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
+{
+    ppmacro *macro;
+    memset(cond, 0, sizeof(*cond));
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            macro = ftepp_macro_find(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;
+    }
+    */
+    cond->on = !!macro;
+    return true;
+}
+
+/**
+ * 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;
+}
+
+/* Basic structure handlers */
+static bool ftepp_else_allowed(ftepp_t *ftepp)
+{
+    if (!vec_size(ftepp->conditions)) {
+        ftepp_error(ftepp, "#else without #if");
+        return false;
+    }
+    if (vec_last(ftepp->conditions).had_else) {
+        ftepp_error(ftepp, "multiple #else for a single #if");
+        return false;
+    }
+    return true;
+}
+
+static bool ftepp_hash(ftepp_t *ftepp)
+{
+    ppcondition cond;
+    ppcondition *pc;
+
+    lex_ctx ctx = ftepp_ctx(ftepp);
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_KEYWORD:
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+            if (!strcmp(ftepp_tokval(ftepp), "define")) {
+                return ftepp_define(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
+                return ftepp_undef(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.on = !cond.on;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.on = !cond.on;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_if(ftepp, &cond))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on  || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "if")) {
+                if (!ftepp_if(ftepp, &cond))
+                    return false;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "else")) {
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on = !pc->was_on;
+                pc->had_else = true;
+                ftepp_next(ftepp);
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
+                if (!vec_size(ftepp->conditions)) {
+                    ftepp_error(ftepp, "#endif without #if");
+                    return false;
+                }
+                vec_pop(ftepp->conditions);
+                ftepp_next(ftepp);
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else {
+                ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
+                return false;
+            }
+            break;
+        default:
+            ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
+            return false;
+        case TOKEN_EOL:
+            ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
+            return false;
+        case TOKEN_EOF:
+            ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
+            return false;
+
+        /* Builtins! Don't forget the builtins! */
+        case TOKEN_INTCONST:
+        case TOKEN_FLOATCONST:
+            ftepp_out(ftepp, "#", false);
+            return true;
+    }
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    return true;
+}
+
+static bool ftepp_preprocess(ftepp_t *ftepp)
+{
+    ppmacro *macro;
+    bool     newline = true;
+
+    ftepp->lex->flags.preprocessing = true;
+    ftepp->lex->flags.mergelines    = false;
+    ftepp->lex->flags.noops         = true;
+
+    ftepp_next(ftepp);
+    do
+    {
+        if (ftepp->token >= TOKEN_EOF)
+            break;
+#if 0
+        ftepp->newline = newline;
+        newline = false;
+#else
+        /* For the sake of FTE compatibility... FU, really */
+        ftepp->newline = newline = true;
+#endif
+
+        switch (ftepp->token) {
+            case TOKEN_KEYWORD:
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+                macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+                if (!macro) {
+                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                    ftepp_next(ftepp);
+                    break;
+                }
+                if (!ftepp_macro_call(ftepp, macro))
+                    ftepp->token = TOKEN_ERROR;
+                break;
+            case '#':
+                if (!ftepp->newline) {
+                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                    ftepp_next(ftepp);
+                    break;
+                }
+                ftepp->lex->flags.mergelines = true;
+                if (ftepp_next(ftepp) >= TOKEN_EOF) {
+                    ftepp_error(ftepp, "error in preprocessor directive");
+                    ftepp->token = TOKEN_ERROR;
+                    break;
+                }
+                if (!ftepp_hash(ftepp))
+                    ftepp->token = TOKEN_ERROR;
+                ftepp->lex->flags.mergelines = false;
+                break;
+            case TOKEN_EOL:
+                newline = true;
+                ftepp_out(ftepp, "\n", true);
+                ftepp_next(ftepp);
+                break;
+            default:
+                ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                ftepp_next(ftepp);
+                break;
+        }
+    } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
+
+    newline = ftepp->token == TOKEN_EOF;
+    return newline;
+}
+
+/* Like in parser.c - files keep the previous state so we have one global
+ * preprocessor. Except here we will want to warn about dangling #ifs.
+ */
+static ftepp_t *ftepp;
+
+static bool ftepp_preprocess_done()
+{
+    bool retval = true;
+    lex_close(ftepp->lex);
+    ftepp->lex = NULL;
+    if (vec_size(ftepp->conditions)) {
+        if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
+            retval = false;
+    }
+    return retval;
+}
+
+bool ftepp_preprocess_file(const char *filename)
+{
+    ftepp->lex = lex_open(filename);
+    if (!ftepp->lex) {
+        con_out("failed to open file \"%s\"\n", filename);
+        return false;
+    }
+    if (!ftepp_preprocess(ftepp)) {
+        ftepp_delete(ftepp);
+        return false;
+    }
+    return ftepp_preprocess_done();
+}
+
+bool ftepp_preprocess_string(const char *name, const char *str)
+{
+    ftepp_t *ftepp = ftepp_new();
+    ftepp->lex = lex_open_string(str, strlen(str), name);
+    if (!ftepp->lex) {
+        con_out("failed to create lexer for string \"%s\"\n", name);
+        return false;
+    }
+    if (!ftepp_preprocess(ftepp)) {
+        ftepp_delete(ftepp);
+        return false;
+    }
+    return ftepp_preprocess_done();
+}
+
+bool ftepp_init(FILE *out)
+{
+    ftepp = ftepp_new();
+    ftepp->output_file = out;
+    return !!ftepp;
+}
+
+void ftepp_finish()
+{
+    if (!ftepp)
+        return;
+    ftepp_delete(ftepp);
+    ftepp = NULL;
+}
diff --git a/gmqcc.h b/gmqcc.h
index b88df48859d212a04204f5ef82f80c226e31060f..c42197011c2617449aba7d2429e5b6184e8f99d0 100644 (file)
--- a/gmqcc.h
+++ b/gmqcc.h
@@ -752,6 +752,14 @@ bool parser_compile_string(const char *name, const char *str);
 bool parser_finish        (const char *output);
 void parser_cleanup       ();
 
+/*===================================================================*/
+/*====================== ftepp.c commandline ========================*/
+/*===================================================================*/
+bool ftepp_init             (FILE *out);
+bool ftepp_preprocess_file  (const char *filename);
+bool ftepp_preprocess_string(const char *name, const char *str);
+void ftepp_finish           ();
+
 /*===================================================================*/
 /*======================= main.c commandline ========================*/
 /*===================================================================*/
diff --git a/ir.c b/ir.c
index 1a99150ffe2c8120f06ad28b0665f3e9e60aa278..f652f6a11767a766112cedb3ae6ccda6588ba8a8 100644 (file)
--- a/ir.c
+++ b/ir.c
@@ -2972,7 +2972,8 @@ bool ir_builder_generate(ir_builder *self, const char *filename)
     stmt.o3.u1 = 0;
     vec_push(code_statements, stmt);
 
-    printf("writing '%s'...\n", filename);
+    if (!opts_pp_only)
+        con_out("writing '%s'...\n", filename);
     return code_write(filename);
 }
 
diff --git a/lexer.c b/lexer.c
index 513499a708bbe35a23c4e68f20cc8f09952b022c..34da7d8a97fa1cf107ceef47cb9be2a3518dc9b7 100644 (file)
--- a/lexer.c
+++ b/lexer.c
@@ -415,13 +415,17 @@ static int lex_skipwhite(lex_file *lex)
 
                 if (lex->flags.preprocessing) {
                     haswhite = true;
+                    /*
                     lex_tokench(lex, '/');
                     lex_tokench(lex, '/');
+                    */
+                    lex_tokench(lex, ' ');
+                    lex_tokench(lex, ' ');
                 }
 
                 while (ch != EOF && ch != '\n') {
                     if (lex->flags.preprocessing)
-                        lex_tokench(lex, ch);
+                        lex_tokench(lex, ' '); /* ch); */
                     ch = lex_getch(lex);
                 }
                 if (lex->flags.preprocessing) {
@@ -436,8 +440,12 @@ static int lex_skipwhite(lex_file *lex)
                 /* multiline comment */
                 if (lex->flags.preprocessing) {
                     haswhite = true;
+                    /*
                     lex_tokench(lex, '/');
                     lex_tokench(lex, '*');
+                    */
+                    lex_tokench(lex, ' ');
+                    lex_tokench(lex, ' ');
                 }
 
                 while (ch != EOF)
@@ -447,14 +455,18 @@ static int lex_skipwhite(lex_file *lex)
                         ch = lex_getch(lex);
                         if (ch == '/') {
                             if (lex->flags.preprocessing) {
+                                /*
                                 lex_tokench(lex, '*');
                                 lex_tokench(lex, '/');
+                                */
+                                lex_tokench(lex, ' ');
+                                lex_tokench(lex, ' ');
                             }
                             break;
                         }
                     }
                     if (lex->flags.preprocessing) {
-                        lex_tokench(lex, ch);
+                        lex_tokench(lex, ' '); /* ch); */
                     }
                 }
                 ch = ' '; /* cause TRUE in the isspace check */
@@ -678,7 +690,21 @@ int lex_do(lex_file *lex)
         return TOKEN_FATAL;
 #endif
 
-    ch = lex_skipwhite(lex);
+    while (true) {
+        ch = lex_skipwhite(lex);
+        if (!lex->flags.mergelines || ch != '\\')
+            break;
+        ch = lex_getch(lex);
+        if (ch != '\n') {
+            lex_ungetch(lex, ch);
+            ch = '\\';
+            break;
+        }
+        /* we reached a linemerge */
+        lex_tokench(lex, '\n');
+        continue;
+    }
+
     lex->sline = lex->line;
     lex->tok.ctx.line = lex->sline;
     lex->tok.ctx.file = lex->name;
@@ -851,6 +877,7 @@ int lex_do(lex_file *lex)
                 return (lex->tok.ttype = TOKEN_OPERATOR);
         case ')':
         case ';':
+        case ':':
         case '{':
         case '}':
         case ']':
diff --git a/lexer.h b/lexer.h
index 76d2b7a711c9ce8f5a2e8ddf704778328c92bb70..b8e2e01f8449c1654ae298ffcff3181f6d58d5cb 100644 (file)
--- a/lexer.h
+++ b/lexer.h
@@ -119,6 +119,7 @@ typedef struct {
            bool noops;
            bool nodigraphs; /* used when lexing string constants */
            bool preprocessing; /* whitespace and EOLs become actual tokens */
+           bool mergelines; /* backslash at the end of a line escapes the newline */
        } flags;
 
     int framevalue;
diff --git a/main.c b/main.c
index 2c14249945cb52744ee13aed23f33a0a0c602ec8..1301e7549b70da667312bf89e2e27f6acb0e5731 100644 (file)
--- a/main.c
+++ b/main.c
@@ -57,28 +57,28 @@ static const char *app_name;
 static int usage() {
     con_out("usage: %s [options] [files...]", app_name);
     con_out("options:\n"
-           "  -h, --help             show this help message\n"
-           "  -debug                 turns on compiler debug messages\n"
-           "  -memchk                turns on compiler memory leak check\n");
+            "  -h, --help             show this help message\n"
+            "  -debug                 turns on compiler debug messages\n"
+            "  -memchk                turns on compiler memory leak check\n");
     con_out("  -o, --output=file      output file, defaults to progs.dat\n"
-           "  -a filename            add an asm file to be assembled\n"
-           "  -s filename            add a progs.src file to be used\n");
+            "  -a filename            add an asm file to be assembled\n"
+            "  -s filename            add a progs.src file to be used\n");
     con_out("  -E                     stop after preprocessing\n");
     con_out("  -f<flag>               enable a flag\n"
-           "  -fno-<flag>            disable a flag\n"
-           "  -std standard          select one of the following standards\n"
-           "       -std=qcc          original QuakeC\n"
-           "       -std=fteqcc       fteqcc QuakeC\n"
-           "       -std=gmqcc        this compiler (default)\n");
+            "  -fno-<flag>            disable a flag\n"
+            "  -std standard          select one of the following standards\n"
+            "       -std=qcc          original QuakeC\n"
+            "       -std=fteqcc       fteqcc QuakeC\n"
+            "       -std=gmqcc        this compiler (default)\n");
     con_out("  -W<warning>            enable a warning\n"
-           "  -Wno-<warning>         disable a warning\n"
-           "  -Wall                  enable all warnings\n"
-           "  -Werror                treat warnings as errors\n");
+            "  -Wno-<warning>         disable a warning\n"
+            "  -Wall                  enable all warnings\n"
+            "  -Werror                treat warnings as errors\n");
     con_out("  -force-crc=num         force a specific checksum into the header\n");
     con_out("\n");
     con_out("flags:\n"
-           "  -fadjust-vector-fields\n"
-           "            when assigning a vector field, its _y and _z fields also get assigned\n"
+            "  -fadjust-vector-fields\n"
+            "            when assigning a vector field, its _y and _z fields also get assigned\n"
            );
     return -1;
 }
@@ -415,6 +415,7 @@ int main(int argc, char **argv) {
     size_t itr;
     int retval = 0;
     bool opts_output_free = false;
+    bool progs_src = false;
 
     app_name = argv[0];
     con_init();
@@ -435,6 +436,8 @@ int main(int argc, char **argv) {
     options_set(opts_warn, WARN_EFFECTLESS_STATEMENT, true);
     options_set(opts_warn, WARN_END_SYS_FIELDS, true);
     options_set(opts_warn, WARN_ASSIGN_FUNCTION_TYPES, true);
+    options_set(opts_warn, WARN_PREPROCESSOR, true);
+    options_set(opts_warn, WARN_MULTIFILE_IF, true);
 
     options_set(opts_flags, ADJUST_VECTOR_FIELDS, true);
 
@@ -463,53 +466,49 @@ int main(int argc, char **argv) {
         con_out("standard = %i\n", opts_standard);
     }
 
-    if (!parser_init()) {
-        con_out("failed to initialize parser\n");
-        retval = 1;
-        goto cleanup;
+    if (!opts_pp_only) {
+        if (!parser_init()) {
+            con_err("failed to initialize parser\n");
+            retval = 1;
+            goto cleanup;
+        }
     }
-
-    util_debug("COM", "starting ...\n");
-
-    if (vec_size(items)) {
-        con_out("Mode: manual\n");
-        con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
-        for (itr = 0; itr < vec_size(items); ++itr) {
-            con_out("  item: %s (%s)\n",
-                   items[itr].filename,
-                   ( (items[itr].type == TYPE_QC ? "qc" :
-                     (items[itr].type == TYPE_ASM ? "asm" :
-                     (items[itr].type == TYPE_SRC ? "progs.src" :
-                     ("unknown"))))));
-
-            if (!parser_compile_file(items[itr].filename))
-            {
-                    retval = 1;
-                    goto cleanup;
+    if (opts_pp_only || opts_standard == COMPILER_FTEQCC) {
+        FILE *out = NULL;
+        if (opts_output_wasset) {
+            out = util_fopen(opts_output, "wb");
+            if (!out) {
+                con_err("failed to open `%s` for writing\n", opts_output);
+                retval = 1;
+                goto cleanup;
             }
         }
-
-        if (!parser_finish(opts_output)) {
+        if (!ftepp_init(out)) {
+            con_err("failed to initialize parser\n");
             retval = 1;
             goto cleanup;
         }
+    }
 
-    } else {
+    util_debug("COM", "starting ...\n");
+
+    if (!vec_size(items)) {
         FILE *src;
         char *line;
         size_t linelen = 0;
 
-        con_out("Mode: progs.src\n");
+        progs_src = true;
+
         src = util_fopen("progs.src", "rb");
         if (!src) {
-            con_out("failed to open `progs.src` for reading\n");
+            con_err("failed to open `progs.src` for reading\n");
             retval = 1;
             goto cleanup;
         }
 
         line = NULL;
         if (!progs_nextline(&line, &linelen, src) || !line[0]) {
-            con_out("illformatted progs.src file: expected output filename in first line\n");
+            con_err("illformatted progs.src file: expected output filename in first line\n");
             retval = 1;
             goto srcdone;
         }
@@ -520,26 +519,68 @@ int main(int argc, char **argv) {
         }
 
         while (progs_nextline(&line, &linelen, src)) {
+            argitem item;
             if (!line[0] || (line[0] == '/' && line[1] == '/'))
                 continue;
-            con_out("  src: %s\n", line);
-            if (!parser_compile_file(line)) {
-                retval = 1;
-                goto srcdone;
-            }
+            item.filename = util_strdup(line);
+            item.type     = TYPE_QC;
+            vec_push(items, item);
         }
 
-        parser_finish(opts_output);
-
 srcdone:
         fclose(src);
         mem_d(line);
     }
 
+    if (retval)
+        goto cleanup;
+
+    if (vec_size(items)) {
+        if (!opts_pp_only) {
+            con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
+            con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
+        }
+        for (itr = 0; itr < vec_size(items); ++itr) {
+            if (!opts_pp_only) {
+                con_out("  item: %s (%s)\n",
+                       items[itr].filename,
+                       ( (items[itr].type == TYPE_QC ? "qc" :
+                         (items[itr].type == TYPE_ASM ? "asm" :
+                         (items[itr].type == TYPE_SRC ? "progs.src" :
+                         ("unknown"))))));
+            }
+
+            if (opts_pp_only) {
+                if (!ftepp_preprocess_file(items[itr].filename)) {
+                    retval = 1;
+                    goto cleanup;
+                }
+            }
+            else if (!parser_compile_file(items[itr].filename)) {
+                retval = 1;
+                goto cleanup;
+            }
+
+            if (progs_src) {
+                mem_d(items[itr].filename);
+                items[itr].filename = NULL;
+            }
+        }
+
+        ftepp_finish();
+        if (!opts_pp_only) {
+            if (!parser_finish(opts_output)) {
+                retval = 1;
+                goto cleanup;
+            }
+        }
+    }
+
     /* stuff */
 
 cleanup:
     util_debug("COM", "cleaning ...\n");
+    ftepp_finish();
     con_close();
     vec_free(items);
 
index e628f43471ba9c3bec6dbb37fd114df5acb60b07..b9589892c56c3a499acb576878de0b63f8ed62ac 100644 (file)
--- a/opts.def
+++ b/opts.def
@@ -52,6 +52,8 @@
     GMQCC_DEFINE_FLAG(EFFECTLESS_STATEMENT)
     GMQCC_DEFINE_FLAG(END_SYS_FIELDS)
     GMQCC_DEFINE_FLAG(ASSIGN_FUNCTION_TYPES)
+    GMQCC_DEFINE_FLAG(PREPROCESSOR)
+    GMQCC_DEFINE_FLAG(MULTIFILE_IF)
 #endif
 
 /* some cleanup so we don't have to */
index 5738869e2ca1e639e39e23beb5abfe3f1965b692..38af9a9e0dacc6997bcc9ede49a1e496fc4c3ce9 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -3419,7 +3419,7 @@ bool parser_compile_file(const char *filename)
 {
     parser->lex = lex_open(filename);
     if (!parser->lex) {
-        con_out("failed to open file \"%s\"\n", filename);
+        con_err("failed to open file \"%s\"\n", filename);
         return false;
     }
     return parser_compile();
@@ -3429,7 +3429,7 @@ bool parser_compile_string(const char *name, const char *str)
 {
     parser->lex = lex_open_string(str, strlen(str), name);
     if (!parser->lex) {
-        con_out("failed to create lexer for string \"%s\"\n", name);
+        con_err("failed to create lexer for string \"%s\"\n", name);
         return false;
     }
     return parser_compile();