]> git.xonotic.org Git - xonotic/gmqcc.git/blobdiff - parser.c
Rename
[xonotic/gmqcc.git] / parser.c
index 10ddbdcb606c4b96b54ff32733f420b2a26317ba..a54094a841f32beb80f736f756f7b00fee1226f8 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -34,7 +34,6 @@
 #define PARSER_HT_SIZE    128
 #define TYPEDEF_HT_SIZE   16
 
-enum parser_pot { POT_PAREN, POT_TERNARY1, POT_TERNARY2 };
 typedef struct {
     lex_file *lex;
     int      tok;
@@ -89,12 +88,6 @@ typedef struct {
     /* we store the '=' operator info */
     const oper_info *assign_op;
 
-    /* Keep track of our ternary vs parenthesis nesting state.
-     * If we reach a 'comma' operator in a ternary without a paren,
-     * we shall trigger -Wternary-precedence.
-     */
-    enum parser_pot *pot;
-
     /* magic values */
     ast_value *const_vec[3];
 
@@ -142,16 +135,6 @@ static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *
     return r;
 }
 
-static bool GMQCC_WARN genwarning(lex_ctx ctx, int warntype, const char *fmt, ...)
-{
-    bool    r;
-    va_list ap;
-    va_start(ap, fmt);
-    r = vcompile_warning(ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
 /**********************************************************************
  * some maths used for constant folding
  */
@@ -386,23 +369,28 @@ static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t
 typedef struct
 {
     size_t etype; /* 0 = expression, others are operators */
-    int             paren;
+    bool            isparen;
     size_t          off;
     ast_expression *out;
     ast_block      *block; /* for commas and function calls */
     lex_ctx ctx;
 } sy_elem;
+
+enum {
+    PAREN_EXPR,
+    PAREN_FUNC,
+    PAREN_INDEX,
+    PAREN_TERNARY1,
+    PAREN_TERNARY2
+};
 typedef struct
 {
-    sy_elem *out;
-    sy_elem *ops;
+    sy_elem        *out;
+    sy_elem        *ops;
+    size_t         *argc;
+    unsigned int   *paren;
 } shunt;
 
-#define SY_PAREN_EXPR '('
-#define SY_PAREN_FUNC 'f'
-#define SY_PAREN_INDEX '['
-#define SY_PAREN_TERNARY '?'
-
 static sy_elem syexp(lex_ctx ctx, ast_expression *v) {
     sy_elem e;
     e.etype = 0;
@@ -410,7 +398,7 @@ static sy_elem syexp(lex_ctx ctx, ast_expression *v) {
     e.out   = v;
     e.block = NULL;
     e.ctx   = ctx;
-    e.paren = 0;
+    e.isparen = false;
     return e;
 }
 
@@ -421,7 +409,7 @@ static sy_elem syblock(lex_ctx ctx, ast_block *v) {
     e.out   = (ast_expression*)v;
     e.block = v;
     e.ctx   = ctx;
-    e.paren = 0;
+    e.isparen = false;
     return e;
 }
 
@@ -432,27 +420,21 @@ static sy_elem syop(lex_ctx ctx, const oper_info *op) {
     e.out   = NULL;
     e.block = NULL;
     e.ctx   = ctx;
-    e.paren = 0;
+    e.isparen = false;
     return e;
 }
 
-static sy_elem syparen(lex_ctx ctx, int p, size_t off) {
+static sy_elem syparen(lex_ctx ctx, size_t off) {
     sy_elem e;
     e.etype = 0;
     e.off   = off;
     e.out   = NULL;
     e.block = NULL;
     e.ctx   = ctx;
-    e.paren = p;
+    e.isparen = true;
     return e;
 }
 
-#ifdef DEBUGSHUNT
-# define DEBUGSHUNTDO(x) x
-#else
-# define DEBUGSHUNTDO(x)
-#endif
-
 /* With regular precedence rules, ent.foo[n] is the same as (ent.foo)[n],
  * so we need to rotate it to become ent.(foo[n]).
  */
@@ -537,7 +519,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
         return false;
     }
 
-    if (vec_last(sy->ops).paren) {
+    if (vec_last(sy->ops).isparen) {
         parseerror(parser, "unmatched parenthesis");
         return false;
     }
@@ -545,8 +527,6 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
     op = &operators[vec_last(sy->ops).etype - 1];
     ctx = vec_last(sy->ops).ctx;
 
-    DEBUGSHUNTDO(con_out("apply %s\n", op->op));
-
     if (vec_size(sy->out) < op->operands) {
         compile_error(ctx, "internal error: not enough operands: %i (operator %s (%i))", vec_size(sy->out),
                       op->op, (int)op->id);
@@ -650,7 +630,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             {
 #if 0
                 /* This is not broken in fteqcc anymore */
-                if (opts.standard != COMPILER_GMQCC) {
+                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
                     /* this error doesn't need to make us bail out */
                     (void)!parsewarning(parser, WARN_EXTENSIONS,
                                         "accessing array-field members of an entity without parenthesis\n"
@@ -661,6 +641,12 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             break;
 
         case opid1(','):
+            if (vec_size(sy->paren) && vec_last(sy->paren) == PAREN_FUNC) {
+                vec_push(sy->out, syexp(ctx, exprs[0]));
+                vec_push(sy->out, syexp(ctx, exprs[1]));
+                vec_last(sy->argc)++;
+                return true;
+            }
             if (blocks[0]) {
                 if (!ast_block_add_expr(blocks[0], exprs[1]))
                     return false;
@@ -1052,11 +1038,11 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             break;
 
         case opid2('?',':'):
-            if (vec_last(parser->pot) != POT_TERNARY2) {
+            if (vec_last(sy->paren) != PAREN_TERNARY2) {
                 compile_error(ctx, "mismatched parenthesis/ternary");
                 return false;
             }
-            vec_pop(parser->pot);
+            vec_pop(sy->paren);
             if (!ast_compare_type(exprs[1], exprs[2])) {
                 ast_type_to_string(exprs[1], ty1, sizeof(ty1));
                 ast_type_to_string(exprs[2], ty2, sizeof(ty2));
@@ -1364,11 +1350,10 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
 #undef NotSameType
 
     if (!out) {
-        compile_error(ctx, "failed to apply operand %s", op->op);
+        compile_error(ctx, "failed to apply operator %s", op->op);
         return false;
     }
 
-    DEBUGSHUNTDO(con_out("applied %s\n", op->op));
     vec_push(sy->out, syexp(ctx, out));
     return true;
 }
@@ -1381,20 +1366,27 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
     ast_call       *call;
 
     size_t          fid;
-    size_t          paramcount;
+    size_t          paramcount, i;
 
+    fid = vec_last(sy->ops).off;
     vec_shrinkby(sy->ops, 1);
-    fid = sy->ops[vec_size(sy->ops)].off;
 
     /* out[fid] is the function
      * everything above is parameters...
-     * 0 params = nothing
-     * 1 params = ast_expression
-     * more = ast_block
      */
+    if (!vec_size(sy->argc)) {
+        parseerror(parser, "internal error: no argument counter available");
+        return false;
+    }
+
+    paramcount = vec_last(sy->argc);
+    vec_pop(sy->argc);
 
-    if (vec_size(sy->out) < 1 || vec_size(sy->out) <= fid) {
-        parseerror(parser, "internal error: function call needs function and parameter list...");
+    if (vec_size(sy->out) < fid) {
+        parseerror(parser, "internal error: broken function call%lu < %lu+%lu\n",
+                   (unsigned long)vec_size(sy->out),
+                   (unsigned long)fid,
+                   (unsigned long)paramcount);
         return false;
     }
 
@@ -1417,47 +1409,31 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
     }
 
     call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun);
-    if (!call) {
-        parseerror(parser, "internal error: failed to create ast_call node");
+    if (!call)
         return false;
-    }
 
-    if (fid+1 == vec_size(sy->out)) {
-        /* no arguments */
-        paramcount = 0;
-    } else if (fid+2 == vec_size(sy->out)) {
-        ast_block *params;
-        vec_shrinkby(sy->out, 1);
-        params = sy->out[vec_size(sy->out)].block;
-        if (!params) {
-            /* 1 param */
-            paramcount = 1;
-            vec_push(call->params, sy->out[vec_size(sy->out)].out);
-        } else {
-            paramcount = vec_size(params->exprs);
-            call->params = params->exprs;
-            params->exprs = NULL;
-            ast_delete(params);
-        }
-        if (parser->max_param_count < paramcount)
-            parser->max_param_count = paramcount;
-        (void)!ast_call_check_types(call);
-    } else {
-        parseerror(parser, "invalid function call");
+    if (fid+1 < vec_size(sy->out))
+        ++paramcount;
+
+    if (fid+1 + paramcount != vec_size(sy->out)) {
+        parseerror(parser, "internal error: parameter count mismatch: (%lu+1+%lu), %lu",
+                   (unsigned long)fid, (unsigned long)paramcount, (unsigned long)vec_size(sy->out));
         return false;
     }
 
+    for (i = 0; i < paramcount; ++i)
+        vec_push(call->params, sy->out[fid+1 + i].out);
+    vec_shrinkby(sy->out, paramcount);
+    (void)!ast_call_check_types(call);
+    if (parser->max_param_count < paramcount)
+        parser->max_param_count = paramcount;
+
     if (ast_istype(fun, ast_value)) {
         funval = (ast_value*)fun;
         if ((fun->expression.flags & AST_FLAG_VARIADIC) &&
             !(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin))
         {
-            size_t va_count;
-            if (paramcount < vec_size(fun->expression.params))
-                va_count = 0;
-            else
-                va_count = paramcount - vec_size(fun->expression.params);
-            call->va_count = (ast_expression*)parser_const_float(parser, (double)va_count);
+            call->va_count = (ast_expression*)parser_const_float(parser, (double)paramcount);
         }
     }
 
@@ -1518,54 +1494,48 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
     return true;
 }
 
-static bool parser_close_paren(parser_t *parser, shunt *sy, bool functions_only)
+static bool parser_close_paren(parser_t *parser, shunt *sy)
 {
     if (!vec_size(sy->ops)) {
         parseerror(parser, "unmatched closing paren");
         return false;
     }
-    /* this would for bit a + (x) because there are no operators inside (x)
-    if (sy->ops[vec_size(sy->ops)-1].paren == 1) {
-        parseerror(parser, "empty parenthesis expression");
-        return false;
-    }
-    */
+
     while (vec_size(sy->ops)) {
-        if (vec_last(sy->ops).paren == SY_PAREN_FUNC) {
-            if (!parser_close_call(parser, sy))
-                return false;
-            break;
-        }
-        if (vec_last(sy->ops).paren == SY_PAREN_EXPR) {
-            if (!vec_size(sy->out)) {
-                compile_error(vec_last(sy->ops).ctx, "empty paren expression");
+        if (vec_last(sy->ops).isparen) {
+            if (vec_last(sy->paren) == PAREN_FUNC) {
+                vec_pop(sy->paren);
+                if (!parser_close_call(parser, sy))
+                    return false;
+                break;
+            }
+            if (vec_last(sy->paren) == PAREN_EXPR) {
+                vec_pop(sy->paren);
+                if (!vec_size(sy->out)) {
+                    compile_error(vec_last(sy->ops).ctx, "empty paren expression");
+                    vec_shrinkby(sy->ops, 1);
+                    return false;
+                }
                 vec_shrinkby(sy->ops, 1);
-                return false;
+                break;
             }
-            vec_shrinkby(sy->ops, 1);
-            return !functions_only;
-        }
-        if (vec_last(sy->ops).paren == SY_PAREN_INDEX) {
-            if (functions_only)
-                return false;
-            /* pop off the parenthesis */
-            vec_shrinkby(sy->ops, 1);
-            /* then apply the index operator */
-            if (!parser_sy_apply_operator(parser, sy))
-                return false;
-            return true;
-        }
-        if (vec_last(sy->ops).paren == SY_PAREN_TERNARY) {
-            if (functions_only)
-                return false;
-            if (vec_last(parser->pot) != POT_TERNARY1) {
-                parseerror(parser, "mismatched colon in ternary expression (missing closing paren?)");
-                return false;
+            if (vec_last(sy->paren) == PAREN_INDEX) {
+                vec_pop(sy->paren);
+                /* pop off the parenthesis */
+                vec_shrinkby(sy->ops, 1);
+                /* then apply the index operator */
+                if (!parser_sy_apply_operator(parser, sy))
+                    return false;
+                break;
             }
-            vec_last(parser->pot) = POT_TERNARY2;
-            /* pop off the parenthesis */
-            vec_shrinkby(sy->ops, 1);
-            return true;
+            if (vec_last(sy->paren) == PAREN_TERNARY1) {
+                vec_last(sy->paren) = PAREN_TERNARY2;
+                /* pop off the parenthesis */
+                vec_shrinkby(sy->ops, 1);
+                break;
+            }
+            compile_error(vec_last(sy->ops).ctx, "invalid parenthesis");
+            return false;
         }
         if (!parser_sy_apply_operator(parser, sy))
             return false;
@@ -1666,324 +1636,227 @@ static ast_expression* parse_vararg_do(parser_t *parser)
 
 static ast_expression* parse_vararg(parser_t *parser)
 {
-    bool             old_noops = parser->lex->flags.noops;
-    enum parser_pot *old_pot   = parser->pot;
+    bool           old_noops = parser->lex->flags.noops;
 
     ast_expression *out;
 
-    parser->pot = NULL;
     parser->lex->flags.noops = true;
     out = parse_vararg_do(parser);
 
-    parser->pot              = old_pot;
     parser->lex->flags.noops = old_noops;
     return out;
 }
 
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels)
+static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
 {
-    ast_expression *expr = NULL;
-    shunt sy;
-    size_t i;
-    bool wantop = false;
-    /* only warn once about an assignment in a truth value because the current code
-     * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part
-     */
-    bool warn_truthvalue = true;
-
-    /* count the parens because an if starts with one, so the
-     * end of a condition is an unmatched closing paren
-     */
-    int parens = 0;
-    int ternaries = 0;
-
-    sy.out = NULL;
-    sy.ops = NULL;
-
-    parser->lex->flags.noops = false;
-
-    parser_reclassify_token(parser);
-
-    while (true)
+    if (OPTS_FLAG(TRANSLATABLE_STRINGS) &&
+        parser->tok == TOKEN_IDENT &&
+        !strcmp(parser_tokval(parser), "_"))
     {
-        if (OPTS_FLAG(TRANSLATABLE_STRINGS) &&
-            parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "_"))
-        {
-            /* a translatable string */
-            ast_value *val;
-
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement, got constant");
-                goto onerr;
-            }
+        /* a translatable string */
+        ast_value *val;
 
-            parser->lex->flags.noops = true;
-            if (!parser_next(parser) || parser->tok != '(') {
-                parseerror(parser, "use _(\"string\") to create a translatable string constant");
-                goto onerr;
-            }
-            parser->lex->flags.noops = false;
-            if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
-                parseerror(parser, "expected a constant string in translatable-string extension");
-                goto onerr;
-            }
-            val = parser_const_string(parser, parser_tokval(parser), true);
-            wantop = true;
-            if (!val)
-                return NULL;
-            vec_push(sy.out, syexp(parser_ctx(parser), (ast_expression*)val));
-            DEBUGSHUNTDO(con_out("push string\n"));
+        parser->lex->flags.noops = true;
+        if (!parser_next(parser) || parser->tok != '(') {
+            parseerror(parser, "use _(\"string\") to create a translatable string constant");
+            return false;
+        }
+        parser->lex->flags.noops = false;
+        if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
+            parseerror(parser, "expected a constant string in translatable-string extension");
+            return false;
+        }
+        val = parser_const_string(parser, parser_tokval(parser), true);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val));
 
-            if (!parser_next(parser) || parser->tok != ')') {
-                parseerror(parser, "expected closing paren after translatable string");
-                goto onerr;
-            }
+        if (!parser_next(parser) || parser->tok != ')') {
+            parseerror(parser, "expected closing paren after translatable string");
+            return false;
         }
-        else if (parser->tok == TOKEN_DOTS)
+        return true;
+    }
+    else if (parser->tok == TOKEN_DOTS)
+    {
+        ast_expression *va;
+        if (!OPTS_FLAG(VARIADIC_ARGS)) {
+            parseerror(parser, "cannot access varargs (try -fvariadic-args)");
+            return false;
+        }
+        va = parse_vararg(parser);
+        if (!va)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), va));
+        return true;
+    }
+    else if (parser->tok == TOKEN_FLOATCONST) {
+        ast_value *val;
+        val = parser_const_float(parser, (parser_token(parser)->constval.f));
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
+        ast_value *val;
+        val = parser_const_float(parser, (double)(parser_token(parser)->constval.i));
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_STRINGCONST) {
+        ast_value *val;
+        val = parser_const_string(parser, parser_tokval(parser), false);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_VECTORCONST) {
+        ast_value *val;
+        val = parser_const_vector(parser, parser_token(parser)->constval.v);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), (ast_expression*)val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_IDENT)
+    {
+        const char     *ctoken = parser_tokval(parser);
+        ast_expression *prev = vec_size(sy->out) ? vec_last(sy->out).out : NULL;
+        ast_expression *var;
+        /* a_vector.{x,y,z} */
+        if (!vec_size(sy->ops) ||
+            !vec_last(sy->ops).etype ||
+            operators[vec_last(sy->ops).etype-1].id != opid1('.') ||
+            (prev >= intrinsic_debug_typestring &&
+             prev <= intrinsic_debug_typestring))
         {
-            ast_expression *va;
-            if (!OPTS_FLAG(VARIADIC_ARGS)) {
-                parseerror(parser, "cannot access varargs (try -fvariadic-args)");
-                goto onerr;
-            }
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement");
-                goto onerr;
-            }
-            wantop = true;
-            va = parse_vararg(parser);
-            if (!va)
-                goto onerr;
-            vec_push(sy.out, syexp(parser_ctx(parser), va));
-            DEBUGSHUNTDO(con_out("push `...`\n"));
+            /* When adding more intrinsics, fix the above condition */
+            prev = NULL;
         }
-        else if (parser->tok == TOKEN_IDENT)
+        if (prev && prev->expression.vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1])
         {
-            const char     *ctoken = parser_tokval(parser);
-            ast_expression *prev = vec_size(sy.out) ? vec_last(sy.out).out : NULL;
-            ast_expression *var;
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement");
-                goto onerr;
+            var = (ast_expression*)parser->const_vec[ctoken[0]-'x'];
+        } else {
+            var = parser_find_var(parser, parser_tokval(parser));
+            if (!var)
+                var = parser_find_field(parser, parser_tokval(parser));
+        }
+        if (!var && with_labels) {
+            var = (ast_expression*)parser_find_label(parser, parser_tokval(parser));
+            if (!with_labels) {
+                ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true);
+                var = (ast_expression*)lbl;
+                vec_push(parser->labels, lbl);
             }
-            wantop = true;
-            /* a_vector.{x,y,z} */
-            if (!vec_size(sy.ops) ||
-                !vec_last(sy.ops).etype ||
-                operators[vec_last(sy.ops).etype-1].id != opid1('.') ||
-                (prev >= intrinsic_debug_typestring &&
-                 prev <= intrinsic_debug_typestring))
-            {
-                /* When adding more intrinsics, fix the above condition */
-                prev = NULL;
+        }
+        if (!var) {
+            /* intrinsics */
+            if (!strcmp(parser_tokval(parser), "__builtin_debug_typestring")) {
+                var = (ast_expression*)intrinsic_debug_typestring;
             }
-            if (prev && prev->expression.vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1])
+            else
             {
-                var = (ast_expression*)parser->const_vec[ctoken[0]-'x'];
-            } else {
-                var = parser_find_var(parser, parser_tokval(parser));
-                if (!var)
-                    var = parser_find_field(parser, parser_tokval(parser));
-            }
-            if (!var && with_labels) {
-                var = (ast_expression*)parser_find_label(parser, parser_tokval(parser));
-                if (!with_labels) {
-                    ast_label *lbl = ast_label_new(parser_ctx(parser), parser_tokval(parser), true);
-                    var = (ast_expression*)lbl;
-                    vec_push(parser->labels, lbl);
-                }
-            }
-            if (!var) {
-                /* intrinsics */
-                if (!strcmp(parser_tokval(parser), "__builtin_debug_typestring")) {
-                    var = (ast_expression*)intrinsic_debug_typestring;
-                }
-                else
-                {
-                    size_t i;
-                    char  *correct = NULL;
-
-                    /*
-                     * sometimes people use preprocessing predefs without enabling them
-                     * i've done this thousands of times already myself.  Lets check for
-                     * it in the predef table.  And diagnose it better :)
-                     */
-                    if (!OPTS_FLAG(FTEPP_PREDEFS)) {
-                        for (i = 0; i < sizeof(ftepp_predefs)/sizeof(*ftepp_predefs); i++) {
-                            if (!strcmp(ftepp_predefs[i].name, parser_tokval(parser))) {
-                                parseerror(parser, "unexpected ident: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
-                                goto onerr;
-                            }
+                char *correct = NULL;
+                size_t i;
+
+                /*
+                 * sometimes people use preprocessing predefs without enabling them
+                 * i've done this thousands of times already myself.  Lets check for
+                 * it in the predef table.  And diagnose it better :)
+                 */
+                if (!OPTS_FLAG(FTEPP_PREDEFS)) {
+                    for (i = 0; i < sizeof(ftepp_predefs)/sizeof(*ftepp_predefs); i++) {
+                        if (!strcmp(ftepp_predefs[i].name, parser_tokval(parser))) {
+                            parseerror(parser, "unexpected ident: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
+                            return false;
                         }
                     }
+                }
 
-                    /*
-                     * TODO: determine the best score for the identifier: be it
-                     * a variable, a field.
-                     *
-                     * We should also consider adding correction tables for
-                     * other things as well.
-                     */
-                    if (opts.correction) {
-                        correction_t corr;
-                        correct_init(&corr);
-
-                        for (i = 0; i < vec_size(parser->correct_variables); i++) {
-                            correct = correct_str(&corr, parser->correct_variables[i], parser_tokval(parser));
-                            if (strcmp(correct, parser_tokval(parser))) {
-                                break;
-                            } else if (correct) {
-                                mem_d(correct);
-                                correct = NULL;
-                            }
-                        }
-                        correct_free(&corr);
-
-                        if (correct) {
-                            parseerror(parser, "unexpected ident: %s (did you mean %s?)", parser_tokval(parser), correct);
+                /*
+                 * TODO: determine the best score for the identifier: be it
+                 * a variable, a field.
+                 *
+                 * We should also consider adding correction tables for
+                 * other things as well.
+                 */
+                if (OPTS_OPTION_BOOL(OPTION_CORRECTION)) {
+                    correction_t corr;
+                    correct_init(&corr);
+
+                    for (i = 0; i < vec_size(parser->correct_variables); i++) {
+                        correct = correct_str(&corr, parser->correct_variables[i], parser_tokval(parser));
+                        if (strcmp(correct, parser_tokval(parser))) {
+                            break;
+                        } else if (correct) {
                             mem_d(correct);
-                            goto onerr;
+                            correct = NULL;
                         }
                     }
-                    parseerror(parser, "unexpected ident: %s", parser_tokval(parser));
-                    goto onerr;
-                }
-            }
-            else
-            {
-                if (ast_istype(var, ast_value)) {
-                    ((ast_value*)var)->uses++;
-                }
-                else if (ast_istype(var, ast_member)) {
-                    ast_member *mem = (ast_member*)var;
-                    if (ast_istype(mem->owner, ast_value))
-                        ((ast_value*)(mem->owner))->uses++;
+                    correct_free(&corr);
+
+                    if (correct) {
+                        parseerror(parser, "unexpected ident: %s (did you mean %s?)", parser_tokval(parser), correct);
+                        mem_d(correct);
+                        return false;
+                    }
                 }
+                parseerror(parser, "unexpected ident: %s", parser_tokval(parser));
+                return false;
             }
-            vec_push(sy.out, syexp(parser_ctx(parser), var));
-            DEBUGSHUNTDO(con_out("push %s\n", parser_tokval(parser)));
-        }
-        else if (parser->tok == TOKEN_FLOATCONST) {
-            ast_value *val;
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement, got constant");
-                goto onerr;
-            }
-            wantop = true;
-            val = parser_const_float(parser, (parser_token(parser)->constval.f));
-            if (!val)
-                return NULL;
-            vec_push(sy.out, syexp(parser_ctx(parser), (ast_expression*)val));
-            DEBUGSHUNTDO(con_out("push %g\n", parser_token(parser)->constval.f));
-        }
-        else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
-            ast_value *val;
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement, got constant");
-                goto onerr;
-            }
-            wantop = true;
-            val = parser_const_float(parser, (double)(parser_token(parser)->constval.i));
-            if (!val)
-                return NULL;
-            vec_push(sy.out, syexp(parser_ctx(parser), (ast_expression*)val));
-            DEBUGSHUNTDO(con_out("push %i\n", parser_token(parser)->constval.i));
         }
-        else if (parser->tok == TOKEN_STRINGCONST) {
-            ast_value *val;
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement, got constant");
-                goto onerr;
+        else
+        {
+            if (ast_istype(var, ast_value)) {
+                ((ast_value*)var)->uses++;
             }
-            wantop = true;
-            val = parser_const_string(parser, parser_tokval(parser), false);
-            if (!val)
-                return NULL;
-            vec_push(sy.out, syexp(parser_ctx(parser), (ast_expression*)val));
-            DEBUGSHUNTDO(con_out("push string\n"));
-        }
-        else if (parser->tok == TOKEN_VECTORCONST) {
-            ast_value *val;
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement, got constant");
-                goto onerr;
+            else if (ast_istype(var, ast_member)) {
+                ast_member *mem = (ast_member*)var;
+                if (ast_istype(mem->owner, ast_value))
+                    ((ast_value*)(mem->owner))->uses++;
             }
-            wantop = true;
-            val = parser_const_vector(parser, parser_token(parser)->constval.v);
-            if (!val)
-                return NULL;
-            vec_push(sy.out, syexp(parser_ctx(parser), (ast_expression*)val));
-            DEBUGSHUNTDO(con_out("push '%g %g %g'\n",
-                                parser_token(parser)->constval.v.x,
-                                parser_token(parser)->constval.v.y,
-                                parser_token(parser)->constval.v.z));
         }
-        else if (parser->tok == '(') {
-            parseerror(parser, "internal error: '(' should be classified as operator");
-            goto onerr;
-        }
-        else if (parser->tok == '[') {
-            parseerror(parser, "internal error: '[' should be classified as operator");
-            goto onerr;
-        }
-        else if (parser->tok == ')') {
-            if (wantop) {
-                DEBUGSHUNTDO(con_out("do[op] )\n"));
-                --parens;
-                if (parens < 0)
-                    break;
-                /* we do expect an operator next */
-                /* closing an opening paren */
-                if (!parser_close_paren(parser, &sy, false))
-                    goto onerr;
-                if (vec_last(parser->pot) != POT_PAREN) {
-                    parseerror(parser, "mismatched parentheses (closing paren during ternary expression?)");
-                    goto onerr;
-                }
-                vec_pop(parser->pot);
-            } else {
-                DEBUGSHUNTDO(con_out("do[nop] )\n"));
-                --parens;
-                if (parens < 0)
-                    break;
-                /* allowed for function calls */
-                if (!parser_close_paren(parser, &sy, true))
-                    goto onerr;
-                if (vec_last(parser->pot) != POT_PAREN) {
-                    parseerror(parser, "mismatched parentheses (closing paren during ternary expression?)");
-                    goto onerr;
-                }
-                vec_pop(parser->pot);
-            }
-            wantop = true;
-        }
-        else if (parser->tok == ']') {
-            if (!wantop)
-                parseerror(parser, "operand expected");
-            --parens;
-            if (parens < 0)
-                break;
-            if (!parser_close_paren(parser, &sy, false))
-                goto onerr;
-            if (vec_last(parser->pot) != POT_PAREN) {
-                parseerror(parser, "mismatched parentheses (closing paren during ternary expression?)");
-                goto onerr;
-            }
-            vec_pop(parser->pot);
-            wantop = true;
-        }
-        else if (parser->tok == TOKEN_TYPENAME) {
+        vec_push(sy->out, syexp(parser_ctx(parser), var));
+        return true;
+    }
+    parseerror(parser, "unexpected token `%s`", parser_tokval(parser));
+    return false;
+}
+
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue, bool with_labels)
+{
+    ast_expression *expr = NULL;
+    shunt sy;
+    size_t i;
+    bool wantop = false;
+    /* only warn once about an assignment in a truth value because the current code
+     * would trigger twice on: if(a = b && ...), once for the if-truth-value, once for the && part
+     */
+    bool warn_truthvalue = true;
+
+    /* count the parens because an if starts with one, so the
+     * end of a condition is an unmatched closing paren
+     */
+    int ternaries = 0;
+
+    memset(&sy, 0, sizeof(sy));
+
+    parser->lex->flags.noops = false;
+
+    parser_reclassify_token(parser);
+
+    while (true)
+    {
+        if (parser->tok == TOKEN_TYPENAME) {
             parseerror(parser, "unexpected typename");
             goto onerr;
         }
-        else if (parser->tok != TOKEN_OPERATOR) {
-            if (wantop) {
-                parseerror(parser, "expected operator or end of statement");
-                goto onerr;
-            }
-            break;
-        }
-        else
+
+        if (parser->tok == TOKEN_OPERATOR)
         {
             /* classify the operator */
             const oper_info *op;
@@ -2005,7 +1878,7 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             op = &operators[o];
 
             /* when declaring variables, a comma starts a new variable */
-            if (op->id == opid1(',') && !parens && stopatcomma) {
+            if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) {
                 /* fixup the token */
                 parser->tok = ',';
                 break;
@@ -2018,12 +1891,12 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             }
 
             if (op->id == opid1(',')) {
-                if (vec_size(parser->pot) && vec_last(parser->pot) == POT_TERNARY2) {
+                if (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) {
                     (void)!parsewarning(parser, WARN_TERNARY_PRECEDENCE, "suggesting parenthesis around ternary expression");
                 }
             }
 
-            if (vec_size(sy.ops) && !vec_last(sy.ops).paren)
+            if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
                 olast = &operators[vec_last(sy.ops).etype-1];
 
 #define IsAssignOp(x) (\
@@ -2040,7 +1913,7 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             if (warn_truthvalue) {
                 if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) ||
                      (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) ||
-                     (truthvalue && !vec_size(parser->pot) && IsAssignOp(op->id))
+                     (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id))
                    )
                 {
                     (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value");
@@ -2054,7 +1927,7 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             {
                 if (!parser_sy_apply_operator(parser, &sy))
                     goto onerr;
-                if (vec_size(sy.ops) && !vec_last(sy.ops).paren)
+                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
                     olast = &operators[vec_last(sy.ops).etype-1];
                 else
                     olast = NULL;
@@ -2063,14 +1936,13 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             if (op->id == opid1('(')) {
                 if (wantop) {
                     size_t sycount = vec_size(sy.out);
-                    DEBUGSHUNTDO(con_out("push [op] (\n"));
-                    ++parens; vec_push(parser->pot, POT_PAREN);
                     /* we expected an operator, this is the function-call operator */
-                    vec_push(sy.ops, syparen(parser_ctx(parser), SY_PAREN_FUNC, sycount-1));
+                    vec_push(sy.paren, PAREN_FUNC);
+                    vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1));
+                    vec_push(sy.argc, 0);
                 } else {
-                    ++parens; vec_push(parser->pot, POT_PAREN);
-                    vec_push(sy.ops, syparen(parser_ctx(parser), SY_PAREN_EXPR, 0));
-                    DEBUGSHUNTDO(con_out("push [nop] (\n"));
+                    vec_push(sy.paren, PAREN_EXPR);
+                    vec_push(sy.ops, syparen(parser_ctx(parser), 0));
                 }
                 wantop = false;
             } else if (op->id == opid1('[')) {
@@ -2078,42 +1950,104 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
                     parseerror(parser, "unexpected array subscript");
                     goto onerr;
                 }
-                ++parens; vec_push(parser->pot, POT_PAREN);
+                vec_push(sy.paren, PAREN_INDEX);
                 /* push both the operator and the paren, this makes life easier */
                 vec_push(sy.ops, syop(parser_ctx(parser), op));
-                vec_push(sy.ops, syparen(parser_ctx(parser), SY_PAREN_INDEX, 0));
+                vec_push(sy.ops, syparen(parser_ctx(parser), 0));
                 wantop = false;
             } else if (op->id == opid2('?',':')) {
                 vec_push(sy.ops, syop(parser_ctx(parser), op));
-                vec_push(sy.ops, syparen(parser_ctx(parser), SY_PAREN_TERNARY, 0));
+                vec_push(sy.ops, syparen(parser_ctx(parser), 0));
                 wantop = false;
                 ++ternaries;
-                vec_push(parser->pot, POT_TERNARY1);
+                vec_push(sy.paren, PAREN_TERNARY1);
             } else if (op->id == opid2(':','?')) {
-                if (!vec_size(parser->pot)) {
+                if (!vec_size(sy.paren)) {
                     parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
                     goto onerr;
                 }
-                if (vec_last(parser->pot) != POT_TERNARY1) {
+                if (vec_last(sy.paren) != PAREN_TERNARY1) {
                     parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
                     goto onerr;
                 }
-                if (!parser_close_paren(parser, &sy, false))
+                if (!parser_close_paren(parser, &sy))
                     goto onerr;
                 vec_push(sy.ops, syop(parser_ctx(parser), op));
                 wantop = false;
                 --ternaries;
             } else {
-                DEBUGSHUNTDO(con_out("push operator %s\n", op->op));
                 vec_push(sy.ops, syop(parser_ctx(parser), op));
                 wantop = !!(op->flags & OP_SUFFIX);
             }
         }
+        else if (parser->tok == ')') {
+            while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+            }
+            if (!vec_size(sy.paren))
+                break;
+            if (wantop) {
+                if (vec_last(sy.paren) == PAREN_TERNARY1) {
+                    parseerror(parser, "mismatched parentheses (closing paren in ternary expression?)");
+                    goto onerr;
+                }
+                if (!parser_close_paren(parser, &sy))
+                    goto onerr;
+            } else {
+                /* must be a function call without parameters */
+                if (vec_last(sy.paren) != PAREN_FUNC) {
+                    parseerror(parser, "closing paren in invalid position");
+                    goto onerr;
+                }
+                if (!parser_close_paren(parser, &sy))
+                    goto onerr;
+            }
+            wantop = true;
+        }
+        else if (parser->tok == '(') {
+            parseerror(parser, "internal error: '(' should be classified as operator");
+            goto onerr;
+        }
+        else if (parser->tok == '[') {
+            parseerror(parser, "internal error: '[' should be classified as operator");
+            goto onerr;
+        }
+        else if (parser->tok == ']') {
+            while (vec_size(sy.paren) && vec_last(sy.paren) == PAREN_TERNARY2) {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+            }
+            if (!vec_size(sy.paren))
+                break;
+            if (vec_last(sy.paren) != PAREN_INDEX) {
+                parseerror(parser, "mismatched parentheses, unexpected ']'");
+                goto onerr;
+            }
+            if (!parser_close_paren(parser, &sy))
+                goto onerr;
+            wantop = true;
+        }
+        else if (!wantop) {
+            if (!parse_sya_operand(parser, &sy, with_labels))
+                goto onerr;
+#if 0
+            if (vec_size(sy.paren) && vec_last(sy.ops).isparen && vec_last(sy.paren) == PAREN_FUNC)
+                vec_last(sy.argc)++;
+#endif
+            wantop = true;
+        }
+        else {
+            parseerror(parser, "expected operator or end of statement");
+            goto onerr;
+        }
+
         if (!parser_next(parser)) {
             goto onerr;
         }
         if (parser->tok == ';' ||
-            (!parens && (parser->tok == ']' || parser->tok == ')' || parser->tok == '}')))
+            ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) &&
+            (parser->tok == ']' || parser->tok == ')' || parser->tok == '}')))
         {
             break;
         }
@@ -2132,12 +2066,12 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
         expr = sy.out[0].out;
     vec_free(sy.out);
     vec_free(sy.ops);
-    DEBUGSHUNTDO(con_out("shunt done\n"));
-    if (vec_size(parser->pot)) {
-        parseerror(parser, "internal error: vec_size(parser->pot) = %lu", (unsigned long)vec_size(parser->pot));
+    if (vec_size(sy.paren)) {
+        parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren));
         return NULL;
     }
-    vec_free(parser->pot);
+    vec_free(sy.paren);
+    vec_free(sy.argc);
     return expr;
 
 onerr:
@@ -2148,6 +2082,8 @@ onerr:
     }
     vec_free(sy.out);
     vec_free(sy.ops);
+    vec_free(sy.paren);
+    vec_free(sy.argc);
     return NULL;
 }
 
@@ -2259,6 +2195,12 @@ static ast_expression* process_condition(parser_t *parser, ast_expression *cond,
     ast_unary *unary;
     ast_expression *prev;
 
+    if (cond->expression.vtype == TYPE_VOID || cond->expression.vtype >= TYPE_VARIANT) {
+        char ty[1024];
+        ast_type_to_string(cond, ty, sizeof(ty));
+        compile_error(ast_ctx(cond), "invalid type for if() condition: %s", ty);
+    }
+
     if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && cond->expression.vtype == TYPE_STRING)
     {
         prev = cond;
@@ -2693,7 +2635,7 @@ static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **ou
 
     if (typevar || parser->tok == TOKEN_TYPENAME) {
 #if 0
-        if (opts.standard != COMPILER_GMQCC) {
+        if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
             if (parsewarning(parser, WARN_EXTENSIONS,
                              "current standard does not allow variable declarations in for-loop initializers"))
                 goto onerr;
@@ -2738,11 +2680,12 @@ static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **ou
 
     /* parse the incrementor */
     if (parser->tok != ')') {
+        lex_ctx condctx = parser_ctx(parser);
         increment = parse_expression_leave(parser, false, false, false);
         if (!increment)
             goto onerr;
         if (!ast_side_effects(increment)) {
-            if (genwarning(ast_ctx(increment), WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+            if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
                 goto onerr;
         }
     }
@@ -3349,15 +3292,26 @@ static bool parse_eol(parser_t *parser)
     return parser->tok == TOKEN_EOL;
 }
 
-static bool parse_pragma_do(parser_t *parser)
-{
-    if (!parser_next(parser) ||
-        parser->tok != TOKEN_IDENT ||
-        strcmp(parser_tokval(parser), "pragma"))
-    {
-        parseerror(parser, "expected `pragma` keyword after `#`, got `%s`", parser_tokval(parser));
-        return false;
-    }
+/*
+ * Traditionally you'd implement warning, error, and message
+ * directives in the preprocessor. However, like #pragma, these
+ * shouldn't depend on -fftepp to utilize.  So they're instead
+ * implemented here.
+ */   
+enum {
+    PARSE_DIRECTIVE_ERROR,
+    PARSE_DIRECTIVE_MESSAGE,
+    PARSE_DIRECTIVE_WARNING,
+    PARSE_DIRECTIVE_COUNT
+};
+
+static const char *parser_directives[PARSE_DIRECTIVE_COUNT] = {
+    "error",
+    "message",
+    "warning"
+};
+
+static bool parse_pragma_do(parser_t *parser) {
     if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) {
         parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser));
         return false;
@@ -3368,35 +3322,99 @@ static bool parse_pragma_do(parser_t *parser)
             parseerror(parser, "`noref` pragma requires an argument: 0 or 1");
             return false;
         }
+
         parser->noref = !!parser_token(parser)->constval.i;
+
         if (!parse_eol(parser)) {
             parseerror(parser, "parse error after `noref` pragma");
             return false;
         }
-    }
-    else
-    {
+    } else {
         (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser));
         return false;
     }
-
     return true;
 }
 
-static bool parse_pragma(parser_t *parser)
+static bool parse_directive_or_pragma_do(parser_t *parser, bool *pragma) {
+
+    size_t type = PARSE_DIRECTIVE_COUNT;
+
+    if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+        parseerror(parser, "expected `pragma, error, message, warning` after `#`, got `%s`",
+            parser_tokval(parser));
+
+        return false;
+    }
+
+    if (!strcmp(parser_tokval(parser), "pragma" )) {
+        *pragma = true;
+
+        return parse_pragma_do(parser);
+    }
+
+    if (!strcmp(parser_tokval(parser), "error"  )) type = PARSE_DIRECTIVE_ERROR;
+    if (!strcmp(parser_tokval(parser), "message")) type = PARSE_DIRECTIVE_MESSAGE;
+    if (!strcmp(parser_tokval(parser), "warning")) type = PARSE_DIRECTIVE_WARNING;
+
+    switch (type) {
+        case PARSE_DIRECTIVE_ERROR:
+        case PARSE_DIRECTIVE_MESSAGE:
+        case PARSE_DIRECTIVE_WARNING:
+            *pragma = false;
+
+            if (!parse_skipwhite(parser) || parser->tok != TOKEN_STRINGCONST) {
+                parseerror(parser, "expected %s, got `%`", parser_directives[type], parser_tokval(parser));
+                return false;
+            }
+
+            switch (type) {
+                case PARSE_DIRECTIVE_ERROR:
+                    con_cprintmsg(&parser->lex->tok.ctx, LVL_ERROR, "error", parser_tokval(parser));
+                    compile_errors ++; /* hack */
+                    break;
+                    /*break;*/
+
+                case PARSE_DIRECTIVE_MESSAGE:
+                    con_cprintmsg(&parser->lex->tok.ctx, LVL_MSG, "message", parser_tokval(parser));
+                    break;
+
+                case PARSE_DIRECTIVE_WARNING:
+                    con_cprintmsg(&parser->lex->tok.ctx, LVL_WARNING, "warning", parser_tokval(parser));
+                    break;
+            }
+
+            if (!parse_eol(parser)) {
+                parseerror(parser, "parse error after `%` directive", parser_directives[type]);
+                return false;
+            }
+
+            return (type != PARSE_DIRECTIVE_ERROR);
+    }
+
+    parseerror(parser, "invalid directive `%s`", parser_tokval(parser));
+    return false;
+}
+
+static bool parse_directive_or_pragma(parser_t *parser)
 {
     bool rv;
+    bool pragma; /* true when parsing pragma */
     parser->lex->flags.preprocessing = true;
-    parser->lex->flags.mergelines = true;
-    rv = parse_pragma_do(parser);
+    parser->lex->flags.mergelines    = true;
+
+    rv = parse_directive_or_pragma_do(parser, &pragma);
+
     if (parser->tok != TOKEN_EOL) {
-        parseerror(parser, "junk after pragma");
+        parseerror(parser, "junk after %s", (pragma) ? "pragma" : "directive");
         rv = false;
     }
     parser->lex->flags.preprocessing = false;
-    parser->lex->flags.mergelines = false;
+    parser->lex->flags.mergelines    = false;
+
     if (!parser_next(parser)) {
-        parseerror(parser, "parse error after pragma");
+        parseerror(parser, "parse error after %s", (pragma) ? "pragma" : "directive");
         rv = false;
     }
     return rv;
@@ -3422,7 +3440,7 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
             parseerror(parser, "cannot declare a variable from here");
             return false;
         }
-        if (opts.standard == COMPILER_QCC) {
+        if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
             if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable"))
                 return false;
         }
@@ -3489,7 +3507,7 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
         }
         else if (!strcmp(parser_tokval(parser), "for"))
         {
-            if (opts.standard == COMPILER_QCC) {
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
                 if (parsewarning(parser, WARN_EXTENSIONS, "for loops are not recognized in the original Quake C standard, to enable try an alternate standard --std=?"))
                     return false;
             }
@@ -3588,12 +3606,13 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
     }
     else
     {
+        lex_ctx ctx = parser_ctx(parser);
         ast_expression *exp = parse_expression(parser, false, false);
         if (!exp)
             return false;
         *out = exp;
         if (!ast_side_effects(exp)) {
-            if (genwarning(ast_ctx(exp), WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+            if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
                 return false;
         }
         return true;
@@ -4068,6 +4087,8 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
             goto enderrfn;
         }
         func->varargs = varargs;
+
+        func->fixedparams = parser_const_float(parser, vec_size(var->expression.params));
     }
 
     parser->function = func;
@@ -4088,7 +4109,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
 
     if (parser->tok == ';')
         return parser_next(parser);
-    else if (opts.standard == COMPILER_QCC)
+    else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
         parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)");
     return retval;
 
@@ -4604,7 +4625,7 @@ static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
         vec_free(params);
 
     /* sanity check */
-    if (vec_size(params) > 8 && opts.standard == COMPILER_QCC)
+    if (vec_size(params) > 8 && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
         (void)!parsewarning(parser, WARN_EXTENSIONS, "more than 8 parameters are not supported by this standard");
 
     /* parse-out */
@@ -4818,7 +4839,7 @@ static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_va
     }
 
     /* now there may be function parens again */
-    if (parser->tok == '(' && opts.standard == COMPILER_QCC)
+    if (parser->tok == '(' && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
         parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
     if (parser->tok == '(' && wasarray)
         parseerror(parser, "arrays as part of a return type is not supported");
@@ -4957,7 +4978,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
 
         /* Part 0: finish the type */
         if (parser->tok == '(') {
-            if (opts.standard == COMPILER_QCC)
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
                 parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
             var = parse_parameter_list(parser, var);
             if (!var) {
@@ -4980,7 +5001,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
         }
         /* for functions returning functions */
         while (parser->tok == '(') {
-            if (opts.standard == COMPILER_QCC)
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
                 parseerror(parser, "C-style function syntax is not allowed in -std=qcc");
             var = parse_parameter_list(parser, var);
             if (!var) {
@@ -5050,7 +5071,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                     goto cleanup;
                     */
                 }
-                if ((opts.standard == COMPILER_QCC || opts.standard == COMPILER_FTEQCC) &&
+                if ((OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC || OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) &&
                     (old = parser_find_global(parser, var->name)))
                 {
                     parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc");
@@ -5122,7 +5143,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                         ast_delete(var);
                         var = proto;
                     }
-                    if (opts.standard == COMPILER_QCC &&
+                    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC &&
                         (old = parser_find_field(parser, var->name)))
                     {
                         parseerror(parser, "cannot declare a field and a global of the same name with -std=qcc");
@@ -5153,7 +5174,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                     retval = false;
                     goto cleanup;
                 }
-                if (opts.standard != COMPILER_GMQCC) {
+                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
                     ast_delete(var);
                     var = NULL;
                     goto skipvar;
@@ -5333,7 +5354,7 @@ skipvar:
             break;
         }
 
-        if (localblock && opts.standard == COMPILER_QCC) {
+        if (localblock && OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
             if (parsewarning(parser, WARN_LOCAL_CONSTANTS,
                              "initializing expression turns variable `%s` into a constant in this standard",
                              var->name) )
@@ -5353,7 +5374,7 @@ skipvar:
                 break;
             }
         }
-        else if (opts.standard == COMPILER_QCC) {
+        else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
             parseerror(parser, "expected '=' before function body in this standard");
         }
 
@@ -5464,7 +5485,7 @@ skipvar:
                 }
             } else {
                 int cvq;
-                shunt sy = { NULL, NULL };
+                shunt sy = { NULL, NULL, NULL, NULL };
                 cvq = var->cvq;
                 var->cvq = CV_NONE;
                 vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var));
@@ -5574,7 +5595,7 @@ static bool parser_global_statement(parser_t *parser)
     }
     else if (parser->tok == '#')
     {
-        return parse_pragma(parser);
+        return parse_directive_or_pragma(parser);
     }
     else if (parser->tok == '$')
     {
@@ -5714,7 +5735,7 @@ bool parser_init()
     parser->const_vec[1] = ast_value_new(empty_ctx, "<vector.y>", TYPE_NOEXPR);
     parser->const_vec[2] = ast_value_new(empty_ctx, "<vector.z>", TYPE_NOEXPR);
 
-    if (opts.add_info) {
+    if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) {
         parser->reserved_version = ast_value_new(empty_ctx, "reserved:version", TYPE_STRING);
         parser->reserved_version->cvq = CV_CONST;
         parser->reserved_version->hasvalue = true;
@@ -5899,8 +5920,8 @@ bool parser_finish(const char *output)
             continue;
         asvalue = (ast_value*)(parser->globals[i]);
         if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) {
-            retval = retval && !genwarning(ast_ctx(asvalue), WARN_UNUSED_VARIABLE,
-                                           "unused global: `%s`", asvalue->name);
+            retval = retval && !compile_warning(ast_ctx(asvalue), WARN_UNUSED_VARIABLE,
+                                                "unused global: `%s`", asvalue->name);
         }
         if (!ast_global_codegen(asvalue, ir, false)) {
             con_out("failed to generate global %s\n", asvalue->name);
@@ -6003,7 +6024,7 @@ bool parser_finish(const char *output)
             return false;
         }
     }
-    if (opts.dump)
+    if (OPTS_OPTION_BOOL(OPTION_DUMP))
         ir_builder_dump(ir, con_out);
     for (i = 0; i < vec_size(parser->functions); ++i) {
         if (!ir_function_finalize(parser->functions[i]->ir_func)) {
@@ -6020,7 +6041,7 @@ bool parser_finish(const char *output)
     }
 
     if (retval) {
-        if (opts.dumpfin)
+        if (OPTS_OPTION_BOOL(OPTION_DUMPFIN))
             ir_builder_dump(ir, con_out);
 
         generate_checksum(parser);