]> git.xonotic.org Git - xonotic/gmqcc.git/blobdiff - parser.c
Another hardcoded -std=gmqcc option removed and added to the implied -std=gmqcc flags
[xonotic/gmqcc.git] / parser.c
index 422930d0d8d1ca7612caee58af4e41ac92f2665a..ce1b212071b3910e2c9fb868e46f2b74f73c37ea 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -1,7 +1,8 @@
 /*
  * Copyright (C) 2012
  *     Wolfgang Bumiller
- *
+ *     Dale Weiler
+ * 
  * 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
@@ -51,6 +52,7 @@ typedef struct {
     ast_value *imm_float_zero;
     ast_value *imm_float_one;
     ast_value *imm_vector_zero;
+    ast_value *nil;
 
     size_t crc_globals;
     size_t crc_fields;
@@ -60,8 +62,10 @@ typedef struct {
     /* All the labels the function defined...
      * Should they be in ast_function instead?
      */
-    ast_label **labels;
-    ast_goto  **gotos;
+    ast_label  **labels;
+    ast_goto   **gotos;
+    const char **breaks;
+    const char **continues;
 
     /* A list of hashtables for each scope */
     ht *variables;
@@ -101,12 +105,12 @@ static void parser_enterblock(parser_t *parser);
 static bool parser_leaveblock(parser_t *parser);
 static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e);
 static bool parse_typedef(parser_t *parser);
-static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool noreturn, bool is_static);
+static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring);
 static ast_block* parse_block(parser_t *parser);
 static bool parse_block_into(parser_t *parser, ast_block *block);
 static bool parse_statement_or_block(parser_t *parser, ast_expression **out);
 static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases);
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma);
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue);
 static ast_expression* parse_expression(parser_t *parser, bool stopatcomma);
 
 static void parseerror(parser_t *parser, const char *fmt, ...)
@@ -757,10 +761,11 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             break;
         case opid1('*'):
             if (exprs[0]->expression.vtype != exprs[1]->expression.vtype &&
-                exprs[0]->expression.vtype != TYPE_VECTOR &&
-                exprs[0]->expression.vtype != TYPE_FLOAT &&
-                exprs[1]->expression.vtype != TYPE_VECTOR &&
-                exprs[1]->expression.vtype != TYPE_FLOAT)
+                !(exprs[0]->expression.vtype == TYPE_VECTOR &&
+                  exprs[1]->expression.vtype == TYPE_FLOAT) &&
+                !(exprs[1]->expression.vtype == TYPE_VECTOR &&
+                  exprs[0]->expression.vtype == TYPE_FLOAT)
+                )
             {
                 parseerror(parser, "invalid types used in expression: cannot multiply types %s and %s",
                            type_name[exprs[1]->expression.vtype],
@@ -796,6 +801,64 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                     {
                         if (CanConstFold(exprs[0], exprs[1]))
                             out = (ast_expression*)parser_const_float(parser, vec3_mulvv(ConstV(0), ConstV(1)));
+                        else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && CanConstFold1(exprs[0])) {
+                            vector vec = ConstV(0);
+                            if (!vec.y && !vec.z) { /* 'n 0 0' * v */
+                                ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+                                out = (ast_expression*)ast_member_new(ctx, exprs[1], 0, NULL);
+                                out->expression.node.keep = false;
+                                ((ast_member*)out)->rvalue = true;
+                                if (vec.x != 1)
+                                    out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_F, (ast_expression*)parser_const_float(parser, vec.x), out);
+                            }
+                            else if (!vec.x && !vec.z) { /* '0 n 0' * v */
+                                ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+                                out = (ast_expression*)ast_member_new(ctx, exprs[1], 1, NULL);
+                                out->expression.node.keep = false;
+                                ((ast_member*)out)->rvalue = true;
+                                if (vec.y != 1)
+                                    out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_F, (ast_expression*)parser_const_float(parser, vec.y), out);
+                            }
+                            else if (!vec.x && !vec.y) { /* '0 n 0' * v */
+                                ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+                                out = (ast_expression*)ast_member_new(ctx, exprs[1], 2, NULL);
+                                out->expression.node.keep = false;
+                                ((ast_member*)out)->rvalue = true;
+                                if (vec.z != 1)
+                                    out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_F, (ast_expression*)parser_const_float(parser, vec.z), out);
+                            }
+                            else
+                                out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
+                        }
+                        else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && CanConstFold1(exprs[1])) {
+                            vector vec = ConstV(1);
+                            if (!vec.y && !vec.z) { /* v * 'n 0 0' */
+                                ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+                                out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL);
+                                out->expression.node.keep = false;
+                                ((ast_member*)out)->rvalue = true;
+                                if (vec.x != 1)
+                                    out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_F, out, (ast_expression*)parser_const_float(parser, vec.x));
+                            }
+                            else if (!vec.x && !vec.z) { /* v * '0 n 0' */
+                                ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+                                out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL);
+                                out->expression.node.keep = false;
+                                ((ast_member*)out)->rvalue = true;
+                                if (vec.y != 1)
+                                    out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_F, out, (ast_expression*)parser_const_float(parser, vec.y));
+                            }
+                            else if (!vec.x && !vec.y) { /* v * '0 n 0' */
+                                ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+                                out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL);
+                                out->expression.node.keep = false;
+                                ((ast_member*)out)->rvalue = true;
+                                if (vec.z != 1)
+                                    out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_F, out, (ast_expression*)parser_const_float(parser, vec.z));
+                            }
+                            else
+                                out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
+                        }
                         else
                             out = (ast_expression*)ast_binary_new(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
                     }
@@ -876,7 +939,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                           ( (generated_op == INSTR_OR)
                             ? (immediate_is_true(ctx, asvalue[0]) || immediate_is_true(ctx, asvalue[1]))
                             : (immediate_is_true(ctx, asvalue[0]) && immediate_is_true(ctx, asvalue[1])) )
-                          ? 0 : 1);
+                          ? 1 : 0);
             }
             else
             {
@@ -920,14 +983,14 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                 return false;
             }
             vec_pop(parser->pot);
-            if (exprs[1]->expression.vtype != exprs[2]->expression.vtype) {
+            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));
                 parseerror(parser, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2);
                 return false;
             }
             if (CanConstFold1(exprs[0]))
-                out = (ConstF(0) ? exprs[1] : exprs[2]);
+                out = (immediate_is_true(ctx, asvalue[0]) ? exprs[1] : exprs[2]);
             else
                 out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]);
             break;
@@ -978,8 +1041,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                 }
                 else
                     assignop = type_storep_instr[exprs[0]->expression.vtype];
-                if (assignop == AINSTR_END ||
-                    !ast_compare_type(field->expression.next, exprs[1]))
+                if (assignop == AINSTR_END || !ast_compare_type(field->expression.next, exprs[1]))
                 {
                     ast_type_to_string(field->expression.next, ty1, sizeof(ty1));
                     ast_type_to_string(exprs[1], ty2, sizeof(ty2));
@@ -1011,7 +1073,8 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                     ast_type_to_string(exprs[1], ty2, sizeof(ty2));
                     parseerror(parser, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
                 }
-                else if (!ast_compare_type(exprs[0], exprs[1])) {
+                else if (!ast_compare_type(exprs[0], exprs[1]))
+                {
                     ast_type_to_string(exprs[0], ty1, sizeof(ty1));
                     ast_type_to_string(exprs[1], ty2, sizeof(ty2));
                     if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
@@ -1313,43 +1376,45 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
         parseerror(parser, "could not determine function return type");
         return false;
     } else {
+        ast_value *fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : NULL);
+
+        if (fun->expression.flags & AST_FLAG_DEPRECATED) {
+            if (!fval) {
+                return !parsewarning(parser, WARN_DEPRECATED,
+                        "call to function (which is marked deprecated)\n",
+                        "-> it has been declared here: %s:%i",
+                        ast_ctx(fun).file, ast_ctx(fun).line);
+            }
+            if (!fval->desc) {
+                return !parsewarning(parser, WARN_DEPRECATED,
+                        "call to `%s` (which is marked deprecated)\n"
+                        "-> `%s` declared here: %s:%i",
+                        fval->name, fval->name, ast_ctx(fun).file, ast_ctx(fun).line);
+            }
+            return !parsewarning(parser, WARN_DEPRECATED,
+                    "call to `%s` (deprecated: %s)\n"
+                    "-> `%s` declared here: %s:%i",
+                    fval->name, fval->desc, fval->name, ast_ctx(fun).file,
+                    ast_ctx(fun).line);
+        }
+
         if (vec_size(fun->expression.params) != paramcount &&
             !((fun->expression.flags & AST_FLAG_VARIADIC) &&
               vec_size(fun->expression.params) < paramcount))
         {
-            ast_value *fval;
             const char *fewmany = (vec_size(fun->expression.params) > paramcount) ? "few" : "many";
-
-            fval = (ast_istype(fun, ast_value) ? ((ast_value*)fun) : NULL);
-            if (opts.standard == COMPILER_GMQCC)
-            {
-                if (fval)
-                    parseerror(parser, "too %s parameters for call to %s: expected %i, got %i\n"
-                               " -> `%s` has been declared here: %s:%i",
-                               fewmany, fval->name, (int)vec_size(fun->expression.params), (int)paramcount,
-                               fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line);
-                else
-                    parseerror(parser, "too %s parameters for function call: expected %i, got %i\n"
-                               " -> it has been declared here: %s:%i",
-                               fewmany, (int)vec_size(fun->expression.params), (int)paramcount,
-                               ast_ctx(fun).file, (int)ast_ctx(fun).line);
-                return false;
-            }
+            if (fval)
+                return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT,
+                                     "too %s parameters for call to %s: expected %i, got %i\n"
+                                     " -> `%s` has been declared here: %s:%i",
+                                     fewmany, fval->name, (int)vec_size(fun->expression.params), (int)paramcount,
+                                     fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line);
             else
-            {
-                if (fval)
-                    return !parsewarning(parser, WARN_TOO_FEW_PARAMETERS,
-                                         "too %s parameters for call to %s: expected %i, got %i\n"
-                                         " -> `%s` has been declared here: %s:%i",
-                                         fewmany, fval->name, (int)vec_size(fun->expression.params), (int)paramcount,
-                                         fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line);
-                else
-                    return !parsewarning(parser, WARN_TOO_FEW_PARAMETERS,
-                                         "too %s parameters for function call: expected %i, got %i\n"
-                                         " -> it has been declared here: %s:%i",
-                                         fewmany, (int)vec_size(fun->expression.params), (int)paramcount,
-                                         ast_ctx(fun).file, (int)ast_ctx(fun).line);
-            }
+                return !parsewarning(parser, WARN_INVALID_PARAMETER_COUNT,
+                                     "too %s parameters for function call: expected %i, got %i\n"
+                                     " -> it has been declared here: %s:%i",
+                                     fewmany, (int)vec_size(fun->expression.params), (int)paramcount,
+                                     ast_ctx(fun).file, (int)ast_ctx(fun).line);
         }
     }
 
@@ -1417,12 +1482,16 @@ static void parser_reclassify_token(parser_t *parser)
     }
 }
 
-static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma)
+static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma, bool truthvalue)
 {
     ast_expression *expr = NULL;
     shunt sy;
     bool wantop = false;
     bool gotmemberof = 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
@@ -1663,7 +1732,7 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             const oper_info *olast = NULL;
             size_t o;
             for (o = 0; o < operator_count; ++o) {
-                if ((!(operators[o].flags & OP_PREFIX) == wantop) &&
+                if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) &&
                     /* !(operators[o].flags & OP_SUFFIX) && / * remove this */
                     !strcmp(parser_tokval(parser), operators[o].op))
                 {
@@ -1699,6 +1768,28 @@ static ast_expression* parse_expression_leave(parser_t *parser, bool stopatcomma
             if (vec_size(sy.ops) && !vec_last(sy.ops).paren)
                 olast = &operators[vec_last(sy.ops).etype-1];
 
+#define IsAssignOp(x) (\
+                (x) == opid1('=') || \
+                (x) == opid2('+','=') || \
+                (x) == opid2('-','=') || \
+                (x) == opid2('*','=') || \
+                (x) == opid2('/','=') || \
+                (x) == opid2('%','=') || \
+                (x) == opid2('&','=') || \
+                (x) == opid2('|','=') || \
+                (x) == opid3('&','~','=') \
+                )
+            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))
+                   )
+                {
+                    (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value");
+                    warn_truthvalue = false;
+                }
+            }
+
             while (olast && (
                     (op->prec < olast->prec) ||
                     (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) )
@@ -1818,7 +1909,7 @@ onerr:
 
 static ast_expression* parse_expression(parser_t *parser, bool stopatcomma)
 {
-    ast_expression *e = parse_expression_leave(parser, stopatcomma);
+    ast_expression *e = parse_expression_leave(parser, stopatcomma, false);
     if (!e)
         return NULL;
     if (!parser_next(parser)) {
@@ -1968,7 +2059,7 @@ static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out)
         return false;
     }
     /* parse the condition */
-    cond = parse_expression_leave(parser, false);
+    cond = parse_expression_leave(parser, false, true);
     if (!cond)
         return false;
     /* closing paren */
@@ -2018,7 +2109,61 @@ static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out)
     return true;
 }
 
+static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out);
 static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = NULL;
+
+    /* skip the 'while' and get the body */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or 'while' condition in parenthesis");
+        else
+            parseerror(parser, "expected 'while' condition in parenthesis");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected 'while' condition in parenthesis");
+            return false;
+        }
+    }
+
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'while' condition in parenthesis");
+        return false;
+    }
+
+    vec_push(parser->breaks, label);
+    vec_push(parser->continues, label);
+
+    rv = parse_while_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        ast_delete(*out);
+        *out = NULL;
+    }
+    else {
+        vec_pop(parser->breaks);
+        vec_pop(parser->continues);
+    }
+    return rv;
+}
+
+static bool parse_while_go(parser_t *parser, ast_block *block, ast_expression **out)
 {
     ast_loop *aloop;
     ast_expression *cond, *ontrue;
@@ -2029,18 +2174,13 @@ static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out
 
     (void)block; /* not touching */
 
-    /* skip the 'while' and check for opening paren */
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected 'while' condition in parenthesis");
-        return false;
-    }
     /* parse into the expression */
     if (!parser_next(parser)) {
         parseerror(parser, "expected 'while' condition after opening paren");
         return false;
     }
     /* parse the condition */
-    cond = parse_expression_leave(parser, false);
+    cond = parse_expression_leave(parser, false, true);
     if (!cond)
         return false;
     /* closing paren */
@@ -2070,7 +2210,56 @@ static bool parse_while(parser_t *parser, ast_block *block, ast_expression **out
     return true;
 }
 
+static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out);
 static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = NULL;
+
+    /* skip the 'do' and get the body */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or body");
+        else
+            parseerror(parser, "expected loop body");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected loop body");
+            return false;
+        }
+    }
+
+    vec_push(parser->breaks, label);
+    vec_push(parser->continues, label);
+
+    rv = parse_dowhile_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        ast_delete(*out);
+        *out = NULL;
+    }
+    else {
+        vec_pop(parser->breaks);
+        vec_pop(parser->continues);
+    }
+    return rv;
+}
+
+static bool parse_dowhile_go(parser_t *parser, ast_block *block, ast_expression **out)
 {
     ast_loop *aloop;
     ast_expression *cond, *ontrue;
@@ -2081,11 +2270,6 @@ static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **o
 
     (void)block; /* not touching */
 
-    /* skip the 'do' and get the body */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected loop body");
-        return false;
-    }
     if (!parse_statement_or_block(parser, &ontrue))
         return false;
 
@@ -2111,7 +2295,7 @@ static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **o
         return false;
     }
     /* parse the condition */
-    cond = parse_expression_leave(parser, false);
+    cond = parse_expression_leave(parser, false, true);
     if (!cond)
         return false;
     /* closing paren */
@@ -2146,7 +2330,60 @@ static bool parse_dowhile(parser_t *parser, ast_block *block, ast_expression **o
     return true;
 }
 
+static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out);
 static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = NULL;
+
+    /* skip the 'for' and check for opening paren */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or 'for' expressions in parenthesis");
+        else
+            parseerror(parser, "expected 'for' expressions in parenthesis");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected 'for' expressions in parenthesis");
+            return false;
+        }
+    }
+
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'for' expressions in parenthesis");
+        return false;
+    }
+
+    vec_push(parser->breaks, label);
+    vec_push(parser->continues, label);
+
+    rv = parse_for_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (vec_last(parser->breaks) != label || vec_last(parser->continues) != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        ast_delete(*out);
+        *out = NULL;
+    }
+    else {
+        vec_pop(parser->breaks);
+        vec_pop(parser->continues);
+    }
+    return rv;
+}
+static bool parse_for_go(parser_t *parser, ast_block *block, ast_expression **out)
 {
     ast_loop       *aloop;
     ast_expression *initexpr, *cond, *increment, *ontrue;
@@ -2164,11 +2401,6 @@ static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
     increment = NULL;
     ontrue    = NULL;
 
-    /* skip the 'while' and check for opening paren */
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected 'for' expressions in parenthesis");
-        goto onerr;
-    }
     /* parse into the expression */
     if (!parser_next(parser)) {
         parseerror(parser, "expected 'for' initializer after opening paren");
@@ -2180,17 +2412,19 @@ static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
         typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
 
     if (typevar || parser->tok == TOKEN_TYPENAME) {
+#if 0
         if (opts.standard != COMPILER_GMQCC) {
             if (parsewarning(parser, WARN_EXTENSIONS,
                              "current standard does not allow variable declarations in for-loop initializers"))
                 goto onerr;
         }
-        if (!parse_variable(parser, block, true, CV_VAR, typevar, false, false, false))
+#endif
+        if (!parse_variable(parser, block, true, CV_VAR, typevar, false, false, 0, NULL))
             goto onerr;
     }
     else if (parser->tok != ';')
     {
-        initexpr = parse_expression_leave(parser, false);
+        initexpr = parse_expression_leave(parser, false, false);
         if (!initexpr)
             goto onerr;
     }
@@ -2207,7 +2441,7 @@ static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
 
     /* parse the condition */
     if (parser->tok != ';') {
-        cond = parse_expression_leave(parser, false);
+        cond = parse_expression_leave(parser, false, true);
         if (!cond)
             goto onerr;
     }
@@ -2224,7 +2458,7 @@ static bool parse_for(parser_t *parser, ast_block *block, ast_expression **out)
 
     /* parse the incrementor */
     if (parser->tok != ')') {
-        increment = parse_expression_leave(parser, false);
+        increment = parse_expression_leave(parser, false, false);
         if (!increment)
             goto onerr;
         if (!ast_side_effects(increment)) {
@@ -2285,7 +2519,9 @@ static bool parse_return(parser_t *parser, ast_block *block, ast_expression **ou
         if (!exp)
             return false;
 
-        if (exp->expression.vtype != expected->expression.next->expression.vtype) {
+        if (exp->expression.vtype != TYPE_NIL &&
+            exp->expression.vtype != expected->expression.next->expression.vtype)
+        {
             parseerror(parser, "return with invalid expression");
         }
 
@@ -2298,10 +2534,7 @@ static bool parse_return(parser_t *parser, ast_block *block, ast_expression **ou
         if (!parser_next(parser))
             parseerror(parser, "parse error");
         if (expected->expression.next->expression.vtype != TYPE_VOID) {
-            if (opts.standard != COMPILER_GMQCC)
-                (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
-            else
-                parseerror(parser, "return without value");
+            (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
         }
         ret = ast_return_new(ctx, NULL);
     }
@@ -2311,11 +2544,39 @@ static bool parse_return(parser_t *parser, ast_block *block, ast_expression **ou
 
 static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue)
 {
-    lex_ctx ctx = parser_ctx(parser);
+    size_t       i;
+    unsigned int levels = 0;
+    lex_ctx      ctx = parser_ctx(parser);
+    const char **loops = (is_continue ? parser->continues : parser->breaks);
 
     (void)block; /* not touching */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected semicolon or loop label");
+        return false;
+    }
 
-    if (!parser_next(parser) || parser->tok != ';') {
+    if (parser->tok == TOKEN_IDENT) {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        i = vec_size(loops);
+        while (i--) {
+            if (loops[i] && !strcmp(loops[i], parser_tokval(parser)))
+                break;
+            if (!i) {
+                parseerror(parser, "no such loop to %s: `%s`",
+                           (is_continue ? "continue" : "break out of"),
+                           parser_tokval(parser));
+                return false;
+            }
+            ++levels;
+        }
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected semicolon");
+            return false;
+        }
+    }
+
+    if (parser->tok != ';') {
         parseerror(parser, "expected semicolon");
         return false;
     }
@@ -2323,21 +2584,21 @@ static bool parse_break_continue(parser_t *parser, ast_block *block, ast_express
     if (!parser_next(parser))
         parseerror(parser, "parse error");
 
-    *out = (ast_expression*)ast_breakcont_new(ctx, is_continue);
+    *out = (ast_expression*)ast_breakcont_new(ctx, is_continue, levels);
     return true;
 }
 
 /* returns true when it was a variable qualifier, false otherwise!
  * on error, cvq is set to CV_WRONG
  */
-static bool parse_var_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *noreturn, bool *is_static)
+static bool parse_qualifiers(parser_t *parser, bool with_local, int *cvq, bool *noref, bool *is_static, uint32_t *_flags, char **message)
 {
     bool had_const    = false;
     bool had_var      = false;
     bool had_noref    = false;
-    bool had_noreturn = false;
     bool had_attrib   = false;
     bool had_static   = false;
+    uint32_t flags    = 0;
 
     *cvq = CV_NONE;
     for (;;) {
@@ -2350,7 +2611,7 @@ static bool parse_var_qualifiers(parser_t *parser, bool with_local, int *cvq, bo
                 return false;
             }
             if (!strcmp(parser_tokval(parser), "noreturn")) {
-                had_noreturn = true;
+                flags |= AST_FLAG_NORETURN;
                 if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
                     parseerror(parser, "`noreturn` attribute has no parameters, expected `]]`");
                     *cvq = CV_WRONG;
@@ -2365,6 +2626,59 @@ static bool parse_var_qualifiers(parser_t *parser, bool with_local, int *cvq, bo
                     return false;
                 }
             }
+            else if (!strcmp(parser_tokval(parser), "inline")) {
+                flags |= AST_FLAG_INLINE;
+                if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    parseerror(parser, "`noref` attribute has no parameters, expected `]]`");
+                    *cvq = CV_WRONG;
+                    return false;
+                }
+            }
+
+
+            else if (!strcmp(parser_tokval(parser), "deprecated") && !(flags & AST_FLAG_DEPRECATED)) {
+                flags   |= AST_FLAG_DEPRECATED;
+                *message = NULL;
+                
+                if (!parser_next(parser)) {
+                    parseerror(parser, "parse error in attribute");
+                    goto argerr;
+                }
+
+                if (parser->tok == '(') {
+                    if (!parser_next(parser) || parser->tok != TOKEN_STRINGCONST) {
+                        parseerror(parser, "`deprecated` attribute missing parameter");
+                        goto argerr;
+                    }
+
+                    *message = util_strdup(parser_tokval(parser));
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+
+                    if(parser->tok != ')') {
+                        parseerror(parser, "`deprecated` attribute expected `)` after parameter");
+                        goto argerr;
+                    }
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+                }
+                /* no message */
+                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    parseerror(parser, "`deprecated` attribute expected `]]`");
+
+                    argerr: /* ugly */
+                    if (*message) mem_d(*message);
+                    *message = NULL;
+                    *cvq     = CV_WRONG;
+                    return false;
+                }
+            }
             else
             {
                 /* Skip tokens until we hit a ]] */
@@ -2388,7 +2702,7 @@ static bool parse_var_qualifiers(parser_t *parser, bool with_local, int *cvq, bo
             had_var = true;
         else if (!strcmp(parser_tokval(parser), "noref"))
             had_noref = true;
-        else if (!had_const && !had_var && !had_noref && !had_noreturn && !had_attrib && !had_static) {
+        else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) {
             return false;
         }
         else
@@ -2403,8 +2717,8 @@ static bool parse_var_qualifiers(parser_t *parser, bool with_local, int *cvq, bo
     else
         *cvq = CV_NONE;
     *noref     = had_noref;
-    *noreturn  = had_noreturn;
     *is_static = had_static;
+    *_flags    = flags;
     return true;
 onerr:
     parseerror(parser, "parse error after variable qualifier");
@@ -2412,7 +2726,59 @@ onerr:
     return true;
 }
 
+static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out);
 static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    bool rv;
+    char *label = NULL;
+
+    /* skip the 'while' and get the body */
+    if (!parser_next(parser)) {
+        if (OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "expected loop label or 'switch' operand in parenthesis");
+        else
+            parseerror(parser, "expected 'switch' operand in parenthesis");
+        return false;
+    }
+
+    if (parser->tok == ':') {
+        if (!OPTS_FLAG(LOOP_LABELS))
+            parseerror(parser, "labeled loops not activated, try using -floop-labels");
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "expected loop label");
+            return false;
+        }
+        label = util_strdup(parser_tokval(parser));
+        if (!parser_next(parser)) {
+            mem_d(label);
+            parseerror(parser, "expected 'switch' operand in parenthesis");
+            return false;
+        }
+    }
+
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'switch' operand in parenthesis");
+        return false;
+    }
+
+    vec_push(parser->breaks, label);
+
+    rv = parse_switch_go(parser, block, out);
+    if (label)
+        mem_d(label);
+    if (vec_last(parser->breaks) != label) {
+        parseerror(parser, "internal error: label stack corrupted");
+        rv = false;
+        ast_delete(*out);
+        *out = NULL;
+    }
+    else {
+        vec_pop(parser->breaks);
+    }
+    return rv;
+}
+
+static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression **out)
 {
     ast_expression *operand;
     ast_value      *opval;
@@ -2421,26 +2787,21 @@ static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **ou
     ast_switch_case swcase;
 
     int  cvq;
-    bool noref, noreturn, is_static;
+    bool noref, is_static;
+    uint32_t qflags = 0;
 
     lex_ctx ctx = parser_ctx(parser);
 
     (void)block; /* not touching */
     (void)opval;
 
-    /* parse over the opening paren */
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected switch operand in parenthesis");
-        return false;
-    }
-
     /* parse into the expression */
     if (!parser_next(parser)) {
         parseerror(parser, "expected switch operand");
         return false;
     }
     /* parse the operand */
-    operand = parse_expression_leave(parser, false);
+    operand = parse_expression_leave(parser, false, false);
     if (!operand)
         return false;
 
@@ -2473,19 +2834,19 @@ static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **ou
         if (parser->tok == TOKEN_IDENT)
             typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
         if (typevar || parser->tok == TOKEN_TYPENAME) {
-            if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, false)) {
+            if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL)) {
                 ast_delete(switchnode);
                 return false;
             }
             continue;
         }
-        if (parse_var_qualifiers(parser, true, &cvq, &noref, &noreturn, &is_static))
+        if (parse_qualifiers(parser, true, &cvq, &noref, &is_static, &qflags, NULL))
         {
             if (cvq == CV_WRONG) {
                 ast_delete(switchnode);
                 return false;
             }
-            if (!parse_variable(parser, block, false, cvq, NULL, noref, noreturn, is_static)) {
+            if (!parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, NULL)) {
                 ast_delete(switchnode);
                 return false;
             }
@@ -2504,7 +2865,7 @@ static bool parse_switch(parser_t *parser, ast_block *block, ast_expression **ou
                 parseerror(parser, "expected expression for case");
                 return false;
             }
-            swcase.value = parse_expression_leave(parser, false);
+            swcase.value = parse_expression_leave(parser, false, false);
             if (!swcase.value) {
                 ast_delete(switchnode);
                 parseerror(parser, "expected expression for case");
@@ -2700,9 +3061,11 @@ static bool parse_pragma(parser_t *parser)
 
 static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
 {
-    bool       noref, noreturn, is_static;
-    int        cvq = CV_NONE;
+    bool       noref, is_static;
+    int        cvq     = CV_NONE;
+    uint32_t   qflags  = 0;
     ast_value *typevar = NULL;
+    char      *vstring = NULL;
 
     *out = NULL;
 
@@ -2720,15 +3083,15 @@ static bool parse_statement(parser_t *parser, ast_block *block, ast_expression *
             if (parsewarning(parser, WARN_EXTENSIONS, "missing 'local' keyword when declaring a local variable"))
                 return false;
         }
-        if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, false))
+        if (!parse_variable(parser, block, false, CV_NONE, typevar, false, false, 0, NULL))
             return false;
         return true;
     }
-    else if (parse_var_qualifiers(parser, !!block, &cvq, &noref, &noreturn, &is_static))
+    else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring))
     {
         if (cvq == CV_WRONG)
             return false;
-        return parse_variable(parser, block, true, cvq, NULL, noref, noreturn, is_static);
+        return parse_variable(parser, block, true, cvq, NULL, noref, is_static, qflags, vstring);
     }
     else if (parser->tok == TOKEN_KEYWORD)
     {
@@ -3035,7 +3398,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
         if (!parser_next(parser))
             return false;
 
-        framenum = parse_expression_leave(parser, true);
+        framenum = parse_expression_leave(parser, true, false);
         if (!framenum) {
             parseerror(parser, "expected a framenumber constant in[frame,think] notation");
             return false;
@@ -3083,7 +3446,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
             nextthink = (ast_expression*)thinkfunc;
 
         } else {
-            nextthink = parse_expression_leave(parser, true);
+            nextthink = parse_expression_leave(parser, true, false);
             if (!nextthink) {
                 ast_unref(framenum);
                 parseerror(parser, "expected a think-function in [frame,think] notation");
@@ -3749,7 +4112,7 @@ static ast_value *parse_arraysize(parser_t *parser, ast_value *var)
         return NULL;
     }
 
-    cexp = parse_expression_leave(parser, true);
+    cexp = parse_expression_leave(parser, true, false);
 
     if (!cexp || !ast_istype(cexp, ast_value)) {
         if (cexp)
@@ -3984,7 +4347,45 @@ static bool parse_typedef(parser_t *parser)
     return true;
 }
 
-static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool noreturn, bool is_static)
+static const char *cvq_to_str(int cvq) {
+    switch (cvq) {
+        case CV_NONE:  return "none";
+        case CV_VAR:   return "`var`";
+        case CV_CONST: return "`const`";
+        default:       return "<INVALID>";
+    }
+}
+
+static bool parser_check_qualifiers(parser_t *parser, const ast_value *var, const ast_value *proto)
+{
+    bool av, ao;
+    if (proto->cvq != var->cvq) {
+        if (!(proto->cvq == CV_CONST && var->cvq == CV_NONE &&
+              !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
+              parser->tok == '='))
+        {
+            return !parsewarning(parser, WARN_DIFFERENT_QUALIFIERS,
+                                 "`%s` declared with different qualifiers: %s\n"
+                                 " -> previous declaration here: %s:%i uses %s",
+                                 var->name, cvq_to_str(var->cvq),
+                                 ast_ctx(proto).file, ast_ctx(proto).line,
+                                 cvq_to_str(proto->cvq));
+        }
+    }
+    av = (var  ->expression.flags & AST_FLAG_NORETURN);
+    ao = (proto->expression.flags & AST_FLAG_NORETURN);
+    if (!av != !ao) {
+        return !parsewarning(parser, WARN_DIFFERENT_ATTRIBUTES,
+                             "`%s` declared with different attributes%s\n"
+                             " -> previous declaration here: %s:%i",
+                             var->name, (av ? ": noreturn" : ""),
+                             ast_ctx(proto).file, ast_ctx(proto).line,
+                             (ao ? ": noreturn" : ""));
+    }
+    return true;
+}
+
+static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofields, int qualifier, ast_value *cached_typedef, bool noref, bool is_static, uint32_t qflags, char *vstring)
 {
     ast_value *var;
     ast_value *proto;
@@ -4051,17 +4452,22 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
         }
 
         var->cvq = qualifier;
-        /* in a noref section we simply bump the usecount */
-        if (noref || parser->noref)
-            var->uses++;
-        if (noreturn)
-            var->expression.flags |= AST_FLAG_NORETURN;
+        var->expression.flags |= qflags;
+        if (var->expression.flags & AST_FLAG_DEPRECATED)
+            var->desc = vstring;
 
         /* Part 1:
          * check for validity: (end_sys_..., multiple-definitions, prototypes, ...)
          * Also: if there was a prototype, `var` will be deleted and set to `proto` which
          * is then filled with the previous definition and the parameter-names replaced.
          */
+        if (!strcmp(var->name, "nil")) {
+            if (OPTS_FLAG(UNTYPED_NIL)) {
+                if (!localblock || !OPTS_FLAG(PERMISSIVE))
+                    parseerror(parser, "name `nil` not allowed (try -fpermissive)");
+            } else
+                (void)!parsewarning(parser, WARN_RESERVED_NAMES, "variable name `nil` is reserved");
+        }
         if (!localblock) {
             /* Deal with end_sys_ vars */
             was_end = false;
@@ -4129,6 +4535,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                         goto cleanup;
                     }
                     proto = (ast_value*)old;
+                    proto->desc = var->desc;
                     if (!ast_compare_type((ast_expression*)proto, (ast_expression*)var)) {
                         parseerror(parser, "conflicting types for `%s`, previous declaration was here: %s:%i",
                                    proto->name,
@@ -4139,6 +4546,14 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                     /* we need the new parameter-names */
                     for (i = 0; i < vec_size(proto->expression.params); ++i)
                         ast_value_set_name(proto->expression.params[i], var->expression.params[i]->name);
+                    if (!parser_check_qualifiers(parser, var, proto)) {
+                        retval = false;
+                        if (proto->desc) 
+                            mem_d(proto->desc);
+                        proto = NULL;
+                        goto cleanup;
+                    }
+                    proto->expression.flags |= var->expression.flags;
                     ast_delete(var);
                     var = proto;
                 }
@@ -4146,29 +4561,28 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                 {
                     /* other globals */
                     if (old) {
-                        if (opts.standard == COMPILER_GMQCC) {
-                            parseerror(parser, "global `%s` already declared here: %s:%i",
-                                       var->name, ast_ctx(old).file, ast_ctx(old).line);
+                        if (parsewarning(parser, WARN_DOUBLE_DECLARATION,
+                                         "global `%s` already declared here: %s:%i",
+                                         var->name, ast_ctx(old).file, ast_ctx(old).line))
+                        {
                             retval = false;
                             goto cleanup;
-                        } else {
-                            if (parsewarning(parser, WARN_DOUBLE_DECLARATION,
-                                             "global `%s` already declared here: %s:%i",
-                                             var->name, ast_ctx(old).file, ast_ctx(old).line))
-                            {
-                                retval = false;
-                                goto cleanup;
-                            }
-                            proto = (ast_value*)old;
-                            if (!ast_istype(old, ast_value)) {
-                                parseerror(parser, "internal error: not an ast_value");
-                                retval = false;
-                                proto = NULL;
-                                goto cleanup;
-                            }
-                            ast_delete(var);
-                            var = proto;
                         }
+                        proto = (ast_value*)old;
+                        if (!ast_istype(old, ast_value)) {
+                            parseerror(parser, "internal error: not an ast_value");
+                            retval = false;
+                            proto = NULL;
+                            goto cleanup;
+                        }
+                        if (!parser_check_qualifiers(parser, var, proto)) {
+                            retval = false;
+                            proto = NULL;
+                            goto cleanup;
+                        }
+                        proto->expression.flags |= var->expression.flags;
+                        ast_delete(var);
+                        var = proto;
                     }
                     if (opts.standard == COMPILER_QCC &&
                         (old = parser_find_field(parser, var->name)))
@@ -4209,6 +4623,10 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
             }
         }
 
+        /* in a noref section we simply bump the usecount */
+        if (noref || parser->noref)
+            var->uses++;
+
         /* Part 2:
          * Create the global/local, and deal with vector types.
          */
@@ -4460,33 +4878,41 @@ skipvar:
             ast_expression *cexp;
             ast_value      *cval;
 
-            cexp = parse_expression_leave(parser, true);
+            cexp = parse_expression_leave(parser, true, false);
             if (!cexp)
                 break;
 
             if (!localblock) {
                 cval = (ast_value*)cexp;
-                if (!ast_istype(cval, ast_value) || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
+                if (cval != parser->nil &&
+                    (!ast_istype(cval, ast_value) || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
+                   )
+                {
                     parseerror(parser, "cannot initialize a global constant variable with a non-constant expression");
+                }
                 else
                 {
-                    if (opts.standard != COMPILER_GMQCC &&
-                        !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
+                    if (!OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
                         qualifier != CV_VAR)
                     {
                         var->cvq = CV_CONST;
                     }
-                    var->hasvalue = true;
-                    if (cval->expression.vtype == TYPE_STRING)
-                        var->constval.vstring = parser_strdup(cval->constval.vstring);
-                    else if (cval->expression.vtype == TYPE_FIELD)
-                        var->constval.vfield = cval;
+                    if (cval == parser->nil)
+                        var->expression.flags |= AST_FLAG_INITIALIZED;
                     else
-                        memcpy(&var->constval, &cval->constval, sizeof(var->constval));
-                    ast_unref(cval);
+                    {
+                        var->hasvalue = true;
+                        if (cval->expression.vtype == TYPE_STRING)
+                            var->constval.vstring = parser_strdup(cval->constval.vstring);
+                        else if (cval->expression.vtype == TYPE_FIELD)
+                            var->constval.vfield = cval;
+                        else
+                            memcpy(&var->constval, &cval->constval, sizeof(var->constval));
+                        ast_unref(cval);
+                    }
                 }
             } else {
-                bool cvq;
+                int cvq;
                 shunt sy = { NULL, NULL };
                 cvq = var->cvq;
                 var->cvq = CV_NONE;
@@ -4561,22 +4987,23 @@ static bool parser_global_statement(parser_t *parser)
 {
     int        cvq       = CV_WRONG;
     bool       noref     = false;
-    bool       noreturn  = false;
     bool       is_static = false;
+    uint32_t   qflags    = 0;
     ast_value *istype    = NULL;
+    char      *vstring   = NULL;
 
     if (parser->tok == TOKEN_IDENT)
         istype = parser_find_typedef(parser, parser_tokval(parser), 0);
 
     if (istype || parser->tok == TOKEN_TYPENAME || parser->tok == '.')
     {
-        return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, false);
+        return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL);
     }
-    else if (parse_var_qualifiers(parser, false, &cvq, &noref, &noreturn, &is_static))
+    else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring))
     {
         if (cvq == CV_WRONG)
             return false;
-        return parse_variable(parser, NULL, true, cvq, NULL, noref, noreturn, is_static);
+        return parse_variable(parser, NULL, true, cvq, NULL, noref, is_static, qflags, vstring);
     }
     else if (parser->tok == TOKEN_KEYWORD)
     {
@@ -4689,6 +5116,7 @@ static parser_t *parser;
 
 bool parser_init()
 {
+    lex_ctx empty_ctx;
     size_t i;
 
     parser = (parser_t*)mem_a(sizeof(parser_t));
@@ -4713,6 +5141,13 @@ bool parser_init()
     vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE));
     vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
     vec_push(parser->_blocktypedefs, 0);
+
+    empty_ctx.file = "<internal>";
+    empty_ctx.line = 0;
+    parser->nil = ast_value_new(empty_ctx, "nil", TYPE_NIL);
+    parser->nil->cvq = CV_CONST;
+    if (OPTS_FLAG(UNTYPED_NIL))
+        util_htset(parser->htglobals, "nil", (void*)parser->nil);
     return true;
 }
 
@@ -4820,6 +5255,10 @@ void parser_cleanup()
 
     vec_free(parser->labels);
     vec_free(parser->gotos);
+    vec_free(parser->breaks);
+    vec_free(parser->continues);
+
+    ast_value_delete(parser->nil);
 
     mem_d(parser);
 }
@@ -4908,6 +5347,17 @@ bool parser_finish(const char *output)
         if (!ast_istype(parser->globals[i], ast_value))
             continue;
         asvalue = (ast_value*)(parser->globals[i]);
+        if (!(asvalue->expression.flags & AST_FLAG_INITIALIZED))
+        {
+            if (asvalue->cvq == CV_CONST && !asvalue->hasvalue)
+                (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_CONSTANT,
+                                       "uninitialized constant: `%s`",
+                                       asvalue->name);
+            else if ((asvalue->cvq == CV_NONE || asvalue->cvq == CV_CONST) && !asvalue->hasvalue)
+                (void)!compile_warning(ast_ctx(asvalue), WARN_UNINITIALIZED_GLOBAL,
+                                       "uninitialized global: `%s`",
+                                       asvalue->name);
+        }
         if (!ast_generate_accessors(asvalue, ir)) {
             ir_builder_delete(ir);
             return false;