]> git.rm.cloudns.org Git - xonotic/gmqcc.git/commitdiff
.c -> .cpp
authorDale Weiler <weilercdale@gmail.com>
Thu, 15 Jan 2015 02:48:47 +0000 (21:48 -0500)
committerDale Weiler <weilercdale@gmail.com>
Thu, 15 Jan 2015 02:48:47 +0000 (21:48 -0500)
33 files changed:
Makefile
ast.c [deleted file]
ast.cpp [new file with mode: 0644]
code.c [deleted file]
code.cpp [new file with mode: 0644]
conout.c [deleted file]
conout.cpp [new file with mode: 0644]
exec.c [deleted file]
exec.cpp [new file with mode: 0644]
fold.c [deleted file]
fold.cpp [new file with mode: 0644]
ftepp.c [deleted file]
ftepp.cpp [new file with mode: 0644]
intrin.c [deleted file]
intrin.cpp [new file with mode: 0644]
ir.c [deleted file]
ir.cpp [new file with mode: 0644]
lexer.c [deleted file]
lexer.cpp [new file with mode: 0644]
main.c [deleted file]
main.cpp [new file with mode: 0644]
opts.c [deleted file]
opts.cpp [new file with mode: 0644]
parser.c [deleted file]
parser.cpp [new file with mode: 0644]
stat.c [deleted file]
stat.cpp [new file with mode: 0644]
test.c [deleted file]
test.cpp [new file with mode: 0644]
utf8.c [deleted file]
utf8.cpp [new file with mode: 0644]
util.c [deleted file]
util.cpp [new file with mode: 0644]

index 91bedc6cfa820264629bfd81e38917c991a8d99e..89d88053c460223bcb4c14f590909478190e5726 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,18 +1,48 @@
-CC ?= clang
-CFLAGS = -MD -std=gnu99 -Wall -Wextra -pedantic-errors -g3
-LDFLAGS = -lm
+CXX ?= clang++
+CXXFLAGS = \
+       -std=c++11 \
+       -Wall \
+       -Wextra \
+       -ffast-math \
+       -fno-exceptions \
+       -fno-rtti \
+       -MD
 
-CSRCS = ast.c code.c conout.c fold.c ftepp.c intrin.c ir.c lexer.c main.c opts.c parser.c stat.c utf8.c util.c
-TSRCS = conout.c opts.c stat.c test.c util.c
-VSRCS = exec.c stat.c util.c
+CSRCS = \
+       ast.cpp \
+       code.cpp \
+       conout.cpp \
+       fold.cpp \
+       ftepp.cpp \
+       intrin.cpp \
+       ir.cpp \
+       lexer.cpp \
+       main.cpp \
+       opts.cpp \
+       parser.cpp \
+       stat.cpp \
+       utf8.cpp \
+       util.cpp
 
-COBJS = $(CSRCS:.c=.o)
-TOBJS = $(TSRCS:.c=.o)
-VOBJS = $(VSRCS:.c=.o)
+TSRCS = \
+       conout.cpp \
+       opts.cpp \
+       stat.cpp \
+       test.cpp \
+       util.cpp
 
-CDEPS = $(CSRCS:.c=.d)
-TDEPS = $(TSRCS:.c=.d)
-VDEPS = $(VSRCS:.c=.d)
+VSRCS = \
+       exec.cpp \
+       stat.cpp \
+       util.cpp
+
+COBJS = $(CSRCS:.cpp=.o)
+TOBJS = $(TSRCS:.cpp=.o)
+VOBJS = $(VSRCS:.cpp=.o)
+
+CDEPS = $(CSRCS:.cpp=.d)
+TDEPS = $(TSRCS:.cpp=.d)
+VDEPS = $(VSRCS:.cpp=.d)
 
 CBIN = gmqcc
 TBIN = testsuite
@@ -21,16 +51,16 @@ VBIN = qcvm
 all: $(CBIN) $(TBIN) $(VBIN)
 
 $(CBIN): $(COBJS)
-       $(CC) $(COBJS) $(LDFLAGS) -o $@
+       $(CXX) $(COBJS) -o $@
 
 $(TBIN): $(TOBJS)
-       $(CC) $(TOBJS) $(LDFLAGS) -o $@
+       $(CXX) $(TOBJS) -o $@
 
 $(VBIN): $(VOBJS)
-       $(CC) $(VOBJS) $(LDFLAGS) -o $@
+       $(CXX) $(VOBJS) -o $@
 
-.c.o:
-       $(CC) -c $(CFLAGS) $< -o $@
+.cpp.o:
+       $(CXX) -c $(CXXFLAGS) $< -o $@
 
 test: $(CBIN) $(TBIN) $(VBIN)
        @./$(TBIN)
diff --git a/ast.c b/ast.c
deleted file mode 100644 (file)
index 29e298a..0000000
--- a/ast.c
+++ /dev/null
@@ -1,3463 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "ast.h"
-#include "parser.h"
-
-#define ast_instantiate(T, ctx, destroyfn)                          \
-    T* self = (T*)mem_a(sizeof(T));                                 \
-    if (!self) {                                                    \
-        return NULL;                                                \
-    }                                                               \
-    ast_node_init((ast_node*)self, ctx, TYPE_##T);                  \
-    ( (ast_node*)self )->destroy = (ast_node_delete*)destroyfn
-
-/*
- * forward declarations, these need not be in ast.h for obvious
- * static reasons.
- */
-static bool ast_member_codegen(ast_member*, ast_function*, bool lvalue, ir_value**);
-static void ast_array_index_delete(ast_array_index*);
-static bool ast_array_index_codegen(ast_array_index*, ast_function*, bool lvalue, ir_value**);
-static void ast_argpipe_delete(ast_argpipe*);
-static bool ast_argpipe_codegen(ast_argpipe*, ast_function*, bool lvalue, ir_value**);
-static void ast_store_delete(ast_store*);
-static bool ast_store_codegen(ast_store*, ast_function*, bool lvalue, ir_value**);
-static void ast_ifthen_delete(ast_ifthen*);
-static bool ast_ifthen_codegen(ast_ifthen*, ast_function*, bool lvalue, ir_value**);
-static void ast_ternary_delete(ast_ternary*);
-static bool ast_ternary_codegen(ast_ternary*, ast_function*, bool lvalue, ir_value**);
-static void ast_loop_delete(ast_loop*);
-static bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**);
-static void ast_breakcont_delete(ast_breakcont*);
-static bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**);
-static void ast_switch_delete(ast_switch*);
-static bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**);
-static void ast_label_delete(ast_label*);
-static void ast_label_register_goto(ast_label*, ast_goto*);
-static bool ast_label_codegen(ast_label*, ast_function*, bool lvalue, ir_value**);
-static bool ast_goto_codegen(ast_goto*, ast_function*, bool lvalue, ir_value**);
-static void ast_goto_delete(ast_goto*);
-static void ast_call_delete(ast_call*);
-static bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**);
-static bool ast_block_codegen(ast_block*, ast_function*, bool lvalue, ir_value**);
-static void ast_unary_delete(ast_unary*);
-static bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**);
-static void ast_entfield_delete(ast_entfield*);
-static bool ast_entfield_codegen(ast_entfield*, ast_function*, bool lvalue, ir_value**);
-static void ast_return_delete(ast_return*);
-static bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**);
-static void ast_binstore_delete(ast_binstore*);
-static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
-static void ast_binary_delete(ast_binary*);
-static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
-static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**);
-
-/* It must not be possible to get here. */
-static GMQCC_NORETURN void _ast_node_destroy(ast_node *self)
-{
-    (void)self;
-    con_err("ast node missing destroy()\n");
-    exit(EXIT_FAILURE);
-}
-
-/* Initialize main ast node aprts */
-static void ast_node_init(ast_node *self, lex_ctx_t ctx, int nodetype)
-{
-    self->context = ctx;
-    self->destroy = &_ast_node_destroy;
-    self->keep    = false;
-    self->nodetype = nodetype;
-    self->side_effects = false;
-}
-
-/* weight and side effects */
-static void _ast_propagate_effects(ast_node *self, ast_node *other)
-{
-    if (ast_side_effects(other))
-        ast_side_effects(self) = true;
-}
-#define ast_propagate_effects(s,o) _ast_propagate_effects(((ast_node*)(s)), ((ast_node*)(o)))
-
-/* General expression initialization */
-static void ast_expression_init(ast_expression *self,
-                                ast_expression_codegen *codegen)
-{
-    self->codegen  = codegen;
-    self->vtype    = TYPE_VOID;
-    self->next     = NULL;
-    self->outl     = NULL;
-    self->outr     = NULL;
-    self->params   = NULL;
-    self->count    = 0;
-    self->varparam = NULL;
-    self->flags    = 0;
-    if (OPTS_OPTION_BOOL(OPTION_COVERAGE))
-        self->flags |= AST_FLAG_BLOCK_COVERAGE;
-}
-
-static void ast_expression_delete(ast_expression *self)
-{
-    size_t i;
-    if (self->next)
-        ast_delete(self->next);
-    for (i = 0; i < vec_size(self->params); ++i) {
-        ast_delete(self->params[i]);
-    }
-    vec_free(self->params);
-    if (self->varparam)
-        ast_delete(self->varparam);
-}
-
-static void ast_expression_delete_full(ast_expression *self)
-{
-    ast_expression_delete(self);
-    mem_d(self);
-}
-
-ast_value* ast_value_copy(const ast_value *self)
-{
-    size_t i;
-    const ast_expression *fromex;
-    ast_expression       *selfex;
-    ast_value *cp = ast_value_new(self->expression.node.context, self->name, self->expression.vtype);
-    if (self->expression.next) {
-        cp->expression.next = ast_type_copy(self->expression.node.context, self->expression.next);
-    }
-    fromex   = &self->expression;
-    selfex = &cp->expression;
-    selfex->count    = fromex->count;
-    selfex->flags    = fromex->flags;
-    for (i = 0; i < vec_size(fromex->params); ++i) {
-        ast_value *v = ast_value_copy(fromex->params[i]);
-        vec_push(selfex->params, v);
-    }
-    return cp;
-}
-
-void ast_type_adopt_impl(ast_expression *self, const ast_expression *other)
-{
-    size_t i;
-    const ast_expression *fromex;
-    ast_expression       *selfex;
-    self->vtype = other->vtype;
-    if (other->next) {
-        self->next = (ast_expression*)ast_type_copy(ast_ctx(self), other->next);
-    }
-    fromex = other;
-    selfex = self;
-    selfex->count    = fromex->count;
-    selfex->flags    = fromex->flags;
-    for (i = 0; i < vec_size(fromex->params); ++i) {
-        ast_value *v = ast_value_copy(fromex->params[i]);
-        vec_push(selfex->params, v);
-    }
-}
-
-static ast_expression* ast_shallow_type(lex_ctx_t ctx, int vtype)
-{
-    ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
-    ast_expression_init(self, NULL);
-    self->codegen = NULL;
-    self->next    = NULL;
-    self->vtype   = vtype;
-    return self;
-}
-
-ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex)
-{
-    size_t i;
-    const ast_expression *fromex;
-    ast_expression       *selfex;
-
-    if (!ex)
-        return NULL;
-    else
-    {
-        ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
-        ast_expression_init(self, NULL);
-
-        fromex = ex;
-        selfex = self;
-
-        /* This may never be codegen()d */
-        selfex->codegen = NULL;
-
-        selfex->vtype = fromex->vtype;
-        if (fromex->next)
-            selfex->next = ast_type_copy(ctx, fromex->next);
-        else
-            selfex->next = NULL;
-
-        selfex->count    = fromex->count;
-        selfex->flags    = fromex->flags;
-        for (i = 0; i < vec_size(fromex->params); ++i) {
-            ast_value *v = ast_value_copy(fromex->params[i]);
-            vec_push(selfex->params, v);
-        }
-
-        return self;
-    }
-}
-
-bool ast_compare_type(ast_expression *a, ast_expression *b)
-{
-    if (a->vtype == TYPE_NIL ||
-        b->vtype == TYPE_NIL)
-        return true;
-    if (a->vtype != b->vtype)
-        return false;
-    if (!a->next != !b->next)
-        return false;
-    if (vec_size(a->params) != vec_size(b->params))
-        return false;
-    if ((a->flags & AST_FLAG_TYPE_MASK) !=
-        (b->flags & AST_FLAG_TYPE_MASK) )
-    {
-        return false;
-    }
-    if (vec_size(a->params)) {
-        size_t i;
-        for (i = 0; i < vec_size(a->params); ++i) {
-            if (!ast_compare_type((ast_expression*)a->params[i],
-                                  (ast_expression*)b->params[i]))
-                return false;
-        }
-    }
-    if (a->next)
-        return ast_compare_type(a->next, b->next);
-    return true;
-}
-
-static size_t ast_type_to_string_impl(ast_expression *e, char *buf, size_t bufsize, size_t pos)
-{
-    const char *typestr;
-    size_t typelen;
-    size_t i;
-
-    if (!e) {
-        if (pos + 6 >= bufsize)
-            goto full;
-        util_strncpy(buf + pos, "(null)", 6);
-        return pos + 6;
-    }
-
-    if (pos + 1 >= bufsize)
-        goto full;
-
-    switch (e->vtype) {
-        case TYPE_VARIANT:
-            util_strncpy(buf + pos, "(variant)", 9);
-            return pos + 9;
-
-        case TYPE_FIELD:
-            buf[pos++] = '.';
-            return ast_type_to_string_impl(e->next, buf, bufsize, pos);
-
-        case TYPE_POINTER:
-            if (pos + 3 >= bufsize)
-                goto full;
-            buf[pos++] = '*';
-            buf[pos++] = '(';
-            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = ')';
-            return pos;
-
-        case TYPE_FUNCTION:
-            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
-            if (pos + 2 >= bufsize)
-                goto full;
-            if (!vec_size(e->params)) {
-                buf[pos++] = '(';
-                buf[pos++] = ')';
-                return pos;
-            }
-            buf[pos++] = '(';
-            pos = ast_type_to_string_impl((ast_expression*)(e->params[0]), buf, bufsize, pos);
-            for (i = 1; i < vec_size(e->params); ++i) {
-                if (pos + 2 >= bufsize)
-                    goto full;
-                buf[pos++] = ',';
-                buf[pos++] = ' ';
-                pos = ast_type_to_string_impl((ast_expression*)(e->params[i]), buf, bufsize, pos);
-            }
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = ')';
-            return pos;
-
-        case TYPE_ARRAY:
-            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = '[';
-            pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->count);
-            if (pos + 1 >= bufsize)
-                goto full;
-            buf[pos++] = ']';
-            return pos;
-
-        default:
-            typestr = type_name[e->vtype];
-            typelen = strlen(typestr);
-            if (pos + typelen >= bufsize)
-                goto full;
-            util_strncpy(buf + pos, typestr, typelen);
-            return pos + typelen;
-    }
-
-full:
-    buf[bufsize-3] = '.';
-    buf[bufsize-2] = '.';
-    buf[bufsize-1] = '.';
-    return bufsize;
-}
-
-void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize)
-{
-    size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0);
-    buf[pos] = 0;
-}
-
-static bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out);
-ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t)
-{
-    ast_instantiate(ast_value, ctx, ast_value_delete);
-    ast_expression_init((ast_expression*)self,
-                        (ast_expression_codegen*)&ast_value_codegen);
-    self->expression.node.keep = true; /* keep */
-
-    self->name = name ? util_strdup(name) : NULL;
-    self->expression.vtype = t;
-    self->expression.next  = NULL;
-    self->isfield  = false;
-    self->cvq      = CV_NONE;
-    self->hasvalue = false;
-    self->isimm    = false;
-    self->inexact  = false;
-    self->uses     = 0;
-    memset(&self->constval, 0, sizeof(self->constval));
-    self->initlist = NULL;
-
-    self->ir_v           = NULL;
-    self->ir_values      = NULL;
-    self->ir_value_count = 0;
-
-    self->setter = NULL;
-    self->getter = NULL;
-    self->desc   = NULL;
-
-    self->argcounter = NULL;
-    self->intrinsic = false;
-
-    return self;
-}
-
-void ast_value_delete(ast_value* self)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    if (self->argcounter)
-        mem_d((void*)self->argcounter);
-    if (self->hasvalue) {
-        switch (self->expression.vtype)
-        {
-        case TYPE_STRING:
-            mem_d((void*)self->constval.vstring);
-            break;
-        case TYPE_FUNCTION:
-            /* unlink us from the function node */
-            self->constval.vfunc->vtype = NULL;
-            break;
-        /* NOTE: delete function? currently collected in
-         * the parser structure
-         */
-        default:
-            break;
-        }
-    }
-    if (self->ir_values)
-        mem_d(self->ir_values);
-
-    if (self->desc)
-        mem_d(self->desc);
-
-    if (self->initlist) {
-        if (self->expression.next->vtype == TYPE_STRING) {
-            /* strings are allocated, free them */
-            size_t i, len = vec_size(self->initlist);
-            /* in theory, len should be expression.count
-             * but let's not take any chances */
-            for (i = 0; i < len; ++i) {
-                if (self->initlist[i].vstring)
-                    mem_d(self->initlist[i].vstring);
-            }
-        }
-        vec_free(self->initlist);
-    }
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-void ast_value_params_add(ast_value *self, ast_value *p)
-{
-    vec_push(self->expression.params, p);
-}
-
-bool ast_value_set_name(ast_value *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-ast_binary* ast_binary_new(lex_ctx_t ctx, int op,
-                           ast_expression* left, ast_expression* right)
-{
-    ast_instantiate(ast_binary, ctx, ast_binary_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binary_codegen);
-
-    if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
-        ast_unary      *unary  = ((ast_unary*)right);
-        ast_expression *normal = unary->operand;
-
-        /* make a-(-b) => a + b */
-        if (unary->op == VINSTR_NEG_F || unary->op == VINSTR_NEG_V) {
-            if (op == INSTR_SUB_F) {
-                op = INSTR_ADD_F;
-                right = normal;
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-            } else if (op == INSTR_SUB_V) {
-                op = INSTR_ADD_V;
-                right = normal;
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-            }
-        }
-    }
-
-    self->op = op;
-    self->left = left;
-    self->right = right;
-    self->right_first = false;
-
-    ast_propagate_effects(self, left);
-    ast_propagate_effects(self, right);
-
-    if (op >= INSTR_EQ_F && op <= INSTR_GT)
-        self->expression.vtype = TYPE_FLOAT;
-    else if (op == INSTR_AND || op == INSTR_OR) {
-        if (OPTS_FLAG(PERL_LOGIC))
-            ast_type_adopt(self, right);
-        else
-            self->expression.vtype = TYPE_FLOAT;
-    }
-    else if (op == INSTR_BITAND || op == INSTR_BITOR)
-        self->expression.vtype = TYPE_FLOAT;
-    else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV)
-        self->expression.vtype = TYPE_VECTOR;
-    else if (op == INSTR_MUL_V)
-        self->expression.vtype = TYPE_FLOAT;
-    else
-        self->expression.vtype = left->vtype;
-
-    /* references all */
-    self->refs = AST_REF_ALL;
-
-    return self;
-}
-
-void ast_binary_delete(ast_binary *self)
-{
-    if (self->refs & AST_REF_LEFT)  ast_unref(self->left);
-    if (self->refs & AST_REF_RIGHT) ast_unref(self->right);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_binstore* ast_binstore_new(lex_ctx_t ctx, int storop, int op,
-                               ast_expression* left, ast_expression* right)
-{
-    ast_instantiate(ast_binstore, ctx, ast_binstore_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binstore_codegen);
-
-    ast_side_effects(self) = true;
-
-    self->opstore = storop;
-    self->opbin   = op;
-    self->dest    = left;
-    self->source  = right;
-
-    self->keep_dest = false;
-
-    ast_type_adopt(self, left);
-    return self;
-}
-
-void ast_binstore_delete(ast_binstore *self)
-{
-    if (!self->keep_dest)
-        ast_unref(self->dest);
-    ast_unref(self->source);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_unary* ast_unary_new(lex_ctx_t ctx, int op,
-                         ast_expression *expr)
-{
-    ast_instantiate(ast_unary, ctx, ast_unary_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_unary_codegen);
-
-    self->op      = op;
-    self->operand = expr;
-
-
-    if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
-        ast_unary *prev = (ast_unary*)((ast_unary*)expr)->operand;
-
-        /* Handle for double negation */
-        if (((ast_unary*)expr)->op == op)
-            prev = (ast_unary*)((ast_unary*)expr)->operand;
-
-        if (ast_istype(prev, ast_unary)) {
-            ast_expression_delete((ast_expression*)self);
-            mem_d(self);
-            ++opts_optimizationcount[OPTIM_PEEPHOLE];
-            return prev;
-        }
-    }
-
-    ast_propagate_effects(self, expr);
-
-    if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) {
-        self->expression.vtype = TYPE_FLOAT;
-    } else if (op == VINSTR_NEG_V) {
-        self->expression.vtype = TYPE_VECTOR;
-    } else {
-        compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]);
-    }
-
-    return self;
-}
-
-void ast_unary_delete(ast_unary *self)
-{
-    if (self->operand) ast_unref(self->operand);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_return* ast_return_new(lex_ctx_t ctx, ast_expression *expr)
-{
-    ast_instantiate(ast_return, ctx, ast_return_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_return_codegen);
-
-    self->operand = expr;
-
-    if (expr)
-        ast_propagate_effects(self, expr);
-
-    return self;
-}
-
-void ast_return_delete(ast_return *self)
-{
-    if (self->operand)
-        ast_unref(self->operand);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field)
-{
-    if (field->vtype != TYPE_FIELD) {
-        compile_error(ctx, "ast_entfield_new with expression not of type field");
-        return NULL;
-    }
-    return ast_entfield_new_force(ctx, entity, field, field->next);
-}
-
-ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype)
-{
-    ast_instantiate(ast_entfield, ctx, ast_entfield_delete);
-
-    if (!outtype) {
-        mem_d(self);
-        /* Error: field has no type... */
-        return NULL;
-    }
-
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_entfield_codegen);
-
-    self->entity = entity;
-    self->field  = field;
-    ast_propagate_effects(self, entity);
-    ast_propagate_effects(self, field);
-
-    ast_type_adopt(self, outtype);
-    return self;
-}
-
-void ast_entfield_delete(ast_entfield *self)
-{
-    ast_unref(self->entity);
-    ast_unref(self->field);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name)
-{
-    ast_instantiate(ast_member, ctx, ast_member_delete);
-    if (field >= 3) {
-        mem_d(self);
-        return NULL;
-    }
-
-    if (owner->vtype != TYPE_VECTOR &&
-        owner->vtype != TYPE_FIELD) {
-        compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->vtype]);
-        mem_d(self);
-        return NULL;
-    }
-
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_member_codegen);
-    self->expression.node.keep = true; /* keep */
-
-    if (owner->vtype == TYPE_VECTOR) {
-        self->expression.vtype = TYPE_FLOAT;
-        self->expression.next  = NULL;
-    } else {
-        self->expression.vtype = TYPE_FIELD;
-        self->expression.next = ast_shallow_type(ctx, TYPE_FLOAT);
-    }
-
-    self->rvalue = false;
-    self->owner  = owner;
-    ast_propagate_effects(self, owner);
-
-    self->field = field;
-    if (name)
-        self->name = util_strdup(name);
-    else
-        self->name = NULL;
-
-    return self;
-}
-
-void ast_member_delete(ast_member *self)
-{
-    /* The owner is always an ast_value, which has .keep=true,
-     * also: ast_members are usually deleted after the owner, thus
-     * this will cause invalid access
-    ast_unref(self->owner);
-     * once we allow (expression).x to access a vector-member, we need
-     * to change this: preferably by creating an alternate ast node for this
-     * purpose that is not garbage-collected.
-    */
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self->name);
-    mem_d(self);
-}
-
-bool ast_member_set_name(ast_member *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index)
-{
-    ast_expression *outtype;
-    ast_instantiate(ast_array_index, ctx, ast_array_index_delete);
-
-    outtype = array->next;
-    if (!outtype) {
-        mem_d(self);
-        /* Error: field has no type... */
-        return NULL;
-    }
-
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_array_index_codegen);
-
-    self->array = array;
-    self->index = index;
-    ast_propagate_effects(self, array);
-    ast_propagate_effects(self, index);
-
-    ast_type_adopt(self, outtype);
-    if (array->vtype == TYPE_FIELD && outtype->vtype == TYPE_ARRAY) {
-        if (self->expression.vtype != TYPE_ARRAY) {
-            compile_error(ast_ctx(self), "array_index node on type");
-            ast_array_index_delete(self);
-            return NULL;
-        }
-        self->array = outtype;
-        self->expression.vtype = TYPE_FIELD;
-    }
-
-    return self;
-}
-
-void ast_array_index_delete(ast_array_index *self)
-{
-    if (self->array)
-        ast_unref(self->array);
-    if (self->index)
-        ast_unref(self->index);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index)
-{
-    ast_instantiate(ast_argpipe, ctx, ast_argpipe_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_argpipe_codegen);
-    self->index = index;
-    self->expression.vtype = TYPE_NOEXPR;
-    return self;
-}
-
-void ast_argpipe_delete(ast_argpipe *self)
-{
-    if (self->index)
-        ast_unref(self->index);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
-{
-    ast_instantiate(ast_ifthen, ctx, ast_ifthen_delete);
-    if (!ontrue && !onfalse) {
-        /* because it is invalid */
-        mem_d(self);
-        return NULL;
-    }
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ifthen_codegen);
-
-    self->cond     = cond;
-    self->on_true  = ontrue;
-    self->on_false = onfalse;
-    ast_propagate_effects(self, cond);
-    if (ontrue)
-        ast_propagate_effects(self, ontrue);
-    if (onfalse)
-        ast_propagate_effects(self, onfalse);
-
-    return self;
-}
-
-void ast_ifthen_delete(ast_ifthen *self)
-{
-    ast_unref(self->cond);
-    if (self->on_true)
-        ast_unref(self->on_true);
-    if (self->on_false)
-        ast_unref(self->on_false);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
-{
-    ast_expression *exprtype = ontrue;
-    ast_instantiate(ast_ternary, ctx, ast_ternary_delete);
-    /* This time NEITHER must be NULL */
-    if (!ontrue || !onfalse) {
-        mem_d(self);
-        return NULL;
-    }
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ternary_codegen);
-
-    self->cond     = cond;
-    self->on_true  = ontrue;
-    self->on_false = onfalse;
-    ast_propagate_effects(self, cond);
-    ast_propagate_effects(self, ontrue);
-    ast_propagate_effects(self, onfalse);
-
-    if (ontrue->vtype == TYPE_NIL)
-        exprtype = onfalse;
-    ast_type_adopt(self, exprtype);
-
-    return self;
-}
-
-void ast_ternary_delete(ast_ternary *self)
-{
-    /* the if()s are only there because computed-gotos can set them
-     * to NULL
-     */
-    if (self->cond)     ast_unref(self->cond);
-    if (self->on_true)  ast_unref(self->on_true);
-    if (self->on_false) ast_unref(self->on_false);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_loop* ast_loop_new(lex_ctx_t ctx,
-                       ast_expression *initexpr,
-                       ast_expression *precond, bool pre_not,
-                       ast_expression *postcond, bool post_not,
-                       ast_expression *increment,
-                       ast_expression *body)
-{
-    ast_instantiate(ast_loop, ctx, ast_loop_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_loop_codegen);
-
-    self->initexpr  = initexpr;
-    self->precond   = precond;
-    self->postcond  = postcond;
-    self->increment = increment;
-    self->body      = body;
-
-    self->pre_not   = pre_not;
-    self->post_not  = post_not;
-
-    if (initexpr)
-        ast_propagate_effects(self, initexpr);
-    if (precond)
-        ast_propagate_effects(self, precond);
-    if (postcond)
-        ast_propagate_effects(self, postcond);
-    if (increment)
-        ast_propagate_effects(self, increment);
-    if (body)
-        ast_propagate_effects(self, body);
-
-    return self;
-}
-
-void ast_loop_delete(ast_loop *self)
-{
-    if (self->initexpr)
-        ast_unref(self->initexpr);
-    if (self->precond)
-        ast_unref(self->precond);
-    if (self->postcond)
-        ast_unref(self->postcond);
-    if (self->increment)
-        ast_unref(self->increment);
-    if (self->body)
-        ast_unref(self->body);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels)
-{
-    ast_instantiate(ast_breakcont, ctx, ast_breakcont_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_breakcont_codegen);
-
-    self->is_continue = iscont;
-    self->levels      = levels;
-
-    return self;
-}
-
-void ast_breakcont_delete(ast_breakcont *self)
-{
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op)
-{
-    ast_instantiate(ast_switch, ctx, ast_switch_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen);
-
-    self->operand = op;
-    self->cases   = NULL;
-
-    ast_propagate_effects(self, op);
-
-    return self;
-}
-
-void ast_switch_delete(ast_switch *self)
-{
-    size_t i;
-    ast_unref(self->operand);
-
-    for (i = 0; i < vec_size(self->cases); ++i) {
-        if (self->cases[i].value)
-            ast_unref(self->cases[i].value);
-        ast_unref(self->cases[i].code);
-    }
-    vec_free(self->cases);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined)
-{
-    ast_instantiate(ast_label, ctx, ast_label_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen);
-
-    self->expression.vtype = TYPE_NOEXPR;
-
-    self->name      = util_strdup(name);
-    self->irblock   = NULL;
-    self->gotos     = NULL;
-    self->undefined = undefined;
-
-    return self;
-}
-
-void ast_label_delete(ast_label *self)
-{
-    mem_d((void*)self->name);
-    vec_free(self->gotos);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-static void ast_label_register_goto(ast_label *self, ast_goto *g)
-{
-    vec_push(self->gotos, g);
-}
-
-ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name)
-{
-    ast_instantiate(ast_goto, ctx, ast_goto_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_goto_codegen);
-
-    self->name    = util_strdup(name);
-    self->target  = NULL;
-    self->irblock_from = NULL;
-
-    return self;
-}
-
-void ast_goto_delete(ast_goto *self)
-{
-    mem_d((void*)self->name);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-void ast_goto_set_label(ast_goto *self, ast_label *label)
-{
-    self->target = label;
-}
-
-ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think)
-{
-    ast_instantiate(ast_state, ctx, ast_state_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen);
-    self->framenum  = frame;
-    self->nextthink = think;
-    return self;
-}
-
-void ast_state_delete(ast_state *self)
-{
-    if (self->framenum)
-        ast_unref(self->framenum);
-    if (self->nextthink)
-        ast_unref(self->nextthink);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_call* ast_call_new(lex_ctx_t ctx,
-                       ast_expression *funcexpr)
-{
-    ast_instantiate(ast_call, ctx, ast_call_delete);
-    if (!funcexpr->next) {
-        compile_error(ctx, "not a function");
-        mem_d(self);
-        return NULL;
-    }
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen);
-
-    ast_side_effects(self) = true;
-
-    self->params   = NULL;
-    self->func     = funcexpr;
-    self->va_count = NULL;
-
-    ast_type_adopt(self, funcexpr->next);
-
-    return self;
-}
-
-void ast_call_delete(ast_call *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->params); ++i)
-        ast_unref(self->params[i]);
-    vec_free(self->params);
-
-    if (self->func)
-        ast_unref(self->func);
-
-    if (self->va_count)
-        ast_unref(self->va_count);
-
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-static bool ast_call_check_vararg(ast_call *self, ast_expression *va_type, ast_expression *exp_type)
-{
-    char texp[1024];
-    char tgot[1024];
-    if (!exp_type)
-        return true;
-    if (!va_type || !ast_compare_type(va_type, exp_type))
-    {
-        if (va_type && exp_type)
-        {
-            ast_type_to_string(va_type,  tgot, sizeof(tgot));
-            ast_type_to_string(exp_type, texp, sizeof(texp));
-            if (OPTS_FLAG(UNSAFE_VARARGS)) {
-                if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES,
-                                    "piped variadic argument differs in type: constrained to type %s, expected type %s",
-                                    tgot, texp))
-                    return false;
-            } else {
-                compile_error(ast_ctx(self),
-                              "piped variadic argument differs in type: constrained to type %s, expected type %s",
-                              tgot, texp);
-                return false;
-            }
-        }
-        else
-        {
-            ast_type_to_string(exp_type, texp, sizeof(texp));
-            if (OPTS_FLAG(UNSAFE_VARARGS)) {
-                if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES,
-                                    "piped variadic argument may differ in type: expected type %s",
-                                    texp))
-                    return false;
-            } else {
-                compile_error(ast_ctx(self),
-                              "piped variadic argument may differ in type: expected type %s",
-                              texp);
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool ast_call_check_types(ast_call *self, ast_expression *va_type)
-{
-    char texp[1024];
-    char tgot[1024];
-    size_t i;
-    bool   retval = true;
-    const  ast_expression *func = self->func;
-    size_t count = vec_size(self->params);
-    if (count > vec_size(func->params))
-        count = vec_size(func->params);
-
-    for (i = 0; i < count; ++i) {
-        if (ast_istype(self->params[i], ast_argpipe)) {
-            /* warn about type safety instead */
-            if (i+1 != count) {
-                compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call");
-                return false;
-            }
-            if (!ast_call_check_vararg(self, va_type, (ast_expression*)func->params[i]))
-                retval = false;
-        }
-        else if (!ast_compare_type(self->params[i], (ast_expression*)(func->params[i])))
-        {
-            ast_type_to_string(self->params[i], tgot, sizeof(tgot));
-            ast_type_to_string((ast_expression*)func->params[i], texp, sizeof(texp));
-            compile_error(ast_ctx(self), "invalid type for parameter %u in function call: expected %s, got %s",
-                     (unsigned int)(i+1), texp, tgot);
-            /* we don't immediately return */
-            retval = false;
-        }
-    }
-    count = vec_size(self->params);
-    if (count > vec_size(func->params) && func->varparam) {
-        for (; i < count; ++i) {
-            if (ast_istype(self->params[i], ast_argpipe)) {
-                /* warn about type safety instead */
-                if (i+1 != count) {
-                    compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call");
-                    return false;
-                }
-                if (!ast_call_check_vararg(self, va_type, func->varparam))
-                    retval = false;
-            }
-            else if (!ast_compare_type(self->params[i], func->varparam))
-            {
-                ast_type_to_string(self->params[i], tgot, sizeof(tgot));
-                ast_type_to_string(func->varparam, texp, sizeof(texp));
-                compile_error(ast_ctx(self), "invalid type for variadic parameter %u in function call: expected %s, got %s",
-                         (unsigned int)(i+1), texp, tgot);
-                /* we don't immediately return */
-                retval = false;
-            }
-        }
-    }
-    return retval;
-}
-
-ast_store* ast_store_new(lex_ctx_t ctx, int op,
-                         ast_expression *dest, ast_expression *source)
-{
-    ast_instantiate(ast_store, ctx, ast_store_delete);
-    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_store_codegen);
-
-    ast_side_effects(self) = true;
-
-    self->op = op;
-    self->dest = dest;
-    self->source = source;
-
-    ast_type_adopt(self, dest);
-
-    return self;
-}
-
-void ast_store_delete(ast_store *self)
-{
-    ast_unref(self->dest);
-    ast_unref(self->source);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-ast_block* ast_block_new(lex_ctx_t ctx)
-{
-    ast_instantiate(ast_block, ctx, ast_block_delete);
-    ast_expression_init((ast_expression*)self,
-                        (ast_expression_codegen*)&ast_block_codegen);
-
-    self->locals  = NULL;
-    self->exprs   = NULL;
-    self->collect = NULL;
-
-    return self;
-}
-
-bool ast_block_add_expr(ast_block *self, ast_expression *e)
-{
-    ast_propagate_effects(self, e);
-    vec_push(self->exprs, e);
-    if (self->expression.next) {
-        ast_delete(self->expression.next);
-        self->expression.next = NULL;
-    }
-    ast_type_adopt(self, e);
-    return true;
-}
-
-void ast_block_collect(ast_block *self, ast_expression *expr)
-{
-    vec_push(self->collect, expr);
-    expr->node.keep = true;
-}
-
-void ast_block_delete(ast_block *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->exprs); ++i)
-        ast_unref(self->exprs[i]);
-    vec_free(self->exprs);
-    for (i = 0; i < vec_size(self->locals); ++i)
-        ast_delete(self->locals[i]);
-    vec_free(self->locals);
-    for (i = 0; i < vec_size(self->collect); ++i)
-        ast_delete(self->collect[i]);
-    vec_free(self->collect);
-    ast_expression_delete((ast_expression*)self);
-    mem_d(self);
-}
-
-void ast_block_set_type(ast_block *self, ast_expression *from)
-{
-    if (self->expression.next)
-        ast_delete(self->expression.next);
-    ast_type_adopt(self, from);
-}
-
-ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype)
-{
-    ast_instantiate(ast_function, ctx, ast_function_delete);
-
-    if (!vtype) {
-        compile_error(ast_ctx(self), "internal error: ast_function_new condition 0");
-        goto cleanup;
-    } else if (vtype->hasvalue || vtype->expression.vtype != TYPE_FUNCTION) {
-        compile_error(ast_ctx(self), "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)",
-                 (int)!vtype,
-                 (int)vtype->hasvalue,
-                 vtype->expression.vtype);
-        goto cleanup;
-    }
-
-    self->vtype  = vtype;
-    self->name   = name ? util_strdup(name) : NULL;
-    self->blocks = NULL;
-
-    self->labelcount = 0;
-    self->builtin = 0;
-
-    self->ir_func = NULL;
-    self->curblock = NULL;
-
-    self->breakblocks    = NULL;
-    self->continueblocks = NULL;
-
-    vtype->hasvalue = true;
-    vtype->constval.vfunc = self;
-
-    self->varargs          = NULL;
-    self->argc             = NULL;
-    self->fixedparams      = NULL;
-    self->return_value     = NULL;
-
-    self->static_names     = NULL;
-    self->static_count     = 0;
-
-    return self;
-
-cleanup:
-    mem_d(self);
-    return NULL;
-}
-
-void ast_function_delete(ast_function *self)
-{
-    size_t i;
-    if (self->name)
-        mem_d((void*)self->name);
-    if (self->vtype) {
-        /* ast_value_delete(self->vtype); */
-        self->vtype->hasvalue = false;
-        self->vtype->constval.vfunc = NULL;
-        /* We use unref - if it was stored in a global table it is supposed
-         * to be deleted from *there*
-         */
-        ast_unref(self->vtype);
-    }
-    for (i = 0; i < vec_size(self->static_names); ++i)
-        mem_d(self->static_names[i]);
-    vec_free(self->static_names);
-    for (i = 0; i < vec_size(self->blocks); ++i)
-        ast_delete(self->blocks[i]);
-    vec_free(self->blocks);
-    vec_free(self->breakblocks);
-    vec_free(self->continueblocks);
-    if (self->varargs)
-        ast_delete(self->varargs);
-    if (self->argc)
-        ast_delete(self->argc);
-    if (self->fixedparams)
-        ast_unref(self->fixedparams);
-    if (self->return_value)
-        ast_unref(self->return_value);
-    mem_d(self);
-}
-
-const char* ast_function_label(ast_function *self, const char *prefix)
-{
-    size_t id;
-    size_t len;
-    char  *from;
-
-    if (!OPTS_OPTION_BOOL(OPTION_DUMP)    &&
-        !OPTS_OPTION_BOOL(OPTION_DUMPFIN) &&
-        !OPTS_OPTION_BOOL(OPTION_DEBUG))
-    {
-        return NULL;
-    }
-
-    id  = (self->labelcount++);
-    len = strlen(prefix);
-
-    from = self->labelbuf + sizeof(self->labelbuf)-1;
-    *from-- = 0;
-    do {
-        *from-- = (id%10) + '0';
-        id /= 10;
-    } while (id);
-    ++from;
-    memcpy(from - len, prefix, len);
-    return from - len;
-}
-
-/*********************************************************************/
-/* AST codegen part
- * by convention you must never pass NULL to the 'ir_value **out'
- * parameter. If you really don't care about the output, pass a dummy.
- * But I can't imagine a pituation where the output is truly unnecessary.
- */
-
-static void _ast_codegen_output_type(ast_expression *self, ir_value *out)
-{
-    if (out->vtype == TYPE_FIELD)
-        out->fieldtype = self->next->vtype;
-    if (out->vtype == TYPE_FUNCTION)
-        out->outtype = self->next->vtype;
-}
-
-#define codegen_output_type(a,o) (_ast_codegen_output_type(&((a)->expression),(o)))
-
-bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    (void)func;
-    (void)lvalue;
-    if (self->expression.vtype == TYPE_NIL) {
-        *out = func->ir_func->owner->nil;
-        return true;
-    }
-    /* NOTE: This is the codegen for a variable used in an expression.
-     * It is not the codegen to generate the value. For this purpose,
-     * ast_local_codegen and ast_global_codegen are to be used before this
-     * is executed. ast_function_codegen should take care of its locals,
-     * and the ast-user should take care of ast_global_codegen to be used
-     * on all the globals.
-     */
-    if (!self->ir_v) {
-        char tname[1024]; /* typename is reserved in C++ */
-        ast_type_to_string((ast_expression*)self, tname, sizeof(tname));
-        compile_error(ast_ctx(self), "ast_value used before generated %s %s", tname, self->name);
-        return false;
-    }
-    *out = self->ir_v;
-    return true;
-}
-
-static bool ast_global_array_set(ast_value *self)
-{
-    size_t count = vec_size(self->initlist);
-    size_t i;
-
-    if (count > self->expression.count) {
-        compile_error(ast_ctx(self), "too many elements in initializer");
-        count = self->expression.count;
-    }
-    else if (count < self->expression.count) {
-        /* add this?
-        compile_warning(ast_ctx(self), "not all elements are initialized");
-        */
-    }
-
-    for (i = 0; i != count; ++i) {
-        switch (self->expression.next->vtype) {
-            case TYPE_FLOAT:
-                if (!ir_value_set_float(self->ir_values[i], self->initlist[i].vfloat))
-                    return false;
-                break;
-            case TYPE_VECTOR:
-                if (!ir_value_set_vector(self->ir_values[i], self->initlist[i].vvec))
-                    return false;
-                break;
-            case TYPE_STRING:
-                if (!ir_value_set_string(self->ir_values[i], self->initlist[i].vstring))
-                    return false;
-                break;
-            case TYPE_ARRAY:
-                /* we don't support them in any other place yet either */
-                compile_error(ast_ctx(self), "TODO: nested arrays");
-                return false;
-            case TYPE_FUNCTION:
-                /* this requiers a bit more work - similar to the fields I suppose */
-                compile_error(ast_ctx(self), "global of type function not properly generated");
-                return false;
-            case TYPE_FIELD:
-                if (!self->initlist[i].vfield) {
-                    compile_error(ast_ctx(self), "field constant without vfield set");
-                    return false;
-                }
-                if (!self->initlist[i].vfield->ir_v) {
-                    compile_error(ast_ctx(self), "field constant generated before its field");
-                    return false;
-                }
-                if (!ir_value_set_field(self->ir_values[i], self->initlist[i].vfield->ir_v))
-                    return false;
-                break;
-            default:
-                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
-                break;
-        }
-    }
-    return true;
-}
-
-static bool check_array(ast_value *self, ast_value *array)
-{
-    if (array->expression.flags & AST_FLAG_ARRAY_INIT && !array->initlist) {
-        compile_error(ast_ctx(self), "array without size: %s", self->name);
-        return false;
-    }
-    /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
-    if (!array->expression.count || array->expression.count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) {
-        compile_error(ast_ctx(self), "Invalid array of size %lu", (unsigned long)array->expression.count);
-        return false;
-    }
-    return true;
-}
-
-bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
-{
-    ir_value *v = NULL;
-
-    if (self->expression.vtype == TYPE_NIL) {
-        compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL");
-        return false;
-    }
-
-    if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION)
-    {
-        ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->vtype);
-        if (!func)
-            return false;
-        func->context = ast_ctx(self);
-        func->value->context = ast_ctx(self);
-
-        self->constval.vfunc->ir_func = func;
-        self->ir_v = func->value;
-        if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-            self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-        if (self->expression.flags & AST_FLAG_ERASEABLE)
-            self->ir_v->flags |= IR_FLAG_ERASABLE;
-        if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE)
-            func->flags |= IR_FLAG_BLOCK_COVERAGE;
-        /* The function is filled later on ast_function_codegen... */
-        return true;
-    }
-
-    if (isfield && self->expression.vtype == TYPE_FIELD) {
-        ast_expression *fieldtype = self->expression.next;
-
-        if (self->hasvalue) {
-            compile_error(ast_ctx(self), "TODO: constant field pointers with value");
-            goto error;
-        }
-
-        if (fieldtype->vtype == TYPE_ARRAY) {
-            size_t ai;
-            char   *name;
-            size_t  namelen;
-
-            ast_expression *elemtype;
-            int             vtype;
-            ast_value      *array = (ast_value*)fieldtype;
-
-            if (!ast_istype(fieldtype, ast_value)) {
-                compile_error(ast_ctx(self), "internal error: ast_value required");
-                return false;
-            }
-
-            if (!check_array(self, array))
-                return false;
-
-            elemtype = array->expression.next;
-            vtype = elemtype->vtype;
-
-            v = ir_builder_create_field(ir, self->name, vtype);
-            if (!v) {
-                compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name);
-                return false;
-            }
-            v->context = ast_ctx(self);
-            v->unique_life = true;
-            v->locked      = true;
-            array->ir_v = self->ir_v = v;
-
-            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-            if (self->expression.flags & AST_FLAG_ERASEABLE)
-                self->ir_v->flags |= IR_FLAG_ERASABLE;
-
-            namelen = strlen(self->name);
-            name    = (char*)mem_a(namelen + 16);
-            util_strncpy(name, self->name, namelen);
-
-            array->ir_values = (ir_value**)mem_a(sizeof(array->ir_values[0]) * array->expression.count);
-            array->ir_values[0] = v;
-            for (ai = 1; ai < array->expression.count; ++ai) {
-                util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
-                array->ir_values[ai] = ir_builder_create_field(ir, name, vtype);
-                if (!array->ir_values[ai]) {
-                    mem_d(name);
-                    compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", name);
-                    return false;
-                }
-                array->ir_values[ai]->context = ast_ctx(self);
-                array->ir_values[ai]->unique_life = true;
-                array->ir_values[ai]->locked      = true;
-                if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                    self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF;
-            }
-            mem_d(name);
-        }
-        else
-        {
-            v = ir_builder_create_field(ir, self->name, self->expression.next->vtype);
-            if (!v)
-                return false;
-            v->context = ast_ctx(self);
-            self->ir_v = v;
-            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-
-            if (self->expression.flags & AST_FLAG_ERASEABLE)
-                self->ir_v->flags |= IR_FLAG_ERASABLE;
-        }
-        return true;
-    }
-
-    if (self->expression.vtype == TYPE_ARRAY) {
-        size_t ai;
-        char   *name;
-        size_t  namelen;
-
-        ast_expression *elemtype = self->expression.next;
-        int vtype = elemtype->vtype;
-
-        if (self->expression.flags & AST_FLAG_ARRAY_INIT && !self->expression.count) {
-            compile_error(ast_ctx(self), "array `%s' has no size", self->name);
-            return false;
-        }
-
-        /* same as with field arrays */
-        if (!check_array(self, self))
-            return false;
-
-        v = ir_builder_create_global(ir, self->name, vtype);
-        if (!v) {
-            compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", self->name);
-            return false;
-        }
-        v->context = ast_ctx(self);
-        v->unique_life = true;
-        v->locked      = true;
-
-        if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-            v->flags |= IR_FLAG_INCLUDE_DEF;
-        if (self->expression.flags & AST_FLAG_ERASEABLE)
-            self->ir_v->flags |= IR_FLAG_ERASABLE;
-
-        namelen = strlen(self->name);
-        name    = (char*)mem_a(namelen + 16);
-        util_strncpy(name, self->name, namelen);
-
-        self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count);
-        self->ir_values[0] = v;
-        for (ai = 1; ai < self->expression.count; ++ai) {
-            util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
-            self->ir_values[ai] = ir_builder_create_global(ir, name, vtype);
-            if (!self->ir_values[ai]) {
-                mem_d(name);
-                compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", name);
-                return false;
-            }
-            self->ir_values[ai]->context = ast_ctx(self);
-            self->ir_values[ai]->unique_life = true;
-            self->ir_values[ai]->locked      = true;
-            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-                self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF;
-        }
-        mem_d(name);
-    }
-    else
-    {
-        /* Arrays don't do this since there's no "array" value which spans across the
-         * whole thing.
-         */
-        v = ir_builder_create_global(ir, self->name, self->expression.vtype);
-        if (!v) {
-            compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name);
-            return false;
-        }
-        codegen_output_type(self, v);
-        v->context = ast_ctx(self);
-    }
-
-    /* link us to the ir_value */
-    v->cvq = self->cvq;
-    self->ir_v = v;
-
-    if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
-        self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
-    if (self->expression.flags & AST_FLAG_ERASEABLE)
-        self->ir_v->flags |= IR_FLAG_ERASABLE;
-
-    /* initialize */
-    if (self->hasvalue) {
-        switch (self->expression.vtype)
-        {
-            case TYPE_FLOAT:
-                if (!ir_value_set_float(v, self->constval.vfloat))
-                    goto error;
-                break;
-            case TYPE_VECTOR:
-                if (!ir_value_set_vector(v, self->constval.vvec))
-                    goto error;
-                break;
-            case TYPE_STRING:
-                if (!ir_value_set_string(v, self->constval.vstring))
-                    goto error;
-                break;
-            case TYPE_ARRAY:
-                ast_global_array_set(self);
-                break;
-            case TYPE_FUNCTION:
-                compile_error(ast_ctx(self), "global of type function not properly generated");
-                goto error;
-                /* Cannot generate an IR value for a function,
-                 * need a pointer pointing to a function rather.
-                 */
-            case TYPE_FIELD:
-                if (!self->constval.vfield) {
-                    compile_error(ast_ctx(self), "field constant without vfield set");
-                    goto error;
-                }
-                if (!self->constval.vfield->ir_v) {
-                    compile_error(ast_ctx(self), "field constant generated before its field");
-                    goto error;
-                }
-                if (!ir_value_set_field(v, self->constval.vfield->ir_v))
-                    goto error;
-                break;
-            default:
-                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
-                break;
-        }
-    }
-    return true;
-
-error: /* clean up */
-    if(v) ir_value_delete(v);
-    return false;
-}
-
-static bool ast_local_codegen(ast_value *self, ir_function *func, bool param)
-{
-    ir_value *v = NULL;
-
-    if (self->expression.vtype == TYPE_NIL) {
-        compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL");
-        return false;
-    }
-
-    if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION)
-    {
-        /* Do we allow local functions? I think not...
-         * this is NOT a function pointer atm.
-         */
-        return false;
-    }
-
-    if (self->expression.vtype == TYPE_ARRAY) {
-        size_t ai;
-        char   *name;
-        size_t  namelen;
-
-        ast_expression *elemtype = self->expression.next;
-        int vtype = elemtype->vtype;
-
-        func->flags |= IR_FLAG_HAS_ARRAYS;
-
-        if (param && !(self->expression.flags & AST_FLAG_IS_VARARG)) {
-            compile_error(ast_ctx(self), "array-parameters are not supported");
-            return false;
-        }
-
-        /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
-        if (!check_array(self, self))
-            return false;
-
-        self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count);
-        if (!self->ir_values) {
-            compile_error(ast_ctx(self), "failed to allocate array values");
-            return false;
-        }
-
-        v = ir_function_create_local(func, self->name, vtype, param);
-        if (!v) {
-            compile_error(ast_ctx(self), "internal error: ir_function_create_local failed");
-            return false;
-        }
-        v->context = ast_ctx(self);
-        v->unique_life = true;
-        v->locked      = true;
-
-        namelen = strlen(self->name);
-        name    = (char*)mem_a(namelen + 16);
-        util_strncpy(name, self->name, namelen);
-
-        self->ir_values[0] = v;
-        for (ai = 1; ai < self->expression.count; ++ai) {
-            util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
-            self->ir_values[ai] = ir_function_create_local(func, name, vtype, param);
-            if (!self->ir_values[ai]) {
-                compile_error(ast_ctx(self), "internal_error: ir_builder_create_global failed on `%s`", name);
-                return false;
-            }
-            self->ir_values[ai]->context = ast_ctx(self);
-            self->ir_values[ai]->unique_life = true;
-            self->ir_values[ai]->locked      = true;
-        }
-        mem_d(name);
-    }
-    else
-    {
-        v = ir_function_create_local(func, self->name, self->expression.vtype, param);
-        if (!v)
-            return false;
-        codegen_output_type(self, v);
-        v->context = ast_ctx(self);
-    }
-
-    /* A constant local... hmmm...
-     * I suppose the IR will have to deal with this
-     */
-    if (self->hasvalue) {
-        switch (self->expression.vtype)
-        {
-            case TYPE_FLOAT:
-                if (!ir_value_set_float(v, self->constval.vfloat))
-                    goto error;
-                break;
-            case TYPE_VECTOR:
-                if (!ir_value_set_vector(v, self->constval.vvec))
-                    goto error;
-                break;
-            case TYPE_STRING:
-                if (!ir_value_set_string(v, self->constval.vstring))
-                    goto error;
-                break;
-            default:
-                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
-                break;
-        }
-    }
-
-    /* link us to the ir_value */
-    v->cvq = self->cvq;
-    self->ir_v = v;
-
-    if (!ast_generate_accessors(self, func->owner))
-        return false;
-    return true;
-
-error: /* clean up */
-    ir_value_delete(v);
-    return false;
-}
-
-bool ast_generate_accessors(ast_value *self, ir_builder *ir)
-{
-    size_t i;
-    bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED);
-    if (!self->setter || !self->getter)
-        return true;
-    for (i = 0; i < self->expression.count; ++i) {
-        if (!self->ir_values) {
-            compile_error(ast_ctx(self), "internal error: no array values generated for `%s`", self->name);
-            return false;
-        }
-        if (!self->ir_values[i]) {
-            compile_error(ast_ctx(self), "internal error: not all array values have been generated for `%s`", self->name);
-            return false;
-        }
-        if (self->ir_values[i]->life) {
-            compile_error(ast_ctx(self), "internal error: function containing `%s` already generated", self->name);
-            return false;
-        }
-    }
-
-    opts_set(opts.warn, WARN_USED_UNINITIALIZED, false);
-    if (self->setter) {
-        if (!ast_global_codegen  (self->setter, ir, false) ||
-            !ast_function_codegen(self->setter->constval.vfunc, ir) ||
-            !ir_function_finalize(self->setter->constval.vfunc->ir_func))
-        {
-            compile_error(ast_ctx(self), "internal error: failed to generate setter for `%s`", self->name);
-            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
-            return false;
-        }
-    }
-    if (self->getter) {
-        if (!ast_global_codegen  (self->getter, ir, false) ||
-            !ast_function_codegen(self->getter->constval.vfunc, ir) ||
-            !ir_function_finalize(self->getter->constval.vfunc->ir_func))
-        {
-            compile_error(ast_ctx(self), "internal error: failed to generate getter for `%s`", self->name);
-            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
-            return false;
-        }
-    }
-    for (i = 0; i < self->expression.count; ++i) {
-        vec_free(self->ir_values[i]->life);
-    }
-    opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
-    return true;
-}
-
-bool ast_function_codegen(ast_function *self, ir_builder *ir)
-{
-    ir_function *irf;
-    ir_value    *dummy;
-    ast_expression         *ec;
-    ast_expression_codegen *cgen;
-
-    size_t    i;
-
-    (void)ir;
-
-    irf = self->ir_func;
-    if (!irf) {
-        compile_error(ast_ctx(self), "internal error: ast_function's related ast_value was not generated yet");
-        return false;
-    }
-
-    /* fill the parameter list */
-    ec = &self->vtype->expression;
-    for (i = 0; i < vec_size(ec->params); ++i)
-    {
-        if (ec->params[i]->expression.vtype == TYPE_FIELD)
-            vec_push(irf->params, ec->params[i]->expression.next->vtype);
-        else
-            vec_push(irf->params, ec->params[i]->expression.vtype);
-        if (!self->builtin) {
-            if (!ast_local_codegen(ec->params[i], self->ir_func, true))
-                return false;
-        }
-    }
-
-    if (self->varargs) {
-        if (!ast_local_codegen(self->varargs, self->ir_func, true))
-            return false;
-        irf->max_varargs = self->varargs->expression.count;
-    }
-
-    if (self->builtin) {
-        irf->builtin = self->builtin;
-        return true;
-    }
-
-    /* have a local return value variable? */
-    if (self->return_value) {
-        if (!ast_local_codegen(self->return_value, self->ir_func, false))
-            return false;
-    }
-
-    if (!vec_size(self->blocks)) {
-        compile_error(ast_ctx(self), "function `%s` has no body", self->name);
-        return false;
-    }
-
-    irf->first = self->curblock = ir_function_create_block(ast_ctx(self), irf, "entry");
-    if (!self->curblock) {
-        compile_error(ast_ctx(self), "failed to allocate entry block for `%s`", self->name);
-        return false;
-    }
-
-    if (self->argc) {
-        ir_value *va_count;
-        ir_value *fixed;
-        ir_value *sub;
-        if (!ast_local_codegen(self->argc, self->ir_func, true))
-            return false;
-        cgen = self->argc->expression.codegen;
-        if (!(*cgen)((ast_expression*)(self->argc), self, false, &va_count))
-            return false;
-        cgen = self->fixedparams->expression.codegen;
-        if (!(*cgen)((ast_expression*)(self->fixedparams), self, false, &fixed))
-            return false;
-        sub = ir_block_create_binop(self->curblock, ast_ctx(self),
-                                    ast_function_label(self, "va_count"), INSTR_SUB_F,
-                                    ir_builder_get_va_count(ir), fixed);
-        if (!sub)
-            return false;
-        if (!ir_block_create_store_op(self->curblock, ast_ctx(self), INSTR_STORE_F,
-                                      va_count, sub))
-        {
-            return false;
-        }
-    }
-
-    for (i = 0; i < vec_size(self->blocks); ++i) {
-        cgen = self->blocks[i]->expression.codegen;
-        if (!(*cgen)((ast_expression*)self->blocks[i], self, false, &dummy))
-            return false;
-    }
-
-    /* TODO: check return types */
-    if (!self->curblock->final)
-    {
-        if (!self->vtype->expression.next ||
-            self->vtype->expression.next->vtype == TYPE_VOID)
-        {
-            return ir_block_create_return(self->curblock, ast_ctx(self), NULL);
-        }
-        else if (vec_size(self->curblock->entries) || self->curblock == irf->first)
-        {
-            if (self->return_value) {
-                cgen = self->return_value->expression.codegen;
-                if (!(*cgen)((ast_expression*)(self->return_value), self, false, &dummy))
-                    return false;
-                return ir_block_create_return(self->curblock, ast_ctx(self), dummy);
-            }
-            else if (compile_warning(ast_ctx(self), WARN_MISSING_RETURN_VALUES,
-                                "control reaches end of non-void function (`%s`) via %s",
-                                self->name, self->curblock->label))
-            {
-                return false;
-            }
-            return ir_block_create_return(self->curblock, ast_ctx(self), NULL);
-        }
-    }
-    return true;
-}
-
-static bool starts_a_label(ast_expression *ex)
-{
-    while (ex && ast_istype(ex, ast_block)) {
-        ast_block *b = (ast_block*)ex;
-        ex = b->exprs[0];
-    }
-    if (!ex)
-        return false;
-    return ast_istype(ex, ast_label);
-}
-
-/* Note, you will not see ast_block_codegen generate ir_blocks.
- * To the AST and the IR, blocks are 2 different things.
- * In the AST it represents a block of code, usually enclosed in
- * curly braces {...}.
- * While in the IR it represents a block in terms of control-flow.
- */
-bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    size_t i;
-
-    /* We don't use this
-     * Note: an ast-representation using the comma-operator
-     * of the form: (a, b, c) = x should not assign to c...
-     */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (code-block)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    /* output is NULL at first, we'll have each expression
-     * assign to out output, thus, a comma-operator represention
-     * using an ast_block will return the last generated value,
-     * so: (b, c) + a  executed both b and c, and returns c,
-     * which is then added to a.
-     */
-    *out = NULL;
-
-    /* generate locals */
-    for (i = 0; i < vec_size(self->locals); ++i)
-    {
-        if (!ast_local_codegen(self->locals[i], func->ir_func, false)) {
-            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
-                compile_error(ast_ctx(self), "failed to generate local `%s`", self->locals[i]->name);
-            return false;
-        }
-    }
-
-    for (i = 0; i < vec_size(self->exprs); ++i)
-    {
-        ast_expression_codegen *gen;
-        if (func->curblock->final && !starts_a_label(self->exprs[i])) {
-            if (compile_warning(ast_ctx(self->exprs[i]), WARN_UNREACHABLE_CODE, "unreachable statement"))
-                return false;
-            continue;
-        }
-        gen = self->exprs[i]->codegen;
-        if (!(*gen)(self->exprs[i], func, false, out))
-            return false;
-    }
-
-    self->expression.outr = *out;
-
-    return true;
-}
-
-bool ast_store_codegen(ast_store *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *left  = NULL;
-    ir_value *right = NULL;
-
-    ast_value       *arr;
-    ast_value       *idx = 0;
-    ast_array_index *ai = NULL;
-
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    if (ast_istype(self->dest, ast_array_index))
-    {
-
-        ai = (ast_array_index*)self->dest;
-        idx = (ast_value*)ai->index;
-
-        if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST)
-            ai = NULL;
-    }
-
-    if (ai) {
-        /* we need to call the setter */
-        ir_value  *iridx, *funval;
-        ir_instr  *call;
-
-        if (lvalue) {
-            compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues");
-            return false;
-        }
-
-        arr = (ast_value*)ai->array;
-        if (!ast_istype(ai->array, ast_value) || !arr->setter) {
-            compile_error(ast_ctx(self), "value has no setter (%s)", arr->name);
-            return false;
-        }
-
-        cgen = idx->expression.codegen;
-        if (!(*cgen)((ast_expression*)(idx), func, false, &iridx))
-            return false;
-
-        cgen = arr->setter->expression.codegen;
-        if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval))
-            return false;
-
-        cgen = self->source->codegen;
-        if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
-            return false;
-
-        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false);
-        if (!call)
-            return false;
-        ir_call_param(call, iridx);
-        ir_call_param(call, right);
-        self->expression.outr = right;
-    }
-    else
-    {
-        /* regular code */
-
-        cgen = self->dest->codegen;
-        /* lvalue! */
-        if (!(*cgen)((ast_expression*)(self->dest), func, true, &left))
-            return false;
-        self->expression.outl = left;
-
-        cgen = self->source->codegen;
-        /* rvalue! */
-        if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
-            return false;
-
-        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->op, left, right))
-            return false;
-        self->expression.outr = right;
-    }
-
-    /* Theoretically, an assinment returns its left side as an
-     * lvalue, if we don't need an lvalue though, we return
-     * the right side as an rvalue, otherwise we have to
-     * somehow know whether or not we need to dereference the pointer
-     * on the left side - that is: OP_LOAD if it was an address.
-     * Also: in original QC we cannot OP_LOADP *anyway*.
-     */
-    *out = (lvalue ? left : right);
-
-    return true;
-}
-
-bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *left, *right;
-
-    /* A binary operation cannot yield an l-value */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (binop)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) &&
-        (self->op == INSTR_AND || self->op == INSTR_OR))
-    {
-        /* NOTE: The short-logic path will ignore right_first */
-
-        /* short circuit evaluation */
-        ir_block *other, *merge;
-        ir_block *from_left, *from_right;
-        ir_instr *phi;
-        size_t    merge_id;
-
-        /* prepare end-block */
-        merge_id = vec_size(func->ir_func->blocks);
-        merge    = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_merge"));
-
-        /* generate the left expression */
-        cgen = self->left->codegen;
-        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
-            return false;
-        /* remember the block */
-        from_left = func->curblock;
-
-        /* create a new block for the right expression */
-        other = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_other"));
-        if (self->op == INSTR_AND) {
-            /* on AND: left==true -> other */
-            if (!ir_block_create_if(func->curblock, ast_ctx(self), left, other, merge))
-                return false;
-        } else {
-            /* on OR: left==false -> other */
-            if (!ir_block_create_if(func->curblock, ast_ctx(self), left, merge, other))
-                return false;
-        }
-        /* use the likely flag */
-        vec_last(func->curblock->instr)->likely = true;
-
-        /* enter the right-expression's block */
-        func->curblock = other;
-        /* generate */
-        cgen = self->right->codegen;
-        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
-            return false;
-        /* remember block */
-        from_right = func->curblock;
-
-        /* jump to the merge block */
-        if (!ir_block_create_jump(func->curblock, ast_ctx(self), merge))
-            return false;
-
-        vec_remove(func->ir_func->blocks, merge_id, 1);
-        vec_push(func->ir_func->blocks, merge);
-
-        func->curblock = merge;
-        phi = ir_block_create_phi(func->curblock, ast_ctx(self),
-                                  ast_function_label(func, "sce_value"),
-                                  self->expression.vtype);
-        ir_phi_add(phi, from_left, left);
-        ir_phi_add(phi, from_right, right);
-        *out = ir_phi_value(phi);
-        if (!*out)
-            return false;
-
-        if (!OPTS_FLAG(PERL_LOGIC)) {
-            /* cast-to-bool */
-            if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->vtype == TYPE_VECTOR) {
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool_v"),
-                                             INSTR_NOT_V, *out);
-                if (!*out)
-                    return false;
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool"),
-                                             INSTR_NOT_F, *out);
-                if (!*out)
-                    return false;
-            }
-            else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->vtype == TYPE_STRING) {
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool_s"),
-                                             INSTR_NOT_S, *out);
-                if (!*out)
-                    return false;
-                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool"),
-                                             INSTR_NOT_F, *out);
-                if (!*out)
-                    return false;
-            }
-            else {
-                *out = ir_block_create_binop(func->curblock, ast_ctx(self),
-                                             ast_function_label(func, "sce_bool"),
-                                             INSTR_AND, *out, *out);
-                if (!*out)
-                    return false;
-            }
-        }
-
-        self->expression.outr = *out;
-        codegen_output_type(self, *out);
-        return true;
-    }
-
-    if (self->right_first) {
-        cgen = self->right->codegen;
-        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
-            return false;
-        cgen = self->left->codegen;
-        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
-            return false;
-    } else {
-        cgen = self->left->codegen;
-        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
-            return false;
-        cgen = self->right->codegen;
-        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
-            return false;
-    }
-
-    *out = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "bin"),
-                                 self->op, left, right);
-    if (!*out)
-        return false;
-    self->expression.outr = *out;
-    codegen_output_type(self, *out);
-
-    return true;
-}
-
-bool ast_binstore_codegen(ast_binstore *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *leftl = NULL, *leftr, *right, *bin;
-
-    ast_value       *arr;
-    ast_value       *idx = 0;
-    ast_array_index *ai = NULL;
-    ir_value        *iridx = NULL;
-
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    if (ast_istype(self->dest, ast_array_index))
-    {
-
-        ai = (ast_array_index*)self->dest;
-        idx = (ast_value*)ai->index;
-
-        if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST)
-            ai = NULL;
-    }
-
-    /* for a binstore we need both an lvalue and an rvalue for the left side */
-    /* rvalue of destination! */
-    if (ai) {
-        cgen = idx->expression.codegen;
-        if (!(*cgen)((ast_expression*)(idx), func, false, &iridx))
-            return false;
-    }
-    cgen = self->dest->codegen;
-    if (!(*cgen)((ast_expression*)(self->dest), func, false, &leftr))
-        return false;
-
-    /* source as rvalue only */
-    cgen = self->source->codegen;
-    if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
-        return false;
-
-    /* now the binary */
-    bin = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "binst"),
-                                self->opbin, leftr, right);
-    self->expression.outr = bin;
-
-
-    if (ai) {
-        /* we need to call the setter */
-        ir_value  *funval;
-        ir_instr  *call;
-
-        if (lvalue) {
-            compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues");
-            return false;
-        }
-
-        arr = (ast_value*)ai->array;
-        if (!ast_istype(ai->array, ast_value) || !arr->setter) {
-            compile_error(ast_ctx(self), "value has no setter (%s)", arr->name);
-            return false;
-        }
-
-        cgen = arr->setter->expression.codegen;
-        if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval))
-            return false;
-
-        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false);
-        if (!call)
-            return false;
-        ir_call_param(call, iridx);
-        ir_call_param(call, bin);
-        self->expression.outr = bin;
-    } else {
-        /* now store them */
-        cgen = self->dest->codegen;
-        /* lvalue of destination */
-        if (!(*cgen)((ast_expression*)(self->dest), func, true, &leftl))
-            return false;
-        self->expression.outl = leftl;
-
-        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->opstore, leftl, bin))
-            return false;
-        self->expression.outr = bin;
-    }
-
-    /* Theoretically, an assinment returns its left side as an
-     * lvalue, if we don't need an lvalue though, we return
-     * the right side as an rvalue, otherwise we have to
-     * somehow know whether or not we need to dereference the pointer
-     * on the left side - that is: OP_LOAD if it was an address.
-     * Also: in original QC we cannot OP_LOADP *anyway*.
-     */
-    *out = (lvalue ? leftl : bin);
-
-    return true;
-}
-
-bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *operand;
-
-    /* An unary operation cannot yield an l-value */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (binop)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    cgen = self->operand->codegen;
-    /* lvalue! */
-    if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
-        return false;
-
-    *out = ir_block_create_unary(func->curblock, ast_ctx(self), ast_function_label(func, "unary"),
-                                 self->op, operand);
-    if (!*out)
-        return false;
-    self->expression.outr = *out;
-
-    return true;
-}
-
-bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *operand;
-
-    *out = NULL;
-
-    /* In the context of a return operation, we don't actually return
-     * anything...
-     */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "return-expression is not an l-value");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_return cannot be reused, it bears no result!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    if (self->operand) {
-        cgen = self->operand->codegen;
-        /* lvalue! */
-        if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
-            return false;
-
-        if (!ir_block_create_return(func->curblock, ast_ctx(self), operand))
-            return false;
-    } else {
-        if (!ir_block_create_return(func->curblock, ast_ctx(self), NULL))
-            return false;
-    }
-
-    return true;
-}
-
-bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *ent, *field;
-
-    /* This function needs to take the 'lvalue' flag into account!
-     * As lvalue we provide a field-pointer, as rvalue we provide the
-     * value in a temp.
-     */
-
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    cgen = self->entity->codegen;
-    if (!(*cgen)((ast_expression*)(self->entity), func, false, &ent))
-        return false;
-
-    cgen = self->field->codegen;
-    if (!(*cgen)((ast_expression*)(self->field), func, false, &field))
-        return false;
-
-    if (lvalue) {
-        /* address! */
-        *out = ir_block_create_fieldaddress(func->curblock, ast_ctx(self), ast_function_label(func, "efa"),
-                                            ent, field);
-    } else {
-        *out = ir_block_create_load_from_ent(func->curblock, ast_ctx(self), ast_function_label(func, "efv"),
-                                             ent, field, self->expression.vtype);
-        /* Done AFTER error checking:
-        codegen_output_type(self, *out);
-        */
-    }
-    if (!*out) {
-        compile_error(ast_ctx(self), "failed to create %s instruction (output type %s)",
-                 (lvalue ? "ADDRESS" : "FIELD"),
-                 type_name[self->expression.vtype]);
-        return false;
-    }
-    if (!lvalue)
-        codegen_output_type(self, *out);
-
-    if (lvalue)
-        self->expression.outl = *out;
-    else
-        self->expression.outr = *out;
-
-    /* Hm that should be it... */
-    return true;
-}
-
-bool ast_member_codegen(ast_member *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value *vec;
-
-    /* in QC this is always an lvalue */
-    if (lvalue && self->rvalue) {
-        compile_error(ast_ctx(self), "not an l-value (member access)");
-        return false;
-    }
-    if (self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    cgen = self->owner->codegen;
-    if (!(*cgen)((ast_expression*)(self->owner), func, false, &vec))
-        return false;
-
-    if (vec->vtype != TYPE_VECTOR &&
-        !(vec->vtype == TYPE_FIELD && self->owner->next->vtype == TYPE_VECTOR))
-    {
-        return false;
-    }
-
-    *out = ir_value_vector_member(vec, self->field);
-    self->expression.outl = *out;
-
-    return (*out != NULL);
-}
-
-bool ast_array_index_codegen(ast_array_index *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_value *arr;
-    ast_value *idx;
-
-    if (!lvalue && self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-    if (lvalue && self->expression.outl) {
-        *out = self->expression.outl;
-        return true;
-    }
-
-    if (!ast_istype(self->array, ast_value)) {
-        compile_error(ast_ctx(self), "array indexing this way is not supported");
-        /* note this would actually be pointer indexing because the left side is
-         * not an actual array but (hopefully) an indexable expression.
-         * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction
-         * support this path will be filled.
-         */
-        return false;
-    }
-
-    arr = (ast_value*)self->array;
-    idx = (ast_value*)self->index;
-
-    if (!ast_istype(self->index, ast_value) || !idx->hasvalue || idx->cvq != CV_CONST) {
-        /* Time to use accessor functions */
-        ast_expression_codegen *cgen;
-        ir_value               *iridx, *funval;
-        ir_instr               *call;
-
-        if (lvalue) {
-            compile_error(ast_ctx(self), "(.2) array indexing here needs a compile-time constant");
-            return false;
-        }
-
-        if (!arr->getter) {
-            compile_error(ast_ctx(self), "value has no getter, don't know how to index it");
-            return false;
-        }
-
-        cgen = self->index->codegen;
-        if (!(*cgen)((ast_expression*)(self->index), func, false, &iridx))
-            return false;
-
-        cgen = arr->getter->expression.codegen;
-        if (!(*cgen)((ast_expression*)(arr->getter), func, true, &funval))
-            return false;
-
-        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "fetch"), funval, false);
-        if (!call)
-            return false;
-        ir_call_param(call, iridx);
-
-        *out = ir_call_value(call);
-        self->expression.outr = *out;
-        (*out)->vtype = self->expression.vtype;
-        codegen_output_type(self, *out);
-        return true;
-    }
-
-    if (idx->expression.vtype == TYPE_FLOAT) {
-        unsigned int arridx = idx->constval.vfloat;
-        if (arridx >= self->array->count)
-        {
-            compile_error(ast_ctx(self), "array index out of bounds: %i", arridx);
-            return false;
-        }
-        *out = arr->ir_values[arridx];
-    }
-    else if (idx->expression.vtype == TYPE_INTEGER) {
-        unsigned int arridx = idx->constval.vint;
-        if (arridx >= self->array->count)
-        {
-            compile_error(ast_ctx(self), "array index out of bounds: %i", arridx);
-            return false;
-        }
-        *out = arr->ir_values[arridx];
-    }
-    else {
-        compile_error(ast_ctx(self), "array indexing here needs an integer constant");
-        return false;
-    }
-    (*out)->vtype = self->expression.vtype;
-    codegen_output_type(self, *out);
-    return true;
-}
-
-bool ast_argpipe_codegen(ast_argpipe *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    *out = NULL;
-    if (lvalue) {
-        compile_error(ast_ctx(self), "argpipe node: not an lvalue");
-        return false;
-    }
-    (void)func;
-    (void)out;
-    compile_error(ast_ctx(self), "TODO: argpipe codegen not implemented");
-    return false;
-}
-
-bool ast_ifthen_codegen(ast_ifthen *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *condval;
-    ir_value *dummy;
-
-    ir_block *cond;
-    ir_block *ontrue;
-    ir_block *onfalse;
-    ir_block *ontrue_endblock = NULL;
-    ir_block *onfalse_endblock = NULL;
-    ir_block *merge = NULL;
-    int       fold  = 0;
-
-    /* We don't output any value, thus also don't care about r/lvalue */
-    (void)out;
-    (void)lvalue;
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_ifthen cannot be reused, it bears no result!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    /* generate the condition */
-    cgen = self->cond->codegen;
-    if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval))
-        return false;
-    /* update the block which will get the jump - because short-logic or ternaries may have changed this */
-    cond = func->curblock;
-
-    /* try constant folding away the condition */
-    if ((fold = fold_cond_ifthen(condval, func, self)) != -1)
-        return fold;
-
-    if (self->on_true) {
-        /* create on-true block */
-        ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "ontrue"));
-        if (!ontrue)
-            return false;
-
-        /* enter the block */
-        func->curblock = ontrue;
-
-        /* generate */
-        cgen = self->on_true->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_true), func, false, &dummy))
-            return false;
-
-        /* we now need to work from the current endpoint */
-        ontrue_endblock = func->curblock;
-    } else
-        ontrue = NULL;
-
-    /* on-false path */
-    if (self->on_false) {
-        /* create on-false block */
-        onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "onfalse"));
-        if (!onfalse)
-            return false;
-
-        /* enter the block */
-        func->curblock = onfalse;
-
-        /* generate */
-        cgen = self->on_false->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_false), func, false, &dummy))
-            return false;
-
-        /* we now need to work from the current endpoint */
-        onfalse_endblock = func->curblock;
-    } else
-        onfalse = NULL;
-
-    /* Merge block were they all merge in to */
-    if (!ontrue || !onfalse || !ontrue_endblock->final || !onfalse_endblock->final)
-    {
-        merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "endif"));
-        if (!merge)
-            return false;
-        /* add jumps ot the merge block */
-        if (ontrue && !ontrue_endblock->final && !ir_block_create_jump(ontrue_endblock, ast_ctx(self), merge))
-            return false;
-        if (onfalse && !onfalse_endblock->final && !ir_block_create_jump(onfalse_endblock, ast_ctx(self), merge))
-            return false;
-
-        /* Now enter the merge block */
-        func->curblock = merge;
-    }
-
-    /* we create the if here, that way all blocks are ordered :)
-     */
-    if (!ir_block_create_if(cond, ast_ctx(self), condval,
-                            (ontrue  ? ontrue  : merge),
-                            (onfalse ? onfalse : merge)))
-    {
-        return false;
-    }
-
-    return true;
-}
-
-bool ast_ternary_codegen(ast_ternary *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *condval;
-    ir_value *trueval, *falseval;
-    ir_instr *phi;
-
-    ir_block *cond = func->curblock;
-    ir_block *cond_out = NULL;
-    ir_block *ontrue, *ontrue_out = NULL;
-    ir_block *onfalse, *onfalse_out = NULL;
-    ir_block *merge;
-    int       fold  = 0;
-
-    /* Ternary can never create an lvalue... */
-    if (lvalue)
-        return false;
-
-    /* In theory it shouldn't be possible to pass through a node twice, but
-     * in case we add any kind of optimization pass for the AST itself, it
-     * may still happen, thus we remember a created ir_value and simply return one
-     * if it already exists.
-     */
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    /* In the following, contraty to ast_ifthen, we assume both paths exist. */
-
-    /* generate the condition */
-    func->curblock = cond;
-    cgen = self->cond->codegen;
-    if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval))
-        return false;
-    cond_out = func->curblock;
-
-    /* try constant folding away the condition */
-    if ((fold = fold_cond_ternary(condval, func, self)) != -1)
-        return fold;
-
-    /* create on-true block */
-    ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_T"));
-    if (!ontrue)
-        return false;
-    else
-    {
-        /* enter the block */
-        func->curblock = ontrue;
-
-        /* generate */
-        cgen = self->on_true->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_true), func, false, &trueval))
-            return false;
-
-        ontrue_out = func->curblock;
-    }
-
-    /* create on-false block */
-    onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_F"));
-    if (!onfalse)
-        return false;
-    else
-    {
-        /* enter the block */
-        func->curblock = onfalse;
-
-        /* generate */
-        cgen = self->on_false->codegen;
-        if (!(*cgen)((ast_expression*)(self->on_false), func, false, &falseval))
-            return false;
-
-        onfalse_out = func->curblock;
-    }
-
-    /* create merge block */
-    merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_out"));
-    if (!merge)
-        return false;
-    /* jump to merge block */
-    if (!ir_block_create_jump(ontrue_out, ast_ctx(self), merge))
-        return false;
-    if (!ir_block_create_jump(onfalse_out, ast_ctx(self), merge))
-        return false;
-
-    /* create if instruction */
-    if (!ir_block_create_if(cond_out, ast_ctx(self), condval, ontrue, onfalse))
-        return false;
-
-    /* Now enter the merge block */
-    func->curblock = merge;
-
-    /* Here, now, we need a PHI node
-     * but first some sanity checking...
-     */
-    if (trueval->vtype != falseval->vtype && trueval->vtype != TYPE_NIL && falseval->vtype != TYPE_NIL) {
-        /* error("ternary with different types on the two sides"); */
-        compile_error(ast_ctx(self), "internal error: ternary operand types invalid");
-        return false;
-    }
-
-    /* create PHI */
-    phi = ir_block_create_phi(merge, ast_ctx(self), ast_function_label(func, "phi"), self->expression.vtype);
-    if (!phi) {
-        compile_error(ast_ctx(self), "internal error: failed to generate phi node");
-        return false;
-    }
-    ir_phi_add(phi, ontrue_out,  trueval);
-    ir_phi_add(phi, onfalse_out, falseval);
-
-    self->expression.outr = ir_phi_value(phi);
-    *out = self->expression.outr;
-
-    codegen_output_type(self, *out);
-
-    return true;
-}
-
-bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *dummy      = NULL;
-    ir_value *precond    = NULL;
-    ir_value *postcond   = NULL;
-
-    /* Since we insert some jumps "late" so we have blocks
-     * ordered "nicely", we need to keep track of the actual end-blocks
-     * of expressions to add the jumps to.
-     */
-    ir_block *bbody      = NULL, *end_bbody      = NULL;
-    ir_block *bprecond   = NULL, *end_bprecond   = NULL;
-    ir_block *bpostcond  = NULL, *end_bpostcond  = NULL;
-    ir_block *bincrement = NULL, *end_bincrement = NULL;
-    ir_block *bout       = NULL, *bin            = NULL;
-
-    /* let's at least move the outgoing block to the end */
-    size_t    bout_id;
-
-    /* 'break' and 'continue' need to be able to find the right blocks */
-    ir_block *bcontinue     = NULL;
-    ir_block *bbreak        = NULL;
-
-    ir_block *tmpblock      = NULL;
-
-    (void)lvalue;
-    (void)out;
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_loop cannot be reused, it bears no result!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    /* NOTE:
-     * Should we ever need some kind of block ordering, better make this function
-     * move blocks around than write a block ordering algorithm later... after all
-     * the ast and ir should work together, not against each other.
-     */
-
-    /* initexpr doesn't get its own block, it's pointless, it could create more blocks
-     * anyway if for example it contains a ternary.
-     */
-    if (self->initexpr)
-    {
-        cgen = self->initexpr->codegen;
-        if (!(*cgen)((ast_expression*)(self->initexpr), func, false, &dummy))
-            return false;
-    }
-
-    /* Store the block from which we enter this chaos */
-    bin = func->curblock;
-
-    /* The pre-loop condition needs its own block since we
-     * need to be able to jump to the start of that expression.
-     */
-    if (self->precond)
-    {
-        bprecond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "pre_loop_cond"));
-        if (!bprecond)
-            return false;
-
-        /* the pre-loop-condition the least important place to 'continue' at */
-        bcontinue = bprecond;
-
-        /* enter */
-        func->curblock = bprecond;
-
-        /* generate */
-        cgen = self->precond->codegen;
-        if (!(*cgen)((ast_expression*)(self->precond), func, false, &precond))
-            return false;
-
-        end_bprecond = func->curblock;
-    } else {
-        bprecond = end_bprecond = NULL;
-    }
-
-    /* Now the next blocks won't be ordered nicely, but we need to
-     * generate them this early for 'break' and 'continue'.
-     */
-    if (self->increment) {
-        bincrement = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_increment"));
-        if (!bincrement)
-            return false;
-        bcontinue = bincrement; /* increment comes before the pre-loop-condition */
-    } else {
-        bincrement = end_bincrement = NULL;
-    }
-
-    if (self->postcond) {
-        bpostcond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "post_loop_cond"));
-        if (!bpostcond)
-            return false;
-        bcontinue = bpostcond; /* postcond comes before the increment */
-    } else {
-        bpostcond = end_bpostcond = NULL;
-    }
-
-    bout_id = vec_size(func->ir_func->blocks);
-    bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_loop"));
-    if (!bout)
-        return false;
-    bbreak = bout;
-
-    /* The loop body... */
-    /* if (self->body) */
-    {
-        bbody = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_body"));
-        if (!bbody)
-            return false;
-
-        /* enter */
-        func->curblock = bbody;
-
-        vec_push(func->breakblocks,    bbreak);
-        if (bcontinue)
-            vec_push(func->continueblocks, bcontinue);
-        else
-            vec_push(func->continueblocks, bbody);
-
-        /* generate */
-        if (self->body) {
-            cgen = self->body->codegen;
-            if (!(*cgen)((ast_expression*)(self->body), func, false, &dummy))
-                return false;
-        }
-
-        end_bbody = func->curblock;
-        vec_pop(func->breakblocks);
-        vec_pop(func->continueblocks);
-    }
-
-    /* post-loop-condition */
-    if (self->postcond)
-    {
-        /* enter */
-        func->curblock = bpostcond;
-
-        /* generate */
-        cgen = self->postcond->codegen;
-        if (!(*cgen)((ast_expression*)(self->postcond), func, false, &postcond))
-            return false;
-
-        end_bpostcond = func->curblock;
-    }
-
-    /* The incrementor */
-    if (self->increment)
-    {
-        /* enter */
-        func->curblock = bincrement;
-
-        /* generate */
-        cgen = self->increment->codegen;
-        if (!(*cgen)((ast_expression*)(self->increment), func, false, &dummy))
-            return false;
-
-        end_bincrement = func->curblock;
-    }
-
-    /* In any case now, we continue from the outgoing block */
-    func->curblock = bout;
-
-    /* Now all blocks are in place */
-    /* From 'bin' we jump to whatever comes first */
-    if      (bprecond)   tmpblock = bprecond;
-    else                 tmpblock = bbody;    /* can never be null */
-
-    /* DEAD CODE
-    else if (bpostcond)  tmpblock = bpostcond;
-    else                 tmpblock = bout;
-    */
-
-    if (!ir_block_create_jump(bin, ast_ctx(self), tmpblock))
-        return false;
-
-    /* From precond */
-    if (bprecond)
-    {
-        ir_block *ontrue, *onfalse;
-        ontrue = bbody; /* can never be null */
-
-        /* all of this is dead code
-        else if (bincrement) ontrue = bincrement;
-        else                 ontrue = bpostcond;
-        */
-
-        onfalse = bout;
-        if (self->pre_not) {
-            tmpblock = ontrue;
-            ontrue   = onfalse;
-            onfalse  = tmpblock;
-        }
-        if (!ir_block_create_if(end_bprecond, ast_ctx(self), precond, ontrue, onfalse))
-            return false;
-    }
-
-    /* from body */
-    if (bbody)
-    {
-        if      (bincrement) tmpblock = bincrement;
-        else if (bpostcond)  tmpblock = bpostcond;
-        else if (bprecond)   tmpblock = bprecond;
-        else                 tmpblock = bbody;
-        if (!end_bbody->final && !ir_block_create_jump(end_bbody, ast_ctx(self), tmpblock))
-            return false;
-    }
-
-    /* from increment */
-    if (bincrement)
-    {
-        if      (bpostcond)  tmpblock = bpostcond;
-        else if (bprecond)   tmpblock = bprecond;
-        else if (bbody)      tmpblock = bbody;
-        else                 tmpblock = bout;
-        if (!ir_block_create_jump(end_bincrement, ast_ctx(self), tmpblock))
-            return false;
-    }
-
-    /* from postcond */
-    if (bpostcond)
-    {
-        ir_block *ontrue, *onfalse;
-        if      (bprecond)   ontrue = bprecond;
-        else                 ontrue = bbody; /* can never be null */
-
-        /* all of this is dead code
-        else if (bincrement) ontrue = bincrement;
-        else                 ontrue = bpostcond;
-        */
-
-        onfalse = bout;
-        if (self->post_not) {
-            tmpblock = ontrue;
-            ontrue   = onfalse;
-            onfalse  = tmpblock;
-        }
-        if (!ir_block_create_if(end_bpostcond, ast_ctx(self), postcond, ontrue, onfalse))
-            return false;
-    }
-
-    /* Move 'bout' to the end */
-    vec_remove(func->ir_func->blocks, bout_id, 1);
-    vec_push(func->ir_func->blocks, bout);
-
-    return true;
-}
-
-bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ir_block *target;
-
-    *out = NULL;
-
-    if (lvalue) {
-        compile_error(ast_ctx(self), "break/continue expression is not an l-value");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_breakcont cannot be reused!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    if (self->is_continue)
-        target = func->continueblocks[vec_size(func->continueblocks)-1-self->levels];
-    else
-        target = func->breakblocks[vec_size(func->breakblocks)-1-self->levels];
-
-    if (!target) {
-        compile_error(ast_ctx(self), "%s is lacking a target block", (self->is_continue ? "continue" : "break"));
-        return false;
-    }
-
-    if (!ir_block_create_jump(func->curblock, ast_ctx(self), target))
-        return false;
-    return true;
-}
-
-bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ast_switch_case *def_case     = NULL;
-    ir_block        *def_bfall    = NULL;
-    ir_block        *def_bfall_to = NULL;
-    bool set_def_bfall_to = false;
-
-    ir_value *dummy     = NULL;
-    ir_value *irop      = NULL;
-    ir_block *bout      = NULL;
-    ir_block *bfall     = NULL;
-    size_t    bout_id;
-    size_t    c;
-
-    char      typestr[1024];
-    uint16_t  cmpinstr;
-
-    if (lvalue) {
-        compile_error(ast_ctx(self), "switch expression is not an l-value");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_switch cannot be reused!");
-        return false;
-    }
-    self->expression.outr = (ir_value*)1;
-
-    (void)lvalue;
-    (void)out;
-
-    cgen = self->operand->codegen;
-    if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop))
-        return false;
-
-    if (!vec_size(self->cases))
-        return true;
-
-    cmpinstr = type_eq_instr[irop->vtype];
-    if (cmpinstr >= VINSTR_END) {
-        ast_type_to_string(self->operand, typestr, sizeof(typestr));
-        compile_error(ast_ctx(self), "invalid type to perform a switch on: %s", typestr);
-        return false;
-    }
-
-    bout_id = vec_size(func->ir_func->blocks);
-    bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_switch"));
-    if (!bout)
-        return false;
-
-    /* setup the break block */
-    vec_push(func->breakblocks, bout);
-
-    /* Now create all cases */
-    for (c = 0; c < vec_size(self->cases); ++c) {
-        ir_value *cond, *val;
-        ir_block *bcase, *bnot;
-        size_t bnot_id;
-
-        ast_switch_case *swcase = &self->cases[c];
-
-        if (swcase->value) {
-            /* A regular case */
-            /* generate the condition operand */
-            cgen = swcase->value->codegen;
-            if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val))
-                return false;
-            /* generate the condition */
-            cond = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "switch_eq"), cmpinstr, irop, val);
-            if (!cond)
-                return false;
-
-            bcase = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "case"));
-            bnot_id = vec_size(func->ir_func->blocks);
-            bnot = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "not_case"));
-            if (!bcase || !bnot)
-                return false;
-            if (set_def_bfall_to) {
-                set_def_bfall_to = false;
-                def_bfall_to = bcase;
-            }
-            if (!ir_block_create_if(func->curblock, ast_ctx(self), cond, bcase, bnot))
-                return false;
-
-            /* Make the previous case-end fall through */
-            if (bfall && !bfall->final) {
-                if (!ir_block_create_jump(bfall, ast_ctx(self), bcase))
-                    return false;
-            }
-
-            /* enter the case */
-            func->curblock = bcase;
-            cgen = swcase->code->codegen;
-            if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy))
-                return false;
-
-            /* remember this block to fall through from */
-            bfall = func->curblock;
-
-            /* enter the else and move it down */
-            func->curblock = bnot;
-            vec_remove(func->ir_func->blocks, bnot_id, 1);
-            vec_push(func->ir_func->blocks, bnot);
-        } else {
-            /* The default case */
-            /* Remember where to fall through from: */
-            def_bfall = bfall;
-            bfall     = NULL;
-            /* remember which case it was */
-            def_case  = swcase;
-            /* And the next case will be remembered */
-            set_def_bfall_to = true;
-        }
-    }
-
-    /* Jump from the last bnot to bout */
-    if (bfall && !bfall->final && !ir_block_create_jump(bfall, ast_ctx(self), bout)) {
-        /*
-        astwarning(ast_ctx(bfall), WARN_???, "missing break after last case");
-        */
-        return false;
-    }
-
-    /* If there was a default case, put it down here */
-    if (def_case) {
-        ir_block *bcase;
-
-        /* No need to create an extra block */
-        bcase = func->curblock;
-
-        /* Insert the fallthrough jump */
-        if (def_bfall && !def_bfall->final) {
-            if (!ir_block_create_jump(def_bfall, ast_ctx(self), bcase))
-                return false;
-        }
-
-        /* Now generate the default code */
-        cgen = def_case->code->codegen;
-        if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy))
-            return false;
-
-        /* see if we need to fall through */
-        if (def_bfall_to && !func->curblock->final)
-        {
-            if (!ir_block_create_jump(func->curblock, ast_ctx(self), def_bfall_to))
-                return false;
-        }
-    }
-
-    /* Jump from the last bnot to bout */
-    if (!func->curblock->final && !ir_block_create_jump(func->curblock, ast_ctx(self), bout))
-        return false;
-    /* enter the outgoing block */
-    func->curblock = bout;
-
-    /* restore the break block */
-    vec_pop(func->breakblocks);
-
-    /* Move 'bout' to the end, it's nicer */
-    vec_remove(func->ir_func->blocks, bout_id, 1);
-    vec_push(func->ir_func->blocks, bout);
-
-    return true;
-}
-
-bool ast_label_codegen(ast_label *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    size_t i;
-    ir_value *dummy;
-
-    if (self->undefined) {
-        compile_error(ast_ctx(self), "internal error: ast_label never defined");
-        return false;
-    }
-
-    *out = NULL;
-    if (lvalue) {
-        compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue");
-        return false;
-    }
-
-    /* simply create a new block and jump to it */
-    self->irblock = ir_function_create_block(ast_ctx(self), func->ir_func, self->name);
-    if (!self->irblock) {
-        compile_error(ast_ctx(self), "failed to allocate label block `%s`", self->name);
-        return false;
-    }
-    if (!func->curblock->final) {
-        if (!ir_block_create_jump(func->curblock, ast_ctx(self), self->irblock))
-            return false;
-    }
-
-    /* enter the new block */
-    func->curblock = self->irblock;
-
-    /* Generate all the leftover gotos */
-    for (i = 0; i < vec_size(self->gotos); ++i) {
-        if (!ast_goto_codegen(self->gotos[i], func, false, &dummy))
-            return false;
-    }
-
-    return true;
-}
-
-bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    *out = NULL;
-    if (lvalue) {
-        compile_error(ast_ctx(self), "internal error: ast_goto cannot be an lvalue");
-        return false;
-    }
-
-    if (self->target->irblock) {
-        if (self->irblock_from) {
-            /* we already tried once, this is the callback */
-            self->irblock_from->final = false;
-            if (!ir_block_create_goto(self->irblock_from, ast_ctx(self), self->target->irblock)) {
-                compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name);
-                return false;
-            }
-        }
-        else
-        {
-            if (!ir_block_create_goto(func->curblock, ast_ctx(self), self->target->irblock)) {
-                compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name);
-                return false;
-            }
-        }
-    }
-    else
-    {
-        /* the target has not yet been created...
-         * close this block in a sneaky way:
-         */
-        func->curblock->final = true;
-        self->irblock_from = func->curblock;
-        ast_label_register_goto(self->target, self);
-    }
-
-    return true;
-}
-
-#include <stdio.h>
-bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-
-    ir_value *frameval, *thinkval;
-
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (state operation)");
-        return false;
-    }
-    if (self->expression.outr) {
-        compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
-        return false;
-    }
-    *out = NULL;
-
-    cgen = self->framenum->codegen;
-    if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
-        return false;
-    if (!frameval)
-        return false;
-
-    cgen = self->nextthink->codegen;
-    if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
-        return false;
-    if (!frameval)
-        return false;
-
-    if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
-        compile_error(ast_ctx(self), "failed to create STATE instruction");
-        return false;
-    }
-
-    self->expression.outr = (ir_value*)1;
-    return true;
-}
-
-bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
-{
-    ast_expression_codegen *cgen;
-    ir_value              **params;
-    ir_instr               *callinstr;
-    size_t i;
-
-    ir_value *funval = NULL;
-
-    /* return values are never lvalues */
-    if (lvalue) {
-        compile_error(ast_ctx(self), "not an l-value (function call)");
-        return false;
-    }
-
-    if (self->expression.outr) {
-        *out = self->expression.outr;
-        return true;
-    }
-
-    cgen = self->func->codegen;
-    if (!(*cgen)((ast_expression*)(self->func), func, false, &funval))
-        return false;
-    if (!funval)
-        return false;
-
-    params = NULL;
-
-    /* parameters */
-    for (i = 0; i < vec_size(self->params); ++i)
-    {
-        ir_value *param;
-        ast_expression *expr = self->params[i];
-
-        cgen = expr->codegen;
-        if (!(*cgen)(expr, func, false, &param))
-            goto error;
-        if (!param)
-            goto error;
-        vec_push(params, param);
-    }
-
-    /* varargs counter */
-    if (self->va_count) {
-        ir_value   *va_count;
-        ir_builder *builder = func->curblock->owner->owner;
-        cgen = self->va_count->codegen;
-        if (!(*cgen)((ast_expression*)(self->va_count), func, false, &va_count))
-            return false;
-        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), INSTR_STORE_F,
-                                      ir_builder_get_va_count(builder), va_count))
-        {
-            return false;
-        }
-    }
-
-    callinstr = ir_block_create_call(func->curblock, ast_ctx(self),
-                                     ast_function_label(func, "call"),
-                                     funval, !!(self->func->flags & AST_FLAG_NORETURN));
-    if (!callinstr)
-        goto error;
-
-    for (i = 0; i < vec_size(params); ++i) {
-        ir_call_param(callinstr, params[i]);
-    }
-
-    *out = ir_call_value(callinstr);
-    self->expression.outr = *out;
-
-    codegen_output_type(self, *out);
-
-    vec_free(params);
-    return true;
-error:
-    vec_free(params);
-    return false;
-}
diff --git a/ast.cpp b/ast.cpp
new file mode 100644 (file)
index 0000000..29e298a
--- /dev/null
+++ b/ast.cpp
@@ -0,0 +1,3463 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "gmqcc.h"
+#include "ast.h"
+#include "parser.h"
+
+#define ast_instantiate(T, ctx, destroyfn)                          \
+    T* self = (T*)mem_a(sizeof(T));                                 \
+    if (!self) {                                                    \
+        return NULL;                                                \
+    }                                                               \
+    ast_node_init((ast_node*)self, ctx, TYPE_##T);                  \
+    ( (ast_node*)self )->destroy = (ast_node_delete*)destroyfn
+
+/*
+ * forward declarations, these need not be in ast.h for obvious
+ * static reasons.
+ */
+static bool ast_member_codegen(ast_member*, ast_function*, bool lvalue, ir_value**);
+static void ast_array_index_delete(ast_array_index*);
+static bool ast_array_index_codegen(ast_array_index*, ast_function*, bool lvalue, ir_value**);
+static void ast_argpipe_delete(ast_argpipe*);
+static bool ast_argpipe_codegen(ast_argpipe*, ast_function*, bool lvalue, ir_value**);
+static void ast_store_delete(ast_store*);
+static bool ast_store_codegen(ast_store*, ast_function*, bool lvalue, ir_value**);
+static void ast_ifthen_delete(ast_ifthen*);
+static bool ast_ifthen_codegen(ast_ifthen*, ast_function*, bool lvalue, ir_value**);
+static void ast_ternary_delete(ast_ternary*);
+static bool ast_ternary_codegen(ast_ternary*, ast_function*, bool lvalue, ir_value**);
+static void ast_loop_delete(ast_loop*);
+static bool ast_loop_codegen(ast_loop*, ast_function*, bool lvalue, ir_value**);
+static void ast_breakcont_delete(ast_breakcont*);
+static bool ast_breakcont_codegen(ast_breakcont*, ast_function*, bool lvalue, ir_value**);
+static void ast_switch_delete(ast_switch*);
+static bool ast_switch_codegen(ast_switch*, ast_function*, bool lvalue, ir_value**);
+static void ast_label_delete(ast_label*);
+static void ast_label_register_goto(ast_label*, ast_goto*);
+static bool ast_label_codegen(ast_label*, ast_function*, bool lvalue, ir_value**);
+static bool ast_goto_codegen(ast_goto*, ast_function*, bool lvalue, ir_value**);
+static void ast_goto_delete(ast_goto*);
+static void ast_call_delete(ast_call*);
+static bool ast_call_codegen(ast_call*, ast_function*, bool lvalue, ir_value**);
+static bool ast_block_codegen(ast_block*, ast_function*, bool lvalue, ir_value**);
+static void ast_unary_delete(ast_unary*);
+static bool ast_unary_codegen(ast_unary*, ast_function*, bool lvalue, ir_value**);
+static void ast_entfield_delete(ast_entfield*);
+static bool ast_entfield_codegen(ast_entfield*, ast_function*, bool lvalue, ir_value**);
+static void ast_return_delete(ast_return*);
+static bool ast_return_codegen(ast_return*, ast_function*, bool lvalue, ir_value**);
+static void ast_binstore_delete(ast_binstore*);
+static bool ast_binstore_codegen(ast_binstore*, ast_function*, bool lvalue, ir_value**);
+static void ast_binary_delete(ast_binary*);
+static bool ast_binary_codegen(ast_binary*, ast_function*, bool lvalue, ir_value**);
+static bool ast_state_codegen(ast_state*, ast_function*, bool lvalue, ir_value**);
+
+/* It must not be possible to get here. */
+static GMQCC_NORETURN void _ast_node_destroy(ast_node *self)
+{
+    (void)self;
+    con_err("ast node missing destroy()\n");
+    exit(EXIT_FAILURE);
+}
+
+/* Initialize main ast node aprts */
+static void ast_node_init(ast_node *self, lex_ctx_t ctx, int nodetype)
+{
+    self->context = ctx;
+    self->destroy = &_ast_node_destroy;
+    self->keep    = false;
+    self->nodetype = nodetype;
+    self->side_effects = false;
+}
+
+/* weight and side effects */
+static void _ast_propagate_effects(ast_node *self, ast_node *other)
+{
+    if (ast_side_effects(other))
+        ast_side_effects(self) = true;
+}
+#define ast_propagate_effects(s,o) _ast_propagate_effects(((ast_node*)(s)), ((ast_node*)(o)))
+
+/* General expression initialization */
+static void ast_expression_init(ast_expression *self,
+                                ast_expression_codegen *codegen)
+{
+    self->codegen  = codegen;
+    self->vtype    = TYPE_VOID;
+    self->next     = NULL;
+    self->outl     = NULL;
+    self->outr     = NULL;
+    self->params   = NULL;
+    self->count    = 0;
+    self->varparam = NULL;
+    self->flags    = 0;
+    if (OPTS_OPTION_BOOL(OPTION_COVERAGE))
+        self->flags |= AST_FLAG_BLOCK_COVERAGE;
+}
+
+static void ast_expression_delete(ast_expression *self)
+{
+    size_t i;
+    if (self->next)
+        ast_delete(self->next);
+    for (i = 0; i < vec_size(self->params); ++i) {
+        ast_delete(self->params[i]);
+    }
+    vec_free(self->params);
+    if (self->varparam)
+        ast_delete(self->varparam);
+}
+
+static void ast_expression_delete_full(ast_expression *self)
+{
+    ast_expression_delete(self);
+    mem_d(self);
+}
+
+ast_value* ast_value_copy(const ast_value *self)
+{
+    size_t i;
+    const ast_expression *fromex;
+    ast_expression       *selfex;
+    ast_value *cp = ast_value_new(self->expression.node.context, self->name, self->expression.vtype);
+    if (self->expression.next) {
+        cp->expression.next = ast_type_copy(self->expression.node.context, self->expression.next);
+    }
+    fromex   = &self->expression;
+    selfex = &cp->expression;
+    selfex->count    = fromex->count;
+    selfex->flags    = fromex->flags;
+    for (i = 0; i < vec_size(fromex->params); ++i) {
+        ast_value *v = ast_value_copy(fromex->params[i]);
+        vec_push(selfex->params, v);
+    }
+    return cp;
+}
+
+void ast_type_adopt_impl(ast_expression *self, const ast_expression *other)
+{
+    size_t i;
+    const ast_expression *fromex;
+    ast_expression       *selfex;
+    self->vtype = other->vtype;
+    if (other->next) {
+        self->next = (ast_expression*)ast_type_copy(ast_ctx(self), other->next);
+    }
+    fromex = other;
+    selfex = self;
+    selfex->count    = fromex->count;
+    selfex->flags    = fromex->flags;
+    for (i = 0; i < vec_size(fromex->params); ++i) {
+        ast_value *v = ast_value_copy(fromex->params[i]);
+        vec_push(selfex->params, v);
+    }
+}
+
+static ast_expression* ast_shallow_type(lex_ctx_t ctx, int vtype)
+{
+    ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
+    ast_expression_init(self, NULL);
+    self->codegen = NULL;
+    self->next    = NULL;
+    self->vtype   = vtype;
+    return self;
+}
+
+ast_expression* ast_type_copy(lex_ctx_t ctx, const ast_expression *ex)
+{
+    size_t i;
+    const ast_expression *fromex;
+    ast_expression       *selfex;
+
+    if (!ex)
+        return NULL;
+    else
+    {
+        ast_instantiate(ast_expression, ctx, ast_expression_delete_full);
+        ast_expression_init(self, NULL);
+
+        fromex = ex;
+        selfex = self;
+
+        /* This may never be codegen()d */
+        selfex->codegen = NULL;
+
+        selfex->vtype = fromex->vtype;
+        if (fromex->next)
+            selfex->next = ast_type_copy(ctx, fromex->next);
+        else
+            selfex->next = NULL;
+
+        selfex->count    = fromex->count;
+        selfex->flags    = fromex->flags;
+        for (i = 0; i < vec_size(fromex->params); ++i) {
+            ast_value *v = ast_value_copy(fromex->params[i]);
+            vec_push(selfex->params, v);
+        }
+
+        return self;
+    }
+}
+
+bool ast_compare_type(ast_expression *a, ast_expression *b)
+{
+    if (a->vtype == TYPE_NIL ||
+        b->vtype == TYPE_NIL)
+        return true;
+    if (a->vtype != b->vtype)
+        return false;
+    if (!a->next != !b->next)
+        return false;
+    if (vec_size(a->params) != vec_size(b->params))
+        return false;
+    if ((a->flags & AST_FLAG_TYPE_MASK) !=
+        (b->flags & AST_FLAG_TYPE_MASK) )
+    {
+        return false;
+    }
+    if (vec_size(a->params)) {
+        size_t i;
+        for (i = 0; i < vec_size(a->params); ++i) {
+            if (!ast_compare_type((ast_expression*)a->params[i],
+                                  (ast_expression*)b->params[i]))
+                return false;
+        }
+    }
+    if (a->next)
+        return ast_compare_type(a->next, b->next);
+    return true;
+}
+
+static size_t ast_type_to_string_impl(ast_expression *e, char *buf, size_t bufsize, size_t pos)
+{
+    const char *typestr;
+    size_t typelen;
+    size_t i;
+
+    if (!e) {
+        if (pos + 6 >= bufsize)
+            goto full;
+        util_strncpy(buf + pos, "(null)", 6);
+        return pos + 6;
+    }
+
+    if (pos + 1 >= bufsize)
+        goto full;
+
+    switch (e->vtype) {
+        case TYPE_VARIANT:
+            util_strncpy(buf + pos, "(variant)", 9);
+            return pos + 9;
+
+        case TYPE_FIELD:
+            buf[pos++] = '.';
+            return ast_type_to_string_impl(e->next, buf, bufsize, pos);
+
+        case TYPE_POINTER:
+            if (pos + 3 >= bufsize)
+                goto full;
+            buf[pos++] = '*';
+            buf[pos++] = '(';
+            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = ')';
+            return pos;
+
+        case TYPE_FUNCTION:
+            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
+            if (pos + 2 >= bufsize)
+                goto full;
+            if (!vec_size(e->params)) {
+                buf[pos++] = '(';
+                buf[pos++] = ')';
+                return pos;
+            }
+            buf[pos++] = '(';
+            pos = ast_type_to_string_impl((ast_expression*)(e->params[0]), buf, bufsize, pos);
+            for (i = 1; i < vec_size(e->params); ++i) {
+                if (pos + 2 >= bufsize)
+                    goto full;
+                buf[pos++] = ',';
+                buf[pos++] = ' ';
+                pos = ast_type_to_string_impl((ast_expression*)(e->params[i]), buf, bufsize, pos);
+            }
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = ')';
+            return pos;
+
+        case TYPE_ARRAY:
+            pos = ast_type_to_string_impl(e->next, buf, bufsize, pos);
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = '[';
+            pos += util_snprintf(buf + pos, bufsize - pos - 1, "%i", (int)e->count);
+            if (pos + 1 >= bufsize)
+                goto full;
+            buf[pos++] = ']';
+            return pos;
+
+        default:
+            typestr = type_name[e->vtype];
+            typelen = strlen(typestr);
+            if (pos + typelen >= bufsize)
+                goto full;
+            util_strncpy(buf + pos, typestr, typelen);
+            return pos + typelen;
+    }
+
+full:
+    buf[bufsize-3] = '.';
+    buf[bufsize-2] = '.';
+    buf[bufsize-1] = '.';
+    return bufsize;
+}
+
+void ast_type_to_string(ast_expression *e, char *buf, size_t bufsize)
+{
+    size_t pos = ast_type_to_string_impl(e, buf, bufsize-1, 0);
+    buf[pos] = 0;
+}
+
+static bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out);
+ast_value* ast_value_new(lex_ctx_t ctx, const char *name, int t)
+{
+    ast_instantiate(ast_value, ctx, ast_value_delete);
+    ast_expression_init((ast_expression*)self,
+                        (ast_expression_codegen*)&ast_value_codegen);
+    self->expression.node.keep = true; /* keep */
+
+    self->name = name ? util_strdup(name) : NULL;
+    self->expression.vtype = t;
+    self->expression.next  = NULL;
+    self->isfield  = false;
+    self->cvq      = CV_NONE;
+    self->hasvalue = false;
+    self->isimm    = false;
+    self->inexact  = false;
+    self->uses     = 0;
+    memset(&self->constval, 0, sizeof(self->constval));
+    self->initlist = NULL;
+
+    self->ir_v           = NULL;
+    self->ir_values      = NULL;
+    self->ir_value_count = 0;
+
+    self->setter = NULL;
+    self->getter = NULL;
+    self->desc   = NULL;
+
+    self->argcounter = NULL;
+    self->intrinsic = false;
+
+    return self;
+}
+
+void ast_value_delete(ast_value* self)
+{
+    if (self->name)
+        mem_d((void*)self->name);
+    if (self->argcounter)
+        mem_d((void*)self->argcounter);
+    if (self->hasvalue) {
+        switch (self->expression.vtype)
+        {
+        case TYPE_STRING:
+            mem_d((void*)self->constval.vstring);
+            break;
+        case TYPE_FUNCTION:
+            /* unlink us from the function node */
+            self->constval.vfunc->vtype = NULL;
+            break;
+        /* NOTE: delete function? currently collected in
+         * the parser structure
+         */
+        default:
+            break;
+        }
+    }
+    if (self->ir_values)
+        mem_d(self->ir_values);
+
+    if (self->desc)
+        mem_d(self->desc);
+
+    if (self->initlist) {
+        if (self->expression.next->vtype == TYPE_STRING) {
+            /* strings are allocated, free them */
+            size_t i, len = vec_size(self->initlist);
+            /* in theory, len should be expression.count
+             * but let's not take any chances */
+            for (i = 0; i < len; ++i) {
+                if (self->initlist[i].vstring)
+                    mem_d(self->initlist[i].vstring);
+            }
+        }
+        vec_free(self->initlist);
+    }
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+void ast_value_params_add(ast_value *self, ast_value *p)
+{
+    vec_push(self->expression.params, p);
+}
+
+bool ast_value_set_name(ast_value *self, const char *name)
+{
+    if (self->name)
+        mem_d((void*)self->name);
+    self->name = util_strdup(name);
+    return !!self->name;
+}
+
+ast_binary* ast_binary_new(lex_ctx_t ctx, int op,
+                           ast_expression* left, ast_expression* right)
+{
+    ast_instantiate(ast_binary, ctx, ast_binary_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binary_codegen);
+
+    if (ast_istype(right, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
+        ast_unary      *unary  = ((ast_unary*)right);
+        ast_expression *normal = unary->operand;
+
+        /* make a-(-b) => a + b */
+        if (unary->op == VINSTR_NEG_F || unary->op == VINSTR_NEG_V) {
+            if (op == INSTR_SUB_F) {
+                op = INSTR_ADD_F;
+                right = normal;
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+            } else if (op == INSTR_SUB_V) {
+                op = INSTR_ADD_V;
+                right = normal;
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+            }
+        }
+    }
+
+    self->op = op;
+    self->left = left;
+    self->right = right;
+    self->right_first = false;
+
+    ast_propagate_effects(self, left);
+    ast_propagate_effects(self, right);
+
+    if (op >= INSTR_EQ_F && op <= INSTR_GT)
+        self->expression.vtype = TYPE_FLOAT;
+    else if (op == INSTR_AND || op == INSTR_OR) {
+        if (OPTS_FLAG(PERL_LOGIC))
+            ast_type_adopt(self, right);
+        else
+            self->expression.vtype = TYPE_FLOAT;
+    }
+    else if (op == INSTR_BITAND || op == INSTR_BITOR)
+        self->expression.vtype = TYPE_FLOAT;
+    else if (op == INSTR_MUL_VF || op == INSTR_MUL_FV)
+        self->expression.vtype = TYPE_VECTOR;
+    else if (op == INSTR_MUL_V)
+        self->expression.vtype = TYPE_FLOAT;
+    else
+        self->expression.vtype = left->vtype;
+
+    /* references all */
+    self->refs = AST_REF_ALL;
+
+    return self;
+}
+
+void ast_binary_delete(ast_binary *self)
+{
+    if (self->refs & AST_REF_LEFT)  ast_unref(self->left);
+    if (self->refs & AST_REF_RIGHT) ast_unref(self->right);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_binstore* ast_binstore_new(lex_ctx_t ctx, int storop, int op,
+                               ast_expression* left, ast_expression* right)
+{
+    ast_instantiate(ast_binstore, ctx, ast_binstore_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_binstore_codegen);
+
+    ast_side_effects(self) = true;
+
+    self->opstore = storop;
+    self->opbin   = op;
+    self->dest    = left;
+    self->source  = right;
+
+    self->keep_dest = false;
+
+    ast_type_adopt(self, left);
+    return self;
+}
+
+void ast_binstore_delete(ast_binstore *self)
+{
+    if (!self->keep_dest)
+        ast_unref(self->dest);
+    ast_unref(self->source);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_unary* ast_unary_new(lex_ctx_t ctx, int op,
+                         ast_expression *expr)
+{
+    ast_instantiate(ast_unary, ctx, ast_unary_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_unary_codegen);
+
+    self->op      = op;
+    self->operand = expr;
+
+
+    if (ast_istype(expr, ast_unary) && OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
+        ast_unary *prev = (ast_unary*)((ast_unary*)expr)->operand;
+
+        /* Handle for double negation */
+        if (((ast_unary*)expr)->op == op)
+            prev = (ast_unary*)((ast_unary*)expr)->operand;
+
+        if (ast_istype(prev, ast_unary)) {
+            ast_expression_delete((ast_expression*)self);
+            mem_d(self);
+            ++opts_optimizationcount[OPTIM_PEEPHOLE];
+            return prev;
+        }
+    }
+
+    ast_propagate_effects(self, expr);
+
+    if ((op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || op == VINSTR_NEG_F) {
+        self->expression.vtype = TYPE_FLOAT;
+    } else if (op == VINSTR_NEG_V) {
+        self->expression.vtype = TYPE_VECTOR;
+    } else {
+        compile_error(ctx, "cannot determine type of unary operation %s", util_instr_str[op]);
+    }
+
+    return self;
+}
+
+void ast_unary_delete(ast_unary *self)
+{
+    if (self->operand) ast_unref(self->operand);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_return* ast_return_new(lex_ctx_t ctx, ast_expression *expr)
+{
+    ast_instantiate(ast_return, ctx, ast_return_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_return_codegen);
+
+    self->operand = expr;
+
+    if (expr)
+        ast_propagate_effects(self, expr);
+
+    return self;
+}
+
+void ast_return_delete(ast_return *self)
+{
+    if (self->operand)
+        ast_unref(self->operand);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_entfield* ast_entfield_new(lex_ctx_t ctx, ast_expression *entity, ast_expression *field)
+{
+    if (field->vtype != TYPE_FIELD) {
+        compile_error(ctx, "ast_entfield_new with expression not of type field");
+        return NULL;
+    }
+    return ast_entfield_new_force(ctx, entity, field, field->next);
+}
+
+ast_entfield* ast_entfield_new_force(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype)
+{
+    ast_instantiate(ast_entfield, ctx, ast_entfield_delete);
+
+    if (!outtype) {
+        mem_d(self);
+        /* Error: field has no type... */
+        return NULL;
+    }
+
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_entfield_codegen);
+
+    self->entity = entity;
+    self->field  = field;
+    ast_propagate_effects(self, entity);
+    ast_propagate_effects(self, field);
+
+    ast_type_adopt(self, outtype);
+    return self;
+}
+
+void ast_entfield_delete(ast_entfield *self)
+{
+    ast_unref(self->entity);
+    ast_unref(self->field);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_member* ast_member_new(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const char *name)
+{
+    ast_instantiate(ast_member, ctx, ast_member_delete);
+    if (field >= 3) {
+        mem_d(self);
+        return NULL;
+    }
+
+    if (owner->vtype != TYPE_VECTOR &&
+        owner->vtype != TYPE_FIELD) {
+        compile_error(ctx, "member-access on an invalid owner of type %s", type_name[owner->vtype]);
+        mem_d(self);
+        return NULL;
+    }
+
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_member_codegen);
+    self->expression.node.keep = true; /* keep */
+
+    if (owner->vtype == TYPE_VECTOR) {
+        self->expression.vtype = TYPE_FLOAT;
+        self->expression.next  = NULL;
+    } else {
+        self->expression.vtype = TYPE_FIELD;
+        self->expression.next = ast_shallow_type(ctx, TYPE_FLOAT);
+    }
+
+    self->rvalue = false;
+    self->owner  = owner;
+    ast_propagate_effects(self, owner);
+
+    self->field = field;
+    if (name)
+        self->name = util_strdup(name);
+    else
+        self->name = NULL;
+
+    return self;
+}
+
+void ast_member_delete(ast_member *self)
+{
+    /* The owner is always an ast_value, which has .keep=true,
+     * also: ast_members are usually deleted after the owner, thus
+     * this will cause invalid access
+    ast_unref(self->owner);
+     * once we allow (expression).x to access a vector-member, we need
+     * to change this: preferably by creating an alternate ast node for this
+     * purpose that is not garbage-collected.
+    */
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self->name);
+    mem_d(self);
+}
+
+bool ast_member_set_name(ast_member *self, const char *name)
+{
+    if (self->name)
+        mem_d((void*)self->name);
+    self->name = util_strdup(name);
+    return !!self->name;
+}
+
+ast_array_index* ast_array_index_new(lex_ctx_t ctx, ast_expression *array, ast_expression *index)
+{
+    ast_expression *outtype;
+    ast_instantiate(ast_array_index, ctx, ast_array_index_delete);
+
+    outtype = array->next;
+    if (!outtype) {
+        mem_d(self);
+        /* Error: field has no type... */
+        return NULL;
+    }
+
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_array_index_codegen);
+
+    self->array = array;
+    self->index = index;
+    ast_propagate_effects(self, array);
+    ast_propagate_effects(self, index);
+
+    ast_type_adopt(self, outtype);
+    if (array->vtype == TYPE_FIELD && outtype->vtype == TYPE_ARRAY) {
+        if (self->expression.vtype != TYPE_ARRAY) {
+            compile_error(ast_ctx(self), "array_index node on type");
+            ast_array_index_delete(self);
+            return NULL;
+        }
+        self->array = outtype;
+        self->expression.vtype = TYPE_FIELD;
+    }
+
+    return self;
+}
+
+void ast_array_index_delete(ast_array_index *self)
+{
+    if (self->array)
+        ast_unref(self->array);
+    if (self->index)
+        ast_unref(self->index);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_argpipe* ast_argpipe_new(lex_ctx_t ctx, ast_expression *index)
+{
+    ast_instantiate(ast_argpipe, ctx, ast_argpipe_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_argpipe_codegen);
+    self->index = index;
+    self->expression.vtype = TYPE_NOEXPR;
+    return self;
+}
+
+void ast_argpipe_delete(ast_argpipe *self)
+{
+    if (self->index)
+        ast_unref(self->index);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_ifthen* ast_ifthen_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
+{
+    ast_instantiate(ast_ifthen, ctx, ast_ifthen_delete);
+    if (!ontrue && !onfalse) {
+        /* because it is invalid */
+        mem_d(self);
+        return NULL;
+    }
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ifthen_codegen);
+
+    self->cond     = cond;
+    self->on_true  = ontrue;
+    self->on_false = onfalse;
+    ast_propagate_effects(self, cond);
+    if (ontrue)
+        ast_propagate_effects(self, ontrue);
+    if (onfalse)
+        ast_propagate_effects(self, onfalse);
+
+    return self;
+}
+
+void ast_ifthen_delete(ast_ifthen *self)
+{
+    ast_unref(self->cond);
+    if (self->on_true)
+        ast_unref(self->on_true);
+    if (self->on_false)
+        ast_unref(self->on_false);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_ternary* ast_ternary_new(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse)
+{
+    ast_expression *exprtype = ontrue;
+    ast_instantiate(ast_ternary, ctx, ast_ternary_delete);
+    /* This time NEITHER must be NULL */
+    if (!ontrue || !onfalse) {
+        mem_d(self);
+        return NULL;
+    }
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_ternary_codegen);
+
+    self->cond     = cond;
+    self->on_true  = ontrue;
+    self->on_false = onfalse;
+    ast_propagate_effects(self, cond);
+    ast_propagate_effects(self, ontrue);
+    ast_propagate_effects(self, onfalse);
+
+    if (ontrue->vtype == TYPE_NIL)
+        exprtype = onfalse;
+    ast_type_adopt(self, exprtype);
+
+    return self;
+}
+
+void ast_ternary_delete(ast_ternary *self)
+{
+    /* the if()s are only there because computed-gotos can set them
+     * to NULL
+     */
+    if (self->cond)     ast_unref(self->cond);
+    if (self->on_true)  ast_unref(self->on_true);
+    if (self->on_false) ast_unref(self->on_false);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_loop* ast_loop_new(lex_ctx_t ctx,
+                       ast_expression *initexpr,
+                       ast_expression *precond, bool pre_not,
+                       ast_expression *postcond, bool post_not,
+                       ast_expression *increment,
+                       ast_expression *body)
+{
+    ast_instantiate(ast_loop, ctx, ast_loop_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_loop_codegen);
+
+    self->initexpr  = initexpr;
+    self->precond   = precond;
+    self->postcond  = postcond;
+    self->increment = increment;
+    self->body      = body;
+
+    self->pre_not   = pre_not;
+    self->post_not  = post_not;
+
+    if (initexpr)
+        ast_propagate_effects(self, initexpr);
+    if (precond)
+        ast_propagate_effects(self, precond);
+    if (postcond)
+        ast_propagate_effects(self, postcond);
+    if (increment)
+        ast_propagate_effects(self, increment);
+    if (body)
+        ast_propagate_effects(self, body);
+
+    return self;
+}
+
+void ast_loop_delete(ast_loop *self)
+{
+    if (self->initexpr)
+        ast_unref(self->initexpr);
+    if (self->precond)
+        ast_unref(self->precond);
+    if (self->postcond)
+        ast_unref(self->postcond);
+    if (self->increment)
+        ast_unref(self->increment);
+    if (self->body)
+        ast_unref(self->body);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_breakcont* ast_breakcont_new(lex_ctx_t ctx, bool iscont, unsigned int levels)
+{
+    ast_instantiate(ast_breakcont, ctx, ast_breakcont_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_breakcont_codegen);
+
+    self->is_continue = iscont;
+    self->levels      = levels;
+
+    return self;
+}
+
+void ast_breakcont_delete(ast_breakcont *self)
+{
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_switch* ast_switch_new(lex_ctx_t ctx, ast_expression *op)
+{
+    ast_instantiate(ast_switch, ctx, ast_switch_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_switch_codegen);
+
+    self->operand = op;
+    self->cases   = NULL;
+
+    ast_propagate_effects(self, op);
+
+    return self;
+}
+
+void ast_switch_delete(ast_switch *self)
+{
+    size_t i;
+    ast_unref(self->operand);
+
+    for (i = 0; i < vec_size(self->cases); ++i) {
+        if (self->cases[i].value)
+            ast_unref(self->cases[i].value);
+        ast_unref(self->cases[i].code);
+    }
+    vec_free(self->cases);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_label* ast_label_new(lex_ctx_t ctx, const char *name, bool undefined)
+{
+    ast_instantiate(ast_label, ctx, ast_label_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_label_codegen);
+
+    self->expression.vtype = TYPE_NOEXPR;
+
+    self->name      = util_strdup(name);
+    self->irblock   = NULL;
+    self->gotos     = NULL;
+    self->undefined = undefined;
+
+    return self;
+}
+
+void ast_label_delete(ast_label *self)
+{
+    mem_d((void*)self->name);
+    vec_free(self->gotos);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+static void ast_label_register_goto(ast_label *self, ast_goto *g)
+{
+    vec_push(self->gotos, g);
+}
+
+ast_goto* ast_goto_new(lex_ctx_t ctx, const char *name)
+{
+    ast_instantiate(ast_goto, ctx, ast_goto_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_goto_codegen);
+
+    self->name    = util_strdup(name);
+    self->target  = NULL;
+    self->irblock_from = NULL;
+
+    return self;
+}
+
+void ast_goto_delete(ast_goto *self)
+{
+    mem_d((void*)self->name);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+void ast_goto_set_label(ast_goto *self, ast_label *label)
+{
+    self->target = label;
+}
+
+ast_state* ast_state_new(lex_ctx_t ctx, ast_expression *frame, ast_expression *think)
+{
+    ast_instantiate(ast_state, ctx, ast_state_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_state_codegen);
+    self->framenum  = frame;
+    self->nextthink = think;
+    return self;
+}
+
+void ast_state_delete(ast_state *self)
+{
+    if (self->framenum)
+        ast_unref(self->framenum);
+    if (self->nextthink)
+        ast_unref(self->nextthink);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_call* ast_call_new(lex_ctx_t ctx,
+                       ast_expression *funcexpr)
+{
+    ast_instantiate(ast_call, ctx, ast_call_delete);
+    if (!funcexpr->next) {
+        compile_error(ctx, "not a function");
+        mem_d(self);
+        return NULL;
+    }
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_call_codegen);
+
+    ast_side_effects(self) = true;
+
+    self->params   = NULL;
+    self->func     = funcexpr;
+    self->va_count = NULL;
+
+    ast_type_adopt(self, funcexpr->next);
+
+    return self;
+}
+
+void ast_call_delete(ast_call *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->params); ++i)
+        ast_unref(self->params[i]);
+    vec_free(self->params);
+
+    if (self->func)
+        ast_unref(self->func);
+
+    if (self->va_count)
+        ast_unref(self->va_count);
+
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+static bool ast_call_check_vararg(ast_call *self, ast_expression *va_type, ast_expression *exp_type)
+{
+    char texp[1024];
+    char tgot[1024];
+    if (!exp_type)
+        return true;
+    if (!va_type || !ast_compare_type(va_type, exp_type))
+    {
+        if (va_type && exp_type)
+        {
+            ast_type_to_string(va_type,  tgot, sizeof(tgot));
+            ast_type_to_string(exp_type, texp, sizeof(texp));
+            if (OPTS_FLAG(UNSAFE_VARARGS)) {
+                if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES,
+                                    "piped variadic argument differs in type: constrained to type %s, expected type %s",
+                                    tgot, texp))
+                    return false;
+            } else {
+                compile_error(ast_ctx(self),
+                              "piped variadic argument differs in type: constrained to type %s, expected type %s",
+                              tgot, texp);
+                return false;
+            }
+        }
+        else
+        {
+            ast_type_to_string(exp_type, texp, sizeof(texp));
+            if (OPTS_FLAG(UNSAFE_VARARGS)) {
+                if (compile_warning(ast_ctx(self), WARN_UNSAFE_TYPES,
+                                    "piped variadic argument may differ in type: expected type %s",
+                                    texp))
+                    return false;
+            } else {
+                compile_error(ast_ctx(self),
+                              "piped variadic argument may differ in type: expected type %s",
+                              texp);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool ast_call_check_types(ast_call *self, ast_expression *va_type)
+{
+    char texp[1024];
+    char tgot[1024];
+    size_t i;
+    bool   retval = true;
+    const  ast_expression *func = self->func;
+    size_t count = vec_size(self->params);
+    if (count > vec_size(func->params))
+        count = vec_size(func->params);
+
+    for (i = 0; i < count; ++i) {
+        if (ast_istype(self->params[i], ast_argpipe)) {
+            /* warn about type safety instead */
+            if (i+1 != count) {
+                compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call");
+                return false;
+            }
+            if (!ast_call_check_vararg(self, va_type, (ast_expression*)func->params[i]))
+                retval = false;
+        }
+        else if (!ast_compare_type(self->params[i], (ast_expression*)(func->params[i])))
+        {
+            ast_type_to_string(self->params[i], tgot, sizeof(tgot));
+            ast_type_to_string((ast_expression*)func->params[i], texp, sizeof(texp));
+            compile_error(ast_ctx(self), "invalid type for parameter %u in function call: expected %s, got %s",
+                     (unsigned int)(i+1), texp, tgot);
+            /* we don't immediately return */
+            retval = false;
+        }
+    }
+    count = vec_size(self->params);
+    if (count > vec_size(func->params) && func->varparam) {
+        for (; i < count; ++i) {
+            if (ast_istype(self->params[i], ast_argpipe)) {
+                /* warn about type safety instead */
+                if (i+1 != count) {
+                    compile_error(ast_ctx(self), "argpipe must be the last parameter to a function call");
+                    return false;
+                }
+                if (!ast_call_check_vararg(self, va_type, func->varparam))
+                    retval = false;
+            }
+            else if (!ast_compare_type(self->params[i], func->varparam))
+            {
+                ast_type_to_string(self->params[i], tgot, sizeof(tgot));
+                ast_type_to_string(func->varparam, texp, sizeof(texp));
+                compile_error(ast_ctx(self), "invalid type for variadic parameter %u in function call: expected %s, got %s",
+                         (unsigned int)(i+1), texp, tgot);
+                /* we don't immediately return */
+                retval = false;
+            }
+        }
+    }
+    return retval;
+}
+
+ast_store* ast_store_new(lex_ctx_t ctx, int op,
+                         ast_expression *dest, ast_expression *source)
+{
+    ast_instantiate(ast_store, ctx, ast_store_delete);
+    ast_expression_init((ast_expression*)self, (ast_expression_codegen*)&ast_store_codegen);
+
+    ast_side_effects(self) = true;
+
+    self->op = op;
+    self->dest = dest;
+    self->source = source;
+
+    ast_type_adopt(self, dest);
+
+    return self;
+}
+
+void ast_store_delete(ast_store *self)
+{
+    ast_unref(self->dest);
+    ast_unref(self->source);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+ast_block* ast_block_new(lex_ctx_t ctx)
+{
+    ast_instantiate(ast_block, ctx, ast_block_delete);
+    ast_expression_init((ast_expression*)self,
+                        (ast_expression_codegen*)&ast_block_codegen);
+
+    self->locals  = NULL;
+    self->exprs   = NULL;
+    self->collect = NULL;
+
+    return self;
+}
+
+bool ast_block_add_expr(ast_block *self, ast_expression *e)
+{
+    ast_propagate_effects(self, e);
+    vec_push(self->exprs, e);
+    if (self->expression.next) {
+        ast_delete(self->expression.next);
+        self->expression.next = NULL;
+    }
+    ast_type_adopt(self, e);
+    return true;
+}
+
+void ast_block_collect(ast_block *self, ast_expression *expr)
+{
+    vec_push(self->collect, expr);
+    expr->node.keep = true;
+}
+
+void ast_block_delete(ast_block *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->exprs); ++i)
+        ast_unref(self->exprs[i]);
+    vec_free(self->exprs);
+    for (i = 0; i < vec_size(self->locals); ++i)
+        ast_delete(self->locals[i]);
+    vec_free(self->locals);
+    for (i = 0; i < vec_size(self->collect); ++i)
+        ast_delete(self->collect[i]);
+    vec_free(self->collect);
+    ast_expression_delete((ast_expression*)self);
+    mem_d(self);
+}
+
+void ast_block_set_type(ast_block *self, ast_expression *from)
+{
+    if (self->expression.next)
+        ast_delete(self->expression.next);
+    ast_type_adopt(self, from);
+}
+
+ast_function* ast_function_new(lex_ctx_t ctx, const char *name, ast_value *vtype)
+{
+    ast_instantiate(ast_function, ctx, ast_function_delete);
+
+    if (!vtype) {
+        compile_error(ast_ctx(self), "internal error: ast_function_new condition 0");
+        goto cleanup;
+    } else if (vtype->hasvalue || vtype->expression.vtype != TYPE_FUNCTION) {
+        compile_error(ast_ctx(self), "internal error: ast_function_new condition %i %i type=%i (probably 2 bodies?)",
+                 (int)!vtype,
+                 (int)vtype->hasvalue,
+                 vtype->expression.vtype);
+        goto cleanup;
+    }
+
+    self->vtype  = vtype;
+    self->name   = name ? util_strdup(name) : NULL;
+    self->blocks = NULL;
+
+    self->labelcount = 0;
+    self->builtin = 0;
+
+    self->ir_func = NULL;
+    self->curblock = NULL;
+
+    self->breakblocks    = NULL;
+    self->continueblocks = NULL;
+
+    vtype->hasvalue = true;
+    vtype->constval.vfunc = self;
+
+    self->varargs          = NULL;
+    self->argc             = NULL;
+    self->fixedparams      = NULL;
+    self->return_value     = NULL;
+
+    self->static_names     = NULL;
+    self->static_count     = 0;
+
+    return self;
+
+cleanup:
+    mem_d(self);
+    return NULL;
+}
+
+void ast_function_delete(ast_function *self)
+{
+    size_t i;
+    if (self->name)
+        mem_d((void*)self->name);
+    if (self->vtype) {
+        /* ast_value_delete(self->vtype); */
+        self->vtype->hasvalue = false;
+        self->vtype->constval.vfunc = NULL;
+        /* We use unref - if it was stored in a global table it is supposed
+         * to be deleted from *there*
+         */
+        ast_unref(self->vtype);
+    }
+    for (i = 0; i < vec_size(self->static_names); ++i)
+        mem_d(self->static_names[i]);
+    vec_free(self->static_names);
+    for (i = 0; i < vec_size(self->blocks); ++i)
+        ast_delete(self->blocks[i]);
+    vec_free(self->blocks);
+    vec_free(self->breakblocks);
+    vec_free(self->continueblocks);
+    if (self->varargs)
+        ast_delete(self->varargs);
+    if (self->argc)
+        ast_delete(self->argc);
+    if (self->fixedparams)
+        ast_unref(self->fixedparams);
+    if (self->return_value)
+        ast_unref(self->return_value);
+    mem_d(self);
+}
+
+const char* ast_function_label(ast_function *self, const char *prefix)
+{
+    size_t id;
+    size_t len;
+    char  *from;
+
+    if (!OPTS_OPTION_BOOL(OPTION_DUMP)    &&
+        !OPTS_OPTION_BOOL(OPTION_DUMPFIN) &&
+        !OPTS_OPTION_BOOL(OPTION_DEBUG))
+    {
+        return NULL;
+    }
+
+    id  = (self->labelcount++);
+    len = strlen(prefix);
+
+    from = self->labelbuf + sizeof(self->labelbuf)-1;
+    *from-- = 0;
+    do {
+        *from-- = (id%10) + '0';
+        id /= 10;
+    } while (id);
+    ++from;
+    memcpy(from - len, prefix, len);
+    return from - len;
+}
+
+/*********************************************************************/
+/* AST codegen part
+ * by convention you must never pass NULL to the 'ir_value **out'
+ * parameter. If you really don't care about the output, pass a dummy.
+ * But I can't imagine a pituation where the output is truly unnecessary.
+ */
+
+static void _ast_codegen_output_type(ast_expression *self, ir_value *out)
+{
+    if (out->vtype == TYPE_FIELD)
+        out->fieldtype = self->next->vtype;
+    if (out->vtype == TYPE_FUNCTION)
+        out->outtype = self->next->vtype;
+}
+
+#define codegen_output_type(a,o) (_ast_codegen_output_type(&((a)->expression),(o)))
+
+bool ast_value_codegen(ast_value *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    (void)func;
+    (void)lvalue;
+    if (self->expression.vtype == TYPE_NIL) {
+        *out = func->ir_func->owner->nil;
+        return true;
+    }
+    /* NOTE: This is the codegen for a variable used in an expression.
+     * It is not the codegen to generate the value. For this purpose,
+     * ast_local_codegen and ast_global_codegen are to be used before this
+     * is executed. ast_function_codegen should take care of its locals,
+     * and the ast-user should take care of ast_global_codegen to be used
+     * on all the globals.
+     */
+    if (!self->ir_v) {
+        char tname[1024]; /* typename is reserved in C++ */
+        ast_type_to_string((ast_expression*)self, tname, sizeof(tname));
+        compile_error(ast_ctx(self), "ast_value used before generated %s %s", tname, self->name);
+        return false;
+    }
+    *out = self->ir_v;
+    return true;
+}
+
+static bool ast_global_array_set(ast_value *self)
+{
+    size_t count = vec_size(self->initlist);
+    size_t i;
+
+    if (count > self->expression.count) {
+        compile_error(ast_ctx(self), "too many elements in initializer");
+        count = self->expression.count;
+    }
+    else if (count < self->expression.count) {
+        /* add this?
+        compile_warning(ast_ctx(self), "not all elements are initialized");
+        */
+    }
+
+    for (i = 0; i != count; ++i) {
+        switch (self->expression.next->vtype) {
+            case TYPE_FLOAT:
+                if (!ir_value_set_float(self->ir_values[i], self->initlist[i].vfloat))
+                    return false;
+                break;
+            case TYPE_VECTOR:
+                if (!ir_value_set_vector(self->ir_values[i], self->initlist[i].vvec))
+                    return false;
+                break;
+            case TYPE_STRING:
+                if (!ir_value_set_string(self->ir_values[i], self->initlist[i].vstring))
+                    return false;
+                break;
+            case TYPE_ARRAY:
+                /* we don't support them in any other place yet either */
+                compile_error(ast_ctx(self), "TODO: nested arrays");
+                return false;
+            case TYPE_FUNCTION:
+                /* this requiers a bit more work - similar to the fields I suppose */
+                compile_error(ast_ctx(self), "global of type function not properly generated");
+                return false;
+            case TYPE_FIELD:
+                if (!self->initlist[i].vfield) {
+                    compile_error(ast_ctx(self), "field constant without vfield set");
+                    return false;
+                }
+                if (!self->initlist[i].vfield->ir_v) {
+                    compile_error(ast_ctx(self), "field constant generated before its field");
+                    return false;
+                }
+                if (!ir_value_set_field(self->ir_values[i], self->initlist[i].vfield->ir_v))
+                    return false;
+                break;
+            default:
+                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
+                break;
+        }
+    }
+    return true;
+}
+
+static bool check_array(ast_value *self, ast_value *array)
+{
+    if (array->expression.flags & AST_FLAG_ARRAY_INIT && !array->initlist) {
+        compile_error(ast_ctx(self), "array without size: %s", self->name);
+        return false;
+    }
+    /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
+    if (!array->expression.count || array->expression.count > OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE)) {
+        compile_error(ast_ctx(self), "Invalid array of size %lu", (unsigned long)array->expression.count);
+        return false;
+    }
+    return true;
+}
+
+bool ast_global_codegen(ast_value *self, ir_builder *ir, bool isfield)
+{
+    ir_value *v = NULL;
+
+    if (self->expression.vtype == TYPE_NIL) {
+        compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL");
+        return false;
+    }
+
+    if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION)
+    {
+        ir_function *func = ir_builder_create_function(ir, self->name, self->expression.next->vtype);
+        if (!func)
+            return false;
+        func->context = ast_ctx(self);
+        func->value->context = ast_ctx(self);
+
+        self->constval.vfunc->ir_func = func;
+        self->ir_v = func->value;
+        if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+            self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
+        if (self->expression.flags & AST_FLAG_ERASEABLE)
+            self->ir_v->flags |= IR_FLAG_ERASABLE;
+        if (self->expression.flags & AST_FLAG_BLOCK_COVERAGE)
+            func->flags |= IR_FLAG_BLOCK_COVERAGE;
+        /* The function is filled later on ast_function_codegen... */
+        return true;
+    }
+
+    if (isfield && self->expression.vtype == TYPE_FIELD) {
+        ast_expression *fieldtype = self->expression.next;
+
+        if (self->hasvalue) {
+            compile_error(ast_ctx(self), "TODO: constant field pointers with value");
+            goto error;
+        }
+
+        if (fieldtype->vtype == TYPE_ARRAY) {
+            size_t ai;
+            char   *name;
+            size_t  namelen;
+
+            ast_expression *elemtype;
+            int             vtype;
+            ast_value      *array = (ast_value*)fieldtype;
+
+            if (!ast_istype(fieldtype, ast_value)) {
+                compile_error(ast_ctx(self), "internal error: ast_value required");
+                return false;
+            }
+
+            if (!check_array(self, array))
+                return false;
+
+            elemtype = array->expression.next;
+            vtype = elemtype->vtype;
+
+            v = ir_builder_create_field(ir, self->name, vtype);
+            if (!v) {
+                compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name);
+                return false;
+            }
+            v->context = ast_ctx(self);
+            v->unique_life = true;
+            v->locked      = true;
+            array->ir_v = self->ir_v = v;
+
+            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+                self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
+            if (self->expression.flags & AST_FLAG_ERASEABLE)
+                self->ir_v->flags |= IR_FLAG_ERASABLE;
+
+            namelen = strlen(self->name);
+            name    = (char*)mem_a(namelen + 16);
+            util_strncpy(name, self->name, namelen);
+
+            array->ir_values = (ir_value**)mem_a(sizeof(array->ir_values[0]) * array->expression.count);
+            array->ir_values[0] = v;
+            for (ai = 1; ai < array->expression.count; ++ai) {
+                util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
+                array->ir_values[ai] = ir_builder_create_field(ir, name, vtype);
+                if (!array->ir_values[ai]) {
+                    mem_d(name);
+                    compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", name);
+                    return false;
+                }
+                array->ir_values[ai]->context = ast_ctx(self);
+                array->ir_values[ai]->unique_life = true;
+                array->ir_values[ai]->locked      = true;
+                if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+                    self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF;
+            }
+            mem_d(name);
+        }
+        else
+        {
+            v = ir_builder_create_field(ir, self->name, self->expression.next->vtype);
+            if (!v)
+                return false;
+            v->context = ast_ctx(self);
+            self->ir_v = v;
+            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+                self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
+
+            if (self->expression.flags & AST_FLAG_ERASEABLE)
+                self->ir_v->flags |= IR_FLAG_ERASABLE;
+        }
+        return true;
+    }
+
+    if (self->expression.vtype == TYPE_ARRAY) {
+        size_t ai;
+        char   *name;
+        size_t  namelen;
+
+        ast_expression *elemtype = self->expression.next;
+        int vtype = elemtype->vtype;
+
+        if (self->expression.flags & AST_FLAG_ARRAY_INIT && !self->expression.count) {
+            compile_error(ast_ctx(self), "array `%s' has no size", self->name);
+            return false;
+        }
+
+        /* same as with field arrays */
+        if (!check_array(self, self))
+            return false;
+
+        v = ir_builder_create_global(ir, self->name, vtype);
+        if (!v) {
+            compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", self->name);
+            return false;
+        }
+        v->context = ast_ctx(self);
+        v->unique_life = true;
+        v->locked      = true;
+
+        if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+            v->flags |= IR_FLAG_INCLUDE_DEF;
+        if (self->expression.flags & AST_FLAG_ERASEABLE)
+            self->ir_v->flags |= IR_FLAG_ERASABLE;
+
+        namelen = strlen(self->name);
+        name    = (char*)mem_a(namelen + 16);
+        util_strncpy(name, self->name, namelen);
+
+        self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count);
+        self->ir_values[0] = v;
+        for (ai = 1; ai < self->expression.count; ++ai) {
+            util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
+            self->ir_values[ai] = ir_builder_create_global(ir, name, vtype);
+            if (!self->ir_values[ai]) {
+                mem_d(name);
+                compile_error(ast_ctx(self), "ir_builder_create_global failed `%s`", name);
+                return false;
+            }
+            self->ir_values[ai]->context = ast_ctx(self);
+            self->ir_values[ai]->unique_life = true;
+            self->ir_values[ai]->locked      = true;
+            if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+                self->ir_values[ai]->flags |= IR_FLAG_INCLUDE_DEF;
+        }
+        mem_d(name);
+    }
+    else
+    {
+        /* Arrays don't do this since there's no "array" value which spans across the
+         * whole thing.
+         */
+        v = ir_builder_create_global(ir, self->name, self->expression.vtype);
+        if (!v) {
+            compile_error(ast_ctx(self), "ir_builder_create_global failed on `%s`", self->name);
+            return false;
+        }
+        codegen_output_type(self, v);
+        v->context = ast_ctx(self);
+    }
+
+    /* link us to the ir_value */
+    v->cvq = self->cvq;
+    self->ir_v = v;
+
+    if (self->expression.flags & AST_FLAG_INCLUDE_DEF)
+        self->ir_v->flags |= IR_FLAG_INCLUDE_DEF;
+    if (self->expression.flags & AST_FLAG_ERASEABLE)
+        self->ir_v->flags |= IR_FLAG_ERASABLE;
+
+    /* initialize */
+    if (self->hasvalue) {
+        switch (self->expression.vtype)
+        {
+            case TYPE_FLOAT:
+                if (!ir_value_set_float(v, self->constval.vfloat))
+                    goto error;
+                break;
+            case TYPE_VECTOR:
+                if (!ir_value_set_vector(v, self->constval.vvec))
+                    goto error;
+                break;
+            case TYPE_STRING:
+                if (!ir_value_set_string(v, self->constval.vstring))
+                    goto error;
+                break;
+            case TYPE_ARRAY:
+                ast_global_array_set(self);
+                break;
+            case TYPE_FUNCTION:
+                compile_error(ast_ctx(self), "global of type function not properly generated");
+                goto error;
+                /* Cannot generate an IR value for a function,
+                 * need a pointer pointing to a function rather.
+                 */
+            case TYPE_FIELD:
+                if (!self->constval.vfield) {
+                    compile_error(ast_ctx(self), "field constant without vfield set");
+                    goto error;
+                }
+                if (!self->constval.vfield->ir_v) {
+                    compile_error(ast_ctx(self), "field constant generated before its field");
+                    goto error;
+                }
+                if (!ir_value_set_field(v, self->constval.vfield->ir_v))
+                    goto error;
+                break;
+            default:
+                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
+                break;
+        }
+    }
+    return true;
+
+error: /* clean up */
+    if(v) ir_value_delete(v);
+    return false;
+}
+
+static bool ast_local_codegen(ast_value *self, ir_function *func, bool param)
+{
+    ir_value *v = NULL;
+
+    if (self->expression.vtype == TYPE_NIL) {
+        compile_error(ast_ctx(self), "internal error: trying to generate a variable of TYPE_NIL");
+        return false;
+    }
+
+    if (self->hasvalue && self->expression.vtype == TYPE_FUNCTION)
+    {
+        /* Do we allow local functions? I think not...
+         * this is NOT a function pointer atm.
+         */
+        return false;
+    }
+
+    if (self->expression.vtype == TYPE_ARRAY) {
+        size_t ai;
+        char   *name;
+        size_t  namelen;
+
+        ast_expression *elemtype = self->expression.next;
+        int vtype = elemtype->vtype;
+
+        func->flags |= IR_FLAG_HAS_ARRAYS;
+
+        if (param && !(self->expression.flags & AST_FLAG_IS_VARARG)) {
+            compile_error(ast_ctx(self), "array-parameters are not supported");
+            return false;
+        }
+
+        /* we are lame now - considering the way QC works we won't tolerate arrays > 1024 elements */
+        if (!check_array(self, self))
+            return false;
+
+        self->ir_values = (ir_value**)mem_a(sizeof(self->ir_values[0]) * self->expression.count);
+        if (!self->ir_values) {
+            compile_error(ast_ctx(self), "failed to allocate array values");
+            return false;
+        }
+
+        v = ir_function_create_local(func, self->name, vtype, param);
+        if (!v) {
+            compile_error(ast_ctx(self), "internal error: ir_function_create_local failed");
+            return false;
+        }
+        v->context = ast_ctx(self);
+        v->unique_life = true;
+        v->locked      = true;
+
+        namelen = strlen(self->name);
+        name    = (char*)mem_a(namelen + 16);
+        util_strncpy(name, self->name, namelen);
+
+        self->ir_values[0] = v;
+        for (ai = 1; ai < self->expression.count; ++ai) {
+            util_snprintf(name + namelen, 16, "[%u]", (unsigned int)ai);
+            self->ir_values[ai] = ir_function_create_local(func, name, vtype, param);
+            if (!self->ir_values[ai]) {
+                compile_error(ast_ctx(self), "internal_error: ir_builder_create_global failed on `%s`", name);
+                return false;
+            }
+            self->ir_values[ai]->context = ast_ctx(self);
+            self->ir_values[ai]->unique_life = true;
+            self->ir_values[ai]->locked      = true;
+        }
+        mem_d(name);
+    }
+    else
+    {
+        v = ir_function_create_local(func, self->name, self->expression.vtype, param);
+        if (!v)
+            return false;
+        codegen_output_type(self, v);
+        v->context = ast_ctx(self);
+    }
+
+    /* A constant local... hmmm...
+     * I suppose the IR will have to deal with this
+     */
+    if (self->hasvalue) {
+        switch (self->expression.vtype)
+        {
+            case TYPE_FLOAT:
+                if (!ir_value_set_float(v, self->constval.vfloat))
+                    goto error;
+                break;
+            case TYPE_VECTOR:
+                if (!ir_value_set_vector(v, self->constval.vvec))
+                    goto error;
+                break;
+            case TYPE_STRING:
+                if (!ir_value_set_string(v, self->constval.vstring))
+                    goto error;
+                break;
+            default:
+                compile_error(ast_ctx(self), "TODO: global constant type %i", self->expression.vtype);
+                break;
+        }
+    }
+
+    /* link us to the ir_value */
+    v->cvq = self->cvq;
+    self->ir_v = v;
+
+    if (!ast_generate_accessors(self, func->owner))
+        return false;
+    return true;
+
+error: /* clean up */
+    ir_value_delete(v);
+    return false;
+}
+
+bool ast_generate_accessors(ast_value *self, ir_builder *ir)
+{
+    size_t i;
+    bool warn = OPTS_WARN(WARN_USED_UNINITIALIZED);
+    if (!self->setter || !self->getter)
+        return true;
+    for (i = 0; i < self->expression.count; ++i) {
+        if (!self->ir_values) {
+            compile_error(ast_ctx(self), "internal error: no array values generated for `%s`", self->name);
+            return false;
+        }
+        if (!self->ir_values[i]) {
+            compile_error(ast_ctx(self), "internal error: not all array values have been generated for `%s`", self->name);
+            return false;
+        }
+        if (self->ir_values[i]->life) {
+            compile_error(ast_ctx(self), "internal error: function containing `%s` already generated", self->name);
+            return false;
+        }
+    }
+
+    opts_set(opts.warn, WARN_USED_UNINITIALIZED, false);
+    if (self->setter) {
+        if (!ast_global_codegen  (self->setter, ir, false) ||
+            !ast_function_codegen(self->setter->constval.vfunc, ir) ||
+            !ir_function_finalize(self->setter->constval.vfunc->ir_func))
+        {
+            compile_error(ast_ctx(self), "internal error: failed to generate setter for `%s`", self->name);
+            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
+            return false;
+        }
+    }
+    if (self->getter) {
+        if (!ast_global_codegen  (self->getter, ir, false) ||
+            !ast_function_codegen(self->getter->constval.vfunc, ir) ||
+            !ir_function_finalize(self->getter->constval.vfunc->ir_func))
+        {
+            compile_error(ast_ctx(self), "internal error: failed to generate getter for `%s`", self->name);
+            opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
+            return false;
+        }
+    }
+    for (i = 0; i < self->expression.count; ++i) {
+        vec_free(self->ir_values[i]->life);
+    }
+    opts_set(opts.warn, WARN_USED_UNINITIALIZED, warn);
+    return true;
+}
+
+bool ast_function_codegen(ast_function *self, ir_builder *ir)
+{
+    ir_function *irf;
+    ir_value    *dummy;
+    ast_expression         *ec;
+    ast_expression_codegen *cgen;
+
+    size_t    i;
+
+    (void)ir;
+
+    irf = self->ir_func;
+    if (!irf) {
+        compile_error(ast_ctx(self), "internal error: ast_function's related ast_value was not generated yet");
+        return false;
+    }
+
+    /* fill the parameter list */
+    ec = &self->vtype->expression;
+    for (i = 0; i < vec_size(ec->params); ++i)
+    {
+        if (ec->params[i]->expression.vtype == TYPE_FIELD)
+            vec_push(irf->params, ec->params[i]->expression.next->vtype);
+        else
+            vec_push(irf->params, ec->params[i]->expression.vtype);
+        if (!self->builtin) {
+            if (!ast_local_codegen(ec->params[i], self->ir_func, true))
+                return false;
+        }
+    }
+
+    if (self->varargs) {
+        if (!ast_local_codegen(self->varargs, self->ir_func, true))
+            return false;
+        irf->max_varargs = self->varargs->expression.count;
+    }
+
+    if (self->builtin) {
+        irf->builtin = self->builtin;
+        return true;
+    }
+
+    /* have a local return value variable? */
+    if (self->return_value) {
+        if (!ast_local_codegen(self->return_value, self->ir_func, false))
+            return false;
+    }
+
+    if (!vec_size(self->blocks)) {
+        compile_error(ast_ctx(self), "function `%s` has no body", self->name);
+        return false;
+    }
+
+    irf->first = self->curblock = ir_function_create_block(ast_ctx(self), irf, "entry");
+    if (!self->curblock) {
+        compile_error(ast_ctx(self), "failed to allocate entry block for `%s`", self->name);
+        return false;
+    }
+
+    if (self->argc) {
+        ir_value *va_count;
+        ir_value *fixed;
+        ir_value *sub;
+        if (!ast_local_codegen(self->argc, self->ir_func, true))
+            return false;
+        cgen = self->argc->expression.codegen;
+        if (!(*cgen)((ast_expression*)(self->argc), self, false, &va_count))
+            return false;
+        cgen = self->fixedparams->expression.codegen;
+        if (!(*cgen)((ast_expression*)(self->fixedparams), self, false, &fixed))
+            return false;
+        sub = ir_block_create_binop(self->curblock, ast_ctx(self),
+                                    ast_function_label(self, "va_count"), INSTR_SUB_F,
+                                    ir_builder_get_va_count(ir), fixed);
+        if (!sub)
+            return false;
+        if (!ir_block_create_store_op(self->curblock, ast_ctx(self), INSTR_STORE_F,
+                                      va_count, sub))
+        {
+            return false;
+        }
+    }
+
+    for (i = 0; i < vec_size(self->blocks); ++i) {
+        cgen = self->blocks[i]->expression.codegen;
+        if (!(*cgen)((ast_expression*)self->blocks[i], self, false, &dummy))
+            return false;
+    }
+
+    /* TODO: check return types */
+    if (!self->curblock->final)
+    {
+        if (!self->vtype->expression.next ||
+            self->vtype->expression.next->vtype == TYPE_VOID)
+        {
+            return ir_block_create_return(self->curblock, ast_ctx(self), NULL);
+        }
+        else if (vec_size(self->curblock->entries) || self->curblock == irf->first)
+        {
+            if (self->return_value) {
+                cgen = self->return_value->expression.codegen;
+                if (!(*cgen)((ast_expression*)(self->return_value), self, false, &dummy))
+                    return false;
+                return ir_block_create_return(self->curblock, ast_ctx(self), dummy);
+            }
+            else if (compile_warning(ast_ctx(self), WARN_MISSING_RETURN_VALUES,
+                                "control reaches end of non-void function (`%s`) via %s",
+                                self->name, self->curblock->label))
+            {
+                return false;
+            }
+            return ir_block_create_return(self->curblock, ast_ctx(self), NULL);
+        }
+    }
+    return true;
+}
+
+static bool starts_a_label(ast_expression *ex)
+{
+    while (ex && ast_istype(ex, ast_block)) {
+        ast_block *b = (ast_block*)ex;
+        ex = b->exprs[0];
+    }
+    if (!ex)
+        return false;
+    return ast_istype(ex, ast_label);
+}
+
+/* Note, you will not see ast_block_codegen generate ir_blocks.
+ * To the AST and the IR, blocks are 2 different things.
+ * In the AST it represents a block of code, usually enclosed in
+ * curly braces {...}.
+ * While in the IR it represents a block in terms of control-flow.
+ */
+bool ast_block_codegen(ast_block *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    size_t i;
+
+    /* We don't use this
+     * Note: an ast-representation using the comma-operator
+     * of the form: (a, b, c) = x should not assign to c...
+     */
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (code-block)");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    /* output is NULL at first, we'll have each expression
+     * assign to out output, thus, a comma-operator represention
+     * using an ast_block will return the last generated value,
+     * so: (b, c) + a  executed both b and c, and returns c,
+     * which is then added to a.
+     */
+    *out = NULL;
+
+    /* generate locals */
+    for (i = 0; i < vec_size(self->locals); ++i)
+    {
+        if (!ast_local_codegen(self->locals[i], func->ir_func, false)) {
+            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
+                compile_error(ast_ctx(self), "failed to generate local `%s`", self->locals[i]->name);
+            return false;
+        }
+    }
+
+    for (i = 0; i < vec_size(self->exprs); ++i)
+    {
+        ast_expression_codegen *gen;
+        if (func->curblock->final && !starts_a_label(self->exprs[i])) {
+            if (compile_warning(ast_ctx(self->exprs[i]), WARN_UNREACHABLE_CODE, "unreachable statement"))
+                return false;
+            continue;
+        }
+        gen = self->exprs[i]->codegen;
+        if (!(*gen)(self->exprs[i], func, false, out))
+            return false;
+    }
+
+    self->expression.outr = *out;
+
+    return true;
+}
+
+bool ast_store_codegen(ast_store *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *left  = NULL;
+    ir_value *right = NULL;
+
+    ast_value       *arr;
+    ast_value       *idx = 0;
+    ast_array_index *ai = NULL;
+
+    if (lvalue && self->expression.outl) {
+        *out = self->expression.outl;
+        return true;
+    }
+
+    if (!lvalue && self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    if (ast_istype(self->dest, ast_array_index))
+    {
+
+        ai = (ast_array_index*)self->dest;
+        idx = (ast_value*)ai->index;
+
+        if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST)
+            ai = NULL;
+    }
+
+    if (ai) {
+        /* we need to call the setter */
+        ir_value  *iridx, *funval;
+        ir_instr  *call;
+
+        if (lvalue) {
+            compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues");
+            return false;
+        }
+
+        arr = (ast_value*)ai->array;
+        if (!ast_istype(ai->array, ast_value) || !arr->setter) {
+            compile_error(ast_ctx(self), "value has no setter (%s)", arr->name);
+            return false;
+        }
+
+        cgen = idx->expression.codegen;
+        if (!(*cgen)((ast_expression*)(idx), func, false, &iridx))
+            return false;
+
+        cgen = arr->setter->expression.codegen;
+        if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval))
+            return false;
+
+        cgen = self->source->codegen;
+        if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
+            return false;
+
+        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false);
+        if (!call)
+            return false;
+        ir_call_param(call, iridx);
+        ir_call_param(call, right);
+        self->expression.outr = right;
+    }
+    else
+    {
+        /* regular code */
+
+        cgen = self->dest->codegen;
+        /* lvalue! */
+        if (!(*cgen)((ast_expression*)(self->dest), func, true, &left))
+            return false;
+        self->expression.outl = left;
+
+        cgen = self->source->codegen;
+        /* rvalue! */
+        if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
+            return false;
+
+        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->op, left, right))
+            return false;
+        self->expression.outr = right;
+    }
+
+    /* Theoretically, an assinment returns its left side as an
+     * lvalue, if we don't need an lvalue though, we return
+     * the right side as an rvalue, otherwise we have to
+     * somehow know whether or not we need to dereference the pointer
+     * on the left side - that is: OP_LOAD if it was an address.
+     * Also: in original QC we cannot OP_LOADP *anyway*.
+     */
+    *out = (lvalue ? left : right);
+
+    return true;
+}
+
+bool ast_binary_codegen(ast_binary *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *left, *right;
+
+    /* A binary operation cannot yield an l-value */
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (binop)");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    if ((OPTS_FLAG(SHORT_LOGIC) || OPTS_FLAG(PERL_LOGIC)) &&
+        (self->op == INSTR_AND || self->op == INSTR_OR))
+    {
+        /* NOTE: The short-logic path will ignore right_first */
+
+        /* short circuit evaluation */
+        ir_block *other, *merge;
+        ir_block *from_left, *from_right;
+        ir_instr *phi;
+        size_t    merge_id;
+
+        /* prepare end-block */
+        merge_id = vec_size(func->ir_func->blocks);
+        merge    = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_merge"));
+
+        /* generate the left expression */
+        cgen = self->left->codegen;
+        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
+            return false;
+        /* remember the block */
+        from_left = func->curblock;
+
+        /* create a new block for the right expression */
+        other = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "sce_other"));
+        if (self->op == INSTR_AND) {
+            /* on AND: left==true -> other */
+            if (!ir_block_create_if(func->curblock, ast_ctx(self), left, other, merge))
+                return false;
+        } else {
+            /* on OR: left==false -> other */
+            if (!ir_block_create_if(func->curblock, ast_ctx(self), left, merge, other))
+                return false;
+        }
+        /* use the likely flag */
+        vec_last(func->curblock->instr)->likely = true;
+
+        /* enter the right-expression's block */
+        func->curblock = other;
+        /* generate */
+        cgen = self->right->codegen;
+        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
+            return false;
+        /* remember block */
+        from_right = func->curblock;
+
+        /* jump to the merge block */
+        if (!ir_block_create_jump(func->curblock, ast_ctx(self), merge))
+            return false;
+
+        vec_remove(func->ir_func->blocks, merge_id, 1);
+        vec_push(func->ir_func->blocks, merge);
+
+        func->curblock = merge;
+        phi = ir_block_create_phi(func->curblock, ast_ctx(self),
+                                  ast_function_label(func, "sce_value"),
+                                  self->expression.vtype);
+        ir_phi_add(phi, from_left, left);
+        ir_phi_add(phi, from_right, right);
+        *out = ir_phi_value(phi);
+        if (!*out)
+            return false;
+
+        if (!OPTS_FLAG(PERL_LOGIC)) {
+            /* cast-to-bool */
+            if (OPTS_FLAG(CORRECT_LOGIC) && (*out)->vtype == TYPE_VECTOR) {
+                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
+                                             ast_function_label(func, "sce_bool_v"),
+                                             INSTR_NOT_V, *out);
+                if (!*out)
+                    return false;
+                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
+                                             ast_function_label(func, "sce_bool"),
+                                             INSTR_NOT_F, *out);
+                if (!*out)
+                    return false;
+            }
+            else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && (*out)->vtype == TYPE_STRING) {
+                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
+                                             ast_function_label(func, "sce_bool_s"),
+                                             INSTR_NOT_S, *out);
+                if (!*out)
+                    return false;
+                *out = ir_block_create_unary(func->curblock, ast_ctx(self),
+                                             ast_function_label(func, "sce_bool"),
+                                             INSTR_NOT_F, *out);
+                if (!*out)
+                    return false;
+            }
+            else {
+                *out = ir_block_create_binop(func->curblock, ast_ctx(self),
+                                             ast_function_label(func, "sce_bool"),
+                                             INSTR_AND, *out, *out);
+                if (!*out)
+                    return false;
+            }
+        }
+
+        self->expression.outr = *out;
+        codegen_output_type(self, *out);
+        return true;
+    }
+
+    if (self->right_first) {
+        cgen = self->right->codegen;
+        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
+            return false;
+        cgen = self->left->codegen;
+        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
+            return false;
+    } else {
+        cgen = self->left->codegen;
+        if (!(*cgen)((ast_expression*)(self->left), func, false, &left))
+            return false;
+        cgen = self->right->codegen;
+        if (!(*cgen)((ast_expression*)(self->right), func, false, &right))
+            return false;
+    }
+
+    *out = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "bin"),
+                                 self->op, left, right);
+    if (!*out)
+        return false;
+    self->expression.outr = *out;
+    codegen_output_type(self, *out);
+
+    return true;
+}
+
+bool ast_binstore_codegen(ast_binstore *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *leftl = NULL, *leftr, *right, *bin;
+
+    ast_value       *arr;
+    ast_value       *idx = 0;
+    ast_array_index *ai = NULL;
+    ir_value        *iridx = NULL;
+
+    if (lvalue && self->expression.outl) {
+        *out = self->expression.outl;
+        return true;
+    }
+
+    if (!lvalue && self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    if (ast_istype(self->dest, ast_array_index))
+    {
+
+        ai = (ast_array_index*)self->dest;
+        idx = (ast_value*)ai->index;
+
+        if (ast_istype(ai->index, ast_value) && idx->hasvalue && idx->cvq == CV_CONST)
+            ai = NULL;
+    }
+
+    /* for a binstore we need both an lvalue and an rvalue for the left side */
+    /* rvalue of destination! */
+    if (ai) {
+        cgen = idx->expression.codegen;
+        if (!(*cgen)((ast_expression*)(idx), func, false, &iridx))
+            return false;
+    }
+    cgen = self->dest->codegen;
+    if (!(*cgen)((ast_expression*)(self->dest), func, false, &leftr))
+        return false;
+
+    /* source as rvalue only */
+    cgen = self->source->codegen;
+    if (!(*cgen)((ast_expression*)(self->source), func, false, &right))
+        return false;
+
+    /* now the binary */
+    bin = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "binst"),
+                                self->opbin, leftr, right);
+    self->expression.outr = bin;
+
+
+    if (ai) {
+        /* we need to call the setter */
+        ir_value  *funval;
+        ir_instr  *call;
+
+        if (lvalue) {
+            compile_error(ast_ctx(self), "array-subscript assignment cannot produce lvalues");
+            return false;
+        }
+
+        arr = (ast_value*)ai->array;
+        if (!ast_istype(ai->array, ast_value) || !arr->setter) {
+            compile_error(ast_ctx(self), "value has no setter (%s)", arr->name);
+            return false;
+        }
+
+        cgen = arr->setter->expression.codegen;
+        if (!(*cgen)((ast_expression*)(arr->setter), func, true, &funval))
+            return false;
+
+        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "store"), funval, false);
+        if (!call)
+            return false;
+        ir_call_param(call, iridx);
+        ir_call_param(call, bin);
+        self->expression.outr = bin;
+    } else {
+        /* now store them */
+        cgen = self->dest->codegen;
+        /* lvalue of destination */
+        if (!(*cgen)((ast_expression*)(self->dest), func, true, &leftl))
+            return false;
+        self->expression.outl = leftl;
+
+        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), self->opstore, leftl, bin))
+            return false;
+        self->expression.outr = bin;
+    }
+
+    /* Theoretically, an assinment returns its left side as an
+     * lvalue, if we don't need an lvalue though, we return
+     * the right side as an rvalue, otherwise we have to
+     * somehow know whether or not we need to dereference the pointer
+     * on the left side - that is: OP_LOAD if it was an address.
+     * Also: in original QC we cannot OP_LOADP *anyway*.
+     */
+    *out = (lvalue ? leftl : bin);
+
+    return true;
+}
+
+bool ast_unary_codegen(ast_unary *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *operand;
+
+    /* An unary operation cannot yield an l-value */
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (binop)");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    cgen = self->operand->codegen;
+    /* lvalue! */
+    if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
+        return false;
+
+    *out = ir_block_create_unary(func->curblock, ast_ctx(self), ast_function_label(func, "unary"),
+                                 self->op, operand);
+    if (!*out)
+        return false;
+    self->expression.outr = *out;
+
+    return true;
+}
+
+bool ast_return_codegen(ast_return *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *operand;
+
+    *out = NULL;
+
+    /* In the context of a return operation, we don't actually return
+     * anything...
+     */
+    if (lvalue) {
+        compile_error(ast_ctx(self), "return-expression is not an l-value");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_return cannot be reused, it bears no result!");
+        return false;
+    }
+    self->expression.outr = (ir_value*)1;
+
+    if (self->operand) {
+        cgen = self->operand->codegen;
+        /* lvalue! */
+        if (!(*cgen)((ast_expression*)(self->operand), func, false, &operand))
+            return false;
+
+        if (!ir_block_create_return(func->curblock, ast_ctx(self), operand))
+            return false;
+    } else {
+        if (!ir_block_create_return(func->curblock, ast_ctx(self), NULL))
+            return false;
+    }
+
+    return true;
+}
+
+bool ast_entfield_codegen(ast_entfield *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *ent, *field;
+
+    /* This function needs to take the 'lvalue' flag into account!
+     * As lvalue we provide a field-pointer, as rvalue we provide the
+     * value in a temp.
+     */
+
+    if (lvalue && self->expression.outl) {
+        *out = self->expression.outl;
+        return true;
+    }
+
+    if (!lvalue && self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    cgen = self->entity->codegen;
+    if (!(*cgen)((ast_expression*)(self->entity), func, false, &ent))
+        return false;
+
+    cgen = self->field->codegen;
+    if (!(*cgen)((ast_expression*)(self->field), func, false, &field))
+        return false;
+
+    if (lvalue) {
+        /* address! */
+        *out = ir_block_create_fieldaddress(func->curblock, ast_ctx(self), ast_function_label(func, "efa"),
+                                            ent, field);
+    } else {
+        *out = ir_block_create_load_from_ent(func->curblock, ast_ctx(self), ast_function_label(func, "efv"),
+                                             ent, field, self->expression.vtype);
+        /* Done AFTER error checking:
+        codegen_output_type(self, *out);
+        */
+    }
+    if (!*out) {
+        compile_error(ast_ctx(self), "failed to create %s instruction (output type %s)",
+                 (lvalue ? "ADDRESS" : "FIELD"),
+                 type_name[self->expression.vtype]);
+        return false;
+    }
+    if (!lvalue)
+        codegen_output_type(self, *out);
+
+    if (lvalue)
+        self->expression.outl = *out;
+    else
+        self->expression.outr = *out;
+
+    /* Hm that should be it... */
+    return true;
+}
+
+bool ast_member_codegen(ast_member *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value *vec;
+
+    /* in QC this is always an lvalue */
+    if (lvalue && self->rvalue) {
+        compile_error(ast_ctx(self), "not an l-value (member access)");
+        return false;
+    }
+    if (self->expression.outl) {
+        *out = self->expression.outl;
+        return true;
+    }
+
+    cgen = self->owner->codegen;
+    if (!(*cgen)((ast_expression*)(self->owner), func, false, &vec))
+        return false;
+
+    if (vec->vtype != TYPE_VECTOR &&
+        !(vec->vtype == TYPE_FIELD && self->owner->next->vtype == TYPE_VECTOR))
+    {
+        return false;
+    }
+
+    *out = ir_value_vector_member(vec, self->field);
+    self->expression.outl = *out;
+
+    return (*out != NULL);
+}
+
+bool ast_array_index_codegen(ast_array_index *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_value *arr;
+    ast_value *idx;
+
+    if (!lvalue && self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+    if (lvalue && self->expression.outl) {
+        *out = self->expression.outl;
+        return true;
+    }
+
+    if (!ast_istype(self->array, ast_value)) {
+        compile_error(ast_ctx(self), "array indexing this way is not supported");
+        /* note this would actually be pointer indexing because the left side is
+         * not an actual array but (hopefully) an indexable expression.
+         * Once we get integer arithmetic, and GADDRESS/GSTORE/GLOAD instruction
+         * support this path will be filled.
+         */
+        return false;
+    }
+
+    arr = (ast_value*)self->array;
+    idx = (ast_value*)self->index;
+
+    if (!ast_istype(self->index, ast_value) || !idx->hasvalue || idx->cvq != CV_CONST) {
+        /* Time to use accessor functions */
+        ast_expression_codegen *cgen;
+        ir_value               *iridx, *funval;
+        ir_instr               *call;
+
+        if (lvalue) {
+            compile_error(ast_ctx(self), "(.2) array indexing here needs a compile-time constant");
+            return false;
+        }
+
+        if (!arr->getter) {
+            compile_error(ast_ctx(self), "value has no getter, don't know how to index it");
+            return false;
+        }
+
+        cgen = self->index->codegen;
+        if (!(*cgen)((ast_expression*)(self->index), func, false, &iridx))
+            return false;
+
+        cgen = arr->getter->expression.codegen;
+        if (!(*cgen)((ast_expression*)(arr->getter), func, true, &funval))
+            return false;
+
+        call = ir_block_create_call(func->curblock, ast_ctx(self), ast_function_label(func, "fetch"), funval, false);
+        if (!call)
+            return false;
+        ir_call_param(call, iridx);
+
+        *out = ir_call_value(call);
+        self->expression.outr = *out;
+        (*out)->vtype = self->expression.vtype;
+        codegen_output_type(self, *out);
+        return true;
+    }
+
+    if (idx->expression.vtype == TYPE_FLOAT) {
+        unsigned int arridx = idx->constval.vfloat;
+        if (arridx >= self->array->count)
+        {
+            compile_error(ast_ctx(self), "array index out of bounds: %i", arridx);
+            return false;
+        }
+        *out = arr->ir_values[arridx];
+    }
+    else if (idx->expression.vtype == TYPE_INTEGER) {
+        unsigned int arridx = idx->constval.vint;
+        if (arridx >= self->array->count)
+        {
+            compile_error(ast_ctx(self), "array index out of bounds: %i", arridx);
+            return false;
+        }
+        *out = arr->ir_values[arridx];
+    }
+    else {
+        compile_error(ast_ctx(self), "array indexing here needs an integer constant");
+        return false;
+    }
+    (*out)->vtype = self->expression.vtype;
+    codegen_output_type(self, *out);
+    return true;
+}
+
+bool ast_argpipe_codegen(ast_argpipe *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    *out = NULL;
+    if (lvalue) {
+        compile_error(ast_ctx(self), "argpipe node: not an lvalue");
+        return false;
+    }
+    (void)func;
+    (void)out;
+    compile_error(ast_ctx(self), "TODO: argpipe codegen not implemented");
+    return false;
+}
+
+bool ast_ifthen_codegen(ast_ifthen *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ir_value *condval;
+    ir_value *dummy;
+
+    ir_block *cond;
+    ir_block *ontrue;
+    ir_block *onfalse;
+    ir_block *ontrue_endblock = NULL;
+    ir_block *onfalse_endblock = NULL;
+    ir_block *merge = NULL;
+    int       fold  = 0;
+
+    /* We don't output any value, thus also don't care about r/lvalue */
+    (void)out;
+    (void)lvalue;
+
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_ifthen cannot be reused, it bears no result!");
+        return false;
+    }
+    self->expression.outr = (ir_value*)1;
+
+    /* generate the condition */
+    cgen = self->cond->codegen;
+    if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval))
+        return false;
+    /* update the block which will get the jump - because short-logic or ternaries may have changed this */
+    cond = func->curblock;
+
+    /* try constant folding away the condition */
+    if ((fold = fold_cond_ifthen(condval, func, self)) != -1)
+        return fold;
+
+    if (self->on_true) {
+        /* create on-true block */
+        ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "ontrue"));
+        if (!ontrue)
+            return false;
+
+        /* enter the block */
+        func->curblock = ontrue;
+
+        /* generate */
+        cgen = self->on_true->codegen;
+        if (!(*cgen)((ast_expression*)(self->on_true), func, false, &dummy))
+            return false;
+
+        /* we now need to work from the current endpoint */
+        ontrue_endblock = func->curblock;
+    } else
+        ontrue = NULL;
+
+    /* on-false path */
+    if (self->on_false) {
+        /* create on-false block */
+        onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "onfalse"));
+        if (!onfalse)
+            return false;
+
+        /* enter the block */
+        func->curblock = onfalse;
+
+        /* generate */
+        cgen = self->on_false->codegen;
+        if (!(*cgen)((ast_expression*)(self->on_false), func, false, &dummy))
+            return false;
+
+        /* we now need to work from the current endpoint */
+        onfalse_endblock = func->curblock;
+    } else
+        onfalse = NULL;
+
+    /* Merge block were they all merge in to */
+    if (!ontrue || !onfalse || !ontrue_endblock->final || !onfalse_endblock->final)
+    {
+        merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "endif"));
+        if (!merge)
+            return false;
+        /* add jumps ot the merge block */
+        if (ontrue && !ontrue_endblock->final && !ir_block_create_jump(ontrue_endblock, ast_ctx(self), merge))
+            return false;
+        if (onfalse && !onfalse_endblock->final && !ir_block_create_jump(onfalse_endblock, ast_ctx(self), merge))
+            return false;
+
+        /* Now enter the merge block */
+        func->curblock = merge;
+    }
+
+    /* we create the if here, that way all blocks are ordered :)
+     */
+    if (!ir_block_create_if(cond, ast_ctx(self), condval,
+                            (ontrue  ? ontrue  : merge),
+                            (onfalse ? onfalse : merge)))
+    {
+        return false;
+    }
+
+    return true;
+}
+
+bool ast_ternary_codegen(ast_ternary *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ir_value *condval;
+    ir_value *trueval, *falseval;
+    ir_instr *phi;
+
+    ir_block *cond = func->curblock;
+    ir_block *cond_out = NULL;
+    ir_block *ontrue, *ontrue_out = NULL;
+    ir_block *onfalse, *onfalse_out = NULL;
+    ir_block *merge;
+    int       fold  = 0;
+
+    /* Ternary can never create an lvalue... */
+    if (lvalue)
+        return false;
+
+    /* In theory it shouldn't be possible to pass through a node twice, but
+     * in case we add any kind of optimization pass for the AST itself, it
+     * may still happen, thus we remember a created ir_value and simply return one
+     * if it already exists.
+     */
+    if (self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    /* In the following, contraty to ast_ifthen, we assume both paths exist. */
+
+    /* generate the condition */
+    func->curblock = cond;
+    cgen = self->cond->codegen;
+    if (!(*cgen)((ast_expression*)(self->cond), func, false, &condval))
+        return false;
+    cond_out = func->curblock;
+
+    /* try constant folding away the condition */
+    if ((fold = fold_cond_ternary(condval, func, self)) != -1)
+        return fold;
+
+    /* create on-true block */
+    ontrue = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_T"));
+    if (!ontrue)
+        return false;
+    else
+    {
+        /* enter the block */
+        func->curblock = ontrue;
+
+        /* generate */
+        cgen = self->on_true->codegen;
+        if (!(*cgen)((ast_expression*)(self->on_true), func, false, &trueval))
+            return false;
+
+        ontrue_out = func->curblock;
+    }
+
+    /* create on-false block */
+    onfalse = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_F"));
+    if (!onfalse)
+        return false;
+    else
+    {
+        /* enter the block */
+        func->curblock = onfalse;
+
+        /* generate */
+        cgen = self->on_false->codegen;
+        if (!(*cgen)((ast_expression*)(self->on_false), func, false, &falseval))
+            return false;
+
+        onfalse_out = func->curblock;
+    }
+
+    /* create merge block */
+    merge = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "tern_out"));
+    if (!merge)
+        return false;
+    /* jump to merge block */
+    if (!ir_block_create_jump(ontrue_out, ast_ctx(self), merge))
+        return false;
+    if (!ir_block_create_jump(onfalse_out, ast_ctx(self), merge))
+        return false;
+
+    /* create if instruction */
+    if (!ir_block_create_if(cond_out, ast_ctx(self), condval, ontrue, onfalse))
+        return false;
+
+    /* Now enter the merge block */
+    func->curblock = merge;
+
+    /* Here, now, we need a PHI node
+     * but first some sanity checking...
+     */
+    if (trueval->vtype != falseval->vtype && trueval->vtype != TYPE_NIL && falseval->vtype != TYPE_NIL) {
+        /* error("ternary with different types on the two sides"); */
+        compile_error(ast_ctx(self), "internal error: ternary operand types invalid");
+        return false;
+    }
+
+    /* create PHI */
+    phi = ir_block_create_phi(merge, ast_ctx(self), ast_function_label(func, "phi"), self->expression.vtype);
+    if (!phi) {
+        compile_error(ast_ctx(self), "internal error: failed to generate phi node");
+        return false;
+    }
+    ir_phi_add(phi, ontrue_out,  trueval);
+    ir_phi_add(phi, onfalse_out, falseval);
+
+    self->expression.outr = ir_phi_value(phi);
+    *out = self->expression.outr;
+
+    codegen_output_type(self, *out);
+
+    return true;
+}
+
+bool ast_loop_codegen(ast_loop *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ir_value *dummy      = NULL;
+    ir_value *precond    = NULL;
+    ir_value *postcond   = NULL;
+
+    /* Since we insert some jumps "late" so we have blocks
+     * ordered "nicely", we need to keep track of the actual end-blocks
+     * of expressions to add the jumps to.
+     */
+    ir_block *bbody      = NULL, *end_bbody      = NULL;
+    ir_block *bprecond   = NULL, *end_bprecond   = NULL;
+    ir_block *bpostcond  = NULL, *end_bpostcond  = NULL;
+    ir_block *bincrement = NULL, *end_bincrement = NULL;
+    ir_block *bout       = NULL, *bin            = NULL;
+
+    /* let's at least move the outgoing block to the end */
+    size_t    bout_id;
+
+    /* 'break' and 'continue' need to be able to find the right blocks */
+    ir_block *bcontinue     = NULL;
+    ir_block *bbreak        = NULL;
+
+    ir_block *tmpblock      = NULL;
+
+    (void)lvalue;
+    (void)out;
+
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_loop cannot be reused, it bears no result!");
+        return false;
+    }
+    self->expression.outr = (ir_value*)1;
+
+    /* NOTE:
+     * Should we ever need some kind of block ordering, better make this function
+     * move blocks around than write a block ordering algorithm later... after all
+     * the ast and ir should work together, not against each other.
+     */
+
+    /* initexpr doesn't get its own block, it's pointless, it could create more blocks
+     * anyway if for example it contains a ternary.
+     */
+    if (self->initexpr)
+    {
+        cgen = self->initexpr->codegen;
+        if (!(*cgen)((ast_expression*)(self->initexpr), func, false, &dummy))
+            return false;
+    }
+
+    /* Store the block from which we enter this chaos */
+    bin = func->curblock;
+
+    /* The pre-loop condition needs its own block since we
+     * need to be able to jump to the start of that expression.
+     */
+    if (self->precond)
+    {
+        bprecond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "pre_loop_cond"));
+        if (!bprecond)
+            return false;
+
+        /* the pre-loop-condition the least important place to 'continue' at */
+        bcontinue = bprecond;
+
+        /* enter */
+        func->curblock = bprecond;
+
+        /* generate */
+        cgen = self->precond->codegen;
+        if (!(*cgen)((ast_expression*)(self->precond), func, false, &precond))
+            return false;
+
+        end_bprecond = func->curblock;
+    } else {
+        bprecond = end_bprecond = NULL;
+    }
+
+    /* Now the next blocks won't be ordered nicely, but we need to
+     * generate them this early for 'break' and 'continue'.
+     */
+    if (self->increment) {
+        bincrement = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_increment"));
+        if (!bincrement)
+            return false;
+        bcontinue = bincrement; /* increment comes before the pre-loop-condition */
+    } else {
+        bincrement = end_bincrement = NULL;
+    }
+
+    if (self->postcond) {
+        bpostcond = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "post_loop_cond"));
+        if (!bpostcond)
+            return false;
+        bcontinue = bpostcond; /* postcond comes before the increment */
+    } else {
+        bpostcond = end_bpostcond = NULL;
+    }
+
+    bout_id = vec_size(func->ir_func->blocks);
+    bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_loop"));
+    if (!bout)
+        return false;
+    bbreak = bout;
+
+    /* The loop body... */
+    /* if (self->body) */
+    {
+        bbody = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "loop_body"));
+        if (!bbody)
+            return false;
+
+        /* enter */
+        func->curblock = bbody;
+
+        vec_push(func->breakblocks,    bbreak);
+        if (bcontinue)
+            vec_push(func->continueblocks, bcontinue);
+        else
+            vec_push(func->continueblocks, bbody);
+
+        /* generate */
+        if (self->body) {
+            cgen = self->body->codegen;
+            if (!(*cgen)((ast_expression*)(self->body), func, false, &dummy))
+                return false;
+        }
+
+        end_bbody = func->curblock;
+        vec_pop(func->breakblocks);
+        vec_pop(func->continueblocks);
+    }
+
+    /* post-loop-condition */
+    if (self->postcond)
+    {
+        /* enter */
+        func->curblock = bpostcond;
+
+        /* generate */
+        cgen = self->postcond->codegen;
+        if (!(*cgen)((ast_expression*)(self->postcond), func, false, &postcond))
+            return false;
+
+        end_bpostcond = func->curblock;
+    }
+
+    /* The incrementor */
+    if (self->increment)
+    {
+        /* enter */
+        func->curblock = bincrement;
+
+        /* generate */
+        cgen = self->increment->codegen;
+        if (!(*cgen)((ast_expression*)(self->increment), func, false, &dummy))
+            return false;
+
+        end_bincrement = func->curblock;
+    }
+
+    /* In any case now, we continue from the outgoing block */
+    func->curblock = bout;
+
+    /* Now all blocks are in place */
+    /* From 'bin' we jump to whatever comes first */
+    if      (bprecond)   tmpblock = bprecond;
+    else                 tmpblock = bbody;    /* can never be null */
+
+    /* DEAD CODE
+    else if (bpostcond)  tmpblock = bpostcond;
+    else                 tmpblock = bout;
+    */
+
+    if (!ir_block_create_jump(bin, ast_ctx(self), tmpblock))
+        return false;
+
+    /* From precond */
+    if (bprecond)
+    {
+        ir_block *ontrue, *onfalse;
+        ontrue = bbody; /* can never be null */
+
+        /* all of this is dead code
+        else if (bincrement) ontrue = bincrement;
+        else                 ontrue = bpostcond;
+        */
+
+        onfalse = bout;
+        if (self->pre_not) {
+            tmpblock = ontrue;
+            ontrue   = onfalse;
+            onfalse  = tmpblock;
+        }
+        if (!ir_block_create_if(end_bprecond, ast_ctx(self), precond, ontrue, onfalse))
+            return false;
+    }
+
+    /* from body */
+    if (bbody)
+    {
+        if      (bincrement) tmpblock = bincrement;
+        else if (bpostcond)  tmpblock = bpostcond;
+        else if (bprecond)   tmpblock = bprecond;
+        else                 tmpblock = bbody;
+        if (!end_bbody->final && !ir_block_create_jump(end_bbody, ast_ctx(self), tmpblock))
+            return false;
+    }
+
+    /* from increment */
+    if (bincrement)
+    {
+        if      (bpostcond)  tmpblock = bpostcond;
+        else if (bprecond)   tmpblock = bprecond;
+        else if (bbody)      tmpblock = bbody;
+        else                 tmpblock = bout;
+        if (!ir_block_create_jump(end_bincrement, ast_ctx(self), tmpblock))
+            return false;
+    }
+
+    /* from postcond */
+    if (bpostcond)
+    {
+        ir_block *ontrue, *onfalse;
+        if      (bprecond)   ontrue = bprecond;
+        else                 ontrue = bbody; /* can never be null */
+
+        /* all of this is dead code
+        else if (bincrement) ontrue = bincrement;
+        else                 ontrue = bpostcond;
+        */
+
+        onfalse = bout;
+        if (self->post_not) {
+            tmpblock = ontrue;
+            ontrue   = onfalse;
+            onfalse  = tmpblock;
+        }
+        if (!ir_block_create_if(end_bpostcond, ast_ctx(self), postcond, ontrue, onfalse))
+            return false;
+    }
+
+    /* Move 'bout' to the end */
+    vec_remove(func->ir_func->blocks, bout_id, 1);
+    vec_push(func->ir_func->blocks, bout);
+
+    return true;
+}
+
+bool ast_breakcont_codegen(ast_breakcont *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ir_block *target;
+
+    *out = NULL;
+
+    if (lvalue) {
+        compile_error(ast_ctx(self), "break/continue expression is not an l-value");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_breakcont cannot be reused!");
+        return false;
+    }
+    self->expression.outr = (ir_value*)1;
+
+    if (self->is_continue)
+        target = func->continueblocks[vec_size(func->continueblocks)-1-self->levels];
+    else
+        target = func->breakblocks[vec_size(func->breakblocks)-1-self->levels];
+
+    if (!target) {
+        compile_error(ast_ctx(self), "%s is lacking a target block", (self->is_continue ? "continue" : "break"));
+        return false;
+    }
+
+    if (!ir_block_create_jump(func->curblock, ast_ctx(self), target))
+        return false;
+    return true;
+}
+
+bool ast_switch_codegen(ast_switch *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ast_switch_case *def_case     = NULL;
+    ir_block        *def_bfall    = NULL;
+    ir_block        *def_bfall_to = NULL;
+    bool set_def_bfall_to = false;
+
+    ir_value *dummy     = NULL;
+    ir_value *irop      = NULL;
+    ir_block *bout      = NULL;
+    ir_block *bfall     = NULL;
+    size_t    bout_id;
+    size_t    c;
+
+    char      typestr[1024];
+    uint16_t  cmpinstr;
+
+    if (lvalue) {
+        compile_error(ast_ctx(self), "switch expression is not an l-value");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_switch cannot be reused!");
+        return false;
+    }
+    self->expression.outr = (ir_value*)1;
+
+    (void)lvalue;
+    (void)out;
+
+    cgen = self->operand->codegen;
+    if (!(*cgen)((ast_expression*)(self->operand), func, false, &irop))
+        return false;
+
+    if (!vec_size(self->cases))
+        return true;
+
+    cmpinstr = type_eq_instr[irop->vtype];
+    if (cmpinstr >= VINSTR_END) {
+        ast_type_to_string(self->operand, typestr, sizeof(typestr));
+        compile_error(ast_ctx(self), "invalid type to perform a switch on: %s", typestr);
+        return false;
+    }
+
+    bout_id = vec_size(func->ir_func->blocks);
+    bout = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "after_switch"));
+    if (!bout)
+        return false;
+
+    /* setup the break block */
+    vec_push(func->breakblocks, bout);
+
+    /* Now create all cases */
+    for (c = 0; c < vec_size(self->cases); ++c) {
+        ir_value *cond, *val;
+        ir_block *bcase, *bnot;
+        size_t bnot_id;
+
+        ast_switch_case *swcase = &self->cases[c];
+
+        if (swcase->value) {
+            /* A regular case */
+            /* generate the condition operand */
+            cgen = swcase->value->codegen;
+            if (!(*cgen)((ast_expression*)(swcase->value), func, false, &val))
+                return false;
+            /* generate the condition */
+            cond = ir_block_create_binop(func->curblock, ast_ctx(self), ast_function_label(func, "switch_eq"), cmpinstr, irop, val);
+            if (!cond)
+                return false;
+
+            bcase = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "case"));
+            bnot_id = vec_size(func->ir_func->blocks);
+            bnot = ir_function_create_block(ast_ctx(self), func->ir_func, ast_function_label(func, "not_case"));
+            if (!bcase || !bnot)
+                return false;
+            if (set_def_bfall_to) {
+                set_def_bfall_to = false;
+                def_bfall_to = bcase;
+            }
+            if (!ir_block_create_if(func->curblock, ast_ctx(self), cond, bcase, bnot))
+                return false;
+
+            /* Make the previous case-end fall through */
+            if (bfall && !bfall->final) {
+                if (!ir_block_create_jump(bfall, ast_ctx(self), bcase))
+                    return false;
+            }
+
+            /* enter the case */
+            func->curblock = bcase;
+            cgen = swcase->code->codegen;
+            if (!(*cgen)((ast_expression*)swcase->code, func, false, &dummy))
+                return false;
+
+            /* remember this block to fall through from */
+            bfall = func->curblock;
+
+            /* enter the else and move it down */
+            func->curblock = bnot;
+            vec_remove(func->ir_func->blocks, bnot_id, 1);
+            vec_push(func->ir_func->blocks, bnot);
+        } else {
+            /* The default case */
+            /* Remember where to fall through from: */
+            def_bfall = bfall;
+            bfall     = NULL;
+            /* remember which case it was */
+            def_case  = swcase;
+            /* And the next case will be remembered */
+            set_def_bfall_to = true;
+        }
+    }
+
+    /* Jump from the last bnot to bout */
+    if (bfall && !bfall->final && !ir_block_create_jump(bfall, ast_ctx(self), bout)) {
+        /*
+        astwarning(ast_ctx(bfall), WARN_???, "missing break after last case");
+        */
+        return false;
+    }
+
+    /* If there was a default case, put it down here */
+    if (def_case) {
+        ir_block *bcase;
+
+        /* No need to create an extra block */
+        bcase = func->curblock;
+
+        /* Insert the fallthrough jump */
+        if (def_bfall && !def_bfall->final) {
+            if (!ir_block_create_jump(def_bfall, ast_ctx(self), bcase))
+                return false;
+        }
+
+        /* Now generate the default code */
+        cgen = def_case->code->codegen;
+        if (!(*cgen)((ast_expression*)def_case->code, func, false, &dummy))
+            return false;
+
+        /* see if we need to fall through */
+        if (def_bfall_to && !func->curblock->final)
+        {
+            if (!ir_block_create_jump(func->curblock, ast_ctx(self), def_bfall_to))
+                return false;
+        }
+    }
+
+    /* Jump from the last bnot to bout */
+    if (!func->curblock->final && !ir_block_create_jump(func->curblock, ast_ctx(self), bout))
+        return false;
+    /* enter the outgoing block */
+    func->curblock = bout;
+
+    /* restore the break block */
+    vec_pop(func->breakblocks);
+
+    /* Move 'bout' to the end, it's nicer */
+    vec_remove(func->ir_func->blocks, bout_id, 1);
+    vec_push(func->ir_func->blocks, bout);
+
+    return true;
+}
+
+bool ast_label_codegen(ast_label *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    size_t i;
+    ir_value *dummy;
+
+    if (self->undefined) {
+        compile_error(ast_ctx(self), "internal error: ast_label never defined");
+        return false;
+    }
+
+    *out = NULL;
+    if (lvalue) {
+        compile_error(ast_ctx(self), "internal error: ast_label cannot be an lvalue");
+        return false;
+    }
+
+    /* simply create a new block and jump to it */
+    self->irblock = ir_function_create_block(ast_ctx(self), func->ir_func, self->name);
+    if (!self->irblock) {
+        compile_error(ast_ctx(self), "failed to allocate label block `%s`", self->name);
+        return false;
+    }
+    if (!func->curblock->final) {
+        if (!ir_block_create_jump(func->curblock, ast_ctx(self), self->irblock))
+            return false;
+    }
+
+    /* enter the new block */
+    func->curblock = self->irblock;
+
+    /* Generate all the leftover gotos */
+    for (i = 0; i < vec_size(self->gotos); ++i) {
+        if (!ast_goto_codegen(self->gotos[i], func, false, &dummy))
+            return false;
+    }
+
+    return true;
+}
+
+bool ast_goto_codegen(ast_goto *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    *out = NULL;
+    if (lvalue) {
+        compile_error(ast_ctx(self), "internal error: ast_goto cannot be an lvalue");
+        return false;
+    }
+
+    if (self->target->irblock) {
+        if (self->irblock_from) {
+            /* we already tried once, this is the callback */
+            self->irblock_from->final = false;
+            if (!ir_block_create_goto(self->irblock_from, ast_ctx(self), self->target->irblock)) {
+                compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name);
+                return false;
+            }
+        }
+        else
+        {
+            if (!ir_block_create_goto(func->curblock, ast_ctx(self), self->target->irblock)) {
+                compile_error(ast_ctx(self), "failed to generate goto to `%s`", self->name);
+                return false;
+            }
+        }
+    }
+    else
+    {
+        /* the target has not yet been created...
+         * close this block in a sneaky way:
+         */
+        func->curblock->final = true;
+        self->irblock_from = func->curblock;
+        ast_label_register_goto(self->target, self);
+    }
+
+    return true;
+}
+
+#include <stdio.h>
+bool ast_state_codegen(ast_state *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+
+    ir_value *frameval, *thinkval;
+
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (state operation)");
+        return false;
+    }
+    if (self->expression.outr) {
+        compile_error(ast_ctx(self), "internal error: ast_state cannot be reused!");
+        return false;
+    }
+    *out = NULL;
+
+    cgen = self->framenum->codegen;
+    if (!(*cgen)((ast_expression*)(self->framenum), func, false, &frameval))
+        return false;
+    if (!frameval)
+        return false;
+
+    cgen = self->nextthink->codegen;
+    if (!(*cgen)((ast_expression*)(self->nextthink), func, false, &thinkval))
+        return false;
+    if (!frameval)
+        return false;
+
+    if (!ir_block_create_state_op(func->curblock, ast_ctx(self), frameval, thinkval)) {
+        compile_error(ast_ctx(self), "failed to create STATE instruction");
+        return false;
+    }
+
+    self->expression.outr = (ir_value*)1;
+    return true;
+}
+
+bool ast_call_codegen(ast_call *self, ast_function *func, bool lvalue, ir_value **out)
+{
+    ast_expression_codegen *cgen;
+    ir_value              **params;
+    ir_instr               *callinstr;
+    size_t i;
+
+    ir_value *funval = NULL;
+
+    /* return values are never lvalues */
+    if (lvalue) {
+        compile_error(ast_ctx(self), "not an l-value (function call)");
+        return false;
+    }
+
+    if (self->expression.outr) {
+        *out = self->expression.outr;
+        return true;
+    }
+
+    cgen = self->func->codegen;
+    if (!(*cgen)((ast_expression*)(self->func), func, false, &funval))
+        return false;
+    if (!funval)
+        return false;
+
+    params = NULL;
+
+    /* parameters */
+    for (i = 0; i < vec_size(self->params); ++i)
+    {
+        ir_value *param;
+        ast_expression *expr = self->params[i];
+
+        cgen = expr->codegen;
+        if (!(*cgen)(expr, func, false, &param))
+            goto error;
+        if (!param)
+            goto error;
+        vec_push(params, param);
+    }
+
+    /* varargs counter */
+    if (self->va_count) {
+        ir_value   *va_count;
+        ir_builder *builder = func->curblock->owner->owner;
+        cgen = self->va_count->codegen;
+        if (!(*cgen)((ast_expression*)(self->va_count), func, false, &va_count))
+            return false;
+        if (!ir_block_create_store_op(func->curblock, ast_ctx(self), INSTR_STORE_F,
+                                      ir_builder_get_va_count(builder), va_count))
+        {
+            return false;
+        }
+    }
+
+    callinstr = ir_block_create_call(func->curblock, ast_ctx(self),
+                                     ast_function_label(func, "call"),
+                                     funval, !!(self->func->flags & AST_FLAG_NORETURN));
+    if (!callinstr)
+        goto error;
+
+    for (i = 0; i < vec_size(params); ++i) {
+        ir_call_param(callinstr, params[i]);
+    }
+
+    *out = ir_call_value(callinstr);
+    self->expression.outr = *out;
+
+    codegen_output_type(self, *out);
+
+    vec_free(params);
+    return true;
+error:
+    vec_free(params);
+    return false;
+}
diff --git a/code.c b/code.c
deleted file mode 100644 (file)
index 314d11b..0000000
--- a/code.c
+++ /dev/null
@@ -1,365 +0,0 @@
-#include <string.h>
-#include "gmqcc.h"
-
-/*
- * We could use the old method of casting to uintptr_t then to void*
- * or qcint_t; however, it's incredibly unsafe for two reasons.
- * 1) The compilers aliasing optimization can legally make it unstable
- *    (it's undefined behaviour).
- *
- * 2) The cast itself depends on fresh storage (newly allocated in which
- *    ever function is using the cast macros), the contents of which are
- *    transferred in a way that the obligation to release storage is not
- *    propagated.
- */
-typedef union {
-    void   *enter;
-    qcint_t leave;
-} code_hash_entry_t;
-
-/* Some sanity macros */
-#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter)
-#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave)
-
-void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx)
-{
-    prog_section_statement_t stmt = *stmt_in;
-
-    if (OPTS_FLAG(TYPELESS_STORES)) {
-        switch (stmt.opcode) {
-            case INSTR_LOAD_S:
-            case INSTR_LOAD_ENT:
-            case INSTR_LOAD_FLD:
-            case INSTR_LOAD_FNC:
-                stmt.opcode = INSTR_LOAD_F;
-                break;
-            case INSTR_STORE_S:
-            case INSTR_STORE_ENT:
-            case INSTR_STORE_FLD:
-            case INSTR_STORE_FNC:
-                stmt.opcode = INSTR_STORE_F;
-                break;
-            case INSTR_STOREP_S:
-            case INSTR_STOREP_ENT:
-            case INSTR_STOREP_FLD:
-            case INSTR_STOREP_FNC:
-                stmt.opcode = INSTR_STOREP_F;
-                break;
-        }
-    }
-
-
-    if (OPTS_FLAG(SORT_OPERANDS)) {
-        uint16_t pair;
-
-        switch (stmt.opcode) {
-            case INSTR_MUL_F:
-            case INSTR_MUL_V:
-            case INSTR_ADD_F:
-            case INSTR_EQ_F:
-            case INSTR_EQ_S:
-            case INSTR_EQ_E:
-            case INSTR_EQ_FNC:
-            case INSTR_NE_F:
-            case INSTR_NE_V:
-            case INSTR_NE_S:
-            case INSTR_NE_E:
-            case INSTR_NE_FNC:
-            case INSTR_AND:
-            case INSTR_OR:
-            case INSTR_BITAND:
-            case INSTR_BITOR:
-                if (stmt.o1.u1 < stmt.o2.u1) {
-                    uint16_t a = stmt.o2.u1;
-                    stmt.o1.u1 = stmt.o2.u1;
-                    stmt.o2.u1 = a;
-                }
-                break;
-
-            case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen;
-            case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen;
-            case INSTR_LT:     pair = INSTR_GT;     goto case_pair_gen;
-            case INSTR_GT:     pair = INSTR_LT;     goto case_pair_gen;
-            case INSTR_LE:     pair = INSTR_GT;     goto case_pair_gen;
-            case INSTR_GE:     pair = INSTR_LE;
-
-            case_pair_gen:
-                if (stmt.o1.u1 < stmt.o2.u1) {
-                    uint16_t x  = stmt.o1.u1;
-                    stmt.o1.u1  = stmt.o2.u1;
-                    stmt.o2.u1  = x;
-                    stmt.opcode = pair;
-                }
-                break;
-        }
-    }
-
-    vec_push(code->statements, stmt);
-    vec_push(code->linenums,   (int)ctx.line);
-    vec_push(code->columnnums, (int)ctx.column);
-}
-
-void code_pop_statement(code_t *code)
-{
-    vec_pop(code->statements);
-    vec_pop(code->linenums);
-    vec_pop(code->columnnums);
-}
-
-code_t *code_init() {
-    static lex_ctx_t                empty_ctx       = {0, 0, 0};
-    static prog_section_function_t  empty_function  = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}};
-    static prog_section_statement_t empty_statement = {0,{0},{0},{0}};
-    static prog_section_def_t       empty_def       = {0, 0, 0};
-
-    code_t *code       = (code_t*)mem_a(sizeof(code_t));
-    int     i          = 0;
-
-    memset(code, 0, sizeof(code_t));
-    code->entfields    = 0;
-    code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024);
-
-    /*
-     * The way progs.dat is suppose to work is odd, there needs to be
-     * some null (empty) statements, functions, and 28 globals
-     */
-    for(; i < 28; i++)
-        vec_push(code->globals, 0);
-
-    vec_push(code->chars, '\0');
-    vec_push(code->functions,  empty_function);
-
-    code_push_statement(code, &empty_statement, empty_ctx);
-
-    vec_push(code->defs,    empty_def);
-    vec_push(code->fields,  empty_def);
-
-    return code;
-}
-
-void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
-
-uint32_t code_genstring(code_t *code, const char *str) {
-    size_t            hash;
-    code_hash_entry_t existing;
-
-    if (!str)
-        return 0;
-
-    if (!*str) {
-        if (!code->string_cached_empty) {
-            code->string_cached_empty = vec_size(code->chars);
-            vec_push(code->chars, 0);
-        }
-        return code->string_cached_empty;
-    }
-
-    if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) {
-        hash                      = ((unsigned char*)str)[strlen(str)-1];
-        CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash);
-    } else {
-        hash                      = util_hthash(code->string_cache, str);
-        CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash);
-    }
-
-    if (CODE_HASH_ENTER(existing))
-        return CODE_HASH_LEAVE(existing);
-
-    CODE_HASH_LEAVE(existing) = vec_size(code->chars);
-    vec_append(code->chars, strlen(str)+1, str);
-
-    util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing));
-    return CODE_HASH_LEAVE(existing);
-}
-
-qcint_t code_alloc_field (code_t *code, size_t qcsize)
-{
-    qcint_t pos = (qcint_t)code->entfields;
-    code->entfields += qcsize;
-    return pos;
-}
-
-static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) {
-    size_t size = 0;
-    if (lno) {
-        size += 4;  /* LNOF */
-        size += sizeof(uint32_t); /* version */
-        size += sizeof(code_header->defs.length);
-        size += sizeof(code_header->globals.length);
-        size += sizeof(code_header->fields.length);
-        size += sizeof(code_header->statements.length);
-        size += sizeof(code->linenums[0])   * vec_size(code->linenums);
-        size += sizeof(code->columnnums[0]) * vec_size(code->columnnums);
-    } else {
-        size += sizeof(prog_header_t);
-        size += sizeof(prog_section_statement_t) * vec_size(code->statements);
-        size += sizeof(prog_section_def_t)       * vec_size(code->defs);
-        size += sizeof(prog_section_field_t)     * vec_size(code->fields);
-        size += sizeof(prog_section_function_t)  * vec_size(code->functions);
-        size += sizeof(int32_t)                  * vec_size(code->globals);
-        size += 1                                * vec_size(code->chars);
-    }
-    return size;
-}
-
-#define code_size_binary(C, H) code_size_generic((C), (H), false)
-#define code_size_debug(C, H)  code_size_generic((C), (H), true)
-
-static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) {
-    size_t i;
-
-    code_header->statements.offset = sizeof(prog_header_t);
-    code_header->statements.length = vec_size(code->statements);
-    code_header->defs.offset       = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements));
-    code_header->defs.length       = vec_size(code->defs);
-    code_header->fields.offset     = code_header->defs.offset       + (sizeof(prog_section_def_t)       * vec_size(code->defs));
-    code_header->fields.length     = vec_size(code->fields);
-    code_header->functions.offset  = code_header->fields.offset     + (sizeof(prog_section_field_t)     * vec_size(code->fields));
-    code_header->functions.length  = vec_size(code->functions);
-    code_header->globals.offset    = code_header->functions.offset  + (sizeof(prog_section_function_t)  * vec_size(code->functions));
-    code_header->globals.length    = vec_size(code->globals);
-    code_header->strings.offset    = code_header->globals.offset    + (sizeof(int32_t)                  * vec_size(code->globals));
-    code_header->strings.length    = vec_size(code->chars);
-    code_header->version           = 6;
-    code_header->skip              = 0;
-
-    if (OPTS_OPTION_BOOL(OPTION_FORCECRC))
-        code_header->crc16         = OPTS_OPTION_U16(OPTION_FORCED_CRC);
-    else
-        code_header->crc16         = code->crc;
-    code_header->entfield          = code->entfields;
-
-    if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) {
-        /* >= + P */
-        vec_push(code->chars, '\0'); /* > */
-        vec_push(code->chars, '\0'); /* = */
-        vec_push(code->chars, '\0'); /* P */
-    }
-
-    /* ensure all data is in LE format */
-    util_swap_header(code_header);
-
-    /*
-     * These are not part of the header but we ensure LE format here to save on duplicated
-     * code.
-     */
-
-    util_swap_statements (code->statements);
-    util_swap_defs_fields(code->defs);
-    util_swap_defs_fields(code->fields);
-    util_swap_functions  (code->functions);
-    util_swap_globals    (code->globals);
-
-    if (!OPTS_OPTION_BOOL(OPTION_QUIET)) {
-        if (lnofile)
-            con_out("writing '%s' and '%s'...\n", filename, lnofile);
-        else
-            con_out("writing '%s'\n", filename);
-    }
-
-    if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
-        !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-    {
-        char buffer[1024];
-        con_out("\nOptimizations:\n");
-        for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) {
-            if (opts_optimizationcount[i]) {
-                util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer));
-                con_out(
-                    "    %s: %u\n",
-                    buffer,
-                    (unsigned int)opts_optimizationcount[i]
-                );
-            }
-        }
-    }
-}
-
-static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) {
-    if (OPTS_OPTION_BOOL(OPTION_QUIET) ||
-        OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-            return;
-
-    con_out("\nFile statistics:\n");
-    con_out("    dat:\n");
-    con_out("        name: %s\n",         filename);
-    con_out("        size: %u (bytes)\n", code_size_binary(code, code_header));
-    con_out("        crc:  0x%04X\n",     code->crc);
-
-    if (lnofile) {
-        con_out("    lno:\n");
-        con_out("        name: %s\n",  lnofile);
-        con_out("        size: %u (bytes)\n",  code_size_debug(code, code_header));
-    }
-
-    con_out("\n");
-}
-
-bool code_write(code_t *code, const char *filename, const char *lnofile) {
-    prog_header_t  code_header;
-    FILE     *fp = NULL;
-
-    code_create_header(code, &code_header, filename, lnofile);
-
-    if (lnofile) {
-        uint32_t version = 1;
-
-        fp = fopen(lnofile, "wb");
-        if (!fp)
-            return false;
-
-        util_endianswap(&version,         1,                          sizeof(version));
-        util_endianswap(code->linenums,   vec_size(code->linenums),   sizeof(code->linenums[0]));
-        util_endianswap(code->columnnums, vec_size(code->columnnums), sizeof(code->columnnums[0]));
-
-        if (fwrite("LNOF",                          4,                                      1,                          fp) != 1 ||
-            fwrite(&version,                        sizeof(version),                        1,                          fp) != 1 ||
-            fwrite(&code_header.defs.length,        sizeof(code_header.defs.length),        1,                          fp) != 1 ||
-            fwrite(&code_header.globals.length,     sizeof(code_header.globals.length),     1,                          fp) != 1 ||
-            fwrite(&code_header.fields.length,      sizeof(code_header.fields.length),      1,                          fp) != 1 ||
-            fwrite(&code_header.statements.length,  sizeof(code_header.statements.length),  1,                          fp) != 1 ||
-            fwrite(code->linenums,                  sizeof(code->linenums[0]),              vec_size(code->linenums),   fp) != vec_size(code->linenums) ||
-            fwrite(code->columnnums,                sizeof(code->columnnums[0]),            vec_size(code->columnnums), fp) != vec_size(code->columnnums))
-        {
-            con_err("failed to write lno file\n");
-        }
-
-        fclose(fp);
-        fp = NULL;
-    }
-
-    fp = fopen(filename, "wb");
-    if (!fp)
-        return false;
-
-    if (1                          != fwrite(&code_header,     sizeof(prog_header_t)           , 1                         , fp) ||
-        vec_size(code->statements) != fwrite(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) ||
-        vec_size(code->defs)       != fwrite(code->defs,       sizeof(prog_section_def_t)      , vec_size(code->defs)      , fp) ||
-        vec_size(code->fields)     != fwrite(code->fields,     sizeof(prog_section_field_t)    , vec_size(code->fields)    , fp) ||
-        vec_size(code->functions)  != fwrite(code->functions,  sizeof(prog_section_function_t) , vec_size(code->functions) , fp) ||
-        vec_size(code->globals)    != fwrite(code->globals,    sizeof(int32_t)                 , vec_size(code->globals)   , fp) ||
-        vec_size(code->chars)      != fwrite(code->chars,      1                               , vec_size(code->chars)     , fp))
-    {
-        fclose(fp);
-        return false;
-    }
-
-    fclose(fp);
-    code_stats(filename, lnofile, code, &code_header);
-    return true;
-}
-
-void code_cleanup(code_t *code) {
-    vec_free(code->statements);
-    vec_free(code->linenums);
-    vec_free(code->columnnums);
-    vec_free(code->defs);
-    vec_free(code->fields);
-    vec_free(code->functions);
-    vec_free(code->globals);
-    vec_free(code->chars);
-
-    util_htdel(code->string_cache);
-
-    mem_d(code);
-}
diff --git a/code.cpp b/code.cpp
new file mode 100644 (file)
index 0000000..314d11b
--- /dev/null
+++ b/code.cpp
@@ -0,0 +1,365 @@
+#include <string.h>
+#include "gmqcc.h"
+
+/*
+ * We could use the old method of casting to uintptr_t then to void*
+ * or qcint_t; however, it's incredibly unsafe for two reasons.
+ * 1) The compilers aliasing optimization can legally make it unstable
+ *    (it's undefined behaviour).
+ *
+ * 2) The cast itself depends on fresh storage (newly allocated in which
+ *    ever function is using the cast macros), the contents of which are
+ *    transferred in a way that the obligation to release storage is not
+ *    propagated.
+ */
+typedef union {
+    void   *enter;
+    qcint_t leave;
+} code_hash_entry_t;
+
+/* Some sanity macros */
+#define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter)
+#define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave)
+
+void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx)
+{
+    prog_section_statement_t stmt = *stmt_in;
+
+    if (OPTS_FLAG(TYPELESS_STORES)) {
+        switch (stmt.opcode) {
+            case INSTR_LOAD_S:
+            case INSTR_LOAD_ENT:
+            case INSTR_LOAD_FLD:
+            case INSTR_LOAD_FNC:
+                stmt.opcode = INSTR_LOAD_F;
+                break;
+            case INSTR_STORE_S:
+            case INSTR_STORE_ENT:
+            case INSTR_STORE_FLD:
+            case INSTR_STORE_FNC:
+                stmt.opcode = INSTR_STORE_F;
+                break;
+            case INSTR_STOREP_S:
+            case INSTR_STOREP_ENT:
+            case INSTR_STOREP_FLD:
+            case INSTR_STOREP_FNC:
+                stmt.opcode = INSTR_STOREP_F;
+                break;
+        }
+    }
+
+
+    if (OPTS_FLAG(SORT_OPERANDS)) {
+        uint16_t pair;
+
+        switch (stmt.opcode) {
+            case INSTR_MUL_F:
+            case INSTR_MUL_V:
+            case INSTR_ADD_F:
+            case INSTR_EQ_F:
+            case INSTR_EQ_S:
+            case INSTR_EQ_E:
+            case INSTR_EQ_FNC:
+            case INSTR_NE_F:
+            case INSTR_NE_V:
+            case INSTR_NE_S:
+            case INSTR_NE_E:
+            case INSTR_NE_FNC:
+            case INSTR_AND:
+            case INSTR_OR:
+            case INSTR_BITAND:
+            case INSTR_BITOR:
+                if (stmt.o1.u1 < stmt.o2.u1) {
+                    uint16_t a = stmt.o2.u1;
+                    stmt.o1.u1 = stmt.o2.u1;
+                    stmt.o2.u1 = a;
+                }
+                break;
+
+            case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen;
+            case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen;
+            case INSTR_LT:     pair = INSTR_GT;     goto case_pair_gen;
+            case INSTR_GT:     pair = INSTR_LT;     goto case_pair_gen;
+            case INSTR_LE:     pair = INSTR_GT;     goto case_pair_gen;
+            case INSTR_GE:     pair = INSTR_LE;
+
+            case_pair_gen:
+                if (stmt.o1.u1 < stmt.o2.u1) {
+                    uint16_t x  = stmt.o1.u1;
+                    stmt.o1.u1  = stmt.o2.u1;
+                    stmt.o2.u1  = x;
+                    stmt.opcode = pair;
+                }
+                break;
+        }
+    }
+
+    vec_push(code->statements, stmt);
+    vec_push(code->linenums,   (int)ctx.line);
+    vec_push(code->columnnums, (int)ctx.column);
+}
+
+void code_pop_statement(code_t *code)
+{
+    vec_pop(code->statements);
+    vec_pop(code->linenums);
+    vec_pop(code->columnnums);
+}
+
+code_t *code_init() {
+    static lex_ctx_t                empty_ctx       = {0, 0, 0};
+    static prog_section_function_t  empty_function  = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}};
+    static prog_section_statement_t empty_statement = {0,{0},{0},{0}};
+    static prog_section_def_t       empty_def       = {0, 0, 0};
+
+    code_t *code       = (code_t*)mem_a(sizeof(code_t));
+    int     i          = 0;
+
+    memset(code, 0, sizeof(code_t));
+    code->entfields    = 0;
+    code->string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024);
+
+    /*
+     * The way progs.dat is suppose to work is odd, there needs to be
+     * some null (empty) statements, functions, and 28 globals
+     */
+    for(; i < 28; i++)
+        vec_push(code->globals, 0);
+
+    vec_push(code->chars, '\0');
+    vec_push(code->functions,  empty_function);
+
+    code_push_statement(code, &empty_statement, empty_ctx);
+
+    vec_push(code->defs,    empty_def);
+    vec_push(code->fields,  empty_def);
+
+    return code;
+}
+
+void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
+
+uint32_t code_genstring(code_t *code, const char *str) {
+    size_t            hash;
+    code_hash_entry_t existing;
+
+    if (!str)
+        return 0;
+
+    if (!*str) {
+        if (!code->string_cached_empty) {
+            code->string_cached_empty = vec_size(code->chars);
+            vec_push(code->chars, 0);
+        }
+        return code->string_cached_empty;
+    }
+
+    if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) {
+        hash                      = ((unsigned char*)str)[strlen(str)-1];
+        CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash);
+    } else {
+        hash                      = util_hthash(code->string_cache, str);
+        CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash);
+    }
+
+    if (CODE_HASH_ENTER(existing))
+        return CODE_HASH_LEAVE(existing);
+
+    CODE_HASH_LEAVE(existing) = vec_size(code->chars);
+    vec_append(code->chars, strlen(str)+1, str);
+
+    util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing));
+    return CODE_HASH_LEAVE(existing);
+}
+
+qcint_t code_alloc_field (code_t *code, size_t qcsize)
+{
+    qcint_t pos = (qcint_t)code->entfields;
+    code->entfields += qcsize;
+    return pos;
+}
+
+static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) {
+    size_t size = 0;
+    if (lno) {
+        size += 4;  /* LNOF */
+        size += sizeof(uint32_t); /* version */
+        size += sizeof(code_header->defs.length);
+        size += sizeof(code_header->globals.length);
+        size += sizeof(code_header->fields.length);
+        size += sizeof(code_header->statements.length);
+        size += sizeof(code->linenums[0])   * vec_size(code->linenums);
+        size += sizeof(code->columnnums[0]) * vec_size(code->columnnums);
+    } else {
+        size += sizeof(prog_header_t);
+        size += sizeof(prog_section_statement_t) * vec_size(code->statements);
+        size += sizeof(prog_section_def_t)       * vec_size(code->defs);
+        size += sizeof(prog_section_field_t)     * vec_size(code->fields);
+        size += sizeof(prog_section_function_t)  * vec_size(code->functions);
+        size += sizeof(int32_t)                  * vec_size(code->globals);
+        size += 1                                * vec_size(code->chars);
+    }
+    return size;
+}
+
+#define code_size_binary(C, H) code_size_generic((C), (H), false)
+#define code_size_debug(C, H)  code_size_generic((C), (H), true)
+
+static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) {
+    size_t i;
+
+    code_header->statements.offset = sizeof(prog_header_t);
+    code_header->statements.length = vec_size(code->statements);
+    code_header->defs.offset       = code_header->statements.offset + (sizeof(prog_section_statement_t) * vec_size(code->statements));
+    code_header->defs.length       = vec_size(code->defs);
+    code_header->fields.offset     = code_header->defs.offset       + (sizeof(prog_section_def_t)       * vec_size(code->defs));
+    code_header->fields.length     = vec_size(code->fields);
+    code_header->functions.offset  = code_header->fields.offset     + (sizeof(prog_section_field_t)     * vec_size(code->fields));
+    code_header->functions.length  = vec_size(code->functions);
+    code_header->globals.offset    = code_header->functions.offset  + (sizeof(prog_section_function_t)  * vec_size(code->functions));
+    code_header->globals.length    = vec_size(code->globals);
+    code_header->strings.offset    = code_header->globals.offset    + (sizeof(int32_t)                  * vec_size(code->globals));
+    code_header->strings.length    = vec_size(code->chars);
+    code_header->version           = 6;
+    code_header->skip              = 0;
+
+    if (OPTS_OPTION_BOOL(OPTION_FORCECRC))
+        code_header->crc16         = OPTS_OPTION_U16(OPTION_FORCED_CRC);
+    else
+        code_header->crc16         = code->crc;
+    code_header->entfield          = code->entfields;
+
+    if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) {
+        /* >= + P */
+        vec_push(code->chars, '\0'); /* > */
+        vec_push(code->chars, '\0'); /* = */
+        vec_push(code->chars, '\0'); /* P */
+    }
+
+    /* ensure all data is in LE format */
+    util_swap_header(code_header);
+
+    /*
+     * These are not part of the header but we ensure LE format here to save on duplicated
+     * code.
+     */
+
+    util_swap_statements (code->statements);
+    util_swap_defs_fields(code->defs);
+    util_swap_defs_fields(code->fields);
+    util_swap_functions  (code->functions);
+    util_swap_globals    (code->globals);
+
+    if (!OPTS_OPTION_BOOL(OPTION_QUIET)) {
+        if (lnofile)
+            con_out("writing '%s' and '%s'...\n", filename, lnofile);
+        else
+            con_out("writing '%s'\n", filename);
+    }
+
+    if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
+        !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+    {
+        char buffer[1024];
+        con_out("\nOptimizations:\n");
+        for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) {
+            if (opts_optimizationcount[i]) {
+                util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer));
+                con_out(
+                    "    %s: %u\n",
+                    buffer,
+                    (unsigned int)opts_optimizationcount[i]
+                );
+            }
+        }
+    }
+}
+
+static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) {
+    if (OPTS_OPTION_BOOL(OPTION_QUIET) ||
+        OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+            return;
+
+    con_out("\nFile statistics:\n");
+    con_out("    dat:\n");
+    con_out("        name: %s\n",         filename);
+    con_out("        size: %u (bytes)\n", code_size_binary(code, code_header));
+    con_out("        crc:  0x%04X\n",     code->crc);
+
+    if (lnofile) {
+        con_out("    lno:\n");
+        con_out("        name: %s\n",  lnofile);
+        con_out("        size: %u (bytes)\n",  code_size_debug(code, code_header));
+    }
+
+    con_out("\n");
+}
+
+bool code_write(code_t *code, const char *filename, const char *lnofile) {
+    prog_header_t  code_header;
+    FILE     *fp = NULL;
+
+    code_create_header(code, &code_header, filename, lnofile);
+
+    if (lnofile) {
+        uint32_t version = 1;
+
+        fp = fopen(lnofile, "wb");
+        if (!fp)
+            return false;
+
+        util_endianswap(&version,         1,                          sizeof(version));
+        util_endianswap(code->linenums,   vec_size(code->linenums),   sizeof(code->linenums[0]));
+        util_endianswap(code->columnnums, vec_size(code->columnnums), sizeof(code->columnnums[0]));
+
+        if (fwrite("LNOF",                          4,                                      1,                          fp) != 1 ||
+            fwrite(&version,                        sizeof(version),                        1,                          fp) != 1 ||
+            fwrite(&code_header.defs.length,        sizeof(code_header.defs.length),        1,                          fp) != 1 ||
+            fwrite(&code_header.globals.length,     sizeof(code_header.globals.length),     1,                          fp) != 1 ||
+            fwrite(&code_header.fields.length,      sizeof(code_header.fields.length),      1,                          fp) != 1 ||
+            fwrite(&code_header.statements.length,  sizeof(code_header.statements.length),  1,                          fp) != 1 ||
+            fwrite(code->linenums,                  sizeof(code->linenums[0]),              vec_size(code->linenums),   fp) != vec_size(code->linenums) ||
+            fwrite(code->columnnums,                sizeof(code->columnnums[0]),            vec_size(code->columnnums), fp) != vec_size(code->columnnums))
+        {
+            con_err("failed to write lno file\n");
+        }
+
+        fclose(fp);
+        fp = NULL;
+    }
+
+    fp = fopen(filename, "wb");
+    if (!fp)
+        return false;
+
+    if (1                          != fwrite(&code_header,     sizeof(prog_header_t)           , 1                         , fp) ||
+        vec_size(code->statements) != fwrite(code->statements, sizeof(prog_section_statement_t), vec_size(code->statements), fp) ||
+        vec_size(code->defs)       != fwrite(code->defs,       sizeof(prog_section_def_t)      , vec_size(code->defs)      , fp) ||
+        vec_size(code->fields)     != fwrite(code->fields,     sizeof(prog_section_field_t)    , vec_size(code->fields)    , fp) ||
+        vec_size(code->functions)  != fwrite(code->functions,  sizeof(prog_section_function_t) , vec_size(code->functions) , fp) ||
+        vec_size(code->globals)    != fwrite(code->globals,    sizeof(int32_t)                 , vec_size(code->globals)   , fp) ||
+        vec_size(code->chars)      != fwrite(code->chars,      1                               , vec_size(code->chars)     , fp))
+    {
+        fclose(fp);
+        return false;
+    }
+
+    fclose(fp);
+    code_stats(filename, lnofile, code, &code_header);
+    return true;
+}
+
+void code_cleanup(code_t *code) {
+    vec_free(code->statements);
+    vec_free(code->linenums);
+    vec_free(code->columnnums);
+    vec_free(code->defs);
+    vec_free(code->fields);
+    vec_free(code->functions);
+    vec_free(code->globals);
+    vec_free(code->chars);
+
+    util_htdel(code->string_cache);
+
+    mem_d(code);
+}
diff --git a/conout.c b/conout.c
deleted file mode 100644 (file)
index 9d0659b..0000000
--- a/conout.c
+++ /dev/null
@@ -1,226 +0,0 @@
-#include <stdio.h>
-#include "gmqcc.h"
-
-#define GMQCC_IS_STDOUT(X) ((X) == stdout)
-#define GMQCC_IS_STDERR(X) ((X) == stderr)
-#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X))
-
-typedef struct {
-    FILE *handle_err;
-    FILE *handle_out;
-    int color_err;
-    int color_out;
-} con_t;
-
-static con_t console;
-
-/*
- * Enables color on output if supported.
- * NOTE: The support for checking colors is NULL.  On windows this will
- * always work, on *nix it depends if the term has colors.
- *
- * NOTE: This prevents colored output to piped stdout/err via isatty
- * checks.
- */
-static void con_enablecolor(void) {
-    console.color_err = util_isatty(console.handle_err);
-    console.color_out = util_isatty(console.handle_out);
-}
-
-/*
- * Does a write to the handle with the format string and list of
- * arguments.  This colorizes for windows as well via translate
- * step.
- */
-static int con_write(FILE *handle, const char *fmt, va_list va) {
-    return vfprintf(handle, fmt, va);
-}
-
-/**********************************************************************
- * EXPOSED INTERFACE BEGINS
- *********************************************************************/
-
-void con_close() {
-    if (!GMQCC_IS_DEFINE(console.handle_err))
-        fclose(console.handle_err);
-    if (!GMQCC_IS_DEFINE(console.handle_out))
-        fclose(console.handle_out);
-}
-
-void con_color(int state) {
-    if (state)
-        con_enablecolor();
-    else {
-        console.color_err = 0;
-        console.color_out = 0;
-    }
-}
-
-void con_init() {
-    console.handle_err = stderr;
-    console.handle_out = stdout;
-    con_enablecolor();
-}
-
-void con_reset() {
-    con_close();
-    con_init();
-}
-
-/*
- * Defaultizer because stdio.h shouldn't be used anywhere except here
- * and inside file.c To prevent mis-match of wrapper-interfaces.
- */
-FILE *con_default_out() {
-    return console.handle_out = stdout;
-}
-
-FILE *con_default_err() {
-    return console.handle_err = stderr;
-}
-
-int con_verr(const char *fmt, va_list va) {
-    return con_write(console.handle_err, fmt, va);
-}
-int con_vout(const char *fmt, va_list va) {
-    return con_write(console.handle_out, fmt, va);
-}
-
-/*
- * Standard stdout/stderr printf functions used generally where they need
- * to be used.
- */
-int con_err(const char *fmt, ...) {
-    va_list va;
-    int ln = 0;
-    va_start(va, fmt);
-    con_verr(fmt, va);
-    va_end(va);
-    return ln;
-}
-int con_out(const char *fmt, ...) {
-    va_list va;
-    int ln = 0;
-    va_start(va, fmt);
-    con_vout(fmt, va);
-    va_end (va);
-    return ln;
-}
-
-/*
- * Utility console message writes for lexer contexts.  These will allow
- * for reporting of file:line based on lexer context, These are used
- * heavily in the parser/ir/ast.
- */
-static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) {
-    /* color selection table */
-    static int sel[] = {
-        CON_WHITE,
-        CON_CYAN,
-        CON_RED
-    };
-
-    int  err                             = !!(level == LVL_ERROR);
-    int  color                           = (err) ? console.color_err : console.color_out;
-    int (*print) (const char *, ...)     = (err) ? &con_err          : &con_out;
-    int (*vprint)(const char *, va_list) = (err) ? &con_verr         : &con_vout;
-
-    if (color)
-        print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype);
-    else
-        print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype);
-
-    vprint(msg, ap);
-    if (condname)
-        print(" [%s]\n", condname);
-    else
-        print("\n");
-}
-
-void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) {
-    con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, NULL);
-}
-
-void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) {
-    va_list   va;
-    va_start(va, msg);
-    con_vprintmsg(level, name, line, column, msgtype, msg, va);
-    va_end  (va);
-}
-
-void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) {
-    con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap);
-}
-
-void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) {
-    va_list   va;
-    va_start(va, msg);
-    con_cvprintmsg(ctx, lvl, msgtype, msg, va);
-    va_end  (va);
-}
-
-/* General error interface: TODO seperate as part of the compiler front-end */
-size_t compile_errors   = 0;
-size_t compile_warnings = 0;
-size_t compile_Werrors  = 0;
-static lex_ctx_t first_werror;
-
-void compile_show_werrors()
-{
-    con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here");
-}
-
-void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap)
-{
-    ++compile_errors;
-    con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap);
-}
-
-void compile_error(lex_ctx_t ctx, const char *msg, ...)
-{
-    va_list ap;
-    va_start(ap, msg);
-    vcompile_error(ctx, msg, ap);
-    va_end(ap);
-}
-
-bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap)
-{
-    const char *msgtype = "warning";
-    int         lvl     = LVL_WARNING;
-    char        warn_name[1024];
-
-    if (!OPTS_WARN(warntype))
-        return false;
-
-    warn_name[0] = '-';
-    warn_name[1] = 'W';
-    (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2);
-
-    ++compile_warnings;
-    if (OPTS_WERROR(warntype)) {
-        if (!compile_Werrors)
-            first_werror = ctx;
-        ++compile_Werrors;
-        msgtype = "Werror";
-        if (OPTS_FLAG(BAIL_ON_WERROR)) {
-            msgtype = "error";
-            ++compile_errors;
-        }
-        lvl = LVL_ERROR;
-    }
-
-    con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name);
-
-    return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR);
-}
-
-bool GMQCC_WARN compile_warning(lex_ctx_t 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;
-}
diff --git a/conout.cpp b/conout.cpp
new file mode 100644 (file)
index 0000000..9d0659b
--- /dev/null
@@ -0,0 +1,226 @@
+#include <stdio.h>
+#include "gmqcc.h"
+
+#define GMQCC_IS_STDOUT(X) ((X) == stdout)
+#define GMQCC_IS_STDERR(X) ((X) == stderr)
+#define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X))
+
+typedef struct {
+    FILE *handle_err;
+    FILE *handle_out;
+    int color_err;
+    int color_out;
+} con_t;
+
+static con_t console;
+
+/*
+ * Enables color on output if supported.
+ * NOTE: The support for checking colors is NULL.  On windows this will
+ * always work, on *nix it depends if the term has colors.
+ *
+ * NOTE: This prevents colored output to piped stdout/err via isatty
+ * checks.
+ */
+static void con_enablecolor(void) {
+    console.color_err = util_isatty(console.handle_err);
+    console.color_out = util_isatty(console.handle_out);
+}
+
+/*
+ * Does a write to the handle with the format string and list of
+ * arguments.  This colorizes for windows as well via translate
+ * step.
+ */
+static int con_write(FILE *handle, const char *fmt, va_list va) {
+    return vfprintf(handle, fmt, va);
+}
+
+/**********************************************************************
+ * EXPOSED INTERFACE BEGINS
+ *********************************************************************/
+
+void con_close() {
+    if (!GMQCC_IS_DEFINE(console.handle_err))
+        fclose(console.handle_err);
+    if (!GMQCC_IS_DEFINE(console.handle_out))
+        fclose(console.handle_out);
+}
+
+void con_color(int state) {
+    if (state)
+        con_enablecolor();
+    else {
+        console.color_err = 0;
+        console.color_out = 0;
+    }
+}
+
+void con_init() {
+    console.handle_err = stderr;
+    console.handle_out = stdout;
+    con_enablecolor();
+}
+
+void con_reset() {
+    con_close();
+    con_init();
+}
+
+/*
+ * Defaultizer because stdio.h shouldn't be used anywhere except here
+ * and inside file.c To prevent mis-match of wrapper-interfaces.
+ */
+FILE *con_default_out() {
+    return console.handle_out = stdout;
+}
+
+FILE *con_default_err() {
+    return console.handle_err = stderr;
+}
+
+int con_verr(const char *fmt, va_list va) {
+    return con_write(console.handle_err, fmt, va);
+}
+int con_vout(const char *fmt, va_list va) {
+    return con_write(console.handle_out, fmt, va);
+}
+
+/*
+ * Standard stdout/stderr printf functions used generally where they need
+ * to be used.
+ */
+int con_err(const char *fmt, ...) {
+    va_list va;
+    int ln = 0;
+    va_start(va, fmt);
+    con_verr(fmt, va);
+    va_end(va);
+    return ln;
+}
+int con_out(const char *fmt, ...) {
+    va_list va;
+    int ln = 0;
+    va_start(va, fmt);
+    con_vout(fmt, va);
+    va_end (va);
+    return ln;
+}
+
+/*
+ * Utility console message writes for lexer contexts.  These will allow
+ * for reporting of file:line based on lexer context, These are used
+ * heavily in the parser/ir/ast.
+ */
+static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) {
+    /* color selection table */
+    static int sel[] = {
+        CON_WHITE,
+        CON_CYAN,
+        CON_RED
+    };
+
+    int  err                             = !!(level == LVL_ERROR);
+    int  color                           = (err) ? console.color_err : console.color_out;
+    int (*print) (const char *, ...)     = (err) ? &con_err          : &con_out;
+    int (*vprint)(const char *, va_list) = (err) ? &con_verr         : &con_vout;
+
+    if (color)
+        print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype);
+    else
+        print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype);
+
+    vprint(msg, ap);
+    if (condname)
+        print(" [%s]\n", condname);
+    else
+        print("\n");
+}
+
+void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) {
+    con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, NULL);
+}
+
+void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) {
+    va_list   va;
+    va_start(va, msg);
+    con_vprintmsg(level, name, line, column, msgtype, msg, va);
+    va_end  (va);
+}
+
+void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) {
+    con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap);
+}
+
+void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) {
+    va_list   va;
+    va_start(va, msg);
+    con_cvprintmsg(ctx, lvl, msgtype, msg, va);
+    va_end  (va);
+}
+
+/* General error interface: TODO seperate as part of the compiler front-end */
+size_t compile_errors   = 0;
+size_t compile_warnings = 0;
+size_t compile_Werrors  = 0;
+static lex_ctx_t first_werror;
+
+void compile_show_werrors()
+{
+    con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here");
+}
+
+void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap)
+{
+    ++compile_errors;
+    con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap);
+}
+
+void compile_error(lex_ctx_t ctx, const char *msg, ...)
+{
+    va_list ap;
+    va_start(ap, msg);
+    vcompile_error(ctx, msg, ap);
+    va_end(ap);
+}
+
+bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap)
+{
+    const char *msgtype = "warning";
+    int         lvl     = LVL_WARNING;
+    char        warn_name[1024];
+
+    if (!OPTS_WARN(warntype))
+        return false;
+
+    warn_name[0] = '-';
+    warn_name[1] = 'W';
+    (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2);
+
+    ++compile_warnings;
+    if (OPTS_WERROR(warntype)) {
+        if (!compile_Werrors)
+            first_werror = ctx;
+        ++compile_Werrors;
+        msgtype = "Werror";
+        if (OPTS_FLAG(BAIL_ON_WERROR)) {
+            msgtype = "error";
+            ++compile_errors;
+        }
+        lvl = LVL_ERROR;
+    }
+
+    con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name);
+
+    return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR);
+}
+
+bool GMQCC_WARN compile_warning(lex_ctx_t 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;
+}
diff --git a/exec.c b/exec.c
deleted file mode 100644 (file)
index 8ec97e4..0000000
--- a/exec.c
+++ /dev/null
@@ -1,1637 +0,0 @@
-#ifndef QCVM_LOOP
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include "gmqcc.h"
-
-static void loaderror(const char *fmt, ...)
-{
-    int     err = errno;
-    va_list ap;
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
-    printf(": %s\n", util_strerror(err));
-}
-
-static void qcvmerror(qc_program_t *prog, const char *fmt, ...)
-{
-    va_list ap;
-
-    prog->vmerror++;
-
-    va_start(ap, fmt);
-    vprintf(fmt, ap);
-    va_end(ap);
-    putchar('\n');
-}
-
-qc_program_t* prog_load(const char *filename, bool skipversion)
-{
-    prog_header_t   header;
-    qc_program_t   *prog;
-    size_t          i;
-    FILE      *file  = fopen(filename, "rb");
-
-    /* we need all those in order to support INSTR_STATE: */
-    bool            has_self      = false,
-                    has_time      = false,
-                    has_think     = false,
-                    has_nextthink = false,
-                    has_frame     = false;
-
-    if (!file)
-        return NULL;
-
-    if (fread(&header, sizeof(header), 1, file) != 1) {
-        loaderror("failed to read header from '%s'", filename);
-        fclose(file);
-        return NULL;
-    }
-
-    util_swap_header(&header);
-
-    if (!skipversion && header.version != 6) {
-        loaderror("header says this is a version %i progs, we need version 6\n", header.version);
-        fclose(file);
-        return NULL;
-    }
-
-    prog = (qc_program_t*)mem_a(sizeof(qc_program_t));
-    if (!prog) {
-        fclose(file);
-        fprintf(stderr, "failed to allocate program data\n");
-        return NULL;
-    }
-    memset(prog, 0, sizeof(*prog));
-
-    prog->entityfields = header.entfield;
-    prog->crc16 = header.crc16;
-
-    prog->filename = util_strdup(filename);
-    if (!prog->filename) {
-        loaderror("failed to store program name");
-        goto error;
-    }
-
-#define read_data(hdrvar, progvar, reserved)                           \
-    if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) {            \
-        loaderror("seek failed");                                      \
-        goto error;                                                    \
-    }                                                                  \
-    if (fread(                                                         \
-            vec_add(prog->progvar, header.hdrvar.length + reserved),   \
-            sizeof(*prog->progvar),                                    \
-            header.hdrvar.length,                                      \
-            file                                                       \
-        )!= header.hdrvar.length                                       \
-    ) {                                                                \
-        loaderror("read failed");                                      \
-        goto error;                                                    \
-    }
-#define read_data1(x)    read_data(x, x, 0)
-#define read_data2(x, y) read_data(x, x, y)
-
-    read_data (statements, code, 0);
-    read_data1(defs);
-    read_data1(fields);
-    read_data1(functions);
-    read_data1(strings);
-    read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */
-
-    util_swap_statements (prog->code);
-    util_swap_defs_fields(prog->defs);
-    util_swap_defs_fields(prog->fields);
-    util_swap_functions  (prog->functions);
-    util_swap_globals    (prog->globals);
-
-    fclose(file);
-
-    /* profile counters */
-    memset(vec_add(prog->profile, vec_size(prog->code)), 0, sizeof(prog->profile[0]) * vec_size(prog->code));
-
-    /* Add tempstring area */
-    prog->tempstring_start = vec_size(prog->strings);
-    prog->tempstring_at    = vec_size(prog->strings);
-    memset(vec_add(prog->strings, 16*1024), 0, 16*1024);
-
-    /* spawn the world entity */
-    vec_push(prog->entitypool, true);
-    memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
-    prog->entities = 1;
-
-    /* cache some globals and fields from names */
-    for (i = 0; i < vec_size(prog->defs); ++i) {
-        const char *name = prog_getstring(prog, prog->defs[i].name);
-        if      (!strcmp(name, "self")) {
-            prog->cached_globals.self = prog->defs[i].offset;
-            has_self = true;
-        }
-        else if (!strcmp(name, "time")) {
-            prog->cached_globals.time = prog->defs[i].offset;
-            has_time = true;
-        }
-    }
-    for (i = 0; i < vec_size(prog->fields); ++i) {
-        const char *name = prog_getstring(prog, prog->fields[i].name);
-        if      (!strcmp(name, "think")) {
-            prog->cached_fields.think     = prog->fields[i].offset;
-            has_think = true;
-        }
-        else if (!strcmp(name, "nextthink")) {
-            prog->cached_fields.nextthink = prog->fields[i].offset;
-            has_nextthink = true;
-        }
-        else if (!strcmp(name, "frame")) {
-            prog->cached_fields.frame     = prog->fields[i].offset;
-            has_frame = true;
-        }
-    }
-    if (has_self && has_time && has_think && has_nextthink && has_frame)
-        prog->supports_state = true;
-
-    return prog;
-
-error:
-    if (prog->filename)
-        mem_d(prog->filename);
-    vec_free(prog->code);
-    vec_free(prog->defs);
-    vec_free(prog->fields);
-    vec_free(prog->functions);
-    vec_free(prog->strings);
-    vec_free(prog->globals);
-    vec_free(prog->entitydata);
-    vec_free(prog->entitypool);
-    mem_d(prog);
-
-    fclose(file);
-    return NULL;
-}
-
-void prog_delete(qc_program_t *prog)
-{
-    if (prog->filename) mem_d(prog->filename);
-    vec_free(prog->code);
-    vec_free(prog->defs);
-    vec_free(prog->fields);
-    vec_free(prog->functions);
-    vec_free(prog->strings);
-    vec_free(prog->globals);
-    vec_free(prog->entitydata);
-    vec_free(prog->entitypool);
-    vec_free(prog->localstack);
-    vec_free(prog->stack);
-    vec_free(prog->profile);
-    mem_d(prog);
-}
-
-/***********************************************************************
- * VM code
- */
-
-const char* prog_getstring(qc_program_t *prog, qcint_t str) {
-    /* cast for return required for C++ */
-    if (str < 0 || str >= (qcint_t)vec_size(prog->strings))
-        return  "<<<invalid string>>>";
-
-    return prog->strings + str;
-}
-
-prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) {
-    size_t i;
-    for (i = 0; i < vec_size(prog->fields); ++i) {
-        if (prog->fields[i].offset == off)
-            return (prog->fields + i);
-    }
-    return NULL;
-}
-
-prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off)
-{
-    size_t i;
-    for (i = 0; i < vec_size(prog->defs); ++i) {
-        if (prog->defs[i].offset == off)
-            return (prog->defs + i);
-    }
-    return NULL;
-}
-
-qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) {
-    if (e >= (qcint_t)vec_size(prog->entitypool)) {
-        prog->vmerror++;
-        fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e);
-        e = 0;
-    }
-    return (qcany_t*)(prog->entitydata + (prog->entityfields * e));
-}
-
-static qcint_t prog_spawn_entity(qc_program_t *prog) {
-    char  *data;
-    qcint_t  e;
-    for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) {
-        if (!prog->entitypool[e]) {
-            data = (char*)(prog->entitydata + (prog->entityfields * e));
-            memset(data, 0, prog->entityfields * sizeof(qcint_t));
-            return e;
-        }
-    }
-    vec_push(prog->entitypool, true);
-    prog->entities++;
-    data = (char*)vec_add(prog->entitydata, prog->entityfields);
-    memset(data, 0, prog->entityfields * sizeof(qcint_t));
-    return e;
-}
-
-static void prog_free_entity(qc_program_t *prog, qcint_t e) {
-    if (!e) {
-        prog->vmerror++;
-        fprintf(stderr, "Trying to free world entity\n");
-        return;
-    }
-    if (e >= (qcint_t)vec_size(prog->entitypool)) {
-        prog->vmerror++;
-        fprintf(stderr, "Trying to free out of bounds entity\n");
-        return;
-    }
-    if (!prog->entitypool[e]) {
-        prog->vmerror++;
-        fprintf(stderr, "Double free on entity\n");
-        return;
-    }
-    prog->entitypool[e] = false;
-}
-
-qcint_t prog_tempstring(qc_program_t *prog, const char *str) {
-    size_t len = strlen(str);
-    size_t at = prog->tempstring_at;
-
-    /* when we reach the end we start over */
-    if (at + len >= vec_size(prog->strings))
-        at = prog->tempstring_start;
-
-    /* when it doesn't fit, reallocate */
-    if (at + len >= vec_size(prog->strings))
-    {
-        (void)vec_add(prog->strings, len+1);
-        memcpy(prog->strings + at, str, len+1);
-        return at;
-    }
-
-    /* when it fits, just copy */
-    memcpy(prog->strings + at, str, len+1);
-    prog->tempstring_at += len+1;
-    return at;
-}
-
-static size_t print_escaped_string(const char *str, size_t maxlen) {
-    size_t len = 2;
-    putchar('"');
-    --maxlen; /* because we're lazy and have escape sequences */
-    while (*str) {
-        if (len >= maxlen) {
-            putchar('.');
-            putchar('.');
-            putchar('.');
-            len += 3;
-            break;
-        }
-        switch (*str) {
-            case '\a': len += 2; putchar('\\'); putchar('a'); break;
-            case '\b': len += 2; putchar('\\'); putchar('b'); break;
-            case '\r': len += 2; putchar('\\'); putchar('r'); break;
-            case '\n': len += 2; putchar('\\'); putchar('n'); break;
-            case '\t': len += 2; putchar('\\'); putchar('t'); break;
-            case '\f': len += 2; putchar('\\'); putchar('f'); break;
-            case '\v': len += 2; putchar('\\'); putchar('v'); break;
-            case '\\': len += 2; putchar('\\'); putchar('\\'); break;
-            case '"':  len += 2; putchar('\\'); putchar('"'); break;
-            default:
-                ++len;
-                putchar(*str);
-                break;
-        }
-        ++str;
-    }
-    putchar('"');
-    return len;
-}
-
-static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) {
-    static char spaces[28+1] = "                            ";
-    prog_section_def_t *def;
-    qcany_t    *value;
-    int       len;
-
-    if (!glob) {
-        if ((len = printf("<null>,")) == -1)
-            len = 0;
-
-        goto done;
-    }
-
-    def = prog_getdef(prog, glob);
-    value = (qcany_t*)(&prog->globals[glob]);
-
-    len = printf("[@%u] ", glob);
-    if (def) {
-        const char *name = prog_getstring(prog, def->name);
-        if (name[0] == '#')
-            len += printf("$");
-        else
-            len += printf("%s ", name);
-        vtype = def->type & DEF_TYPEMASK;
-    }
-
-    switch (vtype) {
-        case TYPE_VOID:
-        case TYPE_ENTITY:
-        case TYPE_FIELD:
-        case TYPE_FUNCTION:
-        case TYPE_POINTER:
-            len += printf("(%i),", value->_int);
-            break;
-        case TYPE_VECTOR:
-            len += printf("'%g %g %g',", value->vector[0],
-                                         value->vector[1],
-                                         value->vector[2]);
-            break;
-        case TYPE_STRING:
-            if (value->string)
-                len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5);
-            else
-                len += printf("(null)");
-            len += printf(",");
-            /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
-            break;
-        case TYPE_FLOAT:
-        default:
-            len += printf("%g,", value->_float);
-            break;
-    }
-done:
-    if (len < (int)sizeof(spaces)-1) {
-        spaces[sizeof(spaces)-1-len] = 0;
-        fputs(spaces, stdout);
-        spaces[sizeof(spaces)-1-len] = ' ';
-    }
-}
-
-static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) {
-    if (st->opcode >= VINSTR_END) {
-        printf("<illegal instruction %d>\n", st->opcode);
-        return;
-    }
-    if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) {
-        size_t i;
-        for (i = 0; i < vec_size(prog->function_stack); ++i)
-            printf("->");
-        printf("%s:", vec_last(prog->function_stack));
-    }
-    printf(" <> %-12s", util_instr_str[st->opcode]);
-    if (st->opcode >= INSTR_IF &&
-        st->opcode <= INSTR_IFNOT)
-    {
-        trace_print_global(prog, st->o1.u1, TYPE_FLOAT);
-        printf("%d\n", st->o2.s1);
-    }
-    else if (st->opcode >= INSTR_CALL0 &&
-             st->opcode <= INSTR_CALL8)
-    {
-        trace_print_global(prog, st->o1.u1, TYPE_FUNCTION);
-        printf("\n");
-    }
-    else if (st->opcode == INSTR_GOTO)
-    {
-        printf("%i\n", st->o1.s1);
-    }
-    else
-    {
-        int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT };
-        switch (st->opcode)
-        {
-            case INSTR_MUL_FV:
-                t[1] = t[2] = TYPE_VECTOR;
-                break;
-            case INSTR_MUL_VF:
-                t[0] = t[2] = TYPE_VECTOR;
-                break;
-            case INSTR_MUL_V:
-                t[0] = t[1] = TYPE_VECTOR;
-                break;
-            case INSTR_ADD_V:
-            case INSTR_SUB_V:
-            case INSTR_EQ_V:
-            case INSTR_NE_V:
-                t[0] = t[1] = t[2] = TYPE_VECTOR;
-                break;
-            case INSTR_EQ_S:
-            case INSTR_NE_S:
-                t[0] = t[1] = TYPE_STRING;
-                break;
-            case INSTR_STORE_F:
-            case INSTR_STOREP_F:
-                t[2] = -1;
-                break;
-            case INSTR_STORE_V:
-                t[0] = t[1] = TYPE_VECTOR; t[2] = -1;
-                break;
-            case INSTR_STORE_S:
-                t[0] = t[1] = TYPE_STRING; t[2] = -1;
-                break;
-            case INSTR_STORE_ENT:
-                t[0] = t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STORE_FLD:
-                t[0] = t[1] = TYPE_FIELD; t[2] = -1;
-                break;
-            case INSTR_STORE_FNC:
-                t[0] = t[1] = TYPE_FUNCTION; t[2] = -1;
-                break;
-            case INSTR_STOREP_V:
-                t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_S:
-                t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_ENT:
-                t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_FLD:
-                t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-            case INSTR_STOREP_FNC:
-                t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1;
-                break;
-        }
-        if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]);
-        else           printf("(none),          ");
-        if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]);
-        else           printf("(none),          ");
-        if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]);
-        else           printf("(none)");
-        printf("\n");
-    }
-}
-
-static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) {
-    qc_exec_stack_t st;
-    size_t  parampos;
-    int32_t p;
-
-    /* back up locals */
-    st.localsp  = vec_size(prog->localstack);
-    st.stmt     = prog->statement;
-    st.function = func;
-
-    if (prog->xflags & VMXF_TRACE) {
-        const char *str = prog_getstring(prog, func->name);
-        vec_push(prog->function_stack, str);
-    }
-
-#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
-    if (vec_size(prog->stack))
-    {
-        prog_section_function_t *cur;
-        cur = prog->stack[vec_size(prog->stack)-1].function;
-        if (cur)
-        {
-            qcint_t *globals = prog->globals + cur->firstlocal;
-            vec_append(prog->localstack, cur->locals, globals);
-        }
-    }
-#else
-    {
-        qcint_t *globals = prog->globals + func->firstlocal;
-        vec_append(prog->localstack, func->locals, globals);
-    }
-#endif
-
-    /* copy parameters */
-    parampos = func->firstlocal;
-    for (p = 0; p < func->nargs; ++p)
-    {
-        size_t s;
-        for (s = 0; s < func->argsize[p]; ++s) {
-            prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s];
-            ++parampos;
-        }
-    }
-
-    vec_push(prog->stack, st);
-
-    return func->entry;
-}
-
-static qcint_t prog_leavefunction(qc_program_t *prog) {
-    prog_section_function_t *prev = NULL;
-    size_t oldsp;
-
-    qc_exec_stack_t st = vec_last(prog->stack);
-
-    if (prog->xflags & VMXF_TRACE) {
-        if (vec_size(prog->function_stack))
-            vec_pop(prog->function_stack);
-    }
-
-#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
-    if (vec_size(prog->stack) > 1) {
-        prev  = prog->stack[vec_size(prog->stack)-2].function;
-        oldsp = prog->stack[vec_size(prog->stack)-2].localsp;
-    }
-#else
-    prev  = prog->stack[vec_size(prog->stack)-1].function;
-    oldsp = prog->stack[vec_size(prog->stack)-1].localsp;
-#endif
-    if (prev) {
-        qcint_t *globals = prog->globals + prev->firstlocal;
-        memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0]));
-        /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */
-        vec_shrinkto(prog->localstack, oldsp);
-    }
-
-    vec_pop(prog->stack);
-
-    return st.stmt - 1; /* offset the ++st */
-}
-
-bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) {
-    long jumpcount = 0;
-    size_t oldxflags = prog->xflags;
-    prog_section_statement_t *st;
-
-    prog->vmerror = 0;
-    prog->xflags = flags;
-
-    st = prog->code + prog_enterfunction(prog, func);
-    --st;
-    switch (flags)
-    {
-        default:
-        case 0:
-        {
-#define QCVM_LOOP    1
-#define QCVM_PROFILE 0
-#define QCVM_TRACE   0
-#           include __FILE__
-        }
-        case (VMXF_TRACE):
-        {
-#define QCVM_PROFILE 0
-#define QCVM_TRACE   1
-#           include __FILE__
-        }
-        case (VMXF_PROFILE):
-        {
-#define QCVM_PROFILE 1
-#define QCVM_TRACE   0
-#           include __FILE__
-        }
-        case (VMXF_TRACE|VMXF_PROFILE):
-        {
-#define QCVM_PROFILE 1
-#define QCVM_TRACE   1
-#           include __FILE__
-        }
-    };
-
-cleanup:
-    prog->xflags = oldxflags;
-    vec_free(prog->localstack);
-    vec_free(prog->stack);
-    if (prog->vmerror)
-        return false;
-    return true;
-}
-
-/***********************************************************************
- * main for when building the standalone executor
- */
-
-#include <math.h>
-
-const char *type_name[TYPE_COUNT] = {
-    "void",
-    "string",
-    "float",
-    "vector",
-    "entity",
-    "field",
-    "function",
-    "pointer",
-    "integer",
-
-    "variant",
-
-    "struct",
-    "union",
-    "array",
-
-    "nil",
-    "noexpr"
-};
-
-typedef struct {
-    int         vtype;
-    const char *value;
-} qcvm_parameter;
-
-static qcvm_parameter *main_params = NULL;
-
-#define CheckArgs(num) do {                                                    \
-    if (prog->argc != (num)) {                                                 \
-        prog->vmerror++;                                                       \
-        fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \
-        __FUNCTION__, prog->argc, (num));                                      \
-        return -1;                                                             \
-    }                                                                          \
-} while (0)
-
-#define GetGlobal(idx) ((qcany_t*)(prog->globals + (idx)))
-#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
-#define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
-
-static int qc_print(qc_program_t *prog) {
-    size_t i;
-    const char *laststr = NULL;
-    for (i = 0; i < (size_t)prog->argc; ++i) {
-        qcany_t *str = (qcany_t*)(prog->globals + OFS_PARM0 + 3*i);
-        laststr = prog_getstring(prog, str->string);
-        printf("%s", laststr);
-    }
-    if (laststr && (prog->xflags & VMXF_TRACE)) {
-        size_t len = strlen(laststr);
-        if (!len || laststr[len-1] != '\n')
-            printf("\n");
-    }
-    return 0;
-}
-
-static int qc_error(qc_program_t *prog) {
-    fprintf(stderr, "*** VM raised an error:\n");
-    qc_print(prog);
-    prog->vmerror++;
-    return -1;
-}
-
-static int qc_ftos(qc_program_t *prog) {
-    char buffer[512];
-    qcany_t *num;
-    qcany_t str;
-    CheckArgs(1);
-    num = GetArg(0);
-    util_snprintf(buffer, sizeof(buffer), "%g", num->_float);
-    str.string = prog_tempstring(prog, buffer);
-    Return(str);
-    return 0;
-}
-
-static int qc_stof(qc_program_t *prog) {
-    qcany_t *str;
-    qcany_t num;
-    CheckArgs(1);
-    str = GetArg(0);
-    num._float = (float)strtod(prog_getstring(prog, str->string), NULL);
-    Return(num);
-    return 0;
-}
-
-static int qc_vtos(qc_program_t *prog) {
-    char buffer[512];
-    qcany_t *num;
-    qcany_t str;
-    CheckArgs(1);
-    num = GetArg(0);
-    util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]);
-    str.string = prog_tempstring(prog, buffer);
-    Return(str);
-    return 0;
-}
-
-static int qc_etos(qc_program_t *prog) {
-    char buffer[512];
-    qcany_t *num;
-    qcany_t str;
-    CheckArgs(1);
-    num = GetArg(0);
-    util_snprintf(buffer, sizeof(buffer), "%i", num->_int);
-    str.string = prog_tempstring(prog, buffer);
-    Return(str);
-    return 0;
-}
-
-static int qc_spawn(qc_program_t *prog) {
-    qcany_t ent;
-    CheckArgs(0);
-    ent.edict = prog_spawn_entity(prog);
-    Return(ent);
-    return (ent.edict ? 0 : -1);
-}
-
-static int qc_kill(qc_program_t *prog) {
-    qcany_t *ent;
-    CheckArgs(1);
-    ent = GetArg(0);
-    prog_free_entity(prog, ent->edict);
-    return 0;
-}
-
-static int qc_sqrt(qc_program_t *prog) {
-    qcany_t *num, out;
-    CheckArgs(1);
-    num = GetArg(0);
-    out._float = sqrt(num->_float);
-    Return(out);
-    return 0;
-}
-
-static int qc_vlen(qc_program_t *prog) {
-    qcany_t *vec, len;
-    CheckArgs(1);
-    vec = GetArg(0);
-    len._float = sqrt(vec->vector[0] * vec->vector[0] +
-                      vec->vector[1] * vec->vector[1] +
-                      vec->vector[2] * vec->vector[2]);
-    Return(len);
-    return 0;
-}
-
-static int qc_normalize(qc_program_t *prog) {
-    double len;
-    qcany_t *vec;
-    qcany_t out;
-    CheckArgs(1);
-    vec = GetArg(0);
-    len = sqrt(vec->vector[0] * vec->vector[0] +
-               vec->vector[1] * vec->vector[1] +
-               vec->vector[2] * vec->vector[2]);
-    if (len)
-        len = 1.0 / len;
-    else
-        len = 0;
-    out.vector[0] = len * vec->vector[0];
-    out.vector[1] = len * vec->vector[1];
-    out.vector[2] = len * vec->vector[2];
-    Return(out);
-    return 0;
-}
-
-static int qc_strcat(qc_program_t *prog) {
-    char  *buffer;
-    size_t len1,   len2;
-    qcany_t *str1,  *str2;
-    qcany_t  out;
-
-    const char *cstr1;
-    const char *cstr2;
-
-    CheckArgs(2);
-    str1 = GetArg(0);
-    str2 = GetArg(1);
-    cstr1 = prog_getstring(prog, str1->string);
-    cstr2 = prog_getstring(prog, str2->string);
-    len1 = strlen(cstr1);
-    len2 = strlen(cstr2);
-    buffer = (char*)mem_a(len1 + len2 + 1);
-    memcpy(buffer, cstr1, len1);
-    memcpy(buffer+len1, cstr2, len2+1);
-    out.string = prog_tempstring(prog, buffer);
-    mem_d(buffer);
-    Return(out);
-    return 0;
-}
-
-static int qc_strcmp(qc_program_t *prog) {
-    qcany_t *str1,  *str2;
-    qcany_t out;
-
-    const char *cstr1;
-    const char *cstr2;
-
-    if (prog->argc != 2 && prog->argc != 3) {
-        fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n",
-               prog->argc);
-        return -1;
-    }
-
-    str1 = GetArg(0);
-    str2 = GetArg(1);
-    cstr1 = prog_getstring(prog, str1->string);
-    cstr2 = prog_getstring(prog, str2->string);
-    if (prog->argc == 3)
-        out._float = strncmp(cstr1, cstr2, GetArg(2)->_float);
-    else
-        out._float = strcmp(cstr1, cstr2);
-    Return(out);
-    return 0;
-}
-
-static int qc_floor(qc_program_t *prog) {
-    qcany_t *num, out;
-    CheckArgs(1);
-    num = GetArg(0);
-    out._float = floor(num->_float);
-    Return(out);
-    return 0;
-}
-
-static int qc_pow(qc_program_t *prog) {
-    qcany_t *base, *exp, out;
-    CheckArgs(2);
-    base = GetArg(0);
-    exp = GetArg(1);
-    out._float = powf(base->_float, exp->_float);
-    Return(out);
-    return 0;
-}
-
-static prog_builtin_t qc_builtins[] = {
-    NULL,
-    &qc_print,       /*   1   */
-    &qc_ftos,        /*   2   */
-    &qc_spawn,       /*   3   */
-    &qc_kill,        /*   4   */
-    &qc_vtos,        /*   5   */
-    &qc_error,       /*   6   */
-    &qc_vlen,        /*   7   */
-    &qc_etos,        /*   8   */
-    &qc_stof,        /*   9   */
-    &qc_strcat,      /*   10  */
-    &qc_strcmp,      /*   11  */
-    &qc_normalize,   /*   12  */
-    &qc_sqrt,        /*   13  */
-    &qc_floor,       /*   14  */
-    &qc_pow          /*   15  */
-};
-
-static const char *arg0 = NULL;
-
-static void version(void) {
-    printf("GMQCC-QCVM %d.%d.%d Built %s %s\n",
-           GMQCC_VERSION_MAJOR,
-           GMQCC_VERSION_MINOR,
-           GMQCC_VERSION_PATCH,
-           __DATE__,
-           __TIME__
-    );
-}
-
-static void usage(void) {
-    printf("usage: %s [options] [parameters] file\n", arg0);
-    printf("options:\n");
-    printf("  -h, --help         print this message\n"
-           "  -trace             trace the execution\n"
-           "  -profile           perform profiling during execution\n"
-           "  -info              print information from the prog's header\n"
-           "  -disasm            disassemble and exit\n"
-           "  -disasm-func func  disassemble and exit\n"
-           "  -printdefs         list the defs section\n"
-           "  -printfields       list the field section\n"
-           "  -printfuns         list functions information\n"
-           "  -v                 be verbose\n"
-           "  -vv                be even more verbose\n");
-    printf("parameters:\n");
-    printf("  -vector <V>   pass a vector parameter to main()\n"
-           "  -float  <f>   pass a float parameter to main()\n"
-           "  -string <s>   pass a string parameter to main() \n");
-}
-
-static void prog_main_setparams(qc_program_t *prog) {
-    size_t i;
-    qcany_t *arg;
-
-    for (i = 0; i < vec_size(main_params); ++i) {
-        arg = GetGlobal(OFS_PARM0 + 3*i);
-        arg->vector[0] = 0;
-        arg->vector[1] = 0;
-        arg->vector[2] = 0;
-        switch (main_params[i].vtype) {
-            case TYPE_VECTOR:
-                (void)util_sscanf(main_params[i].value, " %f %f %f ",
-                                       &arg->vector[0],
-                                       &arg->vector[1],
-                                       &arg->vector[2]);
-                break;
-            case TYPE_FLOAT:
-                arg->_float = atof(main_params[i].value);
-                break;
-            case TYPE_STRING:
-                arg->string = prog_tempstring(prog, main_params[i].value);
-                break;
-            default:
-                fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype);
-                break;
-        }
-    }
-}
-
-static void prog_disasm_function(qc_program_t *prog, size_t id);
-
-int main(int argc, char **argv) {
-    size_t      i;
-    qcint_t       fnmain = -1;
-    qc_program_t *prog;
-    size_t      xflags = VMXF_DEFAULT;
-    bool        opts_printfields = false;
-    bool        opts_printdefs   = false;
-    bool        opts_printfuns   = false;
-    bool        opts_disasm      = false;
-    bool        opts_info        = false;
-    bool        noexec           = false;
-    const char *progsfile        = NULL;
-    const char **dis_list        = NULL;
-    int         opts_v           = 0;
-
-    arg0 = argv[0];
-
-    if (argc < 2) {
-        usage();
-        exit(EXIT_FAILURE);
-    }
-
-    while (argc > 1) {
-        if (!strcmp(argv[1], "-h") ||
-            !strcmp(argv[1], "-help") ||
-            !strcmp(argv[1], "--help"))
-        {
-            usage();
-            exit(EXIT_SUCCESS);
-        }
-        else if (!strcmp(argv[1], "-v")) {
-            ++opts_v;
-            --argc;
-            ++argv;
-        }
-        else if (!strncmp(argv[1], "-vv", 3)) {
-            const char *av = argv[1]+1;
-            for (; *av; ++av) {
-                if (*av == 'v')
-                    ++opts_v;
-                else {
-                    usage();
-                    exit(EXIT_FAILURE);
-                }
-            }
-            --argc;
-            ++argv;
-        }
-        else if (!strcmp(argv[1], "-version") ||
-                 !strcmp(argv[1], "--version"))
-        {
-            version();
-            exit(EXIT_SUCCESS);
-        }
-        else if (!strcmp(argv[1], "-trace")) {
-            --argc;
-            ++argv;
-            xflags |= VMXF_TRACE;
-        }
-        else if (!strcmp(argv[1], "-profile")) {
-            --argc;
-            ++argv;
-            xflags |= VMXF_PROFILE;
-        }
-        else if (!strcmp(argv[1], "-info")) {
-            --argc;
-            ++argv;
-            opts_info = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-disasm")) {
-            --argc;
-            ++argv;
-            opts_disasm = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-disasm-func")) {
-            --argc;
-            ++argv;
-            if (argc <= 1) {
-                usage();
-                exit(EXIT_FAILURE);
-            }
-            vec_push(dis_list, argv[1]);
-            --argc;
-            ++argv;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-printdefs")) {
-            --argc;
-            ++argv;
-            opts_printdefs = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-printfuns")) {
-            --argc;
-            ++argv;
-            opts_printfuns = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-printfields")) {
-            --argc;
-            ++argv;
-            opts_printfields = true;
-            noexec = true;
-        }
-        else if (!strcmp(argv[1], "-vector") ||
-                 !strcmp(argv[1], "-string") ||
-                 !strcmp(argv[1], "-float") )
-        {
-            qcvm_parameter p;
-            if (argv[1][1] == 'f')
-                p.vtype = TYPE_FLOAT;
-            else if (argv[1][1] == 's')
-                p.vtype = TYPE_STRING;
-            else if (argv[1][1] == 'v')
-                p.vtype = TYPE_VECTOR;
-            else
-                p.vtype = TYPE_VOID;
-
-            --argc;
-            ++argv;
-            if (argc < 2) {
-                usage();
-                exit(EXIT_FAILURE);
-            }
-            p.value = argv[1];
-
-            vec_push(main_params, p);
-            --argc;
-            ++argv;
-        }
-        else if (!strcmp(argv[1], "--")) {
-            --argc;
-            ++argv;
-            break;
-        }
-        else if (argv[1][0] != '-') {
-            if (progsfile) {
-                fprintf(stderr, "only 1 program file may be specified\n");
-                usage();
-                exit(EXIT_FAILURE);
-            }
-            progsfile = argv[1];
-            --argc;
-            ++argv;
-        }
-        else
-        {
-            fprintf(stderr, "unknown parameter: %s\n", argv[1]);
-            usage();
-            exit(EXIT_FAILURE);
-        }
-    }
-
-    if (argc == 2 && !progsfile) {
-        progsfile = argv[1];
-        --argc;
-        ++argv;
-    }
-
-    if (!progsfile) {
-        fprintf(stderr, "must specify a program to execute\n");
-        usage();
-        exit(EXIT_FAILURE);
-    }
-
-    prog = prog_load(progsfile, noexec);
-    if (!prog) {
-        fprintf(stderr, "failed to load program '%s'\n", progsfile);
-        exit(EXIT_FAILURE);
-    }
-
-    prog->builtins       = qc_builtins;
-    prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins);
-
-    if (opts_info) {
-        printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16);
-        printf("Entity field space: %u\n", (unsigned int)prog->entityfields);
-        printf("Globals: %u\n", (unsigned int)vec_size(prog->globals));
-        printf("Counts:\n"
-               "      code: %lu\n"
-               "      defs: %lu\n"
-               "    fields: %lu\n"
-               " functions: %lu\n"
-               "   strings: %lu\n",
-               (unsigned long)vec_size(prog->code),
-               (unsigned long)vec_size(prog->defs),
-               (unsigned long)vec_size(prog->fields),
-               (unsigned long)vec_size(prog->functions),
-               (unsigned long)vec_size(prog->strings));
-    }
-
-    if (opts_info) {
-        prog_delete(prog);
-        return 0;
-    }
-    for (i = 0; i < vec_size(dis_list); ++i) {
-        size_t k;
-        printf("Looking for `%s`\n", dis_list[i]);
-        for (k = 1; k < vec_size(prog->functions); ++k) {
-            const char *name = prog_getstring(prog, prog->functions[k].name);
-            if (!strcmp(name, dis_list[i])) {
-                prog_disasm_function(prog, k);
-                break;
-            }
-        }
-    }
-    if (opts_disasm) {
-        for (i = 1; i < vec_size(prog->functions); ++i)
-            prog_disasm_function(prog, i);
-        return 0;
-    }
-    if (opts_printdefs) {
-        const char *getstring = NULL;
-        for (i = 0; i < vec_size(prog->defs); ++i) {
-            printf("Global: %8s %-16s at %u%s",
-                   type_name[prog->defs[i].type & DEF_TYPEMASK],
-                   prog_getstring(prog, prog->defs[i].name),
-                   (unsigned int)prog->defs[i].offset,
-                   ((prog->defs[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
-            if (opts_v) {
-                switch (prog->defs[i].type & DEF_TYPEMASK) {
-                    case TYPE_FLOAT:
-                        printf(" [init: %g]", ((qcany_t*)(prog->globals + prog->defs[i].offset))->_float);
-                        break;
-                    case TYPE_INTEGER:
-                        printf(" [init: %i]", (int)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int ));
-                        break;
-                    case TYPE_ENTITY:
-                    case TYPE_FUNCTION:
-                    case TYPE_FIELD:
-                    case TYPE_POINTER:
-                        printf(" [init: %u]", (unsigned)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int ));
-                        break;
-                    case TYPE_STRING:
-                        getstring = prog_getstring(prog, ((qcany_t*)(prog->globals + prog->defs[i].offset))->string);
-                        printf(" [init: `");
-                        print_escaped_string(getstring, strlen(getstring));
-                        printf("`]\n");
-                        break;
-                    default:
-                        break;
-                }
-            }
-            printf("\n");
-        }
-    }
-    if (opts_printfields) {
-        for (i = 0; i < vec_size(prog->fields); ++i) {
-            printf("Field: %8s %-16s at %u%s\n",
-                   type_name[prog->fields[i].type],
-                   prog_getstring(prog, prog->fields[i].name),
-                   (unsigned int)prog->fields[i].offset,
-                   ((prog->fields[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
-        }
-    }
-    if (opts_printfuns) {
-        for (i = 0; i < vec_size(prog->functions); ++i) {
-            int32_t a;
-            printf("Function: %-16s taking %u parameters:(",
-                   prog_getstring(prog, prog->functions[i].name),
-                   (unsigned int)prog->functions[i].nargs);
-            for (a = 0; a < prog->functions[i].nargs; ++a) {
-                printf(" %i", prog->functions[i].argsize[a]);
-            }
-            if (opts_v > 1) {
-                int32_t start = prog->functions[i].entry;
-                if (start < 0)
-                    printf(") builtin %i\n", (int)-start);
-                else {
-                    size_t funsize = 0;
-                    prog_section_statement_t *st = prog->code + start;
-                    for (;st->opcode != INSTR_DONE; ++st)
-                        ++funsize;
-                    printf(") - %lu instructions", (unsigned long)funsize);
-                    if (opts_v > 2) {
-                        printf(" - locals: %i + %i\n",
-                               prog->functions[i].firstlocal,
-                               prog->functions[i].locals);
-                    }
-                    else
-                        printf("\n");
-                }
-            }
-            else if (opts_v) {
-                printf(") locals: %i + %i\n",
-                       prog->functions[i].firstlocal,
-                       prog->functions[i].locals);
-            }
-            else
-                printf(")\n");
-        }
-    }
-    if (!noexec) {
-        for (i = 1; i < vec_size(prog->functions); ++i) {
-            const char *name = prog_getstring(prog, prog->functions[i].name);
-            if (!strcmp(name, "main"))
-                fnmain = (qcint_t)i;
-        }
-        if (fnmain > 0)
-        {
-            prog_main_setparams(prog);
-            prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
-        }
-        else
-            fprintf(stderr, "No main function found\n");
-    }
-
-    prog_delete(prog);
-    return 0;
-}
-
-static void prog_disasm_function(qc_program_t *prog, size_t id) {
-    prog_section_function_t *fdef = prog->functions + id;
-    prog_section_statement_t *st;
-
-    if (fdef->entry < 0) {
-        printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry);
-        return;
-    }
-    else
-        printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name));
-
-    st = prog->code + fdef->entry;
-    while (st->opcode != INSTR_DONE) {
-        prog_print_statement(prog, st);
-        ++st;
-    }
-}
-#else /* !QCVM_LOOP */
-/*
- * Everything from here on is not including into the compilation of the
- * executor.  This is simply code that is #included via #include __FILE__
- * see when QCVM_LOOP is defined, the rest of the code above do not get
- * re-included.  So this really just acts like one large macro, but it
- * sort of isn't, which makes it nicer looking.
- */
-
-#define OPA ( (qcany_t*) (prog->globals + st->o1.u1) )
-#define OPB ( (qcany_t*) (prog->globals + st->o2.u1) )
-#define OPC ( (qcany_t*) (prog->globals + st->o3.u1) )
-
-#define GLOBAL(x) ( (qcany_t*) (prog->globals + (x)) )
-
-/* to be consistent with current darkplaces behaviour */
-#if !defined(FLOAT_IS_TRUE_FOR_INT)
-#   define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF )
-#endif
-
-while (prog->vmerror == 0) {
-    prog_section_function_t  *newf;
-    qcany_t          *ed;
-    qcany_t          *ptr;
-
-    ++st;
-
-#if QCVM_PROFILE
-    prog->profile[st - prog->code]++;
-#endif
-
-#if QCVM_TRACE
-    prog_print_statement(prog, st);
-#endif
-
-    switch (st->opcode)
-    {
-        default:
-            qcvmerror(prog, "Illegal instruction in %s\n", prog->filename);
-            goto cleanup;
-
-        case INSTR_DONE:
-        case INSTR_RETURN:
-            /* TODO: add instruction count to function profile count */
-            GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0];
-            GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1];
-            GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2];
-
-            st = prog->code + prog_leavefunction(prog);
-            if (!vec_size(prog->stack))
-                goto cleanup;
-
-            break;
-
-        case INSTR_MUL_F:
-            OPC->_float = OPA->_float * OPB->_float;
-            break;
-        case INSTR_MUL_V:
-            OPC->_float = OPA->vector[0]*OPB->vector[0] +
-                          OPA->vector[1]*OPB->vector[1] +
-                          OPA->vector[2]*OPB->vector[2];
-            break;
-        case INSTR_MUL_FV:
-        {
-            qcfloat_t f = OPA->_float;
-            OPC->vector[0] = f * OPB->vector[0];
-            OPC->vector[1] = f * OPB->vector[1];
-            OPC->vector[2] = f * OPB->vector[2];
-            break;
-        }
-        case INSTR_MUL_VF:
-        {
-            qcfloat_t f = OPB->_float;
-            OPC->vector[0] = f * OPA->vector[0];
-            OPC->vector[1] = f * OPA->vector[1];
-            OPC->vector[2] = f * OPA->vector[2];
-            break;
-        }
-        case INSTR_DIV_F:
-            if (OPB->_float != 0.0f)
-                OPC->_float = OPA->_float / OPB->_float;
-            else
-                OPC->_float = 0;
-            break;
-
-        case INSTR_ADD_F:
-            OPC->_float = OPA->_float + OPB->_float;
-            break;
-        case INSTR_ADD_V:
-            OPC->vector[0] = OPA->vector[0] + OPB->vector[0];
-            OPC->vector[1] = OPA->vector[1] + OPB->vector[1];
-            OPC->vector[2] = OPA->vector[2] + OPB->vector[2];
-            break;
-        case INSTR_SUB_F:
-            OPC->_float = OPA->_float - OPB->_float;
-            break;
-        case INSTR_SUB_V:
-            OPC->vector[0] = OPA->vector[0] - OPB->vector[0];
-            OPC->vector[1] = OPA->vector[1] - OPB->vector[1];
-            OPC->vector[2] = OPA->vector[2] - OPB->vector[2];
-            break;
-
-        case INSTR_EQ_F:
-            OPC->_float = (OPA->_float == OPB->_float);
-            break;
-        case INSTR_EQ_V:
-            OPC->_float = ((OPA->vector[0] == OPB->vector[0]) &&
-                           (OPA->vector[1] == OPB->vector[1]) &&
-                           (OPA->vector[2] == OPB->vector[2]) );
-            break;
-        case INSTR_EQ_S:
-            OPC->_float = !strcmp(prog_getstring(prog, OPA->string),
-                                  prog_getstring(prog, OPB->string));
-            break;
-        case INSTR_EQ_E:
-            OPC->_float = (OPA->_int == OPB->_int);
-            break;
-        case INSTR_EQ_FNC:
-            OPC->_float = (OPA->function == OPB->function);
-            break;
-        case INSTR_NE_F:
-            OPC->_float = (OPA->_float != OPB->_float);
-            break;
-        case INSTR_NE_V:
-            OPC->_float = ((OPA->vector[0] != OPB->vector[0]) ||
-                           (OPA->vector[1] != OPB->vector[1]) ||
-                           (OPA->vector[2] != OPB->vector[2]) );
-            break;
-        case INSTR_NE_S:
-            OPC->_float = !!strcmp(prog_getstring(prog, OPA->string),
-                                   prog_getstring(prog, OPB->string));
-            break;
-        case INSTR_NE_E:
-            OPC->_float = (OPA->_int != OPB->_int);
-            break;
-        case INSTR_NE_FNC:
-            OPC->_float = (OPA->function != OPB->function);
-            break;
-
-        case INSTR_LE:
-            OPC->_float = (OPA->_float <= OPB->_float);
-            break;
-        case INSTR_GE:
-            OPC->_float = (OPA->_float >= OPB->_float);
-            break;
-        case INSTR_LT:
-            OPC->_float = (OPA->_float < OPB->_float);
-            break;
-        case INSTR_GT:
-            OPC->_float = (OPA->_float > OPB->_float);
-            break;
-
-        case INSTR_LOAD_F:
-        case INSTR_LOAD_S:
-        case INSTR_LOAD_FLD:
-        case INSTR_LOAD_ENT:
-        case INSTR_LOAD_FNC:
-            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
-                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
-                goto cleanup;
-            }
-            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) {
-                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
-                          prog->filename,
-                          OPB->_int);
-                goto cleanup;
-            }
-            ed = prog_getedict(prog, OPA->edict);
-            OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int;
-            break;
-        case INSTR_LOAD_V:
-            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
-                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
-                goto cleanup;
-            }
-            if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields)
-            {
-                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
-                          prog->filename,
-                          OPB->_int + 2);
-                goto cleanup;
-            }
-            ed = prog_getedict(prog, OPA->edict);
-            ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int );
-            OPC->ivector[0] = ptr->ivector[0];
-            OPC->ivector[1] = ptr->ivector[1];
-            OPC->ivector[2] = ptr->ivector[2];
-            break;
-
-        case INSTR_ADDRESS:
-            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
-                qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict);
-                goto cleanup;
-            }
-            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields))
-            {
-                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
-                          prog->filename,
-                          OPB->_int);
-                goto cleanup;
-            }
-
-            ed = prog_getedict(prog, OPA->edict);
-            OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int;
-            break;
-
-        case INSTR_STORE_F:
-        case INSTR_STORE_S:
-        case INSTR_STORE_ENT:
-        case INSTR_STORE_FLD:
-        case INSTR_STORE_FNC:
-            OPB->_int = OPA->_int;
-            break;
-        case INSTR_STORE_V:
-            OPB->ivector[0] = OPA->ivector[0];
-            OPB->ivector[1] = OPA->ivector[1];
-            OPB->ivector[2] = OPA->ivector[2];
-            break;
-
-        case INSTR_STOREP_F:
-        case INSTR_STOREP_S:
-        case INSTR_STOREP_ENT:
-        case INSTR_STOREP_FLD:
-        case INSTR_STOREP_FNC:
-            if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) {
-                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
-                goto cleanup;
-            }
-            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
-                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
-                          prog->filename,
-                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
-                          OPB->_int);
-            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
-            ptr->_int = OPA->_int;
-            break;
-        case INSTR_STOREP_V:
-            if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) {
-                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
-                goto cleanup;
-            }
-            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
-                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
-                          prog->filename,
-                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
-                          OPB->_int);
-            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
-            ptr->ivector[0] = OPA->ivector[0];
-            ptr->ivector[1] = OPA->ivector[1];
-            ptr->ivector[2] = OPA->ivector[2];
-            break;
-
-        case INSTR_NOT_F:
-            OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int);
-            break;
-        case INSTR_NOT_V:
-            OPC->_float = !OPA->vector[0] &&
-                          !OPA->vector[1] &&
-                          !OPA->vector[2];
-            break;
-        case INSTR_NOT_S:
-            OPC->_float = !OPA->string ||
-                          !*prog_getstring(prog, OPA->string);
-            break;
-        case INSTR_NOT_ENT:
-            OPC->_float = (OPA->edict == 0);
-            break;
-        case INSTR_NOT_FNC:
-            OPC->_float = !OPA->function;
-            break;
-
-        case INSTR_IF:
-            /* this is consistent with darkplaces' behaviour */
-            if(FLOAT_IS_TRUE_FOR_INT(OPA->_int))
-            {
-                st += st->o2.s1 - 1;    /* offset the s++ */
-                if (++jumpcount >= maxjumps)
-                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
-            }
-            break;
-        case INSTR_IFNOT:
-            if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int))
-            {
-                st += st->o2.s1 - 1;    /* offset the s++ */
-                if (++jumpcount >= maxjumps)
-                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
-            }
-            break;
-
-        case INSTR_CALL0:
-        case INSTR_CALL1:
-        case INSTR_CALL2:
-        case INSTR_CALL3:
-        case INSTR_CALL4:
-        case INSTR_CALL5:
-        case INSTR_CALL6:
-        case INSTR_CALL7:
-        case INSTR_CALL8:
-            prog->argc = st->opcode - INSTR_CALL0;
-            if (!OPA->function)
-                qcvmerror(prog, "NULL function in `%s`", prog->filename);
-
-            if(!OPA->function || OPA->function >= (qcint_t)vec_size(prog->functions))
-            {
-                qcvmerror(prog, "CALL outside the program in `%s`", prog->filename);
-                goto cleanup;
-            }
-
-            newf = &prog->functions[OPA->function];
-            newf->profile++;
-
-            prog->statement = (st - prog->code) + 1;
-
-            if (newf->entry < 0)
-            {
-                /* negative statements are built in functions */
-                qcint_t builtinnumber = -newf->entry;
-                if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber])
-                    prog->builtins[builtinnumber](prog);
-                else
-                    qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources",
-                              builtinnumber, prog->filename);
-            }
-            else
-                st = prog->code + prog_enterfunction(prog, newf) - 1; /* offset st++ */
-            if (prog->vmerror)
-                goto cleanup;
-            break;
-
-        case INSTR_STATE:
-        {
-            qcfloat_t *nextthink;
-            qcfloat_t *time;
-            qcfloat_t *frame;
-            if (!prog->supports_state) {
-                qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename);
-                goto cleanup;
-            }
-            ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]);
-            ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function;
-
-            frame     = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame];
-            *frame    = OPA->_float;
-            nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink];
-            time      = (qcfloat_t*)(prog->globals + prog->cached_globals.time);
-            *nextthink = *time + 0.1;
-            break;
-        }
-
-        case INSTR_GOTO:
-            st += st->o1.s1 - 1;    /* offset the s++ */
-            if (++jumpcount == 10000000)
-                qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
-            break;
-
-        case INSTR_AND:
-            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) &&
-                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
-            break;
-        case INSTR_OR:
-            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) ||
-                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
-            break;
-
-        case INSTR_BITAND:
-            OPC->_float = ((int)OPA->_float) & ((int)OPB->_float);
-            break;
-        case INSTR_BITOR:
-            OPC->_float = ((int)OPA->_float) | ((int)OPB->_float);
-            break;
-    }
-}
-
-#undef QCVM_PROFILE
-#undef QCVM_TRACE
-#endif /* !QCVM_LOOP */
diff --git a/exec.cpp b/exec.cpp
new file mode 100644 (file)
index 0000000..8ec97e4
--- /dev/null
+++ b/exec.cpp
@@ -0,0 +1,1637 @@
+#ifndef QCVM_LOOP
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "gmqcc.h"
+
+static void loaderror(const char *fmt, ...)
+{
+    int     err = errno;
+    va_list ap;
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+    printf(": %s\n", util_strerror(err));
+}
+
+static void qcvmerror(qc_program_t *prog, const char *fmt, ...)
+{
+    va_list ap;
+
+    prog->vmerror++;
+
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+    putchar('\n');
+}
+
+qc_program_t* prog_load(const char *filename, bool skipversion)
+{
+    prog_header_t   header;
+    qc_program_t   *prog;
+    size_t          i;
+    FILE      *file  = fopen(filename, "rb");
+
+    /* we need all those in order to support INSTR_STATE: */
+    bool            has_self      = false,
+                    has_time      = false,
+                    has_think     = false,
+                    has_nextthink = false,
+                    has_frame     = false;
+
+    if (!file)
+        return NULL;
+
+    if (fread(&header, sizeof(header), 1, file) != 1) {
+        loaderror("failed to read header from '%s'", filename);
+        fclose(file);
+        return NULL;
+    }
+
+    util_swap_header(&header);
+
+    if (!skipversion && header.version != 6) {
+        loaderror("header says this is a version %i progs, we need version 6\n", header.version);
+        fclose(file);
+        return NULL;
+    }
+
+    prog = (qc_program_t*)mem_a(sizeof(qc_program_t));
+    if (!prog) {
+        fclose(file);
+        fprintf(stderr, "failed to allocate program data\n");
+        return NULL;
+    }
+    memset(prog, 0, sizeof(*prog));
+
+    prog->entityfields = header.entfield;
+    prog->crc16 = header.crc16;
+
+    prog->filename = util_strdup(filename);
+    if (!prog->filename) {
+        loaderror("failed to store program name");
+        goto error;
+    }
+
+#define read_data(hdrvar, progvar, reserved)                           \
+    if (fseek(file, header.hdrvar.offset, SEEK_SET) != 0) {            \
+        loaderror("seek failed");                                      \
+        goto error;                                                    \
+    }                                                                  \
+    if (fread(                                                         \
+            vec_add(prog->progvar, header.hdrvar.length + reserved),   \
+            sizeof(*prog->progvar),                                    \
+            header.hdrvar.length,                                      \
+            file                                                       \
+        )!= header.hdrvar.length                                       \
+    ) {                                                                \
+        loaderror("read failed");                                      \
+        goto error;                                                    \
+    }
+#define read_data1(x)    read_data(x, x, 0)
+#define read_data2(x, y) read_data(x, x, y)
+
+    read_data (statements, code, 0);
+    read_data1(defs);
+    read_data1(fields);
+    read_data1(functions);
+    read_data1(strings);
+    read_data2(globals, 2); /* reserve more in case a RETURN using with the global at "the end" exists */
+
+    util_swap_statements (prog->code);
+    util_swap_defs_fields(prog->defs);
+    util_swap_defs_fields(prog->fields);
+    util_swap_functions  (prog->functions);
+    util_swap_globals    (prog->globals);
+
+    fclose(file);
+
+    /* profile counters */
+    memset(vec_add(prog->profile, vec_size(prog->code)), 0, sizeof(prog->profile[0]) * vec_size(prog->code));
+
+    /* Add tempstring area */
+    prog->tempstring_start = vec_size(prog->strings);
+    prog->tempstring_at    = vec_size(prog->strings);
+    memset(vec_add(prog->strings, 16*1024), 0, 16*1024);
+
+    /* spawn the world entity */
+    vec_push(prog->entitypool, true);
+    memset(vec_add(prog->entitydata, prog->entityfields), 0, prog->entityfields * sizeof(prog->entitydata[0]));
+    prog->entities = 1;
+
+    /* cache some globals and fields from names */
+    for (i = 0; i < vec_size(prog->defs); ++i) {
+        const char *name = prog_getstring(prog, prog->defs[i].name);
+        if      (!strcmp(name, "self")) {
+            prog->cached_globals.self = prog->defs[i].offset;
+            has_self = true;
+        }
+        else if (!strcmp(name, "time")) {
+            prog->cached_globals.time = prog->defs[i].offset;
+            has_time = true;
+        }
+    }
+    for (i = 0; i < vec_size(prog->fields); ++i) {
+        const char *name = prog_getstring(prog, prog->fields[i].name);
+        if      (!strcmp(name, "think")) {
+            prog->cached_fields.think     = prog->fields[i].offset;
+            has_think = true;
+        }
+        else if (!strcmp(name, "nextthink")) {
+            prog->cached_fields.nextthink = prog->fields[i].offset;
+            has_nextthink = true;
+        }
+        else if (!strcmp(name, "frame")) {
+            prog->cached_fields.frame     = prog->fields[i].offset;
+            has_frame = true;
+        }
+    }
+    if (has_self && has_time && has_think && has_nextthink && has_frame)
+        prog->supports_state = true;
+
+    return prog;
+
+error:
+    if (prog->filename)
+        mem_d(prog->filename);
+    vec_free(prog->code);
+    vec_free(prog->defs);
+    vec_free(prog->fields);
+    vec_free(prog->functions);
+    vec_free(prog->strings);
+    vec_free(prog->globals);
+    vec_free(prog->entitydata);
+    vec_free(prog->entitypool);
+    mem_d(prog);
+
+    fclose(file);
+    return NULL;
+}
+
+void prog_delete(qc_program_t *prog)
+{
+    if (prog->filename) mem_d(prog->filename);
+    vec_free(prog->code);
+    vec_free(prog->defs);
+    vec_free(prog->fields);
+    vec_free(prog->functions);
+    vec_free(prog->strings);
+    vec_free(prog->globals);
+    vec_free(prog->entitydata);
+    vec_free(prog->entitypool);
+    vec_free(prog->localstack);
+    vec_free(prog->stack);
+    vec_free(prog->profile);
+    mem_d(prog);
+}
+
+/***********************************************************************
+ * VM code
+ */
+
+const char* prog_getstring(qc_program_t *prog, qcint_t str) {
+    /* cast for return required for C++ */
+    if (str < 0 || str >= (qcint_t)vec_size(prog->strings))
+        return  "<<<invalid string>>>";
+
+    return prog->strings + str;
+}
+
+prog_section_def_t* prog_entfield(qc_program_t *prog, qcint_t off) {
+    size_t i;
+    for (i = 0; i < vec_size(prog->fields); ++i) {
+        if (prog->fields[i].offset == off)
+            return (prog->fields + i);
+    }
+    return NULL;
+}
+
+prog_section_def_t* prog_getdef(qc_program_t *prog, qcint_t off)
+{
+    size_t i;
+    for (i = 0; i < vec_size(prog->defs); ++i) {
+        if (prog->defs[i].offset == off)
+            return (prog->defs + i);
+    }
+    return NULL;
+}
+
+qcany_t* prog_getedict(qc_program_t *prog, qcint_t e) {
+    if (e >= (qcint_t)vec_size(prog->entitypool)) {
+        prog->vmerror++;
+        fprintf(stderr, "Accessing out of bounds edict %i\n", (int)e);
+        e = 0;
+    }
+    return (qcany_t*)(prog->entitydata + (prog->entityfields * e));
+}
+
+static qcint_t prog_spawn_entity(qc_program_t *prog) {
+    char  *data;
+    qcint_t  e;
+    for (e = 0; e < (qcint_t)vec_size(prog->entitypool); ++e) {
+        if (!prog->entitypool[e]) {
+            data = (char*)(prog->entitydata + (prog->entityfields * e));
+            memset(data, 0, prog->entityfields * sizeof(qcint_t));
+            return e;
+        }
+    }
+    vec_push(prog->entitypool, true);
+    prog->entities++;
+    data = (char*)vec_add(prog->entitydata, prog->entityfields);
+    memset(data, 0, prog->entityfields * sizeof(qcint_t));
+    return e;
+}
+
+static void prog_free_entity(qc_program_t *prog, qcint_t e) {
+    if (!e) {
+        prog->vmerror++;
+        fprintf(stderr, "Trying to free world entity\n");
+        return;
+    }
+    if (e >= (qcint_t)vec_size(prog->entitypool)) {
+        prog->vmerror++;
+        fprintf(stderr, "Trying to free out of bounds entity\n");
+        return;
+    }
+    if (!prog->entitypool[e]) {
+        prog->vmerror++;
+        fprintf(stderr, "Double free on entity\n");
+        return;
+    }
+    prog->entitypool[e] = false;
+}
+
+qcint_t prog_tempstring(qc_program_t *prog, const char *str) {
+    size_t len = strlen(str);
+    size_t at = prog->tempstring_at;
+
+    /* when we reach the end we start over */
+    if (at + len >= vec_size(prog->strings))
+        at = prog->tempstring_start;
+
+    /* when it doesn't fit, reallocate */
+    if (at + len >= vec_size(prog->strings))
+    {
+        (void)vec_add(prog->strings, len+1);
+        memcpy(prog->strings + at, str, len+1);
+        return at;
+    }
+
+    /* when it fits, just copy */
+    memcpy(prog->strings + at, str, len+1);
+    prog->tempstring_at += len+1;
+    return at;
+}
+
+static size_t print_escaped_string(const char *str, size_t maxlen) {
+    size_t len = 2;
+    putchar('"');
+    --maxlen; /* because we're lazy and have escape sequences */
+    while (*str) {
+        if (len >= maxlen) {
+            putchar('.');
+            putchar('.');
+            putchar('.');
+            len += 3;
+            break;
+        }
+        switch (*str) {
+            case '\a': len += 2; putchar('\\'); putchar('a'); break;
+            case '\b': len += 2; putchar('\\'); putchar('b'); break;
+            case '\r': len += 2; putchar('\\'); putchar('r'); break;
+            case '\n': len += 2; putchar('\\'); putchar('n'); break;
+            case '\t': len += 2; putchar('\\'); putchar('t'); break;
+            case '\f': len += 2; putchar('\\'); putchar('f'); break;
+            case '\v': len += 2; putchar('\\'); putchar('v'); break;
+            case '\\': len += 2; putchar('\\'); putchar('\\'); break;
+            case '"':  len += 2; putchar('\\'); putchar('"'); break;
+            default:
+                ++len;
+                putchar(*str);
+                break;
+        }
+        ++str;
+    }
+    putchar('"');
+    return len;
+}
+
+static void trace_print_global(qc_program_t *prog, unsigned int glob, int vtype) {
+    static char spaces[28+1] = "                            ";
+    prog_section_def_t *def;
+    qcany_t    *value;
+    int       len;
+
+    if (!glob) {
+        if ((len = printf("<null>,")) == -1)
+            len = 0;
+
+        goto done;
+    }
+
+    def = prog_getdef(prog, glob);
+    value = (qcany_t*)(&prog->globals[glob]);
+
+    len = printf("[@%u] ", glob);
+    if (def) {
+        const char *name = prog_getstring(prog, def->name);
+        if (name[0] == '#')
+            len += printf("$");
+        else
+            len += printf("%s ", name);
+        vtype = def->type & DEF_TYPEMASK;
+    }
+
+    switch (vtype) {
+        case TYPE_VOID:
+        case TYPE_ENTITY:
+        case TYPE_FIELD:
+        case TYPE_FUNCTION:
+        case TYPE_POINTER:
+            len += printf("(%i),", value->_int);
+            break;
+        case TYPE_VECTOR:
+            len += printf("'%g %g %g',", value->vector[0],
+                                         value->vector[1],
+                                         value->vector[2]);
+            break;
+        case TYPE_STRING:
+            if (value->string)
+                len += print_escaped_string(prog_getstring(prog, value->string), sizeof(spaces)-len-5);
+            else
+                len += printf("(null)");
+            len += printf(",");
+            /* len += printf("\"%s\",", prog_getstring(prog, value->string)); */
+            break;
+        case TYPE_FLOAT:
+        default:
+            len += printf("%g,", value->_float);
+            break;
+    }
+done:
+    if (len < (int)sizeof(spaces)-1) {
+        spaces[sizeof(spaces)-1-len] = 0;
+        fputs(spaces, stdout);
+        spaces[sizeof(spaces)-1-len] = ' ';
+    }
+}
+
+static void prog_print_statement(qc_program_t *prog, prog_section_statement_t *st) {
+    if (st->opcode >= VINSTR_END) {
+        printf("<illegal instruction %d>\n", st->opcode);
+        return;
+    }
+    if ((prog->xflags & VMXF_TRACE) && vec_size(prog->function_stack)) {
+        size_t i;
+        for (i = 0; i < vec_size(prog->function_stack); ++i)
+            printf("->");
+        printf("%s:", vec_last(prog->function_stack));
+    }
+    printf(" <> %-12s", util_instr_str[st->opcode]);
+    if (st->opcode >= INSTR_IF &&
+        st->opcode <= INSTR_IFNOT)
+    {
+        trace_print_global(prog, st->o1.u1, TYPE_FLOAT);
+        printf("%d\n", st->o2.s1);
+    }
+    else if (st->opcode >= INSTR_CALL0 &&
+             st->opcode <= INSTR_CALL8)
+    {
+        trace_print_global(prog, st->o1.u1, TYPE_FUNCTION);
+        printf("\n");
+    }
+    else if (st->opcode == INSTR_GOTO)
+    {
+        printf("%i\n", st->o1.s1);
+    }
+    else
+    {
+        int t[3] = { TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT };
+        switch (st->opcode)
+        {
+            case INSTR_MUL_FV:
+                t[1] = t[2] = TYPE_VECTOR;
+                break;
+            case INSTR_MUL_VF:
+                t[0] = t[2] = TYPE_VECTOR;
+                break;
+            case INSTR_MUL_V:
+                t[0] = t[1] = TYPE_VECTOR;
+                break;
+            case INSTR_ADD_V:
+            case INSTR_SUB_V:
+            case INSTR_EQ_V:
+            case INSTR_NE_V:
+                t[0] = t[1] = t[2] = TYPE_VECTOR;
+                break;
+            case INSTR_EQ_S:
+            case INSTR_NE_S:
+                t[0] = t[1] = TYPE_STRING;
+                break;
+            case INSTR_STORE_F:
+            case INSTR_STOREP_F:
+                t[2] = -1;
+                break;
+            case INSTR_STORE_V:
+                t[0] = t[1] = TYPE_VECTOR; t[2] = -1;
+                break;
+            case INSTR_STORE_S:
+                t[0] = t[1] = TYPE_STRING; t[2] = -1;
+                break;
+            case INSTR_STORE_ENT:
+                t[0] = t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STORE_FLD:
+                t[0] = t[1] = TYPE_FIELD; t[2] = -1;
+                break;
+            case INSTR_STORE_FNC:
+                t[0] = t[1] = TYPE_FUNCTION; t[2] = -1;
+                break;
+            case INSTR_STOREP_V:
+                t[0] = TYPE_VECTOR; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_S:
+                t[0] = TYPE_STRING; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_ENT:
+                t[0] = TYPE_ENTITY; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_FLD:
+                t[0] = TYPE_FIELD; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+            case INSTR_STOREP_FNC:
+                t[0] = TYPE_FUNCTION; t[1] = TYPE_ENTITY; t[2] = -1;
+                break;
+        }
+        if (t[0] >= 0) trace_print_global(prog, st->o1.u1, t[0]);
+        else           printf("(none),          ");
+        if (t[1] >= 0) trace_print_global(prog, st->o2.u1, t[1]);
+        else           printf("(none),          ");
+        if (t[2] >= 0) trace_print_global(prog, st->o3.u1, t[2]);
+        else           printf("(none)");
+        printf("\n");
+    }
+}
+
+static qcint_t prog_enterfunction(qc_program_t *prog, prog_section_function_t *func) {
+    qc_exec_stack_t st;
+    size_t  parampos;
+    int32_t p;
+
+    /* back up locals */
+    st.localsp  = vec_size(prog->localstack);
+    st.stmt     = prog->statement;
+    st.function = func;
+
+    if (prog->xflags & VMXF_TRACE) {
+        const char *str = prog_getstring(prog, func->name);
+        vec_push(prog->function_stack, str);
+    }
+
+#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
+    if (vec_size(prog->stack))
+    {
+        prog_section_function_t *cur;
+        cur = prog->stack[vec_size(prog->stack)-1].function;
+        if (cur)
+        {
+            qcint_t *globals = prog->globals + cur->firstlocal;
+            vec_append(prog->localstack, cur->locals, globals);
+        }
+    }
+#else
+    {
+        qcint_t *globals = prog->globals + func->firstlocal;
+        vec_append(prog->localstack, func->locals, globals);
+    }
+#endif
+
+    /* copy parameters */
+    parampos = func->firstlocal;
+    for (p = 0; p < func->nargs; ++p)
+    {
+        size_t s;
+        for (s = 0; s < func->argsize[p]; ++s) {
+            prog->globals[parampos] = prog->globals[OFS_PARM0 + 3*p + s];
+            ++parampos;
+        }
+    }
+
+    vec_push(prog->stack, st);
+
+    return func->entry;
+}
+
+static qcint_t prog_leavefunction(qc_program_t *prog) {
+    prog_section_function_t *prev = NULL;
+    size_t oldsp;
+
+    qc_exec_stack_t st = vec_last(prog->stack);
+
+    if (prog->xflags & VMXF_TRACE) {
+        if (vec_size(prog->function_stack))
+            vec_pop(prog->function_stack);
+    }
+
+#ifdef QCVM_BACKUP_STRATEGY_CALLER_VARS
+    if (vec_size(prog->stack) > 1) {
+        prev  = prog->stack[vec_size(prog->stack)-2].function;
+        oldsp = prog->stack[vec_size(prog->stack)-2].localsp;
+    }
+#else
+    prev  = prog->stack[vec_size(prog->stack)-1].function;
+    oldsp = prog->stack[vec_size(prog->stack)-1].localsp;
+#endif
+    if (prev) {
+        qcint_t *globals = prog->globals + prev->firstlocal;
+        memcpy(globals, prog->localstack + oldsp, prev->locals * sizeof(prog->localstack[0]));
+        /* vec_remove(prog->localstack, oldsp, vec_size(prog->localstack)-oldsp); */
+        vec_shrinkto(prog->localstack, oldsp);
+    }
+
+    vec_pop(prog->stack);
+
+    return st.stmt - 1; /* offset the ++st */
+}
+
+bool prog_exec(qc_program_t *prog, prog_section_function_t *func, size_t flags, long maxjumps) {
+    long jumpcount = 0;
+    size_t oldxflags = prog->xflags;
+    prog_section_statement_t *st;
+
+    prog->vmerror = 0;
+    prog->xflags = flags;
+
+    st = prog->code + prog_enterfunction(prog, func);
+    --st;
+    switch (flags)
+    {
+        default:
+        case 0:
+        {
+#define QCVM_LOOP    1
+#define QCVM_PROFILE 0
+#define QCVM_TRACE   0
+#           include __FILE__
+        }
+        case (VMXF_TRACE):
+        {
+#define QCVM_PROFILE 0
+#define QCVM_TRACE   1
+#           include __FILE__
+        }
+        case (VMXF_PROFILE):
+        {
+#define QCVM_PROFILE 1
+#define QCVM_TRACE   0
+#           include __FILE__
+        }
+        case (VMXF_TRACE|VMXF_PROFILE):
+        {
+#define QCVM_PROFILE 1
+#define QCVM_TRACE   1
+#           include __FILE__
+        }
+    };
+
+cleanup:
+    prog->xflags = oldxflags;
+    vec_free(prog->localstack);
+    vec_free(prog->stack);
+    if (prog->vmerror)
+        return false;
+    return true;
+}
+
+/***********************************************************************
+ * main for when building the standalone executor
+ */
+
+#include <math.h>
+
+const char *type_name[TYPE_COUNT] = {
+    "void",
+    "string",
+    "float",
+    "vector",
+    "entity",
+    "field",
+    "function",
+    "pointer",
+    "integer",
+
+    "variant",
+
+    "struct",
+    "union",
+    "array",
+
+    "nil",
+    "noexpr"
+};
+
+typedef struct {
+    int         vtype;
+    const char *value;
+} qcvm_parameter;
+
+static qcvm_parameter *main_params = NULL;
+
+#define CheckArgs(num) do {                                                    \
+    if (prog->argc != (num)) {                                                 \
+        prog->vmerror++;                                                       \
+        fprintf(stderr, "ERROR: invalid number of arguments for %s: %i, expected %i\n", \
+        __FUNCTION__, prog->argc, (num));                                      \
+        return -1;                                                             \
+    }                                                                          \
+} while (0)
+
+#define GetGlobal(idx) ((qcany_t*)(prog->globals + (idx)))
+#define GetArg(num) GetGlobal(OFS_PARM0 + 3*(num))
+#define Return(any) *(GetGlobal(OFS_RETURN)) = (any)
+
+static int qc_print(qc_program_t *prog) {
+    size_t i;
+    const char *laststr = NULL;
+    for (i = 0; i < (size_t)prog->argc; ++i) {
+        qcany_t *str = (qcany_t*)(prog->globals + OFS_PARM0 + 3*i);
+        laststr = prog_getstring(prog, str->string);
+        printf("%s", laststr);
+    }
+    if (laststr && (prog->xflags & VMXF_TRACE)) {
+        size_t len = strlen(laststr);
+        if (!len || laststr[len-1] != '\n')
+            printf("\n");
+    }
+    return 0;
+}
+
+static int qc_error(qc_program_t *prog) {
+    fprintf(stderr, "*** VM raised an error:\n");
+    qc_print(prog);
+    prog->vmerror++;
+    return -1;
+}
+
+static int qc_ftos(qc_program_t *prog) {
+    char buffer[512];
+    qcany_t *num;
+    qcany_t str;
+    CheckArgs(1);
+    num = GetArg(0);
+    util_snprintf(buffer, sizeof(buffer), "%g", num->_float);
+    str.string = prog_tempstring(prog, buffer);
+    Return(str);
+    return 0;
+}
+
+static int qc_stof(qc_program_t *prog) {
+    qcany_t *str;
+    qcany_t num;
+    CheckArgs(1);
+    str = GetArg(0);
+    num._float = (float)strtod(prog_getstring(prog, str->string), NULL);
+    Return(num);
+    return 0;
+}
+
+static int qc_vtos(qc_program_t *prog) {
+    char buffer[512];
+    qcany_t *num;
+    qcany_t str;
+    CheckArgs(1);
+    num = GetArg(0);
+    util_snprintf(buffer, sizeof(buffer), "'%g %g %g'", num->vector[0], num->vector[1], num->vector[2]);
+    str.string = prog_tempstring(prog, buffer);
+    Return(str);
+    return 0;
+}
+
+static int qc_etos(qc_program_t *prog) {
+    char buffer[512];
+    qcany_t *num;
+    qcany_t str;
+    CheckArgs(1);
+    num = GetArg(0);
+    util_snprintf(buffer, sizeof(buffer), "%i", num->_int);
+    str.string = prog_tempstring(prog, buffer);
+    Return(str);
+    return 0;
+}
+
+static int qc_spawn(qc_program_t *prog) {
+    qcany_t ent;
+    CheckArgs(0);
+    ent.edict = prog_spawn_entity(prog);
+    Return(ent);
+    return (ent.edict ? 0 : -1);
+}
+
+static int qc_kill(qc_program_t *prog) {
+    qcany_t *ent;
+    CheckArgs(1);
+    ent = GetArg(0);
+    prog_free_entity(prog, ent->edict);
+    return 0;
+}
+
+static int qc_sqrt(qc_program_t *prog) {
+    qcany_t *num, out;
+    CheckArgs(1);
+    num = GetArg(0);
+    out._float = sqrt(num->_float);
+    Return(out);
+    return 0;
+}
+
+static int qc_vlen(qc_program_t *prog) {
+    qcany_t *vec, len;
+    CheckArgs(1);
+    vec = GetArg(0);
+    len._float = sqrt(vec->vector[0] * vec->vector[0] +
+                      vec->vector[1] * vec->vector[1] +
+                      vec->vector[2] * vec->vector[2]);
+    Return(len);
+    return 0;
+}
+
+static int qc_normalize(qc_program_t *prog) {
+    double len;
+    qcany_t *vec;
+    qcany_t out;
+    CheckArgs(1);
+    vec = GetArg(0);
+    len = sqrt(vec->vector[0] * vec->vector[0] +
+               vec->vector[1] * vec->vector[1] +
+               vec->vector[2] * vec->vector[2]);
+    if (len)
+        len = 1.0 / len;
+    else
+        len = 0;
+    out.vector[0] = len * vec->vector[0];
+    out.vector[1] = len * vec->vector[1];
+    out.vector[2] = len * vec->vector[2];
+    Return(out);
+    return 0;
+}
+
+static int qc_strcat(qc_program_t *prog) {
+    char  *buffer;
+    size_t len1,   len2;
+    qcany_t *str1,  *str2;
+    qcany_t  out;
+
+    const char *cstr1;
+    const char *cstr2;
+
+    CheckArgs(2);
+    str1 = GetArg(0);
+    str2 = GetArg(1);
+    cstr1 = prog_getstring(prog, str1->string);
+    cstr2 = prog_getstring(prog, str2->string);
+    len1 = strlen(cstr1);
+    len2 = strlen(cstr2);
+    buffer = (char*)mem_a(len1 + len2 + 1);
+    memcpy(buffer, cstr1, len1);
+    memcpy(buffer+len1, cstr2, len2+1);
+    out.string = prog_tempstring(prog, buffer);
+    mem_d(buffer);
+    Return(out);
+    return 0;
+}
+
+static int qc_strcmp(qc_program_t *prog) {
+    qcany_t *str1,  *str2;
+    qcany_t out;
+
+    const char *cstr1;
+    const char *cstr2;
+
+    if (prog->argc != 2 && prog->argc != 3) {
+        fprintf(stderr, "ERROR: invalid number of arguments for strcmp/strncmp: %i, expected 2 or 3\n",
+               prog->argc);
+        return -1;
+    }
+
+    str1 = GetArg(0);
+    str2 = GetArg(1);
+    cstr1 = prog_getstring(prog, str1->string);
+    cstr2 = prog_getstring(prog, str2->string);
+    if (prog->argc == 3)
+        out._float = strncmp(cstr1, cstr2, GetArg(2)->_float);
+    else
+        out._float = strcmp(cstr1, cstr2);
+    Return(out);
+    return 0;
+}
+
+static int qc_floor(qc_program_t *prog) {
+    qcany_t *num, out;
+    CheckArgs(1);
+    num = GetArg(0);
+    out._float = floor(num->_float);
+    Return(out);
+    return 0;
+}
+
+static int qc_pow(qc_program_t *prog) {
+    qcany_t *base, *exp, out;
+    CheckArgs(2);
+    base = GetArg(0);
+    exp = GetArg(1);
+    out._float = powf(base->_float, exp->_float);
+    Return(out);
+    return 0;
+}
+
+static prog_builtin_t qc_builtins[] = {
+    NULL,
+    &qc_print,       /*   1   */
+    &qc_ftos,        /*   2   */
+    &qc_spawn,       /*   3   */
+    &qc_kill,        /*   4   */
+    &qc_vtos,        /*   5   */
+    &qc_error,       /*   6   */
+    &qc_vlen,        /*   7   */
+    &qc_etos,        /*   8   */
+    &qc_stof,        /*   9   */
+    &qc_strcat,      /*   10  */
+    &qc_strcmp,      /*   11  */
+    &qc_normalize,   /*   12  */
+    &qc_sqrt,        /*   13  */
+    &qc_floor,       /*   14  */
+    &qc_pow          /*   15  */
+};
+
+static const char *arg0 = NULL;
+
+static void version(void) {
+    printf("GMQCC-QCVM %d.%d.%d Built %s %s\n",
+           GMQCC_VERSION_MAJOR,
+           GMQCC_VERSION_MINOR,
+           GMQCC_VERSION_PATCH,
+           __DATE__,
+           __TIME__
+    );
+}
+
+static void usage(void) {
+    printf("usage: %s [options] [parameters] file\n", arg0);
+    printf("options:\n");
+    printf("  -h, --help         print this message\n"
+           "  -trace             trace the execution\n"
+           "  -profile           perform profiling during execution\n"
+           "  -info              print information from the prog's header\n"
+           "  -disasm            disassemble and exit\n"
+           "  -disasm-func func  disassemble and exit\n"
+           "  -printdefs         list the defs section\n"
+           "  -printfields       list the field section\n"
+           "  -printfuns         list functions information\n"
+           "  -v                 be verbose\n"
+           "  -vv                be even more verbose\n");
+    printf("parameters:\n");
+    printf("  -vector <V>   pass a vector parameter to main()\n"
+           "  -float  <f>   pass a float parameter to main()\n"
+           "  -string <s>   pass a string parameter to main() \n");
+}
+
+static void prog_main_setparams(qc_program_t *prog) {
+    size_t i;
+    qcany_t *arg;
+
+    for (i = 0; i < vec_size(main_params); ++i) {
+        arg = GetGlobal(OFS_PARM0 + 3*i);
+        arg->vector[0] = 0;
+        arg->vector[1] = 0;
+        arg->vector[2] = 0;
+        switch (main_params[i].vtype) {
+            case TYPE_VECTOR:
+                (void)util_sscanf(main_params[i].value, " %f %f %f ",
+                                       &arg->vector[0],
+                                       &arg->vector[1],
+                                       &arg->vector[2]);
+                break;
+            case TYPE_FLOAT:
+                arg->_float = atof(main_params[i].value);
+                break;
+            case TYPE_STRING:
+                arg->string = prog_tempstring(prog, main_params[i].value);
+                break;
+            default:
+                fprintf(stderr, "error: unhandled parameter type: %i\n", main_params[i].vtype);
+                break;
+        }
+    }
+}
+
+static void prog_disasm_function(qc_program_t *prog, size_t id);
+
+int main(int argc, char **argv) {
+    size_t      i;
+    qcint_t       fnmain = -1;
+    qc_program_t *prog;
+    size_t      xflags = VMXF_DEFAULT;
+    bool        opts_printfields = false;
+    bool        opts_printdefs   = false;
+    bool        opts_printfuns   = false;
+    bool        opts_disasm      = false;
+    bool        opts_info        = false;
+    bool        noexec           = false;
+    const char *progsfile        = NULL;
+    const char **dis_list        = NULL;
+    int         opts_v           = 0;
+
+    arg0 = argv[0];
+
+    if (argc < 2) {
+        usage();
+        exit(EXIT_FAILURE);
+    }
+
+    while (argc > 1) {
+        if (!strcmp(argv[1], "-h") ||
+            !strcmp(argv[1], "-help") ||
+            !strcmp(argv[1], "--help"))
+        {
+            usage();
+            exit(EXIT_SUCCESS);
+        }
+        else if (!strcmp(argv[1], "-v")) {
+            ++opts_v;
+            --argc;
+            ++argv;
+        }
+        else if (!strncmp(argv[1], "-vv", 3)) {
+            const char *av = argv[1]+1;
+            for (; *av; ++av) {
+                if (*av == 'v')
+                    ++opts_v;
+                else {
+                    usage();
+                    exit(EXIT_FAILURE);
+                }
+            }
+            --argc;
+            ++argv;
+        }
+        else if (!strcmp(argv[1], "-version") ||
+                 !strcmp(argv[1], "--version"))
+        {
+            version();
+            exit(EXIT_SUCCESS);
+        }
+        else if (!strcmp(argv[1], "-trace")) {
+            --argc;
+            ++argv;
+            xflags |= VMXF_TRACE;
+        }
+        else if (!strcmp(argv[1], "-profile")) {
+            --argc;
+            ++argv;
+            xflags |= VMXF_PROFILE;
+        }
+        else if (!strcmp(argv[1], "-info")) {
+            --argc;
+            ++argv;
+            opts_info = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-disasm")) {
+            --argc;
+            ++argv;
+            opts_disasm = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-disasm-func")) {
+            --argc;
+            ++argv;
+            if (argc <= 1) {
+                usage();
+                exit(EXIT_FAILURE);
+            }
+            vec_push(dis_list, argv[1]);
+            --argc;
+            ++argv;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-printdefs")) {
+            --argc;
+            ++argv;
+            opts_printdefs = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-printfuns")) {
+            --argc;
+            ++argv;
+            opts_printfuns = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-printfields")) {
+            --argc;
+            ++argv;
+            opts_printfields = true;
+            noexec = true;
+        }
+        else if (!strcmp(argv[1], "-vector") ||
+                 !strcmp(argv[1], "-string") ||
+                 !strcmp(argv[1], "-float") )
+        {
+            qcvm_parameter p;
+            if (argv[1][1] == 'f')
+                p.vtype = TYPE_FLOAT;
+            else if (argv[1][1] == 's')
+                p.vtype = TYPE_STRING;
+            else if (argv[1][1] == 'v')
+                p.vtype = TYPE_VECTOR;
+            else
+                p.vtype = TYPE_VOID;
+
+            --argc;
+            ++argv;
+            if (argc < 2) {
+                usage();
+                exit(EXIT_FAILURE);
+            }
+            p.value = argv[1];
+
+            vec_push(main_params, p);
+            --argc;
+            ++argv;
+        }
+        else if (!strcmp(argv[1], "--")) {
+            --argc;
+            ++argv;
+            break;
+        }
+        else if (argv[1][0] != '-') {
+            if (progsfile) {
+                fprintf(stderr, "only 1 program file may be specified\n");
+                usage();
+                exit(EXIT_FAILURE);
+            }
+            progsfile = argv[1];
+            --argc;
+            ++argv;
+        }
+        else
+        {
+            fprintf(stderr, "unknown parameter: %s\n", argv[1]);
+            usage();
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    if (argc == 2 && !progsfile) {
+        progsfile = argv[1];
+        --argc;
+        ++argv;
+    }
+
+    if (!progsfile) {
+        fprintf(stderr, "must specify a program to execute\n");
+        usage();
+        exit(EXIT_FAILURE);
+    }
+
+    prog = prog_load(progsfile, noexec);
+    if (!prog) {
+        fprintf(stderr, "failed to load program '%s'\n", progsfile);
+        exit(EXIT_FAILURE);
+    }
+
+    prog->builtins       = qc_builtins;
+    prog->builtins_count = GMQCC_ARRAY_COUNT(qc_builtins);
+
+    if (opts_info) {
+        printf("Program's system-checksum = 0x%04x\n", (unsigned int)prog->crc16);
+        printf("Entity field space: %u\n", (unsigned int)prog->entityfields);
+        printf("Globals: %u\n", (unsigned int)vec_size(prog->globals));
+        printf("Counts:\n"
+               "      code: %lu\n"
+               "      defs: %lu\n"
+               "    fields: %lu\n"
+               " functions: %lu\n"
+               "   strings: %lu\n",
+               (unsigned long)vec_size(prog->code),
+               (unsigned long)vec_size(prog->defs),
+               (unsigned long)vec_size(prog->fields),
+               (unsigned long)vec_size(prog->functions),
+               (unsigned long)vec_size(prog->strings));
+    }
+
+    if (opts_info) {
+        prog_delete(prog);
+        return 0;
+    }
+    for (i = 0; i < vec_size(dis_list); ++i) {
+        size_t k;
+        printf("Looking for `%s`\n", dis_list[i]);
+        for (k = 1; k < vec_size(prog->functions); ++k) {
+            const char *name = prog_getstring(prog, prog->functions[k].name);
+            if (!strcmp(name, dis_list[i])) {
+                prog_disasm_function(prog, k);
+                break;
+            }
+        }
+    }
+    if (opts_disasm) {
+        for (i = 1; i < vec_size(prog->functions); ++i)
+            prog_disasm_function(prog, i);
+        return 0;
+    }
+    if (opts_printdefs) {
+        const char *getstring = NULL;
+        for (i = 0; i < vec_size(prog->defs); ++i) {
+            printf("Global: %8s %-16s at %u%s",
+                   type_name[prog->defs[i].type & DEF_TYPEMASK],
+                   prog_getstring(prog, prog->defs[i].name),
+                   (unsigned int)prog->defs[i].offset,
+                   ((prog->defs[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
+            if (opts_v) {
+                switch (prog->defs[i].type & DEF_TYPEMASK) {
+                    case TYPE_FLOAT:
+                        printf(" [init: %g]", ((qcany_t*)(prog->globals + prog->defs[i].offset))->_float);
+                        break;
+                    case TYPE_INTEGER:
+                        printf(" [init: %i]", (int)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int ));
+                        break;
+                    case TYPE_ENTITY:
+                    case TYPE_FUNCTION:
+                    case TYPE_FIELD:
+                    case TYPE_POINTER:
+                        printf(" [init: %u]", (unsigned)( ((qcany_t*)(prog->globals + prog->defs[i].offset))->_int ));
+                        break;
+                    case TYPE_STRING:
+                        getstring = prog_getstring(prog, ((qcany_t*)(prog->globals + prog->defs[i].offset))->string);
+                        printf(" [init: `");
+                        print_escaped_string(getstring, strlen(getstring));
+                        printf("`]\n");
+                        break;
+                    default:
+                        break;
+                }
+            }
+            printf("\n");
+        }
+    }
+    if (opts_printfields) {
+        for (i = 0; i < vec_size(prog->fields); ++i) {
+            printf("Field: %8s %-16s at %u%s\n",
+                   type_name[prog->fields[i].type],
+                   prog_getstring(prog, prog->fields[i].name),
+                   (unsigned int)prog->fields[i].offset,
+                   ((prog->fields[i].type & DEF_SAVEGLOBAL) ? " [SAVE]" : ""));
+        }
+    }
+    if (opts_printfuns) {
+        for (i = 0; i < vec_size(prog->functions); ++i) {
+            int32_t a;
+            printf("Function: %-16s taking %u parameters:(",
+                   prog_getstring(prog, prog->functions[i].name),
+                   (unsigned int)prog->functions[i].nargs);
+            for (a = 0; a < prog->functions[i].nargs; ++a) {
+                printf(" %i", prog->functions[i].argsize[a]);
+            }
+            if (opts_v > 1) {
+                int32_t start = prog->functions[i].entry;
+                if (start < 0)
+                    printf(") builtin %i\n", (int)-start);
+                else {
+                    size_t funsize = 0;
+                    prog_section_statement_t *st = prog->code + start;
+                    for (;st->opcode != INSTR_DONE; ++st)
+                        ++funsize;
+                    printf(") - %lu instructions", (unsigned long)funsize);
+                    if (opts_v > 2) {
+                        printf(" - locals: %i + %i\n",
+                               prog->functions[i].firstlocal,
+                               prog->functions[i].locals);
+                    }
+                    else
+                        printf("\n");
+                }
+            }
+            else if (opts_v) {
+                printf(") locals: %i + %i\n",
+                       prog->functions[i].firstlocal,
+                       prog->functions[i].locals);
+            }
+            else
+                printf(")\n");
+        }
+    }
+    if (!noexec) {
+        for (i = 1; i < vec_size(prog->functions); ++i) {
+            const char *name = prog_getstring(prog, prog->functions[i].name);
+            if (!strcmp(name, "main"))
+                fnmain = (qcint_t)i;
+        }
+        if (fnmain > 0)
+        {
+            prog_main_setparams(prog);
+            prog_exec(prog, &prog->functions[fnmain], xflags, VM_JUMPS_DEFAULT);
+        }
+        else
+            fprintf(stderr, "No main function found\n");
+    }
+
+    prog_delete(prog);
+    return 0;
+}
+
+static void prog_disasm_function(qc_program_t *prog, size_t id) {
+    prog_section_function_t *fdef = prog->functions + id;
+    prog_section_statement_t *st;
+
+    if (fdef->entry < 0) {
+        printf("FUNCTION \"%s\" = builtin #%i\n", prog_getstring(prog, fdef->name), (int)-fdef->entry);
+        return;
+    }
+    else
+        printf("FUNCTION \"%s\"\n", prog_getstring(prog, fdef->name));
+
+    st = prog->code + fdef->entry;
+    while (st->opcode != INSTR_DONE) {
+        prog_print_statement(prog, st);
+        ++st;
+    }
+}
+#else /* !QCVM_LOOP */
+/*
+ * Everything from here on is not including into the compilation of the
+ * executor.  This is simply code that is #included via #include __FILE__
+ * see when QCVM_LOOP is defined, the rest of the code above do not get
+ * re-included.  So this really just acts like one large macro, but it
+ * sort of isn't, which makes it nicer looking.
+ */
+
+#define OPA ( (qcany_t*) (prog->globals + st->o1.u1) )
+#define OPB ( (qcany_t*) (prog->globals + st->o2.u1) )
+#define OPC ( (qcany_t*) (prog->globals + st->o3.u1) )
+
+#define GLOBAL(x) ( (qcany_t*) (prog->globals + (x)) )
+
+/* to be consistent with current darkplaces behaviour */
+#if !defined(FLOAT_IS_TRUE_FOR_INT)
+#   define FLOAT_IS_TRUE_FOR_INT(x) ( (x) & 0x7FFFFFFF )
+#endif
+
+while (prog->vmerror == 0) {
+    prog_section_function_t  *newf;
+    qcany_t          *ed;
+    qcany_t          *ptr;
+
+    ++st;
+
+#if QCVM_PROFILE
+    prog->profile[st - prog->code]++;
+#endif
+
+#if QCVM_TRACE
+    prog_print_statement(prog, st);
+#endif
+
+    switch (st->opcode)
+    {
+        default:
+            qcvmerror(prog, "Illegal instruction in %s\n", prog->filename);
+            goto cleanup;
+
+        case INSTR_DONE:
+        case INSTR_RETURN:
+            /* TODO: add instruction count to function profile count */
+            GLOBAL(OFS_RETURN)->ivector[0] = OPA->ivector[0];
+            GLOBAL(OFS_RETURN)->ivector[1] = OPA->ivector[1];
+            GLOBAL(OFS_RETURN)->ivector[2] = OPA->ivector[2];
+
+            st = prog->code + prog_leavefunction(prog);
+            if (!vec_size(prog->stack))
+                goto cleanup;
+
+            break;
+
+        case INSTR_MUL_F:
+            OPC->_float = OPA->_float * OPB->_float;
+            break;
+        case INSTR_MUL_V:
+            OPC->_float = OPA->vector[0]*OPB->vector[0] +
+                          OPA->vector[1]*OPB->vector[1] +
+                          OPA->vector[2]*OPB->vector[2];
+            break;
+        case INSTR_MUL_FV:
+        {
+            qcfloat_t f = OPA->_float;
+            OPC->vector[0] = f * OPB->vector[0];
+            OPC->vector[1] = f * OPB->vector[1];
+            OPC->vector[2] = f * OPB->vector[2];
+            break;
+        }
+        case INSTR_MUL_VF:
+        {
+            qcfloat_t f = OPB->_float;
+            OPC->vector[0] = f * OPA->vector[0];
+            OPC->vector[1] = f * OPA->vector[1];
+            OPC->vector[2] = f * OPA->vector[2];
+            break;
+        }
+        case INSTR_DIV_F:
+            if (OPB->_float != 0.0f)
+                OPC->_float = OPA->_float / OPB->_float;
+            else
+                OPC->_float = 0;
+            break;
+
+        case INSTR_ADD_F:
+            OPC->_float = OPA->_float + OPB->_float;
+            break;
+        case INSTR_ADD_V:
+            OPC->vector[0] = OPA->vector[0] + OPB->vector[0];
+            OPC->vector[1] = OPA->vector[1] + OPB->vector[1];
+            OPC->vector[2] = OPA->vector[2] + OPB->vector[2];
+            break;
+        case INSTR_SUB_F:
+            OPC->_float = OPA->_float - OPB->_float;
+            break;
+        case INSTR_SUB_V:
+            OPC->vector[0] = OPA->vector[0] - OPB->vector[0];
+            OPC->vector[1] = OPA->vector[1] - OPB->vector[1];
+            OPC->vector[2] = OPA->vector[2] - OPB->vector[2];
+            break;
+
+        case INSTR_EQ_F:
+            OPC->_float = (OPA->_float == OPB->_float);
+            break;
+        case INSTR_EQ_V:
+            OPC->_float = ((OPA->vector[0] == OPB->vector[0]) &&
+                           (OPA->vector[1] == OPB->vector[1]) &&
+                           (OPA->vector[2] == OPB->vector[2]) );
+            break;
+        case INSTR_EQ_S:
+            OPC->_float = !strcmp(prog_getstring(prog, OPA->string),
+                                  prog_getstring(prog, OPB->string));
+            break;
+        case INSTR_EQ_E:
+            OPC->_float = (OPA->_int == OPB->_int);
+            break;
+        case INSTR_EQ_FNC:
+            OPC->_float = (OPA->function == OPB->function);
+            break;
+        case INSTR_NE_F:
+            OPC->_float = (OPA->_float != OPB->_float);
+            break;
+        case INSTR_NE_V:
+            OPC->_float = ((OPA->vector[0] != OPB->vector[0]) ||
+                           (OPA->vector[1] != OPB->vector[1]) ||
+                           (OPA->vector[2] != OPB->vector[2]) );
+            break;
+        case INSTR_NE_S:
+            OPC->_float = !!strcmp(prog_getstring(prog, OPA->string),
+                                   prog_getstring(prog, OPB->string));
+            break;
+        case INSTR_NE_E:
+            OPC->_float = (OPA->_int != OPB->_int);
+            break;
+        case INSTR_NE_FNC:
+            OPC->_float = (OPA->function != OPB->function);
+            break;
+
+        case INSTR_LE:
+            OPC->_float = (OPA->_float <= OPB->_float);
+            break;
+        case INSTR_GE:
+            OPC->_float = (OPA->_float >= OPB->_float);
+            break;
+        case INSTR_LT:
+            OPC->_float = (OPA->_float < OPB->_float);
+            break;
+        case INSTR_GT:
+            OPC->_float = (OPA->_float > OPB->_float);
+            break;
+
+        case INSTR_LOAD_F:
+        case INSTR_LOAD_S:
+        case INSTR_LOAD_FLD:
+        case INSTR_LOAD_ENT:
+        case INSTR_LOAD_FNC:
+            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
+                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
+                goto cleanup;
+            }
+            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields)) {
+                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
+                          prog->filename,
+                          OPB->_int);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, OPA->edict);
+            OPC->_int = ((qcany_t*)( ((qcint_t*)ed) + OPB->_int ))->_int;
+            break;
+        case INSTR_LOAD_V:
+            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
+                qcvmerror(prog, "progs `%s` attempted to read an out of bounds entity", prog->filename);
+                goto cleanup;
+            }
+            if (OPB->_int < 0 || OPB->_int + 3 > (qcint_t)prog->entityfields)
+            {
+                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
+                          prog->filename,
+                          OPB->_int + 2);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, OPA->edict);
+            ptr = (qcany_t*)( ((qcint_t*)ed) + OPB->_int );
+            OPC->ivector[0] = ptr->ivector[0];
+            OPC->ivector[1] = ptr->ivector[1];
+            OPC->ivector[2] = ptr->ivector[2];
+            break;
+
+        case INSTR_ADDRESS:
+            if (OPA->edict < 0 || OPA->edict >= prog->entities) {
+                qcvmerror(prog, "prog `%s` attempted to address an out of bounds entity %i", prog->filename, OPA->edict);
+                goto cleanup;
+            }
+            if ((unsigned int)(OPB->_int) >= (unsigned int)(prog->entityfields))
+            {
+                qcvmerror(prog, "prog `%s` attempted to read an invalid field from entity (%i)",
+                          prog->filename,
+                          OPB->_int);
+                goto cleanup;
+            }
+
+            ed = prog_getedict(prog, OPA->edict);
+            OPC->_int = ((qcint_t*)ed) - prog->entitydata + OPB->_int;
+            break;
+
+        case INSTR_STORE_F:
+        case INSTR_STORE_S:
+        case INSTR_STORE_ENT:
+        case INSTR_STORE_FLD:
+        case INSTR_STORE_FNC:
+            OPB->_int = OPA->_int;
+            break;
+        case INSTR_STORE_V:
+            OPB->ivector[0] = OPA->ivector[0];
+            OPB->ivector[1] = OPA->ivector[1];
+            OPB->ivector[2] = OPA->ivector[2];
+            break;
+
+        case INSTR_STOREP_F:
+        case INSTR_STOREP_S:
+        case INSTR_STOREP_ENT:
+        case INSTR_STOREP_FLD:
+        case INSTR_STOREP_FNC:
+            if (OPB->_int < 0 || OPB->_int >= (qcint_t)vec_size(prog->entitydata)) {
+                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
+                goto cleanup;
+            }
+            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
+                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
+                          prog->filename,
+                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
+                          OPB->_int);
+            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
+            ptr->_int = OPA->_int;
+            break;
+        case INSTR_STOREP_V:
+            if (OPB->_int < 0 || OPB->_int + 2 >= (qcint_t)vec_size(prog->entitydata)) {
+                qcvmerror(prog, "`%s` attempted to write to an out of bounds edict (%i)", prog->filename, OPB->_int);
+                goto cleanup;
+            }
+            if (OPB->_int < (qcint_t)prog->entityfields && !prog->allowworldwrites)
+                qcvmerror(prog, "`%s` tried to assign to world.%s (field %i)\n",
+                          prog->filename,
+                          prog_getstring(prog, prog_entfield(prog, OPB->_int)->name),
+                          OPB->_int);
+            ptr = (qcany_t*)(prog->entitydata + OPB->_int);
+            ptr->ivector[0] = OPA->ivector[0];
+            ptr->ivector[1] = OPA->ivector[1];
+            ptr->ivector[2] = OPA->ivector[2];
+            break;
+
+        case INSTR_NOT_F:
+            OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int);
+            break;
+        case INSTR_NOT_V:
+            OPC->_float = !OPA->vector[0] &&
+                          !OPA->vector[1] &&
+                          !OPA->vector[2];
+            break;
+        case INSTR_NOT_S:
+            OPC->_float = !OPA->string ||
+                          !*prog_getstring(prog, OPA->string);
+            break;
+        case INSTR_NOT_ENT:
+            OPC->_float = (OPA->edict == 0);
+            break;
+        case INSTR_NOT_FNC:
+            OPC->_float = !OPA->function;
+            break;
+
+        case INSTR_IF:
+            /* this is consistent with darkplaces' behaviour */
+            if(FLOAT_IS_TRUE_FOR_INT(OPA->_int))
+            {
+                st += st->o2.s1 - 1;    /* offset the s++ */
+                if (++jumpcount >= maxjumps)
+                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
+            }
+            break;
+        case INSTR_IFNOT:
+            if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int))
+            {
+                st += st->o2.s1 - 1;    /* offset the s++ */
+                if (++jumpcount >= maxjumps)
+                    qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
+            }
+            break;
+
+        case INSTR_CALL0:
+        case INSTR_CALL1:
+        case INSTR_CALL2:
+        case INSTR_CALL3:
+        case INSTR_CALL4:
+        case INSTR_CALL5:
+        case INSTR_CALL6:
+        case INSTR_CALL7:
+        case INSTR_CALL8:
+            prog->argc = st->opcode - INSTR_CALL0;
+            if (!OPA->function)
+                qcvmerror(prog, "NULL function in `%s`", prog->filename);
+
+            if(!OPA->function || OPA->function >= (qcint_t)vec_size(prog->functions))
+            {
+                qcvmerror(prog, "CALL outside the program in `%s`", prog->filename);
+                goto cleanup;
+            }
+
+            newf = &prog->functions[OPA->function];
+            newf->profile++;
+
+            prog->statement = (st - prog->code) + 1;
+
+            if (newf->entry < 0)
+            {
+                /* negative statements are built in functions */
+                qcint_t builtinnumber = -newf->entry;
+                if (builtinnumber < (qcint_t)prog->builtins_count && prog->builtins[builtinnumber])
+                    prog->builtins[builtinnumber](prog);
+                else
+                    qcvmerror(prog, "No such builtin #%i in %s! Try updating your gmqcc sources",
+                              builtinnumber, prog->filename);
+            }
+            else
+                st = prog->code + prog_enterfunction(prog, newf) - 1; /* offset st++ */
+            if (prog->vmerror)
+                goto cleanup;
+            break;
+
+        case INSTR_STATE:
+        {
+            qcfloat_t *nextthink;
+            qcfloat_t *time;
+            qcfloat_t *frame;
+            if (!prog->supports_state) {
+                qcvmerror(prog, "`%s` tried to execute a STATE operation but misses its defs!", prog->filename);
+                goto cleanup;
+            }
+            ed = prog_getedict(prog, prog->globals[prog->cached_globals.self]);
+            ((qcint_t*)ed)[prog->cached_fields.think] = OPB->function;
+
+            frame     = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.frame];
+            *frame    = OPA->_float;
+            nextthink = (qcfloat_t*)&((qcint_t*)ed)[prog->cached_fields.nextthink];
+            time      = (qcfloat_t*)(prog->globals + prog->cached_globals.time);
+            *nextthink = *time + 0.1;
+            break;
+        }
+
+        case INSTR_GOTO:
+            st += st->o1.s1 - 1;    /* offset the s++ */
+            if (++jumpcount == 10000000)
+                qcvmerror(prog, "`%s` hit the runaway loop counter limit of %li jumps", prog->filename, jumpcount);
+            break;
+
+        case INSTR_AND:
+            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) &&
+                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
+            break;
+        case INSTR_OR:
+            OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) ||
+                          FLOAT_IS_TRUE_FOR_INT(OPB->_int);
+            break;
+
+        case INSTR_BITAND:
+            OPC->_float = ((int)OPA->_float) & ((int)OPB->_float);
+            break;
+        case INSTR_BITOR:
+            OPC->_float = ((int)OPA->_float) | ((int)OPB->_float);
+            break;
+    }
+}
+
+#undef QCVM_PROFILE
+#undef QCVM_TRACE
+#endif /* !QCVM_LOOP */
diff --git a/fold.c b/fold.c
deleted file mode 100644 (file)
index 64b20d9..0000000
--- a/fold.c
+++ /dev/null
@@ -1,1668 +0,0 @@
-#include <string.h>
-#include <math.h>
-
-#include "ast.h"
-#include "parser.h"
-
-#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024
-#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024
-
-/* The options to use for inexact and arithmetic exceptions */
-#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN
-#define FOLD_TINYNESS SFLOAT_TBEFORE
-
-/*
- * Comparing float values is an unsafe operation when the operands to the
- * comparison are floating point values that are inexact. For instance 1/3 is an
- * inexact value. The FPU is meant to raise exceptions when these sorts of things
- * happen, including division by zero, underflows and overflows. The C standard
- * library provides us with the <fenv.h> header to gain access to the floating-
- * point environment and lets us set the rounding mode and check for these exceptions.
- * The problem is the standard C library allows an implementation to leave these
- * stubbed out and does not require they be implemented. Furthermore, depending
- * on implementations there is no control over the FPU. This is an IEE 754
- * conforming implementation in software to compensate.
- */
-typedef uint32_t sfloat_t;
-
-typedef union {
-    qcfloat_t f;
-    sfloat_t  s;
-} sfloat_cast_t;
-
-/* Exception flags */
-typedef enum {
-    SFLOAT_NOEXCEPT  = 0,
-    SFLOAT_INVALID   = 1,
-    SFLOAT_DIVBYZERO = 4,
-    SFLOAT_OVERFLOW  = 8,
-    SFLOAT_UNDERFLOW = 16,
-    SFLOAT_INEXACT   = 32
-} sfloat_exceptionflags_t;
-
-/* Rounding modes */
-typedef enum {
-    SFLOAT_ROUND_NEAREST_EVEN,
-    SFLOAT_ROUND_DOWN,
-    SFLOAT_ROUND_UP,
-    SFLOAT_ROUND_TO_ZERO
-} sfloat_roundingmode_t;
-
-/* Underflow tininess-detection mode */
-typedef enum {
-    SFLOAT_TAFTER,
-    SFLOAT_TBEFORE
-} sfloat_tdetect_t;
-
-typedef struct {
-    sfloat_roundingmode_t   roundingmode;
-    sfloat_exceptionflags_t exceptionflags;
-    sfloat_tdetect_t        tiny;
-} sfloat_state_t;
-
-/* Counts the number of leading zero bits before the most-significand one bit. */
-#ifdef _MSC_VER
-/* MSVC has an intrinsic for this */
-    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
-        int r = 0;
-        _BitScanForward(&r, x);
-        return r;
-    }
-#   define SFLOAT_CLZ(X, SUB) \
-        (sfloat_clz((X)) - (SUB))
-#elif defined(__GNUC__) || defined(__CLANG__)
-/* Clang and GCC have a builtin for this */
-#   define SFLOAT_CLZ(X, SUB) \
-        (__builtin_clz((X)) - (SUB))
-#else
-/* Native fallback */
-    static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) {
-        x -= ((x >> 1) & 0x55555555);
-        x  = (((x >> 2) & 0x33333333) + (x & 0x33333333));
-        x  = (((x >> 4) + x) & 0x0F0F0F0F);
-        x += x >> 8;
-        x += x >> 16;
-        return x & 0x0000003F;
-    }
-    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
-        x |= (x >> 1);
-        x |= (x >> 2);
-        x |= (x >> 4);
-        x |= (x >> 8);
-        x |= (x >> 16);
-        return 32 - sfloat_popcnt(x);
-    }
-#   define SFLOAT_CLZ(X, SUB) \
-        (sfloat_clz((X) - (SUB)))
-#endif
-
-/* The value of a NaN */
-#define SFLOAT_NAN 0xFFFFFFFF
-/* Test if NaN */
-#define SFLOAT_ISNAN(A) \
-    (0xFF000000 < (uint32_t)((A) << 1))
-/* Test if signaling NaN */
-#define SFLOAT_ISSNAN(A) \
-    (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF))
-/* Raise exception */
-#define SFLOAT_RAISE(STATE, FLAGS) \
-    ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS)))
-/*
- * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits
- * are shifted off they are forced into the least significand bit of the result
- * by setting it to one. As a result of this, the value of `COUNT' can be
- * arbitrarily large; if `COUNT' is greater than 32, the result will be either
- * zero or one, depending on whether `A' is a zero or non-zero. The result is
- * stored into the value pointed by `Z'.
- */
-#define SFLOAT_SHIFT(SIZE, A, COUNT, Z)                                      \
-    *(Z) = ((COUNT) == 0)                                                    \
-        ? 1                                                                  \
-        : (((COUNT) < (SIZE))                                                \
-            ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \
-            : ((A) != 0))
-
-/* Extract fractional component */
-#define SFLOAT_EXTRACT_FRAC(X) \
-    ((uint32_t)((X) & 0x007FFFFF))
-/* Extract exponent component */
-#define SFLOAT_EXTRACT_EXP(X) \
-    ((int16_t)((X) >> 23) & 0xFF)
-/* Extract sign bit */
-#define SFLOAT_EXTRACT_SIGN(X) \
-    ((X) >> 31)
-/*
- * Normalizes the subnormal value represented by the denormalized significand
- * `SA'. The normalized exponent and significand are stored at the locations
- * pointed by `Z' and `SZ' respectively.
- */
-#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \
-    (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8))
-/*
- * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value
- * giving the result.
- *
- * After the shifting into their proper positions, the fields are added together
- * to form the result. This means any integer portion of `SIG' will be added
- * to the exponent. Similarly, because a properly normalized significand will
- * always have an integer portion equal to one, the exponent input `EXP' should
- * be one less than the desired result exponent whenever the significant input
- * `SIG' is a complete, normalized significand.
- */
-#define SFLOAT_PACK(SIGN, EXP, SIG) \
-    (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG))
-
-/*
- * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate
- * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is
- * raised.
- */
-static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    bool isnan_a  = SFLOAT_ISNAN(a);
-    bool issnan_a = SFLOAT_ISSNAN(a);
-    bool isnan_b  = SFLOAT_ISNAN(b);
-    bool issnan_b = SFLOAT_ISSNAN(b);
-
-    a |= 0x00400000;
-    b |= 0x00400000;
-
-    if (issnan_a | issnan_b)
-        SFLOAT_RAISE(state, SFLOAT_INVALID);
-    if (isnan_a)
-        return (issnan_a & isnan_b) ? b : a;
-    return b;
-}
-
-/*
- * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand
- * `sig_z' and returns the appropriate value corresponding to the abstract input.
- *
- * The abstract value is simply rounded and packed into the format. If the abstract
- * input cannot be represented exactly an inexact exception is raised. If the
- * abstract input is too large, the overflow and inexact exceptions are both raised
- * and an infinity or maximal finite value is returned. If the abstract value is
- * too small, the value is rounded to a subnormal and the underflow and inexact
- * exceptions are only raised if the value cannot be represented exactly with
- * a subnormal.
- *
- * The input significand `sig_z' has it's binary point between bits 30 and 29,
- * this is seven bits to the left of its usual location. The shifted significand
- * must be normalized or smaller than this. If it's not normalized then the exponent
- * `exp_z' must be zero; in that case, the result returned is a subnormal number
- * which must not require rounding. In the more usual case where the significand
- * is normalized, the exponent must be one less than the *true* exponent.
- *
- * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE.
- */
-static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
-    sfloat_roundingmode_t mode      = state->roundingmode;
-    bool                  even      = !!(mode == SFLOAT_ROUND_NEAREST_EVEN);
-    unsigned char         increment = 0x40;
-    unsigned char         bits      = sig_z & 0x7F;
-
-    if (!even) {
-        if (mode == SFLOAT_ROUND_TO_ZERO)
-            increment = 0;
-        else {
-            increment = 0x7F;
-            if (sign_z) {
-                if (mode == SFLOAT_ROUND_UP)
-                    increment = 0;
-            } else {
-                if (mode == SFLOAT_ROUND_DOWN)
-                    increment = 0;
-            }
-        }
-    }
-
-    if (0xFD <= (uint16_t)exp_z) {
-        if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) {
-            SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT);
-            return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0);
-        }
-        if (exp_z < 0) {
-            /* Check for underflow */
-            bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000);
-            SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z);
-            exp_z = 0;
-            bits = sig_z & 0x7F;
-            if (tiny && bits)
-                SFLOAT_RAISE(state, SFLOAT_UNDERFLOW);
-        }
-    }
-    if (bits)
-        SFLOAT_RAISE(state, SFLOAT_INEXACT);
-    sig_z = (sig_z + increment) >> 7;
-    sig_z &= ~(((bits ^ 0x40) == 0) & even);
-    if (sig_z == 0)
-        exp_z = 0;
-    return SFLOAT_PACK(sign_z, exp_z, sig_z);
-}
-
-/*
- * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand
- * `sig_z' and returns the appropriate value corresponding to the abstract input.
- * This function is exactly like `PACK_round' except the significand does not have
- * to be normalized.
- *
- * Bit 31 of the significand must be zero and the exponent must be one less than
- * the *true* exponent.
- */
-static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
-    unsigned char c = SFLOAT_CLZ(sig_z, 1);
-    return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c);
-}
-
-/*
- * Returns the result of adding the absolute values of `a' and `b'. The sign
- * `sign_z' is ignored if the result is a NaN.
- */
-static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
-    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z = 0;
-    int16_t  exp_d = exp_a - exp_b;
-    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6;
-    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6;
-    uint32_t sig_z = 0;
-
-    if (0 < exp_d) {
-        if (exp_a == 0xFF)
-            return sig_a ? sfloat_propagate_nan(state, a, b) : a;
-        if (exp_b == 0)
-            --exp_d;
-        else
-            sig_b |= 0x20000000;
-        SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
-        exp_z = exp_a;
-    } else if (exp_d < 0) {
-        if (exp_b == 0xFF)
-            return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0);
-        if (exp_a == 0)
-            ++exp_d;
-        else
-            sig_a |= 0x20000000;
-        SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
-        exp_z = exp_b;
-    } else {
-        if (exp_a == 0xFF)
-            return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a;
-        if (exp_a == 0)
-            return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6);
-        sig_z = 0x40000000 + sig_a + sig_b;
-        exp_z = exp_a;
-        goto end;
-    }
-    sig_a |= 0x20000000;
-    sig_z = (sig_a + sig_b) << 1;
-    --exp_z;
-    if ((int32_t)sig_z < 0) {
-        sig_z = sig_a + sig_b;
-        ++exp_z;
-    }
-end:
-    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
-}
-
-/*
- * Returns the result of subtracting the absolute values of `a' and `b'. If the
- * sign `sign_z' is one, the difference is negated before being returned. The
- * sign is ignored if the result is a NaN.
- */
-static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
-    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z = 0;
-    int16_t  exp_d = exp_a - exp_b;
-    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7;
-    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7;
-    uint32_t sig_z = 0;
-
-    if (0 < exp_d) goto exp_greater_a;
-    if (exp_d < 0) goto exp_greater_b;
-
-    if (exp_a == 0xFF) {
-        if (sig_a | sig_b)
-            return sfloat_propagate_nan(state, a, b);
-        SFLOAT_RAISE(state, SFLOAT_INVALID);
-        return SFLOAT_NAN;
-    }
-
-    if (exp_a == 0)
-        exp_a = exp_b = 1;
-
-    if (sig_b < sig_a) goto greater_a;
-    if (sig_a < sig_b) goto greater_b;
-
-    return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0);
-
-exp_greater_b:
-    if (exp_b == 0xFF)
-        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0);
-    if (exp_a == 0)
-        ++exp_d;
-    else
-        sig_a |= 0x40000000;
-    SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
-    sig_b |= 0x40000000;
-greater_b:
-    sig_z = sig_b - sig_a;
-    exp_z = exp_b;
-    sign_z ^= 1;
-    goto end;
-
-exp_greater_a:
-    if (exp_a == 0xFF)
-        return (sig_a) ? sfloat_propagate_nan(state, a, b) : a;
-    if (exp_b == 0)
-        --exp_d;
-    else
-        sig_b |= 0x40000000;
-    SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
-    sig_a |= 0x40000000;
-greater_a:
-    sig_z = sig_a - sig_b;
-    exp_z = exp_a;
-
-end:
-    --exp_z;
-    return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z);
-}
-
-static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
-    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
-    return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a)
-                              : sfloat_sub_impl(state, a, b, sign_a);
-}
-
-static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
-    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
-    return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a)
-                              : sfloat_add_impl(state, a, b, sign_a);
-}
-
-static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z   = 0;
-    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
-    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
-    uint32_t sig_z   = 0;
-    uint64_t sig_z64 = 0;
-    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
-    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
-    bool     sign_z  = sign_a ^ sign_b;
-
-    if (exp_a == 0xFF) {
-        if (sig_a || ((exp_b == 0xFF) && sig_b))
-            return sfloat_propagate_nan(state, a, b);
-        if ((exp_b | sig_b) == 0) {
-            SFLOAT_RAISE(state, SFLOAT_INVALID);
-            return SFLOAT_NAN;
-        }
-        return SFLOAT_PACK(sign_z, 0xFF, 0);
-    }
-    if (exp_b == 0xFF) {
-        if (sig_b)
-            return sfloat_propagate_nan(state, a, b);
-        if ((exp_a | sig_a) == 0) {
-            SFLOAT_RAISE(state, SFLOAT_INVALID);
-            return SFLOAT_NAN;
-        }
-        return SFLOAT_PACK(sign_z, 0xFF, 0);
-    }
-    if (exp_a == 0) {
-        if (sig_a == 0)
-            return SFLOAT_PACK(sign_z, 0, 0);
-        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
-    }
-    if (exp_b == 0) {
-        if (sig_b == 0)
-            return SFLOAT_PACK(sign_z, 0, 0);
-        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
-    }
-    exp_z = exp_a + exp_b - 0x7F;
-    sig_a = (sig_a | 0x00800000) << 7;
-    sig_b = (sig_b | 0x00800000) << 8;
-    SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64);
-    sig_z = sig_z64;
-    if (0 <= (int32_t)(sig_z << 1)) {
-        sig_z <<= 1;
-        --exp_z;
-    }
-    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
-}
-
-static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
-    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
-    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
-    int16_t  exp_z   = 0;
-    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
-    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
-    uint32_t sig_z   = 0;
-    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
-    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
-    bool     sign_z  = sign_a ^ sign_b;
-
-    if (exp_a == 0xFF) {
-        if (sig_a)
-            return sfloat_propagate_nan(state, a, b);
-        if (exp_b == 0xFF) {
-            if (sig_b)
-                return sfloat_propagate_nan(state, a, b);
-            SFLOAT_RAISE(state, SFLOAT_INVALID);
-            return SFLOAT_NAN;
-        }
-        return SFLOAT_PACK(sign_z, 0xFF, 0);
-    }
-    if (exp_b == 0xFF)
-        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0);
-    if (exp_b == 0) {
-        if (sig_b == 0) {
-            if ((exp_a | sig_a) == 0) {
-                SFLOAT_RAISE(state, SFLOAT_INVALID);
-                return SFLOAT_NAN;
-            }
-            SFLOAT_RAISE(state, SFLOAT_DIVBYZERO);
-            return SFLOAT_PACK(sign_z, 0xFF, 0);
-        }
-        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
-    }
-    if (exp_a == 0) {
-        if (sig_a == 0)
-            return SFLOAT_PACK(sign_z, 0, 0);
-        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
-    }
-    exp_z = exp_a - exp_b + 0x7D;
-    sig_a = (sig_a | 0x00800000) << 7;
-    sig_b = (sig_b | 0x00800000) << 8;
-    if (sig_b <= (sig_a + sig_a)) {
-        sig_a >>= 1;
-        ++exp_z;
-    }
-    sig_z = (((uint64_t)sig_a) << 32) / sig_b;
-    if ((sig_z & 0x3F) == 0)
-        sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32);
-    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
-}
-
-static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) {
-    sfloat_cast_t neg;
-    neg.f = -1;
-    return sfloat_mul(state, a, neg.s);
-}
-
-static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) {
-    /* Exception comes from vector component */
-    if (vec) {
-        if (state->exceptionflags & SFLOAT_DIVBYZERO)
-            compile_error(ctx, "division by zero in `%s' component", vec);
-        if (state->exceptionflags & SFLOAT_INVALID)
-            compile_error(ctx, "undefined (inf) in `%s' component", vec);
-        if (state->exceptionflags & SFLOAT_OVERFLOW)
-            compile_error(ctx, "arithmetic overflow in `%s' component", vec);
-        if (state->exceptionflags & SFLOAT_UNDERFLOW)
-            compile_error(ctx, "arithmetic underflow in `%s' component", vec);
-            return;
-    }
-    if (state->exceptionflags & SFLOAT_DIVBYZERO)
-        compile_error(ctx, "division by zero");
-    if (state->exceptionflags & SFLOAT_INVALID)
-        compile_error(ctx, "undefined (inf)");
-    if (state->exceptionflags & SFLOAT_OVERFLOW)
-        compile_error(ctx, "arithmetic overflow");
-    if (state->exceptionflags & SFLOAT_UNDERFLOW)
-        compile_error(ctx, "arithmetic underflow");
-}
-
-static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) {
-    state->exceptionflags = SFLOAT_NOEXCEPT;
-    state->roundingmode   = FOLD_ROUNDING;
-    state->tiny           = FOLD_TINYNESS;
-}
-
-/*
- * There is two stages to constant folding in GMQCC: there is the parse
- * stage constant folding, where, with the help of the AST, operator
- * usages can be constant folded. Then there is the constant folding
- * in the IR for things like eliding if statements, can occur.
- *
- * This file is thus, split into two parts.
- */
-
-#define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
-#define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
-#define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
-#define isarray(X)      (((ast_expression*)(X))->vtype == TYPE_ARRAY)
-#define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
-
-/*
- * Implementation of basic vector math for vec3_t, for trivial constant
- * folding.
- *
- * TODO: gcc/clang hinting for autovectorization
- */
-typedef enum {
-    VEC_COMP_X = 1 << 0,
-    VEC_COMP_Y = 1 << 1,
-    VEC_COMP_Z = 1 << 2
-} vec3_comp_t;
-
-typedef struct {
-    sfloat_cast_t x;
-    sfloat_cast_t y;
-    sfloat_cast_t z;
-} vec3_soft_t;
-
-typedef struct {
-    vec3_comp_t    faults;
-    sfloat_state_t state[3];
-} vec3_soft_state_t;
-
-static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) {
-    vec3_soft_t soft;
-    soft.x.f = vec.x;
-    soft.y.f = vec.y;
-    soft.z.f = vec.z;
-    return soft;
-}
-
-static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) {
-    sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags;
-    if (flags & SFLOAT_DIVBYZERO) return true;
-    if (flags & SFLOAT_INVALID)   return true;
-    if (flags & SFLOAT_OVERFLOW)  return true;
-    if (flags & SFLOAT_UNDERFLOW) return true;
-    return false;
-}
-
-static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state,
-                                        sfloat_t         (*callback)(sfloat_state_t *, sfloat_t, sfloat_t),
-                                        vec3_t             a,
-                                        vec3_t             b)
-{
-    vec3_soft_t sa = vec3_soft_convert(a);
-    vec3_soft_t sb = vec3_soft_convert(b);
-    callback(&state->state[0], sa.x.s, sb.x.s);
-    if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X);
-    callback(&state->state[1], sa.y.s, sb.y.s);
-    if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y);
-    callback(&state->state[2], sa.z.s, sb.z.s);
-    if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z);
-}
-
-static GMQCC_INLINE void vec3_check_except(vec3_t     a,
-                                           vec3_t     b,
-                                           lex_ctx_t  ctx,
-                                           sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t))
-{
-    vec3_soft_state_t state;
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        return;
-
-    sfloat_init(&state.state[0]);
-    sfloat_init(&state.state[1]);
-    sfloat_init(&state.state[2]);
-
-    vec3_soft_eval(&state, callback, a, b);
-    if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x");
-    if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y");
-    if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z");
-}
-
-static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_t out;
-    vec3_check_except(a, b, ctx, &sfloat_add);
-    out.x = a.x + b.x;
-    out.y = a.y + b.y;
-    out.z = a.z + b.z;
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_t out;
-    vec3_check_except(a, b, ctx, &sfloat_sub);
-    out.x = a.x - b.x;
-    out.y = a.y - b.y;
-    out.z = a.z - b.z;
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) {
-    vec3_t         out;
-    sfloat_cast_t  v[3];
-    sfloat_state_t s[3];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    v[0].f = a.x;
-    v[1].f = a.y;
-    v[2].f = a.z;
-
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-
-    sfloat_neg(&s[0], v[0].s);
-    sfloat_neg(&s[1], v[1].s);
-    sfloat_neg(&s[2], v[2].s);
-
-    sfloat_check(ctx, &s[0], NULL);
-    sfloat_check(ctx, &s[1], NULL);
-    sfloat_check(ctx, &s[2], NULL);
-
-end:
-    out.x = -a.x;
-    out.y = -a.y;
-    out.z = -a.z;
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x));
-    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y));
-    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b));
-    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b));
-    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x));
-    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y));
-    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b));
-    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b));
-    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x));
-    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y));
-    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) {
-    vec3_t out;
-    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b));
-    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b));
-    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b));
-    return out;
-}
-
-static GMQCC_INLINE vec3_t vec3_not(vec3_t a) {
-    vec3_t out;
-    out.x = -1-a.x;
-    out.y = -1-a.y;
-    out.z = -1-a.z;
-    return out;
-}
-
-static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_soft_t    sa;
-    vec3_soft_t    sb;
-    sfloat_state_t s[5];
-    sfloat_t       r[5];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    sa = vec3_soft_convert(a);
-    sb = vec3_soft_convert(b);
-
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-    sfloat_init(&s[3]);
-    sfloat_init(&s[4]);
-
-    r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s);
-    r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s);
-    r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s);
-    r[3] = sfloat_add(&s[3], r[0],   r[1]);
-    r[4] = sfloat_add(&s[4], r[3],   r[2]);
-
-    sfloat_check(ctx, &s[0], NULL);
-    sfloat_check(ctx, &s[1], NULL);
-    sfloat_check(ctx, &s[2], NULL);
-    sfloat_check(ctx, &s[3], NULL);
-    sfloat_check(ctx, &s[4], NULL);
-
-end:
-    return (a.x * b.x + a.y * b.y + a.z * b.z);
-}
-
-static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) {
-    vec3_t         out;
-    vec3_soft_t    sa;
-    sfloat_cast_t  sb;
-    sfloat_state_t s[3];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    sa   = vec3_soft_convert(a);
-    sb.f = b;
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-
-    sfloat_mul(&s[0], sa.x.s, sb.s);
-    sfloat_mul(&s[1], sa.y.s, sb.s);
-    sfloat_mul(&s[2], sa.z.s, sb.s);
-
-    sfloat_check(ctx, &s[0], "x");
-    sfloat_check(ctx, &s[1], "y");
-    sfloat_check(ctx, &s[2], "z");
-
-end:
-    out.x = a.x * b;
-    out.y = a.y * b;
-    out.z = a.z * b;
-    return out;
-}
-
-static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) {
-    return a.x == b.x &&
-           a.y == b.y &&
-           a.z == b.z;
-}
-
-static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) {
-    vec3_t out;
-    out.x = x;
-    out.y = y;
-    out.z = z;
-    return out;
-}
-
-static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) {
-    return (!a.x && !a.y && !a.z);
-}
-
-static GMQCC_INLINE bool vec3_pbool(vec3_t a) {
-    return (a.x || a.y || a.z);
-}
-
-static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) {
-    vec3_t         out;
-    vec3_soft_t    sa;
-    vec3_soft_t    sb;
-    sfloat_t       r[9];
-    sfloat_state_t s[9];
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto end;
-
-    sa = vec3_soft_convert(a);
-    sb = vec3_soft_convert(b);
-
-    sfloat_init(&s[0]);
-    sfloat_init(&s[1]);
-    sfloat_init(&s[2]);
-    sfloat_init(&s[3]);
-    sfloat_init(&s[4]);
-    sfloat_init(&s[5]);
-    sfloat_init(&s[6]);
-    sfloat_init(&s[7]);
-    sfloat_init(&s[8]);
-
-    r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s);
-    r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s);
-    r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s);
-    r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s);
-    r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s);
-    r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s);
-    r[6] = sfloat_sub(&s[6], r[0],   r[1]);
-    r[7] = sfloat_sub(&s[7], r[2],   r[3]);
-    r[8] = sfloat_sub(&s[8], r[4],   r[5]);
-
-    sfloat_check(ctx, &s[0], NULL);
-    sfloat_check(ctx, &s[1], NULL);
-    sfloat_check(ctx, &s[2], NULL);
-    sfloat_check(ctx, &s[3], NULL);
-    sfloat_check(ctx, &s[4], NULL);
-    sfloat_check(ctx, &s[5], NULL);
-    sfloat_check(ctx, &s[6], "x");
-    sfloat_check(ctx, &s[7], "y");
-    sfloat_check(ctx, &s[8], "z");
-
-end:
-    out.x = a.y * b.z - a.z * b.y;
-    out.y = a.z * b.x - a.x * b.z;
-    out.z = a.x * b.y - a.y * b.x;
-    return out;
-}
-
-static lex_ctx_t fold_ctx(fold_t *fold) {
-    lex_ctx_t ctx;
-    if (fold->parser->lex)
-        return parser_ctx(fold->parser);
-
-    memset(&ctx, 0, sizeof(ctx));
-    return ctx;
-}
-
-static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) {
-    switch (v->expression.vtype) {
-        case TYPE_FLOAT:
-            return !!v->constval.vfloat;
-        case TYPE_INTEGER:
-            return !!v->constval.vint;
-        case TYPE_VECTOR:
-            if (OPTS_FLAG(CORRECT_LOGIC))
-                return vec3_pbool(v->constval.vvec);
-            return !!(v->constval.vvec.x);
-        case TYPE_STRING:
-            if (!v->constval.vstring)
-                return false;
-            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
-                return true;
-            return !!v->constval.vstring[0];
-        default:
-            compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type");
-            break;
-    }
-    return !!v->constval.vfunc;
-}
-
-/* Handy macros to determine if an ast_value can be constant folded. */
-#define fold_can_1(X)  \
-    (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \
-                ((ast_expression*)(X))->vtype != TYPE_FUNCTION)
-
-#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y))
-
-#define fold_immvalue_float(E)  ((E)->constval.vfloat)
-#define fold_immvalue_vector(E) ((E)->constval.vvec)
-#define fold_immvalue_string(E) ((E)->constval.vstring)
-
-fold_t *fold_init(parser_t *parser) {
-    fold_t *fold                 = (fold_t*)mem_a(sizeof(fold_t));
-    fold->parser                 = parser;
-    fold->imm_float              = NULL;
-    fold->imm_vector             = NULL;
-    fold->imm_string             = NULL;
-    fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE);
-    fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE);
-
-    /*
-     * prime the tables with common constant values at constant
-     * locations.
-     */
-    (void)fold_constgen_float (fold,  0.0f, false);
-    (void)fold_constgen_float (fold,  1.0f, false);
-    (void)fold_constgen_float (fold, -1.0f, false);
-    (void)fold_constgen_float (fold,  2.0f, false);
-
-    (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f));
-    (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f));
-
-    return fold;
-}
-
-bool fold_generate(fold_t *fold, ir_builder *ir) {
-    /* generate globals for immediate folded values */
-    size_t     i;
-    ast_value *cur;
-
-    for (i = 0; i < vec_size(fold->imm_float);   ++i)
-        if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err;
-    for (i = 0; i < vec_size(fold->imm_vector);  ++i)
-        if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err;
-    for (i = 0; i < vec_size(fold->imm_string);  ++i)
-        if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err;
-
-    return true;
-
-err:
-    con_out("failed to generate global %s\n", cur->name);
-    ir_builder_delete(ir);
-    return false;
-}
-
-void fold_cleanup(fold_t *fold) {
-    size_t i;
-
-    for (i = 0; i < vec_size(fold->imm_float);  ++i) ast_delete(fold->imm_float[i]);
-    for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]);
-    for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]);
-
-    vec_free(fold->imm_float);
-    vec_free(fold->imm_vector);
-    vec_free(fold->imm_string);
-
-    util_htdel(fold->imm_string_untranslate);
-    util_htdel(fold->imm_string_dotranslate);
-
-    mem_d(fold);
-}
-
-ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) {
-    ast_value  *out = NULL;
-    size_t      i;
-
-    for (i = 0; i < vec_size(fold->imm_float); i++) {
-        if (!memcmp(&fold->imm_float[i]->constval.vfloat, &value, sizeof(qcfloat_t)))
-            return (ast_expression*)fold->imm_float[i];
-    }
-
-    out                  = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT);
-    out->cvq             = CV_CONST;
-    out->hasvalue        = true;
-    out->inexact         = inexact;
-    out->constval.vfloat = value;
-
-    vec_push(fold->imm_float, out);
-
-    return (ast_expression*)out;
-}
-
-ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) {
-    ast_value *out;
-    size_t     i;
-
-    for (i = 0; i < vec_size(fold->imm_vector); i++) {
-        if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value))
-            return (ast_expression*)fold->imm_vector[i];
-    }
-
-    out                = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR);
-    out->cvq           = CV_CONST;
-    out->hasvalue      = true;
-    out->constval.vvec = value;
-
-    vec_push(fold->imm_vector, out);
-
-    return (ast_expression*)out;
-}
-
-ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) {
-    hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate;
-    ast_value    *out   = NULL;
-    size_t        hash  = util_hthash(table, str);
-
-    if ((out = (ast_value*)util_htgeth(table, str, hash)))
-        return (ast_expression*)out;
-
-    if (translate) {
-        char name[32];
-        util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++));
-        out                    = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING);
-        out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */
-    } else
-        out                    = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING);
-
-    out->cvq              = CV_CONST;
-    out->hasvalue         = true;
-    out->isimm            = true;
-    out->constval.vstring = parser_strdup(str);
-
-    vec_push(fold->imm_string, out);
-    util_htseth(table, str, hash, out);
-
-    return (ast_expression*)out;
-}
-
-typedef union {
-    void     (*callback)(void);
-    sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t);
-    sfloat_t (*unary)(sfloat_state_t *, sfloat_t);
-} float_check_callback_t;
-
-static bool fold_check_except_float_impl(void     (*callback)(void),
-                                         fold_t    *fold,
-                                         ast_value *a,
-                                         ast_value *b)
-{
-    float_check_callback_t call;
-    sfloat_state_t s;
-    sfloat_cast_t ca;
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES))
-        return false;
-
-    call.callback = callback;
-    sfloat_init(&s);
-    ca.f = fold_immvalue_float(a);
-    if (b) {
-        sfloat_cast_t cb;
-        cb.f = fold_immvalue_float(b);
-        call.binary(&s, ca.s, cb.s);
-    } else {
-        call.unary(&s, ca.s);
-    }
-
-    if (s.exceptionflags == 0)
-        return false;
-
-    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
-        goto inexact_possible;
-
-    sfloat_check(fold_ctx(fold), &s, NULL);
-
-inexact_possible:
-    return s.exceptionflags & SFLOAT_INEXACT;
-}
-
-#define fold_check_except_float(CALLBACK, FOLD, A, B) \
-    fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B))
-
-static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) {
-    lex_ctx_t ctx = fold_ctx(fold);
-    if (!OPTS_WARN(WARN_INEXACT_COMPARES))
-        return false;
-    if (!a->inexact && !b->inexact)
-        return false;
-    return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison");
-}
-
-static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) {
-    qcfloat_t x = (&vec.x)[set[0]-'x'];
-    qcfloat_t y = (&vec.x)[set[1]-'x'];
-    qcfloat_t z = (&vec.x)[set[2]-'x'];
-    if (!y && !z) {
-        ast_expression *out;
-        ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
-        out                        = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL);
-        out->node.keep             = false;
-        ((ast_member*)out)->rvalue = true;
-        if (x != -1.0f)
-            return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out);
-    }
-    return NULL;
-}
-
-
-static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) {
-    if (isfloat(a)) {
-        if (fold_can_1(a)) {
-            /* Negation can produce inexact as well */
-            bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL);
-            return fold_constgen_float(fold, -fold_immvalue_float(a), inexact);
-        }
-    } else if (isvector(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a)));
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) {
-    if (isfloat(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_float(fold, !fold_immvalue_float(a), false);
-    } else if (isvector(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false);
-    } else if (isstring(a)) {
-        if (fold_can_1(a)) {
-            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
-                return fold_constgen_float(fold, !fold_immvalue_string(a), false);
-            else
-                return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false);
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b)) {
-            bool inexact = fold_check_except_float(&sfloat_add, fold, a, b);
-            return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact);
-        }
-    } else if (isvector(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_vector(fold, vec3_add(fold_ctx(fold),
-                                                       fold_immvalue_vector(a),
-                                                       fold_immvalue_vector(b)));
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b)) {
-            bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b);
-            return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact);
-        }
-    } else if (isvector(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold),
-                                                       fold_immvalue_vector(a),
-                                                       fold_immvalue_vector(b)));
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (isvector(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(b), fold_immvalue_float(a)));
-        } else {
-            if (fold_can_2(a, b)) {
-                bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b);
-                return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact);
-            }
-        }
-    } else if (isvector(a)) {
-        if (isfloat(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_float(b)));
-        } else {
-            if (fold_can_2(a, b)) {
-                return fold_constgen_float(fold, vec3_mulvv(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_vector(b)), false);
-            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) {
-                ast_expression *out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out;
-            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) {
-                ast_expression *out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out;
-                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out;
-            }
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b)) {
-            bool inexact = fold_check_except_float(&sfloat_div, fold, a, b);
-            return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact);
-        } else if (fold_can_1(b)) {
-            return (ast_expression*)ast_binary_new(
-                fold_ctx(fold),
-                INSTR_MUL_F,
-                (ast_expression*)a,
-                fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false)
-            );
-        }
-    } else if (isvector(a)) {
-        if (fold_can_2(a, b)) {
-            return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b)));
-        } else {
-            return (ast_expression*)ast_binary_new(
-                fold_ctx(fold),
-                INSTR_MUL_VF,
-                (ast_expression*)a,
-                (fold_can_1(b))
-                    ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false)
-                    : (ast_expression*)ast_binary_new(
-                                            fold_ctx(fold),
-                                            INSTR_DIV_F,
-                                            (ast_expression*)fold->imm_float[1],
-                                            (ast_expression*)b
-                    )
-            );
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) {
-    return (fold_can_2(a, b))
-                ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false)
-                : NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false);
-    } else {
-        if (isvector(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b)));
-        } else {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false);
-    } else {
-        if (isvector(b)) {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b)));
-        } else {
-            if (fold_can_2(a, b))
-                return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) {
-    if (isfloat(a)) {
-        if (fold_can_2(a, b))
-            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false);
-    } else {
-        if (fold_can_2(a, b)) {
-            if (isvector(b))
-                return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b)));
-            else
-                return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b) && isfloats(a, b))
-        return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b) && isfloats(a, b))
-        return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float expr) {
-    if (fold_can_2(a, b)) {
-        if (OPTS_FLAG(PERL_LOGIC)) {
-            if (expr)
-                return (fold_immediate_true(fold, a)) ? (ast_expression*)a : (ast_expression*)b;
-            else
-                return (fold_immediate_true(fold, a)) ? (ast_expression*)b : (ast_expression*)a;
-        } else {
-            return fold_constgen_float (
-                fold,
-                ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b))
-                        : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b)))
-                            ? 1
-                            : 0,
-                false
-            );
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) {
-    if (fold_can_1(a)) {
-        return fold_immediate_true(fold, a)
-                    ? (ast_expression*)b
-                    : (ast_expression*)c;
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b))
-        return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a,b)) {
-        fold_check_inexact_float(fold, a, b);
-        if (fold_immvalue_float(a) <  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2];
-        if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0];
-        if (fold_immvalue_float(a) >  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1];
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) {
-    if (fold_can_2(a, b)) {
-        fold_check_inexact_float(fold, a, b);
-        return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))]
-                    : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))];
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) {
-    if (fold_can_2(a, b)) {
-        if (isfloat(a) && isfloat(b)) {
-            float la = fold_immvalue_float(a);
-            float lb = fold_immvalue_float(b);
-            fold_check_inexact_float(fold, a, b);
-            return (ast_expression*)fold->imm_float[!(ne ? la == lb : la != lb)];
-        } if (isvector(a) && isvector(b)) {
-            vec3_t la = fold_immvalue_vector(a);
-            vec3_t lb = fold_immvalue_vector(b);
-            return (ast_expression*)fold->imm_float[!(ne ? vec3_cmp(la, lb) : !vec3_cmp(la, lb))];
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) {
-    if (isfloat(a)) {
-        if (fold_can_1(a))
-            return fold_constgen_float(fold, -1-fold_immvalue_float(a), false);
-    } else {
-        if (isvector(a)) {
-            if (fold_can_1(a))
-                return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a)));
-        }
-    }
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) {
-    if (fold_can_2(a, b))
-        return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold),
-                                                     fold_immvalue_vector(a),
-                                                     fold_immvalue_vector(b)));
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) {
-    if (fold_can_1(a) && isstring(a))
-        return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false);
-    if (isarray(a))
-        return fold_constgen_float(fold, vec_size(a->initlist), false);
-    return NULL;
-}
-
-ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) {
-    ast_value      *a = (ast_value*)opexprs[0];
-    ast_value      *b = (ast_value*)opexprs[1];
-    ast_value      *c = (ast_value*)opexprs[2];
-    ast_expression *e = NULL;
-
-    /* can a fold operation be applied to this operator usage? */
-    if (!info->folds)
-        return NULL;
-
-    switch(info->operands) {
-        case 3: if(!c) return NULL;
-        case 2: if(!b) return NULL;
-        case 1:
-        if(!a) {
-            compile_error(fold_ctx(fold), "internal error: fold_op no operands to fold\n");
-            return NULL;
-        }
-    }
-
-    /*
-     * we could use a boolean and default case but ironically gcc produces
-     * invalid broken assembly from that operation. clang/tcc get it right,
-     * but interestingly ignore compiling this to a jump-table when I do that,
-     * this happens to be the most efficent method, since you have per-level
-     * granularity on the pointer check happening only for the case you check
-     * it in. Opposed to the default method which would involve a boolean and
-     * pointer check after wards.
-     */
-    #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD)    \
-        case opid##ARGS ARGS_OPID:                          \
-            if ((e = fold_op_##OP ARGS_FOLD)) {             \
-                ++opts_optimizationcount[OPTIM_CONST_FOLD]; \
-            }                                               \
-            return e
-
-    switch(info->id) {
-        fold_op_case(2, ('-', 'P'),      neg,    (fold, a));
-        fold_op_case(2, ('!', 'P'),      not,    (fold, a));
-        fold_op_case(1, ('+'),           add,    (fold, a, b));
-        fold_op_case(1, ('-'),           sub,    (fold, a, b));
-        fold_op_case(1, ('*'),           mul,    (fold, a, b));
-        fold_op_case(1, ('/'),           div,    (fold, a, b));
-        fold_op_case(1, ('%'),           mod,    (fold, a, b));
-        fold_op_case(1, ('|'),           bor,    (fold, a, b));
-        fold_op_case(1, ('&'),           band,   (fold, a, b));
-        fold_op_case(1, ('^'),           xor,    (fold, a, b));
-        fold_op_case(1, ('<'),           ltgt,   (fold, a, b, true));
-        fold_op_case(1, ('>'),           ltgt,   (fold, a, b, false));
-        fold_op_case(2, ('<', '<'),      lshift, (fold, a, b));
-        fold_op_case(2, ('>', '>'),      rshift, (fold, a, b));
-        fold_op_case(2, ('|', '|'),      andor,  (fold, a, b, true));
-        fold_op_case(2, ('&', '&'),      andor,  (fold, a, b, false));
-        fold_op_case(2, ('?', ':'),      tern,   (fold, a, b, c));
-        fold_op_case(2, ('*', '*'),      exp,    (fold, a, b));
-        fold_op_case(3, ('<','=','>'),   lteqgt, (fold, a, b));
-        fold_op_case(2, ('!', '='),      cmp,    (fold, a, b, true));
-        fold_op_case(2, ('=', '='),      cmp,    (fold, a, b, false));
-        fold_op_case(2, ('~', 'P'),      bnot,   (fold, a));
-        fold_op_case(2, ('>', '<'),      cross,  (fold, a, b));
-        fold_op_case(3, ('l', 'e', 'n'), length, (fold, a));
-    }
-    #undef fold_op_case
-    compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator");
-    return NULL;
-}
-
-/*
- * Constant folding for compiler intrinsics, similar approach to operator
- * folding, primarily: individual functions for each intrinsics to fold,
- * and a generic selection function.
- */
-static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) {
-    return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) {
-    return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false);
-}
-static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) {
-    return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false);
-}
-
-
-ast_expression *fold_intrin(fold_t *fold, const char *intrin, ast_expression **arg) {
-    ast_expression *ret = NULL;
-    ast_value      *a   = (ast_value*)arg[0];
-    ast_value      *b   = (ast_value*)arg[1];
-
-    if (!strcmp(intrin, "isfinite")) ret = fold_intrin_isfinite(fold, a);
-    if (!strcmp(intrin, "isinf"))    ret = fold_intrin_isinf(fold, a);
-    if (!strcmp(intrin, "isnan"))    ret = fold_intrin_isnan(fold, a);
-    if (!strcmp(intrin, "isnormal")) ret = fold_intrin_isnormal(fold, a);
-    if (!strcmp(intrin, "signbit"))  ret = fold_intrin_signbit(fold, a);
-    if (!strcmp(intrin, "acosh"))    ret = fold_intirn_acosh(fold, a);
-    if (!strcmp(intrin, "asinh"))    ret = fold_intrin_asinh(fold, a);
-    if (!strcmp(intrin, "atanh"))    ret = fold_intrin_atanh(fold, a);
-    if (!strcmp(intrin, "exp"))      ret = fold_intrin_exp(fold, a);
-    if (!strcmp(intrin, "exp2"))     ret = fold_intrin_exp2(fold, a);
-    if (!strcmp(intrin, "expm1"))    ret = fold_intrin_expm1(fold, a);
-    if (!strcmp(intrin, "mod"))      ret = fold_intrin_mod(fold, a, b);
-    if (!strcmp(intrin, "pow"))      ret = fold_intrin_pow(fold, a, b);
-    if (!strcmp(intrin, "fabs"))     ret = fold_intrin_fabs(fold, a);
-
-    if (ret)
-        ++opts_optimizationcount[OPTIM_CONST_FOLD];
-
-    return ret;
-}
-
-/*
- * These are all the actual constant folding methods that happen in between
- * the AST/IR stage of the compiler , i.e eliminating branches for const
- * expressions, which is the only supported thing so far. We undefine the
- * testing macros here because an ir_value is differant than an ast_value.
- */
-#undef expect
-#undef isfloat
-#undef isstring
-#undef isvector
-#undef fold_immvalue_float
-#undef fold_immvalue_string
-#undef fold_immvalue_vector
-#undef fold_can_1
-#undef fold_can_2
-
-#define isfloat(X)              ((X)->vtype == TYPE_FLOAT)
-/*#define isstring(X)             ((X)->vtype == TYPE_STRING)*/
-/*#define isvector(X)             ((X)->vtype == TYPE_VECTOR)*/
-#define fold_immvalue_float(X)  ((X)->constval.vfloat)
-#define fold_immvalue_vector(X) ((X)->constval.vvec)
-/*#define fold_immvalue_string(X) ((X)->constval.vstring)*/
-#define fold_can_1(X)           ((X)->hasvalue && (X)->cvq == CV_CONST)
-/*#define fold_can_2(X,Y)         (fold_can_1(X) && fold_can_1(Y))*/
-
-static ast_expression *fold_superfluous(ast_expression *left, ast_expression *right, int op) {
-    ast_expression *swapped = NULL; /* using this as bool */
-    ast_value *load;
-
-    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) {
-        swapped = left;
-        left    = right;
-        right   = swapped;
-    }
-
-    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right)))
-        return NULL;
-
-    switch (op) {
-        case INSTR_DIV_F:
-            if (swapped)
-                return NULL;
-        case INSTR_MUL_F:
-            if (fold_immvalue_float(load) == 1.0f) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-
-
-        case INSTR_SUB_F:
-            if (swapped)
-                return NULL;
-        case INSTR_ADD_F:
-            if (fold_immvalue_float(load) == 0.0f) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-
-        case INSTR_MUL_V:
-            if (vec3_cmp(fold_immvalue_vector(load), vec3_create(1, 1, 1))) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-
-        case INSTR_SUB_V:
-            if (swapped)
-                return NULL;
-        case INSTR_ADD_V:
-            if (vec3_cmp(fold_immvalue_vector(load), vec3_create(0, 0, 0))) {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                ast_unref(right);
-                return left;
-            }
-            break;
-    }
-
-    return NULL;
-}
-
-ast_expression *fold_binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) {
-    ast_expression *ret = fold_superfluous(left, right, op);
-    if (ret)
-        return ret;
-    return (ast_expression*)ast_binary_new(ctx, op, left, right);
-}
-
-static GMQCC_INLINE int fold_cond(ir_value *condval, ast_function *func, ast_ifthen *branch) {
-    if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) {
-        ast_expression_codegen *cgen;
-        ir_block               *elide;
-        ir_value               *dummy;
-        bool                    istrue  = (fold_immvalue_float(condval) != 0.0f && branch->on_true);
-        bool                    isfalse = (fold_immvalue_float(condval) == 0.0f && branch->on_false);
-        ast_expression         *path    = (istrue)  ? branch->on_true  :
-                                          (isfalse) ? branch->on_false : NULL;
-        if (!path) {
-            /*
-             * no path to take implies that the evaluation is if(0) and there
-             * is no else block. so eliminate all the code.
-             */
-            ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
-            return true;
-        }
-
-        if (!(elide = ir_function_create_block(ast_ctx(branch), func->ir_func, ast_function_label(func, ((istrue) ? "ontrue" : "onfalse")))))
-            return false;
-        if (!(*(cgen = path->codegen))((ast_expression*)path, func, false, &dummy))
-            return false;
-        if (!ir_block_create_jump(func->curblock, ast_ctx(branch), elide))
-            return false;
-        /*
-         * now the branch has been eliminated and the correct block for the constant evaluation
-         * is expanded into the current block for the function.
-         */
-        func->curblock = elide;
-        ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
-        return true;
-    }
-    return -1; /* nothing done */
-}
-
-int fold_cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) {
-    return fold_cond(condval, func, (ast_ifthen*)branch);
-}
-
-int fold_cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) {
-    return fold_cond(condval, func, branch);
-}
diff --git a/fold.cpp b/fold.cpp
new file mode 100644 (file)
index 0000000..64b20d9
--- /dev/null
+++ b/fold.cpp
@@ -0,0 +1,1668 @@
+#include <string.h>
+#include <math.h>
+
+#include "ast.h"
+#include "parser.h"
+
+#define FOLD_STRING_UNTRANSLATE_HTSIZE 1024
+#define FOLD_STRING_DOTRANSLATE_HTSIZE 1024
+
+/* The options to use for inexact and arithmetic exceptions */
+#define FOLD_ROUNDING SFLOAT_ROUND_NEAREST_EVEN
+#define FOLD_TINYNESS SFLOAT_TBEFORE
+
+/*
+ * Comparing float values is an unsafe operation when the operands to the
+ * comparison are floating point values that are inexact. For instance 1/3 is an
+ * inexact value. The FPU is meant to raise exceptions when these sorts of things
+ * happen, including division by zero, underflows and overflows. The C standard
+ * library provides us with the <fenv.h> header to gain access to the floating-
+ * point environment and lets us set the rounding mode and check for these exceptions.
+ * The problem is the standard C library allows an implementation to leave these
+ * stubbed out and does not require they be implemented. Furthermore, depending
+ * on implementations there is no control over the FPU. This is an IEE 754
+ * conforming implementation in software to compensate.
+ */
+typedef uint32_t sfloat_t;
+
+typedef union {
+    qcfloat_t f;
+    sfloat_t  s;
+} sfloat_cast_t;
+
+/* Exception flags */
+typedef enum {
+    SFLOAT_NOEXCEPT  = 0,
+    SFLOAT_INVALID   = 1,
+    SFLOAT_DIVBYZERO = 4,
+    SFLOAT_OVERFLOW  = 8,
+    SFLOAT_UNDERFLOW = 16,
+    SFLOAT_INEXACT   = 32
+} sfloat_exceptionflags_t;
+
+/* Rounding modes */
+typedef enum {
+    SFLOAT_ROUND_NEAREST_EVEN,
+    SFLOAT_ROUND_DOWN,
+    SFLOAT_ROUND_UP,
+    SFLOAT_ROUND_TO_ZERO
+} sfloat_roundingmode_t;
+
+/* Underflow tininess-detection mode */
+typedef enum {
+    SFLOAT_TAFTER,
+    SFLOAT_TBEFORE
+} sfloat_tdetect_t;
+
+typedef struct {
+    sfloat_roundingmode_t   roundingmode;
+    sfloat_exceptionflags_t exceptionflags;
+    sfloat_tdetect_t        tiny;
+} sfloat_state_t;
+
+/* Counts the number of leading zero bits before the most-significand one bit. */
+#ifdef _MSC_VER
+/* MSVC has an intrinsic for this */
+    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
+        int r = 0;
+        _BitScanForward(&r, x);
+        return r;
+    }
+#   define SFLOAT_CLZ(X, SUB) \
+        (sfloat_clz((X)) - (SUB))
+#elif defined(__GNUC__) || defined(__CLANG__)
+/* Clang and GCC have a builtin for this */
+#   define SFLOAT_CLZ(X, SUB) \
+        (__builtin_clz((X)) - (SUB))
+#else
+/* Native fallback */
+    static GMQCC_INLINE uint32_t sfloat_popcnt(uint32_t x) {
+        x -= ((x >> 1) & 0x55555555);
+        x  = (((x >> 2) & 0x33333333) + (x & 0x33333333));
+        x  = (((x >> 4) + x) & 0x0F0F0F0F);
+        x += x >> 8;
+        x += x >> 16;
+        return x & 0x0000003F;
+    }
+    static GMQCC_INLINE uint32_t sfloat_clz(uint32_t x) {
+        x |= (x >> 1);
+        x |= (x >> 2);
+        x |= (x >> 4);
+        x |= (x >> 8);
+        x |= (x >> 16);
+        return 32 - sfloat_popcnt(x);
+    }
+#   define SFLOAT_CLZ(X, SUB) \
+        (sfloat_clz((X) - (SUB)))
+#endif
+
+/* The value of a NaN */
+#define SFLOAT_NAN 0xFFFFFFFF
+/* Test if NaN */
+#define SFLOAT_ISNAN(A) \
+    (0xFF000000 < (uint32_t)((A) << 1))
+/* Test if signaling NaN */
+#define SFLOAT_ISSNAN(A) \
+    (((((A) >> 22) & 0x1FF) == 0x1FE) && ((A) & 0x003FFFFF))
+/* Raise exception */
+#define SFLOAT_RAISE(STATE, FLAGS) \
+    ((STATE)->exceptionflags = (sfloat_exceptionflags_t)((STATE)->exceptionflags | (FLAGS)))
+/*
+ * Shifts `A' right by the number of bits given in `COUNT'. If any non-zero bits
+ * are shifted off they are forced into the least significand bit of the result
+ * by setting it to one. As a result of this, the value of `COUNT' can be
+ * arbitrarily large; if `COUNT' is greater than 32, the result will be either
+ * zero or one, depending on whether `A' is a zero or non-zero. The result is
+ * stored into the value pointed by `Z'.
+ */
+#define SFLOAT_SHIFT(SIZE, A, COUNT, Z)                                      \
+    *(Z) = ((COUNT) == 0)                                                    \
+        ? 1                                                                  \
+        : (((COUNT) < (SIZE))                                                \
+            ? ((A) >> (COUNT)) | (((A) << ((-(COUNT)) & ((SIZE) - 1))) != 0) \
+            : ((A) != 0))
+
+/* Extract fractional component */
+#define SFLOAT_EXTRACT_FRAC(X) \
+    ((uint32_t)((X) & 0x007FFFFF))
+/* Extract exponent component */
+#define SFLOAT_EXTRACT_EXP(X) \
+    ((int16_t)((X) >> 23) & 0xFF)
+/* Extract sign bit */
+#define SFLOAT_EXTRACT_SIGN(X) \
+    ((X) >> 31)
+/*
+ * Normalizes the subnormal value represented by the denormalized significand
+ * `SA'. The normalized exponent and significand are stored at the locations
+ * pointed by `Z' and `SZ' respectively.
+ */
+#define SFLOAT_SUBNORMALIZE(SA, Z, SZ) \
+    (void)(*(SZ) = (SA) << SFLOAT_CLZ((SA), 8), *(Z) = 1 - SFLOAT_CLZ((SA), 8))
+/*
+ * Packs the sign `SIGN', exponent `EXP' and significand `SIG' into the value
+ * giving the result.
+ *
+ * After the shifting into their proper positions, the fields are added together
+ * to form the result. This means any integer portion of `SIG' will be added
+ * to the exponent. Similarly, because a properly normalized significand will
+ * always have an integer portion equal to one, the exponent input `EXP' should
+ * be one less than the desired result exponent whenever the significant input
+ * `SIG' is a complete, normalized significand.
+ */
+#define SFLOAT_PACK(SIGN, EXP, SIG) \
+    (sfloat_t)((((uint32_t)(SIGN)) << 31) + (((uint32_t)(EXP)) << 23) + (SIG))
+
+/*
+ * Takes two values `a' and `b', one of which is a NaN, and returns the appropriate
+ * NaN result. If either `a' or `b' is a signaling NaN than an invalid exception is
+ * raised.
+ */
+static sfloat_t sfloat_propagate_nan(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    bool isnan_a  = SFLOAT_ISNAN(a);
+    bool issnan_a = SFLOAT_ISSNAN(a);
+    bool isnan_b  = SFLOAT_ISNAN(b);
+    bool issnan_b = SFLOAT_ISSNAN(b);
+
+    a |= 0x00400000;
+    b |= 0x00400000;
+
+    if (issnan_a | issnan_b)
+        SFLOAT_RAISE(state, SFLOAT_INVALID);
+    if (isnan_a)
+        return (issnan_a & isnan_b) ? b : a;
+    return b;
+}
+
+/*
+ * Takes an abstract value having sign `sign_z', exponent `exp_z', and significand
+ * `sig_z' and returns the appropriate value corresponding to the abstract input.
+ *
+ * The abstract value is simply rounded and packed into the format. If the abstract
+ * input cannot be represented exactly an inexact exception is raised. If the
+ * abstract input is too large, the overflow and inexact exceptions are both raised
+ * and an infinity or maximal finite value is returned. If the abstract value is
+ * too small, the value is rounded to a subnormal and the underflow and inexact
+ * exceptions are only raised if the value cannot be represented exactly with
+ * a subnormal.
+ *
+ * The input significand `sig_z' has it's binary point between bits 30 and 29,
+ * this is seven bits to the left of its usual location. The shifted significand
+ * must be normalized or smaller than this. If it's not normalized then the exponent
+ * `exp_z' must be zero; in that case, the result returned is a subnormal number
+ * which must not require rounding. In the more usual case where the significand
+ * is normalized, the exponent must be one less than the *true* exponent.
+ *
+ * The handling of underflow and overflow is otherwise in alignment with IEC/IEEE.
+ */
+static sfloat_t SFLOAT_PACK_round(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
+    sfloat_roundingmode_t mode      = state->roundingmode;
+    bool                  even      = !!(mode == SFLOAT_ROUND_NEAREST_EVEN);
+    unsigned char         increment = 0x40;
+    unsigned char         bits      = sig_z & 0x7F;
+
+    if (!even) {
+        if (mode == SFLOAT_ROUND_TO_ZERO)
+            increment = 0;
+        else {
+            increment = 0x7F;
+            if (sign_z) {
+                if (mode == SFLOAT_ROUND_UP)
+                    increment = 0;
+            } else {
+                if (mode == SFLOAT_ROUND_DOWN)
+                    increment = 0;
+            }
+        }
+    }
+
+    if (0xFD <= (uint16_t)exp_z) {
+        if ((0xFD < exp_z) || ((exp_z == 0xFD) && ((int32_t)(sig_z + increment) < 0))) {
+            SFLOAT_RAISE(state, SFLOAT_OVERFLOW | SFLOAT_INEXACT);
+            return SFLOAT_PACK(sign_z, 0xFF, 0) - (increment == 0);
+        }
+        if (exp_z < 0) {
+            /* Check for underflow */
+            bool tiny = (state->tiny == SFLOAT_TBEFORE) || (exp_z < -1) || (sig_z + increment < 0x80000000);
+            SFLOAT_SHIFT(32, sig_z, -exp_z, &sig_z);
+            exp_z = 0;
+            bits = sig_z & 0x7F;
+            if (tiny && bits)
+                SFLOAT_RAISE(state, SFLOAT_UNDERFLOW);
+        }
+    }
+    if (bits)
+        SFLOAT_RAISE(state, SFLOAT_INEXACT);
+    sig_z = (sig_z + increment) >> 7;
+    sig_z &= ~(((bits ^ 0x40) == 0) & even);
+    if (sig_z == 0)
+        exp_z = 0;
+    return SFLOAT_PACK(sign_z, exp_z, sig_z);
+}
+
+/*
+ * Takes an abstract value having sign `sign_z', exponent `exp_z' and significand
+ * `sig_z' and returns the appropriate value corresponding to the abstract input.
+ * This function is exactly like `PACK_round' except the significand does not have
+ * to be normalized.
+ *
+ * Bit 31 of the significand must be zero and the exponent must be one less than
+ * the *true* exponent.
+ */
+static sfloat_t SFLOAT_PACK_normal(sfloat_state_t *state, bool sign_z, int16_t exp_z, uint32_t sig_z) {
+    unsigned char c = SFLOAT_CLZ(sig_z, 1);
+    return SFLOAT_PACK_round(state, sign_z, exp_z - c, sig_z << c);
+}
+
+/*
+ * Returns the result of adding the absolute values of `a' and `b'. The sign
+ * `sign_z' is ignored if the result is a NaN.
+ */
+static sfloat_t sfloat_add_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
+    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z = 0;
+    int16_t  exp_d = exp_a - exp_b;
+    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 6;
+    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 6;
+    uint32_t sig_z = 0;
+
+    if (0 < exp_d) {
+        if (exp_a == 0xFF)
+            return sig_a ? sfloat_propagate_nan(state, a, b) : a;
+        if (exp_b == 0)
+            --exp_d;
+        else
+            sig_b |= 0x20000000;
+        SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
+        exp_z = exp_a;
+    } else if (exp_d < 0) {
+        if (exp_b == 0xFF)
+            return sig_b ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0xFF, 0);
+        if (exp_a == 0)
+            ++exp_d;
+        else
+            sig_a |= 0x20000000;
+        SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
+        exp_z = exp_b;
+    } else {
+        if (exp_a == 0xFF)
+            return (sig_a | sig_b) ? sfloat_propagate_nan(state, a, b) : a;
+        if (exp_a == 0)
+            return SFLOAT_PACK(sign_z, 0, (sig_a + sig_b) >> 6);
+        sig_z = 0x40000000 + sig_a + sig_b;
+        exp_z = exp_a;
+        goto end;
+    }
+    sig_a |= 0x20000000;
+    sig_z = (sig_a + sig_b) << 1;
+    --exp_z;
+    if ((int32_t)sig_z < 0) {
+        sig_z = sig_a + sig_b;
+        ++exp_z;
+    }
+end:
+    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
+}
+
+/*
+ * Returns the result of subtracting the absolute values of `a' and `b'. If the
+ * sign `sign_z' is one, the difference is negated before being returned. The
+ * sign is ignored if the result is a NaN.
+ */
+static sfloat_t sfloat_sub_impl(sfloat_state_t *state, sfloat_t a, sfloat_t b, bool sign_z) {
+    int16_t  exp_a = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z = 0;
+    int16_t  exp_d = exp_a - exp_b;
+    uint32_t sig_a = SFLOAT_EXTRACT_FRAC(a) << 7;
+    uint32_t sig_b = SFLOAT_EXTRACT_FRAC(b) << 7;
+    uint32_t sig_z = 0;
+
+    if (0 < exp_d) goto exp_greater_a;
+    if (exp_d < 0) goto exp_greater_b;
+
+    if (exp_a == 0xFF) {
+        if (sig_a | sig_b)
+            return sfloat_propagate_nan(state, a, b);
+        SFLOAT_RAISE(state, SFLOAT_INVALID);
+        return SFLOAT_NAN;
+    }
+
+    if (exp_a == 0)
+        exp_a = exp_b = 1;
+
+    if (sig_b < sig_a) goto greater_a;
+    if (sig_a < sig_b) goto greater_b;
+
+    return SFLOAT_PACK(state->roundingmode == SFLOAT_ROUND_DOWN, 0, 0);
+
+exp_greater_b:
+    if (exp_b == 0xFF)
+        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z ^ 1, 0xFF, 0);
+    if (exp_a == 0)
+        ++exp_d;
+    else
+        sig_a |= 0x40000000;
+    SFLOAT_SHIFT(32, sig_a, -exp_d, &sig_a);
+    sig_b |= 0x40000000;
+greater_b:
+    sig_z = sig_b - sig_a;
+    exp_z = exp_b;
+    sign_z ^= 1;
+    goto end;
+
+exp_greater_a:
+    if (exp_a == 0xFF)
+        return (sig_a) ? sfloat_propagate_nan(state, a, b) : a;
+    if (exp_b == 0)
+        --exp_d;
+    else
+        sig_b |= 0x40000000;
+    SFLOAT_SHIFT(32, sig_b, exp_d, &sig_b);
+    sig_a |= 0x40000000;
+greater_a:
+    sig_z = sig_a - sig_b;
+    exp_z = exp_a;
+
+end:
+    --exp_z;
+    return SFLOAT_PACK_normal(state, sign_z, exp_z, sig_z);
+}
+
+static GMQCC_INLINE sfloat_t sfloat_add(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
+    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
+    return (sign_a == sign_b) ? sfloat_add_impl(state, a, b, sign_a)
+                              : sfloat_sub_impl(state, a, b, sign_a);
+}
+
+static GMQCC_INLINE sfloat_t sfloat_sub(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    bool sign_a = SFLOAT_EXTRACT_SIGN(a);
+    bool sign_b = SFLOAT_EXTRACT_SIGN(b);
+    return (sign_a == sign_b) ? sfloat_sub_impl(state, a, b, sign_a)
+                              : sfloat_add_impl(state, a, b, sign_a);
+}
+
+static sfloat_t sfloat_mul(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z   = 0;
+    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
+    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
+    uint32_t sig_z   = 0;
+    uint64_t sig_z64 = 0;
+    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
+    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
+    bool     sign_z  = sign_a ^ sign_b;
+
+    if (exp_a == 0xFF) {
+        if (sig_a || ((exp_b == 0xFF) && sig_b))
+            return sfloat_propagate_nan(state, a, b);
+        if ((exp_b | sig_b) == 0) {
+            SFLOAT_RAISE(state, SFLOAT_INVALID);
+            return SFLOAT_NAN;
+        }
+        return SFLOAT_PACK(sign_z, 0xFF, 0);
+    }
+    if (exp_b == 0xFF) {
+        if (sig_b)
+            return sfloat_propagate_nan(state, a, b);
+        if ((exp_a | sig_a) == 0) {
+            SFLOAT_RAISE(state, SFLOAT_INVALID);
+            return SFLOAT_NAN;
+        }
+        return SFLOAT_PACK(sign_z, 0xFF, 0);
+    }
+    if (exp_a == 0) {
+        if (sig_a == 0)
+            return SFLOAT_PACK(sign_z, 0, 0);
+        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
+    }
+    if (exp_b == 0) {
+        if (sig_b == 0)
+            return SFLOAT_PACK(sign_z, 0, 0);
+        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
+    }
+    exp_z = exp_a + exp_b - 0x7F;
+    sig_a = (sig_a | 0x00800000) << 7;
+    sig_b = (sig_b | 0x00800000) << 8;
+    SFLOAT_SHIFT(64, ((uint64_t)sig_a) * sig_b, 32, &sig_z64);
+    sig_z = sig_z64;
+    if (0 <= (int32_t)(sig_z << 1)) {
+        sig_z <<= 1;
+        --exp_z;
+    }
+    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
+}
+
+static sfloat_t sfloat_div(sfloat_state_t *state, sfloat_t a, sfloat_t b) {
+    int16_t  exp_a   = SFLOAT_EXTRACT_EXP(a);
+    int16_t  exp_b   = SFLOAT_EXTRACT_EXP(b);
+    int16_t  exp_z   = 0;
+    uint32_t sig_a   = SFLOAT_EXTRACT_FRAC(a);
+    uint32_t sig_b   = SFLOAT_EXTRACT_FRAC(b);
+    uint32_t sig_z   = 0;
+    bool     sign_a  = SFLOAT_EXTRACT_SIGN(a);
+    bool     sign_b  = SFLOAT_EXTRACT_SIGN(b);
+    bool     sign_z  = sign_a ^ sign_b;
+
+    if (exp_a == 0xFF) {
+        if (sig_a)
+            return sfloat_propagate_nan(state, a, b);
+        if (exp_b == 0xFF) {
+            if (sig_b)
+                return sfloat_propagate_nan(state, a, b);
+            SFLOAT_RAISE(state, SFLOAT_INVALID);
+            return SFLOAT_NAN;
+        }
+        return SFLOAT_PACK(sign_z, 0xFF, 0);
+    }
+    if (exp_b == 0xFF)
+        return (sig_b) ? sfloat_propagate_nan(state, a, b) : SFLOAT_PACK(sign_z, 0, 0);
+    if (exp_b == 0) {
+        if (sig_b == 0) {
+            if ((exp_a | sig_a) == 0) {
+                SFLOAT_RAISE(state, SFLOAT_INVALID);
+                return SFLOAT_NAN;
+            }
+            SFLOAT_RAISE(state, SFLOAT_DIVBYZERO);
+            return SFLOAT_PACK(sign_z, 0xFF, 0);
+        }
+        SFLOAT_SUBNORMALIZE(sig_b, &exp_b, &sig_b);
+    }
+    if (exp_a == 0) {
+        if (sig_a == 0)
+            return SFLOAT_PACK(sign_z, 0, 0);
+        SFLOAT_SUBNORMALIZE(sig_a, &exp_a, &sig_a);
+    }
+    exp_z = exp_a - exp_b + 0x7D;
+    sig_a = (sig_a | 0x00800000) << 7;
+    sig_b = (sig_b | 0x00800000) << 8;
+    if (sig_b <= (sig_a + sig_a)) {
+        sig_a >>= 1;
+        ++exp_z;
+    }
+    sig_z = (((uint64_t)sig_a) << 32) / sig_b;
+    if ((sig_z & 0x3F) == 0)
+        sig_z |= ((uint64_t)sig_b * sig_z != ((uint64_t)sig_a) << 32);
+    return SFLOAT_PACK_round(state, sign_z, exp_z, sig_z);
+}
+
+static sfloat_t sfloat_neg(sfloat_state_t *state, sfloat_t a) {
+    sfloat_cast_t neg;
+    neg.f = -1;
+    return sfloat_mul(state, a, neg.s);
+}
+
+static GMQCC_INLINE void sfloat_check(lex_ctx_t ctx, sfloat_state_t *state, const char *vec) {
+    /* Exception comes from vector component */
+    if (vec) {
+        if (state->exceptionflags & SFLOAT_DIVBYZERO)
+            compile_error(ctx, "division by zero in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_INVALID)
+            compile_error(ctx, "undefined (inf) in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_OVERFLOW)
+            compile_error(ctx, "arithmetic overflow in `%s' component", vec);
+        if (state->exceptionflags & SFLOAT_UNDERFLOW)
+            compile_error(ctx, "arithmetic underflow in `%s' component", vec);
+            return;
+    }
+    if (state->exceptionflags & SFLOAT_DIVBYZERO)
+        compile_error(ctx, "division by zero");
+    if (state->exceptionflags & SFLOAT_INVALID)
+        compile_error(ctx, "undefined (inf)");
+    if (state->exceptionflags & SFLOAT_OVERFLOW)
+        compile_error(ctx, "arithmetic overflow");
+    if (state->exceptionflags & SFLOAT_UNDERFLOW)
+        compile_error(ctx, "arithmetic underflow");
+}
+
+static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) {
+    state->exceptionflags = SFLOAT_NOEXCEPT;
+    state->roundingmode   = FOLD_ROUNDING;
+    state->tiny           = FOLD_TINYNESS;
+}
+
+/*
+ * There is two stages to constant folding in GMQCC: there is the parse
+ * stage constant folding, where, with the help of the AST, operator
+ * usages can be constant folded. Then there is the constant folding
+ * in the IR for things like eliding if statements, can occur.
+ *
+ * This file is thus, split into two parts.
+ */
+
+#define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
+#define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
+#define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
+#define isarray(X)      (((ast_expression*)(X))->vtype == TYPE_ARRAY)
+#define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
+
+/*
+ * Implementation of basic vector math for vec3_t, for trivial constant
+ * folding.
+ *
+ * TODO: gcc/clang hinting for autovectorization
+ */
+typedef enum {
+    VEC_COMP_X = 1 << 0,
+    VEC_COMP_Y = 1 << 1,
+    VEC_COMP_Z = 1 << 2
+} vec3_comp_t;
+
+typedef struct {
+    sfloat_cast_t x;
+    sfloat_cast_t y;
+    sfloat_cast_t z;
+} vec3_soft_t;
+
+typedef struct {
+    vec3_comp_t    faults;
+    sfloat_state_t state[3];
+} vec3_soft_state_t;
+
+static GMQCC_INLINE vec3_soft_t vec3_soft_convert(vec3_t vec) {
+    vec3_soft_t soft;
+    soft.x.f = vec.x;
+    soft.y.f = vec.y;
+    soft.z.f = vec.z;
+    return soft;
+}
+
+static GMQCC_INLINE bool vec3_soft_exception(vec3_soft_state_t *vstate, size_t index) {
+    sfloat_exceptionflags_t flags = vstate->state[index].exceptionflags;
+    if (flags & SFLOAT_DIVBYZERO) return true;
+    if (flags & SFLOAT_INVALID)   return true;
+    if (flags & SFLOAT_OVERFLOW)  return true;
+    if (flags & SFLOAT_UNDERFLOW) return true;
+    return false;
+}
+
+static GMQCC_INLINE void vec3_soft_eval(vec3_soft_state_t *state,
+                                        sfloat_t         (*callback)(sfloat_state_t *, sfloat_t, sfloat_t),
+                                        vec3_t             a,
+                                        vec3_t             b)
+{
+    vec3_soft_t sa = vec3_soft_convert(a);
+    vec3_soft_t sb = vec3_soft_convert(b);
+    callback(&state->state[0], sa.x.s, sb.x.s);
+    if (vec3_soft_exception(state, 0)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_X);
+    callback(&state->state[1], sa.y.s, sb.y.s);
+    if (vec3_soft_exception(state, 1)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Y);
+    callback(&state->state[2], sa.z.s, sb.z.s);
+    if (vec3_soft_exception(state, 2)) state->faults = (vec3_comp_t)(state->faults | VEC_COMP_Z);
+}
+
+static GMQCC_INLINE void vec3_check_except(vec3_t     a,
+                                           vec3_t     b,
+                                           lex_ctx_t  ctx,
+                                           sfloat_t (*callback)(sfloat_state_t *, sfloat_t, sfloat_t))
+{
+    vec3_soft_state_t state;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        return;
+
+    sfloat_init(&state.state[0]);
+    sfloat_init(&state.state[1]);
+    sfloat_init(&state.state[2]);
+
+    vec3_soft_eval(&state, callback, a, b);
+    if (state.faults & VEC_COMP_X) sfloat_check(ctx, &state.state[0], "x");
+    if (state.faults & VEC_COMP_Y) sfloat_check(ctx, &state.state[1], "y");
+    if (state.faults & VEC_COMP_Z) sfloat_check(ctx, &state.state[2], "z");
+}
+
+static GMQCC_INLINE vec3_t vec3_add(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t out;
+    vec3_check_except(a, b, ctx, &sfloat_add);
+    out.x = a.x + b.x;
+    out.y = a.y + b.y;
+    out.z = a.z + b.z;
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_sub(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t out;
+    vec3_check_except(a, b, ctx, &sfloat_sub);
+    out.x = a.x - b.x;
+    out.y = a.y - b.y;
+    out.z = a.z - b.z;
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_neg(lex_ctx_t ctx, vec3_t a) {
+    vec3_t         out;
+    sfloat_cast_t  v[3];
+    sfloat_state_t s[3];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    v[0].f = a.x;
+    v[1].f = a.y;
+    v[2].f = a.z;
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+
+    sfloat_neg(&s[0], v[0].s);
+    sfloat_neg(&s[1], v[1].s);
+    sfloat_neg(&s[2], v[2].s);
+
+    sfloat_check(ctx, &s[0], NULL);
+    sfloat_check(ctx, &s[1], NULL);
+    sfloat_check(ctx, &s[2], NULL);
+
+end:
+    out.x = -a.x;
+    out.y = -a.y;
+    out.z = -a.z;
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x));
+    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y));
+    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b));
+    out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b));
+    out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x));
+    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y));
+    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b));
+    out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b));
+    out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x));
+    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b.y));
+    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b.z));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) {
+    vec3_t out;
+    out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b));
+    out.y = (qcfloat_t)(((qcint_t)a.y) ^ ((qcint_t)b));
+    out.z = (qcfloat_t)(((qcint_t)a.z) ^ ((qcint_t)b));
+    return out;
+}
+
+static GMQCC_INLINE vec3_t vec3_not(vec3_t a) {
+    vec3_t out;
+    out.x = -1-a.x;
+    out.y = -1-a.y;
+    out.z = -1-a.z;
+    return out;
+}
+
+static GMQCC_INLINE qcfloat_t vec3_mulvv(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_soft_t    sa;
+    vec3_soft_t    sb;
+    sfloat_state_t s[5];
+    sfloat_t       r[5];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa = vec3_soft_convert(a);
+    sb = vec3_soft_convert(b);
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+    sfloat_init(&s[3]);
+    sfloat_init(&s[4]);
+
+    r[0] = sfloat_mul(&s[0], sa.x.s, sb.x.s);
+    r[1] = sfloat_mul(&s[1], sa.y.s, sb.y.s);
+    r[2] = sfloat_mul(&s[2], sa.z.s, sb.z.s);
+    r[3] = sfloat_add(&s[3], r[0],   r[1]);
+    r[4] = sfloat_add(&s[4], r[3],   r[2]);
+
+    sfloat_check(ctx, &s[0], NULL);
+    sfloat_check(ctx, &s[1], NULL);
+    sfloat_check(ctx, &s[2], NULL);
+    sfloat_check(ctx, &s[3], NULL);
+    sfloat_check(ctx, &s[4], NULL);
+
+end:
+    return (a.x * b.x + a.y * b.y + a.z * b.z);
+}
+
+static GMQCC_INLINE vec3_t vec3_mulvf(lex_ctx_t ctx, vec3_t a, qcfloat_t b) {
+    vec3_t         out;
+    vec3_soft_t    sa;
+    sfloat_cast_t  sb;
+    sfloat_state_t s[3];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa   = vec3_soft_convert(a);
+    sb.f = b;
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+
+    sfloat_mul(&s[0], sa.x.s, sb.s);
+    sfloat_mul(&s[1], sa.y.s, sb.s);
+    sfloat_mul(&s[2], sa.z.s, sb.s);
+
+    sfloat_check(ctx, &s[0], "x");
+    sfloat_check(ctx, &s[1], "y");
+    sfloat_check(ctx, &s[2], "z");
+
+end:
+    out.x = a.x * b;
+    out.y = a.y * b;
+    out.z = a.z * b;
+    return out;
+}
+
+static GMQCC_INLINE bool vec3_cmp(vec3_t a, vec3_t b) {
+    return a.x == b.x &&
+           a.y == b.y &&
+           a.z == b.z;
+}
+
+static GMQCC_INLINE vec3_t vec3_create(float x, float y, float z) {
+    vec3_t out;
+    out.x = x;
+    out.y = y;
+    out.z = z;
+    return out;
+}
+
+static GMQCC_INLINE qcfloat_t vec3_notf(vec3_t a) {
+    return (!a.x && !a.y && !a.z);
+}
+
+static GMQCC_INLINE bool vec3_pbool(vec3_t a) {
+    return (a.x || a.y || a.z);
+}
+
+static GMQCC_INLINE vec3_t vec3_cross(lex_ctx_t ctx, vec3_t a, vec3_t b) {
+    vec3_t         out;
+    vec3_soft_t    sa;
+    vec3_soft_t    sb;
+    sfloat_t       r[9];
+    sfloat_state_t s[9];
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto end;
+
+    sa = vec3_soft_convert(a);
+    sb = vec3_soft_convert(b);
+
+    sfloat_init(&s[0]);
+    sfloat_init(&s[1]);
+    sfloat_init(&s[2]);
+    sfloat_init(&s[3]);
+    sfloat_init(&s[4]);
+    sfloat_init(&s[5]);
+    sfloat_init(&s[6]);
+    sfloat_init(&s[7]);
+    sfloat_init(&s[8]);
+
+    r[0] = sfloat_mul(&s[0], sa.y.s, sb.z.s);
+    r[1] = sfloat_mul(&s[1], sa.z.s, sb.y.s);
+    r[2] = sfloat_mul(&s[2], sa.z.s, sb.x.s);
+    r[3] = sfloat_mul(&s[3], sa.x.s, sb.z.s);
+    r[4] = sfloat_mul(&s[4], sa.x.s, sb.y.s);
+    r[5] = sfloat_mul(&s[5], sa.y.s, sb.x.s);
+    r[6] = sfloat_sub(&s[6], r[0],   r[1]);
+    r[7] = sfloat_sub(&s[7], r[2],   r[3]);
+    r[8] = sfloat_sub(&s[8], r[4],   r[5]);
+
+    sfloat_check(ctx, &s[0], NULL);
+    sfloat_check(ctx, &s[1], NULL);
+    sfloat_check(ctx, &s[2], NULL);
+    sfloat_check(ctx, &s[3], NULL);
+    sfloat_check(ctx, &s[4], NULL);
+    sfloat_check(ctx, &s[5], NULL);
+    sfloat_check(ctx, &s[6], "x");
+    sfloat_check(ctx, &s[7], "y");
+    sfloat_check(ctx, &s[8], "z");
+
+end:
+    out.x = a.y * b.z - a.z * b.y;
+    out.y = a.z * b.x - a.x * b.z;
+    out.z = a.x * b.y - a.y * b.x;
+    return out;
+}
+
+static lex_ctx_t fold_ctx(fold_t *fold) {
+    lex_ctx_t ctx;
+    if (fold->parser->lex)
+        return parser_ctx(fold->parser);
+
+    memset(&ctx, 0, sizeof(ctx));
+    return ctx;
+}
+
+static GMQCC_INLINE bool fold_immediate_true(fold_t *fold, ast_value *v) {
+    switch (v->expression.vtype) {
+        case TYPE_FLOAT:
+            return !!v->constval.vfloat;
+        case TYPE_INTEGER:
+            return !!v->constval.vint;
+        case TYPE_VECTOR:
+            if (OPTS_FLAG(CORRECT_LOGIC))
+                return vec3_pbool(v->constval.vvec);
+            return !!(v->constval.vvec.x);
+        case TYPE_STRING:
+            if (!v->constval.vstring)
+                return false;
+            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
+                return true;
+            return !!v->constval.vstring[0];
+        default:
+            compile_error(fold_ctx(fold), "internal error: fold_immediate_true on invalid type");
+            break;
+    }
+    return !!v->constval.vfunc;
+}
+
+/* Handy macros to determine if an ast_value can be constant folded. */
+#define fold_can_1(X)  \
+    (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \
+                ((ast_expression*)(X))->vtype != TYPE_FUNCTION)
+
+#define fold_can_2(X, Y) (fold_can_1(X) && fold_can_1(Y))
+
+#define fold_immvalue_float(E)  ((E)->constval.vfloat)
+#define fold_immvalue_vector(E) ((E)->constval.vvec)
+#define fold_immvalue_string(E) ((E)->constval.vstring)
+
+fold_t *fold_init(parser_t *parser) {
+    fold_t *fold                 = (fold_t*)mem_a(sizeof(fold_t));
+    fold->parser                 = parser;
+    fold->imm_float              = NULL;
+    fold->imm_vector             = NULL;
+    fold->imm_string             = NULL;
+    fold->imm_string_untranslate = util_htnew(FOLD_STRING_UNTRANSLATE_HTSIZE);
+    fold->imm_string_dotranslate = util_htnew(FOLD_STRING_DOTRANSLATE_HTSIZE);
+
+    /*
+     * prime the tables with common constant values at constant
+     * locations.
+     */
+    (void)fold_constgen_float (fold,  0.0f, false);
+    (void)fold_constgen_float (fold,  1.0f, false);
+    (void)fold_constgen_float (fold, -1.0f, false);
+    (void)fold_constgen_float (fold,  2.0f, false);
+
+    (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f));
+    (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f));
+
+    return fold;
+}
+
+bool fold_generate(fold_t *fold, ir_builder *ir) {
+    /* generate globals for immediate folded values */
+    size_t     i;
+    ast_value *cur;
+
+    for (i = 0; i < vec_size(fold->imm_float);   ++i)
+        if (!ast_global_codegen ((cur = fold->imm_float[i]), ir, false)) goto err;
+    for (i = 0; i < vec_size(fold->imm_vector);  ++i)
+        if (!ast_global_codegen((cur = fold->imm_vector[i]), ir, false)) goto err;
+    for (i = 0; i < vec_size(fold->imm_string);  ++i)
+        if (!ast_global_codegen((cur = fold->imm_string[i]), ir, false)) goto err;
+
+    return true;
+
+err:
+    con_out("failed to generate global %s\n", cur->name);
+    ir_builder_delete(ir);
+    return false;
+}
+
+void fold_cleanup(fold_t *fold) {
+    size_t i;
+
+    for (i = 0; i < vec_size(fold->imm_float);  ++i) ast_delete(fold->imm_float[i]);
+    for (i = 0; i < vec_size(fold->imm_vector); ++i) ast_delete(fold->imm_vector[i]);
+    for (i = 0; i < vec_size(fold->imm_string); ++i) ast_delete(fold->imm_string[i]);
+
+    vec_free(fold->imm_float);
+    vec_free(fold->imm_vector);
+    vec_free(fold->imm_string);
+
+    util_htdel(fold->imm_string_untranslate);
+    util_htdel(fold->imm_string_dotranslate);
+
+    mem_d(fold);
+}
+
+ast_expression *fold_constgen_float(fold_t *fold, qcfloat_t value, bool inexact) {
+    ast_value  *out = NULL;
+    size_t      i;
+
+    for (i = 0; i < vec_size(fold->imm_float); i++) {
+        if (!memcmp(&fold->imm_float[i]->constval.vfloat, &value, sizeof(qcfloat_t)))
+            return (ast_expression*)fold->imm_float[i];
+    }
+
+    out                  = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_FLOAT);
+    out->cvq             = CV_CONST;
+    out->hasvalue        = true;
+    out->inexact         = inexact;
+    out->constval.vfloat = value;
+
+    vec_push(fold->imm_float, out);
+
+    return (ast_expression*)out;
+}
+
+ast_expression *fold_constgen_vector(fold_t *fold, vec3_t value) {
+    ast_value *out;
+    size_t     i;
+
+    for (i = 0; i < vec_size(fold->imm_vector); i++) {
+        if (vec3_cmp(fold->imm_vector[i]->constval.vvec, value))
+            return (ast_expression*)fold->imm_vector[i];
+    }
+
+    out                = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_VECTOR);
+    out->cvq           = CV_CONST;
+    out->hasvalue      = true;
+    out->constval.vvec = value;
+
+    vec_push(fold->imm_vector, out);
+
+    return (ast_expression*)out;
+}
+
+ast_expression *fold_constgen_string(fold_t *fold, const char *str, bool translate) {
+    hash_table_t *table = (translate) ? fold->imm_string_untranslate : fold->imm_string_dotranslate;
+    ast_value    *out   = NULL;
+    size_t        hash  = util_hthash(table, str);
+
+    if ((out = (ast_value*)util_htgeth(table, str, hash)))
+        return (ast_expression*)out;
+
+    if (translate) {
+        char name[32];
+        util_snprintf(name, sizeof(name), "dotranslate_%lu", (unsigned long)(fold->parser->translated++));
+        out                    = ast_value_new(parser_ctx(fold->parser), name, TYPE_STRING);
+        out->expression.flags |= AST_FLAG_INCLUDE_DEF; /* def needs to be included for translatables */
+    } else
+        out                    = ast_value_new(fold_ctx(fold), "#IMMEDIATE", TYPE_STRING);
+
+    out->cvq              = CV_CONST;
+    out->hasvalue         = true;
+    out->isimm            = true;
+    out->constval.vstring = parser_strdup(str);
+
+    vec_push(fold->imm_string, out);
+    util_htseth(table, str, hash, out);
+
+    return (ast_expression*)out;
+}
+
+typedef union {
+    void     (*callback)(void);
+    sfloat_t (*binary)(sfloat_state_t *, sfloat_t, sfloat_t);
+    sfloat_t (*unary)(sfloat_state_t *, sfloat_t);
+} float_check_callback_t;
+
+static bool fold_check_except_float_impl(void     (*callback)(void),
+                                         fold_t    *fold,
+                                         ast_value *a,
+                                         ast_value *b)
+{
+    float_check_callback_t call;
+    sfloat_state_t s;
+    sfloat_cast_t ca;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS) && !OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+
+    call.callback = callback;
+    sfloat_init(&s);
+    ca.f = fold_immvalue_float(a);
+    if (b) {
+        sfloat_cast_t cb;
+        cb.f = fold_immvalue_float(b);
+        call.binary(&s, ca.s, cb.s);
+    } else {
+        call.unary(&s, ca.s);
+    }
+
+    if (s.exceptionflags == 0)
+        return false;
+
+    if (!OPTS_FLAG(ARITHMETIC_EXCEPTIONS))
+        goto inexact_possible;
+
+    sfloat_check(fold_ctx(fold), &s, NULL);
+
+inexact_possible:
+    return s.exceptionflags & SFLOAT_INEXACT;
+}
+
+#define fold_check_except_float(CALLBACK, FOLD, A, B) \
+    fold_check_except_float_impl(((void (*)(void))(CALLBACK)), (FOLD), (A), (B))
+
+static bool fold_check_inexact_float(fold_t *fold, ast_value *a, ast_value *b) {
+    lex_ctx_t ctx = fold_ctx(fold);
+    if (!OPTS_WARN(WARN_INEXACT_COMPARES))
+        return false;
+    if (!a->inexact && !b->inexact)
+        return false;
+    return compile_warning(ctx, WARN_INEXACT_COMPARES, "inexact value in comparison");
+}
+
+static GMQCC_INLINE ast_expression *fold_op_mul_vec(fold_t *fold, vec3_t vec, ast_value *sel, const char *set) {
+    qcfloat_t x = (&vec.x)[set[0]-'x'];
+    qcfloat_t y = (&vec.x)[set[1]-'x'];
+    qcfloat_t z = (&vec.x)[set[2]-'x'];
+    if (!y && !z) {
+        ast_expression *out;
+        ++opts_optimizationcount[OPTIM_VECTOR_COMPONENTS];
+        out                        = (ast_expression*)ast_member_new(fold_ctx(fold), (ast_expression*)sel, set[0]-'x', NULL);
+        out->node.keep             = false;
+        ((ast_member*)out)->rvalue = true;
+        if (x != -1.0f)
+            return (ast_expression*)ast_binary_new(fold_ctx(fold), INSTR_MUL_F, fold_constgen_float(fold, x, false), out);
+    }
+    return NULL;
+}
+
+
+static GMQCC_INLINE ast_expression *fold_op_neg(fold_t *fold, ast_value *a) {
+    if (isfloat(a)) {
+        if (fold_can_1(a)) {
+            /* Negation can produce inexact as well */
+            bool inexact = fold_check_except_float(&sfloat_neg, fold, a, NULL);
+            return fold_constgen_float(fold, -fold_immvalue_float(a), inexact);
+        }
+    } else if (isvector(a)) {
+        if (fold_can_1(a))
+            return fold_constgen_vector(fold, vec3_neg(fold_ctx(fold), fold_immvalue_vector(a)));
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_not(fold_t *fold, ast_value *a) {
+    if (isfloat(a)) {
+        if (fold_can_1(a))
+            return fold_constgen_float(fold, !fold_immvalue_float(a), false);
+    } else if (isvector(a)) {
+        if (fold_can_1(a))
+            return fold_constgen_float(fold, vec3_notf(fold_immvalue_vector(a)), false);
+    } else if (isstring(a)) {
+        if (fold_can_1(a)) {
+            if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
+                return fold_constgen_float(fold, !fold_immvalue_string(a), false);
+            else
+                return fold_constgen_float(fold, !fold_immvalue_string(a) || !*fold_immvalue_string(a), false);
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_add(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b)) {
+            bool inexact = fold_check_except_float(&sfloat_add, fold, a, b);
+            return fold_constgen_float(fold, fold_immvalue_float(a) + fold_immvalue_float(b), inexact);
+        }
+    } else if (isvector(a)) {
+        if (fold_can_2(a, b))
+            return fold_constgen_vector(fold, vec3_add(fold_ctx(fold),
+                                                       fold_immvalue_vector(a),
+                                                       fold_immvalue_vector(b)));
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_sub(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b)) {
+            bool inexact = fold_check_except_float(&sfloat_sub, fold, a, b);
+            return fold_constgen_float(fold, fold_immvalue_float(a) - fold_immvalue_float(b), inexact);
+        }
+    } else if (isvector(a)) {
+        if (fold_can_2(a, b))
+            return fold_constgen_vector(fold, vec3_sub(fold_ctx(fold),
+                                                       fold_immvalue_vector(a),
+                                                       fold_immvalue_vector(b)));
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_mul(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (isvector(b)) {
+            if (fold_can_2(a, b))
+                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(b), fold_immvalue_float(a)));
+        } else {
+            if (fold_can_2(a, b)) {
+                bool inexact = fold_check_except_float(&sfloat_mul, fold, a, b);
+                return fold_constgen_float(fold, fold_immvalue_float(a) * fold_immvalue_float(b), inexact);
+            }
+        }
+    } else if (isvector(a)) {
+        if (isfloat(b)) {
+            if (fold_can_2(a, b))
+                return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_float(b)));
+        } else {
+            if (fold_can_2(a, b)) {
+                return fold_constgen_float(fold, vec3_mulvv(fold_ctx(fold), fold_immvalue_vector(a), fold_immvalue_vector(b)), false);
+            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(a)) {
+                ast_expression *out;
+                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "xyz"))) return out;
+                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "yxz"))) return out;
+                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(a), b, "zxy"))) return out;
+            } else if (OPTS_OPTIMIZATION(OPTIM_VECTOR_COMPONENTS) && fold_can_1(b)) {
+                ast_expression *out;
+                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "xyz"))) return out;
+                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "yxz"))) return out;
+                if ((out = fold_op_mul_vec(fold, fold_immvalue_vector(b), a, "zxy"))) return out;
+            }
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_div(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b)) {
+            bool inexact = fold_check_except_float(&sfloat_div, fold, a, b);
+            return fold_constgen_float(fold, fold_immvalue_float(a) / fold_immvalue_float(b), inexact);
+        } else if (fold_can_1(b)) {
+            return (ast_expression*)ast_binary_new(
+                fold_ctx(fold),
+                INSTR_MUL_F,
+                (ast_expression*)a,
+                fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false)
+            );
+        }
+    } else if (isvector(a)) {
+        if (fold_can_2(a, b)) {
+            return fold_constgen_vector(fold, vec3_mulvf(fold_ctx(fold), fold_immvalue_vector(a), 1.0f / fold_immvalue_float(b)));
+        } else {
+            return (ast_expression*)ast_binary_new(
+                fold_ctx(fold),
+                INSTR_MUL_VF,
+                (ast_expression*)a,
+                (fold_can_1(b))
+                    ? (ast_expression*)fold_constgen_float(fold, 1.0f / fold_immvalue_float(b), false)
+                    : (ast_expression*)ast_binary_new(
+                                            fold_ctx(fold),
+                                            INSTR_DIV_F,
+                                            (ast_expression*)fold->imm_float[1],
+                                            (ast_expression*)b
+                    )
+            );
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_value *b) {
+    return (fold_can_2(a, b))
+                ? fold_constgen_float(fold, fmod(fold_immvalue_float(a), fold_immvalue_float(b)), false)
+                : NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b))
+            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b))), false);
+    } else {
+        if (isvector(b)) {
+            if (fold_can_2(a, b))
+                return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b)));
+        } else {
+            if (fold_can_2(a, b))
+                return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b))
+            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b))), false);
+    } else {
+        if (isvector(b)) {
+            if (fold_can_2(a, b))
+                return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b)));
+        } else {
+            if (fold_can_2(a, b))
+                return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_xor(fold_t *fold, ast_value *a, ast_value *b) {
+    if (isfloat(a)) {
+        if (fold_can_2(a, b))
+            return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) ^ ((qcint_t)fold_immvalue_float(b))), false);
+    } else {
+        if (fold_can_2(a, b)) {
+            if (isvector(b))
+                return fold_constgen_vector(fold, vec3_xor(fold_immvalue_vector(a), fold_immvalue_vector(b)));
+            else
+                return fold_constgen_vector(fold, vec3_xorvf(fold_immvalue_vector(a), fold_immvalue_float(b)));
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_lshift(fold_t *fold, ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b) && isfloats(a, b))
+        return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) * powf(2.0f, fold_immvalue_float(b))), false);
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_rshift(fold_t *fold, ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b) && isfloats(a, b))
+        return fold_constgen_float(fold, (qcfloat_t)floorf(fold_immvalue_float(a) / powf(2.0f, fold_immvalue_float(b))), false);
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_andor(fold_t *fold, ast_value *a, ast_value *b, float expr) {
+    if (fold_can_2(a, b)) {
+        if (OPTS_FLAG(PERL_LOGIC)) {
+            if (expr)
+                return (fold_immediate_true(fold, a)) ? (ast_expression*)a : (ast_expression*)b;
+            else
+                return (fold_immediate_true(fold, a)) ? (ast_expression*)b : (ast_expression*)a;
+        } else {
+            return fold_constgen_float (
+                fold,
+                ((expr) ? (fold_immediate_true(fold, a) || fold_immediate_true(fold, b))
+                        : (fold_immediate_true(fold, a) && fold_immediate_true(fold, b)))
+                            ? 1
+                            : 0,
+                false
+            );
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_tern(fold_t *fold, ast_value *a, ast_value *b, ast_value *c) {
+    if (fold_can_1(a)) {
+        return fold_immediate_true(fold, a)
+                    ? (ast_expression*)b
+                    : (ast_expression*)c;
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_exp(fold_t *fold, ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b))
+        return fold_constgen_float(fold, (qcfloat_t)powf(fold_immvalue_float(a), fold_immvalue_float(b)), false);
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_lteqgt(fold_t *fold, ast_value *a, ast_value *b) {
+    if (fold_can_2(a,b)) {
+        fold_check_inexact_float(fold, a, b);
+        if (fold_immvalue_float(a) <  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[2];
+        if (fold_immvalue_float(a) == fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[0];
+        if (fold_immvalue_float(a) >  fold_immvalue_float(b)) return (ast_expression*)fold->imm_float[1];
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_ltgt(fold_t *fold, ast_value *a, ast_value *b, bool lt) {
+    if (fold_can_2(a, b)) {
+        fold_check_inexact_float(fold, a, b);
+        return (lt) ? (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) < fold_immvalue_float(b))]
+                    : (ast_expression*)fold->imm_float[!!(fold_immvalue_float(a) > fold_immvalue_float(b))];
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_value *b, bool ne) {
+    if (fold_can_2(a, b)) {
+        if (isfloat(a) && isfloat(b)) {
+            float la = fold_immvalue_float(a);
+            float lb = fold_immvalue_float(b);
+            fold_check_inexact_float(fold, a, b);
+            return (ast_expression*)fold->imm_float[!(ne ? la == lb : la != lb)];
+        } if (isvector(a) && isvector(b)) {
+            vec3_t la = fold_immvalue_vector(a);
+            vec3_t lb = fold_immvalue_vector(b);
+            return (ast_expression*)fold->imm_float[!(ne ? vec3_cmp(la, lb) : !vec3_cmp(la, lb))];
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) {
+    if (isfloat(a)) {
+        if (fold_can_1(a))
+            return fold_constgen_float(fold, -1-fold_immvalue_float(a), false);
+    } else {
+        if (isvector(a)) {
+            if (fold_can_1(a))
+                return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a)));
+        }
+    }
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, ast_value *b) {
+    if (fold_can_2(a, b))
+        return fold_constgen_vector(fold, vec3_cross(fold_ctx(fold),
+                                                     fold_immvalue_vector(a),
+                                                     fold_immvalue_vector(b)));
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) {
+    if (fold_can_1(a) && isstring(a))
+        return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false);
+    if (isarray(a))
+        return fold_constgen_float(fold, vec_size(a->initlist), false);
+    return NULL;
+}
+
+ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) {
+    ast_value      *a = (ast_value*)opexprs[0];
+    ast_value      *b = (ast_value*)opexprs[1];
+    ast_value      *c = (ast_value*)opexprs[2];
+    ast_expression *e = NULL;
+
+    /* can a fold operation be applied to this operator usage? */
+    if (!info->folds)
+        return NULL;
+
+    switch(info->operands) {
+        case 3: if(!c) return NULL;
+        case 2: if(!b) return NULL;
+        case 1:
+        if(!a) {
+            compile_error(fold_ctx(fold), "internal error: fold_op no operands to fold\n");
+            return NULL;
+        }
+    }
+
+    /*
+     * we could use a boolean and default case but ironically gcc produces
+     * invalid broken assembly from that operation. clang/tcc get it right,
+     * but interestingly ignore compiling this to a jump-table when I do that,
+     * this happens to be the most efficent method, since you have per-level
+     * granularity on the pointer check happening only for the case you check
+     * it in. Opposed to the default method which would involve a boolean and
+     * pointer check after wards.
+     */
+    #define fold_op_case(ARGS, ARGS_OPID, OP, ARGS_FOLD)    \
+        case opid##ARGS ARGS_OPID:                          \
+            if ((e = fold_op_##OP ARGS_FOLD)) {             \
+                ++opts_optimizationcount[OPTIM_CONST_FOLD]; \
+            }                                               \
+            return e
+
+    switch(info->id) {
+        fold_op_case(2, ('-', 'P'),      neg,    (fold, a));
+        fold_op_case(2, ('!', 'P'),      not,    (fold, a));
+        fold_op_case(1, ('+'),           add,    (fold, a, b));
+        fold_op_case(1, ('-'),           sub,    (fold, a, b));
+        fold_op_case(1, ('*'),           mul,    (fold, a, b));
+        fold_op_case(1, ('/'),           div,    (fold, a, b));
+        fold_op_case(1, ('%'),           mod,    (fold, a, b));
+        fold_op_case(1, ('|'),           bor,    (fold, a, b));
+        fold_op_case(1, ('&'),           band,   (fold, a, b));
+        fold_op_case(1, ('^'),           xor,    (fold, a, b));
+        fold_op_case(1, ('<'),           ltgt,   (fold, a, b, true));
+        fold_op_case(1, ('>'),           ltgt,   (fold, a, b, false));
+        fold_op_case(2, ('<', '<'),      lshift, (fold, a, b));
+        fold_op_case(2, ('>', '>'),      rshift, (fold, a, b));
+        fold_op_case(2, ('|', '|'),      andor,  (fold, a, b, true));
+        fold_op_case(2, ('&', '&'),      andor,  (fold, a, b, false));
+        fold_op_case(2, ('?', ':'),      tern,   (fold, a, b, c));
+        fold_op_case(2, ('*', '*'),      exp,    (fold, a, b));
+        fold_op_case(3, ('<','=','>'),   lteqgt, (fold, a, b));
+        fold_op_case(2, ('!', '='),      cmp,    (fold, a, b, true));
+        fold_op_case(2, ('=', '='),      cmp,    (fold, a, b, false));
+        fold_op_case(2, ('~', 'P'),      bnot,   (fold, a));
+        fold_op_case(2, ('>', '<'),      cross,  (fold, a, b));
+        fold_op_case(3, ('l', 'e', 'n'), length, (fold, a));
+    }
+    #undef fold_op_case
+    compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator");
+    return NULL;
+}
+
+/*
+ * Constant folding for compiler intrinsics, similar approach to operator
+ * folding, primarily: individual functions for each intrinsics to fold,
+ * and a generic selection function.
+ */
+static GMQCC_INLINE ast_expression *fold_intrin_isfinite(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, isfinite(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_isinf(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, isinf(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_isnan(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, isnan(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_isnormal(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, isnormal(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_signbit(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, signbit(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intirn_acosh(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, acoshf(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_asinh(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, asinhf(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_atanh(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, (float)atanh(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_exp(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, expf(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_exp2(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, exp2f(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_expm1(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, expm1f(fold_immvalue_float(a)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_mod(fold_t *fold, ast_value *lhs, ast_value *rhs) {
+    return fold_constgen_float(fold, fmodf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_pow(fold_t *fold, ast_value *lhs, ast_value *rhs) {
+    return fold_constgen_float(fold, powf(fold_immvalue_float(lhs), fold_immvalue_float(rhs)), false);
+}
+static GMQCC_INLINE ast_expression *fold_intrin_fabs(fold_t *fold, ast_value *a) {
+    return fold_constgen_float(fold, fabsf(fold_immvalue_float(a)), false);
+}
+
+
+ast_expression *fold_intrin(fold_t *fold, const char *intrin, ast_expression **arg) {
+    ast_expression *ret = NULL;
+    ast_value      *a   = (ast_value*)arg[0];
+    ast_value      *b   = (ast_value*)arg[1];
+
+    if (!strcmp(intrin, "isfinite")) ret = fold_intrin_isfinite(fold, a);
+    if (!strcmp(intrin, "isinf"))    ret = fold_intrin_isinf(fold, a);
+    if (!strcmp(intrin, "isnan"))    ret = fold_intrin_isnan(fold, a);
+    if (!strcmp(intrin, "isnormal")) ret = fold_intrin_isnormal(fold, a);
+    if (!strcmp(intrin, "signbit"))  ret = fold_intrin_signbit(fold, a);
+    if (!strcmp(intrin, "acosh"))    ret = fold_intirn_acosh(fold, a);
+    if (!strcmp(intrin, "asinh"))    ret = fold_intrin_asinh(fold, a);
+    if (!strcmp(intrin, "atanh"))    ret = fold_intrin_atanh(fold, a);
+    if (!strcmp(intrin, "exp"))      ret = fold_intrin_exp(fold, a);
+    if (!strcmp(intrin, "exp2"))     ret = fold_intrin_exp2(fold, a);
+    if (!strcmp(intrin, "expm1"))    ret = fold_intrin_expm1(fold, a);
+    if (!strcmp(intrin, "mod"))      ret = fold_intrin_mod(fold, a, b);
+    if (!strcmp(intrin, "pow"))      ret = fold_intrin_pow(fold, a, b);
+    if (!strcmp(intrin, "fabs"))     ret = fold_intrin_fabs(fold, a);
+
+    if (ret)
+        ++opts_optimizationcount[OPTIM_CONST_FOLD];
+
+    return ret;
+}
+
+/*
+ * These are all the actual constant folding methods that happen in between
+ * the AST/IR stage of the compiler , i.e eliminating branches for const
+ * expressions, which is the only supported thing so far. We undefine the
+ * testing macros here because an ir_value is differant than an ast_value.
+ */
+#undef expect
+#undef isfloat
+#undef isstring
+#undef isvector
+#undef fold_immvalue_float
+#undef fold_immvalue_string
+#undef fold_immvalue_vector
+#undef fold_can_1
+#undef fold_can_2
+
+#define isfloat(X)              ((X)->vtype == TYPE_FLOAT)
+/*#define isstring(X)             ((X)->vtype == TYPE_STRING)*/
+/*#define isvector(X)             ((X)->vtype == TYPE_VECTOR)*/
+#define fold_immvalue_float(X)  ((X)->constval.vfloat)
+#define fold_immvalue_vector(X) ((X)->constval.vvec)
+/*#define fold_immvalue_string(X) ((X)->constval.vstring)*/
+#define fold_can_1(X)           ((X)->hasvalue && (X)->cvq == CV_CONST)
+/*#define fold_can_2(X,Y)         (fold_can_1(X) && fold_can_1(Y))*/
+
+static ast_expression *fold_superfluous(ast_expression *left, ast_expression *right, int op) {
+    ast_expression *swapped = NULL; /* using this as bool */
+    ast_value *load;
+
+    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right))) {
+        swapped = left;
+        left    = right;
+        right   = swapped;
+    }
+
+    if (!ast_istype(right, ast_value) || !fold_can_1((load = (ast_value*)right)))
+        return NULL;
+
+    switch (op) {
+        case INSTR_DIV_F:
+            if (swapped)
+                return NULL;
+        case INSTR_MUL_F:
+            if (fold_immvalue_float(load) == 1.0f) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+
+
+        case INSTR_SUB_F:
+            if (swapped)
+                return NULL;
+        case INSTR_ADD_F:
+            if (fold_immvalue_float(load) == 0.0f) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+
+        case INSTR_MUL_V:
+            if (vec3_cmp(fold_immvalue_vector(load), vec3_create(1, 1, 1))) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+
+        case INSTR_SUB_V:
+            if (swapped)
+                return NULL;
+        case INSTR_ADD_V:
+            if (vec3_cmp(fold_immvalue_vector(load), vec3_create(0, 0, 0))) {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                ast_unref(right);
+                return left;
+            }
+            break;
+    }
+
+    return NULL;
+}
+
+ast_expression *fold_binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right) {
+    ast_expression *ret = fold_superfluous(left, right, op);
+    if (ret)
+        return ret;
+    return (ast_expression*)ast_binary_new(ctx, op, left, right);
+}
+
+static GMQCC_INLINE int fold_cond(ir_value *condval, ast_function *func, ast_ifthen *branch) {
+    if (isfloat(condval) && fold_can_1(condval) && OPTS_OPTIMIZATION(OPTIM_CONST_FOLD_DCE)) {
+        ast_expression_codegen *cgen;
+        ir_block               *elide;
+        ir_value               *dummy;
+        bool                    istrue  = (fold_immvalue_float(condval) != 0.0f && branch->on_true);
+        bool                    isfalse = (fold_immvalue_float(condval) == 0.0f && branch->on_false);
+        ast_expression         *path    = (istrue)  ? branch->on_true  :
+                                          (isfalse) ? branch->on_false : NULL;
+        if (!path) {
+            /*
+             * no path to take implies that the evaluation is if(0) and there
+             * is no else block. so eliminate all the code.
+             */
+            ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
+            return true;
+        }
+
+        if (!(elide = ir_function_create_block(ast_ctx(branch), func->ir_func, ast_function_label(func, ((istrue) ? "ontrue" : "onfalse")))))
+            return false;
+        if (!(*(cgen = path->codegen))((ast_expression*)path, func, false, &dummy))
+            return false;
+        if (!ir_block_create_jump(func->curblock, ast_ctx(branch), elide))
+            return false;
+        /*
+         * now the branch has been eliminated and the correct block for the constant evaluation
+         * is expanded into the current block for the function.
+         */
+        func->curblock = elide;
+        ++opts_optimizationcount[OPTIM_CONST_FOLD_DCE];
+        return true;
+    }
+    return -1; /* nothing done */
+}
+
+int fold_cond_ternary(ir_value *condval, ast_function *func, ast_ternary *branch) {
+    return fold_cond(condval, func, (ast_ifthen*)branch);
+}
+
+int fold_cond_ifthen(ir_value *condval, ast_function *func, ast_ifthen *branch) {
+    return fold_cond(condval, func, branch);
+}
diff --git a/ftepp.c b/ftepp.c
deleted file mode 100644 (file)
index 1c80d9e..0000000
--- a/ftepp.c
+++ /dev/null
@@ -1,1954 +0,0 @@
-#include <string.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-
-#include "gmqcc.h"
-#include "lexer.h"
-
-#define HT_MACROS 1024
-
-typedef struct {
-    bool on;
-    bool was_on;
-    bool had_else;
-} ppcondition;
-
-typedef struct {
-    int   token;
-    char *value;
-    /* a copy from the lexer */
-    union {
-        vec3_t v;
-        int    i;
-        double f;
-        int    t; /* type */
-    } constval;
-} pptoken;
-
-typedef struct {
-    lex_ctx_t ctx;
-
-    char   *name;
-    char  **params;
-    /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
-    bool    has_params;
-    bool    variadic;
-
-    pptoken **output;
-} ppmacro;
-
-typedef struct ftepp_s {
-    lex_file    *lex;
-    int          token;
-    unsigned int errors;
-
-    bool         output_on;
-    ppcondition *conditions;
-    /*ppmacro    **macros;*/
-    ht           macros;  /* hashtable<string, ppmacro*> */
-    char        *output_string;
-
-    char        *itemname;
-    char        *includename;
-    bool         in_macro;
-
-    uint32_t predef_countval;
-    uint32_t predef_randval;
-} ftepp_t;
-
-/* __DATE__ */
-static char *ftepp_predef_date(ftepp_t *context) {
-    const struct tm *itime = NULL;
-    char            *value = (char*)mem_a(82);
-    time_t           rtime;
-
-    (void)context;
-
-    time (&rtime);
-    itime = util_localtime(&rtime);
-    strftime(value, 82, "\"%b %d %Y\"", itime);
-
-    return value;
-}
-
-/* __TIME__ */
-static char *ftepp_predef_time(ftepp_t *context) {
-    const struct tm *itime = NULL;
-    char            *value = (char*)mem_a(82);
-    time_t           rtime;
-
-    (void)context;
-
-    time (&rtime);
-    itime = util_localtime(&rtime);
-    strftime(value, 82, "\"%X\"", itime);
-
-    return value;
-}
-
-/* __LINE__ */
-static char *ftepp_predef_line(ftepp_t *context) {
-    char *value;
-
-    util_asprintf(&value, "%d", (int)context->lex->line);
-    return value;
-}
-/* __FILE__ */
-static char *ftepp_predef_file(ftepp_t *context) {
-    size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */
-    char  *value  = (char*)mem_a(length);
-
-    util_snprintf(value, length, "\"%s\"", context->lex->name);
-    return value;
-}
-/* __COUNTER_LAST__ */
-static char *ftepp_predef_counterlast(ftepp_t *context) {
-    char *value;
-    util_asprintf(&value, "%u", context->predef_countval);
-    return value;
-}
-/* __COUNTER__ */
-static char *ftepp_predef_counter(ftepp_t *context) {
-    char *value;
-
-    context->predef_countval ++;
-    util_asprintf(&value, "%u", context->predef_countval);
-
-    return value;
-}
-/* __RANDOM__ */
-static char *ftepp_predef_random(ftepp_t *context) {
-    char *value;
-
-    context->predef_randval = (util_rand() % 0xFF) + 1;
-    util_asprintf(&value, "%u", context->predef_randval);
-    return value;
-}
-/* __RANDOM_LAST__ */
-static char *ftepp_predef_randomlast(ftepp_t *context) {
-    char *value;
-
-    util_asprintf(&value, "%u", context->predef_randval);
-    return value;
-}
-/* __TIMESTAMP__ */
-static char *ftepp_predef_timestamp(ftepp_t *context) {
-    struct stat finfo;
-    const char *find;
-    char       *value;
-    size_t      size;
-
-    if (stat(context->lex->name, &finfo))
-        return util_strdup("\"<failed to determine timestamp>\"");
-
-    find = util_ctime(&finfo.st_mtime);
-    value = (char*)mem_a(strlen(find) + 1);
-    memcpy(&value[1], find, (size = strlen(find)) - 1);
-
-    value[0]    = '"';
-    value[size] = '"';
-
-    return value;
-}
-
-typedef struct {
-    const char   *name;
-    char       *(*func)(ftepp_t *);
-} ftepp_predef_t;
-
-static const ftepp_predef_t ftepp_predefs[] = {
-    { "__LINE__",         &ftepp_predef_line        },
-    { "__FILE__",         &ftepp_predef_file        },
-    { "__COUNTER__",      &ftepp_predef_counter     },
-    { "__COUNTER_LAST__", &ftepp_predef_counterlast },
-    { "__RANDOM__",       &ftepp_predef_random      },
-    { "__RANDOM_LAST__",  &ftepp_predef_randomlast  },
-    { "__DATE__",         &ftepp_predef_date        },
-    { "__TIME__",         &ftepp_predef_time        },
-    { "__TIME_STAMP__",   &ftepp_predef_timestamp   }
-};
-
-static GMQCC_INLINE size_t ftepp_predef_index(const char *name) {
-    /* no hashtable here, we simply check for one to exist the naive way */
-    size_t i;
-    for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++)
-        if (!strcmp(ftepp_predefs[i-1].name, name))
-            return i;
-    return 0;
-}
-
-bool ftepp_predef_exists(const char *name);
-bool ftepp_predef_exists(const char *name) {
-    return ftepp_predef_index(name) != 0;
-}
-
-/* singleton because we're allowed */
-static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) {
-    size_t i = ftepp_predef_index(name);
-    return (i != 0) ? ftepp_predefs[i-1].func : NULL;
-}
-
-#define ftepp_tokval(f) ((f)->lex->tok.value)
-#define ftepp_ctx(f)    ((f)->lex->tok.ctx)
-
-static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...)
-{
-    va_list ap;
-
-    ftepp->errors++;
-
-    va_start(ap, fmt);
-    con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap);
-    va_end(ap);
-}
-
-static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
-{
-    va_list ap;
-
-    ftepp->errors++;
-
-    va_start(ap, fmt);
-    con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap);
-    va_end(ap);
-}
-
-static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
-{
-    bool    r;
-    va_list ap;
-
-    va_start(ap, fmt);
-    r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-static pptoken *pptoken_make(ftepp_t *ftepp)
-{
-    pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
-    token->token = ftepp->token;
-    token->value = util_strdup(ftepp_tokval(ftepp));
-    memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
-    return token;
-}
-
-static GMQCC_INLINE void pptoken_delete(pptoken *self)
-{
-    mem_d(self->value);
-    mem_d(self);
-}
-
-static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name)
-{
-    ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
-
-    (void)ctx;
-    memset(macro, 0, sizeof(*macro));
-    macro->name = util_strdup(name);
-    return macro;
-}
-
-static void ppmacro_delete(ppmacro *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->params); ++i)
-        mem_d(self->params[i]);
-    vec_free(self->params);
-    for (i = 0; i < vec_size(self->output); ++i)
-        pptoken_delete(self->output[i]);
-    vec_free(self->output);
-    mem_d(self->name);
-    mem_d(self);
-}
-
-static ftepp_t* ftepp_new(void)
-{
-    ftepp_t *ftepp;
-
-    ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
-    memset(ftepp, 0, sizeof(*ftepp));
-
-    ftepp->macros          = util_htnew(HT_MACROS);
-    ftepp->output_on       = true;
-    ftepp->predef_countval = 0;
-    ftepp->predef_randval  = 0;
-
-    return ftepp;
-}
-
-static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self)
-{
-    vec_free(self->output_string);
-}
-
-static void ftepp_delete(ftepp_t *self)
-{
-    ftepp_flush_do(self);
-    if (self->itemname)
-        mem_d(self->itemname);
-    if (self->includename)
-        vec_free(self->includename);
-
-    util_htrem(self->macros, (void (*)(void*))&ppmacro_delete);
-
-    vec_free(self->conditions);
-    if (self->lex)
-        lex_close(self->lex);
-    mem_d(self);
-}
-
-static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
-{
-    if (ignore_cond || ftepp->output_on)
-    {
-        size_t len;
-        char  *data;
-        len = strlen(str);
-        data = vec_add(ftepp->output_string, len);
-        memcpy(data, str, len);
-    }
-}
-
-static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp)
-{
-    size_t i;
-    ftepp->output_on = true;
-    for (i = 0; i < vec_size(ftepp->conditions); ++i)
-        ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
-}
-
-static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
-{
-    return (ppmacro*)util_htget(ftepp->macros, name);
-}
-
-static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
-{
-    util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete);
-}
-
-static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp)
-{
-    return (ftepp->token = lex_do(ftepp->lex));
-}
-
-/* Important: this does not skip newlines! */
-static bool ftepp_skipspace(ftepp_t *ftepp)
-{
-    if (ftepp->token != TOKEN_WHITE)
-        return true;
-    while (ftepp_next(ftepp) == TOKEN_WHITE) {}
-    if (ftepp->token >= TOKEN_EOF) {
-        ftepp_error(ftepp, "unexpected end of preprocessor directive");
-        return false;
-    }
-    return true;
-}
-
-/* this one skips EOLs as well */
-static bool ftepp_skipallwhite(ftepp_t *ftepp)
-{
-    if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
-        return true;
-    do {
-        ftepp_next(ftepp);
-    } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
-    if (ftepp->token >= TOKEN_EOF) {
-        ftepp_error(ftepp, "unexpected end of preprocessor directive");
-        return false;
-    }
-    return true;
-}
-
-/**
- * The huge macro parsing code...
- */
-static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
-{
-    do {
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-        if (ftepp->token == ')')
-            break;
-        switch (ftepp->token) {
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-            case TOKEN_KEYWORD:
-                vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
-                break;
-            case TOKEN_DOTS:
-                macro->variadic = true;
-                break;
-            default:
-                ftepp_error(ftepp, "unexpected token in parameter list");
-                return false;
-        }
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-        if (macro->variadic && ftepp->token != ')') {
-            ftepp_error(ftepp, "cannot have parameters after the variadic parameters");
-            return false;
-        }
-    } while (ftepp->token == ',');
-
-    if (ftepp->token != ')') {
-        ftepp_error(ftepp, "expected closing paren after macro parameter list");
-        return false;
-    }
-    ftepp_next(ftepp);
-    /* skipspace happens in ftepp_define */
-    return true;
-}
-
-static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
-{
-    pptoken *ptok;
-    while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
-        bool   subscript = false;
-        size_t index     = 0;
-        if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) {
-            subscript = !!(ftepp_next(ftepp) == '#');
-
-            if (subscript && ftepp_next(ftepp) != '#') {
-                ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting");
-                return false;
-            } else if (subscript) {
-                if (ftepp_next(ftepp) == '[') {
-                    if (ftepp_next(ftepp) != TOKEN_INTCONST) {
-                        ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript");
-                        return false;
-                    }
-
-                    index = (int)strtol(ftepp_tokval(ftepp), NULL, 10);
-
-                    if (ftepp_next(ftepp) != ']') {
-                        ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript");
-                        return false;
-                    }
-
-                    /*
-                     * mark it as an array to be handled later as such and not
-                     * as traditional __VA_ARGS__
-                     */
-                    ftepp->token = TOKEN_VA_ARGS_ARRAY;
-                    ptok = pptoken_make(ftepp);
-                    ptok->constval.i = index;
-                    vec_push(macro->output, ptok);
-                    ftepp_next(ftepp);
-                } else {
-                    ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__");
-                    return false;
-                }
-            } else {
-                int old = ftepp->token;
-                ftepp->token = TOKEN_VA_ARGS;
-                ptok = pptoken_make(ftepp);
-                vec_push(macro->output, ptok);
-                ftepp->token = old;
-            }
-        }
-        else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) {
-            ftepp->token = TOKEN_VA_COUNT;
-            ptok         = pptoken_make(ftepp);
-            vec_push(macro->output, ptok);
-            ftepp_next(ftepp);
-        } else {
-            ptok = pptoken_make(ftepp);
-            vec_push(macro->output, ptok);
-            ftepp_next(ftepp);
-        }
-    }
-    /* recursive expansion can cause EOFs here */
-    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
-        ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
-        return false;
-    }
-    return true;
-}
-
-static const char *ftepp_math_constants[][2] = {
-    { "M_E",        "2.7182818284590452354"  }, /* e          */
-    { "M_LOG2E",    "1.4426950408889634074"  }, /* log_2 e    */
-    { "M_LOG10E",   "0.43429448190325182765" }, /* log_10 e   */
-    { "M_LN2",      "0.69314718055994530942" }, /* log_e 2    */
-    { "M_LN10",     "2.30258509299404568402" }, /* log_e 10   */
-    { "M_PI",       "3.14159265358979323846" }, /* pi         */
-    { "M_PI_2",     "1.57079632679489661923" }, /* pi/2       */
-    { "M_PI_4",     "0.78539816339744830962" }, /* pi/4       */
-    { "M_1_PI",     "0.31830988618379067154" }, /* 1/pi       */
-    { "M_2_PI",     "0.63661977236758134308" }, /* 2/pi       */
-    { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */
-    { "M_SQRT2",    "1.41421356237309504880" }, /* sqrt(2)    */
-    { "M_SQRT1_2",  "0.70710678118654752440" }, /* 1/sqrt(2)  */
-    { "M_TAU",      "6.28318530717958647692" }  /* pi*2       */
-};
-
-static bool ftepp_define(ftepp_t *ftepp)
-{
-    ppmacro *macro = NULL;
-    size_t l = ftepp_ctx(ftepp).line;
-    size_t i;
-    bool   mathconstant = false;
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    switch (ftepp->token) {
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-        case TOKEN_KEYWORD:
-            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
-                for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) {
-                    if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) {
-                        mathconstant = true;
-                        break;
-                    }
-                }
-            }
-
-            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-
-            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
-                /* user defined ones take precedence */
-                if (macro && mathconstant) {
-                    ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
-                    macro = NULL;
-                }
-            }
-
-            if (macro && ftepp->output_on) {
-                if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp)))
-                    return false;
-                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
-            }
-            macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
-            break;
-        default:
-            ftepp_error(ftepp, "expected macro name");
-            return false;
-    }
-
-    (void)ftepp_next(ftepp);
-
-    if (ftepp->token == '(') {
-        macro->has_params = true;
-        if (!ftepp_define_params(ftepp, macro)) {
-            ppmacro_delete(macro);
-            return false;
-        }
-    }
-
-    if (!ftepp_skipspace(ftepp)) {
-        ppmacro_delete(macro);
-        return false;
-    }
-
-    if (!ftepp_define_body(ftepp, macro)) {
-        ppmacro_delete(macro);
-        return false;
-    }
-
-    if (ftepp->output_on)
-        util_htset(ftepp->macros, macro->name, (void*)macro);
-    else {
-        ppmacro_delete(macro);
-    }
-
-    for (; l < ftepp_ctx(ftepp).line; ++l)
-        ftepp_out(ftepp, "\n", true);
-    return true;
-}
-
-/**
- * When a macro is used we have to handle parameters as well
- * as special-concatenation via ## or stringification via #
- *
- * Note: parenthesis can nest, so FOO((a),b) is valid, but only
- * this kind of parens. Curly braces or [] don't count towards the
- * paren-level.
- */
-typedef struct {
-    pptoken **tokens;
-} macroparam;
-
-static void macroparam_clean(macroparam *self)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->tokens); ++i)
-        pptoken_delete(self->tokens[i]);
-    vec_free(self->tokens);
-}
-
-/* need to leave the last token up */
-static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
-{
-    macroparam *params = NULL;
-    pptoken    *ptok;
-    macroparam  mp;
-    size_t      parens = 0;
-    size_t      i;
-
-    if (!ftepp_skipallwhite(ftepp))
-        return false;
-    while (ftepp->token != ')') {
-        mp.tokens = NULL;
-        if (!ftepp_skipallwhite(ftepp))
-            return false;
-        while (parens || ftepp->token != ',') {
-            if (ftepp->token == '(')
-                ++parens;
-            else if (ftepp->token == ')') {
-                if (!parens)
-                    break;
-                --parens;
-            }
-            ptok = pptoken_make(ftepp);
-            vec_push(mp.tokens, ptok);
-            if (ftepp_next(ftepp) >= TOKEN_EOF) {
-                ftepp_error(ftepp, "unexpected end of file in macro call");
-                goto on_error;
-            }
-        }
-        vec_push(params, mp);
-        mp.tokens = NULL;
-        if (ftepp->token == ')')
-            break;
-        if (ftepp->token != ',') {
-            ftepp_error(ftepp, "expected closing paren or comma in macro call");
-            goto on_error;
-        }
-        if (ftepp_next(ftepp) >= TOKEN_EOF) {
-            ftepp_error(ftepp, "unexpected end of file in macro call");
-            goto on_error;
-        }
-    }
-    *out_params = params;
-    return true;
-
-on_error:
-    if (mp.tokens)
-        macroparam_clean(&mp);
-    for (i = 0; i < vec_size(params); ++i)
-        macroparam_clean(&params[i]);
-    vec_free(params);
-    return false;
-}
-
-static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
-{
-    size_t i;
-    for (i = 0; i < vec_size(macro->params); ++i) {
-        if (!strcmp(macro->params[i], name)) {
-            *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
-{
-    char        chs[2];
-    const char *ch;
-    chs[1] = 0;
-    switch (token->token) {
-        case TOKEN_STRINGCONST:
-            ch = token->value;
-            while (*ch) {
-                /* in preprocessor mode strings already are string,
-                 * so we don't get actual newline bytes here.
-                 * Still need to escape backslashes and quotes.
-                 */
-                switch (*ch) {
-                    case '\\': ftepp_out(ftepp, "\\\\", false); break;
-                    case '"':  ftepp_out(ftepp, "\\\"", false); break;
-                    default:
-                        chs[0] = *ch;
-                        ftepp_out(ftepp, chs, false);
-                        break;
-                }
-                ++ch;
-            }
-            break;
-        /*case TOKEN_WHITE:
-            ftepp_out(ftepp, " ", false);
-            break;*/
-        case TOKEN_EOL:
-            ftepp_out(ftepp, "\\n", false);
-            break;
-        default:
-            ftepp_out(ftepp, token->value, false);
-            break;
-    }
-}
-
-static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
-{
-    size_t i;
-    ftepp_out(ftepp, "\"", false);
-    for (i = 0; i < vec_size(param->tokens); ++i)
-        ftepp_stringify_token(ftepp, param->tokens[i]);
-    ftepp_out(ftepp, "\"", false);
-}
-
-static void ftepp_recursion_header(ftepp_t *ftepp)
-{
-    ftepp_out(ftepp, "\n#pragma push(line)\n", false);
-}
-
-static void ftepp_recursion_footer(ftepp_t *ftepp)
-{
-    ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
-}
-
-static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
-static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
-{
-    size_t   i;
-    pptoken *out;
-    for (i = 0; i < vec_size(param->tokens); ++i) {
-        out = param->tokens[i];
-        if (out->token == TOKEN_EOL)
-            ftepp_out(ftepp, "\n", false);
-        else {
-            ppmacro *find = ftepp_macro_find(ftepp, out->value);
-            if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
-                ftepp_macro_expand(ftepp, find, NULL, false);
-            else
-                ftepp_out(ftepp, out->value, false);
-        }
-    }
-}
-
-static bool ftepp_preprocess(ftepp_t *ftepp);
-static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline)
-{
-    char     *buffer       = NULL;
-    char     *old_string   = ftepp->output_string;
-    char     *inner_string;
-    lex_file *old_lexer    = ftepp->lex;
-    size_t    vararg_start = vec_size(macro->params);
-    bool      retval       = true;
-    bool      has_newlines;
-    size_t    varargs;
-
-    size_t    o, pi;
-    lex_file *inlex;
-
-    bool      old_inmacro;
-    bool      strip = false;
-
-    int nextok;
-
-    if (vararg_start < vec_size(params))
-        varargs = vec_size(params) - vararg_start;
-    else
-        varargs = 0;
-
-    /* really ... */
-    if (!vec_size(macro->output))
-        return true;
-
-    ftepp->output_string = NULL;
-    for (o = 0; o < vec_size(macro->output); ++o) {
-        pptoken *out = macro->output[o];
-        switch (out->token) {
-            case TOKEN_VA_ARGS:
-                if (!macro->variadic) {
-                    ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro");
-                    vec_free(old_string);
-                    return false;
-                }
-                if (!varargs)
-                    break;
-
-                pi = 0;
-                ftepp_param_out(ftepp, &params[pi + vararg_start]);
-                for (++pi; pi < varargs; ++pi) {
-                    ftepp_out(ftepp, ", ", false);
-                    ftepp_param_out(ftepp, &params[pi + vararg_start]);
-                }
-                break;
-
-            case TOKEN_VA_ARGS_ARRAY:
-                if ((size_t)out->constval.i >= varargs) {
-                    ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i);
-                    vec_free(old_string);
-                    return false;
-                }
-
-                ftepp_param_out(ftepp, &params[out->constval.i + vararg_start]);
-                break;
-
-            case TOKEN_VA_COUNT:
-                util_asprintf(&buffer, "%d", varargs);
-                ftepp_out(ftepp, buffer, false);
-                mem_d(buffer);
-                break;
-
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-            case TOKEN_KEYWORD:
-                if (!macro_params_find(macro, out->value, &pi)) {
-                    ftepp_out(ftepp, out->value, false);
-                    break;
-                } else
-                    ftepp_param_out(ftepp, &params[pi]);
-                break;
-            case '#':
-                if (o + 1 < vec_size(macro->output)) {
-                    nextok = macro->output[o+1]->token;
-                    if (nextok == '#') {
-                        /* raw concatenation */
-                        ++o;
-                        strip = true;
-                        break;
-                    }
-                    if ( (nextok == TOKEN_IDENT    ||
-                          nextok == TOKEN_KEYWORD  ||
-                          nextok == TOKEN_TYPENAME) &&
-                        macro_params_find(macro, macro->output[o+1]->value, &pi))
-                    {
-                        ++o;
-
-                        ftepp_stringify(ftepp, &params[pi]);
-                        break;
-                    }
-                }
-                ftepp_out(ftepp, "#", false);
-                break;
-            case TOKEN_EOL:
-                ftepp_out(ftepp, "\n", false);
-                break;
-            default:
-                buffer = out->value;
-                #define buffer_stripable(X) ((X) == ' ' || (X) == '\t')
-                if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer))
-                    buffer++;
-                if (strip) {
-                    while (buffer_stripable(*buffer)) buffer++;
-                    strip = false;
-                }
-                ftepp_out(ftepp, buffer, false);
-                break;
-        }
-    }
-    vec_push(ftepp->output_string, 0);
-    /* Now run the preprocessor recursively on this string buffer */
-    /*
-    printf("__________\n%s\n=========\n", ftepp->output_string);
-    */
-    inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
-    if (!inlex) {
-        ftepp_error(ftepp, "internal error: failed to instantiate lexer");
-        retval = false;
-        goto cleanup;
-    }
-
-    inlex->line  = ftepp->lex->line;
-    inlex->sline = ftepp->lex->sline;
-    ftepp->lex   = inlex;
-
-    old_inmacro     = ftepp->in_macro;
-    ftepp->in_macro = true;
-    ftepp->output_string = NULL;
-    if (!ftepp_preprocess(ftepp)) {
-        ftepp->in_macro = old_inmacro;
-        vec_free(ftepp->lex->open_string);
-        vec_free(ftepp->output_string);
-        lex_close(ftepp->lex);
-        retval = false;
-        goto cleanup;
-    }
-    ftepp->in_macro = old_inmacro;
-    vec_free(ftepp->lex->open_string);
-    lex_close(ftepp->lex);
-
-    inner_string = ftepp->output_string;
-    ftepp->output_string = old_string;
-
-    has_newlines = (strchr(inner_string, '\n') != NULL);
-
-    if (has_newlines && !old_inmacro)
-        ftepp_recursion_header(ftepp);
-
-    vec_append(ftepp->output_string, vec_size(inner_string), inner_string);
-    vec_free(inner_string);
-
-    if (has_newlines && !old_inmacro)
-        ftepp_recursion_footer(ftepp);
-
-    if (resetline && !ftepp->in_macro) {
-        char lineno[128];
-        util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline));
-        ftepp_out(ftepp, lineno, false);
-    }
-
-    old_string = ftepp->output_string;
-cleanup:
-    ftepp->lex           = old_lexer;
-    ftepp->output_string = old_string;
-    return retval;
-}
-
-static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
-{
-    size_t     o;
-    macroparam *params = NULL;
-    bool        retval = true;
-    size_t      paramline;
-
-    if (!macro->has_params) {
-        if (!ftepp_macro_expand(ftepp, macro, NULL, false))
-            return false;
-        ftepp_next(ftepp);
-        return true;
-    }
-    ftepp_next(ftepp);
-
-    if (!ftepp_skipallwhite(ftepp))
-        return false;
-
-    if (ftepp->token != '(') {
-        ftepp_error(ftepp, "expected macro parameters in parenthesis");
-        return false;
-    }
-
-    ftepp_next(ftepp);
-    paramline = ftepp->lex->sline;
-    if (!ftepp_macro_call_params(ftepp, &params))
-        return false;
-
-    if ( vec_size(params) < vec_size(macro->params) ||
-        (vec_size(params) > vec_size(macro->params) && !macro->variadic) )
-    {
-        ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name,
-                    (macro->variadic ? " at least" : ""),
-                    (unsigned int)vec_size(macro->params),
-                    (unsigned int)vec_size(params));
-        retval = false;
-        goto cleanup;
-    }
-
-    if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline)))
-        retval = false;
-    ftepp_next(ftepp);
-
-cleanup:
-    for (o = 0; o < vec_size(params); ++o)
-        macroparam_clean(&params[o]);
-    vec_free(params);
-    return retval;
-}
-
-/**
- * #if - the FTEQCC way:
- *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
- *    <numbers>    => True if the number is not 0
- *    !<factor>    => True if the factor yields false
- *    !!<factor>   => ERROR on 2 or more unary nots
- *    <macro>      => becomes the macro's FIRST token regardless of parameters
- *    <e> && <e>   => True if both expressions are true
- *    <e> || <e>   => True if either expression is true
- *    <string>     => False
- *    <ident>      => False (remember for macros the <macro> rule applies instead)
- * Unary + and - are weird and wrong in fteqcc so we don't allow them
- * parenthesis in expressions are allowed
- * parameter lists on macros are errors
- * No mathematical calculations are executed
- */
-static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out);
-static bool ftepp_if_op(ftepp_t *ftepp)
-{
-    ftepp->lex->flags.noops = false;
-    ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    ftepp->lex->flags.noops = true;
-    return true;
-}
-static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out)
-{
-    ppmacro *macro;
-    bool     wasnot = false;
-    bool     wasneg = false;
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    while (ftepp->token == '!') {
-        wasnot = true;
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-    }
-
-    if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-"))
-    {
-        wasneg = true;
-        ftepp_next(ftepp);
-        if (!ftepp_skipspace(ftepp))
-            return false;
-    }
-
-    switch (ftepp->token) {
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-        case TOKEN_KEYWORD:
-            if (!strcmp(ftepp_tokval(ftepp), "defined")) {
-                ftepp_next(ftepp);
-                if (!ftepp_skipspace(ftepp))
-                    return false;
-                if (ftepp->token != '(') {
-                    ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
-                    return false;
-                }
-                ftepp_next(ftepp);
-                if (!ftepp_skipspace(ftepp))
-                    return false;
-                if (ftepp->token != TOKEN_IDENT &&
-                    ftepp->token != TOKEN_TYPENAME &&
-                    ftepp->token != TOKEN_KEYWORD)
-                {
-                    ftepp_error(ftepp, "defined() used on an unexpected token type");
-                    return false;
-                }
-                macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-                *out = !!macro;
-                ftepp_next(ftepp);
-                if (!ftepp_skipspace(ftepp))
-                    return false;
-                if (ftepp->token != ')') {
-                    ftepp_error(ftepp, "expected closing paren");
-                    return false;
-                }
-                break;
-            }
-
-            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-            if (!macro || !vec_size(macro->output)) {
-                *out = false;
-                *value_out = 0;
-            } else {
-                /* This does not expand recursively! */
-                switch (macro->output[0]->token) {
-                    case TOKEN_INTCONST:
-                        *value_out = macro->output[0]->constval.i;
-                        *out = !!(macro->output[0]->constval.i);
-                        break;
-                    case TOKEN_FLOATCONST:
-                        *value_out = macro->output[0]->constval.f;
-                        *out = !!(macro->output[0]->constval.f);
-                        break;
-                    default:
-                        *out = false;
-                        break;
-                }
-            }
-            break;
-        case TOKEN_STRINGCONST:
-            *value_out = 0;
-            *out = false;
-            break;
-        case TOKEN_INTCONST:
-            *value_out = ftepp->lex->tok.constval.i;
-            *out = !!(ftepp->lex->tok.constval.i);
-            break;
-        case TOKEN_FLOATCONST:
-            *value_out = ftepp->lex->tok.constval.f;
-            *out = !!(ftepp->lex->tok.constval.f);
-            break;
-
-        case '(':
-            ftepp_next(ftepp);
-            if (!ftepp_if_expr(ftepp, out, value_out))
-                return false;
-            if (ftepp->token != ')') {
-                ftepp_error(ftepp, "expected closing paren in #if expression");
-                return false;
-            }
-            break;
-
-        default:
-            ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp));
-            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
-                ftepp_error(ftepp, "internal: token %i\n", ftepp->token);
-            return false;
-    }
-    if (wasneg)
-        *value_out = -*value_out;
-    if (wasnot) {
-        *out = !*out;
-        *value_out = (*out ? 1 : 0);
-    }
-    return true;
-}
-
-/*
-static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out)
-{
-    if (!ftepp_next(ftepp))
-        return false;
-    return ftepp_if_value(ftepp, out, value_out);
-}
-*/
-
-static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out)
-{
-    if (!ftepp_if_value(ftepp, out, value_out))
-        return false;
-
-    if (!ftepp_if_op(ftepp))
-        return false;
-
-    if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR)
-        return true;
-
-    /* FTEQCC is all right-associative and no precedence here */
-    if (!strcmp(ftepp_tokval(ftepp), "&&") ||
-        !strcmp(ftepp_tokval(ftepp), "||"))
-    {
-        bool next = false;
-        char opc  = ftepp_tokval(ftepp)[0];
-        double nextvalue;
-
-        (void)nextvalue;
-        if (!ftepp_next(ftepp))
-            return false;
-        if (!ftepp_if_expr(ftepp, &next, &nextvalue))
-            return false;
-
-        if (opc == '&')
-            *out = *out && next;
-        else
-            *out = *out || next;
-
-        *value_out = (*out ? 1 : 0);
-        return true;
-    }
-    else if (!strcmp(ftepp_tokval(ftepp), "==") ||
-             !strcmp(ftepp_tokval(ftepp), "!=") ||
-             !strcmp(ftepp_tokval(ftepp), ">=") ||
-             !strcmp(ftepp_tokval(ftepp), "<=") ||
-             !strcmp(ftepp_tokval(ftepp), ">") ||
-             !strcmp(ftepp_tokval(ftepp), "<"))
-    {
-        bool next = false;
-        const char opc0 = ftepp_tokval(ftepp)[0];
-        const char opc1 = ftepp_tokval(ftepp)[1];
-        double other;
-
-        if (!ftepp_next(ftepp))
-            return false;
-        if (!ftepp_if_expr(ftepp, &next, &other))
-            return false;
-
-        if (opc0 == '=')
-            *out = (*value_out == other);
-        else if (opc0 == '!')
-            *out = (*value_out != other);
-        else if (opc0 == '>') {
-            if (opc1 == '=') *out = (*value_out >= other);
-            else             *out = (*value_out > other);
-        }
-        else if (opc0 == '<') {
-            if (opc1 == '=') *out = (*value_out <= other);
-            else             *out = (*value_out < other);
-        }
-        *value_out = (*out ? 1 : 0);
-
-        return true;
-    }
-    else {
-        ftepp_error(ftepp, "junk after #if");
-        return false;
-    }
-}
-
-static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
-{
-    bool result = false;
-    double dummy = 0;
-
-    memset(cond, 0, sizeof(*cond));
-    (void)ftepp_next(ftepp);
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    if (ftepp->token == TOKEN_EOL) {
-        ftepp_error(ftepp, "expected expression for #if-directive");
-        return false;
-    }
-
-    if (!ftepp_if_expr(ftepp, &result, &dummy))
-        return false;
-
-    cond->on = result;
-    return true;
-}
-
-/**
- * ifdef is rather simple
- */
-static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
-{
-    ppmacro *macro;
-    memset(cond, 0, sizeof(*cond));
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    switch (ftepp->token) {
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-        case TOKEN_KEYWORD:
-            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-            break;
-        default:
-            ftepp_error(ftepp, "expected macro name");
-            return false;
-    }
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    /* relaxing this condition
-    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
-        ftepp_error(ftepp, "stray tokens after #ifdef");
-        return false;
-    }
-    */
-    cond->on = !!macro;
-    return true;
-}
-
-/**
- * undef is also simple
- */
-static bool ftepp_undef(ftepp_t *ftepp)
-{
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    if (ftepp->output_on) {
-        switch (ftepp->token) {
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-            case TOKEN_KEYWORD:
-                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
-                break;
-            default:
-                ftepp_error(ftepp, "expected macro name");
-                return false;
-        }
-    }
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    /* relaxing this condition
-    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
-        ftepp_error(ftepp, "stray tokens after #ifdef");
-        return false;
-    }
-    */
-    return true;
-}
-
-/* Special unescape-string function which skips a leading quote
- * and stops at a quote, not just at \0
- */
-static void unescape(const char *str, char *out) {
-    ++str;
-    while (*str && *str != '"') {
-        if (*str == '\\') {
-            ++str;
-            switch (*str) {
-                case '\\': *out++ = *str; break;
-                case '"':  *out++ = *str; break;
-                case 'a':  *out++ = '\a'; break;
-                case 'b':  *out++ = '\b'; break;
-                case 'r':  *out++ = '\r'; break;
-                case 'n':  *out++ = '\n'; break;
-                case 't':  *out++ = '\t'; break;
-                case 'f':  *out++ = '\f'; break;
-                case 'v':  *out++ = '\v'; break;
-                default:
-                    *out++ = '\\';
-                    *out++ = *str;
-                    break;
-            }
-            ++str;
-            continue;
-        }
-
-        *out++ = *str++;
-    }
-    *out = 0;
-}
-
-static char *ftepp_include_find_path(const char *file, const char *pathfile)
-{
-    FILE *fp;
-    char       *filename = NULL;
-    const char *last_slash;
-    size_t      len;
-
-    if (!pathfile)
-        return NULL;
-
-    last_slash = strrchr(pathfile, '/');
-
-    if (last_slash) {
-        len = last_slash - pathfile;
-        memcpy(vec_add(filename, len), pathfile, len);
-        vec_push(filename, '/');
-    }
-
-    len = strlen(file);
-    memcpy(vec_add(filename, len+1), file, len);
-    vec_last(filename) = 0;
-
-    fp = fopen(filename, "rb");
-    if (fp) {
-        fclose(fp);
-        return filename;
-    }
-    vec_free(filename);
-    return NULL;
-}
-
-static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
-{
-    char *filename = NULL;
-
-    filename = ftepp_include_find_path(file, ftepp->includename);
-    if (!filename)
-        filename = ftepp_include_find_path(file, ftepp->itemname);
-    return filename;
-}
-
-static bool ftepp_directive_warning(ftepp_t *ftepp) {
-    char *message = NULL;
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    /* handle the odd non string constant case so it works like C */
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        bool  store   = false;
-        vec_append(message, 8, "#warning");
-        ftepp_next(ftepp);
-        while (ftepp->token != TOKEN_EOL) {
-            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
-            ftepp_next(ftepp);
-        }
-        vec_push(message, '\0');
-        if (ftepp->output_on)
-            store = ftepp_warn(ftepp, WARN_CPP, message);
-        else
-            store = false;
-        vec_free(message);
-        return store;
-    }
-
-    if (!ftepp->output_on)
-        return false;
-
-    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
-    return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
-}
-
-static void ftepp_directive_error(ftepp_t *ftepp) {
-    char *message = NULL;
-
-    if (!ftepp_skipspace(ftepp))
-        return;
-
-    /* handle the odd non string constant case so it works like C */
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        vec_append(message, 6, "#error");
-        ftepp_next(ftepp);
-        while (ftepp->token != TOKEN_EOL) {
-            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
-            ftepp_next(ftepp);
-        }
-        vec_push(message, '\0');
-        if (ftepp->output_on)
-            ftepp_error(ftepp, message);
-        vec_free(message);
-        return;
-    }
-
-    if (!ftepp->output_on)
-        return;
-
-    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
-    ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
-}
-
-static void ftepp_directive_message(ftepp_t *ftepp) {
-    char *message = NULL;
-
-    if (!ftepp_skipspace(ftepp))
-        return;
-
-    /* handle the odd non string constant case so it works like C */
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        vec_append(message, 8, "#message");
-        ftepp_next(ftepp);
-        while (ftepp->token != TOKEN_EOL) {
-            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
-            ftepp_next(ftepp);
-        }
-        vec_push(message, '\0');
-        if (ftepp->output_on)
-            con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message);
-        vec_free(message);
-        return;
-    }
-
-    if (!ftepp->output_on)
-        return;
-
-    unescape     (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
-    con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message",  ftepp_tokval(ftepp));
-}
-
-/**
- * Include a file.
- * FIXME: do we need/want a -I option?
- * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
- */
-static bool ftepp_include(ftepp_t *ftepp)
-{
-    lex_file *old_lexer = ftepp->lex;
-    lex_file *inlex;
-    lex_ctx_t ctx;
-    char     lineno[128];
-    char     *filename;
-    char     *parsename = NULL;
-    char     *old_includename;
-
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    if (ftepp->token != TOKEN_STRINGCONST) {
-        ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-        if (macro) {
-            char *backup = ftepp->output_string;
-            ftepp->output_string = NULL;
-            if (ftepp_macro_expand(ftepp, macro, NULL, true)) {
-                parsename = util_strdup(ftepp->output_string);
-                vec_free(ftepp->output_string);
-                ftepp->output_string = backup;
-            } else {
-                ftepp->output_string = backup;
-                ftepp_error(ftepp, "expected filename to include");
-                return false;
-            }
-        } else if (OPTS_FLAG(FTEPP_PREDEFS)) {
-            /* Well it could be a predefine like __LINE__ */
-            char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
-            if (predef) {
-                parsename = predef(ftepp);
-            } else {
-                ftepp_error(ftepp, "expected filename to include");
-                return false;
-            }
-        }
-    }
-
-    if (!ftepp->output_on) {
-        (void)ftepp_next(ftepp);
-        return true;
-    }
-
-    if (parsename)
-        unescape(parsename, parsename);
-    else {
-        char *tokval = ftepp_tokval(ftepp);
-        unescape(tokval, tokval);
-        parsename = util_strdup(tokval);
-    }
-
-    ctx = ftepp_ctx(ftepp);
-    ftepp_out(ftepp, "\n#pragma file(", false);
-    ftepp_out(ftepp, parsename, false);
-    ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
-
-    filename = ftepp_include_find(ftepp, parsename);
-    if (!filename) {
-        ftepp_error(ftepp, "failed to open include file `%s`", parsename);
-        mem_d(parsename);
-        return false;
-    }
-    mem_d(parsename);
-    inlex = lex_open(filename);
-    if (!inlex) {
-        ftepp_error(ftepp, "open failed on include file `%s`", filename);
-        vec_free(filename);
-        return false;
-    }
-    ftepp->lex = inlex;
-    old_includename = ftepp->includename;
-    ftepp->includename = filename;
-    if (!ftepp_preprocess(ftepp)) {
-        vec_free(ftepp->includename);
-        ftepp->includename = old_includename;
-        lex_close(ftepp->lex);
-        ftepp->lex = old_lexer;
-        return false;
-    }
-    vec_free(ftepp->includename);
-    ftepp->includename = old_includename;
-    lex_close(ftepp->lex);
-    ftepp->lex = old_lexer;
-
-    ftepp_out(ftepp, "\n#pragma file(", false);
-    ftepp_out(ftepp, ctx.file, false);
-    util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
-    ftepp_out(ftepp, lineno, false);
-
-    /* skip the line */
-    (void)ftepp_next(ftepp);
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    if (ftepp->token != TOKEN_EOL) {
-        ftepp_error(ftepp, "stray tokens after #include");
-        return false;
-    }
-    (void)ftepp_next(ftepp);
-
-    return true;
-}
-
-/* Basic structure handlers */
-static bool ftepp_else_allowed(ftepp_t *ftepp)
-{
-    if (!vec_size(ftepp->conditions)) {
-        ftepp_error(ftepp, "#else without #if");
-        return false;
-    }
-    if (vec_last(ftepp->conditions).had_else) {
-        ftepp_error(ftepp, "multiple #else for a single #if");
-        return false;
-    }
-    return true;
-}
-
-static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) {
-    if (ftepp->in_macro)
-        (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash);
-}
-
-static bool ftepp_hash(ftepp_t *ftepp)
-{
-    ppcondition cond;
-    ppcondition *pc;
-
-    lex_ctx_t ctx = ftepp_ctx(ftepp);
-
-    if (!ftepp_skipspace(ftepp))
-        return false;
-
-    switch (ftepp->token) {
-        case TOKEN_KEYWORD:
-        case TOKEN_IDENT:
-        case TOKEN_TYPENAME:
-            if (!strcmp(ftepp_tokval(ftepp), "define")) {
-                ftepp_inmacro(ftepp, "define");
-                return ftepp_define(ftepp);
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
-                ftepp_inmacro(ftepp, "undef");
-                return ftepp_undef(ftepp);
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
-                ftepp_inmacro(ftepp, "ifdef");
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                cond.was_on = cond.on;
-                vec_push(ftepp->conditions, cond);
-                ftepp->output_on = ftepp->output_on && cond.on;
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
-                ftepp_inmacro(ftepp, "ifndef");
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                cond.on = !cond.on;
-                cond.was_on = cond.on;
-                vec_push(ftepp->conditions, cond);
-                ftepp->output_on = ftepp->output_on && cond.on;
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
-                ftepp_inmacro(ftepp, "elifdef");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                pc = &vec_last(ftepp->conditions);
-                pc->on     = !pc->was_on && cond.on;
-                pc->was_on = pc->was_on || pc->on;
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
-                ftepp_inmacro(ftepp, "elifndef");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                if (!ftepp_ifdef(ftepp, &cond))
-                    return false;
-                cond.on = !cond.on;
-                pc = &vec_last(ftepp->conditions);
-                pc->on     = !pc->was_on && cond.on;
-                pc->was_on = pc->was_on || pc->on;
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
-                ftepp_inmacro(ftepp, "elif");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                if (!ftepp_if(ftepp, &cond))
-                    return false;
-                pc = &vec_last(ftepp->conditions);
-                pc->on     = !pc->was_on && cond.on;
-                pc->was_on = pc->was_on  || pc->on;
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "if")) {
-                ftepp_inmacro(ftepp, "if");
-                if (!ftepp_if(ftepp, &cond))
-                    return false;
-                cond.was_on = cond.on;
-                vec_push(ftepp->conditions, cond);
-                ftepp->output_on = ftepp->output_on && cond.on;
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "else")) {
-                ftepp_inmacro(ftepp, "else");
-                if (!ftepp_else_allowed(ftepp))
-                    return false;
-                pc = &vec_last(ftepp->conditions);
-                pc->on = !pc->was_on;
-                pc->had_else = true;
-                ftepp_next(ftepp);
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
-                ftepp_inmacro(ftepp, "endif");
-                if (!vec_size(ftepp->conditions)) {
-                    ftepp_error(ftepp, "#endif without #if");
-                    return false;
-                }
-                vec_pop(ftepp->conditions);
-                ftepp_next(ftepp);
-                ftepp_update_output_condition(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "include")) {
-                ftepp_inmacro(ftepp, "include");
-                return ftepp_include(ftepp);
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
-                ftepp_out(ftepp, "#", false);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "warning")) {
-                ftepp_directive_warning(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "error")) {
-                ftepp_directive_error(ftepp);
-                break;
-            }
-            else if (!strcmp(ftepp_tokval(ftepp), "message")) {
-                ftepp_directive_message(ftepp);
-                break;
-            }
-            else {
-                if (ftepp->output_on) {
-                    ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
-                    return false;
-                } else {
-                    ftepp_next(ftepp);
-                    break;
-                }
-            }
-            /* break; never reached */
-        default:
-            ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
-            return false;
-        case TOKEN_EOL:
-            ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
-            return false;
-        case TOKEN_EOF:
-            ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
-            return false;
-
-        /* Builtins! Don't forget the builtins! */
-        case TOKEN_INTCONST:
-        case TOKEN_FLOATCONST:
-            ftepp_out(ftepp, "#", false);
-            return true;
-    }
-    if (!ftepp_skipspace(ftepp))
-        return false;
-    return true;
-}
-
-static bool ftepp_preprocess(ftepp_t *ftepp)
-{
-    ppmacro *macro;
-    bool     newline = true;
-
-    /* predef stuff */
-    char    *expand  = NULL;
-
-    ftepp->lex->flags.preprocessing = true;
-    ftepp->lex->flags.mergelines    = false;
-    ftepp->lex->flags.noops         = true;
-
-    ftepp_next(ftepp);
-    do
-    {
-        if (ftepp->token >= TOKEN_EOF)
-            break;
-        switch (ftepp->token) {
-            case TOKEN_KEYWORD:
-            case TOKEN_IDENT:
-            case TOKEN_TYPENAME:
-                /* is it a predef? */
-                if (OPTS_FLAG(FTEPP_PREDEFS)) {
-                    char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
-                    if (predef) {
-                        expand = predef(ftepp);
-                        ftepp_out (ftepp, expand, false);
-                        ftepp_next(ftepp);
-
-                        mem_d(expand);
-                        break;
-                    }
-                }
-
-                if (ftepp->output_on)
-                    macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
-                else
-                    macro = NULL;
-
-                if (!macro) {
-                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
-                    ftepp_next(ftepp);
-                    break;
-                }
-                if (!ftepp_macro_call(ftepp, macro))
-                    ftepp->token = TOKEN_ERROR;
-                break;
-            case '#':
-                if (!newline) {
-                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
-                    ftepp_next(ftepp);
-                    break;
-                }
-                ftepp->lex->flags.mergelines = true;
-                if (ftepp_next(ftepp) >= TOKEN_EOF) {
-                    ftepp_error(ftepp, "error in preprocessor directive");
-                    ftepp->token = TOKEN_ERROR;
-                    break;
-                }
-                if (!ftepp_hash(ftepp))
-                    ftepp->token = TOKEN_ERROR;
-                ftepp->lex->flags.mergelines = false;
-                break;
-            case TOKEN_EOL:
-                newline = true;
-                ftepp_out(ftepp, "\n", true);
-                ftepp_next(ftepp);
-                break;
-            case TOKEN_WHITE:
-                /* same as default but don't set newline=false */
-                ftepp_out(ftepp, ftepp_tokval(ftepp), true);
-                ftepp_next(ftepp);
-                break;
-            default:
-                newline = false;
-                ftepp_out(ftepp, ftepp_tokval(ftepp), false);
-                ftepp_next(ftepp);
-                break;
-        }
-    } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
-
-    /* force a 0 at the end but don't count it as added to the output */
-    vec_push(ftepp->output_string, 0);
-    vec_shrinkby(ftepp->output_string, 1);
-
-    return (ftepp->token == TOKEN_EOF);
-}
-
-/* Like in parser.c - files keep the previous state so we have one global
- * preprocessor. Except here we will want to warn about dangling #ifs.
- */
-static bool ftepp_preprocess_done(ftepp_t *ftepp)
-{
-    bool retval = true;
-    if (vec_size(ftepp->conditions)) {
-        if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
-            retval = false;
-    }
-    lex_close(ftepp->lex);
-    ftepp->lex = NULL;
-    if (ftepp->itemname) {
-        mem_d(ftepp->itemname);
-        ftepp->itemname = NULL;
-    }
-    return retval;
-}
-
-bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename)
-{
-    ftepp->lex = lex_open(filename);
-    ftepp->itemname = util_strdup(filename);
-    if (!ftepp->lex) {
-        con_out("failed to open file \"%s\"\n", filename);
-        return false;
-    }
-    if (!ftepp_preprocess(ftepp))
-        return false;
-    return ftepp_preprocess_done(ftepp);
-}
-
-bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str)
-{
-    ftepp->lex = lex_open_string(str, strlen(str), name);
-    ftepp->itemname = util_strdup(name);
-    if (!ftepp->lex) {
-        con_out("failed to create lexer for string \"%s\"\n", name);
-        return false;
-    }
-    if (!ftepp_preprocess(ftepp))
-        return false;
-    return ftepp_preprocess_done(ftepp);
-}
-
-
-void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) {
-    char *create = NULL;
-
-    /* use saner path for empty macros */
-    if (!value) {
-        ftepp_add_define(ftepp, "__builtin__", name);
-        return;
-    }
-
-    vec_append(create, 8,           "#define ");
-    vec_append(create, strlen(name), name);
-    vec_push  (create, ' ');
-    vec_append(create, strlen(value), value);
-    vec_push  (create, 0);
-
-    ftepp_preprocess_string(ftepp, "__builtin__", create);
-    vec_free  (create);
-}
-
-ftepp_t *ftepp_create()
-{
-    ftepp_t *ftepp;
-    char minor[32];
-    char major[32];
-    size_t i;
-
-    ftepp = ftepp_new();
-    if (!ftepp)
-        return NULL;
-
-    memset(minor, 0, sizeof(minor));
-    memset(major, 0, sizeof(major));
-
-    /* set the right macro based on the selected standard */
-    ftepp_add_define(ftepp, NULL, "GMQCC");
-    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
-        ftepp_add_define(ftepp, NULL, "__STD_FTEQCC__");
-        /* 1.00 */
-        major[0] = '"';
-        major[1] = '1';
-        major[2] = '"';
-
-        minor[0] = '"';
-        minor[1] = '0';
-        minor[2] = '"';
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
-        ftepp_add_define(ftepp, NULL, "__STD_GMQCC__");
-        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
-        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) {
-        ftepp_add_define(ftepp, NULL, "__STD_QCCX__");
-        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
-        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-        ftepp_add_define(ftepp, NULL, "__STD_QCC__");
-        /* 1.0 */
-        major[0] = '"';
-        major[1] = '1';
-        major[2] = '"';
-
-        minor[0] = '"';
-        minor[1] = '0';
-        minor[2] = '"';
-    }
-
-    ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor);
-    ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major);
-
-    /*
-     * We're going to just make __NULL__ nil, which works for 60% of the
-     * cases of __NULL_ for fteqcc.
-     */
-    ftepp_add_macro(ftepp, "__NULL__", "nil");
-
-    /* add all the math constants if they can be */
-    if (OPTS_FLAG(FTEPP_MATHDEFS)) {
-        for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++)
-            if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0]))
-                ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]);
-    }
-
-    return ftepp;
-}
-
-void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name)
-{
-    ppmacro *macro;
-    lex_ctx_t ctx = { "__builtin__", 0, 0 };
-    ctx.file = source;
-    macro = ppmacro_new(ctx, name);
-    /*vec_push(ftepp->macros, macro);*/
-    util_htset(ftepp->macros, name, macro);
-}
-
-const char *ftepp_get(ftepp_t *ftepp)
-{
-    return ftepp->output_string;
-}
-
-void ftepp_flush(ftepp_t *ftepp)
-{
-    ftepp_flush_do(ftepp);
-}
-
-void ftepp_finish(ftepp_t *ftepp)
-{
-    if (!ftepp)
-        return;
-    ftepp_delete(ftepp);
-}
diff --git a/ftepp.cpp b/ftepp.cpp
new file mode 100644 (file)
index 0000000..1c80d9e
--- /dev/null
+++ b/ftepp.cpp
@@ -0,0 +1,1954 @@
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "gmqcc.h"
+#include "lexer.h"
+
+#define HT_MACROS 1024
+
+typedef struct {
+    bool on;
+    bool was_on;
+    bool had_else;
+} ppcondition;
+
+typedef struct {
+    int   token;
+    char *value;
+    /* a copy from the lexer */
+    union {
+        vec3_t v;
+        int    i;
+        double f;
+        int    t; /* type */
+    } constval;
+} pptoken;
+
+typedef struct {
+    lex_ctx_t ctx;
+
+    char   *name;
+    char  **params;
+    /* yes we need an extra flag since `#define FOO x` is not the same as `#define FOO() x` */
+    bool    has_params;
+    bool    variadic;
+
+    pptoken **output;
+} ppmacro;
+
+typedef struct ftepp_s {
+    lex_file    *lex;
+    int          token;
+    unsigned int errors;
+
+    bool         output_on;
+    ppcondition *conditions;
+    /*ppmacro    **macros;*/
+    ht           macros;  /* hashtable<string, ppmacro*> */
+    char        *output_string;
+
+    char        *itemname;
+    char        *includename;
+    bool         in_macro;
+
+    uint32_t predef_countval;
+    uint32_t predef_randval;
+} ftepp_t;
+
+/* __DATE__ */
+static char *ftepp_predef_date(ftepp_t *context) {
+    const struct tm *itime = NULL;
+    char            *value = (char*)mem_a(82);
+    time_t           rtime;
+
+    (void)context;
+
+    time (&rtime);
+    itime = util_localtime(&rtime);
+    strftime(value, 82, "\"%b %d %Y\"", itime);
+
+    return value;
+}
+
+/* __TIME__ */
+static char *ftepp_predef_time(ftepp_t *context) {
+    const struct tm *itime = NULL;
+    char            *value = (char*)mem_a(82);
+    time_t           rtime;
+
+    (void)context;
+
+    time (&rtime);
+    itime = util_localtime(&rtime);
+    strftime(value, 82, "\"%X\"", itime);
+
+    return value;
+}
+
+/* __LINE__ */
+static char *ftepp_predef_line(ftepp_t *context) {
+    char *value;
+
+    util_asprintf(&value, "%d", (int)context->lex->line);
+    return value;
+}
+/* __FILE__ */
+static char *ftepp_predef_file(ftepp_t *context) {
+    size_t length = strlen(context->lex->name) + 3; /* two quotes and a terminator */
+    char  *value  = (char*)mem_a(length);
+
+    util_snprintf(value, length, "\"%s\"", context->lex->name);
+    return value;
+}
+/* __COUNTER_LAST__ */
+static char *ftepp_predef_counterlast(ftepp_t *context) {
+    char *value;
+    util_asprintf(&value, "%u", context->predef_countval);
+    return value;
+}
+/* __COUNTER__ */
+static char *ftepp_predef_counter(ftepp_t *context) {
+    char *value;
+
+    context->predef_countval ++;
+    util_asprintf(&value, "%u", context->predef_countval);
+
+    return value;
+}
+/* __RANDOM__ */
+static char *ftepp_predef_random(ftepp_t *context) {
+    char *value;
+
+    context->predef_randval = (util_rand() % 0xFF) + 1;
+    util_asprintf(&value, "%u", context->predef_randval);
+    return value;
+}
+/* __RANDOM_LAST__ */
+static char *ftepp_predef_randomlast(ftepp_t *context) {
+    char *value;
+
+    util_asprintf(&value, "%u", context->predef_randval);
+    return value;
+}
+/* __TIMESTAMP__ */
+static char *ftepp_predef_timestamp(ftepp_t *context) {
+    struct stat finfo;
+    const char *find;
+    char       *value;
+    size_t      size;
+
+    if (stat(context->lex->name, &finfo))
+        return util_strdup("\"<failed to determine timestamp>\"");
+
+    find = util_ctime(&finfo.st_mtime);
+    value = (char*)mem_a(strlen(find) + 1);
+    memcpy(&value[1], find, (size = strlen(find)) - 1);
+
+    value[0]    = '"';
+    value[size] = '"';
+
+    return value;
+}
+
+typedef struct {
+    const char   *name;
+    char       *(*func)(ftepp_t *);
+} ftepp_predef_t;
+
+static const ftepp_predef_t ftepp_predefs[] = {
+    { "__LINE__",         &ftepp_predef_line        },
+    { "__FILE__",         &ftepp_predef_file        },
+    { "__COUNTER__",      &ftepp_predef_counter     },
+    { "__COUNTER_LAST__", &ftepp_predef_counterlast },
+    { "__RANDOM__",       &ftepp_predef_random      },
+    { "__RANDOM_LAST__",  &ftepp_predef_randomlast  },
+    { "__DATE__",         &ftepp_predef_date        },
+    { "__TIME__",         &ftepp_predef_time        },
+    { "__TIME_STAMP__",   &ftepp_predef_timestamp   }
+};
+
+static GMQCC_INLINE size_t ftepp_predef_index(const char *name) {
+    /* no hashtable here, we simply check for one to exist the naive way */
+    size_t i;
+    for(i = 1; i < GMQCC_ARRAY_COUNT(ftepp_predefs) + 1; i++)
+        if (!strcmp(ftepp_predefs[i-1].name, name))
+            return i;
+    return 0;
+}
+
+bool ftepp_predef_exists(const char *name);
+bool ftepp_predef_exists(const char *name) {
+    return ftepp_predef_index(name) != 0;
+}
+
+/* singleton because we're allowed */
+static GMQCC_INLINE char *(*ftepp_predef(const char *name))(ftepp_t *context) {
+    size_t i = ftepp_predef_index(name);
+    return (i != 0) ? ftepp_predefs[i-1].func : NULL;
+}
+
+#define ftepp_tokval(f) ((f)->lex->tok.value)
+#define ftepp_ctx(f)    ((f)->lex->tok.ctx)
+
+static void ftepp_errorat(ftepp_t *ftepp, lex_ctx_t ctx, const char *fmt, ...)
+{
+    va_list ap;
+
+    ftepp->errors++;
+
+    va_start(ap, fmt);
+    con_cvprintmsg(ctx, LVL_ERROR, "error", fmt, ap);
+    va_end(ap);
+}
+
+static void ftepp_error(ftepp_t *ftepp, const char *fmt, ...)
+{
+    va_list ap;
+
+    ftepp->errors++;
+
+    va_start(ap, fmt);
+    con_cvprintmsg(ftepp->lex->tok.ctx, LVL_ERROR, "error", fmt, ap);
+    va_end(ap);
+}
+
+static bool GMQCC_WARN ftepp_warn(ftepp_t *ftepp, int warntype, const char *fmt, ...)
+{
+    bool    r;
+    va_list ap;
+
+    va_start(ap, fmt);
+    r = vcompile_warning(ftepp->lex->tok.ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+static pptoken *pptoken_make(ftepp_t *ftepp)
+{
+    pptoken *token = (pptoken*)mem_a(sizeof(pptoken));
+    token->token = ftepp->token;
+    token->value = util_strdup(ftepp_tokval(ftepp));
+    memcpy(&token->constval, &ftepp->lex->tok.constval, sizeof(token->constval));
+    return token;
+}
+
+static GMQCC_INLINE void pptoken_delete(pptoken *self)
+{
+    mem_d(self->value);
+    mem_d(self);
+}
+
+static ppmacro *ppmacro_new(lex_ctx_t ctx, const char *name)
+{
+    ppmacro *macro = (ppmacro*)mem_a(sizeof(ppmacro));
+
+    (void)ctx;
+    memset(macro, 0, sizeof(*macro));
+    macro->name = util_strdup(name);
+    return macro;
+}
+
+static void ppmacro_delete(ppmacro *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->params); ++i)
+        mem_d(self->params[i]);
+    vec_free(self->params);
+    for (i = 0; i < vec_size(self->output); ++i)
+        pptoken_delete(self->output[i]);
+    vec_free(self->output);
+    mem_d(self->name);
+    mem_d(self);
+}
+
+static ftepp_t* ftepp_new(void)
+{
+    ftepp_t *ftepp;
+
+    ftepp = (ftepp_t*)mem_a(sizeof(*ftepp));
+    memset(ftepp, 0, sizeof(*ftepp));
+
+    ftepp->macros          = util_htnew(HT_MACROS);
+    ftepp->output_on       = true;
+    ftepp->predef_countval = 0;
+    ftepp->predef_randval  = 0;
+
+    return ftepp;
+}
+
+static GMQCC_INLINE void ftepp_flush_do(ftepp_t *self)
+{
+    vec_free(self->output_string);
+}
+
+static void ftepp_delete(ftepp_t *self)
+{
+    ftepp_flush_do(self);
+    if (self->itemname)
+        mem_d(self->itemname);
+    if (self->includename)
+        vec_free(self->includename);
+
+    util_htrem(self->macros, (void (*)(void*))&ppmacro_delete);
+
+    vec_free(self->conditions);
+    if (self->lex)
+        lex_close(self->lex);
+    mem_d(self);
+}
+
+static void ftepp_out(ftepp_t *ftepp, const char *str, bool ignore_cond)
+{
+    if (ignore_cond || ftepp->output_on)
+    {
+        size_t len;
+        char  *data;
+        len = strlen(str);
+        data = vec_add(ftepp->output_string, len);
+        memcpy(data, str, len);
+    }
+}
+
+static GMQCC_INLINE void ftepp_update_output_condition(ftepp_t *ftepp)
+{
+    size_t i;
+    ftepp->output_on = true;
+    for (i = 0; i < vec_size(ftepp->conditions); ++i)
+        ftepp->output_on = ftepp->output_on && ftepp->conditions[i].on;
+}
+
+static GMQCC_INLINE ppmacro* ftepp_macro_find(ftepp_t *ftepp, const char *name)
+{
+    return (ppmacro*)util_htget(ftepp->macros, name);
+}
+
+static GMQCC_INLINE void ftepp_macro_delete(ftepp_t *ftepp, const char *name)
+{
+    util_htrm(ftepp->macros, name, (void (*)(void*))&ppmacro_delete);
+}
+
+static GMQCC_INLINE int ftepp_next(ftepp_t *ftepp)
+{
+    return (ftepp->token = lex_do(ftepp->lex));
+}
+
+/* Important: this does not skip newlines! */
+static bool ftepp_skipspace(ftepp_t *ftepp)
+{
+    if (ftepp->token != TOKEN_WHITE)
+        return true;
+    while (ftepp_next(ftepp) == TOKEN_WHITE) {}
+    if (ftepp->token >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected end of preprocessor directive");
+        return false;
+    }
+    return true;
+}
+
+/* this one skips EOLs as well */
+static bool ftepp_skipallwhite(ftepp_t *ftepp)
+{
+    if (ftepp->token != TOKEN_WHITE && ftepp->token != TOKEN_EOL)
+        return true;
+    do {
+        ftepp_next(ftepp);
+    } while (ftepp->token == TOKEN_WHITE || ftepp->token == TOKEN_EOL);
+    if (ftepp->token >= TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected end of preprocessor directive");
+        return false;
+    }
+    return true;
+}
+
+/**
+ * The huge macro parsing code...
+ */
+static bool ftepp_define_params(ftepp_t *ftepp, ppmacro *macro)
+{
+    do {
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+        if (ftepp->token == ')')
+            break;
+        switch (ftepp->token) {
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                vec_push(macro->params, util_strdup(ftepp_tokval(ftepp)));
+                break;
+            case TOKEN_DOTS:
+                macro->variadic = true;
+                break;
+            default:
+                ftepp_error(ftepp, "unexpected token in parameter list");
+                return false;
+        }
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+        if (macro->variadic && ftepp->token != ')') {
+            ftepp_error(ftepp, "cannot have parameters after the variadic parameters");
+            return false;
+        }
+    } while (ftepp->token == ',');
+
+    if (ftepp->token != ')') {
+        ftepp_error(ftepp, "expected closing paren after macro parameter list");
+        return false;
+    }
+    ftepp_next(ftepp);
+    /* skipspace happens in ftepp_define */
+    return true;
+}
+
+static bool ftepp_define_body(ftepp_t *ftepp, ppmacro *macro)
+{
+    pptoken *ptok;
+    while (ftepp->token != TOKEN_EOL && ftepp->token < TOKEN_EOF) {
+        bool   subscript = false;
+        size_t index     = 0;
+        if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_ARGS__")) {
+            subscript = !!(ftepp_next(ftepp) == '#');
+
+            if (subscript && ftepp_next(ftepp) != '#') {
+                ftepp_error(ftepp, "expected `##` in __VA_ARGS__ for subscripting");
+                return false;
+            } else if (subscript) {
+                if (ftepp_next(ftepp) == '[') {
+                    if (ftepp_next(ftepp) != TOKEN_INTCONST) {
+                        ftepp_error(ftepp, "expected index for __VA_ARGS__ subscript");
+                        return false;
+                    }
+
+                    index = (int)strtol(ftepp_tokval(ftepp), NULL, 10);
+
+                    if (ftepp_next(ftepp) != ']') {
+                        ftepp_error(ftepp, "expected `]` in __VA_ARGS__ subscript");
+                        return false;
+                    }
+
+                    /*
+                     * mark it as an array to be handled later as such and not
+                     * as traditional __VA_ARGS__
+                     */
+                    ftepp->token = TOKEN_VA_ARGS_ARRAY;
+                    ptok = pptoken_make(ftepp);
+                    ptok->constval.i = index;
+                    vec_push(macro->output, ptok);
+                    ftepp_next(ftepp);
+                } else {
+                    ftepp_error(ftepp, "expected `[` for subscripting of __VA_ARGS__");
+                    return false;
+                }
+            } else {
+                int old = ftepp->token;
+                ftepp->token = TOKEN_VA_ARGS;
+                ptok = pptoken_make(ftepp);
+                vec_push(macro->output, ptok);
+                ftepp->token = old;
+            }
+        }
+        else if (macro->variadic && !strcmp(ftepp_tokval(ftepp), "__VA_COUNT__")) {
+            ftepp->token = TOKEN_VA_COUNT;
+            ptok         = pptoken_make(ftepp);
+            vec_push(macro->output, ptok);
+            ftepp_next(ftepp);
+        } else {
+            ptok = pptoken_make(ftepp);
+            vec_push(macro->output, ptok);
+            ftepp_next(ftepp);
+        }
+    }
+    /* recursive expansion can cause EOFs here */
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "unexpected junk after macro or unexpected end of file");
+        return false;
+    }
+    return true;
+}
+
+static const char *ftepp_math_constants[][2] = {
+    { "M_E",        "2.7182818284590452354"  }, /* e          */
+    { "M_LOG2E",    "1.4426950408889634074"  }, /* log_2 e    */
+    { "M_LOG10E",   "0.43429448190325182765" }, /* log_10 e   */
+    { "M_LN2",      "0.69314718055994530942" }, /* log_e 2    */
+    { "M_LN10",     "2.30258509299404568402" }, /* log_e 10   */
+    { "M_PI",       "3.14159265358979323846" }, /* pi         */
+    { "M_PI_2",     "1.57079632679489661923" }, /* pi/2       */
+    { "M_PI_4",     "0.78539816339744830962" }, /* pi/4       */
+    { "M_1_PI",     "0.31830988618379067154" }, /* 1/pi       */
+    { "M_2_PI",     "0.63661977236758134308" }, /* 2/pi       */
+    { "M_2_SQRTPI", "1.12837916709551257390" }, /* 2/sqrt(pi) */
+    { "M_SQRT2",    "1.41421356237309504880" }, /* sqrt(2)    */
+    { "M_SQRT1_2",  "0.70710678118654752440" }, /* 1/sqrt(2)  */
+    { "M_TAU",      "6.28318530717958647692" }  /* pi*2       */
+};
+
+static bool ftepp_define(ftepp_t *ftepp)
+{
+    ppmacro *macro = NULL;
+    size_t l = ftepp_ctx(ftepp).line;
+    size_t i;
+    bool   mathconstant = false;
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
+                for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++) {
+                    if (!strcmp(ftepp_math_constants[i][0], ftepp_tokval(ftepp))) {
+                        mathconstant = true;
+                        break;
+                    }
+                }
+            }
+
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+
+            if (OPTS_FLAG(FTEPP_MATHDEFS)) {
+                /* user defined ones take precedence */
+                if (macro && mathconstant) {
+                    ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+                    macro = NULL;
+                }
+            }
+
+            if (macro && ftepp->output_on) {
+                if (ftepp_warn(ftepp, WARN_CPP, "redefining `%s`", ftepp_tokval(ftepp)))
+                    return false;
+                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+            }
+            macro = ppmacro_new(ftepp_ctx(ftepp), ftepp_tokval(ftepp));
+            break;
+        default:
+            ftepp_error(ftepp, "expected macro name");
+            return false;
+    }
+
+    (void)ftepp_next(ftepp);
+
+    if (ftepp->token == '(') {
+        macro->has_params = true;
+        if (!ftepp_define_params(ftepp, macro)) {
+            ppmacro_delete(macro);
+            return false;
+        }
+    }
+
+    if (!ftepp_skipspace(ftepp)) {
+        ppmacro_delete(macro);
+        return false;
+    }
+
+    if (!ftepp_define_body(ftepp, macro)) {
+        ppmacro_delete(macro);
+        return false;
+    }
+
+    if (ftepp->output_on)
+        util_htset(ftepp->macros, macro->name, (void*)macro);
+    else {
+        ppmacro_delete(macro);
+    }
+
+    for (; l < ftepp_ctx(ftepp).line; ++l)
+        ftepp_out(ftepp, "\n", true);
+    return true;
+}
+
+/**
+ * When a macro is used we have to handle parameters as well
+ * as special-concatenation via ## or stringification via #
+ *
+ * Note: parenthesis can nest, so FOO((a),b) is valid, but only
+ * this kind of parens. Curly braces or [] don't count towards the
+ * paren-level.
+ */
+typedef struct {
+    pptoken **tokens;
+} macroparam;
+
+static void macroparam_clean(macroparam *self)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->tokens); ++i)
+        pptoken_delete(self->tokens[i]);
+    vec_free(self->tokens);
+}
+
+/* need to leave the last token up */
+static bool ftepp_macro_call_params(ftepp_t *ftepp, macroparam **out_params)
+{
+    macroparam *params = NULL;
+    pptoken    *ptok;
+    macroparam  mp;
+    size_t      parens = 0;
+    size_t      i;
+
+    if (!ftepp_skipallwhite(ftepp))
+        return false;
+    while (ftepp->token != ')') {
+        mp.tokens = NULL;
+        if (!ftepp_skipallwhite(ftepp))
+            return false;
+        while (parens || ftepp->token != ',') {
+            if (ftepp->token == '(')
+                ++parens;
+            else if (ftepp->token == ')') {
+                if (!parens)
+                    break;
+                --parens;
+            }
+            ptok = pptoken_make(ftepp);
+            vec_push(mp.tokens, ptok);
+            if (ftepp_next(ftepp) >= TOKEN_EOF) {
+                ftepp_error(ftepp, "unexpected end of file in macro call");
+                goto on_error;
+            }
+        }
+        vec_push(params, mp);
+        mp.tokens = NULL;
+        if (ftepp->token == ')')
+            break;
+        if (ftepp->token != ',') {
+            ftepp_error(ftepp, "expected closing paren or comma in macro call");
+            goto on_error;
+        }
+        if (ftepp_next(ftepp) >= TOKEN_EOF) {
+            ftepp_error(ftepp, "unexpected end of file in macro call");
+            goto on_error;
+        }
+    }
+    *out_params = params;
+    return true;
+
+on_error:
+    if (mp.tokens)
+        macroparam_clean(&mp);
+    for (i = 0; i < vec_size(params); ++i)
+        macroparam_clean(&params[i]);
+    vec_free(params);
+    return false;
+}
+
+static bool macro_params_find(ppmacro *macro, const char *name, size_t *idx)
+{
+    size_t i;
+    for (i = 0; i < vec_size(macro->params); ++i) {
+        if (!strcmp(macro->params[i], name)) {
+            *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void ftepp_stringify_token(ftepp_t *ftepp, pptoken *token)
+{
+    char        chs[2];
+    const char *ch;
+    chs[1] = 0;
+    switch (token->token) {
+        case TOKEN_STRINGCONST:
+            ch = token->value;
+            while (*ch) {
+                /* in preprocessor mode strings already are string,
+                 * so we don't get actual newline bytes here.
+                 * Still need to escape backslashes and quotes.
+                 */
+                switch (*ch) {
+                    case '\\': ftepp_out(ftepp, "\\\\", false); break;
+                    case '"':  ftepp_out(ftepp, "\\\"", false); break;
+                    default:
+                        chs[0] = *ch;
+                        ftepp_out(ftepp, chs, false);
+                        break;
+                }
+                ++ch;
+            }
+            break;
+        /*case TOKEN_WHITE:
+            ftepp_out(ftepp, " ", false);
+            break;*/
+        case TOKEN_EOL:
+            ftepp_out(ftepp, "\\n", false);
+            break;
+        default:
+            ftepp_out(ftepp, token->value, false);
+            break;
+    }
+}
+
+static void ftepp_stringify(ftepp_t *ftepp, macroparam *param)
+{
+    size_t i;
+    ftepp_out(ftepp, "\"", false);
+    for (i = 0; i < vec_size(param->tokens); ++i)
+        ftepp_stringify_token(ftepp, param->tokens[i]);
+    ftepp_out(ftepp, "\"", false);
+}
+
+static void ftepp_recursion_header(ftepp_t *ftepp)
+{
+    ftepp_out(ftepp, "\n#pragma push(line)\n", false);
+}
+
+static void ftepp_recursion_footer(ftepp_t *ftepp)
+{
+    ftepp_out(ftepp, "\n#pragma pop(line)\n", false);
+}
+
+static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline);
+static void ftepp_param_out(ftepp_t *ftepp, macroparam *param)
+{
+    size_t   i;
+    pptoken *out;
+    for (i = 0; i < vec_size(param->tokens); ++i) {
+        out = param->tokens[i];
+        if (out->token == TOKEN_EOL)
+            ftepp_out(ftepp, "\n", false);
+        else {
+            ppmacro *find = ftepp_macro_find(ftepp, out->value);
+            if (OPTS_FLAG(FTEPP_INDIRECT_EXPANSION) && find && !find->has_params)
+                ftepp_macro_expand(ftepp, find, NULL, false);
+            else
+                ftepp_out(ftepp, out->value, false);
+        }
+    }
+}
+
+static bool ftepp_preprocess(ftepp_t *ftepp);
+static bool ftepp_macro_expand(ftepp_t *ftepp, ppmacro *macro, macroparam *params, bool resetline)
+{
+    char     *buffer       = NULL;
+    char     *old_string   = ftepp->output_string;
+    char     *inner_string;
+    lex_file *old_lexer    = ftepp->lex;
+    size_t    vararg_start = vec_size(macro->params);
+    bool      retval       = true;
+    bool      has_newlines;
+    size_t    varargs;
+
+    size_t    o, pi;
+    lex_file *inlex;
+
+    bool      old_inmacro;
+    bool      strip = false;
+
+    int nextok;
+
+    if (vararg_start < vec_size(params))
+        varargs = vec_size(params) - vararg_start;
+    else
+        varargs = 0;
+
+    /* really ... */
+    if (!vec_size(macro->output))
+        return true;
+
+    ftepp->output_string = NULL;
+    for (o = 0; o < vec_size(macro->output); ++o) {
+        pptoken *out = macro->output[o];
+        switch (out->token) {
+            case TOKEN_VA_ARGS:
+                if (!macro->variadic) {
+                    ftepp_error(ftepp, "internal preprocessor error: TOKEN_VA_ARGS in non-variadic macro");
+                    vec_free(old_string);
+                    return false;
+                }
+                if (!varargs)
+                    break;
+
+                pi = 0;
+                ftepp_param_out(ftepp, &params[pi + vararg_start]);
+                for (++pi; pi < varargs; ++pi) {
+                    ftepp_out(ftepp, ", ", false);
+                    ftepp_param_out(ftepp, &params[pi + vararg_start]);
+                }
+                break;
+
+            case TOKEN_VA_ARGS_ARRAY:
+                if ((size_t)out->constval.i >= varargs) {
+                    ftepp_error(ftepp, "subscript of `[%u]` is out of bounds for `__VA_ARGS__`", out->constval.i);
+                    vec_free(old_string);
+                    return false;
+                }
+
+                ftepp_param_out(ftepp, &params[out->constval.i + vararg_start]);
+                break;
+
+            case TOKEN_VA_COUNT:
+                util_asprintf(&buffer, "%d", varargs);
+                ftepp_out(ftepp, buffer, false);
+                mem_d(buffer);
+                break;
+
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                if (!macro_params_find(macro, out->value, &pi)) {
+                    ftepp_out(ftepp, out->value, false);
+                    break;
+                } else
+                    ftepp_param_out(ftepp, &params[pi]);
+                break;
+            case '#':
+                if (o + 1 < vec_size(macro->output)) {
+                    nextok = macro->output[o+1]->token;
+                    if (nextok == '#') {
+                        /* raw concatenation */
+                        ++o;
+                        strip = true;
+                        break;
+                    }
+                    if ( (nextok == TOKEN_IDENT    ||
+                          nextok == TOKEN_KEYWORD  ||
+                          nextok == TOKEN_TYPENAME) &&
+                        macro_params_find(macro, macro->output[o+1]->value, &pi))
+                    {
+                        ++o;
+
+                        ftepp_stringify(ftepp, &params[pi]);
+                        break;
+                    }
+                }
+                ftepp_out(ftepp, "#", false);
+                break;
+            case TOKEN_EOL:
+                ftepp_out(ftepp, "\n", false);
+                break;
+            default:
+                buffer = out->value;
+                #define buffer_stripable(X) ((X) == ' ' || (X) == '\t')
+                if (vec_size(macro->output) > o + 1 && macro->output[o+1]->token == '#' && buffer_stripable(*buffer))
+                    buffer++;
+                if (strip) {
+                    while (buffer_stripable(*buffer)) buffer++;
+                    strip = false;
+                }
+                ftepp_out(ftepp, buffer, false);
+                break;
+        }
+    }
+    vec_push(ftepp->output_string, 0);
+    /* Now run the preprocessor recursively on this string buffer */
+    /*
+    printf("__________\n%s\n=========\n", ftepp->output_string);
+    */
+    inlex = lex_open_string(ftepp->output_string, vec_size(ftepp->output_string)-1, ftepp->lex->name);
+    if (!inlex) {
+        ftepp_error(ftepp, "internal error: failed to instantiate lexer");
+        retval = false;
+        goto cleanup;
+    }
+
+    inlex->line  = ftepp->lex->line;
+    inlex->sline = ftepp->lex->sline;
+    ftepp->lex   = inlex;
+
+    old_inmacro     = ftepp->in_macro;
+    ftepp->in_macro = true;
+    ftepp->output_string = NULL;
+    if (!ftepp_preprocess(ftepp)) {
+        ftepp->in_macro = old_inmacro;
+        vec_free(ftepp->lex->open_string);
+        vec_free(ftepp->output_string);
+        lex_close(ftepp->lex);
+        retval = false;
+        goto cleanup;
+    }
+    ftepp->in_macro = old_inmacro;
+    vec_free(ftepp->lex->open_string);
+    lex_close(ftepp->lex);
+
+    inner_string = ftepp->output_string;
+    ftepp->output_string = old_string;
+
+    has_newlines = (strchr(inner_string, '\n') != NULL);
+
+    if (has_newlines && !old_inmacro)
+        ftepp_recursion_header(ftepp);
+
+    vec_append(ftepp->output_string, vec_size(inner_string), inner_string);
+    vec_free(inner_string);
+
+    if (has_newlines && !old_inmacro)
+        ftepp_recursion_footer(ftepp);
+
+    if (resetline && !ftepp->in_macro) {
+        char lineno[128];
+        util_snprintf(lineno, 128, "\n#pragma line(%lu)\n", (unsigned long)(old_lexer->sline));
+        ftepp_out(ftepp, lineno, false);
+    }
+
+    old_string = ftepp->output_string;
+cleanup:
+    ftepp->lex           = old_lexer;
+    ftepp->output_string = old_string;
+    return retval;
+}
+
+static bool ftepp_macro_call(ftepp_t *ftepp, ppmacro *macro)
+{
+    size_t     o;
+    macroparam *params = NULL;
+    bool        retval = true;
+    size_t      paramline;
+
+    if (!macro->has_params) {
+        if (!ftepp_macro_expand(ftepp, macro, NULL, false))
+            return false;
+        ftepp_next(ftepp);
+        return true;
+    }
+    ftepp_next(ftepp);
+
+    if (!ftepp_skipallwhite(ftepp))
+        return false;
+
+    if (ftepp->token != '(') {
+        ftepp_error(ftepp, "expected macro parameters in parenthesis");
+        return false;
+    }
+
+    ftepp_next(ftepp);
+    paramline = ftepp->lex->sline;
+    if (!ftepp_macro_call_params(ftepp, &params))
+        return false;
+
+    if ( vec_size(params) < vec_size(macro->params) ||
+        (vec_size(params) > vec_size(macro->params) && !macro->variadic) )
+    {
+        ftepp_error(ftepp, "macro %s expects%s %u paramteters, %u provided", macro->name,
+                    (macro->variadic ? " at least" : ""),
+                    (unsigned int)vec_size(macro->params),
+                    (unsigned int)vec_size(params));
+        retval = false;
+        goto cleanup;
+    }
+
+    if (!ftepp_macro_expand(ftepp, macro, params, (paramline != ftepp->lex->sline)))
+        retval = false;
+    ftepp_next(ftepp);
+
+cleanup:
+    for (o = 0; o < vec_size(params); ++o)
+        macroparam_clean(&params[o]);
+    vec_free(params);
+    return retval;
+}
+
+/**
+ * #if - the FTEQCC way:
+ *    defined(FOO) => true if FOO was #defined regardless of parameters or contents
+ *    <numbers>    => True if the number is not 0
+ *    !<factor>    => True if the factor yields false
+ *    !!<factor>   => ERROR on 2 or more unary nots
+ *    <macro>      => becomes the macro's FIRST token regardless of parameters
+ *    <e> && <e>   => True if both expressions are true
+ *    <e> || <e>   => True if either expression is true
+ *    <string>     => False
+ *    <ident>      => False (remember for macros the <macro> rule applies instead)
+ * Unary + and - are weird and wrong in fteqcc so we don't allow them
+ * parenthesis in expressions are allowed
+ * parameter lists on macros are errors
+ * No mathematical calculations are executed
+ */
+static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out);
+static bool ftepp_if_op(ftepp_t *ftepp)
+{
+    ftepp->lex->flags.noops = false;
+    ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    ftepp->lex->flags.noops = true;
+    return true;
+}
+static bool ftepp_if_value(ftepp_t *ftepp, bool *out, double *value_out)
+{
+    ppmacro *macro;
+    bool     wasnot = false;
+    bool     wasneg = false;
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    while (ftepp->token == '!') {
+        wasnot = true;
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+    }
+
+    if (ftepp->token == TOKEN_OPERATOR && !strcmp(ftepp_tokval(ftepp), "-"))
+    {
+        wasneg = true;
+        ftepp_next(ftepp);
+        if (!ftepp_skipspace(ftepp))
+            return false;
+    }
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            if (!strcmp(ftepp_tokval(ftepp), "defined")) {
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != '(') {
+                    ftepp_error(ftepp, "`defined` keyword in #if requires a macro name in parenthesis");
+                    return false;
+                }
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != TOKEN_IDENT &&
+                    ftepp->token != TOKEN_TYPENAME &&
+                    ftepp->token != TOKEN_KEYWORD)
+                {
+                    ftepp_error(ftepp, "defined() used on an unexpected token type");
+                    return false;
+                }
+                macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+                *out = !!macro;
+                ftepp_next(ftepp);
+                if (!ftepp_skipspace(ftepp))
+                    return false;
+                if (ftepp->token != ')') {
+                    ftepp_error(ftepp, "expected closing paren");
+                    return false;
+                }
+                break;
+            }
+
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+            if (!macro || !vec_size(macro->output)) {
+                *out = false;
+                *value_out = 0;
+            } else {
+                /* This does not expand recursively! */
+                switch (macro->output[0]->token) {
+                    case TOKEN_INTCONST:
+                        *value_out = macro->output[0]->constval.i;
+                        *out = !!(macro->output[0]->constval.i);
+                        break;
+                    case TOKEN_FLOATCONST:
+                        *value_out = macro->output[0]->constval.f;
+                        *out = !!(macro->output[0]->constval.f);
+                        break;
+                    default:
+                        *out = false;
+                        break;
+                }
+            }
+            break;
+        case TOKEN_STRINGCONST:
+            *value_out = 0;
+            *out = false;
+            break;
+        case TOKEN_INTCONST:
+            *value_out = ftepp->lex->tok.constval.i;
+            *out = !!(ftepp->lex->tok.constval.i);
+            break;
+        case TOKEN_FLOATCONST:
+            *value_out = ftepp->lex->tok.constval.f;
+            *out = !!(ftepp->lex->tok.constval.f);
+            break;
+
+        case '(':
+            ftepp_next(ftepp);
+            if (!ftepp_if_expr(ftepp, out, value_out))
+                return false;
+            if (ftepp->token != ')') {
+                ftepp_error(ftepp, "expected closing paren in #if expression");
+                return false;
+            }
+            break;
+
+        default:
+            ftepp_error(ftepp, "junk in #if: `%s` ...", ftepp_tokval(ftepp));
+            if (OPTS_OPTION_BOOL(OPTION_DEBUG))
+                ftepp_error(ftepp, "internal: token %i\n", ftepp->token);
+            return false;
+    }
+    if (wasneg)
+        *value_out = -*value_out;
+    if (wasnot) {
+        *out = !*out;
+        *value_out = (*out ? 1 : 0);
+    }
+    return true;
+}
+
+/*
+static bool ftepp_if_nextvalue(ftepp_t *ftepp, bool *out, double *value_out)
+{
+    if (!ftepp_next(ftepp))
+        return false;
+    return ftepp_if_value(ftepp, out, value_out);
+}
+*/
+
+static bool ftepp_if_expr(ftepp_t *ftepp, bool *out, double *value_out)
+{
+    if (!ftepp_if_value(ftepp, out, value_out))
+        return false;
+
+    if (!ftepp_if_op(ftepp))
+        return false;
+
+    if (ftepp->token == ')' || ftepp->token != TOKEN_OPERATOR)
+        return true;
+
+    /* FTEQCC is all right-associative and no precedence here */
+    if (!strcmp(ftepp_tokval(ftepp), "&&") ||
+        !strcmp(ftepp_tokval(ftepp), "||"))
+    {
+        bool next = false;
+        char opc  = ftepp_tokval(ftepp)[0];
+        double nextvalue;
+
+        (void)nextvalue;
+        if (!ftepp_next(ftepp))
+            return false;
+        if (!ftepp_if_expr(ftepp, &next, &nextvalue))
+            return false;
+
+        if (opc == '&')
+            *out = *out && next;
+        else
+            *out = *out || next;
+
+        *value_out = (*out ? 1 : 0);
+        return true;
+    }
+    else if (!strcmp(ftepp_tokval(ftepp), "==") ||
+             !strcmp(ftepp_tokval(ftepp), "!=") ||
+             !strcmp(ftepp_tokval(ftepp), ">=") ||
+             !strcmp(ftepp_tokval(ftepp), "<=") ||
+             !strcmp(ftepp_tokval(ftepp), ">") ||
+             !strcmp(ftepp_tokval(ftepp), "<"))
+    {
+        bool next = false;
+        const char opc0 = ftepp_tokval(ftepp)[0];
+        const char opc1 = ftepp_tokval(ftepp)[1];
+        double other;
+
+        if (!ftepp_next(ftepp))
+            return false;
+        if (!ftepp_if_expr(ftepp, &next, &other))
+            return false;
+
+        if (opc0 == '=')
+            *out = (*value_out == other);
+        else if (opc0 == '!')
+            *out = (*value_out != other);
+        else if (opc0 == '>') {
+            if (opc1 == '=') *out = (*value_out >= other);
+            else             *out = (*value_out > other);
+        }
+        else if (opc0 == '<') {
+            if (opc1 == '=') *out = (*value_out <= other);
+            else             *out = (*value_out < other);
+        }
+        *value_out = (*out ? 1 : 0);
+
+        return true;
+    }
+    else {
+        ftepp_error(ftepp, "junk after #if");
+        return false;
+    }
+}
+
+static bool ftepp_if(ftepp_t *ftepp, ppcondition *cond)
+{
+    bool result = false;
+    double dummy = 0;
+
+    memset(cond, 0, sizeof(*cond));
+    (void)ftepp_next(ftepp);
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    if (ftepp->token == TOKEN_EOL) {
+        ftepp_error(ftepp, "expected expression for #if-directive");
+        return false;
+    }
+
+    if (!ftepp_if_expr(ftepp, &result, &dummy))
+        return false;
+
+    cond->on = result;
+    return true;
+}
+
+/**
+ * ifdef is rather simple
+ */
+static bool ftepp_ifdef(ftepp_t *ftepp, ppcondition *cond)
+{
+    ppmacro *macro;
+    memset(cond, 0, sizeof(*cond));
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+        case TOKEN_KEYWORD:
+            macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+            break;
+        default:
+            ftepp_error(ftepp, "expected macro name");
+            return false;
+    }
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    /* relaxing this condition
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "stray tokens after #ifdef");
+        return false;
+    }
+    */
+    cond->on = !!macro;
+    return true;
+}
+
+/**
+ * undef is also simple
+ */
+static bool ftepp_undef(ftepp_t *ftepp)
+{
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    if (ftepp->output_on) {
+        switch (ftepp->token) {
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+            case TOKEN_KEYWORD:
+                ftepp_macro_delete(ftepp, ftepp_tokval(ftepp));
+                break;
+            default:
+                ftepp_error(ftepp, "expected macro name");
+                return false;
+        }
+    }
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    /* relaxing this condition
+    if (ftepp->token != TOKEN_EOL && ftepp->token != TOKEN_EOF) {
+        ftepp_error(ftepp, "stray tokens after #ifdef");
+        return false;
+    }
+    */
+    return true;
+}
+
+/* Special unescape-string function which skips a leading quote
+ * and stops at a quote, not just at \0
+ */
+static void unescape(const char *str, char *out) {
+    ++str;
+    while (*str && *str != '"') {
+        if (*str == '\\') {
+            ++str;
+            switch (*str) {
+                case '\\': *out++ = *str; break;
+                case '"':  *out++ = *str; break;
+                case 'a':  *out++ = '\a'; break;
+                case 'b':  *out++ = '\b'; break;
+                case 'r':  *out++ = '\r'; break;
+                case 'n':  *out++ = '\n'; break;
+                case 't':  *out++ = '\t'; break;
+                case 'f':  *out++ = '\f'; break;
+                case 'v':  *out++ = '\v'; break;
+                default:
+                    *out++ = '\\';
+                    *out++ = *str;
+                    break;
+            }
+            ++str;
+            continue;
+        }
+
+        *out++ = *str++;
+    }
+    *out = 0;
+}
+
+static char *ftepp_include_find_path(const char *file, const char *pathfile)
+{
+    FILE *fp;
+    char       *filename = NULL;
+    const char *last_slash;
+    size_t      len;
+
+    if (!pathfile)
+        return NULL;
+
+    last_slash = strrchr(pathfile, '/');
+
+    if (last_slash) {
+        len = last_slash - pathfile;
+        memcpy(vec_add(filename, len), pathfile, len);
+        vec_push(filename, '/');
+    }
+
+    len = strlen(file);
+    memcpy(vec_add(filename, len+1), file, len);
+    vec_last(filename) = 0;
+
+    fp = fopen(filename, "rb");
+    if (fp) {
+        fclose(fp);
+        return filename;
+    }
+    vec_free(filename);
+    return NULL;
+}
+
+static char *ftepp_include_find(ftepp_t *ftepp, const char *file)
+{
+    char *filename = NULL;
+
+    filename = ftepp_include_find_path(file, ftepp->includename);
+    if (!filename)
+        filename = ftepp_include_find_path(file, ftepp->itemname);
+    return filename;
+}
+
+static bool ftepp_directive_warning(ftepp_t *ftepp) {
+    char *message = NULL;
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    /* handle the odd non string constant case so it works like C */
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        bool  store   = false;
+        vec_append(message, 8, "#warning");
+        ftepp_next(ftepp);
+        while (ftepp->token != TOKEN_EOL) {
+            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+            ftepp_next(ftepp);
+        }
+        vec_push(message, '\0');
+        if (ftepp->output_on)
+            store = ftepp_warn(ftepp, WARN_CPP, message);
+        else
+            store = false;
+        vec_free(message);
+        return store;
+    }
+
+    if (!ftepp->output_on)
+        return false;
+
+    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    return ftepp_warn(ftepp, WARN_CPP, "#warning %s", ftepp_tokval(ftepp));
+}
+
+static void ftepp_directive_error(ftepp_t *ftepp) {
+    char *message = NULL;
+
+    if (!ftepp_skipspace(ftepp))
+        return;
+
+    /* handle the odd non string constant case so it works like C */
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        vec_append(message, 6, "#error");
+        ftepp_next(ftepp);
+        while (ftepp->token != TOKEN_EOL) {
+            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+            ftepp_next(ftepp);
+        }
+        vec_push(message, '\0');
+        if (ftepp->output_on)
+            ftepp_error(ftepp, message);
+        vec_free(message);
+        return;
+    }
+
+    if (!ftepp->output_on)
+        return;
+
+    unescape  (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    ftepp_error(ftepp, "#error %s", ftepp_tokval(ftepp));
+}
+
+static void ftepp_directive_message(ftepp_t *ftepp) {
+    char *message = NULL;
+
+    if (!ftepp_skipspace(ftepp))
+        return;
+
+    /* handle the odd non string constant case so it works like C */
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        vec_append(message, 8, "#message");
+        ftepp_next(ftepp);
+        while (ftepp->token != TOKEN_EOL) {
+            vec_append(message, strlen(ftepp_tokval(ftepp)), ftepp_tokval(ftepp));
+            ftepp_next(ftepp);
+        }
+        vec_push(message, '\0');
+        if (ftepp->output_on)
+            con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message", message);
+        vec_free(message);
+        return;
+    }
+
+    if (!ftepp->output_on)
+        return;
+
+    unescape     (ftepp_tokval(ftepp), ftepp_tokval(ftepp));
+    con_cprintmsg(ftepp->lex->tok.ctx, LVL_MSG, "message",  ftepp_tokval(ftepp));
+}
+
+/**
+ * Include a file.
+ * FIXME: do we need/want a -I option?
+ * FIXME: what about when dealing with files in subdirectories coming from a progs.src?
+ */
+static bool ftepp_include(ftepp_t *ftepp)
+{
+    lex_file *old_lexer = ftepp->lex;
+    lex_file *inlex;
+    lex_ctx_t ctx;
+    char     lineno[128];
+    char     *filename;
+    char     *parsename = NULL;
+    char     *old_includename;
+
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    if (ftepp->token != TOKEN_STRINGCONST) {
+        ppmacro *macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+        if (macro) {
+            char *backup = ftepp->output_string;
+            ftepp->output_string = NULL;
+            if (ftepp_macro_expand(ftepp, macro, NULL, true)) {
+                parsename = util_strdup(ftepp->output_string);
+                vec_free(ftepp->output_string);
+                ftepp->output_string = backup;
+            } else {
+                ftepp->output_string = backup;
+                ftepp_error(ftepp, "expected filename to include");
+                return false;
+            }
+        } else if (OPTS_FLAG(FTEPP_PREDEFS)) {
+            /* Well it could be a predefine like __LINE__ */
+            char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
+            if (predef) {
+                parsename = predef(ftepp);
+            } else {
+                ftepp_error(ftepp, "expected filename to include");
+                return false;
+            }
+        }
+    }
+
+    if (!ftepp->output_on) {
+        (void)ftepp_next(ftepp);
+        return true;
+    }
+
+    if (parsename)
+        unescape(parsename, parsename);
+    else {
+        char *tokval = ftepp_tokval(ftepp);
+        unescape(tokval, tokval);
+        parsename = util_strdup(tokval);
+    }
+
+    ctx = ftepp_ctx(ftepp);
+    ftepp_out(ftepp, "\n#pragma file(", false);
+    ftepp_out(ftepp, parsename, false);
+    ftepp_out(ftepp, ")\n#pragma line(1)\n", false);
+
+    filename = ftepp_include_find(ftepp, parsename);
+    if (!filename) {
+        ftepp_error(ftepp, "failed to open include file `%s`", parsename);
+        mem_d(parsename);
+        return false;
+    }
+    mem_d(parsename);
+    inlex = lex_open(filename);
+    if (!inlex) {
+        ftepp_error(ftepp, "open failed on include file `%s`", filename);
+        vec_free(filename);
+        return false;
+    }
+    ftepp->lex = inlex;
+    old_includename = ftepp->includename;
+    ftepp->includename = filename;
+    if (!ftepp_preprocess(ftepp)) {
+        vec_free(ftepp->includename);
+        ftepp->includename = old_includename;
+        lex_close(ftepp->lex);
+        ftepp->lex = old_lexer;
+        return false;
+    }
+    vec_free(ftepp->includename);
+    ftepp->includename = old_includename;
+    lex_close(ftepp->lex);
+    ftepp->lex = old_lexer;
+
+    ftepp_out(ftepp, "\n#pragma file(", false);
+    ftepp_out(ftepp, ctx.file, false);
+    util_snprintf(lineno, sizeof(lineno), ")\n#pragma line(%lu)\n", (unsigned long)(ctx.line+1));
+    ftepp_out(ftepp, lineno, false);
+
+    /* skip the line */
+    (void)ftepp_next(ftepp);
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    if (ftepp->token != TOKEN_EOL) {
+        ftepp_error(ftepp, "stray tokens after #include");
+        return false;
+    }
+    (void)ftepp_next(ftepp);
+
+    return true;
+}
+
+/* Basic structure handlers */
+static bool ftepp_else_allowed(ftepp_t *ftepp)
+{
+    if (!vec_size(ftepp->conditions)) {
+        ftepp_error(ftepp, "#else without #if");
+        return false;
+    }
+    if (vec_last(ftepp->conditions).had_else) {
+        ftepp_error(ftepp, "multiple #else for a single #if");
+        return false;
+    }
+    return true;
+}
+
+static GMQCC_INLINE void ftepp_inmacro(ftepp_t *ftepp, const char *hash) {
+    if (ftepp->in_macro)
+        (void)!ftepp_warn(ftepp, WARN_DIRECTIVE_INMACRO, "`#%s` directive in macro", hash);
+}
+
+static bool ftepp_hash(ftepp_t *ftepp)
+{
+    ppcondition cond;
+    ppcondition *pc;
+
+    lex_ctx_t ctx = ftepp_ctx(ftepp);
+
+    if (!ftepp_skipspace(ftepp))
+        return false;
+
+    switch (ftepp->token) {
+        case TOKEN_KEYWORD:
+        case TOKEN_IDENT:
+        case TOKEN_TYPENAME:
+            if (!strcmp(ftepp_tokval(ftepp), "define")) {
+                ftepp_inmacro(ftepp, "define");
+                return ftepp_define(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "undef")) {
+                ftepp_inmacro(ftepp, "undef");
+                return ftepp_undef(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "ifdef")) {
+                ftepp_inmacro(ftepp, "ifdef");
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "ifndef")) {
+                ftepp_inmacro(ftepp, "ifndef");
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.on = !cond.on;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elifdef")) {
+                ftepp_inmacro(ftepp, "elifdef");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elifndef")) {
+                ftepp_inmacro(ftepp, "elifndef");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_ifdef(ftepp, &cond))
+                    return false;
+                cond.on = !cond.on;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "elif")) {
+                ftepp_inmacro(ftepp, "elif");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                if (!ftepp_if(ftepp, &cond))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on     = !pc->was_on && cond.on;
+                pc->was_on = pc->was_on  || pc->on;
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "if")) {
+                ftepp_inmacro(ftepp, "if");
+                if (!ftepp_if(ftepp, &cond))
+                    return false;
+                cond.was_on = cond.on;
+                vec_push(ftepp->conditions, cond);
+                ftepp->output_on = ftepp->output_on && cond.on;
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "else")) {
+                ftepp_inmacro(ftepp, "else");
+                if (!ftepp_else_allowed(ftepp))
+                    return false;
+                pc = &vec_last(ftepp->conditions);
+                pc->on = !pc->was_on;
+                pc->had_else = true;
+                ftepp_next(ftepp);
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "endif")) {
+                ftepp_inmacro(ftepp, "endif");
+                if (!vec_size(ftepp->conditions)) {
+                    ftepp_error(ftepp, "#endif without #if");
+                    return false;
+                }
+                vec_pop(ftepp->conditions);
+                ftepp_next(ftepp);
+                ftepp_update_output_condition(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "include")) {
+                ftepp_inmacro(ftepp, "include");
+                return ftepp_include(ftepp);
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "pragma")) {
+                ftepp_out(ftepp, "#", false);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "warning")) {
+                ftepp_directive_warning(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "error")) {
+                ftepp_directive_error(ftepp);
+                break;
+            }
+            else if (!strcmp(ftepp_tokval(ftepp), "message")) {
+                ftepp_directive_message(ftepp);
+                break;
+            }
+            else {
+                if (ftepp->output_on) {
+                    ftepp_error(ftepp, "unrecognized preprocessor directive: `%s`", ftepp_tokval(ftepp));
+                    return false;
+                } else {
+                    ftepp_next(ftepp);
+                    break;
+                }
+            }
+            /* break; never reached */
+        default:
+            ftepp_error(ftepp, "unexpected preprocessor token: `%s`", ftepp_tokval(ftepp));
+            return false;
+        case TOKEN_EOL:
+            ftepp_errorat(ftepp, ctx, "empty preprocessor directive");
+            return false;
+        case TOKEN_EOF:
+            ftepp_error(ftepp, "missing newline at end of file", ftepp_tokval(ftepp));
+            return false;
+
+        /* Builtins! Don't forget the builtins! */
+        case TOKEN_INTCONST:
+        case TOKEN_FLOATCONST:
+            ftepp_out(ftepp, "#", false);
+            return true;
+    }
+    if (!ftepp_skipspace(ftepp))
+        return false;
+    return true;
+}
+
+static bool ftepp_preprocess(ftepp_t *ftepp)
+{
+    ppmacro *macro;
+    bool     newline = true;
+
+    /* predef stuff */
+    char    *expand  = NULL;
+
+    ftepp->lex->flags.preprocessing = true;
+    ftepp->lex->flags.mergelines    = false;
+    ftepp->lex->flags.noops         = true;
+
+    ftepp_next(ftepp);
+    do
+    {
+        if (ftepp->token >= TOKEN_EOF)
+            break;
+        switch (ftepp->token) {
+            case TOKEN_KEYWORD:
+            case TOKEN_IDENT:
+            case TOKEN_TYPENAME:
+                /* is it a predef? */
+                if (OPTS_FLAG(FTEPP_PREDEFS)) {
+                    char *(*predef)(ftepp_t*) = ftepp_predef(ftepp_tokval(ftepp));
+                    if (predef) {
+                        expand = predef(ftepp);
+                        ftepp_out (ftepp, expand, false);
+                        ftepp_next(ftepp);
+
+                        mem_d(expand);
+                        break;
+                    }
+                }
+
+                if (ftepp->output_on)
+                    macro = ftepp_macro_find(ftepp, ftepp_tokval(ftepp));
+                else
+                    macro = NULL;
+
+                if (!macro) {
+                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                    ftepp_next(ftepp);
+                    break;
+                }
+                if (!ftepp_macro_call(ftepp, macro))
+                    ftepp->token = TOKEN_ERROR;
+                break;
+            case '#':
+                if (!newline) {
+                    ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                    ftepp_next(ftepp);
+                    break;
+                }
+                ftepp->lex->flags.mergelines = true;
+                if (ftepp_next(ftepp) >= TOKEN_EOF) {
+                    ftepp_error(ftepp, "error in preprocessor directive");
+                    ftepp->token = TOKEN_ERROR;
+                    break;
+                }
+                if (!ftepp_hash(ftepp))
+                    ftepp->token = TOKEN_ERROR;
+                ftepp->lex->flags.mergelines = false;
+                break;
+            case TOKEN_EOL:
+                newline = true;
+                ftepp_out(ftepp, "\n", true);
+                ftepp_next(ftepp);
+                break;
+            case TOKEN_WHITE:
+                /* same as default but don't set newline=false */
+                ftepp_out(ftepp, ftepp_tokval(ftepp), true);
+                ftepp_next(ftepp);
+                break;
+            default:
+                newline = false;
+                ftepp_out(ftepp, ftepp_tokval(ftepp), false);
+                ftepp_next(ftepp);
+                break;
+        }
+    } while (!ftepp->errors && ftepp->token < TOKEN_EOF);
+
+    /* force a 0 at the end but don't count it as added to the output */
+    vec_push(ftepp->output_string, 0);
+    vec_shrinkby(ftepp->output_string, 1);
+
+    return (ftepp->token == TOKEN_EOF);
+}
+
+/* Like in parser.c - files keep the previous state so we have one global
+ * preprocessor. Except here we will want to warn about dangling #ifs.
+ */
+static bool ftepp_preprocess_done(ftepp_t *ftepp)
+{
+    bool retval = true;
+    if (vec_size(ftepp->conditions)) {
+        if (ftepp_warn(ftepp, WARN_MULTIFILE_IF, "#if spanning multiple files, is this intended?"))
+            retval = false;
+    }
+    lex_close(ftepp->lex);
+    ftepp->lex = NULL;
+    if (ftepp->itemname) {
+        mem_d(ftepp->itemname);
+        ftepp->itemname = NULL;
+    }
+    return retval;
+}
+
+bool ftepp_preprocess_file(ftepp_t *ftepp, const char *filename)
+{
+    ftepp->lex = lex_open(filename);
+    ftepp->itemname = util_strdup(filename);
+    if (!ftepp->lex) {
+        con_out("failed to open file \"%s\"\n", filename);
+        return false;
+    }
+    if (!ftepp_preprocess(ftepp))
+        return false;
+    return ftepp_preprocess_done(ftepp);
+}
+
+bool ftepp_preprocess_string(ftepp_t *ftepp, const char *name, const char *str)
+{
+    ftepp->lex = lex_open_string(str, strlen(str), name);
+    ftepp->itemname = util_strdup(name);
+    if (!ftepp->lex) {
+        con_out("failed to create lexer for string \"%s\"\n", name);
+        return false;
+    }
+    if (!ftepp_preprocess(ftepp))
+        return false;
+    return ftepp_preprocess_done(ftepp);
+}
+
+
+void ftepp_add_macro(ftepp_t *ftepp, const char *name, const char *value) {
+    char *create = NULL;
+
+    /* use saner path for empty macros */
+    if (!value) {
+        ftepp_add_define(ftepp, "__builtin__", name);
+        return;
+    }
+
+    vec_append(create, 8,           "#define ");
+    vec_append(create, strlen(name), name);
+    vec_push  (create, ' ');
+    vec_append(create, strlen(value), value);
+    vec_push  (create, 0);
+
+    ftepp_preprocess_string(ftepp, "__builtin__", create);
+    vec_free  (create);
+}
+
+ftepp_t *ftepp_create()
+{
+    ftepp_t *ftepp;
+    char minor[32];
+    char major[32];
+    size_t i;
+
+    ftepp = ftepp_new();
+    if (!ftepp)
+        return NULL;
+
+    memset(minor, 0, sizeof(minor));
+    memset(major, 0, sizeof(major));
+
+    /* set the right macro based on the selected standard */
+    ftepp_add_define(ftepp, NULL, "GMQCC");
+    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
+        ftepp_add_define(ftepp, NULL, "__STD_FTEQCC__");
+        /* 1.00 */
+        major[0] = '"';
+        major[1] = '1';
+        major[2] = '"';
+
+        minor[0] = '"';
+        minor[1] = '0';
+        minor[2] = '"';
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
+        ftepp_add_define(ftepp, NULL, "__STD_GMQCC__");
+        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
+        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCCX) {
+        ftepp_add_define(ftepp, NULL, "__STD_QCCX__");
+        util_snprintf(major, 32, "\"%d\"", GMQCC_VERSION_MAJOR);
+        util_snprintf(minor, 32, "\"%d\"", GMQCC_VERSION_MINOR);
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+        ftepp_add_define(ftepp, NULL, "__STD_QCC__");
+        /* 1.0 */
+        major[0] = '"';
+        major[1] = '1';
+        major[2] = '"';
+
+        minor[0] = '"';
+        minor[1] = '0';
+        minor[2] = '"';
+    }
+
+    ftepp_add_macro(ftepp, "__STD_VERSION_MINOR__", minor);
+    ftepp_add_macro(ftepp, "__STD_VERSION_MAJOR__", major);
+
+    /*
+     * We're going to just make __NULL__ nil, which works for 60% of the
+     * cases of __NULL_ for fteqcc.
+     */
+    ftepp_add_macro(ftepp, "__NULL__", "nil");
+
+    /* add all the math constants if they can be */
+    if (OPTS_FLAG(FTEPP_MATHDEFS)) {
+        for (i = 0; i < GMQCC_ARRAY_COUNT(ftepp_math_constants); i++)
+            if (!ftepp_macro_find(ftepp, ftepp_math_constants[i][0]))
+                ftepp_add_macro(ftepp, ftepp_math_constants[i][0], ftepp_math_constants[i][1]);
+    }
+
+    return ftepp;
+}
+
+void ftepp_add_define(ftepp_t *ftepp, const char *source, const char *name)
+{
+    ppmacro *macro;
+    lex_ctx_t ctx = { "__builtin__", 0, 0 };
+    ctx.file = source;
+    macro = ppmacro_new(ctx, name);
+    /*vec_push(ftepp->macros, macro);*/
+    util_htset(ftepp->macros, name, macro);
+}
+
+const char *ftepp_get(ftepp_t *ftepp)
+{
+    return ftepp->output_string;
+}
+
+void ftepp_flush(ftepp_t *ftepp)
+{
+    ftepp_flush_do(ftepp);
+}
+
+void ftepp_finish(ftepp_t *ftepp)
+{
+    if (!ftepp)
+        return;
+    ftepp_delete(ftepp);
+}
diff --git a/intrin.c b/intrin.c
deleted file mode 100644 (file)
index 9ef0207..0000000
--- a/intrin.c
+++ /dev/null
@@ -1,2082 +0,0 @@
-#include <string.h>
-#include "parser.h"
-
-#define intrin_ctx(I) parser_ctx((I)->parser)
-
-static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) {
-    ast_value    *value = NULL;
-    ast_function *func  = NULL;
-    char          buffer[1024];
-    char          stype [1024];
-
-    util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name);
-    util_snprintf(stype,  sizeof(stype),   "<%s>",        type_name[vtype]);
-
-    value                    = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION);
-    value->intrinsic         = true;
-    value->expression.next   = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype);
-    func                     = ast_function_new(intrin_ctx(intrin), buffer, value);
-    value->expression.flags |= AST_FLAG_ERASEABLE;
-
-    *out = value;
-    return func;
-}
-
-static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) {
-    vec_push(intrin->parser->functions, func);
-    vec_push(intrin->parser->globals,   (ast_expression*)value);
-}
-
-#define QC_POW_EPSILON 0.00001f
-
-/*
- * since some intrinsics depend on each other there is the possibility
- * that an intrinsic will fail to get a 'depended' function that a
- * builtin needs, causing some dependency in the chain to have a NULL
- * function. This will cause a segmentation fault at code generation,
- * even though an error was raised. To contiue to allow it (instead
- * of stopping compilation right away). We need to return from the
- * parser, before compilation stops after all the collected errors.
- */
-static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from);
-static ast_expression *intrin_nullfunc(intrin_t *intrin) {
-    ast_value    *value = NULL;
-    ast_function *func  = intrin_value(intrin, &value, NULL, TYPE_VOID);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_isfinite(intrin_t *intrin) {
-    /*
-     * float isfinite(float x) {
-     *     return !(isnan(x) || isinf(x));
-     * }
-     */
-    ast_value    *value     = NULL;
-    ast_value    *x         = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_function *func      = intrin_value(intrin, &value, "isfinite", TYPE_FLOAT);
-    ast_call     *callisnan = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isnan", "isfinite"));
-    ast_call     *callisinf = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isinf", "isfinite"));
-    ast_block    *block     = ast_block_new(intrin_ctx(intrin));
-
-    /* float x; */
-    vec_push(value->expression.params, x);
-
-    /* <callisnan> = isnan(x); */
-    vec_push(callisnan->params, (ast_expression*)x);
-
-    /* <callisinf> = isinf(x); */
-    vec_push(callisinf->params, (ast_expression*)x);
-
-    /* return (!<callisnan> || <callisinf>); */
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_unary_new(
-                intrin_ctx(intrin),
-                INSTR_NOT_F,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_OR,
-                    (ast_expression*)callisnan,
-                    (ast_expression*)callisinf
-                )
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;;
-}
-
-static ast_expression *intrin_isinf(intrin_t *intrin) {
-    /*
-     * float isinf(float x) {
-     *     return (x != 0.0) && (x + x == x);
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_value    *x     = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
-    ast_function *func  = intrin_value(intrin, &value, "isinf", TYPE_FLOAT);
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_AND,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_NE_F,
-                    (ast_expression*)x,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_EQ_F,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_ADD_F,
-                        (ast_expression*)x,
-                        (ast_expression*)x
-                    ),
-                    (ast_expression*)x
-                )
-            )
-        )
-    );
-
-    vec_push(value->expression.params, x);
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_isnan(intrin_t *intrin) {
-    /*
-     * float isnan(float x) {
-     *   float local;
-     *   local = x;
-     *
-     *   return (x != local);
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x",     TYPE_FLOAT);
-    ast_value    *local  = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "isnan", TYPE_FLOAT);
-
-    vec_push(body->locals, local);
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)local,
-            (ast_expression*)arg1
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_NE_F,
-                (ast_expression*)arg1,
-                (ast_expression*)local
-            )
-        )
-    );
-
-    vec_push(value->expression.params, arg1);
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_isnormal(intrin_t *intrin) {
-    /*
-     * float isnormal(float x) {
-     *     return isfinite(x);
-     * }
-     */
-    ast_value    *value         = NULL;
-    ast_call     *callisfinite  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "isfinite", "isnormal"));
-    ast_value    *x             = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body          = ast_block_new(intrin_ctx(intrin));
-    ast_function *func          = intrin_value(intrin, &value, "isnormal", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-    vec_push(callisfinite->params, (ast_expression*)x);
-
-    /* return <callisfinite> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callisfinite
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_signbit(intrin_t *intrin) {
-    /*
-     * float signbit(float x) {
-     *     return (x < 0);
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "signbit", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* return (x < 0); */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_ternary_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LT,
-                    (ast_expression*)x,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)intrin->fold->imm_float[0]
-            )
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_acosh(intrin_t *intrin) {
-    /*
-     * float acosh(float x) {
-     *     return log(x + sqrt((x * x) - 1));
-     * }
-     */
-    ast_value    *value    = NULL;
-    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_call     *calllog  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "acosh"));
-    ast_call     *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "acosh"));
-    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
-    ast_function *func     = intrin_value(intrin, &value, "acosh", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callsqrt> = sqrt((x * x) - 1); */
-    vec_push(callsqrt->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_SUB_F,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)x,
-                (ast_expression*)x
-            ),
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* <calllog> = log(x + <callsqrt>); */
-    vec_push(calllog->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_ADD_F,
-            (ast_expression*)x,
-            (ast_expression*)callsqrt
-        )
-    );
-
-    /* return <calllog>; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)calllog
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_asinh(intrin_t *intrin) {
-    /*
-     * float asinh(float x) {
-     *     return log(x + sqrt((x * x) + 1));
-     * }
-     */
-    ast_value    *value    = NULL;
-    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_call     *calllog  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "asinh"));
-    ast_call     *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "asinh"));
-    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
-    ast_function *func     = intrin_value(intrin, &value, "asinh", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callsqrt> = sqrt((x * x) + 1); */
-    vec_push(callsqrt->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_ADD_F,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)x,
-                (ast_expression*)x
-            ),
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* <calllog> = log(x + <callsqrt>); */
-    vec_push(calllog->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_ADD_F,
-            (ast_expression*)x,
-            (ast_expression*)callsqrt
-        )
-    );
-
-    /* return <calllog>; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)calllog
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_atanh(intrin_t *intrin) {
-    /*
-     * float atanh(float x) {
-     *     return 0.5 * log((1 + x) / (1 - x))
-     * }
-     */
-    ast_value    *value   = NULL;
-    ast_value    *x       = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_call     *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "atanh"));
-    ast_block    *body    = ast_block_new(intrin_ctx(intrin));
-    ast_function *func    = intrin_value(intrin, &value, "atanh", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callog> = log((1 + x) / (1 - x)); */
-    vec_push(calllog->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_DIV_F,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_ADD_F,
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)x
-            ),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_SUB_F,
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)x
-            )
-        )
-    );
-
-    /* return 0.5 * <calllog>; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_MUL_F,
-            (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false),
-            (ast_expression*)calllog
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_exp(intrin_t *intrin) {
-    /*
-     * float exp(float x) {
-     *     float sum = 1.0;
-     *     float acc = 1.0;
-     *     float i;
-     *     for (i = 1; i < 200; ++i)
-     *         sum += (acc *= x / i);
-     *
-     *     return sum;
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_value    *x     = ast_value_new(intrin_ctx(intrin), "x",   TYPE_FLOAT);
-    ast_value    *sum   = ast_value_new(intrin_ctx(intrin), "sum", TYPE_FLOAT);
-    ast_value    *acc   = ast_value_new(intrin_ctx(intrin), "acc", TYPE_FLOAT);
-    ast_value    *i     = ast_value_new(intrin_ctx(intrin), "i",   TYPE_FLOAT);
-    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
-    ast_function *func  = intrin_value(intrin, &value, "exp", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-    vec_push(body->locals, sum);
-    vec_push(body->locals, acc);
-    vec_push(body->locals, i);
-
-    /* sum = 1.0; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)sum,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* acc = 1.0; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)acc,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /*
-     * for (i = 1; i < 200; ++i)
-     *     sum += (acc *= x / i);
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            /* i = 1; */
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)i,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            /* i < 200; */
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LT,
-                (ast_expression*)i,
-                (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false)
-            ),
-            false,
-            NULL,
-            false,
-            /* ++i; */
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_ADD_F,
-                (ast_expression*)i,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            /* sum += (acc *= (x / i)) */
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_ADD_F,
-                (ast_expression*)sum,
-                (ast_expression*)ast_binstore_new(
-                    intrin_ctx(intrin),
-                    INSTR_STORE_F,
-                    INSTR_MUL_F,
-                    (ast_expression*)acc,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_DIV_F,
-                        (ast_expression*)x,
-                        (ast_expression*)i
-                    )
-                )
-            )
-        )
-    );
-
-    /* return sum; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)sum
-        )
-    );
-
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_exp2(intrin_t *intrin) {
-    /*
-     * float exp2(float x) {
-     *     return pow(2, x);
-     * }
-     */
-    ast_value    *value     = NULL;
-    ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp2"));
-    ast_value    *arg1      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body      = ast_block_new(intrin_ctx(intrin));
-    ast_function *func      = intrin_value(intrin, &value, "exp2", TYPE_FLOAT);
-
-    vec_push(value->expression.params, arg1);
-
-    vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
-    vec_push(callpow->params, (ast_expression*)arg1);
-
-    /* return <callpow> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callpow
-        )
-    );
-
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_expm1(intrin_t *intrin) {
-    /*
-     * float expm1(float x) {
-     *     return exp(x) - 1;
-     * }
-     */
-    ast_value    *value    = NULL;
-    ast_call     *callexp  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "exp", "expm1"));
-    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
-    ast_function *func     = intrin_value(intrin, &value, "expm1", TYPE_FLOAT);
-
-    vec_push(value->expression.params, x);
-
-    /* <callexp> = exp(x); */
-    vec_push(callexp->params, (ast_expression*)x);
-
-    /* return <callexp> - 1; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_SUB_F,
-                (ast_expression*)callexp,
-                (ast_expression*)intrin->fold->imm_float[1]
-            )
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_pow(intrin_t *intrin) {
-    /*
-     *
-     * float pow(float base, float exp) {
-     *     float result;
-     *     float low;
-     *     float high;
-     *     float mid;
-     *     float square;
-     *     float accumulate;
-     *
-     *     if (exp == 0.0)
-     *         return 1;
-     *     if (exp == 1.0)
-     *         return base;
-     *     if (exp < 0)
-     *         return 1.0 / pow(base, -exp);
-     *     if (exp >= 1) {
-     *         result = pow(base, exp / 2);
-     *         return result * result;
-     *     }
-     *
-     *     low        = 0.0f;
-     *     high       = 1.0f;
-     *     square     = sqrt(base);
-     *     accumulate = square;
-     *     mid        = high / 2.0f
-     *
-     *     while (fabs(mid - exp) > QC_POW_EPSILON) {
-     *         square = sqrt(square);
-     *         if (mid < exp) {
-     *             low         = mid;
-     *             accumulate *= square;
-     *         } else {
-     *             high        = mid;
-     *             accumulate *= (1.0f / square);
-     *         }
-     *         mid = (low + high) / 2;
-     *     }
-     *     return accumulate;
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT);
-
-    /* prepare some calls for later */
-    ast_call *callpow1  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);                  /* for pow(base, -exp)    */
-    ast_call *callpow2  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);                  /* for pow(vase, exp / 2) */
-    ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base)         */
-    ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square)       */
-    ast_call *callfabs  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp)    */
-
-    /* prepare some blocks for later */
-    ast_block *expgt1       = ast_block_new(intrin_ctx(intrin));
-    ast_block *midltexp     = ast_block_new(intrin_ctx(intrin));
-    ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin));
-    ast_block *whileblock   = ast_block_new(intrin_ctx(intrin));
-
-    /* float pow(float base, float exp) */
-    ast_value    *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT);
-    ast_value    *exp  = ast_value_new(intrin_ctx(intrin), "exp",  TYPE_FLOAT);
-    /* { */
-    ast_block    *body = ast_block_new(intrin_ctx(intrin));
-
-    /*
-     * float result;
-     * float low;
-     * float high;
-     * float square;
-     * float accumulate;
-     * float mid;
-     */
-    ast_value *result     = ast_value_new(intrin_ctx(intrin), "result",     TYPE_FLOAT);
-    ast_value *low        = ast_value_new(intrin_ctx(intrin), "low",        TYPE_FLOAT);
-    ast_value *high       = ast_value_new(intrin_ctx(intrin), "high",       TYPE_FLOAT);
-    ast_value *square     = ast_value_new(intrin_ctx(intrin), "square",     TYPE_FLOAT);
-    ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT);
-    ast_value *mid        = ast_value_new(intrin_ctx(intrin), "mid",        TYPE_FLOAT);
-    vec_push(body->locals, result);
-    vec_push(body->locals, low);
-    vec_push(body->locals, high);
-    vec_push(body->locals, square);
-    vec_push(body->locals, accumulate);
-    vec_push(body->locals, mid);
-
-    vec_push(value->expression.params, base);
-    vec_push(value->expression.params, exp);
-
-    /*
-     * if (exp == 0.0)
-     *     return 1;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_EQ_F,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[0]
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            NULL
-        )
-    );
-
-    /*
-     * if (exp == 1.0)
-     *     return base;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_EQ_F,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)base
-            ),
-            NULL
-        )
-    );
-
-    /* <callpow1> = pow(base, -exp) */
-    vec_push(callpow1->params, (ast_expression*)base);
-    vec_push(callpow1->params,
-        (ast_expression*)ast_unary_new(
-            intrin_ctx(intrin),
-            VINSTR_NEG_F,
-            (ast_expression*)exp
-        )
-    );
-
-    /*
-     * if (exp < 0)
-     *     return 1.0 / <callpow1>;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LT,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[0]
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_DIV_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)callpow1
-                )
-            ),
-            NULL
-        )
-    );
-
-    /* <callpow2> = pow(base, exp / 2) */
-    vec_push(callpow2->params, (ast_expression*)base);
-    vec_push(callpow2->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_DIV_F,
-            (ast_expression*)exp,
-            (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-        )
-    );
-
-    /*
-     * <expgt1> = {
-     *     result = <callpow2>;
-     *     return result * result;
-     * }
-     */
-    vec_push(expgt1->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)result,
-            (ast_expression*)callpow2
-        )
-    );
-    vec_push(expgt1->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)result,
-                (ast_expression*)result
-            )
-        )
-    );
-
-    /*
-     * if (exp >= 1) {
-     *     <expgt1>
-     * }
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GE,
-                (ast_expression*)exp,
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            (ast_expression*)expgt1,
-            NULL
-        )
-    );
-
-    /*
-     * <callsqrt1> = sqrt(base)
-     */
-    vec_push(callsqrt1->params, (ast_expression*)base);
-
-    /*
-     * low        = 0.0f;
-     * high       = 1.0f;
-     * square     = sqrt(base);
-     * accumulate = square;
-     * mid        = high / 2.0f;
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)low,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)high,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)square,
-            (ast_expression*)callsqrt1
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)accumulate,
-            (ast_expression*)square
-        )
-    );
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)mid,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)high,
-                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-            )
-        )
-    );
-
-    /*
-     * <midltexp> = {
-     *     low         = mid;
-     *     accumulate *= square;
-     * }
-     */
-    vec_push(midltexp->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)low,
-            (ast_expression*)mid
-        )
-    );
-    vec_push(midltexp->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)accumulate,
-            (ast_expression*)square
-        )
-    );
-
-    /*
-     * <midltexpelse> = {
-     *     high        = mid;
-     *     accumulate *= (1.0 / square);
-     * }
-     */
-    vec_push(midltexpelse->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)high,
-            (ast_expression*)mid
-        )
-    );
-    vec_push(midltexpelse->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)accumulate,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)intrin->fold->imm_float[1],
-                (ast_expression*)square
-            )
-        )
-    );
-
-    /*
-     * <callsqrt2> = sqrt(square)
-     */
-    vec_push(callsqrt2->params, (ast_expression*)square);
-
-    /*
-     * <whileblock> = {
-     *     square = <callsqrt2>;
-     *     if (mid < exp)
-     *          <midltexp>;
-     *     else
-     *          <midltexpelse>;
-     *
-     *     mid = (low + high) / 2;
-     * }
-     */
-    vec_push(whileblock->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)square,
-            (ast_expression*)callsqrt2
-        )
-    );
-    vec_push(whileblock->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LT,
-                (ast_expression*)mid,
-                (ast_expression*)exp
-            ),
-            (ast_expression*)midltexp,
-            (ast_expression*)midltexpelse
-        )
-    );
-    vec_push(whileblock->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)mid,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)low,
-                    (ast_expression*)high
-                ),
-                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-            )
-        )
-    );
-
-    /*
-     * <callabs> = fabs(mid - exp)
-     */
-    vec_push(callfabs->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_SUB_F,
-            (ast_expression*)mid,
-            (ast_expression*)exp
-        )
-    );
-
-    /*
-     * while (<callfabs>  > epsilon)
-     *     <whileblock>
-     */
-    vec_push(body->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            /* init */
-            NULL,
-            /* pre condition */
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GT,
-                (ast_expression*)callfabs,
-                (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false)
-            ),
-            /* pre not */
-            false,
-            /* post condition */
-            NULL,
-            /* post not */
-            false,
-            /* increment expression */
-            NULL,
-            /* code block */
-            (ast_expression*)whileblock
-        )
-    );
-
-    /* return accumulate */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)accumulate
-        )
-    );
-
-    /* } */
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_mod(intrin_t *intrin) {
-    /*
-     * float mod(float a, float b) {
-     *     float div = a / b;
-     *     float sign = (div < 0.0f) ? -1 : 1;
-     *     return a - b * sign * floor(sign * div);
-     * }
-     */
-    ast_value    *value = NULL;
-    ast_call     *call  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod"));
-    ast_value    *a     = ast_value_new(intrin_ctx(intrin), "a",    TYPE_FLOAT);
-    ast_value    *b     = ast_value_new(intrin_ctx(intrin), "b",    TYPE_FLOAT);
-    ast_value    *div   = ast_value_new(intrin_ctx(intrin), "div",  TYPE_FLOAT);
-    ast_value    *sign  = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT);
-    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
-    ast_function *func  = intrin_value(intrin, &value, "mod", TYPE_FLOAT);
-
-    vec_push(value->expression.params, a);
-    vec_push(value->expression.params, b);
-
-    vec_push(body->locals, div);
-    vec_push(body->locals, sign);
-
-    /* div = a / b; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)div,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)a,
-                (ast_expression*)b
-            )
-        )
-    );
-
-    /* sign = (div < 0.0f) ? -1 : 1; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)sign,
-            (ast_expression*)ast_ternary_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LT,
-                    (ast_expression*)div,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)intrin->fold->imm_float[2],
-                (ast_expression*)intrin->fold->imm_float[1]
-            )
-        )
-    );
-
-    /* floor(sign * div) */
-    vec_push(call->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            INSTR_MUL_F,
-            (ast_expression*)sign,
-            (ast_expression*)div
-        )
-    );
-
-    /* return a - b * sign * <call> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_SUB_F,
-                (ast_expression*)a,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_MUL_F,
-                    (ast_expression*)b,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_MUL_F,
-                        (ast_expression*)sign,
-                        (ast_expression*)call
-                    )
-                )
-            )
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_fabs(intrin_t *intrin) {
-    /*
-     * float fabs(float x) {
-     *     return x < 0 ? -x : x;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "fabs", TYPE_FLOAT);
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_ternary_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LE,
-                    (ast_expression*)arg1,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)ast_unary_new(
-                    intrin_ctx(intrin),
-                    VINSTR_NEG_F,
-                    (ast_expression*)arg1
-                ),
-                (ast_expression*)arg1
-            )
-        )
-    );
-
-    vec_push(value->expression.params, arg1);
-    vec_push(func->blocks, body);
-
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_epsilon(intrin_t *intrin) {
-    /*
-     * float epsilon(void) {
-     *     float eps = 1.0f;
-     *     do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f);
-     *     return eps;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *eps    = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, "epsilon", TYPE_FLOAT);
-
-    vec_push(body->locals, eps);
-
-    /* eps = 1.0f; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)eps,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            NULL,
-            false,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_NE_F,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_MUL_F,
-                        (ast_expression*)eps,
-                        (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-                    )
-                ),
-                (ast_expression*)intrin->fold->imm_float[1]
-            ),
-            false,
-            NULL,
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_DIV_F,
-                (ast_expression*)eps,
-                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-            )
-        )
-    );
-
-    /* return eps; */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)eps
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_nan(intrin_t *intrin) {
-    /*
-     * float nan(void) {
-     *     float x = 0.0f;
-     *     return x / x;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_function *func   = intrin_value(intrin, &value, "nan", TYPE_FLOAT);
-    ast_block    *block  = ast_block_new(intrin_ctx(intrin));
-
-    vec_push(block->locals, x);
-
-    vec_push(block->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)x,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)x,
-                (ast_expression*)x
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_inf(intrin_t *intrin) {
-    /*
-     * float inf(void) {
-     *     float x = 1.0f;
-     *     float y = 0.0f;
-     *     return x / y;
-     * }
-     */
-    ast_value    *value  = NULL;
-    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_value    *y      = ast_value_new(intrin_ctx(intrin), "y", TYPE_FLOAT);
-    ast_function *func   = intrin_value(intrin, &value, "inf", TYPE_FLOAT);
-    ast_block    *block  = ast_block_new(intrin_ctx(intrin));
-    size_t        i;
-
-    vec_push(block->locals, x);
-    vec_push(block->locals, y);
-
-    /* to keep code size down */
-    for (i = 0; i <= 1; i++) {
-        vec_push(block->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i == 0) ? x : y),
-                (ast_expression*)intrin->fold->imm_float[i]
-            )
-        );
-    }
-
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_DIV_F,
-                (ast_expression*)x,
-                (ast_expression*)y
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_ln(intrin_t *intrin) {
-    /*
-     * float log(float power, float base) {
-     *   float whole;
-     *   float nth
-     *   float sign = 1.0f;
-     *   float eps  = epsilon();
-     *
-     *   if (power <= 1.0f || bbase <= 1.0) {
-     *       if (power <= 0.0f || base <= 0.0f)
-     *           return nan();
-     *
-     *       if (power < 1.0f) {
-     *           power = 1.0f / power;
-     *           sign *= -1.0f;
-     *       }
-     *
-     *       if (base < 1.0f) {
-     *           sign *= -1.0f;
-     *           base  = 1.0f / base;
-     *       }
-     *   }
-     *
-     *   float A_i       = 1;
-     *   float B_i       = 0;
-     *   float A_iminus1 = 0;
-     *   float B_iminus1 = 1;
-     *
-     *   for (;;) {
-     *       whole = power;
-     *       nth   = 0.0f;
-     *
-     *       while (whole >= base) {
-     *           float base2    = base;
-     *           float n2       = 1.0f;
-     *           float newbase2 = base2 * base2;
-     *
-     *           while (whole >= newbase2) {
-     *               base2     = newbase2;
-     *               n2       *= 2;
-     *               newbase2 *= newbase2;
-     *           }
-     *
-     *           whole /= base2;
-     *           nth += n2;
-     *       }
-     *
-     *       float b_iplus1 = n;
-     *       float A_iplus1 = b_iplus1 * A_i + A_iminus1;
-     *       float B_iplus1 = b_iplus1 * B_i + B_iminus1;
-     *
-     *       A_iminus1 = A_i;
-     *       B_iminus1 = B_i;
-     *       A_i       = A_iplus1;
-     *       B_i       = B_iplus1;
-     *
-     *       if (whole <= 1.0f + eps)
-     *           break;
-     *
-     *       power = base;
-     *       bower = whole;
-     *   }
-     *   return sign * A_i / B_i;
-     * }
-     */
-
-    ast_value    *value      = NULL;
-    ast_value    *power      = ast_value_new(intrin_ctx(intrin), "power",     TYPE_FLOAT);
-    ast_value    *base       = ast_value_new(intrin_ctx(intrin), "base",      TYPE_FLOAT);
-    ast_value    *whole      = ast_value_new(intrin_ctx(intrin), "whole",     TYPE_FLOAT);
-    ast_value    *nth        = ast_value_new(intrin_ctx(intrin), "nth",       TYPE_FLOAT);
-    ast_value    *sign       = ast_value_new(intrin_ctx(intrin), "sign",      TYPE_FLOAT);
-    ast_value    *A_i        = ast_value_new(intrin_ctx(intrin), "A_i",       TYPE_FLOAT);
-    ast_value    *B_i        = ast_value_new(intrin_ctx(intrin), "B_i",       TYPE_FLOAT);
-    ast_value    *A_iminus1  = ast_value_new(intrin_ctx(intrin), "A_iminus1", TYPE_FLOAT);
-    ast_value    *B_iminus1  = ast_value_new(intrin_ctx(intrin), "B_iminus1", TYPE_FLOAT);
-    ast_value    *b_iplus1   = ast_value_new(intrin_ctx(intrin), "b_iplus1",  TYPE_FLOAT);
-    ast_value    *A_iplus1   = ast_value_new(intrin_ctx(intrin), "A_iplus1",  TYPE_FLOAT);
-    ast_value    *B_iplus1   = ast_value_new(intrin_ctx(intrin), "B_iplus1",  TYPE_FLOAT);
-    ast_value    *eps        = ast_value_new(intrin_ctx(intrin), "eps",       TYPE_FLOAT);
-    ast_value    *base2      = ast_value_new(intrin_ctx(intrin), "base2",     TYPE_FLOAT);
-    ast_value    *n2         = ast_value_new(intrin_ctx(intrin), "n2",        TYPE_FLOAT);
-    ast_value    *newbase2   = ast_value_new(intrin_ctx(intrin), "newbase2",  TYPE_FLOAT);
-    ast_block    *block      = ast_block_new(intrin_ctx(intrin));
-    ast_block    *plt1orblt1 = ast_block_new(intrin_ctx(intrin)); /* (power <= 1.0f || base <= 1.0f) */
-    ast_block    *plt1       = ast_block_new(intrin_ctx(intrin)); /* (power < 1.0f) */
-    ast_block    *blt1       = ast_block_new(intrin_ctx(intrin)); /* (base  < 1.0f) */
-    ast_block    *forloop    = ast_block_new(intrin_ctx(intrin)); /* for(;;) */
-    ast_block    *whileloop  = ast_block_new(intrin_ctx(intrin)); /* while (whole >= base) */
-    ast_block    *nestwhile  = ast_block_new(intrin_ctx(intrin)); /* while (whole >= newbase2) */
-    ast_function *func       = intrin_value(intrin, &value, "ln", TYPE_FLOAT);
-    size_t        i;
-
-    vec_push(value->expression.params, power);
-    vec_push(value->expression.params, base);
-
-    vec_push(block->locals, whole);
-    vec_push(block->locals, nth);
-    vec_push(block->locals, sign);
-    vec_push(block->locals, eps);
-    vec_push(block->locals, A_i);
-    vec_push(block->locals, B_i);
-    vec_push(block->locals, A_iminus1);
-    vec_push(block->locals, B_iminus1);
-
-    /* sign = 1.0f; */
-    vec_push(block->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)sign,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* eps = __builtin_epsilon(); */
-    vec_push(block->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)eps,
-            (ast_expression*)ast_call_new(
-                intrin_ctx(intrin),
-                intrin_func_self(intrin, "__builtin_epsilon", "ln")
-            )
-        )
-    );
-
-    /*
-     * A_i       = 1;
-     * B_i       = 0;
-     * A_iminus1 = 0;
-     * B_iminus1 = 1;
-     */
-    for (i = 0; i <= 1; i++) {
-        int j;
-        for (j = 1; j >= 0; j--) {
-            vec_push(block->exprs,
-                (ast_expression*)ast_store_new(
-                    intrin_ctx(intrin),
-                    INSTR_STORE_F,
-                    (ast_expression*)((j) ? ((i) ? B_iminus1 : A_i)
-                                          : ((i) ? A_iminus1 : B_i)),
-                    (ast_expression*)intrin->fold->imm_float[j]
-                )
-            );
-        }
-    }
-
-    /*
-     * <plt1> = {
-     *     power = 1.0f / power;
-     *     sign *= -1.0f;
-     * }
-     * <blt1> = {
-     *     base  = 1.0f / base;
-     *     sign *= -1.0f;
-     * }
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(((i) ? blt1 : plt1)->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? base : power),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_DIV_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)((i) ? base : power)
-                )
-            )
-        );
-        vec_push(plt1->exprs,
-            (ast_expression*)ast_binstore_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                INSTR_MUL_F,
-                (ast_expression*)sign,
-                (ast_expression*)intrin->fold->imm_float[2]
-            )
-        );
-    }
-
-    /*
-     * <plt1orblt1> = {
-     *     if (power <= 0.0 || base <= 0.0f)
-     *         return __builtin_nan();
-     *     if (power < 1.0f)
-     *         <plt1>
-     *     if (base < 1.0f)
-     *         <blt1>
-     * }
-     */
-    vec_push(plt1orblt1->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_OR,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LE,
-                    (ast_expression*)power,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                ),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LE,
-                    (ast_expression*)base,
-                    (ast_expression*)intrin->fold->imm_float[0]
-                )
-            ),
-            (ast_expression*)ast_return_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_call_new(
-                    intrin_ctx(intrin),
-                    intrin_func_self(intrin, "__builtin_nan", "ln")
-                )
-            ),
-            NULL
-        )
-    );
-
-    for (i = 0; i <= 1; i++) {
-        vec_push(plt1orblt1->exprs,
-            (ast_expression*)ast_ifthen_new(
-                intrin_ctx(intrin),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_LT,
-                    (ast_expression*)((i) ? base : power),
-                    (ast_expression*)intrin->fold->imm_float[1]
-                ),
-                (ast_expression*)((i) ? blt1 : plt1),
-                NULL
-            )
-        );
-    }
-
-    vec_push(block->exprs, (ast_expression*)plt1orblt1);
-
-
-    /* whole = power; */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)whole,
-            (ast_expression*)power
-        )
-    );
-
-    /* nth = 0.0f; */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)nth,
-            (ast_expression*)intrin->fold->imm_float[0]
-        )
-    );
-
-    /* base2 = base; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)base2,
-            (ast_expression*)base
-        )
-    );
-
-    /* n2 = 1.0f; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)n2,
-            (ast_expression*)intrin->fold->imm_float[1]
-        )
-    );
-
-    /* newbase2 = base2 * base2; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)newbase2,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)base2,
-                (ast_expression*)base2
-            )
-        )
-    );
-
-    /* while loop locals */
-    vec_push(whileloop->locals, base2);
-    vec_push(whileloop->locals, n2);
-    vec_push(whileloop->locals, newbase2);
-
-    /* base2 = newbase2; */
-    vec_push(nestwhile->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)base2,
-            (ast_expression*)newbase2
-        )
-    );
-
-    /* n2 *= 2; */
-    vec_push(nestwhile->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)n2,
-            (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
-        )
-    );
-
-    /* newbase2 *= newbase2; */
-    vec_push(nestwhile->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_MUL_F,
-            (ast_expression*)newbase2,
-            (ast_expression*)newbase2
-        )
-    );
-
-    /* while (whole >= newbase2) */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GE,
-                (ast_expression*)whole,
-                (ast_expression*)newbase2
-            ),
-            false,
-            NULL,
-            false,
-            NULL,
-            (ast_expression*)nestwhile
-        )
-    );
-
-    /* whole /= base2; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_DIV_F,
-            (ast_expression*)whole,
-            (ast_expression*)base2
-        )
-    );
-
-    /* nth += n2; */
-    vec_push(whileloop->exprs,
-        (ast_expression*)ast_binstore_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            INSTR_ADD_F,
-            (ast_expression*)nth,
-            (ast_expression*)n2
-        )
-    );
-
-    /* while (whole >= base) */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_GE,
-                (ast_expression*)whole,
-                (ast_expression*)base
-            ),
-            false,
-            NULL,
-            false,
-            NULL,
-            (ast_expression*)whileloop
-        )
-    );
-
-    vec_push(forloop->locals, b_iplus1);
-    vec_push(forloop->locals, A_iplus1);
-    vec_push(forloop->locals, B_iplus1);
-
-    /* b_iplus1 = nth; */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_store_new(
-            intrin_ctx(intrin),
-            INSTR_STORE_F,
-            (ast_expression*)b_iplus1,
-            (ast_expression*)nth
-        )
-    );
-
-    /*
-     * A_iplus1 = b_iplus1 * A_i + A_iminus1;
-     * B_iplus1 = b_iplus1 * B_i + B_iminus1;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? B_iplus1 : A_iplus1),
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)ast_binary_new(
-                        intrin_ctx(intrin),
-                        INSTR_MUL_F,
-                        (ast_expression*)b_iplus1,
-                        (ast_expression*) ((i) ? B_i : A_i)
-                    ),
-                    (ast_expression*)((i) ? B_iminus1 : A_iminus1)
-                )
-            )
-        );
-    }
-
-    /*
-     * A_iminus1 = A_i;
-     * B_iminus1 = B_i;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? B_iminus1 : A_iminus1),
-                (ast_expression*)((i) ? B_i       : A_i)
-            )
-        );
-    }
-
-    /*
-     * A_i = A_iplus1;
-     * B_i = B_iplus1;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? B_i      : A_i),
-                (ast_expression*)((i) ? B_iplus1 : A_iplus1)
-            )
-        );
-    }
-
-    /*
-     * if (whole <= 1.0f + eps)
-     *     break;
-     */
-    vec_push(forloop->exprs,
-        (ast_expression*)ast_ifthen_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_LE,
-                (ast_expression*)whole,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_ADD_F,
-                    (ast_expression*)intrin->fold->imm_float[1],
-                    (ast_expression*)eps
-                )
-            ),
-            (ast_expression*)ast_breakcont_new(
-                intrin_ctx(intrin),
-                false,
-                0
-            ),
-            NULL
-        )
-    );
-
-    /*
-     * power = base;
-     * base  = whole;
-     */
-    for (i = 0; i <= 1; i++) {
-        vec_push(forloop->exprs,
-            (ast_expression*)ast_store_new(
-                intrin_ctx(intrin),
-                INSTR_STORE_F,
-                (ast_expression*)((i) ? base  : power),
-                (ast_expression*)((i) ? whole : base)
-            )
-        );
-    }
-
-    /* add the for loop block */
-    vec_push(block->exprs,
-        (ast_expression*)ast_loop_new(
-            intrin_ctx(intrin),
-            NULL,
-            /* for(; 1; ) ?? (can this be NULL too?) */
-            (ast_expression*)intrin->fold->imm_float[1],
-            false,
-            NULL,
-            false,
-            NULL,
-            (ast_expression*)forloop
-        )
-    );
-
-    /* return sign * A_i / B_il */
-    vec_push(block->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)ast_binary_new(
-                intrin_ctx(intrin),
-                INSTR_MUL_F,
-                (ast_expression*)sign,
-                (ast_expression*)ast_binary_new(
-                    intrin_ctx(intrin),
-                    INSTR_DIV_F,
-                    (ast_expression*)A_i,
-                    (ast_expression*)B_i
-                )
-            )
-        )
-    );
-
-    vec_push(func->blocks, block);
-    intrin_reg(intrin, value, func);
-
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, float base) {
-    ast_value    *value  = NULL;
-    ast_call     *callln = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "__builtin_ln", name));
-    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
-    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
-    ast_function *func   = intrin_value(intrin, &value, name, TYPE_FLOAT);
-
-    vec_push(value->expression.params, arg1);
-
-    vec_push(callln->params, (ast_expression*)arg1);
-    vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base, false));
-
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callln
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_log(intrin_t *intrin) {
-    return intrin_log_variant(intrin, "log", 2.7182818284590452354);
-}
-static ast_expression *intrin_log10(intrin_t *intrin) {
-    return intrin_log_variant(intrin, "log10", 10);
-}
-static ast_expression *intrin_log2(intrin_t *intrin) {
-    return intrin_log_variant(intrin, "log2", 2);
-}
-static ast_expression *intrin_logb(intrin_t *intrin) {
-    /* FLT_RADIX == 2 for now */
-    return intrin_log_variant(intrin, "log2", 2);
-}
-
-static ast_expression *intrin_shift_variant(intrin_t *intrin, const char *name, size_t instr) {
-    /*
-     * float [shift] (float a, float b) {
-     *   return floor(a [instr] pow(2, b));
-     */
-    ast_value    *value     = NULL;
-    ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", name));
-    ast_call     *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", name));
-    ast_value    *a         = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT);
-    ast_value    *b         = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT);
-    ast_block    *body      = ast_block_new(intrin_ctx(intrin));
-    ast_function *func      = intrin_value(intrin, &value, name, TYPE_FLOAT);
-
-    vec_push(value->expression.params, a);
-    vec_push(value->expression.params, b);
-
-    /* <callpow> = pow(2, b) */
-    vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
-    vec_push(callpow->params, (ast_expression*)b);
-
-    /* <callfloor> = floor(a [instr] <callpow>) */
-    vec_push(
-        callfloor->params,
-        (ast_expression*)ast_binary_new(
-            intrin_ctx(intrin),
-            instr,
-            (ast_expression*)a,
-            (ast_expression*)callpow
-        )
-    );
-
-    /* return <callfloor> */
-    vec_push(body->exprs,
-        (ast_expression*)ast_return_new(
-            intrin_ctx(intrin),
-            (ast_expression*)callfloor
-        )
-    );
-
-    vec_push(func->blocks, body);
-    intrin_reg(intrin, value, func);
-    return (ast_expression*)value;
-}
-
-static ast_expression *intrin_lshift(intrin_t *intrin) {
-    return intrin_shift_variant(intrin, "lshift", INSTR_MUL_F);
-}
-
-static ast_expression *intrin_rshift(intrin_t *intrin) {
-    return intrin_shift_variant(intrin, "rshift", INSTR_DIV_F);
-}
-
-/*
- * TODO: make static (and handle ast_type_string) here for the builtin
- * instead of in SYA parse close.
- */
-ast_expression *intrin_debug_typestring(intrin_t *intrin) {
-    (void)intrin;
-    return (ast_expression*)0x1;
-}
-
-static const intrin_func_t intrinsics[] = {
-    {&intrin_isfinite,         "__builtin_isfinite",         "isfinite", 1},
-    {&intrin_isinf,            "__builtin_isinf",            "isinf",    1},
-    {&intrin_isnan,            "__builtin_isnan",            "isnan",    1},
-    {&intrin_isnormal,         "__builtin_isnormal",         "isnormal", 1},
-    {&intrin_signbit,          "__builtin_signbit",          "signbit",  1},
-    {&intrin_acosh,            "__builtin_acosh",            "acosh",    1},
-    {&intrin_asinh,            "__builtin_asinh",            "asinh",    1},
-    {&intrin_atanh,            "__builtin_atanh",            "atanh",    1},
-    {&intrin_exp,              "__builtin_exp",              "exp",      1},
-    {&intrin_exp2,             "__builtin_exp2",             "exp2",     1},
-    {&intrin_expm1,            "__builtin_expm1",            "expm1",    1},
-    {&intrin_mod,              "__builtin_mod",              "mod",      2},
-    {&intrin_pow,              "__builtin_pow",              "pow",      2},
-    {&intrin_fabs,             "__builtin_fabs",             "fabs",     1},
-    {&intrin_log,              "__builtin_log",              "log",      1},
-    {&intrin_log10,            "__builtin_log10",            "log10",    1},
-    {&intrin_log2,             "__builtin_log2",             "log2",     1},
-    {&intrin_logb,             "__builtin_logb",             "logb",     1},
-    {&intrin_lshift,           "__builtin_lshift",           "",         2},
-    {&intrin_rshift,           "__builtin_rshift",           "",         2},
-    {&intrin_epsilon,          "__builtin_epsilon",          "",         0},
-    {&intrin_nan,              "__builtin_nan",              "",         0},
-    {&intrin_inf,              "__builtin_inf",              "",         0},
-    {&intrin_ln,               "__builtin_ln",               "",         2},
-    {&intrin_debug_typestring, "__builtin_debug_typestring", "",         0},
-    {&intrin_nullfunc,         "#nullfunc",                  "",         0}
-};
-
-static void intrin_error(intrin_t *intrin, const char *fmt, ...) {
-    va_list ap;
-    va_start(ap, fmt);
-    vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap);
-    va_end(ap);
-}
-
-/* exposed */
-intrin_t *intrin_init(parser_t *parser) {
-    intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t));
-    size_t    i;
-
-    intrin->parser     = parser;
-    intrin->fold       = parser->fold;
-    intrin->intrinsics = NULL;
-    intrin->generated  = NULL;
-
-    vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics);
-
-    /* populate with null pointers for tracking generation */
-    for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++)
-        vec_push(intrin->generated, NULL);
-
-    return intrin;
-}
-
-void intrin_cleanup(intrin_t *intrin) {
-    vec_free(intrin->intrinsics);
-    vec_free(intrin->generated);
-    mem_d(intrin);
-}
-
-ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) {
-    size_t i;
-    if (!value || !value->name)
-        return NULL;
-    for (i = 0; i < vec_size(intrin->intrinsics); i++)
-        if (!strcmp(value->name, intrin->intrinsics[i].name))
-            return (vec_size(exprs) != intrin->intrinsics[i].args)
-                        ? NULL
-                        : fold_intrin(intrin->fold, value->name + 10, exprs);
-    return NULL;
-}
-
-static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) {
-    size_t i;
-    for (i = 0; i < vec_size(intrin->intrinsics); i++) {
-        if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare))
-            continue;
-        if (intrin->generated[i])
-            return intrin->generated[i];
-        return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin);
-    }
-    return NULL;
-}
-
-static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) {
-    size_t           i;
-    ast_expression  *find;
-
-    /* try current first */
-    if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION)
-        for (i = 0; i < vec_size(intrin->parser->functions); ++i)
-            if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0)
-                return find;
-    /* try name second */
-    if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name),  name)))
-        return find;
-    /* try alias third */
-    if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name)))
-        return find;
-
-    if (from) {
-        intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from);
-        return intrin_func_self(intrin, "#nullfunc", NULL);
-    }
-    return NULL;
-}
-
-ast_expression *intrin_func(intrin_t *intrin, const char *name) {
-    return intrin_func_self(intrin, name, NULL);
-}
diff --git a/intrin.cpp b/intrin.cpp
new file mode 100644 (file)
index 0000000..9ef0207
--- /dev/null
@@ -0,0 +1,2082 @@
+#include <string.h>
+#include "parser.h"
+
+#define intrin_ctx(I) parser_ctx((I)->parser)
+
+static GMQCC_INLINE ast_function *intrin_value(intrin_t *intrin, ast_value **out, const char *name, qcint_t vtype) {
+    ast_value    *value = NULL;
+    ast_function *func  = NULL;
+    char          buffer[1024];
+    char          stype [1024];
+
+    util_snprintf(buffer, sizeof(buffer), "__builtin_%s", name);
+    util_snprintf(stype,  sizeof(stype),   "<%s>",        type_name[vtype]);
+
+    value                    = ast_value_new(intrin_ctx(intrin), buffer, TYPE_FUNCTION);
+    value->intrinsic         = true;
+    value->expression.next   = (ast_expression*)ast_value_new(intrin_ctx(intrin), stype, vtype);
+    func                     = ast_function_new(intrin_ctx(intrin), buffer, value);
+    value->expression.flags |= AST_FLAG_ERASEABLE;
+
+    *out = value;
+    return func;
+}
+
+static GMQCC_INLINE void intrin_reg(intrin_t *intrin, ast_value *const value, ast_function *const func) {
+    vec_push(intrin->parser->functions, func);
+    vec_push(intrin->parser->globals,   (ast_expression*)value);
+}
+
+#define QC_POW_EPSILON 0.00001f
+
+/*
+ * since some intrinsics depend on each other there is the possibility
+ * that an intrinsic will fail to get a 'depended' function that a
+ * builtin needs, causing some dependency in the chain to have a NULL
+ * function. This will cause a segmentation fault at code generation,
+ * even though an error was raised. To contiue to allow it (instead
+ * of stopping compilation right away). We need to return from the
+ * parser, before compilation stops after all the collected errors.
+ */
+static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from);
+static ast_expression *intrin_nullfunc(intrin_t *intrin) {
+    ast_value    *value = NULL;
+    ast_function *func  = intrin_value(intrin, &value, NULL, TYPE_VOID);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_isfinite(intrin_t *intrin) {
+    /*
+     * float isfinite(float x) {
+     *     return !(isnan(x) || isinf(x));
+     * }
+     */
+    ast_value    *value     = NULL;
+    ast_value    *x         = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_function *func      = intrin_value(intrin, &value, "isfinite", TYPE_FLOAT);
+    ast_call     *callisnan = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isnan", "isfinite"));
+    ast_call     *callisinf = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "isinf", "isfinite"));
+    ast_block    *block     = ast_block_new(intrin_ctx(intrin));
+
+    /* float x; */
+    vec_push(value->expression.params, x);
+
+    /* <callisnan> = isnan(x); */
+    vec_push(callisnan->params, (ast_expression*)x);
+
+    /* <callisinf> = isinf(x); */
+    vec_push(callisinf->params, (ast_expression*)x);
+
+    /* return (!<callisnan> || <callisinf>); */
+    vec_push(block->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_unary_new(
+                intrin_ctx(intrin),
+                INSTR_NOT_F,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_OR,
+                    (ast_expression*)callisnan,
+                    (ast_expression*)callisinf
+                )
+            )
+        )
+    );
+
+    vec_push(func->blocks, block);
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;;
+}
+
+static ast_expression *intrin_isinf(intrin_t *intrin) {
+    /*
+     * float isinf(float x) {
+     *     return (x != 0.0) && (x + x == x);
+     * }
+     */
+    ast_value    *value = NULL;
+    ast_value    *x     = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
+    ast_function *func  = intrin_value(intrin, &value, "isinf", TYPE_FLOAT);
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_AND,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_NE_F,
+                    (ast_expression*)x,
+                    (ast_expression*)intrin->fold->imm_float[0]
+                ),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_EQ_F,
+                    (ast_expression*)ast_binary_new(
+                        intrin_ctx(intrin),
+                        INSTR_ADD_F,
+                        (ast_expression*)x,
+                        (ast_expression*)x
+                    ),
+                    (ast_expression*)x
+                )
+            )
+        )
+    );
+
+    vec_push(value->expression.params, x);
+    vec_push(func->blocks, body);
+
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_isnan(intrin_t *intrin) {
+    /*
+     * float isnan(float x) {
+     *   float local;
+     *   local = x;
+     *
+     *   return (x != local);
+     * }
+     */
+    ast_value    *value  = NULL;
+    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x",     TYPE_FLOAT);
+    ast_value    *local  = ast_value_new(intrin_ctx(intrin), "local", TYPE_FLOAT);
+    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
+    ast_function *func   = intrin_value(intrin, &value, "isnan", TYPE_FLOAT);
+
+    vec_push(body->locals, local);
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)local,
+            (ast_expression*)arg1
+        )
+    );
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_NE_F,
+                (ast_expression*)arg1,
+                (ast_expression*)local
+            )
+        )
+    );
+
+    vec_push(value->expression.params, arg1);
+    vec_push(func->blocks, body);
+
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_isnormal(intrin_t *intrin) {
+    /*
+     * float isnormal(float x) {
+     *     return isfinite(x);
+     * }
+     */
+    ast_value    *value         = NULL;
+    ast_call     *callisfinite  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "isfinite", "isnormal"));
+    ast_value    *x             = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body          = ast_block_new(intrin_ctx(intrin));
+    ast_function *func          = intrin_value(intrin, &value, "isnormal", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+    vec_push(callisfinite->params, (ast_expression*)x);
+
+    /* return <callisfinite> */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)callisfinite
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_signbit(intrin_t *intrin) {
+    /*
+     * float signbit(float x) {
+     *     return (x < 0);
+     * }
+     */
+    ast_value    *value  = NULL;
+    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
+    ast_function *func   = intrin_value(intrin, &value, "signbit", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+
+    /* return (x < 0); */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_ternary_new(
+                intrin_ctx(intrin),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_LT,
+                    (ast_expression*)x,
+                    (ast_expression*)intrin->fold->imm_float[0]
+                ),
+                (ast_expression*)intrin->fold->imm_float[1],
+                (ast_expression*)intrin->fold->imm_float[0]
+            )
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_acosh(intrin_t *intrin) {
+    /*
+     * float acosh(float x) {
+     *     return log(x + sqrt((x * x) - 1));
+     * }
+     */
+    ast_value    *value    = NULL;
+    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_call     *calllog  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "acosh"));
+    ast_call     *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "acosh"));
+    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
+    ast_function *func     = intrin_value(intrin, &value, "acosh", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+
+    /* <callsqrt> = sqrt((x * x) - 1); */
+    vec_push(callsqrt->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_SUB_F,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_MUL_F,
+                (ast_expression*)x,
+                (ast_expression*)x
+            ),
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    /* <calllog> = log(x + <callsqrt>); */
+    vec_push(calllog->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_ADD_F,
+            (ast_expression*)x,
+            (ast_expression*)callsqrt
+        )
+    );
+
+    /* return <calllog>; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)calllog
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_asinh(intrin_t *intrin) {
+    /*
+     * float asinh(float x) {
+     *     return log(x + sqrt((x * x) + 1));
+     * }
+     */
+    ast_value    *value    = NULL;
+    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_call     *calllog  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "asinh"));
+    ast_call     *callsqrt = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "asinh"));
+    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
+    ast_function *func     = intrin_value(intrin, &value, "asinh", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+
+    /* <callsqrt> = sqrt((x * x) + 1); */
+    vec_push(callsqrt->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_ADD_F,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_MUL_F,
+                (ast_expression*)x,
+                (ast_expression*)x
+            ),
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    /* <calllog> = log(x + <callsqrt>); */
+    vec_push(calllog->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_ADD_F,
+            (ast_expression*)x,
+            (ast_expression*)callsqrt
+        )
+    );
+
+    /* return <calllog>; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)calllog
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_atanh(intrin_t *intrin) {
+    /*
+     * float atanh(float x) {
+     *     return 0.5 * log((1 + x) / (1 - x))
+     * }
+     */
+    ast_value    *value   = NULL;
+    ast_value    *x       = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_call     *calllog = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "log", "atanh"));
+    ast_block    *body    = ast_block_new(intrin_ctx(intrin));
+    ast_function *func    = intrin_value(intrin, &value, "atanh", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+
+    /* <callog> = log((1 + x) / (1 - x)); */
+    vec_push(calllog->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_DIV_F,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_ADD_F,
+                (ast_expression*)intrin->fold->imm_float[1],
+                (ast_expression*)x
+            ),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_SUB_F,
+                (ast_expression*)intrin->fold->imm_float[1],
+                (ast_expression*)x
+            )
+        )
+    );
+
+    /* return 0.5 * <calllog>; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_MUL_F,
+            (ast_expression*)fold_constgen_float(intrin->fold, 0.5, false),
+            (ast_expression*)calllog
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_exp(intrin_t *intrin) {
+    /*
+     * float exp(float x) {
+     *     float sum = 1.0;
+     *     float acc = 1.0;
+     *     float i;
+     *     for (i = 1; i < 200; ++i)
+     *         sum += (acc *= x / i);
+     *
+     *     return sum;
+     * }
+     */
+    ast_value    *value = NULL;
+    ast_value    *x     = ast_value_new(intrin_ctx(intrin), "x",   TYPE_FLOAT);
+    ast_value    *sum   = ast_value_new(intrin_ctx(intrin), "sum", TYPE_FLOAT);
+    ast_value    *acc   = ast_value_new(intrin_ctx(intrin), "acc", TYPE_FLOAT);
+    ast_value    *i     = ast_value_new(intrin_ctx(intrin), "i",   TYPE_FLOAT);
+    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
+    ast_function *func  = intrin_value(intrin, &value, "exp", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+    vec_push(body->locals, sum);
+    vec_push(body->locals, acc);
+    vec_push(body->locals, i);
+
+    /* sum = 1.0; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)sum,
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    /* acc = 1.0; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)acc,
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    /*
+     * for (i = 1; i < 200; ++i)
+     *     sum += (acc *= x / i);
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_loop_new(
+            intrin_ctx(intrin),
+            /* i = 1; */
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)i,
+                (ast_expression*)intrin->fold->imm_float[1]
+            ),
+            /* i < 200; */
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_LT,
+                (ast_expression*)i,
+                (ast_expression*)fold_constgen_float(intrin->fold, 200.0f, false)
+            ),
+            false,
+            NULL,
+            false,
+            /* ++i; */
+            (ast_expression*)ast_binstore_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                INSTR_ADD_F,
+                (ast_expression*)i,
+                (ast_expression*)intrin->fold->imm_float[1]
+            ),
+            /* sum += (acc *= (x / i)) */
+            (ast_expression*)ast_binstore_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                INSTR_ADD_F,
+                (ast_expression*)sum,
+                (ast_expression*)ast_binstore_new(
+                    intrin_ctx(intrin),
+                    INSTR_STORE_F,
+                    INSTR_MUL_F,
+                    (ast_expression*)acc,
+                    (ast_expression*)ast_binary_new(
+                        intrin_ctx(intrin),
+                        INSTR_DIV_F,
+                        (ast_expression*)x,
+                        (ast_expression*)i
+                    )
+                )
+            )
+        )
+    );
+
+    /* return sum; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)sum
+        )
+    );
+
+    vec_push(func->blocks, body);
+
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_exp2(intrin_t *intrin) {
+    /*
+     * float exp2(float x) {
+     *     return pow(2, x);
+     * }
+     */
+    ast_value    *value     = NULL;
+    ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", "exp2"));
+    ast_value    *arg1      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body      = ast_block_new(intrin_ctx(intrin));
+    ast_function *func      = intrin_value(intrin, &value, "exp2", TYPE_FLOAT);
+
+    vec_push(value->expression.params, arg1);
+
+    vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
+    vec_push(callpow->params, (ast_expression*)arg1);
+
+    /* return <callpow> */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)callpow
+        )
+    );
+
+    vec_push(func->blocks, body);
+
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_expm1(intrin_t *intrin) {
+    /*
+     * float expm1(float x) {
+     *     return exp(x) - 1;
+     * }
+     */
+    ast_value    *value    = NULL;
+    ast_call     *callexp  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "exp", "expm1"));
+    ast_value    *x        = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body     = ast_block_new(intrin_ctx(intrin));
+    ast_function *func     = intrin_value(intrin, &value, "expm1", TYPE_FLOAT);
+
+    vec_push(value->expression.params, x);
+
+    /* <callexp> = exp(x); */
+    vec_push(callexp->params, (ast_expression*)x);
+
+    /* return <callexp> - 1; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_SUB_F,
+                (ast_expression*)callexp,
+                (ast_expression*)intrin->fold->imm_float[1]
+            )
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_pow(intrin_t *intrin) {
+    /*
+     *
+     * float pow(float base, float exp) {
+     *     float result;
+     *     float low;
+     *     float high;
+     *     float mid;
+     *     float square;
+     *     float accumulate;
+     *
+     *     if (exp == 0.0)
+     *         return 1;
+     *     if (exp == 1.0)
+     *         return base;
+     *     if (exp < 0)
+     *         return 1.0 / pow(base, -exp);
+     *     if (exp >= 1) {
+     *         result = pow(base, exp / 2);
+     *         return result * result;
+     *     }
+     *
+     *     low        = 0.0f;
+     *     high       = 1.0f;
+     *     square     = sqrt(base);
+     *     accumulate = square;
+     *     mid        = high / 2.0f
+     *
+     *     while (fabs(mid - exp) > QC_POW_EPSILON) {
+     *         square = sqrt(square);
+     *         if (mid < exp) {
+     *             low         = mid;
+     *             accumulate *= square;
+     *         } else {
+     *             high        = mid;
+     *             accumulate *= (1.0f / square);
+     *         }
+     *         mid = (low + high) / 2;
+     *     }
+     *     return accumulate;
+     * }
+     */
+    ast_value    *value = NULL;
+    ast_function *func = intrin_value(intrin, &value, "pow", TYPE_FLOAT);
+
+    /* prepare some calls for later */
+    ast_call *callpow1  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);                  /* for pow(base, -exp)    */
+    ast_call *callpow2  = ast_call_new(intrin_ctx(intrin), (ast_expression*)value);                  /* for pow(vase, exp / 2) */
+    ast_call *callsqrt1 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(base)         */
+    ast_call *callsqrt2 = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "sqrt", "pow")); /* for sqrt(square)       */
+    ast_call *callfabs  = ast_call_new(intrin_ctx(intrin), intrin_func_self(intrin, "fabs", "pow")); /* for fabs(mid - exp)    */
+
+    /* prepare some blocks for later */
+    ast_block *expgt1       = ast_block_new(intrin_ctx(intrin));
+    ast_block *midltexp     = ast_block_new(intrin_ctx(intrin));
+    ast_block *midltexpelse = ast_block_new(intrin_ctx(intrin));
+    ast_block *whileblock   = ast_block_new(intrin_ctx(intrin));
+
+    /* float pow(float base, float exp) */
+    ast_value    *base = ast_value_new(intrin_ctx(intrin), "base", TYPE_FLOAT);
+    ast_value    *exp  = ast_value_new(intrin_ctx(intrin), "exp",  TYPE_FLOAT);
+    /* { */
+    ast_block    *body = ast_block_new(intrin_ctx(intrin));
+
+    /*
+     * float result;
+     * float low;
+     * float high;
+     * float square;
+     * float accumulate;
+     * float mid;
+     */
+    ast_value *result     = ast_value_new(intrin_ctx(intrin), "result",     TYPE_FLOAT);
+    ast_value *low        = ast_value_new(intrin_ctx(intrin), "low",        TYPE_FLOAT);
+    ast_value *high       = ast_value_new(intrin_ctx(intrin), "high",       TYPE_FLOAT);
+    ast_value *square     = ast_value_new(intrin_ctx(intrin), "square",     TYPE_FLOAT);
+    ast_value *accumulate = ast_value_new(intrin_ctx(intrin), "accumulate", TYPE_FLOAT);
+    ast_value *mid        = ast_value_new(intrin_ctx(intrin), "mid",        TYPE_FLOAT);
+    vec_push(body->locals, result);
+    vec_push(body->locals, low);
+    vec_push(body->locals, high);
+    vec_push(body->locals, square);
+    vec_push(body->locals, accumulate);
+    vec_push(body->locals, mid);
+
+    vec_push(value->expression.params, base);
+    vec_push(value->expression.params, exp);
+
+    /*
+     * if (exp == 0.0)
+     *     return 1;
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_EQ_F,
+                (ast_expression*)exp,
+                (ast_expression*)intrin->fold->imm_float[0]
+            ),
+            (ast_expression*)ast_return_new(
+                intrin_ctx(intrin),
+                (ast_expression*)intrin->fold->imm_float[1]
+            ),
+            NULL
+        )
+    );
+
+    /*
+     * if (exp == 1.0)
+     *     return base;
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_EQ_F,
+                (ast_expression*)exp,
+                (ast_expression*)intrin->fold->imm_float[1]
+            ),
+            (ast_expression*)ast_return_new(
+                intrin_ctx(intrin),
+                (ast_expression*)base
+            ),
+            NULL
+        )
+    );
+
+    /* <callpow1> = pow(base, -exp) */
+    vec_push(callpow1->params, (ast_expression*)base);
+    vec_push(callpow1->params,
+        (ast_expression*)ast_unary_new(
+            intrin_ctx(intrin),
+            VINSTR_NEG_F,
+            (ast_expression*)exp
+        )
+    );
+
+    /*
+     * if (exp < 0)
+     *     return 1.0 / <callpow1>;
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_LT,
+                (ast_expression*)exp,
+                (ast_expression*)intrin->fold->imm_float[0]
+            ),
+            (ast_expression*)ast_return_new(
+                intrin_ctx(intrin),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_DIV_F,
+                    (ast_expression*)intrin->fold->imm_float[1],
+                    (ast_expression*)callpow1
+                )
+            ),
+            NULL
+        )
+    );
+
+    /* <callpow2> = pow(base, exp / 2) */
+    vec_push(callpow2->params, (ast_expression*)base);
+    vec_push(callpow2->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_DIV_F,
+            (ast_expression*)exp,
+            (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
+        )
+    );
+
+    /*
+     * <expgt1> = {
+     *     result = <callpow2>;
+     *     return result * result;
+     * }
+     */
+    vec_push(expgt1->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)result,
+            (ast_expression*)callpow2
+        )
+    );
+    vec_push(expgt1->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_MUL_F,
+                (ast_expression*)result,
+                (ast_expression*)result
+            )
+        )
+    );
+
+    /*
+     * if (exp >= 1) {
+     *     <expgt1>
+     * }
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_GE,
+                (ast_expression*)exp,
+                (ast_expression*)intrin->fold->imm_float[1]
+            ),
+            (ast_expression*)expgt1,
+            NULL
+        )
+    );
+
+    /*
+     * <callsqrt1> = sqrt(base)
+     */
+    vec_push(callsqrt1->params, (ast_expression*)base);
+
+    /*
+     * low        = 0.0f;
+     * high       = 1.0f;
+     * square     = sqrt(base);
+     * accumulate = square;
+     * mid        = high / 2.0f;
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)low,
+            (ast_expression*)intrin->fold->imm_float[0]
+        )
+    );
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)high,
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)square,
+            (ast_expression*)callsqrt1
+        )
+    );
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)accumulate,
+            (ast_expression*)square
+        )
+    );
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)mid,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_DIV_F,
+                (ast_expression*)high,
+                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
+            )
+        )
+    );
+
+    /*
+     * <midltexp> = {
+     *     low         = mid;
+     *     accumulate *= square;
+     * }
+     */
+    vec_push(midltexp->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)low,
+            (ast_expression*)mid
+        )
+    );
+    vec_push(midltexp->exprs,
+        (ast_expression*)ast_binstore_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            (ast_expression*)accumulate,
+            (ast_expression*)square
+        )
+    );
+
+    /*
+     * <midltexpelse> = {
+     *     high        = mid;
+     *     accumulate *= (1.0 / square);
+     * }
+     */
+    vec_push(midltexpelse->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)high,
+            (ast_expression*)mid
+        )
+    );
+    vec_push(midltexpelse->exprs,
+        (ast_expression*)ast_binstore_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            (ast_expression*)accumulate,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_DIV_F,
+                (ast_expression*)intrin->fold->imm_float[1],
+                (ast_expression*)square
+            )
+        )
+    );
+
+    /*
+     * <callsqrt2> = sqrt(square)
+     */
+    vec_push(callsqrt2->params, (ast_expression*)square);
+
+    /*
+     * <whileblock> = {
+     *     square = <callsqrt2>;
+     *     if (mid < exp)
+     *          <midltexp>;
+     *     else
+     *          <midltexpelse>;
+     *
+     *     mid = (low + high) / 2;
+     * }
+     */
+    vec_push(whileblock->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)square,
+            (ast_expression*)callsqrt2
+        )
+    );
+    vec_push(whileblock->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_LT,
+                (ast_expression*)mid,
+                (ast_expression*)exp
+            ),
+            (ast_expression*)midltexp,
+            (ast_expression*)midltexpelse
+        )
+    );
+    vec_push(whileblock->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)mid,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_DIV_F,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_ADD_F,
+                    (ast_expression*)low,
+                    (ast_expression*)high
+                ),
+                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
+            )
+        )
+    );
+
+    /*
+     * <callabs> = fabs(mid - exp)
+     */
+    vec_push(callfabs->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_SUB_F,
+            (ast_expression*)mid,
+            (ast_expression*)exp
+        )
+    );
+
+    /*
+     * while (<callfabs>  > epsilon)
+     *     <whileblock>
+     */
+    vec_push(body->exprs,
+        (ast_expression*)ast_loop_new(
+            intrin_ctx(intrin),
+            /* init */
+            NULL,
+            /* pre condition */
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_GT,
+                (ast_expression*)callfabs,
+                (ast_expression*)fold_constgen_float(intrin->fold, QC_POW_EPSILON, false)
+            ),
+            /* pre not */
+            false,
+            /* post condition */
+            NULL,
+            /* post not */
+            false,
+            /* increment expression */
+            NULL,
+            /* code block */
+            (ast_expression*)whileblock
+        )
+    );
+
+    /* return accumulate */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)accumulate
+        )
+    );
+
+    /* } */
+    vec_push(func->blocks, body);
+
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_mod(intrin_t *intrin) {
+    /*
+     * float mod(float a, float b) {
+     *     float div = a / b;
+     *     float sign = (div < 0.0f) ? -1 : 1;
+     *     return a - b * sign * floor(sign * div);
+     * }
+     */
+    ast_value    *value = NULL;
+    ast_call     *call  = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", "mod"));
+    ast_value    *a     = ast_value_new(intrin_ctx(intrin), "a",    TYPE_FLOAT);
+    ast_value    *b     = ast_value_new(intrin_ctx(intrin), "b",    TYPE_FLOAT);
+    ast_value    *div   = ast_value_new(intrin_ctx(intrin), "div",  TYPE_FLOAT);
+    ast_value    *sign  = ast_value_new(intrin_ctx(intrin), "sign", TYPE_FLOAT);
+    ast_block    *body  = ast_block_new(intrin_ctx(intrin));
+    ast_function *func  = intrin_value(intrin, &value, "mod", TYPE_FLOAT);
+
+    vec_push(value->expression.params, a);
+    vec_push(value->expression.params, b);
+
+    vec_push(body->locals, div);
+    vec_push(body->locals, sign);
+
+    /* div = a / b; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)div,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_DIV_F,
+                (ast_expression*)a,
+                (ast_expression*)b
+            )
+        )
+    );
+
+    /* sign = (div < 0.0f) ? -1 : 1; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)sign,
+            (ast_expression*)ast_ternary_new(
+                intrin_ctx(intrin),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_LT,
+                    (ast_expression*)div,
+                    (ast_expression*)intrin->fold->imm_float[0]
+                ),
+                (ast_expression*)intrin->fold->imm_float[2],
+                (ast_expression*)intrin->fold->imm_float[1]
+            )
+        )
+    );
+
+    /* floor(sign * div) */
+    vec_push(call->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            INSTR_MUL_F,
+            (ast_expression*)sign,
+            (ast_expression*)div
+        )
+    );
+
+    /* return a - b * sign * <call> */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_SUB_F,
+                (ast_expression*)a,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_MUL_F,
+                    (ast_expression*)b,
+                    (ast_expression*)ast_binary_new(
+                        intrin_ctx(intrin),
+                        INSTR_MUL_F,
+                        (ast_expression*)sign,
+                        (ast_expression*)call
+                    )
+                )
+            )
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_fabs(intrin_t *intrin) {
+    /*
+     * float fabs(float x) {
+     *     return x < 0 ? -x : x;
+     * }
+     */
+    ast_value    *value  = NULL;
+    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
+    ast_function *func   = intrin_value(intrin, &value, "fabs", TYPE_FLOAT);
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_ternary_new(
+                intrin_ctx(intrin),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_LE,
+                    (ast_expression*)arg1,
+                    (ast_expression*)intrin->fold->imm_float[0]
+                ),
+                (ast_expression*)ast_unary_new(
+                    intrin_ctx(intrin),
+                    VINSTR_NEG_F,
+                    (ast_expression*)arg1
+                ),
+                (ast_expression*)arg1
+            )
+        )
+    );
+
+    vec_push(value->expression.params, arg1);
+    vec_push(func->blocks, body);
+
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_epsilon(intrin_t *intrin) {
+    /*
+     * float epsilon(void) {
+     *     float eps = 1.0f;
+     *     do { eps /= 2.0f; } while ((1.0f + (eps / 2.0f)) != 1.0f);
+     *     return eps;
+     * }
+     */
+    ast_value    *value  = NULL;
+    ast_value    *eps    = ast_value_new(intrin_ctx(intrin), "eps", TYPE_FLOAT);
+    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
+    ast_function *func   = intrin_value(intrin, &value, "epsilon", TYPE_FLOAT);
+
+    vec_push(body->locals, eps);
+
+    /* eps = 1.0f; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)eps,
+            (ast_expression*)intrin->fold->imm_float[0]
+        )
+    );
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_loop_new(
+            intrin_ctx(intrin),
+            NULL,
+            NULL,
+            false,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_NE_F,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_ADD_F,
+                    (ast_expression*)intrin->fold->imm_float[1],
+                    (ast_expression*)ast_binary_new(
+                        intrin_ctx(intrin),
+                        INSTR_MUL_F,
+                        (ast_expression*)eps,
+                        (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
+                    )
+                ),
+                (ast_expression*)intrin->fold->imm_float[1]
+            ),
+            false,
+            NULL,
+            (ast_expression*)ast_binstore_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                INSTR_DIV_F,
+                (ast_expression*)eps,
+                (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
+            )
+        )
+    );
+
+    /* return eps; */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)eps
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_nan(intrin_t *intrin) {
+    /*
+     * float nan(void) {
+     *     float x = 0.0f;
+     *     return x / x;
+     * }
+     */
+    ast_value    *value  = NULL;
+    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_function *func   = intrin_value(intrin, &value, "nan", TYPE_FLOAT);
+    ast_block    *block  = ast_block_new(intrin_ctx(intrin));
+
+    vec_push(block->locals, x);
+
+    vec_push(block->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)x,
+            (ast_expression*)intrin->fold->imm_float[0]
+        )
+    );
+
+    vec_push(block->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_DIV_F,
+                (ast_expression*)x,
+                (ast_expression*)x
+            )
+        )
+    );
+
+    vec_push(func->blocks, block);
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_inf(intrin_t *intrin) {
+    /*
+     * float inf(void) {
+     *     float x = 1.0f;
+     *     float y = 0.0f;
+     *     return x / y;
+     * }
+     */
+    ast_value    *value  = NULL;
+    ast_value    *x      = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_value    *y      = ast_value_new(intrin_ctx(intrin), "y", TYPE_FLOAT);
+    ast_function *func   = intrin_value(intrin, &value, "inf", TYPE_FLOAT);
+    ast_block    *block  = ast_block_new(intrin_ctx(intrin));
+    size_t        i;
+
+    vec_push(block->locals, x);
+    vec_push(block->locals, y);
+
+    /* to keep code size down */
+    for (i = 0; i <= 1; i++) {
+        vec_push(block->exprs,
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)((i == 0) ? x : y),
+                (ast_expression*)intrin->fold->imm_float[i]
+            )
+        );
+    }
+
+    vec_push(block->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_DIV_F,
+                (ast_expression*)x,
+                (ast_expression*)y
+            )
+        )
+    );
+
+    vec_push(func->blocks, block);
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_ln(intrin_t *intrin) {
+    /*
+     * float log(float power, float base) {
+     *   float whole;
+     *   float nth
+     *   float sign = 1.0f;
+     *   float eps  = epsilon();
+     *
+     *   if (power <= 1.0f || bbase <= 1.0) {
+     *       if (power <= 0.0f || base <= 0.0f)
+     *           return nan();
+     *
+     *       if (power < 1.0f) {
+     *           power = 1.0f / power;
+     *           sign *= -1.0f;
+     *       }
+     *
+     *       if (base < 1.0f) {
+     *           sign *= -1.0f;
+     *           base  = 1.0f / base;
+     *       }
+     *   }
+     *
+     *   float A_i       = 1;
+     *   float B_i       = 0;
+     *   float A_iminus1 = 0;
+     *   float B_iminus1 = 1;
+     *
+     *   for (;;) {
+     *       whole = power;
+     *       nth   = 0.0f;
+     *
+     *       while (whole >= base) {
+     *           float base2    = base;
+     *           float n2       = 1.0f;
+     *           float newbase2 = base2 * base2;
+     *
+     *           while (whole >= newbase2) {
+     *               base2     = newbase2;
+     *               n2       *= 2;
+     *               newbase2 *= newbase2;
+     *           }
+     *
+     *           whole /= base2;
+     *           nth += n2;
+     *       }
+     *
+     *       float b_iplus1 = n;
+     *       float A_iplus1 = b_iplus1 * A_i + A_iminus1;
+     *       float B_iplus1 = b_iplus1 * B_i + B_iminus1;
+     *
+     *       A_iminus1 = A_i;
+     *       B_iminus1 = B_i;
+     *       A_i       = A_iplus1;
+     *       B_i       = B_iplus1;
+     *
+     *       if (whole <= 1.0f + eps)
+     *           break;
+     *
+     *       power = base;
+     *       bower = whole;
+     *   }
+     *   return sign * A_i / B_i;
+     * }
+     */
+
+    ast_value    *value      = NULL;
+    ast_value    *power      = ast_value_new(intrin_ctx(intrin), "power",     TYPE_FLOAT);
+    ast_value    *base       = ast_value_new(intrin_ctx(intrin), "base",      TYPE_FLOAT);
+    ast_value    *whole      = ast_value_new(intrin_ctx(intrin), "whole",     TYPE_FLOAT);
+    ast_value    *nth        = ast_value_new(intrin_ctx(intrin), "nth",       TYPE_FLOAT);
+    ast_value    *sign       = ast_value_new(intrin_ctx(intrin), "sign",      TYPE_FLOAT);
+    ast_value    *A_i        = ast_value_new(intrin_ctx(intrin), "A_i",       TYPE_FLOAT);
+    ast_value    *B_i        = ast_value_new(intrin_ctx(intrin), "B_i",       TYPE_FLOAT);
+    ast_value    *A_iminus1  = ast_value_new(intrin_ctx(intrin), "A_iminus1", TYPE_FLOAT);
+    ast_value    *B_iminus1  = ast_value_new(intrin_ctx(intrin), "B_iminus1", TYPE_FLOAT);
+    ast_value    *b_iplus1   = ast_value_new(intrin_ctx(intrin), "b_iplus1",  TYPE_FLOAT);
+    ast_value    *A_iplus1   = ast_value_new(intrin_ctx(intrin), "A_iplus1",  TYPE_FLOAT);
+    ast_value    *B_iplus1   = ast_value_new(intrin_ctx(intrin), "B_iplus1",  TYPE_FLOAT);
+    ast_value    *eps        = ast_value_new(intrin_ctx(intrin), "eps",       TYPE_FLOAT);
+    ast_value    *base2      = ast_value_new(intrin_ctx(intrin), "base2",     TYPE_FLOAT);
+    ast_value    *n2         = ast_value_new(intrin_ctx(intrin), "n2",        TYPE_FLOAT);
+    ast_value    *newbase2   = ast_value_new(intrin_ctx(intrin), "newbase2",  TYPE_FLOAT);
+    ast_block    *block      = ast_block_new(intrin_ctx(intrin));
+    ast_block    *plt1orblt1 = ast_block_new(intrin_ctx(intrin)); /* (power <= 1.0f || base <= 1.0f) */
+    ast_block    *plt1       = ast_block_new(intrin_ctx(intrin)); /* (power < 1.0f) */
+    ast_block    *blt1       = ast_block_new(intrin_ctx(intrin)); /* (base  < 1.0f) */
+    ast_block    *forloop    = ast_block_new(intrin_ctx(intrin)); /* for(;;) */
+    ast_block    *whileloop  = ast_block_new(intrin_ctx(intrin)); /* while (whole >= base) */
+    ast_block    *nestwhile  = ast_block_new(intrin_ctx(intrin)); /* while (whole >= newbase2) */
+    ast_function *func       = intrin_value(intrin, &value, "ln", TYPE_FLOAT);
+    size_t        i;
+
+    vec_push(value->expression.params, power);
+    vec_push(value->expression.params, base);
+
+    vec_push(block->locals, whole);
+    vec_push(block->locals, nth);
+    vec_push(block->locals, sign);
+    vec_push(block->locals, eps);
+    vec_push(block->locals, A_i);
+    vec_push(block->locals, B_i);
+    vec_push(block->locals, A_iminus1);
+    vec_push(block->locals, B_iminus1);
+
+    /* sign = 1.0f; */
+    vec_push(block->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)sign,
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    /* eps = __builtin_epsilon(); */
+    vec_push(block->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)eps,
+            (ast_expression*)ast_call_new(
+                intrin_ctx(intrin),
+                intrin_func_self(intrin, "__builtin_epsilon", "ln")
+            )
+        )
+    );
+
+    /*
+     * A_i       = 1;
+     * B_i       = 0;
+     * A_iminus1 = 0;
+     * B_iminus1 = 1;
+     */
+    for (i = 0; i <= 1; i++) {
+        int j;
+        for (j = 1; j >= 0; j--) {
+            vec_push(block->exprs,
+                (ast_expression*)ast_store_new(
+                    intrin_ctx(intrin),
+                    INSTR_STORE_F,
+                    (ast_expression*)((j) ? ((i) ? B_iminus1 : A_i)
+                                          : ((i) ? A_iminus1 : B_i)),
+                    (ast_expression*)intrin->fold->imm_float[j]
+                )
+            );
+        }
+    }
+
+    /*
+     * <plt1> = {
+     *     power = 1.0f / power;
+     *     sign *= -1.0f;
+     * }
+     * <blt1> = {
+     *     base  = 1.0f / base;
+     *     sign *= -1.0f;
+     * }
+     */
+    for (i = 0; i <= 1; i++) {
+        vec_push(((i) ? blt1 : plt1)->exprs,
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)((i) ? base : power),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_DIV_F,
+                    (ast_expression*)intrin->fold->imm_float[1],
+                    (ast_expression*)((i) ? base : power)
+                )
+            )
+        );
+        vec_push(plt1->exprs,
+            (ast_expression*)ast_binstore_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                INSTR_MUL_F,
+                (ast_expression*)sign,
+                (ast_expression*)intrin->fold->imm_float[2]
+            )
+        );
+    }
+
+    /*
+     * <plt1orblt1> = {
+     *     if (power <= 0.0 || base <= 0.0f)
+     *         return __builtin_nan();
+     *     if (power < 1.0f)
+     *         <plt1>
+     *     if (base < 1.0f)
+     *         <blt1>
+     * }
+     */
+    vec_push(plt1orblt1->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_OR,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_LE,
+                    (ast_expression*)power,
+                    (ast_expression*)intrin->fold->imm_float[0]
+                ),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_LE,
+                    (ast_expression*)base,
+                    (ast_expression*)intrin->fold->imm_float[0]
+                )
+            ),
+            (ast_expression*)ast_return_new(
+                intrin_ctx(intrin),
+                (ast_expression*)ast_call_new(
+                    intrin_ctx(intrin),
+                    intrin_func_self(intrin, "__builtin_nan", "ln")
+                )
+            ),
+            NULL
+        )
+    );
+
+    for (i = 0; i <= 1; i++) {
+        vec_push(plt1orblt1->exprs,
+            (ast_expression*)ast_ifthen_new(
+                intrin_ctx(intrin),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_LT,
+                    (ast_expression*)((i) ? base : power),
+                    (ast_expression*)intrin->fold->imm_float[1]
+                ),
+                (ast_expression*)((i) ? blt1 : plt1),
+                NULL
+            )
+        );
+    }
+
+    vec_push(block->exprs, (ast_expression*)plt1orblt1);
+
+
+    /* whole = power; */
+    vec_push(forloop->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)whole,
+            (ast_expression*)power
+        )
+    );
+
+    /* nth = 0.0f; */
+    vec_push(forloop->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)nth,
+            (ast_expression*)intrin->fold->imm_float[0]
+        )
+    );
+
+    /* base2 = base; */
+    vec_push(whileloop->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)base2,
+            (ast_expression*)base
+        )
+    );
+
+    /* n2 = 1.0f; */
+    vec_push(whileloop->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)n2,
+            (ast_expression*)intrin->fold->imm_float[1]
+        )
+    );
+
+    /* newbase2 = base2 * base2; */
+    vec_push(whileloop->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)newbase2,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_MUL_F,
+                (ast_expression*)base2,
+                (ast_expression*)base2
+            )
+        )
+    );
+
+    /* while loop locals */
+    vec_push(whileloop->locals, base2);
+    vec_push(whileloop->locals, n2);
+    vec_push(whileloop->locals, newbase2);
+
+    /* base2 = newbase2; */
+    vec_push(nestwhile->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)base2,
+            (ast_expression*)newbase2
+        )
+    );
+
+    /* n2 *= 2; */
+    vec_push(nestwhile->exprs,
+        (ast_expression*)ast_binstore_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            (ast_expression*)n2,
+            (ast_expression*)intrin->fold->imm_float[3] /* 2.0f */
+        )
+    );
+
+    /* newbase2 *= newbase2; */
+    vec_push(nestwhile->exprs,
+        (ast_expression*)ast_binstore_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            INSTR_MUL_F,
+            (ast_expression*)newbase2,
+            (ast_expression*)newbase2
+        )
+    );
+
+    /* while (whole >= newbase2) */
+    vec_push(whileloop->exprs,
+        (ast_expression*)ast_loop_new(
+            intrin_ctx(intrin),
+            NULL,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_GE,
+                (ast_expression*)whole,
+                (ast_expression*)newbase2
+            ),
+            false,
+            NULL,
+            false,
+            NULL,
+            (ast_expression*)nestwhile
+        )
+    );
+
+    /* whole /= base2; */
+    vec_push(whileloop->exprs,
+        (ast_expression*)ast_binstore_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            INSTR_DIV_F,
+            (ast_expression*)whole,
+            (ast_expression*)base2
+        )
+    );
+
+    /* nth += n2; */
+    vec_push(whileloop->exprs,
+        (ast_expression*)ast_binstore_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            INSTR_ADD_F,
+            (ast_expression*)nth,
+            (ast_expression*)n2
+        )
+    );
+
+    /* while (whole >= base) */
+    vec_push(forloop->exprs,
+        (ast_expression*)ast_loop_new(
+            intrin_ctx(intrin),
+            NULL,
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_GE,
+                (ast_expression*)whole,
+                (ast_expression*)base
+            ),
+            false,
+            NULL,
+            false,
+            NULL,
+            (ast_expression*)whileloop
+        )
+    );
+
+    vec_push(forloop->locals, b_iplus1);
+    vec_push(forloop->locals, A_iplus1);
+    vec_push(forloop->locals, B_iplus1);
+
+    /* b_iplus1 = nth; */
+    vec_push(forloop->exprs,
+        (ast_expression*)ast_store_new(
+            intrin_ctx(intrin),
+            INSTR_STORE_F,
+            (ast_expression*)b_iplus1,
+            (ast_expression*)nth
+        )
+    );
+
+    /*
+     * A_iplus1 = b_iplus1 * A_i + A_iminus1;
+     * B_iplus1 = b_iplus1 * B_i + B_iminus1;
+     */
+    for (i = 0; i <= 1; i++) {
+        vec_push(forloop->exprs,
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)((i) ? B_iplus1 : A_iplus1),
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_ADD_F,
+                    (ast_expression*)ast_binary_new(
+                        intrin_ctx(intrin),
+                        INSTR_MUL_F,
+                        (ast_expression*)b_iplus1,
+                        (ast_expression*) ((i) ? B_i : A_i)
+                    ),
+                    (ast_expression*)((i) ? B_iminus1 : A_iminus1)
+                )
+            )
+        );
+    }
+
+    /*
+     * A_iminus1 = A_i;
+     * B_iminus1 = B_i;
+     */
+    for (i = 0; i <= 1; i++) {
+        vec_push(forloop->exprs,
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)((i) ? B_iminus1 : A_iminus1),
+                (ast_expression*)((i) ? B_i       : A_i)
+            )
+        );
+    }
+
+    /*
+     * A_i = A_iplus1;
+     * B_i = B_iplus1;
+     */
+    for (i = 0; i <= 1; i++) {
+        vec_push(forloop->exprs,
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)((i) ? B_i      : A_i),
+                (ast_expression*)((i) ? B_iplus1 : A_iplus1)
+            )
+        );
+    }
+
+    /*
+     * if (whole <= 1.0f + eps)
+     *     break;
+     */
+    vec_push(forloop->exprs,
+        (ast_expression*)ast_ifthen_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_LE,
+                (ast_expression*)whole,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_ADD_F,
+                    (ast_expression*)intrin->fold->imm_float[1],
+                    (ast_expression*)eps
+                )
+            ),
+            (ast_expression*)ast_breakcont_new(
+                intrin_ctx(intrin),
+                false,
+                0
+            ),
+            NULL
+        )
+    );
+
+    /*
+     * power = base;
+     * base  = whole;
+     */
+    for (i = 0; i <= 1; i++) {
+        vec_push(forloop->exprs,
+            (ast_expression*)ast_store_new(
+                intrin_ctx(intrin),
+                INSTR_STORE_F,
+                (ast_expression*)((i) ? base  : power),
+                (ast_expression*)((i) ? whole : base)
+            )
+        );
+    }
+
+    /* add the for loop block */
+    vec_push(block->exprs,
+        (ast_expression*)ast_loop_new(
+            intrin_ctx(intrin),
+            NULL,
+            /* for(; 1; ) ?? (can this be NULL too?) */
+            (ast_expression*)intrin->fold->imm_float[1],
+            false,
+            NULL,
+            false,
+            NULL,
+            (ast_expression*)forloop
+        )
+    );
+
+    /* return sign * A_i / B_il */
+    vec_push(block->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)ast_binary_new(
+                intrin_ctx(intrin),
+                INSTR_MUL_F,
+                (ast_expression*)sign,
+                (ast_expression*)ast_binary_new(
+                    intrin_ctx(intrin),
+                    INSTR_DIV_F,
+                    (ast_expression*)A_i,
+                    (ast_expression*)B_i
+                )
+            )
+        )
+    );
+
+    vec_push(func->blocks, block);
+    intrin_reg(intrin, value, func);
+
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_log_variant(intrin_t *intrin, const char *name, float base) {
+    ast_value    *value  = NULL;
+    ast_call     *callln = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "__builtin_ln", name));
+    ast_value    *arg1   = ast_value_new(intrin_ctx(intrin), "x", TYPE_FLOAT);
+    ast_block    *body   = ast_block_new(intrin_ctx(intrin));
+    ast_function *func   = intrin_value(intrin, &value, name, TYPE_FLOAT);
+
+    vec_push(value->expression.params, arg1);
+
+    vec_push(callln->params, (ast_expression*)arg1);
+    vec_push(callln->params, (ast_expression*)fold_constgen_float(intrin->fold, base, false));
+
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)callln
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_log(intrin_t *intrin) {
+    return intrin_log_variant(intrin, "log", 2.7182818284590452354);
+}
+static ast_expression *intrin_log10(intrin_t *intrin) {
+    return intrin_log_variant(intrin, "log10", 10);
+}
+static ast_expression *intrin_log2(intrin_t *intrin) {
+    return intrin_log_variant(intrin, "log2", 2);
+}
+static ast_expression *intrin_logb(intrin_t *intrin) {
+    /* FLT_RADIX == 2 for now */
+    return intrin_log_variant(intrin, "log2", 2);
+}
+
+static ast_expression *intrin_shift_variant(intrin_t *intrin, const char *name, size_t instr) {
+    /*
+     * float [shift] (float a, float b) {
+     *   return floor(a [instr] pow(2, b));
+     */
+    ast_value    *value     = NULL;
+    ast_call     *callpow   = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "pow", name));
+    ast_call     *callfloor = ast_call_new (intrin_ctx(intrin), intrin_func_self(intrin, "floor", name));
+    ast_value    *a         = ast_value_new(intrin_ctx(intrin), "a", TYPE_FLOAT);
+    ast_value    *b         = ast_value_new(intrin_ctx(intrin), "b", TYPE_FLOAT);
+    ast_block    *body      = ast_block_new(intrin_ctx(intrin));
+    ast_function *func      = intrin_value(intrin, &value, name, TYPE_FLOAT);
+
+    vec_push(value->expression.params, a);
+    vec_push(value->expression.params, b);
+
+    /* <callpow> = pow(2, b) */
+    vec_push(callpow->params, (ast_expression*)intrin->fold->imm_float[3]);
+    vec_push(callpow->params, (ast_expression*)b);
+
+    /* <callfloor> = floor(a [instr] <callpow>) */
+    vec_push(
+        callfloor->params,
+        (ast_expression*)ast_binary_new(
+            intrin_ctx(intrin),
+            instr,
+            (ast_expression*)a,
+            (ast_expression*)callpow
+        )
+    );
+
+    /* return <callfloor> */
+    vec_push(body->exprs,
+        (ast_expression*)ast_return_new(
+            intrin_ctx(intrin),
+            (ast_expression*)callfloor
+        )
+    );
+
+    vec_push(func->blocks, body);
+    intrin_reg(intrin, value, func);
+    return (ast_expression*)value;
+}
+
+static ast_expression *intrin_lshift(intrin_t *intrin) {
+    return intrin_shift_variant(intrin, "lshift", INSTR_MUL_F);
+}
+
+static ast_expression *intrin_rshift(intrin_t *intrin) {
+    return intrin_shift_variant(intrin, "rshift", INSTR_DIV_F);
+}
+
+/*
+ * TODO: make static (and handle ast_type_string) here for the builtin
+ * instead of in SYA parse close.
+ */
+ast_expression *intrin_debug_typestring(intrin_t *intrin) {
+    (void)intrin;
+    return (ast_expression*)0x1;
+}
+
+static const intrin_func_t intrinsics[] = {
+    {&intrin_isfinite,         "__builtin_isfinite",         "isfinite", 1},
+    {&intrin_isinf,            "__builtin_isinf",            "isinf",    1},
+    {&intrin_isnan,            "__builtin_isnan",            "isnan",    1},
+    {&intrin_isnormal,         "__builtin_isnormal",         "isnormal", 1},
+    {&intrin_signbit,          "__builtin_signbit",          "signbit",  1},
+    {&intrin_acosh,            "__builtin_acosh",            "acosh",    1},
+    {&intrin_asinh,            "__builtin_asinh",            "asinh",    1},
+    {&intrin_atanh,            "__builtin_atanh",            "atanh",    1},
+    {&intrin_exp,              "__builtin_exp",              "exp",      1},
+    {&intrin_exp2,             "__builtin_exp2",             "exp2",     1},
+    {&intrin_expm1,            "__builtin_expm1",            "expm1",    1},
+    {&intrin_mod,              "__builtin_mod",              "mod",      2},
+    {&intrin_pow,              "__builtin_pow",              "pow",      2},
+    {&intrin_fabs,             "__builtin_fabs",             "fabs",     1},
+    {&intrin_log,              "__builtin_log",              "log",      1},
+    {&intrin_log10,            "__builtin_log10",            "log10",    1},
+    {&intrin_log2,             "__builtin_log2",             "log2",     1},
+    {&intrin_logb,             "__builtin_logb",             "logb",     1},
+    {&intrin_lshift,           "__builtin_lshift",           "",         2},
+    {&intrin_rshift,           "__builtin_rshift",           "",         2},
+    {&intrin_epsilon,          "__builtin_epsilon",          "",         0},
+    {&intrin_nan,              "__builtin_nan",              "",         0},
+    {&intrin_inf,              "__builtin_inf",              "",         0},
+    {&intrin_ln,               "__builtin_ln",               "",         2},
+    {&intrin_debug_typestring, "__builtin_debug_typestring", "",         0},
+    {&intrin_nullfunc,         "#nullfunc",                  "",         0}
+};
+
+static void intrin_error(intrin_t *intrin, const char *fmt, ...) {
+    va_list ap;
+    va_start(ap, fmt);
+    vcompile_error(intrin->parser->lex->tok.ctx, fmt, ap);
+    va_end(ap);
+}
+
+/* exposed */
+intrin_t *intrin_init(parser_t *parser) {
+    intrin_t *intrin = (intrin_t*)mem_a(sizeof(intrin_t));
+    size_t    i;
+
+    intrin->parser     = parser;
+    intrin->fold       = parser->fold;
+    intrin->intrinsics = NULL;
+    intrin->generated  = NULL;
+
+    vec_append(intrin->intrinsics, GMQCC_ARRAY_COUNT(intrinsics), intrinsics);
+
+    /* populate with null pointers for tracking generation */
+    for (i = 0; i < GMQCC_ARRAY_COUNT(intrinsics); i++)
+        vec_push(intrin->generated, NULL);
+
+    return intrin;
+}
+
+void intrin_cleanup(intrin_t *intrin) {
+    vec_free(intrin->intrinsics);
+    vec_free(intrin->generated);
+    mem_d(intrin);
+}
+
+ast_expression *intrin_fold(intrin_t *intrin, ast_value *value, ast_expression **exprs) {
+    size_t i;
+    if (!value || !value->name)
+        return NULL;
+    for (i = 0; i < vec_size(intrin->intrinsics); i++)
+        if (!strcmp(value->name, intrin->intrinsics[i].name))
+            return (vec_size(exprs) != intrin->intrinsics[i].args)
+                        ? NULL
+                        : fold_intrin(intrin->fold, value->name + 10, exprs);
+    return NULL;
+}
+
+static GMQCC_INLINE ast_expression *intrin_func_try(intrin_t *intrin, size_t offset, const char *compare) {
+    size_t i;
+    for (i = 0; i < vec_size(intrin->intrinsics); i++) {
+        if (strcmp(*(char **)((char *)&intrin->intrinsics[i] + offset), compare))
+            continue;
+        if (intrin->generated[i])
+            return intrin->generated[i];
+        return intrin->generated[i] = intrin->intrinsics[i].intrin(intrin);
+    }
+    return NULL;
+}
+
+static ast_expression *intrin_func_self(intrin_t *intrin, const char *name, const char *from) {
+    size_t           i;
+    ast_expression  *find;
+
+    /* try current first */
+    if ((find = parser_find_global(intrin->parser, name)) && ((ast_value*)find)->expression.vtype == TYPE_FUNCTION)
+        for (i = 0; i < vec_size(intrin->parser->functions); ++i)
+            if (((ast_value*)find)->name && !strcmp(intrin->parser->functions[i]->name, ((ast_value*)find)->name) && intrin->parser->functions[i]->builtin < 0)
+                return find;
+    /* try name second */
+    if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, name),  name)))
+        return find;
+    /* try alias third */
+    if ((find = intrin_func_try(intrin, offsetof(intrin_func_t, alias), name)))
+        return find;
+
+    if (from) {
+        intrin_error(intrin, "need function `%s', compiler depends on it for `__builtin_%s'", name, from);
+        return intrin_func_self(intrin, "#nullfunc", NULL);
+    }
+    return NULL;
+}
+
+ast_expression *intrin_func(intrin_t *intrin, const char *name) {
+    return intrin_func_self(intrin, name, NULL);
+}
diff --git a/ir.c b/ir.c
deleted file mode 100644 (file)
index 8c3d234..0000000
--- a/ir.c
+++ /dev/null
@@ -1,4441 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "ir.h"
-
-/***********************************************************************
- * Type sizes used at multiple points in the IR codegen
- */
-
-const char *type_name[TYPE_COUNT] = {
-    "void",
-    "string",
-    "float",
-    "vector",
-    "entity",
-    "field",
-    "function",
-    "pointer",
-    "integer",
-    "variant",
-    "struct",
-    "union",
-    "array",
-
-    "nil",
-    "<no-expression>"
-};
-
-static size_t type_sizeof_[TYPE_COUNT] = {
-    1, /* TYPE_VOID     */
-    1, /* TYPE_STRING   */
-    1, /* TYPE_FLOAT    */
-    3, /* TYPE_VECTOR   */
-    1, /* TYPE_ENTITY   */
-    1, /* TYPE_FIELD    */
-    1, /* TYPE_FUNCTION */
-    1, /* TYPE_POINTER  */
-    1, /* TYPE_INTEGER  */
-    3, /* TYPE_VARIANT  */
-    0, /* TYPE_STRUCT   */
-    0, /* TYPE_UNION    */
-    0, /* TYPE_ARRAY    */
-    0, /* TYPE_NIL      */
-    0, /* TYPE_NOESPR   */
-};
-
-const uint16_t type_store_instr[TYPE_COUNT] = {
-    INSTR_STORE_F, /* should use I when having integer support */
-    INSTR_STORE_S,
-    INSTR_STORE_F,
-    INSTR_STORE_V,
-    INSTR_STORE_ENT,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FNC,
-    INSTR_STORE_ENT, /* should use I */
-#if 0
-    INSTR_STORE_I, /* integer type */
-#else
-    INSTR_STORE_F,
-#endif
-
-    INSTR_STORE_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t field_store_instr[TYPE_COUNT] = {
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_V,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-    INSTR_STORE_FLD,
-#if 0
-    INSTR_STORE_FLD, /* integer type */
-#else
-    INSTR_STORE_FLD,
-#endif
-
-    INSTR_STORE_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_storep_instr[TYPE_COUNT] = {
-    INSTR_STOREP_F, /* should use I when having integer support */
-    INSTR_STOREP_S,
-    INSTR_STOREP_F,
-    INSTR_STOREP_V,
-    INSTR_STOREP_ENT,
-    INSTR_STOREP_FLD,
-    INSTR_STOREP_FNC,
-    INSTR_STOREP_ENT, /* should use I */
-#if 0
-    INSTR_STOREP_ENT, /* integer type */
-#else
-    INSTR_STOREP_F,
-#endif
-
-    INSTR_STOREP_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_eq_instr[TYPE_COUNT] = {
-    INSTR_EQ_F, /* should use I when having integer support */
-    INSTR_EQ_S,
-    INSTR_EQ_F,
-    INSTR_EQ_V,
-    INSTR_EQ_E,
-    INSTR_EQ_E, /* FLD has no comparison */
-    INSTR_EQ_FNC,
-    INSTR_EQ_E, /* should use I */
-#if 0
-    INSTR_EQ_I,
-#else
-    INSTR_EQ_F,
-#endif
-
-    INSTR_EQ_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_ne_instr[TYPE_COUNT] = {
-    INSTR_NE_F, /* should use I when having integer support */
-    INSTR_NE_S,
-    INSTR_NE_F,
-    INSTR_NE_V,
-    INSTR_NE_E,
-    INSTR_NE_E, /* FLD has no comparison */
-    INSTR_NE_FNC,
-    INSTR_NE_E, /* should use I */
-#if 0
-    INSTR_NE_I,
-#else
-    INSTR_NE_F,
-#endif
-
-    INSTR_NE_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-const uint16_t type_not_instr[TYPE_COUNT] = {
-    INSTR_NOT_F, /* should use I when having integer support */
-    VINSTR_END,  /* not to be used, depends on string related -f flags */
-    INSTR_NOT_F,
-    INSTR_NOT_V,
-    INSTR_NOT_ENT,
-    INSTR_NOT_ENT,
-    INSTR_NOT_FNC,
-    INSTR_NOT_ENT, /* should use I */
-#if 0
-    INSTR_NOT_I, /* integer type */
-#else
-    INSTR_NOT_F,
-#endif
-
-    INSTR_NOT_V, /* variant, should never be accessed */
-
-    VINSTR_END, /* struct */
-    VINSTR_END, /* union  */
-    VINSTR_END, /* array  */
-    VINSTR_END, /* nil    */
-    VINSTR_END, /* noexpr */
-};
-
-/* protos */
-static ir_value*       ir_value_var(const char *name, int st, int vtype);
-static bool            ir_value_set_name(ir_value*, const char *name);
-static void            ir_value_dump(ir_value*, int (*oprintf)(const char*,...));
-
-static ir_value*       ir_gen_extparam_proto(ir_builder *ir);
-static void            ir_gen_extparam      (ir_builder *ir);
-
-static bool            ir_builder_set_name(ir_builder *self, const char *name);
-
-static ir_function*    ir_function_new(struct ir_builder_s *owner, int returntype);
-static bool            ir_function_set_name(ir_function*, const char *name);
-static void            ir_function_delete(ir_function*);
-static void            ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...));
-
-static ir_value*       ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label,
-                                        int op, ir_value *a, ir_value *b, int outype);
-static void            ir_block_delete(ir_block*);
-static ir_block*       ir_block_new(struct ir_function_s *owner, const char *label);
-static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
-static bool            ir_block_set_label(ir_block*, const char *label);
-static void            ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...));
-
-static bool            ir_instr_op(ir_instr*, int op, ir_value *value, bool writing);
-static void            ir_instr_delete(ir_instr*);
-static void            ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...));
-/* error functions */
-
-static void irerror(lex_ctx_t ctx, const char *msg, ...)
-{
-    va_list ap;
-    va_start(ap, msg);
-    con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap);
-    va_end(ap);
-}
-
-static bool GMQCC_WARN irwarning(lex_ctx_t 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;
-}
-
-/***********************************************************************
- * Vector utility functions
- */
-
-static bool GMQCC_WARN vec_ir_value_find(ir_value **vec, const ir_value *what, size_t *idx)
-{
-    size_t i;
-    size_t len = vec_size(vec);
-    for (i = 0; i < len; ++i) {
-        if (vec[i] == what) {
-            if (idx) *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx)
-{
-    size_t i;
-    size_t len = vec_size(vec);
-    for (i = 0; i < len; ++i) {
-        if (vec[i] == what) {
-            if (idx) *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool GMQCC_WARN vec_ir_instr_find(ir_instr **vec, ir_instr *what, size_t *idx)
-{
-    size_t i;
-    size_t len = vec_size(vec);
-    for (i = 0; i < len; ++i) {
-        if (vec[i] == what) {
-            if (idx) *idx = i;
-            return true;
-        }
-    }
-    return false;
-}
-
-/***********************************************************************
- * IR Builder
- */
-
-static void ir_block_delete_quick(ir_block* self);
-static void ir_instr_delete_quick(ir_instr *self);
-static void ir_function_delete_quick(ir_function *self);
-
-ir_builder* ir_builder_new(const char *modulename)
-{
-    ir_builder* self;
-    size_t      i;
-
-    self = (ir_builder*)mem_a(sizeof(*self));
-    if (!self)
-        return NULL;
-
-    self->functions   = NULL;
-    self->globals     = NULL;
-    self->fields      = NULL;
-    self->filenames   = NULL;
-    self->filestrings = NULL;
-    self->htglobals   = util_htnew(IR_HT_SIZE);
-    self->htfields    = util_htnew(IR_HT_SIZE);
-    self->htfunctions = util_htnew(IR_HT_SIZE);
-
-    self->extparams       = NULL;
-    self->extparam_protos = NULL;
-
-    self->first_common_globaltemp = 0;
-    self->max_globaltemps         = 0;
-    self->first_common_local      = 0;
-    self->max_locals              = 0;
-
-    self->str_immediate = 0;
-    self->name = NULL;
-    if (!ir_builder_set_name(self, modulename)) {
-        mem_d(self);
-        return NULL;
-    }
-
-    self->nil = ir_value_var("nil", store_value, TYPE_NIL);
-    self->nil->cvq = CV_CONST;
-
-    for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
-        /* we write to them, but they're not supposed to be used outside the IR, so
-         * let's not allow the generation of ir_instrs which use these.
-         * So it's a constant noexpr.
-         */
-        self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR);
-        self->vinstr_temp[i]->cvq = CV_CONST;
-    }
-
-    self->reserved_va_count = NULL;
-    self->coverage_func     = NULL;
-
-    self->code              = code_init();
-
-    return self;
-}
-
-void ir_builder_delete(ir_builder* self)
-{
-    size_t i;
-    util_htdel(self->htglobals);
-    util_htdel(self->htfields);
-    util_htdel(self->htfunctions);
-    mem_d((void*)self->name);
-    for (i = 0; i != vec_size(self->functions); ++i) {
-        ir_function_delete_quick(self->functions[i]);
-    }
-    vec_free(self->functions);
-    for (i = 0; i != vec_size(self->extparams); ++i) {
-        ir_value_delete(self->extparams[i]);
-    }
-    vec_free(self->extparams);
-    vec_free(self->extparam_protos);
-    for (i = 0; i != vec_size(self->globals); ++i) {
-        ir_value_delete(self->globals[i]);
-    }
-    vec_free(self->globals);
-    for (i = 0; i != vec_size(self->fields); ++i) {
-        ir_value_delete(self->fields[i]);
-    }
-    ir_value_delete(self->nil);
-    for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
-        ir_value_delete(self->vinstr_temp[i]);
-    }
-    vec_free(self->fields);
-    vec_free(self->filenames);
-    vec_free(self->filestrings);
-
-    code_cleanup(self->code);
-    mem_d(self);
-}
-
-bool ir_builder_set_name(ir_builder *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-static ir_function* ir_builder_get_function(ir_builder *self, const char *name)
-{
-    return (ir_function*)util_htget(self->htfunctions, name);
-}
-
-ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype)
-{
-    ir_function *fn = ir_builder_get_function(self, name);
-    if (fn) {
-        return NULL;
-    }
-
-    fn = ir_function_new(self, outtype);
-    if (!ir_function_set_name(fn, name))
-    {
-        ir_function_delete(fn);
-        return NULL;
-    }
-    vec_push(self->functions, fn);
-    util_htset(self->htfunctions, name, fn);
-
-    fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION);
-    if (!fn->value) {
-        ir_function_delete(fn);
-        return NULL;
-    }
-
-    fn->value->hasvalue = true;
-    fn->value->outtype = outtype;
-    fn->value->constval.vfunc = fn;
-    fn->value->context = fn->context;
-
-    return fn;
-}
-
-static ir_value* ir_builder_get_global(ir_builder *self, const char *name)
-{
-    return (ir_value*)util_htget(self->htglobals, name);
-}
-
-ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype)
-{
-    ir_value *ve;
-
-    if (name[0] != '#')
-    {
-        ve = ir_builder_get_global(self, name);
-        if (ve) {
-            return NULL;
-        }
-    }
-
-    ve = ir_value_var(name, store_global, vtype);
-    vec_push(self->globals, ve);
-    util_htset(self->htglobals, name, ve);
-    return ve;
-}
-
-ir_value* ir_builder_get_va_count(ir_builder *self)
-{
-    if (self->reserved_va_count)
-        return self->reserved_va_count;
-    return (self->reserved_va_count = ir_builder_create_global(self, "reserved:va_count", TYPE_FLOAT));
-}
-
-static ir_value* ir_builder_get_field(ir_builder *self, const char *name)
-{
-    return (ir_value*)util_htget(self->htfields, name);
-}
-
-
-ir_value* ir_builder_create_field(ir_builder *self, const char *name, int vtype)
-{
-    ir_value *ve = ir_builder_get_field(self, name);
-    if (ve) {
-        return NULL;
-    }
-
-    ve = ir_value_var(name, store_global, TYPE_FIELD);
-    ve->fieldtype = vtype;
-    vec_push(self->fields, ve);
-    util_htset(self->htfields, name, ve);
-    return ve;
-}
-
-/***********************************************************************
- *IR Function
- */
-
-static bool ir_function_naive_phi(ir_function*);
-static void ir_function_enumerate(ir_function*);
-static bool ir_function_calculate_liferanges(ir_function*);
-static bool ir_function_allocate_locals(ir_function*);
-
-ir_function* ir_function_new(ir_builder* owner, int outtype)
-{
-    ir_function *self;
-    self = (ir_function*)mem_a(sizeof(*self));
-
-    if (!self)
-        return NULL;
-
-    memset(self, 0, sizeof(*self));
-
-    self->name = NULL;
-    if (!ir_function_set_name(self, "<@unnamed>")) {
-        mem_d(self);
-        return NULL;
-    }
-    self->flags = 0;
-
-    self->owner = owner;
-    self->context.file = "<@no context>";
-    self->context.line = 0;
-    self->outtype = outtype;
-    self->value = NULL;
-    self->builtin = 0;
-
-    self->params = NULL;
-    self->blocks = NULL;
-    self->values = NULL;
-    self->locals = NULL;
-
-    self->max_varargs = 0;
-
-    self->code_function_def = -1;
-    self->allocated_locals = 0;
-    self->globaltemps      = 0;
-
-    self->run_id = 0;
-    return self;
-}
-
-bool ir_function_set_name(ir_function *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-static void ir_function_delete_quick(ir_function *self)
-{
-    size_t i;
-    mem_d((void*)self->name);
-
-    for (i = 0; i != vec_size(self->blocks); ++i)
-        ir_block_delete_quick(self->blocks[i]);
-    vec_free(self->blocks);
-
-    vec_free(self->params);
-
-    for (i = 0; i != vec_size(self->values); ++i)
-        ir_value_delete(self->values[i]);
-    vec_free(self->values);
-
-    for (i = 0; i != vec_size(self->locals); ++i)
-        ir_value_delete(self->locals[i]);
-    vec_free(self->locals);
-
-    /* self->value is deleted by the builder */
-
-    mem_d(self);
-}
-
-void ir_function_delete(ir_function *self)
-{
-    size_t i;
-    mem_d((void*)self->name);
-
-    for (i = 0; i != vec_size(self->blocks); ++i)
-        ir_block_delete(self->blocks[i]);
-    vec_free(self->blocks);
-
-    vec_free(self->params);
-
-    for (i = 0; i != vec_size(self->values); ++i)
-        ir_value_delete(self->values[i]);
-    vec_free(self->values);
-
-    for (i = 0; i != vec_size(self->locals); ++i)
-        ir_value_delete(self->locals[i]);
-    vec_free(self->locals);
-
-    /* self->value is deleted by the builder */
-
-    mem_d(self);
-}
-
-static void ir_function_collect_value(ir_function *self, ir_value *v)
-{
-    vec_push(self->values, v);
-}
-
-ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label)
-{
-    ir_block* bn = ir_block_new(self, label);
-    bn->context = ctx;
-    vec_push(self->blocks, bn);
-
-    if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func)
-        (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false);
-
-    return bn;
-}
-
-static bool instr_is_operation(uint16_t op)
-{
-    return ( (op >= INSTR_MUL_F  && op <= INSTR_GT) ||
-             (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) ||
-             (op == INSTR_ADDRESS) ||
-             (op >= INSTR_NOT_F  && op <= INSTR_NOT_FNC) ||
-             (op >= INSTR_AND    && op <= INSTR_BITOR) ||
-             (op >= INSTR_CALL0  && op <= INSTR_CALL8) ||
-             (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) );
-}
-
-static bool ir_function_pass_peephole(ir_function *self)
-{
-    size_t b;
-
-    for (b = 0; b < vec_size(self->blocks); ++b) {
-        size_t    i;
-        ir_block *block = self->blocks[b];
-
-        for (i = 0; i < vec_size(block->instr); ++i) {
-            ir_instr *inst;
-            inst = block->instr[i];
-
-            if (i >= 1 &&
-                (inst->opcode >= INSTR_STORE_F &&
-                 inst->opcode <= INSTR_STORE_FNC))
-            {
-                ir_instr *store;
-                ir_instr *oper;
-                ir_value *value;
-
-                store = inst;
-
-                oper  = block->instr[i-1];
-                if (!instr_is_operation(oper->opcode))
-                    continue;
-
-                /* Don't change semantics of MUL_VF in engines where these may not alias. */
-                if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) {
-                    if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1])
-                        continue;
-                    if (oper->opcode == INSTR_MUL_FV && oper->_ops[1]->memberof == oper->_ops[2])
-                        continue;
-                }
-
-                value = oper->_ops[0];
-
-                /* only do it for SSA values */
-                if (value->store != store_value)
-                    continue;
-
-                /* don't optimize out the temp if it's used later again */
-                if (vec_size(value->reads) != 1)
-                    continue;
-
-                /* The very next store must use this value */
-                if (value->reads[0] != store)
-                    continue;
-
-                /* And of course the store must _read_ from it, so it's in
-                 * OP 1 */
-                if (store->_ops[1] != value)
-                    continue;
-
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                (void)!ir_instr_op(oper, 0, store->_ops[0], true);
-
-                vec_remove(block->instr, i, 1);
-                ir_instr_delete(store);
-            }
-            else if (inst->opcode == VINSTR_COND)
-            {
-                /* COND on a value resulting from a NOT could
-                 * remove the NOT and swap its operands
-                 */
-                while (true) {
-                    ir_block *tmp;
-                    size_t    inotid;
-                    ir_instr *inot;
-                    ir_value *value;
-                    value = inst->_ops[0];
-
-                    if (value->store != store_value ||
-                        vec_size(value->reads) != 1 ||
-                        value->reads[0] != inst)
-                    {
-                        break;
-                    }
-
-                    inot = value->writes[0];
-                    if (inot->_ops[0] != value ||
-                        inot->opcode < INSTR_NOT_F ||
-                        inot->opcode > INSTR_NOT_FNC ||
-                        inot->opcode == INSTR_NOT_V || /* can't do these */
-                        inot->opcode == INSTR_NOT_S)
-                    {
-                        break;
-                    }
-
-                    /* count */
-                    ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                    /* change operand */
-                    (void)!ir_instr_op(inst, 0, inot->_ops[1], false);
-                    /* remove NOT */
-                    tmp = inot->owner;
-                    for (inotid = 0; inotid < vec_size(tmp->instr); ++inotid) {
-                        if (tmp->instr[inotid] == inot)
-                            break;
-                    }
-                    if (inotid >= vec_size(tmp->instr)) {
-                        compile_error(inst->context, "sanity-check failed: failed to find instruction to optimize out");
-                        return false;
-                    }
-                    vec_remove(tmp->instr, inotid, 1);
-                    ir_instr_delete(inot);
-                    /* swap ontrue/onfalse */
-                    tmp = inst->bops[0];
-                    inst->bops[0] = inst->bops[1];
-                    inst->bops[1] = tmp;
-                }
-                continue;
-            }
-        }
-    }
-
-    return true;
-}
-
-static bool ir_function_pass_tailrecursion(ir_function *self)
-{
-    size_t b, p;
-
-    for (b = 0; b < vec_size(self->blocks); ++b) {
-        ir_value *funcval;
-        ir_instr *ret, *call, *store = NULL;
-        ir_block *block = self->blocks[b];
-
-        if (!block->final || vec_size(block->instr) < 2)
-            continue;
-
-        ret = block->instr[vec_size(block->instr)-1];
-        if (ret->opcode != INSTR_DONE && ret->opcode != INSTR_RETURN)
-            continue;
-
-        call = block->instr[vec_size(block->instr)-2];
-        if (call->opcode >= INSTR_STORE_F && call->opcode <= INSTR_STORE_FNC) {
-            /* account for the unoptimized
-             * CALL
-             * STORE %return, %tmp
-             * RETURN %tmp
-             * version
-             */
-            if (vec_size(block->instr) < 3)
-                continue;
-
-            store = call;
-            call = block->instr[vec_size(block->instr)-3];
-        }
-
-        if (call->opcode < INSTR_CALL0 || call->opcode > INSTR_CALL8)
-            continue;
-
-        if (store) {
-            /* optimize out the STORE */
-            if (ret->_ops[0]   &&
-                ret->_ops[0]   == store->_ops[0] &&
-                store->_ops[1] == call->_ops[0])
-            {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                call->_ops[0] = store->_ops[0];
-                vec_remove(block->instr, vec_size(block->instr) - 2, 1);
-                ir_instr_delete(store);
-            }
-            else
-                continue;
-        }
-
-        if (!call->_ops[0])
-            continue;
-
-        funcval = call->_ops[1];
-        if (!funcval)
-            continue;
-        if (funcval->vtype != TYPE_FUNCTION || funcval->constval.vfunc != self)
-            continue;
-
-        /* now we have a CALL and a RET, check if it's a tailcall */
-        if (ret->_ops[0] && call->_ops[0] != ret->_ops[0])
-            continue;
-
-        ++opts_optimizationcount[OPTIM_TAIL_RECURSION];
-        vec_shrinkby(block->instr, 2);
-
-        block->final = false; /* open it back up */
-
-        /* emite parameter-stores */
-        for (p = 0; p < vec_size(call->params); ++p) {
-            /* assert(call->params_count <= self->locals_count); */
-            if (!ir_block_create_store(block, call->context, self->locals[p], call->params[p])) {
-                irerror(call->context, "failed to create tailcall store instruction for parameter %i", (int)p);
-                return false;
-            }
-        }
-        if (!ir_block_create_jump(block, call->context, self->blocks[0])) {
-            irerror(call->context, "failed to create tailcall jump");
-            return false;
-        }
-
-        ir_instr_delete(call);
-        ir_instr_delete(ret);
-    }
-
-    return true;
-}
-
-bool ir_function_finalize(ir_function *self)
-{
-    size_t i;
-
-    if (self->builtin)
-        return true;
-
-    if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
-        if (!ir_function_pass_peephole(self)) {
-            irerror(self->context, "generic optimization pass broke something in `%s`", self->name);
-            return false;
-        }
-    }
-
-    if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) {
-        if (!ir_function_pass_tailrecursion(self)) {
-            irerror(self->context, "tail-recursion optimization pass broke something in `%s`", self->name);
-            return false;
-        }
-    }
-
-    if (!ir_function_naive_phi(self)) {
-        irerror(self->context, "internal error: ir_function_naive_phi failed");
-        return false;
-    }
-
-    for (i = 0; i < vec_size(self->locals); ++i) {
-        ir_value *v = self->locals[i];
-        if (v->vtype == TYPE_VECTOR ||
-            (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR))
-        {
-            ir_value_vector_member(v, 0);
-            ir_value_vector_member(v, 1);
-            ir_value_vector_member(v, 2);
-        }
-    }
-    for (i = 0; i < vec_size(self->values); ++i) {
-        ir_value *v = self->values[i];
-        if (v->vtype == TYPE_VECTOR ||
-            (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR))
-        {
-            ir_value_vector_member(v, 0);
-            ir_value_vector_member(v, 1);
-            ir_value_vector_member(v, 2);
-        }
-    }
-
-    ir_function_enumerate(self);
-
-    if (!ir_function_calculate_liferanges(self))
-        return false;
-    if (!ir_function_allocate_locals(self))
-        return false;
-    return true;
-}
-
-ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param)
-{
-    ir_value *ve;
-
-    if (param &&
-        vec_size(self->locals) &&
-        self->locals[vec_size(self->locals)-1]->store != store_param) {
-        irerror(self->context, "cannot add parameters after adding locals");
-        return NULL;
-    }
-
-    ve = ir_value_var(name, (param ? store_param : store_local), vtype);
-    if (param)
-        ve->locked = true;
-    vec_push(self->locals, ve);
-    return ve;
-}
-
-/***********************************************************************
- *IR Block
- */
-
-ir_block* ir_block_new(ir_function* owner, const char *name)
-{
-    ir_block *self;
-    self = (ir_block*)mem_a(sizeof(*self));
-    if (!self)
-        return NULL;
-
-    memset(self, 0, sizeof(*self));
-
-    self->label = NULL;
-    if (name && !ir_block_set_label(self, name)) {
-        mem_d(self);
-        return NULL;
-    }
-    self->owner = owner;
-    self->context.file = "<@no context>";
-    self->context.line = 0;
-    self->final = false;
-
-    self->instr   = NULL;
-    self->entries = NULL;
-    self->exits   = NULL;
-
-    self->eid = 0;
-    self->is_return = false;
-
-    self->living = NULL;
-
-    self->generated = false;
-
-    return self;
-}
-
-static void ir_block_delete_quick(ir_block* self)
-{
-    size_t i;
-    if (self->label) mem_d(self->label);
-    for (i = 0; i != vec_size(self->instr); ++i)
-        ir_instr_delete_quick(self->instr[i]);
-    vec_free(self->instr);
-    vec_free(self->entries);
-    vec_free(self->exits);
-    vec_free(self->living);
-    mem_d(self);
-}
-
-void ir_block_delete(ir_block* self)
-{
-    size_t i;
-    if (self->label) mem_d(self->label);
-    for (i = 0; i != vec_size(self->instr); ++i)
-        ir_instr_delete(self->instr[i]);
-    vec_free(self->instr);
-    vec_free(self->entries);
-    vec_free(self->exits);
-    vec_free(self->living);
-    mem_d(self);
-}
-
-bool ir_block_set_label(ir_block *self, const char *name)
-{
-    if (self->label)
-        mem_d((void*)self->label);
-    self->label = util_strdup(name);
-    return !!self->label;
-}
-
-/***********************************************************************
- *IR Instructions
- */
-
-static ir_instr* ir_instr_new(lex_ctx_t ctx, ir_block* owner, int op)
-{
-    ir_instr *self;
-    self = (ir_instr*)mem_a(sizeof(*self));
-    if (!self)
-        return NULL;
-
-    self->owner = owner;
-    self->context = ctx;
-    self->opcode = op;
-    self->_ops[0] = NULL;
-    self->_ops[1] = NULL;
-    self->_ops[2] = NULL;
-    self->bops[0] = NULL;
-    self->bops[1] = NULL;
-
-    self->phi    = NULL;
-    self->params = NULL;
-
-    self->eid = 0;
-
-    self->likely = true;
-    return self;
-}
-
-static void ir_instr_delete_quick(ir_instr *self)
-{
-    vec_free(self->phi);
-    vec_free(self->params);
-    mem_d(self);
-}
-
-static void ir_instr_delete(ir_instr *self)
-{
-    size_t i;
-    /* The following calls can only delete from
-     * vectors, we still want to delete this instruction
-     * so ignore the return value. Since with the warn_unused_result attribute
-     * gcc doesn't care about an explicit: (void)foo(); to ignore the result,
-     * I have to improvise here and use if(foo());
-     */
-    for (i = 0; i < vec_size(self->phi); ++i) {
-        size_t idx;
-        if (vec_ir_instr_find(self->phi[i].value->writes, self, &idx))
-            vec_remove(self->phi[i].value->writes, idx, 1);
-        if (vec_ir_instr_find(self->phi[i].value->reads, self, &idx))
-            vec_remove(self->phi[i].value->reads, idx, 1);
-    }
-    vec_free(self->phi);
-    for (i = 0; i < vec_size(self->params); ++i) {
-        size_t idx;
-        if (vec_ir_instr_find(self->params[i]->writes, self, &idx))
-            vec_remove(self->params[i]->writes, idx, 1);
-        if (vec_ir_instr_find(self->params[i]->reads, self, &idx))
-            vec_remove(self->params[i]->reads, idx, 1);
-    }
-    vec_free(self->params);
-    (void)!ir_instr_op(self, 0, NULL, false);
-    (void)!ir_instr_op(self, 1, NULL, false);
-    (void)!ir_instr_op(self, 2, NULL, false);
-    mem_d(self);
-}
-
-static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing)
-{
-    if (v && v->vtype == TYPE_NOEXPR) {
-        irerror(self->context, "tried to use a NOEXPR value");
-        return false;
-    }
-
-    if (self->_ops[op]) {
-        size_t idx;
-        if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx))
-            vec_remove(self->_ops[op]->writes, idx, 1);
-        else if (vec_ir_instr_find(self->_ops[op]->reads, self, &idx))
-            vec_remove(self->_ops[op]->reads, idx, 1);
-    }
-    if (v) {
-        if (writing)
-            vec_push(v->writes, self);
-        else
-            vec_push(v->reads, self);
-    }
-    self->_ops[op] = v;
-    return true;
-}
-
-/***********************************************************************
- *IR Value
- */
-
-static void ir_value_code_setaddr(ir_value *self, int32_t gaddr)
-{
-    self->code.globaladdr = gaddr;
-    if (self->members[0]) self->members[0]->code.globaladdr = gaddr;
-    if (self->members[1]) self->members[1]->code.globaladdr = gaddr;
-    if (self->members[2]) self->members[2]->code.globaladdr = gaddr;
-}
-
-static int32_t ir_value_code_addr(const ir_value *self)
-{
-    if (self->store == store_return)
-        return OFS_RETURN + self->code.addroffset;
-    return self->code.globaladdr + self->code.addroffset;
-}
-
-ir_value* ir_value_var(const char *name, int storetype, int vtype)
-{
-    ir_value *self;
-    self = (ir_value*)mem_a(sizeof(*self));
-    self->vtype = vtype;
-    self->fieldtype = TYPE_VOID;
-    self->outtype = TYPE_VOID;
-    self->store = storetype;
-    self->flags = 0;
-
-    self->reads  = NULL;
-    self->writes = NULL;
-
-    self->cvq          = CV_NONE;
-    self->hasvalue     = false;
-    self->context.file = "<@no context>";
-    self->context.line = 0;
-    self->name = NULL;
-    if (name && !ir_value_set_name(self, name)) {
-        irerror(self->context, "out of memory");
-        mem_d(self);
-        return NULL;
-    }
-
-    memset(&self->constval, 0, sizeof(self->constval));
-    memset(&self->code,     0, sizeof(self->code));
-
-    self->members[0] = NULL;
-    self->members[1] = NULL;
-    self->members[2] = NULL;
-    self->memberof = NULL;
-
-    self->unique_life = false;
-    self->locked      = false;
-    self->callparam   = false;
-
-    self->life = NULL;
-    return self;
-}
-
-/*  helper function */
-static ir_value* ir_builder_imm_float(ir_builder *self, float value, bool add_to_list) {
-    ir_value *v = ir_value_var("#IMMEDIATE", store_global, TYPE_FLOAT);
-    v->flags |= IR_FLAG_ERASABLE;
-    v->hasvalue = true;
-    v->cvq = CV_CONST;
-    v->constval.vfloat = value;
-
-    vec_push(self->globals, v);
-    if (add_to_list)
-        vec_push(self->const_floats, v);
-    return v;
-}
-
-ir_value* ir_value_vector_member(ir_value *self, unsigned int member)
-{
-    char     *name;
-    size_t    len;
-    ir_value *m;
-    if (member >= 3)
-        return NULL;
-
-    if (self->members[member])
-        return self->members[member];
-
-    if (self->name) {
-        len = strlen(self->name);
-        name = (char*)mem_a(len + 3);
-        memcpy(name, self->name, len);
-        name[len+0] = '_';
-        name[len+1] = 'x' + member;
-        name[len+2] = '\0';
-    }
-    else
-        name = NULL;
-
-    if (self->vtype == TYPE_VECTOR)
-    {
-        m = ir_value_var(name, self->store, TYPE_FLOAT);
-        if (name)
-            mem_d(name);
-        if (!m)
-            return NULL;
-        m->context = self->context;
-
-        self->members[member] = m;
-        m->code.addroffset = member;
-    }
-    else if (self->vtype == TYPE_FIELD)
-    {
-        if (self->fieldtype != TYPE_VECTOR)
-            return NULL;
-        m = ir_value_var(name, self->store, TYPE_FIELD);
-        if (name)
-            mem_d(name);
-        if (!m)
-            return NULL;
-        m->fieldtype = TYPE_FLOAT;
-        m->context = self->context;
-
-        self->members[member] = m;
-        m->code.addroffset = member;
-    }
-    else
-    {
-        irerror(self->context, "invalid member access on %s", self->name);
-        return NULL;
-    }
-
-    m->memberof = self;
-    return m;
-}
-
-static GMQCC_INLINE size_t ir_value_sizeof(const ir_value *self)
-{
-    if (self->vtype == TYPE_FIELD && self->fieldtype == TYPE_VECTOR)
-        return type_sizeof_[TYPE_VECTOR];
-    return type_sizeof_[self->vtype];
-}
-
-static ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int vtype)
-{
-    ir_value *v = ir_value_var(name, storetype, vtype);
-    if (!v)
-        return NULL;
-    ir_function_collect_value(owner, v);
-    return v;
-}
-
-void ir_value_delete(ir_value* self)
-{
-    size_t i;
-    if (self->name)
-        mem_d((void*)self->name);
-    if (self->hasvalue)
-    {
-        if (self->vtype == TYPE_STRING)
-            mem_d((void*)self->constval.vstring);
-    }
-    if (!(self->flags & IR_FLAG_SPLIT_VECTOR)) {
-        for (i = 0; i < 3; ++i) {
-            if (self->members[i])
-                ir_value_delete(self->members[i]);
-        }
-    }
-    vec_free(self->reads);
-    vec_free(self->writes);
-    vec_free(self->life);
-    mem_d(self);
-}
-
-bool ir_value_set_name(ir_value *self, const char *name)
-{
-    if (self->name)
-        mem_d((void*)self->name);
-    self->name = util_strdup(name);
-    return !!self->name;
-}
-
-bool ir_value_set_float(ir_value *self, float f)
-{
-    if (self->vtype != TYPE_FLOAT)
-        return false;
-    self->constval.vfloat = f;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_func(ir_value *self, int f)
-{
-    if (self->vtype != TYPE_FUNCTION)
-        return false;
-    self->constval.vint = f;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_vector(ir_value *self, vec3_t v)
-{
-    if (self->vtype != TYPE_VECTOR)
-        return false;
-    self->constval.vvec = v;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_field(ir_value *self, ir_value *fld)
-{
-    if (self->vtype != TYPE_FIELD)
-        return false;
-    self->constval.vpointer = fld;
-    self->hasvalue = true;
-    return true;
-}
-
-bool ir_value_set_string(ir_value *self, const char *str)
-{
-    if (self->vtype != TYPE_STRING)
-        return false;
-    self->constval.vstring = util_strdupe(str);
-    self->hasvalue = true;
-    return true;
-}
-
-#if 0
-bool ir_value_set_int(ir_value *self, int i)
-{
-    if (self->vtype != TYPE_INTEGER)
-        return false;
-    self->constval.vint = i;
-    self->hasvalue = true;
-    return true;
-}
-#endif
-
-bool ir_value_lives(ir_value *self, size_t at)
-{
-    size_t i;
-    for (i = 0; i < vec_size(self->life); ++i)
-    {
-        ir_life_entry_t *life = &self->life[i];
-        if (life->start <= at && at <= life->end)
-            return true;
-        if (life->start > at) /* since it's ordered */
-            return false;
-    }
-    return false;
-}
-
-static bool ir_value_life_insert(ir_value *self, size_t idx, ir_life_entry_t e)
-{
-    size_t k;
-    vec_push(self->life, e);
-    for (k = vec_size(self->life)-1; k > idx; --k)
-        self->life[k] = self->life[k-1];
-    self->life[idx] = e;
-    return true;
-}
-
-static bool ir_value_life_merge(ir_value *self, size_t s)
-{
-    size_t i;
-    const size_t vs = vec_size(self->life);
-    ir_life_entry_t *life = NULL;
-    ir_life_entry_t *before = NULL;
-    ir_life_entry_t new_entry;
-
-    /* Find the first range >= s */
-    for (i = 0; i < vs; ++i)
-    {
-        before = life;
-        life = &self->life[i];
-        if (life->start > s)
-            break;
-    }
-    /* nothing found? append */
-    if (i == vs) {
-        ir_life_entry_t e;
-        if (life && life->end+1 == s)
-        {
-            /* previous life range can be merged in */
-            life->end++;
-            return true;
-        }
-        if (life && life->end >= s)
-            return false;
-        e.start = e.end = s;
-        vec_push(self->life, e);
-        return true;
-    }
-    /* found */
-    if (before)
-    {
-        if (before->end + 1 == s &&
-            life->start - 1 == s)
-        {
-            /* merge */
-            before->end = life->end;
-            vec_remove(self->life, i, 1);
-            return true;
-        }
-        if (before->end + 1 == s)
-        {
-            /* extend before */
-            before->end++;
-            return true;
-        }
-        /* already contained */
-        if (before->end >= s)
-            return false;
-    }
-    /* extend */
-    if (life->start - 1 == s)
-    {
-        life->start--;
-        return true;
-    }
-    /* insert a new entry */
-    new_entry.start = new_entry.end = s;
-    return ir_value_life_insert(self, i, new_entry);
-}
-
-static bool ir_value_life_merge_into(ir_value *self, const ir_value *other)
-{
-    size_t i, myi;
-
-    if (!vec_size(other->life))
-        return true;
-
-    if (!vec_size(self->life)) {
-        size_t count = vec_size(other->life);
-        ir_life_entry_t *life = vec_add(self->life, count);
-        memcpy(life, other->life, count * sizeof(*life));
-        return true;
-    }
-
-    myi = 0;
-    for (i = 0; i < vec_size(other->life); ++i)
-    {
-        const ir_life_entry_t *life = &other->life[i];
-        while (true)
-        {
-            ir_life_entry_t *entry = &self->life[myi];
-
-            if (life->end+1 < entry->start)
-            {
-                /* adding an interval before entry */
-                if (!ir_value_life_insert(self, myi, *life))
-                    return false;
-                ++myi;
-                break;
-            }
-
-            if (life->start <  entry->start &&
-                life->end+1 >= entry->start)
-            {
-                /* starts earlier and overlaps */
-                entry->start = life->start;
-            }
-
-            if (life->end   >  entry->end &&
-                life->start <= entry->end+1)
-            {
-                /* ends later and overlaps */
-                entry->end = life->end;
-            }
-
-            /* see if our change combines it with the next ranges */
-            while (myi+1 < vec_size(self->life) &&
-                   entry->end+1 >= self->life[1+myi].start)
-            {
-                /* overlaps with (myi+1) */
-                if (entry->end < self->life[1+myi].end)
-                    entry->end = self->life[1+myi].end;
-                vec_remove(self->life, myi+1, 1);
-                entry = &self->life[myi];
-            }
-
-            /* see if we're after the entry */
-            if (life->start > entry->end)
-            {
-                ++myi;
-                /* append if we're at the end */
-                if (myi >= vec_size(self->life)) {
-                    vec_push(self->life, *life);
-                    break;
-                }
-                /* otherweise check the next range */
-                continue;
-            }
-            break;
-        }
-    }
-    return true;
-}
-
-static bool ir_values_overlap(const ir_value *a, const ir_value *b)
-{
-    /* For any life entry in A see if it overlaps with
-     * any life entry in B.
-     * Note that the life entries are orderes, so we can make a
-     * more efficient algorithm there than naively translating the
-     * statement above.
-     */
-
-    ir_life_entry_t *la, *lb, *enda, *endb;
-
-    /* first of all, if either has no life range, they cannot clash */
-    if (!vec_size(a->life) || !vec_size(b->life))
-        return false;
-
-    la = a->life;
-    lb = b->life;
-    enda = la + vec_size(a->life);
-    endb = lb + vec_size(b->life);
-    while (true)
-    {
-        /* check if the entries overlap, for that,
-         * both must start before the other one ends.
-         */
-        if (la->start < lb->end &&
-            lb->start < la->end)
-        {
-            return true;
-        }
-
-        /* entries are ordered
-         * one entry is earlier than the other
-         * that earlier entry will be moved forward
-         */
-        if (la->start < lb->start)
-        {
-            /* order: A B, move A forward
-             * check if we hit the end with A
-             */
-            if (++la == enda)
-                break;
-        }
-        else /* if (lb->start < la->start)  actually <= */
-        {
-            /* order: B A, move B forward
-             * check if we hit the end with B
-             */
-            if (++lb == endb)
-                break;
-        }
-    }
-    return false;
-}
-
-/***********************************************************************
- *IR main operations
- */
-
-static bool ir_check_unreachable(ir_block *self)
-{
-    /* The IR should never have to deal with unreachable code */
-    if (!self->final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/)
-        return true;
-    irerror(self->context, "unreachable statement (%s)", self->label);
-    return false;
-}
-
-bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-
-    if (target->store == store_value &&
-        (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC))
-    {
-        irerror(self->context, "cannot store to an SSA value");
-        irerror(self->context, "trying to store: %s <- %s", target->name, what->name);
-        irerror(self->context, "instruction: %s", util_instr_str[op]);
-        return false;
-    }
-
-    in = ir_instr_new(ctx, self, op);
-    if (!in)
-        return false;
-
-    if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) ||
-        !ir_instr_op(in, 1, what, false))
-    {
-        ir_instr_delete(in);
-        return false;
-    }
-    vec_push(self->instr, in);
-    return true;
-}
-
-bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-
-    in = ir_instr_new(ctx, self, INSTR_STATE);
-    if (!in)
-        return false;
-
-    if (!ir_instr_op(in, 0, frame, false) ||
-        !ir_instr_op(in, 1, think, false))
-    {
-        ir_instr_delete(in);
-        return false;
-    }
-    vec_push(self->instr, in);
-    return true;
-}
-
-static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
-{
-    int op = 0;
-    int vtype;
-    if (target->vtype == TYPE_VARIANT)
-        vtype = what->vtype;
-    else
-        vtype = target->vtype;
-
-#if 0
-    if      (vtype == TYPE_FLOAT   && what->vtype == TYPE_INTEGER)
-        op = INSTR_CONV_ITOF;
-    else if (vtype == TYPE_INTEGER && what->vtype == TYPE_FLOAT)
-        op = INSTR_CONV_FTOI;
-#endif
-        op = type_store_instr[vtype];
-
-    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
-        if (op == INSTR_STORE_FLD && what->fieldtype == TYPE_VECTOR)
-            op = INSTR_STORE_V;
-    }
-
-    return ir_block_create_store_op(self, ctx, op, target, what);
-}
-
-bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
-{
-    int op = 0;
-    int vtype;
-
-    if (target->vtype != TYPE_POINTER)
-        return false;
-
-    /* storing using pointer - target is a pointer, type must be
-     * inferred from source
-     */
-    vtype = what->vtype;
-
-    op = type_storep_instr[vtype];
-    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
-        if (op == INSTR_STOREP_FLD && what->fieldtype == TYPE_VECTOR)
-            op = INSTR_STOREP_V;
-    }
-
-    return ir_block_create_store_op(self, ctx, op, target, what);
-}
-
-bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-
-    self->final = true;
-
-    self->is_return = true;
-    in = ir_instr_new(ctx, self, INSTR_RETURN);
-    if (!in)
-        return false;
-
-    if (v && !ir_instr_op(in, 0, v, false)) {
-        ir_instr_delete(in);
-        return false;
-    }
-
-    vec_push(self->instr, in);
-    return true;
-}
-
-bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v,
-                        ir_block *ontrue, ir_block *onfalse)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-    self->final = true;
-    /*in = ir_instr_new(ctx, self, (v->vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/
-    in = ir_instr_new(ctx, self, VINSTR_COND);
-    if (!in)
-        return false;
-
-    if (!ir_instr_op(in, 0, v, false)) {
-        ir_instr_delete(in);
-        return false;
-    }
-
-    in->bops[0] = ontrue;
-    in->bops[1] = onfalse;
-
-    vec_push(self->instr, in);
-
-    vec_push(self->exits, ontrue);
-    vec_push(self->exits, onfalse);
-    vec_push(ontrue->entries,  self);
-    vec_push(onfalse->entries, self);
-    return true;
-}
-
-bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to)
-{
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return false;
-    self->final = true;
-    in = ir_instr_new(ctx, self, VINSTR_JUMP);
-    if (!in)
-        return false;
-
-    in->bops[0] = to;
-    vec_push(self->instr, in);
-
-    vec_push(self->exits, to);
-    vec_push(to->entries, self);
-    return true;
-}
-
-bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to)
-{
-    self->owner->flags |= IR_FLAG_HAS_GOTO;
-    return ir_block_create_jump(self, ctx, to);
-}
-
-ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, int ot)
-{
-    ir_value *out;
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return NULL;
-    in = ir_instr_new(ctx, self, VINSTR_PHI);
-    if (!in)
-        return NULL;
-    out = ir_value_out(self->owner, label, store_value, ot);
-    if (!out) {
-        ir_instr_delete(in);
-        return NULL;
-    }
-    if (!ir_instr_op(in, 0, out, true)) {
-        ir_instr_delete(in);
-        ir_value_delete(out);
-        return NULL;
-    }
-    vec_push(self->instr, in);
-    return in;
-}
-
-ir_value* ir_phi_value(ir_instr *self)
-{
-    return self->_ops[0];
-}
-
-void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v)
-{
-    ir_phi_entry_t pe;
-
-    if (!vec_ir_block_find(self->owner->entries, b, NULL)) {
-        /* Must not be possible to cause this, otherwise the AST
-         * is doing something wrong.
-         */
-        irerror(self->context, "Invalid entry block for PHI");
-        exit(EXIT_FAILURE);
-    }
-
-    pe.value = v;
-    pe.from = b;
-    vec_push(v->reads, self);
-    vec_push(self->phi, pe);
-}
-
-/* call related code */
-ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn)
-{
-    ir_value *out;
-    ir_instr *in;
-    if (!ir_check_unreachable(self))
-        return NULL;
-    in = ir_instr_new(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0));
-    if (!in)
-        return NULL;
-    if (noreturn) {
-        self->final = true;
-        self->is_return = true;
-    }
-    out = ir_value_out(self->owner, label, (func->outtype == TYPE_VOID) ? store_return : store_value, func->outtype);
-    if (!out) {
-        ir_instr_delete(in);
-        return NULL;
-    }
-    if (!ir_instr_op(in, 0, out, true) ||
-        !ir_instr_op(in, 1, func, false))
-    {
-        ir_instr_delete(in);
-        ir_value_delete(out);
-        return NULL;
-    }
-    vec_push(self->instr, in);
-    /*
-    if (noreturn) {
-        if (!ir_block_create_return(self, ctx, NULL)) {
-            compile_error(ctx, "internal error: failed to generate dummy-return instruction");
-            ir_instr_delete(in);
-            return NULL;
-        }
-    }
-    */
-    return in;
-}
-
-ir_value* ir_call_value(ir_instr *self)
-{
-    return self->_ops[0];
-}
-
-void ir_call_param(ir_instr* self, ir_value *v)
-{
-    vec_push(self->params, v);
-    vec_push(v->reads, self);
-}
-
-/* binary op related code */
-
-ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx,
-                                const char *label, int opcode,
-                                ir_value *left, ir_value *right)
-{
-    int ot = TYPE_VOID;
-    switch (opcode) {
-        case INSTR_ADD_F:
-        case INSTR_SUB_F:
-        case INSTR_DIV_F:
-        case INSTR_MUL_F:
-        case INSTR_MUL_V:
-        case INSTR_AND:
-        case INSTR_OR:
-#if 0
-        case INSTR_AND_I:
-        case INSTR_AND_IF:
-        case INSTR_AND_FI:
-        case INSTR_OR_I:
-        case INSTR_OR_IF:
-        case INSTR_OR_FI:
-#endif
-        case INSTR_BITAND:
-        case INSTR_BITOR:
-        case VINSTR_BITXOR:
-#if 0
-        case INSTR_SUB_S: /* -- offset of string as float */
-        case INSTR_MUL_IF:
-        case INSTR_MUL_FI:
-        case INSTR_DIV_IF:
-        case INSTR_DIV_FI:
-        case INSTR_BITOR_IF:
-        case INSTR_BITOR_FI:
-        case INSTR_BITAND_FI:
-        case INSTR_BITAND_IF:
-        case INSTR_EQ_I:
-        case INSTR_NE_I:
-#endif
-            ot = TYPE_FLOAT;
-            break;
-#if 0
-        case INSTR_ADD_I:
-        case INSTR_ADD_IF:
-        case INSTR_ADD_FI:
-        case INSTR_SUB_I:
-        case INSTR_SUB_FI:
-        case INSTR_SUB_IF:
-        case INSTR_MUL_I:
-        case INSTR_DIV_I:
-        case INSTR_BITAND_I:
-        case INSTR_BITOR_I:
-        case INSTR_XOR_I:
-        case INSTR_RSHIFT_I:
-        case INSTR_LSHIFT_I:
-            ot = TYPE_INTEGER;
-            break;
-#endif
-        case INSTR_ADD_V:
-        case INSTR_SUB_V:
-        case INSTR_MUL_VF:
-        case INSTR_MUL_FV:
-        case VINSTR_BITAND_V:
-        case VINSTR_BITOR_V:
-        case VINSTR_BITXOR_V:
-        case VINSTR_BITAND_VF:
-        case VINSTR_BITOR_VF:
-        case VINSTR_BITXOR_VF:
-        case VINSTR_CROSS:
-#if 0
-        case INSTR_DIV_VF:
-        case INSTR_MUL_IV:
-        case INSTR_MUL_VI:
-#endif
-            ot = TYPE_VECTOR;
-            break;
-#if 0
-        case INSTR_ADD_SF:
-            ot = TYPE_POINTER;
-            break;
-#endif
-    /*
-     * after the following default case, the value of opcode can never
-     * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65
-     */
-        default:
-            /* ranges: */
-            /* boolean operations result in floats */
-
-            /*
-             * opcode >= 10 takes true branch opcode is at least 10
-             * opcode <= 23 takes false branch opcode is at least 24
-             */
-            if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT)
-                ot = TYPE_FLOAT;
-
-            /*
-             * At condition "opcode <= 23", the value of "opcode" must be
-             * at least 24.
-             * At condition "opcode <= 23", the value of "opcode" cannot be
-             * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}.
-             * The condition "opcode <= 23" cannot be true.
-             *
-             * Thus ot=2 (TYPE_FLOAT) can never be true
-             */
-#if 0
-            else if (opcode >= INSTR_LE && opcode <= INSTR_GT)
-                ot = TYPE_FLOAT;
-            else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI)
-                ot = TYPE_FLOAT;
-#endif
-            break;
-    };
-    if (ot == TYPE_VOID) {
-        /* The AST or parser were supposed to check this! */
-        return NULL;
-    }
-
-    return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot);
-}
-
-ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx,
-                                const char *label, int opcode,
-                                ir_value *operand)
-{
-    int ot = TYPE_FLOAT;
-    switch (opcode) {
-        case INSTR_NOT_F:
-        case INSTR_NOT_V:
-        case INSTR_NOT_S:
-        case INSTR_NOT_ENT:
-        case INSTR_NOT_FNC: /*
-        case INSTR_NOT_I:   */
-            ot = TYPE_FLOAT;
-            break;
-
-        /*
-         * Negation for virtual instructions is emulated with 0-value. Thankfully
-         * the operand for 0 already exists so we just source it from here.
-         */
-        case VINSTR_NEG_F:
-            return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_F, NULL, operand, ot);
-        case VINSTR_NEG_V:
-            return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_V, NULL, operand, TYPE_VECTOR);
-
-        default:
-            ot = operand->vtype;
-            break;
-    };
-    if (ot == TYPE_VOID) {
-        /* The AST or parser were supposed to check this! */
-        return NULL;
-    }
-
-    /* let's use the general instruction creator and pass NULL for OPB */
-    return ir_block_create_general_instr(self, ctx, label, opcode, operand, NULL, ot);
-}
-
-static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label,
-                                        int op, ir_value *a, ir_value *b, int outype)
-{
-    ir_instr *instr;
-    ir_value *out;
-
-    out = ir_value_out(self->owner, label, store_value, outype);
-    if (!out)
-        return NULL;
-
-    instr = ir_instr_new(ctx, self, op);
-    if (!instr) {
-        ir_value_delete(out);
-        return NULL;
-    }
-
-    if (!ir_instr_op(instr, 0, out, true) ||
-        !ir_instr_op(instr, 1, a, false) ||
-        !ir_instr_op(instr, 2, b, false) )
-    {
-        goto on_error;
-    }
-
-    vec_push(self->instr, instr);
-
-    return out;
-on_error:
-    ir_instr_delete(instr);
-    ir_value_delete(out);
-    return NULL;
-}
-
-ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field)
-{
-    ir_value *v;
-
-    /* Support for various pointer types todo if so desired */
-    if (ent->vtype != TYPE_ENTITY)
-        return NULL;
-
-    if (field->vtype != TYPE_FIELD)
-        return NULL;
-
-    v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER);
-    v->fieldtype = field->fieldtype;
-    return v;
-}
-
-ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, int outype)
-{
-    int op;
-    if (ent->vtype != TYPE_ENTITY)
-        return NULL;
-
-    /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */
-    if (field->vtype != TYPE_FIELD)
-        return NULL;
-
-    switch (outype)
-    {
-        case TYPE_FLOAT:    op = INSTR_LOAD_F;   break;
-        case TYPE_VECTOR:   op = INSTR_LOAD_V;   break;
-        case TYPE_STRING:   op = INSTR_LOAD_S;   break;
-        case TYPE_FIELD:    op = INSTR_LOAD_FLD; break;
-        case TYPE_ENTITY:   op = INSTR_LOAD_ENT; break;
-        case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break;
-#if 0
-        case TYPE_POINTER: op = INSTR_LOAD_I;   break;
-        case TYPE_INTEGER: op = INSTR_LOAD_I;   break;
-#endif
-        default:
-            irerror(self->context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]);
-            return NULL;
-    }
-
-    return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype);
-}
-
-/* PHI resolving breaks the SSA, and must thus be the last
- * step before life-range calculation.
- */
-
-static bool ir_block_naive_phi(ir_block *self);
-bool ir_function_naive_phi(ir_function *self)
-{
-    size_t i;
-
-    for (i = 0; i < vec_size(self->blocks); ++i)
-    {
-        if (!ir_block_naive_phi(self->blocks[i]))
-            return false;
-    }
-    return true;
-}
-
-static bool ir_block_naive_phi(ir_block *self)
-{
-    size_t i, p; /*, w;*/
-    /* FIXME: optionally, create_phi can add the phis
-     * to a list so we don't need to loop through blocks
-     * - anyway: "don't optimize YET"
-     */
-    for (i = 0; i < vec_size(self->instr); ++i)
-    {
-        ir_instr *instr = self->instr[i];
-        if (instr->opcode != VINSTR_PHI)
-            continue;
-
-        vec_remove(self->instr, i, 1);
-        --i; /* NOTE: i+1 below */
-
-        for (p = 0; p < vec_size(instr->phi); ++p)
-        {
-            ir_value *v = instr->phi[p].value;
-            ir_block *b = instr->phi[p].from;
-
-            if (v->store == store_value &&
-                vec_size(v->reads) == 1 &&
-                vec_size(v->writes) == 1)
-            {
-                /* replace the value */
-                if (!ir_instr_op(v->writes[0], 0, instr->_ops[0], true))
-                    return false;
-            }
-            else
-            {
-                /* force a move instruction */
-                ir_instr *prevjump = vec_last(b->instr);
-                vec_pop(b->instr);
-                b->final = false;
-                instr->_ops[0]->store = store_global;
-                if (!ir_block_create_store(b, instr->context, instr->_ops[0], v))
-                    return false;
-                instr->_ops[0]->store = store_value;
-                vec_push(b->instr, prevjump);
-                b->final = true;
-            }
-        }
-        ir_instr_delete(instr);
-    }
-    return true;
-}
-
-/***********************************************************************
- *IR Temp allocation code
- * Propagating value life ranges by walking through the function backwards
- * until no more changes are made.
- * In theory this should happen once more than once for every nested loop
- * level.
- * Though this implementation might run an additional time for if nests.
- */
-
-/* Enumerate instructions used by value's life-ranges
- */
-static void ir_block_enumerate(ir_block *self, size_t *_eid)
-{
-    size_t i;
-    size_t eid = *_eid;
-    for (i = 0; i < vec_size(self->instr); ++i)
-    {
-        self->instr[i]->eid = eid++;
-    }
-    *_eid = eid;
-}
-
-/* Enumerate blocks and instructions.
- * The block-enumeration is unordered!
- * We do not really use the block enumreation, however
- * the instruction enumeration is important for life-ranges.
- */
-void ir_function_enumerate(ir_function *self)
-{
-    size_t i;
-    size_t instruction_id = 0;
-    for (i = 0; i < vec_size(self->blocks); ++i)
-    {
-        /* each block now gets an additional "entry" instruction id
-         * we can use to avoid point-life issues
-         */
-        self->blocks[i]->entry_id = instruction_id;
-        ++instruction_id;
-
-        self->blocks[i]->eid = i;
-        ir_block_enumerate(self->blocks[i], &instruction_id);
-    }
-}
-
-/* Local-value allocator
- * After finishing creating the liferange of all values used in a function
- * we can allocate their global-positions.
- * This is the counterpart to register-allocation in register machines.
- */
-typedef struct {
-    ir_value **locals;
-    size_t    *sizes;
-    size_t    *positions;
-    bool      *unique;
-} function_allocator;
-
-static bool function_allocator_alloc(function_allocator *alloc, ir_value *var)
-{
-    ir_value *slot;
-    size_t vsize = ir_value_sizeof(var);
-
-    var->code.local = vec_size(alloc->locals);
-
-    slot = ir_value_var("reg", store_global, var->vtype);
-    if (!slot)
-        return false;
-
-    if (!ir_value_life_merge_into(slot, var))
-        goto localerror;
-
-    vec_push(alloc->locals, slot);
-    vec_push(alloc->sizes, vsize);
-    vec_push(alloc->unique, var->unique_life);
-
-    return true;
-
-localerror:
-    ir_value_delete(slot);
-    return false;
-}
-
-static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v)
-{
-    size_t a;
-    ir_value *slot;
-
-    if (v->unique_life)
-        return function_allocator_alloc(alloc, v);
-
-    for (a = 0; a < vec_size(alloc->locals); ++a)
-    {
-        /* if it's reserved for a unique liferange: skip */
-        if (alloc->unique[a])
-            continue;
-
-        slot = alloc->locals[a];
-
-        /* never resize parameters
-         * will be required later when overlapping temps + locals
-         */
-        if (a < vec_size(self->params) &&
-            alloc->sizes[a] < ir_value_sizeof(v))
-        {
-            continue;
-        }
-
-        if (ir_values_overlap(v, slot))
-            continue;
-
-        if (!ir_value_life_merge_into(slot, v))
-            return false;
-
-        /* adjust size for this slot */
-        if (alloc->sizes[a] < ir_value_sizeof(v))
-            alloc->sizes[a] = ir_value_sizeof(v);
-
-        v->code.local = a;
-        return true;
-    }
-    if (a >= vec_size(alloc->locals)) {
-        if (!function_allocator_alloc(alloc, v))
-            return false;
-    }
-    return true;
-}
-
-bool ir_function_allocate_locals(ir_function *self)
-{
-    size_t i;
-    bool   retval = true;
-    size_t pos;
-    bool   opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS);
-
-    ir_value *v;
-
-    function_allocator lockalloc, globalloc;
-
-    if (!vec_size(self->locals) && !vec_size(self->values))
-        return true;
-
-    globalloc.locals    = NULL;
-    globalloc.sizes     = NULL;
-    globalloc.positions = NULL;
-    globalloc.unique    = NULL;
-    lockalloc.locals    = NULL;
-    lockalloc.sizes     = NULL;
-    lockalloc.positions = NULL;
-    lockalloc.unique    = NULL;
-
-    for (i = 0; i < vec_size(self->locals); ++i)
-    {
-        v = self->locals[i];
-        if ((self->flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) {
-            v->locked      = true;
-            v->unique_life = true;
-        }
-        else if (i >= vec_size(self->params))
-            break;
-        else
-            v->locked = true; /* lock parameters locals */
-        if (!function_allocator_alloc((v->locked || !opt_gt ? &lockalloc : &globalloc), v))
-            goto error;
-    }
-    for (; i < vec_size(self->locals); ++i)
-    {
-        v = self->locals[i];
-        if (!vec_size(v->life))
-            continue;
-        if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v))
-            goto error;
-    }
-
-    /* Allocate a slot for any value that still exists */
-    for (i = 0; i < vec_size(self->values); ++i)
-    {
-        v = self->values[i];
-
-        if (!vec_size(v->life))
-            continue;
-
-        /* CALL optimization:
-         * If the value is a parameter-temp: 1 write, 1 read from a CALL
-         * and it's not "locked", write it to the OFS_PARM directly.
-         */
-        if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->locked && !v->unique_life) {
-            if (vec_size(v->reads) == 1 && vec_size(v->writes) == 1 &&
-                (v->reads[0]->opcode == VINSTR_NRCALL ||
-                 (v->reads[0]->opcode >= INSTR_CALL0 && v->reads[0]->opcode <= INSTR_CALL8)
-                )
-               )
-            {
-                size_t    param;
-                ir_instr *call = v->reads[0];
-                if (!vec_ir_value_find(call->params, v, &param)) {
-                    irerror(call->context, "internal error: unlocked parameter %s not found", v->name);
-                    goto error;
-                }
-                ++opts_optimizationcount[OPTIM_CALL_STORES];
-                v->callparam = true;
-                if (param < 8)
-                    ir_value_code_setaddr(v, OFS_PARM0 + 3*param);
-                else {
-                    size_t nprotos = vec_size(self->owner->extparam_protos);
-                    ir_value *ep;
-                    param -= 8;
-                    if (nprotos > param)
-                        ep = self->owner->extparam_protos[param];
-                    else
-                    {
-                        ep = ir_gen_extparam_proto(self->owner);
-                        while (++nprotos <= param)
-                            ep = ir_gen_extparam_proto(self->owner);
-                    }
-                    ir_instr_op(v->writes[0], 0, ep, true);
-                    call->params[param+8] = ep;
-                }
-                continue;
-            }
-            if (vec_size(v->writes) == 1 && v->writes[0]->opcode == INSTR_CALL0)
-            {
-                v->store = store_return;
-                if (v->members[0]) v->members[0]->store = store_return;
-                if (v->members[1]) v->members[1]->store = store_return;
-                if (v->members[2]) v->members[2]->store = store_return;
-                ++opts_optimizationcount[OPTIM_CALL_STORES];
-                continue;
-            }
-        }
-
-        if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v))
-            goto error;
-    }
-
-    if (!lockalloc.sizes && !globalloc.sizes) {
-        goto cleanup;
-    }
-    vec_push(lockalloc.positions, 0);
-    vec_push(globalloc.positions, 0);
-
-    /* Adjust slot positions based on sizes */
-    if (lockalloc.sizes) {
-        pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0);
-        for (i = 1; i < vec_size(lockalloc.sizes); ++i)
-        {
-            pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1];
-            vec_push(lockalloc.positions, pos);
-        }
-        self->allocated_locals = pos + vec_last(lockalloc.sizes);
-    }
-    if (globalloc.sizes) {
-        pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0);
-        for (i = 1; i < vec_size(globalloc.sizes); ++i)
-        {
-            pos = globalloc.positions[i-1] + globalloc.sizes[i-1];
-            vec_push(globalloc.positions, pos);
-        }
-        self->globaltemps = pos + vec_last(globalloc.sizes);
-    }
-
-    /* Locals need to know their new position */
-    for (i = 0; i < vec_size(self->locals); ++i) {
-        v = self->locals[i];
-        if (v->locked || !opt_gt)
-            v->code.local = lockalloc.positions[v->code.local];
-        else
-            v->code.local = globalloc.positions[v->code.local];
-    }
-    /* Take over the actual slot positions on values */
-    for (i = 0; i < vec_size(self->values); ++i) {
-        v = self->values[i];
-        if (v->locked || !opt_gt)
-            v->code.local = lockalloc.positions[v->code.local];
-        else
-            v->code.local = globalloc.positions[v->code.local];
-    }
-
-    goto cleanup;
-
-error:
-    retval = false;
-cleanup:
-    for (i = 0; i < vec_size(lockalloc.locals); ++i)
-        ir_value_delete(lockalloc.locals[i]);
-    for (i = 0; i < vec_size(globalloc.locals); ++i)
-        ir_value_delete(globalloc.locals[i]);
-    vec_free(globalloc.unique);
-    vec_free(globalloc.locals);
-    vec_free(globalloc.sizes);
-    vec_free(globalloc.positions);
-    vec_free(lockalloc.unique);
-    vec_free(lockalloc.locals);
-    vec_free(lockalloc.sizes);
-    vec_free(lockalloc.positions);
-    return retval;
-}
-
-/* Get information about which operand
- * is read from, or written to.
- */
-static void ir_op_read_write(int op, size_t *read, size_t *write)
-{
-    switch (op)
-    {
-    case VINSTR_JUMP:
-    case INSTR_GOTO:
-        *write = 0;
-        *read = 0;
-        break;
-    case INSTR_IF:
-    case INSTR_IFNOT:
-#if 0
-    case INSTR_IF_S:
-    case INSTR_IFNOT_S:
-#endif
-    case INSTR_RETURN:
-    case VINSTR_COND:
-        *write = 0;
-        *read = 1;
-        break;
-    case INSTR_STOREP_F:
-    case INSTR_STOREP_V:
-    case INSTR_STOREP_S:
-    case INSTR_STOREP_ENT:
-    case INSTR_STOREP_FLD:
-    case INSTR_STOREP_FNC:
-        *write = 0;
-        *read  = 7;
-        break;
-    default:
-        *write = 1;
-        *read = 6;
-        break;
-    };
-}
-
-static bool ir_block_living_add_instr(ir_block *self, size_t eid)
-{
-    size_t       i;
-    const size_t vs = vec_size(self->living);
-    bool         changed = false;
-    for (i = 0; i != vs; ++i)
-    {
-        if (ir_value_life_merge(self->living[i], eid))
-            changed = true;
-    }
-    return changed;
-}
-
-static bool ir_block_living_lock(ir_block *self)
-{
-    size_t i;
-    bool changed = false;
-    for (i = 0; i != vec_size(self->living); ++i)
-    {
-        if (!self->living[i]->locked) {
-            self->living[i]->locked = true;
-            changed = true;
-        }
-    }
-    return changed;
-}
-
-static bool ir_block_life_propagate(ir_block *self, bool *changed)
-{
-    ir_instr *instr;
-    ir_value *value;
-    size_t i, o, p, mem, cnt;
-    /* bitmasks which operands are read from or written to */
-    size_t read, write;
-    char dbg_ind[16];
-    dbg_ind[0] = '#';
-    dbg_ind[1] = '0';
-    (void)dbg_ind;
-
-    vec_free(self->living);
-
-    p = vec_size(self->exits);
-    for (i = 0; i < p; ++i) {
-        ir_block *prev = self->exits[i];
-        cnt = vec_size(prev->living);
-        for (o = 0; o < cnt; ++o) {
-            if (!vec_ir_value_find(self->living, prev->living[o], NULL))
-                vec_push(self->living, prev->living[o]);
-        }
-    }
-
-    i = vec_size(self->instr);
-    while (i)
-    { --i;
-        instr = self->instr[i];
-
-        /* See which operands are read and write operands */
-        ir_op_read_write(instr->opcode, &read, &write);
-
-        /* Go through the 3 main operands
-         * writes first, then reads
-         */
-        for (o = 0; o < 3; ++o)
-        {
-            if (!instr->_ops[o]) /* no such operand */
-                continue;
-
-            value = instr->_ops[o];
-
-            /* We only care about locals */
-            /* we also calculate parameter liferanges so that locals
-             * can take up parameter slots */
-            if (value->store != store_value &&
-                value->store != store_local &&
-                value->store != store_param)
-                continue;
-
-            /* write operands */
-            /* When we write to a local, we consider it "dead" for the
-             * remaining upper part of the function, since in SSA a value
-             * can only be written once (== created)
-             */
-            if (write & (1<<o))
-            {
-                size_t idx;
-                bool in_living = vec_ir_value_find(self->living, value, &idx);
-                if (!in_living)
-                {
-                    /* If the value isn't alive it hasn't been read before... */
-                    /* TODO: See if the warning can be emitted during parsing or AST processing
-                     * otherwise have warning printed here.
-                     * IF printing a warning here: include filecontext_t,
-                     * and make sure it's only printed once
-                     * since this function is run multiple times.
-                     */
-                    /* con_err( "Value only written %s\n", value->name); */
-                    if (ir_value_life_merge(value, instr->eid))
-                        *changed = true;
-                } else {
-                    /* since 'living' won't contain it
-                     * anymore, merge the value, since
-                     * (A) doesn't.
-                     */
-                    if (ir_value_life_merge(value, instr->eid))
-                        *changed = true;
-                    /* Then remove */
-                    vec_remove(self->living, idx, 1);
-                }
-                /* Removing a vector removes all members */
-                for (mem = 0; mem < 3; ++mem) {
-                    if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], &idx)) {
-                        if (ir_value_life_merge(value->members[mem], instr->eid))
-                            *changed = true;
-                        vec_remove(self->living, idx, 1);
-                    }
-                }
-                /* Removing the last member removes the vector */
-                if (value->memberof) {
-                    value = value->memberof;
-                    for (mem = 0; mem < 3; ++mem) {
-                        if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], NULL))
-                            break;
-                    }
-                    if (mem == 3 && vec_ir_value_find(self->living, value, &idx)) {
-                        if (ir_value_life_merge(value, instr->eid))
-                            *changed = true;
-                        vec_remove(self->living, idx, 1);
-                    }
-                }
-            }
-        }
-
-        /* These operations need a special case as they can break when using
-         * same source and destination operand otherwise, as the engine may
-         * read the source multiple times. */
-        if (instr->opcode == INSTR_MUL_VF ||
-            instr->opcode == VINSTR_BITAND_VF ||
-            instr->opcode == VINSTR_BITOR_VF ||
-            instr->opcode == VINSTR_BITXOR ||
-            instr->opcode == VINSTR_BITXOR_VF ||
-            instr->opcode == VINSTR_BITXOR_V ||
-            instr->opcode == VINSTR_CROSS)
-        {
-            value = instr->_ops[2];
-            /* the float source will get an additional lifetime */
-            if (ir_value_life_merge(value, instr->eid+1))
-                *changed = true;
-            if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1))
-                *changed = true;
-        }
-
-        if (instr->opcode == INSTR_MUL_FV ||
-            instr->opcode == INSTR_LOAD_V ||
-            instr->opcode == VINSTR_BITXOR ||
-            instr->opcode == VINSTR_BITXOR_VF ||
-            instr->opcode == VINSTR_BITXOR_V ||
-            instr->opcode == VINSTR_CROSS)
-        {
-            value = instr->_ops[1];
-            /* the float source will get an additional lifetime */
-            if (ir_value_life_merge(value, instr->eid+1))
-                *changed = true;
-            if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1))
-                *changed = true;
-        }
-
-        for (o = 0; o < 3; ++o)
-        {
-            if (!instr->_ops[o]) /* no such operand */
-                continue;
-
-            value = instr->_ops[o];
-
-            /* We only care about locals */
-            /* we also calculate parameter liferanges so that locals
-             * can take up parameter slots */
-            if (value->store != store_value &&
-                value->store != store_local &&
-                value->store != store_param)
-                continue;
-
-            /* read operands */
-            if (read & (1<<o))
-            {
-                if (!vec_ir_value_find(self->living, value, NULL))
-                    vec_push(self->living, value);
-                /* reading adds the full vector */
-                if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
-                    vec_push(self->living, value->memberof);
-                for (mem = 0; mem < 3; ++mem) {
-                    if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
-                        vec_push(self->living, value->members[mem]);
-                }
-            }
-        }
-        /* PHI operands are always read operands */
-        for (p = 0; p < vec_size(instr->phi); ++p)
-        {
-            value = instr->phi[p].value;
-            if (!vec_ir_value_find(self->living, value, NULL))
-                vec_push(self->living, value);
-            /* reading adds the full vector */
-            if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
-                vec_push(self->living, value->memberof);
-            for (mem = 0; mem < 3; ++mem) {
-                if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
-                    vec_push(self->living, value->members[mem]);
-            }
-        }
-
-        /* on a call, all these values must be "locked" */
-        if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) {
-            if (ir_block_living_lock(self))
-                *changed = true;
-        }
-        /* call params are read operands too */
-        for (p = 0; p < vec_size(instr->params); ++p)
-        {
-            value = instr->params[p];
-            if (!vec_ir_value_find(self->living, value, NULL))
-                vec_push(self->living, value);
-            /* reading adds the full vector */
-            if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
-                vec_push(self->living, value->memberof);
-            for (mem = 0; mem < 3; ++mem) {
-                if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
-                    vec_push(self->living, value->members[mem]);
-            }
-        }
-
-        /* (A) */
-        if (ir_block_living_add_instr(self, instr->eid))
-            *changed = true;
-    }
-    /* the "entry" instruction ID */
-    if (ir_block_living_add_instr(self, self->entry_id))
-        *changed = true;
-
-    return true;
-}
-
-bool ir_function_calculate_liferanges(ir_function *self)
-{
-    size_t i, s;
-    bool changed;
-
-    /* parameters live at 0 */
-    for (i = 0; i < vec_size(self->params); ++i)
-        if (!ir_value_life_merge(self->locals[i], 0))
-            compile_error(self->context, "internal error: failed value-life merging");
-
-    do {
-        self->run_id++;
-        changed = false;
-        i = vec_size(self->blocks);
-        while (i--) {
-            ir_block_life_propagate(self->blocks[i], &changed);
-        }
-    } while (changed);
-
-    if (vec_size(self->blocks)) {
-        ir_block *block = self->blocks[0];
-        for (i = 0; i < vec_size(block->living); ++i) {
-            ir_value *v = block->living[i];
-            if (v->store != store_local)
-                continue;
-            if (v->vtype == TYPE_VECTOR)
-                continue;
-            self->flags |= IR_FLAG_HAS_UNINITIALIZED;
-            /* find the instruction reading from it */
-            for (s = 0; s < vec_size(v->reads); ++s) {
-                if (v->reads[s]->eid == v->life[0].end)
-                    break;
-            }
-            if (s < vec_size(v->reads)) {
-                if (irwarning(v->context, WARN_USED_UNINITIALIZED,
-                              "variable `%s` may be used uninitialized in this function\n"
-                              " -> %s:%i",
-                              v->name,
-                              v->reads[s]->context.file, v->reads[s]->context.line)
-                   )
-                {
-                    return false;
-                }
-                continue;
-            }
-            if (v->memberof) {
-                ir_value *vec = v->memberof;
-                for (s = 0; s < vec_size(vec->reads); ++s) {
-                    if (vec->reads[s]->eid == v->life[0].end)
-                        break;
-                }
-                if (s < vec_size(vec->reads)) {
-                    if (irwarning(v->context, WARN_USED_UNINITIALIZED,
-                                  "variable `%s` may be used uninitialized in this function\n"
-                                  " -> %s:%i",
-                                  v->name,
-                                  vec->reads[s]->context.file, vec->reads[s]->context.line)
-                       )
-                    {
-                        return false;
-                    }
-                    continue;
-                }
-            }
-            if (irwarning(v->context, WARN_USED_UNINITIALIZED,
-                          "variable `%s` may be used uninitialized in this function", v->name))
-            {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-/***********************************************************************
- *IR Code-Generation
- *
- * Since the IR has the convention of putting 'write' operands
- * at the beginning, we have to rotate the operands of instructions
- * properly in order to generate valid QCVM code.
- *
- * Having destinations at a fixed position is more convenient. In QC
- * this is *mostly* OPC,  but FTE adds at least 2 instructions which
- * read from from OPA,  and store to OPB rather than OPC.   Which is
- * partially the reason why the implementation of these instructions
- * in darkplaces has been delayed for so long.
- *
- * Breaking conventions is annoying...
- */
-static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal);
-
-static bool gen_global_field(code_t *code, ir_value *global)
-{
-    if (global->hasvalue)
-    {
-        ir_value *fld = global->constval.vpointer;
-        if (!fld) {
-            irerror(global->context, "Invalid field constant with no field: %s", global->name);
-            return false;
-        }
-
-        /* copy the field's value */
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, fld->code.fieldaddr);
-        if (global->fieldtype == TYPE_VECTOR) {
-            vec_push(code->globals, fld->code.fieldaddr+1);
-            vec_push(code->globals, fld->code.fieldaddr+2);
-        }
-    }
-    else
-    {
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, 0);
-        if (global->fieldtype == TYPE_VECTOR) {
-            vec_push(code->globals, 0);
-            vec_push(code->globals, 0);
-        }
-    }
-    if (global->code.globaladdr < 0)
-        return false;
-    return true;
-}
-
-static bool gen_global_pointer(code_t *code, ir_value *global)
-{
-    if (global->hasvalue)
-    {
-        ir_value *target = global->constval.vpointer;
-        if (!target) {
-            irerror(global->context, "Invalid pointer constant: %s", global->name);
-            /* NULL pointers are pointing to the NULL constant, which also
-             * sits at address 0, but still has an ir_value for itself.
-             */
-            return false;
-        }
-
-        /* Here, relocations ARE possible - in fteqcc-enhanced-qc:
-         * void() foo; <- proto
-         * void() *fooptr = &foo;
-         * void() foo = { code }
-         */
-        if (!target->code.globaladdr) {
-            /* FIXME: Check for the constant nullptr ir_value!
-             * because then code.globaladdr being 0 is valid.
-             */
-            irerror(global->context, "FIXME: Relocation support");
-            return false;
-        }
-
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, target->code.globaladdr);
-    }
-    else
-    {
-        ir_value_code_setaddr(global, vec_size(code->globals));
-        vec_push(code->globals, 0);
-    }
-    if (global->code.globaladdr < 0)
-        return false;
-    return true;
-}
-
-static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block)
-{
-    prog_section_statement_t stmt;
-    ir_instr *instr;
-    ir_block *target;
-    ir_block *ontrue;
-    ir_block *onfalse;
-    size_t    stidx;
-    size_t    i;
-    int       j;
-
-    block->generated = true;
-    block->code_start = vec_size(code->statements);
-    for (i = 0; i < vec_size(block->instr); ++i)
-    {
-        instr = block->instr[i];
-
-        if (instr->opcode == VINSTR_PHI) {
-            irerror(block->context, "cannot generate virtual instruction (phi)");
-            return false;
-        }
-
-        if (instr->opcode == VINSTR_JUMP) {
-            target = instr->bops[0];
-            /* for uncoditional jumps, if the target hasn't been generated
-             * yet, we generate them right here.
-             */
-            if (!target->generated)
-                return gen_blocks_recursive(code, func, target);
-
-            /* otherwise we generate a jump instruction */
-            stmt.opcode = INSTR_GOTO;
-            stmt.o1.s1 = (target->code_start) - vec_size(code->statements);
-            stmt.o2.s1 = 0;
-            stmt.o3.s1 = 0;
-            if (stmt.o1.s1 != 1)
-                code_push_statement(code, &stmt, instr->context);
-
-            /* no further instructions can be in this block */
-            return true;
-        }
-
-        if (instr->opcode == VINSTR_BITXOR) {
-            stmt.opcode = INSTR_BITOR;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            stmt.opcode = INSTR_BITAND;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            code_push_statement(code, &stmt, instr->context);
-            stmt.opcode = INSTR_SUB_F;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITAND_V) {
-            stmt.opcode = INSTR_BITAND;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITOR_V) {
-            stmt.opcode = INSTR_BITOR;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o2.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITXOR_V) {
-            for (j = 0; j < 3; ++j) {
-                stmt.opcode = INSTR_BITOR;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j;
-                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-                stmt.opcode = INSTR_BITAND;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j;
-                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            stmt.opcode = INSTR_SUB_V;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITAND_VF) {
-            stmt.opcode = INSTR_BITAND;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITOR_VF) {
-            stmt.opcode = INSTR_BITOR;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-            ++stmt.o1.s1;
-            ++stmt.o3.s1;
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_BITXOR_VF) {
-            for (j = 0; j < 3; ++j) {
-                stmt.opcode = INSTR_BITOR;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-                stmt.opcode = INSTR_BITAND;
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
-                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            stmt.opcode = INSTR_SUB_V;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_CROSS) {
-            stmt.opcode = INSTR_MUL_F;
-            for (j = 0; j < 3; ++j) {
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 1) % 3;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 2) % 3;
-                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 2) % 3;
-                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 1) % 3;
-                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            stmt.opcode = INSTR_SUB_V;
-            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
-            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
-            code_push_statement(code, &stmt, instr->context);
-
-            /* instruction generated */
-            continue;
-        }
-
-        if (instr->opcode == VINSTR_COND) {
-            ontrue  = instr->bops[0];
-            onfalse = instr->bops[1];
-            /* TODO: have the AST signal which block should
-             * come first: eg. optimize IFs without ELSE...
-             */
-
-            stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
-            stmt.o2.u1 = 0;
-            stmt.o3.s1 = 0;
-
-            if (ontrue->generated) {
-                stmt.opcode = INSTR_IF;
-                stmt.o2.s1 = (ontrue->code_start) - vec_size(code->statements);
-                if (stmt.o2.s1 != 1)
-                    code_push_statement(code, &stmt, instr->context);
-            }
-            if (onfalse->generated) {
-                stmt.opcode = INSTR_IFNOT;
-                stmt.o2.s1 = (onfalse->code_start) - vec_size(code->statements);
-                if (stmt.o2.s1 != 1)
-                    code_push_statement(code, &stmt, instr->context);
-            }
-            if (!ontrue->generated) {
-                if (onfalse->generated)
-                    return gen_blocks_recursive(code, func, ontrue);
-            }
-            if (!onfalse->generated) {
-                if (ontrue->generated)
-                    return gen_blocks_recursive(code, func, onfalse);
-            }
-            /* neither ontrue nor onfalse exist */
-            stmt.opcode = INSTR_IFNOT;
-            if (!instr->likely) {
-                /* Honor the likelyhood hint */
-                ir_block *tmp = onfalse;
-                stmt.opcode = INSTR_IF;
-                onfalse = ontrue;
-                ontrue = tmp;
-            }
-            stidx = vec_size(code->statements);
-            code_push_statement(code, &stmt, instr->context);
-            /* on false we jump, so add ontrue-path */
-            if (!gen_blocks_recursive(code, func, ontrue))
-                return false;
-            /* fixup the jump address */
-            code->statements[stidx].o2.s1 = vec_size(code->statements) - stidx;
-            /* generate onfalse path */
-            if (onfalse->generated) {
-                /* fixup the jump address */
-                code->statements[stidx].o2.s1 = (onfalse->code_start) - (stidx);
-                if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) {
-                    code->statements[stidx] = code->statements[stidx+1];
-                    if (code->statements[stidx].o1.s1 < 0)
-                        code->statements[stidx].o1.s1++;
-                    code_pop_statement(code);
-                }
-                stmt.opcode = vec_last(code->statements).opcode;
-                if (stmt.opcode == INSTR_GOTO ||
-                    stmt.opcode == INSTR_IF ||
-                    stmt.opcode == INSTR_IFNOT ||
-                    stmt.opcode == INSTR_RETURN ||
-                    stmt.opcode == INSTR_DONE)
-                {
-                    /* no use jumping from here */
-                    return true;
-                }
-                /* may have been generated in the previous recursive call */
-                stmt.opcode = INSTR_GOTO;
-                stmt.o1.s1 = (onfalse->code_start) - vec_size(code->statements);
-                stmt.o2.s1 = 0;
-                stmt.o3.s1 = 0;
-                if (stmt.o1.s1 != 1)
-                    code_push_statement(code, &stmt, instr->context);
-                return true;
-            }
-            else if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) {
-                code->statements[stidx] = code->statements[stidx+1];
-                if (code->statements[stidx].o1.s1 < 0)
-                    code->statements[stidx].o1.s1++;
-                code_pop_statement(code);
-            }
-            /* if not, generate now */
-            return gen_blocks_recursive(code, func, onfalse);
-        }
-
-        if ( (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8)
-           || instr->opcode == VINSTR_NRCALL)
-        {
-            size_t p, first;
-            ir_value *retvalue;
-
-            first = vec_size(instr->params);
-            if (first > 8)
-                first = 8;
-            for (p = 0; p < first; ++p)
-            {
-                ir_value *param = instr->params[p];
-                if (param->callparam)
-                    continue;
-
-                stmt.opcode = INSTR_STORE_F;
-                stmt.o3.u1 = 0;
-
-                if (param->vtype == TYPE_FIELD)
-                    stmt.opcode = field_store_instr[param->fieldtype];
-                else if (param->vtype == TYPE_NIL)
-                    stmt.opcode = INSTR_STORE_V;
-                else
-                    stmt.opcode = type_store_instr[param->vtype];
-                stmt.o1.u1 = ir_value_code_addr(param);
-                stmt.o2.u1 = OFS_PARM0 + 3 * p;
-
-                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
-                    /* fetch 3 separate floats */
-                    stmt.opcode = INSTR_STORE_F;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
-                    code_push_statement(code, &stmt, instr->context);
-                }
-                else
-                    code_push_statement(code, &stmt, instr->context);
-            }
-            /* Now handle extparams */
-            first = vec_size(instr->params);
-            for (; p < first; ++p)
-            {
-                ir_builder *ir = func->owner;
-                ir_value *param = instr->params[p];
-                ir_value *targetparam;
-
-                if (param->callparam)
-                    continue;
-
-                if (p-8 >= vec_size(ir->extparams))
-                    ir_gen_extparam(ir);
-
-                targetparam = ir->extparams[p-8];
-
-                stmt.opcode = INSTR_STORE_F;
-                stmt.o3.u1 = 0;
-
-                if (param->vtype == TYPE_FIELD)
-                    stmt.opcode = field_store_instr[param->fieldtype];
-                else if (param->vtype == TYPE_NIL)
-                    stmt.opcode = INSTR_STORE_V;
-                else
-                    stmt.opcode = type_store_instr[param->vtype];
-                stmt.o1.u1 = ir_value_code_addr(param);
-                stmt.o2.u1 = ir_value_code_addr(targetparam);
-                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
-                    /* fetch 3 separate floats */
-                    stmt.opcode = INSTR_STORE_F;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
-                    code_push_statement(code, &stmt, instr->context);
-                    stmt.o2.u1++;
-                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
-                    code_push_statement(code, &stmt, instr->context);
-                }
-                else
-                    code_push_statement(code, &stmt, instr->context);
-            }
-
-            stmt.opcode = INSTR_CALL0 + vec_size(instr->params);
-            if (stmt.opcode > INSTR_CALL8)
-                stmt.opcode = INSTR_CALL8;
-            stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o2.u1 = 0;
-            stmt.o3.u1 = 0;
-            code_push_statement(code, &stmt, instr->context);
-
-            retvalue = instr->_ops[0];
-            if (retvalue && retvalue->store != store_return &&
-                (retvalue->store == store_global || vec_size(retvalue->life)))
-            {
-                /* not to be kept in OFS_RETURN */
-                if (retvalue->vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS))
-                    stmt.opcode = field_store_instr[retvalue->fieldtype];
-                else
-                    stmt.opcode = type_store_instr[retvalue->vtype];
-                stmt.o1.u1 = OFS_RETURN;
-                stmt.o2.u1 = ir_value_code_addr(retvalue);
-                stmt.o3.u1 = 0;
-                code_push_statement(code, &stmt, instr->context);
-            }
-            continue;
-        }
-
-        if (instr->opcode == INSTR_STATE) {
-            stmt.opcode = instr->opcode;
-            if (instr->_ops[0])
-                stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
-            if (instr->_ops[1])
-                stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]);
-            stmt.o3.u1 = 0;
-            code_push_statement(code, &stmt, instr->context);
-            continue;
-        }
-
-        stmt.opcode = instr->opcode;
-        stmt.o1.u1 = 0;
-        stmt.o2.u1 = 0;
-        stmt.o3.u1 = 0;
-
-        /* This is the general order of operands */
-        if (instr->_ops[0])
-            stmt.o3.u1 = ir_value_code_addr(instr->_ops[0]);
-
-        if (instr->_ops[1])
-            stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]);
-
-        if (instr->_ops[2])
-            stmt.o2.u1 = ir_value_code_addr(instr->_ops[2]);
-
-        if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE)
-        {
-            stmt.o1.u1 = stmt.o3.u1;
-            stmt.o3.u1 = 0;
-        }
-        else if ((stmt.opcode >= INSTR_STORE_F &&
-                  stmt.opcode <= INSTR_STORE_FNC) ||
-                 (stmt.opcode >= INSTR_STOREP_F &&
-                  stmt.opcode <= INSTR_STOREP_FNC))
-        {
-            /* 2-operand instructions with A -> B */
-            stmt.o2.u1 = stmt.o3.u1;
-            stmt.o3.u1 = 0;
-
-            /* tiny optimization, don't output
-             * STORE a, a
-             */
-            if (stmt.o2.u1 == stmt.o1.u1 &&
-                OPTS_OPTIMIZATION(OPTIM_PEEPHOLE))
-            {
-                ++opts_optimizationcount[OPTIM_PEEPHOLE];
-                continue;
-            }
-        }
-        code_push_statement(code, &stmt, instr->context);
-    }
-    return true;
-}
-
-static bool gen_function_code(code_t *code, ir_function *self)
-{
-    ir_block *block;
-    prog_section_statement_t stmt, *retst;
-
-    /* Starting from entry point, we generate blocks "as they come"
-     * for now. Dead blocks will not be translated obviously.
-     */
-    if (!vec_size(self->blocks)) {
-        irerror(self->context, "Function '%s' declared without body.", self->name);
-        return false;
-    }
-
-    block = self->blocks[0];
-    if (block->generated)
-        return true;
-
-    if (!gen_blocks_recursive(code, self, block)) {
-        irerror(self->context, "failed to generate blocks for '%s'", self->name);
-        return false;
-    }
-
-    /* code_write and qcvm -disasm need to know that the function ends here */
-    retst = &vec_last(code->statements);
-    if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) &&
-        self->outtype == TYPE_VOID &&
-        retst->opcode == INSTR_RETURN &&
-        !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1)
-    {
-        retst->opcode = INSTR_DONE;
-        ++opts_optimizationcount[OPTIM_VOID_RETURN];
-    } else {
-        lex_ctx_t last;
-
-        stmt.opcode = INSTR_DONE;
-        stmt.o1.u1  = 0;
-        stmt.o2.u1  = 0;
-        stmt.o3.u1  = 0;
-        last.line   = vec_last(code->linenums);
-        last.column = vec_last(code->columnnums);
-
-        code_push_statement(code, &stmt, last);
-    }
-    return true;
-}
-
-static qcint_t ir_builder_filestring(ir_builder *ir, const char *filename)
-{
-    /* NOTE: filename pointers are copied, we never strdup them,
-     * thus we can use pointer-comparison to find the string.
-     */
-    size_t i;
-    qcint_t  str;
-
-    for (i = 0; i < vec_size(ir->filenames); ++i) {
-        if (ir->filenames[i] == filename)
-            return ir->filestrings[i];
-    }
-
-    str = code_genstring(ir->code, filename);
-    vec_push(ir->filenames, filename);
-    vec_push(ir->filestrings, str);
-    return str;
-}
-
-static bool gen_global_function(ir_builder *ir, ir_value *global)
-{
-    prog_section_function_t fun;
-    ir_function            *irfun;
-
-    size_t i;
-
-    if (!global->hasvalue || (!global->constval.vfunc))
-    {
-        irerror(global->context, "Invalid state of function-global: not constant: %s", global->name);
-        return false;
-    }
-
-    irfun = global->constval.vfunc;
-
-    fun.name    = global->code.name;
-    fun.file    = ir_builder_filestring(ir, global->context.file);
-    fun.profile = 0; /* always 0 */
-    fun.nargs   = vec_size(irfun->params);
-    if (fun.nargs > 8)
-        fun.nargs = 8;
-
-    for (i = 0;i < 8; ++i) {
-        if ((int32_t)i >= fun.nargs)
-            fun.argsize[i] = 0;
-        else
-            fun.argsize[i] = type_sizeof_[irfun->params[i]];
-    }
-
-    fun.firstlocal = 0;
-    fun.locals     = irfun->allocated_locals;
-
-    if (irfun->builtin)
-        fun.entry = irfun->builtin+1;
-    else {
-        irfun->code_function_def = vec_size(ir->code->functions);
-        fun.entry                = vec_size(ir->code->statements);
-    }
-
-    vec_push(ir->code->functions, fun);
-    return true;
-}
-
-static ir_value* ir_gen_extparam_proto(ir_builder *ir)
-{
-    ir_value *global;
-    char      name[128];
-
-    util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparam_protos)));
-    global = ir_value_var(name, store_global, TYPE_VECTOR);
-
-    vec_push(ir->extparam_protos, global);
-    return global;
-}
-
-static void ir_gen_extparam(ir_builder *ir)
-{
-    prog_section_def_t def;
-    ir_value          *global;
-
-    if (vec_size(ir->extparam_protos) < vec_size(ir->extparams)+1)
-        global = ir_gen_extparam_proto(ir);
-    else
-        global = ir->extparam_protos[vec_size(ir->extparams)];
-
-    def.name   = code_genstring(ir->code, global->name);
-    def.type   = TYPE_VECTOR;
-    def.offset = vec_size(ir->code->globals);
-
-    vec_push(ir->code->defs, def);
-
-    ir_value_code_setaddr(global, def.offset);
-
-    vec_push(ir->code->globals, 0);
-    vec_push(ir->code->globals, 0);
-    vec_push(ir->code->globals, 0);
-
-    vec_push(ir->extparams, global);
-}
-
-static bool gen_function_extparam_copy(code_t *code, ir_function *self)
-{
-    size_t i, ext, numparams;
-
-    ir_builder *ir = self->owner;
-    ir_value   *ep;
-    prog_section_statement_t stmt;
-
-    numparams = vec_size(self->params);
-    if (!numparams)
-        return true;
-
-    stmt.opcode = INSTR_STORE_F;
-    stmt.o3.s1 = 0;
-    for (i = 8; i < numparams; ++i) {
-        ext = i - 8;
-        if (ext >= vec_size(ir->extparams))
-            ir_gen_extparam(ir);
-
-        ep = ir->extparams[ext];
-
-        stmt.opcode = type_store_instr[self->locals[i]->vtype];
-        if (self->locals[i]->vtype == TYPE_FIELD &&
-            self->locals[i]->fieldtype == TYPE_VECTOR)
-        {
-            stmt.opcode = INSTR_STORE_V;
-        }
-        stmt.o1.u1 = ir_value_code_addr(ep);
-        stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
-        code_push_statement(code, &stmt, self->context);
-    }
-
-    return true;
-}
-
-static bool gen_function_varargs_copy(code_t *code, ir_function *self)
-{
-    size_t i, ext, numparams, maxparams;
-
-    ir_builder *ir = self->owner;
-    ir_value   *ep;
-    prog_section_statement_t stmt;
-
-    numparams = vec_size(self->params);
-    if (!numparams)
-        return true;
-
-    stmt.opcode = INSTR_STORE_V;
-    stmt.o3.s1 = 0;
-    maxparams = numparams + self->max_varargs;
-    for (i = numparams; i < maxparams; ++i) {
-        if (i < 8) {
-            stmt.o1.u1 = OFS_PARM0 + 3*i;
-            stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
-            code_push_statement(code, &stmt, self->context);
-            continue;
-        }
-        ext = i - 8;
-        while (ext >= vec_size(ir->extparams))
-            ir_gen_extparam(ir);
-
-        ep = ir->extparams[ext];
-
-        stmt.o1.u1 = ir_value_code_addr(ep);
-        stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
-        code_push_statement(code, &stmt, self->context);
-    }
-
-    return true;
-}
-
-static bool gen_function_locals(ir_builder *ir, ir_value *global)
-{
-    prog_section_function_t *def;
-    ir_function             *irfun;
-    size_t                   i;
-    uint32_t                 firstlocal, firstglobal;
-
-    irfun = global->constval.vfunc;
-    def   = ir->code->functions + irfun->code_function_def;
-
-    if (OPTS_OPTION_BOOL(OPTION_G) ||
-        !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS)        ||
-        (irfun->flags & IR_FLAG_MASK_NO_OVERLAP))
-    {
-        firstlocal = def->firstlocal = vec_size(ir->code->globals);
-    } else {
-        firstlocal = def->firstlocal = ir->first_common_local;
-        ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS];
-    }
-
-    firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? ir->first_common_globaltemp : firstlocal);
-
-    for (i = vec_size(ir->code->globals); i < firstlocal + irfun->allocated_locals; ++i)
-        vec_push(ir->code->globals, 0);
-    for (i = 0; i < vec_size(irfun->locals); ++i) {
-        ir_value *v = irfun->locals[i];
-        if (v->locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) {
-            ir_value_code_setaddr(v, firstlocal + v->code.local);
-            if (!ir_builder_gen_global(ir, irfun->locals[i], true)) {
-                irerror(irfun->locals[i]->context, "failed to generate local %s", irfun->locals[i]->name);
-                return false;
-            }
-        }
-        else
-            ir_value_code_setaddr(v, firstglobal + v->code.local);
-    }
-    for (i = 0; i < vec_size(irfun->values); ++i)
-    {
-        ir_value *v = irfun->values[i];
-        if (v->callparam)
-            continue;
-        if (v->locked)
-            ir_value_code_setaddr(v, firstlocal + v->code.local);
-        else
-            ir_value_code_setaddr(v, firstglobal + v->code.local);
-    }
-    return true;
-}
-
-static bool gen_global_function_code(ir_builder *ir, ir_value *global)
-{
-    prog_section_function_t *fundef;
-    ir_function             *irfun;
-
-    (void)ir;
-
-    irfun = global->constval.vfunc;
-    if (!irfun) {
-        if (global->cvq == CV_NONE) {
-            if (irwarning(global->context, WARN_IMPLICIT_FUNCTION_POINTER,
-                          "function `%s` has no body and in QC implicitly becomes a function-pointer",
-                          global->name))
-            {
-                /* Not bailing out just now. If this happens a lot you don't want to have
-                 * to rerun gmqcc for each such function.
-                 */
-
-                /* return false; */
-            }
-        }
-        /* this was a function pointer, don't generate code for those */
-        return true;
-    }
-
-    if (irfun->builtin)
-        return true;
-
-    /*
-     * If there is no definition and the thing is eraseable, we can ignore
-     * outputting the function to begin with.
-     */
-    if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) {
-        return true;
-    }
-
-    if (irfun->code_function_def < 0) {
-        irerror(irfun->context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->name);
-        return false;
-    }
-    fundef = &ir->code->functions[irfun->code_function_def];
-
-    fundef->entry = vec_size(ir->code->statements);
-    if (!gen_function_locals(ir, global)) {
-        irerror(irfun->context, "Failed to generate locals for function %s", irfun->name);
-        return false;
-    }
-    if (!gen_function_extparam_copy(ir->code, irfun)) {
-        irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name);
-        return false;
-    }
-    if (irfun->max_varargs && !gen_function_varargs_copy(ir->code, irfun)) {
-        irerror(irfun->context, "Failed to generate vararg-copy code for function %s", irfun->name);
-        return false;
-    }
-    if (!gen_function_code(ir->code, irfun)) {
-        irerror(irfun->context, "Failed to generate code for function %s", irfun->name);
-        return false;
-    }
-    return true;
-}
-
-static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name)
-{
-    char  *component;
-    size_t len, i;
-
-    if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS))
-        return;
-
-    def.type = TYPE_FLOAT;
-
-    len = strlen(name);
-
-    component = (char*)mem_a(len+3);
-    memcpy(component, name, len);
-    len += 2;
-    component[len-0] = 0;
-    component[len-2] = '_';
-
-    component[len-1] = 'x';
-
-    for (i = 0; i < 3; ++i) {
-        def.name = code_genstring(code, component);
-        vec_push(code->defs, def);
-        def.offset++;
-        component[len-1]++;
-    }
-
-    mem_d(component);
-}
-
-static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name)
-{
-    char  *component;
-    size_t len, i;
-
-    if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS))
-        return;
-
-    fld.type = TYPE_FLOAT;
-
-    len = strlen(name);
-
-    component = (char*)mem_a(len+3);
-    memcpy(component, name, len);
-    len += 2;
-    component[len-0] = 0;
-    component[len-2] = '_';
-
-    component[len-1] = 'x';
-
-    for (i = 0; i < 3; ++i) {
-        fld.name = code_genstring(code, component);
-        vec_push(code->fields, fld);
-        fld.offset++;
-        component[len-1]++;
-    }
-
-    mem_d(component);
-}
-
-static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal)
-{
-    size_t             i;
-    int32_t           *iptr;
-    prog_section_def_t def;
-    bool               pushdef = opts.optimizeoff;
-
-    /* we don't generate split-vectors */
-    if (global->vtype == TYPE_VECTOR && (global->flags & IR_FLAG_SPLIT_VECTOR))
-        return true;
-
-    def.type   = global->vtype;
-    def.offset = vec_size(self->code->globals);
-    def.name   = 0;
-    if (OPTS_OPTION_BOOL(OPTION_G) || !islocal)
-    {
-        pushdef = true;
-
-        /*
-         * if we're eraseable and the function isn't referenced ignore outputting
-         * the function.
-         */
-        if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) {
-            return true;
-        }
-
-        if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) &&
-            !(global->flags & IR_FLAG_INCLUDE_DEF) &&
-            (global->name[0] == '#' || global->cvq == CV_CONST))
-        {
-            pushdef = false;
-        }
-
-        if (pushdef) {
-            if (global->name[0] == '#') {
-                if (!self->str_immediate)
-                    self->str_immediate = code_genstring(self->code, "IMMEDIATE");
-                def.name = global->code.name = self->str_immediate;
-            }
-            else
-                def.name = global->code.name = code_genstring(self->code, global->name);
-        }
-        else
-            def.name   = 0;
-        if (islocal) {
-            def.offset = ir_value_code_addr(global);
-            vec_push(self->code->defs, def);
-            if (global->vtype == TYPE_VECTOR)
-                gen_vector_defs(self->code, def, global->name);
-            else if (global->vtype == TYPE_FIELD && global->fieldtype == TYPE_VECTOR)
-                gen_vector_defs(self->code, def, global->name);
-            return true;
-        }
-    }
-    if (islocal)
-        return true;
-
-    switch (global->vtype)
-    {
-    case TYPE_VOID:
-        if (!strcmp(global->name, "end_sys_globals")) {
-            /* TODO: remember this point... all the defs before this one
-             * should be checksummed and added to progdefs.h when we generate it.
-             */
-        }
-        else if (!strcmp(global->name, "end_sys_fields")) {
-            /* TODO: same as above but for entity-fields rather than globsl
-             */
-        }
-        else if(irwarning(global->context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`",
-                          global->name))
-        {
-            /* Not bailing out */
-            /* return false; */
-        }
-        /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far
-         * the system fields actually go? Though the engine knows this anyway...
-         * Maybe this could be an -foption
-         * fteqcc creates data for end_sys_* - of size 1, so let's do the same
-         */
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        vec_push(self->code->globals, 0);
-        /* Add the def */
-        if (pushdef) vec_push(self->code->defs, def);
-        return true;
-    case TYPE_POINTER:
-        if (pushdef) vec_push(self->code->defs, def);
-        return gen_global_pointer(self->code, global);
-    case TYPE_FIELD:
-        if (pushdef) {
-            vec_push(self->code->defs, def);
-            if (global->fieldtype == TYPE_VECTOR)
-                gen_vector_defs(self->code, def, global->name);
-        }
-        return gen_global_field(self->code, global);
-    case TYPE_ENTITY:
-        /* fall through */
-    case TYPE_FLOAT:
-    {
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (global->hasvalue) {
-            iptr = (int32_t*)&global->constval.ivec[0];
-            vec_push(self->code->globals, *iptr);
-        } else {
-            vec_push(self->code->globals, 0);
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-        if (pushdef) vec_push(self->code->defs, def);
-
-        return global->code.globaladdr >= 0;
-    }
-    case TYPE_STRING:
-    {
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (global->hasvalue) {
-            uint32_t load = code_genstring(self->code, global->constval.vstring);
-            vec_push(self->code->globals, load);
-        } else {
-            vec_push(self->code->globals, 0);
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-        if (pushdef) vec_push(self->code->defs, def);
-        return global->code.globaladdr >= 0;
-    }
-    case TYPE_VECTOR:
-    {
-        size_t d;
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (global->hasvalue) {
-            iptr = (int32_t*)&global->constval.ivec[0];
-            vec_push(self->code->globals, iptr[0]);
-            if (global->code.globaladdr < 0)
-                return false;
-            for (d = 1; d < type_sizeof_[global->vtype]; ++d) {
-                vec_push(self->code->globals, iptr[d]);
-            }
-        } else {
-            vec_push(self->code->globals, 0);
-            if (global->code.globaladdr < 0)
-                return false;
-            for (d = 1; d < type_sizeof_[global->vtype]; ++d) {
-                vec_push(self->code->globals, 0);
-            }
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-
-        if (pushdef) {
-            vec_push(self->code->defs, def);
-            def.type &= ~DEF_SAVEGLOBAL;
-            gen_vector_defs(self->code, def, global->name);
-        }
-        return global->code.globaladdr >= 0;
-    }
-    case TYPE_FUNCTION:
-        ir_value_code_setaddr(global, vec_size(self->code->globals));
-        if (!global->hasvalue) {
-            vec_push(self->code->globals, 0);
-            if (global->code.globaladdr < 0)
-                return false;
-        } else {
-            vec_push(self->code->globals, vec_size(self->code->functions));
-            if (!gen_global_function(self, global))
-                return false;
-        }
-        if (!islocal && global->cvq != CV_CONST)
-            def.type |= DEF_SAVEGLOBAL;
-        if (pushdef) vec_push(self->code->defs, def);
-        return true;
-    case TYPE_VARIANT:
-        /* assume biggest type */
-            ir_value_code_setaddr(global, vec_size(self->code->globals));
-            vec_push(self->code->globals, 0);
-            for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i)
-                vec_push(self->code->globals, 0);
-            return true;
-    default:
-        /* refuse to create 'void' type or any other fancy business. */
-        irerror(global->context, "Invalid type for global variable `%s`: %s",
-                global->name, type_name[global->vtype]);
-        return false;
-    }
-}
-
-static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field)
-{
-    field->code.fieldaddr = code_alloc_field(code, type_sizeof_[field->fieldtype]);
-}
-
-static bool ir_builder_gen_field(ir_builder *self, ir_value *field)
-{
-    prog_section_def_t def;
-    prog_section_field_t fld;
-
-    (void)self;
-
-    def.type   = (uint16_t)field->vtype;
-    def.offset = (uint16_t)vec_size(self->code->globals);
-
-    /* create a global named the same as the field */
-    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
-        /* in our standard, the global gets a dot prefix */
-        size_t len = strlen(field->name);
-        char name[1024];
-
-        /* we really don't want to have to allocate this, and 1024
-         * bytes is more than enough for a variable/field name
-         */
-        if (len+2 >= sizeof(name)) {
-            irerror(field->context, "invalid field name size: %u", (unsigned int)len);
-            return false;
-        }
-
-        name[0] = '.';
-        memcpy(name+1, field->name, len); /* no strncpy - we used strlen above */
-        name[len+1] = 0;
-
-        def.name = code_genstring(self->code, name);
-        fld.name = def.name + 1; /* we reuse that string table entry */
-    } else {
-        /* in plain QC, there cannot be a global with the same name,
-         * and so we also name the global the same.
-         * FIXME: fteqcc should create a global as well
-         * check if it actually uses the same name. Probably does
-         */
-        def.name = code_genstring(self->code, field->name);
-        fld.name = def.name;
-    }
-
-    field->code.name = def.name;
-
-    vec_push(self->code->defs, def);
-
-    fld.type = field->fieldtype;
-
-    if (fld.type == TYPE_VOID) {
-        irerror(field->context, "field is missing a type: %s - don't know its size", field->name);
-        return false;
-    }
-
-    fld.offset = field->code.fieldaddr;
-
-    vec_push(self->code->fields, fld);
-
-    ir_value_code_setaddr(field, vec_size(self->code->globals));
-    vec_push(self->code->globals, fld.offset);
-    if (fld.type == TYPE_VECTOR) {
-        vec_push(self->code->globals, fld.offset+1);
-        vec_push(self->code->globals, fld.offset+2);
-    }
-
-    if (field->fieldtype == TYPE_VECTOR) {
-        gen_vector_defs  (self->code, def, field->name);
-        gen_vector_fields(self->code, fld, field->name);
-    }
-
-    return field->code.globaladdr >= 0;
-}
-
-static void ir_builder_collect_reusables(ir_builder *builder) {
-    size_t i;
-    ir_value **reusables = NULL;
-    for (i = 0; i < vec_size(builder->globals); ++i) {
-        ir_value *value = builder->globals[i];
-        if (value->vtype != TYPE_FLOAT || !value->hasvalue)
-            continue;
-        if (value->cvq == CV_CONST || (value->name && value->name[0] == '#')) {
-            vec_push(reusables, value);
-        }
-    }
-    builder->const_floats = reusables;
-}
-
-static void ir_builder_split_vector(ir_builder *self, ir_value *vec) {
-    size_t i, count;
-    ir_value* found[3] = { NULL, NULL, NULL };
-
-    /* must not be written to */
-    if (vec_size(vec->writes))
-        return;
-    /* must not be trying to access individual members */
-    if (vec->members[0] || vec->members[1] || vec->members[2])
-        return;
-    /* should be actually used otherwise it won't be generated anyway */
-    count = vec_size(vec->reads);
-    if (!count)
-        return;
-
-    /* may only be used directly as function parameters, so if we find some other instruction cancel */
-    for (i = 0; i != count; ++i) {
-        /* we only split vectors if they're used directly as parameter to a call only! */
-        ir_instr *user = vec->reads[i];
-        if ((user->opcode < INSTR_CALL0 || user->opcode > INSTR_CALL8) && user->opcode != VINSTR_NRCALL)
-            return;
-    }
-
-    vec->flags |= IR_FLAG_SPLIT_VECTOR;
-
-    /* find existing floats making up the split */
-    count = vec_size(self->const_floats);
-    for (i = 0; i != count; ++i) {
-        ir_value *c = self->const_floats[i];
-        if (!found[0] && c->constval.vfloat == vec->constval.vvec.x)
-            found[0] = c;
-        if (!found[1] && c->constval.vfloat == vec->constval.vvec.y)
-            found[1] = c;
-        if (!found[2] && c->constval.vfloat == vec->constval.vvec.z)
-            found[2] = c;
-        if (found[0] && found[1] && found[2])
-            break;
-    }
-
-    /* generate floats for not yet found components */
-    if (!found[0])
-        found[0] = ir_builder_imm_float(self, vec->constval.vvec.x, true);
-    if (!found[1]) {
-        if (vec->constval.vvec.y == vec->constval.vvec.x)
-            found[1] = found[0];
-        else
-            found[1] = ir_builder_imm_float(self, vec->constval.vvec.y, true);
-    }
-    if (!found[2]) {
-        if (vec->constval.vvec.z == vec->constval.vvec.x)
-            found[2] = found[0];
-        else if (vec->constval.vvec.z == vec->constval.vvec.y)
-            found[2] = found[1];
-        else
-            found[2] = ir_builder_imm_float(self, vec->constval.vvec.z, true);
-    }
-
-    /* the .members array should be safe to use here. */
-    vec->members[0] = found[0];
-    vec->members[1] = found[1];
-    vec->members[2] = found[2];
-
-    /* register the readers for these floats */
-    count = vec_size(vec->reads);
-    for (i = 0; i != count; ++i) {
-        vec_push(found[0]->reads, vec->reads[i]);
-        vec_push(found[1]->reads, vec->reads[i]);
-        vec_push(found[2]->reads, vec->reads[i]);
-    }
-}
-
-static void ir_builder_split_vectors(ir_builder *self) {
-    size_t i, count = vec_size(self->globals);
-    for (i = 0; i != count; ++i) {
-        ir_value *v = self->globals[i];
-        if (v->vtype != TYPE_VECTOR || !v->name || v->name[0] != '#')
-            continue;
-        ir_builder_split_vector(self, self->globals[i]);
-    }
-}
-
-bool ir_builder_generate(ir_builder *self, const char *filename)
-{
-    prog_section_statement_t stmt;
-    size_t i;
-    char  *lnofile = NULL;
-
-    if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) {
-        ir_builder_collect_reusables(self);
-        if (vec_size(self->const_floats) > 0)
-            ir_builder_split_vectors(self);
-    }
-
-    for (i = 0; i < vec_size(self->fields); ++i)
-    {
-        ir_builder_prepare_field(self->code, self->fields[i]);
-    }
-
-    for (i = 0; i < vec_size(self->globals); ++i)
-    {
-        if (!ir_builder_gen_global(self, self->globals[i], false)) {
-            return false;
-        }
-        if (self->globals[i]->vtype == TYPE_FUNCTION) {
-            ir_function *func = self->globals[i]->constval.vfunc;
-            if (func && self->max_locals < func->allocated_locals &&
-                !(func->flags & IR_FLAG_MASK_NO_OVERLAP))
-            {
-                self->max_locals = func->allocated_locals;
-            }
-            if (func && self->max_globaltemps < func->globaltemps)
-                self->max_globaltemps = func->globaltemps;
-        }
-    }
-
-    for (i = 0; i < vec_size(self->fields); ++i)
-    {
-        if (!ir_builder_gen_field(self, self->fields[i])) {
-            return false;
-        }
-    }
-
-    /* generate nil */
-    ir_value_code_setaddr(self->nil, vec_size(self->code->globals));
-    vec_push(self->code->globals, 0);
-    vec_push(self->code->globals, 0);
-    vec_push(self->code->globals, 0);
-
-    /* generate virtual-instruction temps */
-    for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) {
-        ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals));
-        vec_push(self->code->globals, 0);
-        vec_push(self->code->globals, 0);
-        vec_push(self->code->globals, 0);
-    }
-
-    /* generate global temps */
-    self->first_common_globaltemp = vec_size(self->code->globals);
-    for (i = 0; i < self->max_globaltemps; ++i) {
-        vec_push(self->code->globals, 0);
-    }
-    /* generate common locals */
-    self->first_common_local = vec_size(self->code->globals);
-    for (i = 0; i < self->max_locals; ++i) {
-        vec_push(self->code->globals, 0);
-    }
-
-    /* generate function code */
-    for (i = 0; i < vec_size(self->globals); ++i)
-    {
-        if (self->globals[i]->vtype == TYPE_FUNCTION) {
-            if (!gen_global_function_code(self, self->globals[i])) {
-                return false;
-            }
-        }
-    }
-
-    if (vec_size(self->code->globals) >= 65536) {
-        irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle (%u). Bailing out.", (unsigned int)vec_size(self->code->globals));
-        return false;
-    }
-
-    /* DP errors if the last instruction is not an INSTR_DONE. */
-    if (vec_last(self->code->statements).opcode != INSTR_DONE)
-    {
-        lex_ctx_t last;
-
-        stmt.opcode = INSTR_DONE;
-        stmt.o1.u1  = 0;
-        stmt.o2.u1  = 0;
-        stmt.o3.u1  = 0;
-        last.line   = vec_last(self->code->linenums);
-        last.column = vec_last(self->code->columnnums);
-
-        code_push_statement(self->code, &stmt, last);
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-        return true;
-
-    if (vec_size(self->code->statements) != vec_size(self->code->linenums)) {
-        con_err("Linecounter wrong: %lu != %lu\n",
-                (unsigned long)vec_size(self->code->statements),
-                (unsigned long)vec_size(self->code->linenums));
-    } else if (OPTS_FLAG(LNO)) {
-        char  *dot;
-        size_t filelen = strlen(filename);
-
-        memcpy(vec_add(lnofile, filelen+1), filename, filelen+1);
-        dot = strrchr(lnofile, '.');
-        if (!dot) {
-            vec_pop(lnofile);
-        } else {
-            vec_shrinkto(lnofile, dot - lnofile);
-        }
-        memcpy(vec_add(lnofile, 5), ".lno", 5);
-    }
-
-    if (!code_write(self->code, filename, lnofile)) {
-        vec_free(lnofile);
-        return false;
-    }
-
-    vec_free(lnofile);
-    return true;
-}
-
-/***********************************************************************
- *IR DEBUG Dump functions...
- */
-
-#define IND_BUFSZ 1024
-
-static const char *qc_opname(int op)
-{
-    if (op < 0) return "<INVALID>";
-    if (op < VINSTR_END)
-        return util_instr_str[op];
-    switch (op) {
-        case VINSTR_END:       return "END";
-        case VINSTR_PHI:       return "PHI";
-        case VINSTR_JUMP:      return "JUMP";
-        case VINSTR_COND:      return "COND";
-        case VINSTR_BITXOR:    return "BITXOR";
-        case VINSTR_BITAND_V:  return "BITAND_V";
-        case VINSTR_BITOR_V:   return "BITOR_V";
-        case VINSTR_BITXOR_V:  return "BITXOR_V";
-        case VINSTR_BITAND_VF: return "BITAND_VF";
-        case VINSTR_BITOR_VF:  return "BITOR_VF";
-        case VINSTR_BITXOR_VF: return "BITXOR_VF";
-        case VINSTR_CROSS:     return "CROSS";
-        case VINSTR_NEG_F:     return "NEG_F";
-        case VINSTR_NEG_V:     return "NEG_V";
-        default:               return "<UNK>";
-    }
-}
-
-void ir_builder_dump(ir_builder *b, int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    char indent[IND_BUFSZ];
-    indent[0] = '\t';
-    indent[1] = 0;
-
-    oprintf("module %s\n", b->name);
-    for (i = 0; i < vec_size(b->globals); ++i)
-    {
-        oprintf("global ");
-        if (b->globals[i]->hasvalue)
-            oprintf("%s = ", b->globals[i]->name);
-        ir_value_dump(b->globals[i], oprintf);
-        oprintf("\n");
-    }
-    for (i = 0; i < vec_size(b->functions); ++i)
-        ir_function_dump(b->functions[i], indent, oprintf);
-    oprintf("endmodule %s\n", b->name);
-}
-
-static const char *storenames[] = {
-    "[global]", "[local]", "[param]", "[value]", "[return]"
-};
-
-void ir_function_dump(ir_function *f, char *ind,
-                      int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    if (f->builtin != 0) {
-        oprintf("%sfunction %s = builtin %i\n", ind, f->name, -f->builtin);
-        return;
-    }
-    oprintf("%sfunction %s\n", ind, f->name);
-    util_strncat(ind, "\t", IND_BUFSZ-1);
-    if (vec_size(f->locals))
-    {
-        oprintf("%s%i locals:\n", ind, (int)vec_size(f->locals));
-        for (i = 0; i < vec_size(f->locals); ++i) {
-            oprintf("%s\t", ind);
-            ir_value_dump(f->locals[i], oprintf);
-            oprintf("\n");
-        }
-    }
-    oprintf("%sliferanges:\n", ind);
-    for (i = 0; i < vec_size(f->locals); ++i) {
-        const char *attr = "";
-        size_t l, m;
-        ir_value *v = f->locals[i];
-        if (v->unique_life && v->locked)
-            attr = "unique,locked ";
-        else if (v->unique_life)
-            attr = "unique ";
-        else if (v->locked)
-            attr = "locked ";
-        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype],
-                storenames[v->store],
-                attr, (v->callparam ? "callparam " : ""),
-                (int)v->code.local);
-        if (!v->life)
-            oprintf("[null]");
-        for (l = 0; l < vec_size(v->life); ++l) {
-            oprintf("[%i,%i] ", v->life[l].start, v->life[l].end);
-        }
-        oprintf("\n");
-        for (m = 0; m < 3; ++m) {
-            ir_value *vm = v->members[m];
-            if (!vm)
-                continue;
-            oprintf("%s\t%s: @%i ", ind, vm->name, (int)vm->code.local);
-            for (l = 0; l < vec_size(vm->life); ++l) {
-                oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end);
-            }
-            oprintf("\n");
-        }
-    }
-    for (i = 0; i < vec_size(f->values); ++i) {
-        const char *attr = "";
-        size_t l, m;
-        ir_value *v = f->values[i];
-        if (v->unique_life && v->locked)
-            attr = "unique,locked ";
-        else if (v->unique_life)
-            attr = "unique ";
-        else if (v->locked)
-            attr = "locked ";
-        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype],
-                storenames[v->store],
-                attr, (v->callparam ? "callparam " : ""),
-                (int)v->code.local);
-        if (!v->life)
-            oprintf("[null]");
-        for (l = 0; l < vec_size(v->life); ++l) {
-            oprintf("[%i,%i] ", v->life[l].start, v->life[l].end);
-        }
-        oprintf("\n");
-        for (m = 0; m < 3; ++m) {
-            ir_value *vm = v->members[m];
-            if (!vm)
-                continue;
-            if (vm->unique_life && vm->locked)
-                attr = "unique,locked ";
-            else if (vm->unique_life)
-                attr = "unique ";
-            else if (vm->locked)
-                attr = "locked ";
-            oprintf("%s\t%s: %s@%i ", ind, vm->name, attr, (int)vm->code.local);
-            for (l = 0; l < vec_size(vm->life); ++l) {
-                oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end);
-            }
-            oprintf("\n");
-        }
-    }
-    if (vec_size(f->blocks))
-    {
-        oprintf("%slife passes: %i\n", ind, (int)f->run_id);
-        for (i = 0; i < vec_size(f->blocks); ++i) {
-            ir_block_dump(f->blocks[i], ind, oprintf);
-        }
-
-    }
-    ind[strlen(ind)-1] = 0;
-    oprintf("%sendfunction %s\n", ind, f->name);
-}
-
-void ir_block_dump(ir_block* b, char *ind,
-                   int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    oprintf("%s:%s\n", ind, b->label);
-    util_strncat(ind, "\t", IND_BUFSZ-1);
-
-    if (b->instr && b->instr[0])
-        oprintf("%s (%i) [entry]\n", ind, (int)(b->instr[0]->eid-1));
-    for (i = 0; i < vec_size(b->instr); ++i)
-        ir_instr_dump(b->instr[i], ind, oprintf);
-    ind[strlen(ind)-1] = 0;
-}
-
-static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    oprintf("%s <- phi ", in->_ops[0]->name);
-    for (i = 0; i < vec_size(in->phi); ++i)
-    {
-        oprintf("([%s] : %s) ", in->phi[i].from->label,
-                                in->phi[i].value->name);
-    }
-    oprintf("\n");
-}
-
-void ir_instr_dump(ir_instr *in, char *ind,
-                       int (*oprintf)(const char*, ...))
-{
-    size_t i;
-    const char *comma = NULL;
-
-    oprintf("%s (%i) ", ind, (int)in->eid);
-
-    if (in->opcode == VINSTR_PHI) {
-        dump_phi(in, oprintf);
-        return;
-    }
-
-    util_strncat(ind, "\t", IND_BUFSZ-1);
-
-    if (in->_ops[0] && (in->_ops[1] || in->_ops[2])) {
-        ir_value_dump(in->_ops[0], oprintf);
-        if (in->_ops[1] || in->_ops[2])
-            oprintf(" <- ");
-    }
-    if (in->opcode == INSTR_CALL0 || in->opcode == VINSTR_NRCALL) {
-        oprintf("CALL%i\t", vec_size(in->params));
-    } else
-        oprintf("%s\t", qc_opname(in->opcode));
-
-    if (in->_ops[0] && !(in->_ops[1] || in->_ops[2])) {
-        ir_value_dump(in->_ops[0], oprintf);
-        comma = ",\t";
-    }
-    else
-    {
-        for (i = 1; i != 3; ++i) {
-            if (in->_ops[i]) {
-                if (comma)
-                    oprintf(comma);
-                ir_value_dump(in->_ops[i], oprintf);
-                comma = ",\t";
-            }
-        }
-    }
-    if (in->bops[0]) {
-        if (comma)
-            oprintf(comma);
-        oprintf("[%s]", in->bops[0]->label);
-        comma = ",\t";
-    }
-    if (in->bops[1])
-        oprintf("%s[%s]", comma, in->bops[1]->label);
-    if (vec_size(in->params)) {
-        oprintf("\tparams: ");
-        for (i = 0; i != vec_size(in->params); ++i) {
-            oprintf("%s, ", in->params[i]->name);
-        }
-    }
-    oprintf("\n");
-    ind[strlen(ind)-1] = 0;
-}
-
-static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...))
-{
-    oprintf("\"");
-    for (; *str; ++str) {
-        switch (*str) {
-            case '\n': oprintf("\\n"); break;
-            case '\r': oprintf("\\r"); break;
-            case '\t': oprintf("\\t"); break;
-            case '\v': oprintf("\\v"); break;
-            case '\f': oprintf("\\f"); break;
-            case '\b': oprintf("\\b"); break;
-            case '\a': oprintf("\\a"); break;
-            case '\\': oprintf("\\\\"); break;
-            case '"': oprintf("\\\""); break;
-            default: oprintf("%c", *str); break;
-        }
-    }
-    oprintf("\"");
-}
-
-void ir_value_dump(ir_value* v, int (*oprintf)(const char*, ...))
-{
-    if (v->hasvalue) {
-        switch (v->vtype) {
-            default:
-            case TYPE_VOID:
-                oprintf("(void)");
-                break;
-            case TYPE_FUNCTION:
-                oprintf("fn:%s", v->name);
-                break;
-            case TYPE_FLOAT:
-                oprintf("%g", v->constval.vfloat);
-                break;
-            case TYPE_VECTOR:
-                oprintf("'%g %g %g'",
-                        v->constval.vvec.x,
-                        v->constval.vvec.y,
-                        v->constval.vvec.z);
-                break;
-            case TYPE_ENTITY:
-                oprintf("(entity)");
-                break;
-            case TYPE_STRING:
-                ir_value_dump_string(v->constval.vstring, oprintf);
-                break;
-#if 0
-            case TYPE_INTEGER:
-                oprintf("%i", v->constval.vint);
-                break;
-#endif
-            case TYPE_POINTER:
-                oprintf("&%s",
-                    v->constval.vpointer->name);
-                break;
-        }
-    } else {
-        oprintf("%s", v->name);
-    }
-}
-
-void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...))
-{
-    size_t i;
-    oprintf("Life of %12s:", self->name);
-    for (i = 0; i < vec_size(self->life); ++i)
-    {
-        oprintf(" + [%i, %i]\n", self->life[i].start, self->life[i].end);
-    }
-}
diff --git a/ir.cpp b/ir.cpp
new file mode 100644 (file)
index 0000000..8c3d234
--- /dev/null
+++ b/ir.cpp
@@ -0,0 +1,4441 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "gmqcc.h"
+#include "ir.h"
+
+/***********************************************************************
+ * Type sizes used at multiple points in the IR codegen
+ */
+
+const char *type_name[TYPE_COUNT] = {
+    "void",
+    "string",
+    "float",
+    "vector",
+    "entity",
+    "field",
+    "function",
+    "pointer",
+    "integer",
+    "variant",
+    "struct",
+    "union",
+    "array",
+
+    "nil",
+    "<no-expression>"
+};
+
+static size_t type_sizeof_[TYPE_COUNT] = {
+    1, /* TYPE_VOID     */
+    1, /* TYPE_STRING   */
+    1, /* TYPE_FLOAT    */
+    3, /* TYPE_VECTOR   */
+    1, /* TYPE_ENTITY   */
+    1, /* TYPE_FIELD    */
+    1, /* TYPE_FUNCTION */
+    1, /* TYPE_POINTER  */
+    1, /* TYPE_INTEGER  */
+    3, /* TYPE_VARIANT  */
+    0, /* TYPE_STRUCT   */
+    0, /* TYPE_UNION    */
+    0, /* TYPE_ARRAY    */
+    0, /* TYPE_NIL      */
+    0, /* TYPE_NOESPR   */
+};
+
+const uint16_t type_store_instr[TYPE_COUNT] = {
+    INSTR_STORE_F, /* should use I when having integer support */
+    INSTR_STORE_S,
+    INSTR_STORE_F,
+    INSTR_STORE_V,
+    INSTR_STORE_ENT,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FNC,
+    INSTR_STORE_ENT, /* should use I */
+#if 0
+    INSTR_STORE_I, /* integer type */
+#else
+    INSTR_STORE_F,
+#endif
+
+    INSTR_STORE_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t field_store_instr[TYPE_COUNT] = {
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_V,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+    INSTR_STORE_FLD,
+#if 0
+    INSTR_STORE_FLD, /* integer type */
+#else
+    INSTR_STORE_FLD,
+#endif
+
+    INSTR_STORE_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_storep_instr[TYPE_COUNT] = {
+    INSTR_STOREP_F, /* should use I when having integer support */
+    INSTR_STOREP_S,
+    INSTR_STOREP_F,
+    INSTR_STOREP_V,
+    INSTR_STOREP_ENT,
+    INSTR_STOREP_FLD,
+    INSTR_STOREP_FNC,
+    INSTR_STOREP_ENT, /* should use I */
+#if 0
+    INSTR_STOREP_ENT, /* integer type */
+#else
+    INSTR_STOREP_F,
+#endif
+
+    INSTR_STOREP_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_eq_instr[TYPE_COUNT] = {
+    INSTR_EQ_F, /* should use I when having integer support */
+    INSTR_EQ_S,
+    INSTR_EQ_F,
+    INSTR_EQ_V,
+    INSTR_EQ_E,
+    INSTR_EQ_E, /* FLD has no comparison */
+    INSTR_EQ_FNC,
+    INSTR_EQ_E, /* should use I */
+#if 0
+    INSTR_EQ_I,
+#else
+    INSTR_EQ_F,
+#endif
+
+    INSTR_EQ_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_ne_instr[TYPE_COUNT] = {
+    INSTR_NE_F, /* should use I when having integer support */
+    INSTR_NE_S,
+    INSTR_NE_F,
+    INSTR_NE_V,
+    INSTR_NE_E,
+    INSTR_NE_E, /* FLD has no comparison */
+    INSTR_NE_FNC,
+    INSTR_NE_E, /* should use I */
+#if 0
+    INSTR_NE_I,
+#else
+    INSTR_NE_F,
+#endif
+
+    INSTR_NE_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+const uint16_t type_not_instr[TYPE_COUNT] = {
+    INSTR_NOT_F, /* should use I when having integer support */
+    VINSTR_END,  /* not to be used, depends on string related -f flags */
+    INSTR_NOT_F,
+    INSTR_NOT_V,
+    INSTR_NOT_ENT,
+    INSTR_NOT_ENT,
+    INSTR_NOT_FNC,
+    INSTR_NOT_ENT, /* should use I */
+#if 0
+    INSTR_NOT_I, /* integer type */
+#else
+    INSTR_NOT_F,
+#endif
+
+    INSTR_NOT_V, /* variant, should never be accessed */
+
+    VINSTR_END, /* struct */
+    VINSTR_END, /* union  */
+    VINSTR_END, /* array  */
+    VINSTR_END, /* nil    */
+    VINSTR_END, /* noexpr */
+};
+
+/* protos */
+static ir_value*       ir_value_var(const char *name, int st, int vtype);
+static bool            ir_value_set_name(ir_value*, const char *name);
+static void            ir_value_dump(ir_value*, int (*oprintf)(const char*,...));
+
+static ir_value*       ir_gen_extparam_proto(ir_builder *ir);
+static void            ir_gen_extparam      (ir_builder *ir);
+
+static bool            ir_builder_set_name(ir_builder *self, const char *name);
+
+static ir_function*    ir_function_new(struct ir_builder_s *owner, int returntype);
+static bool            ir_function_set_name(ir_function*, const char *name);
+static void            ir_function_delete(ir_function*);
+static void            ir_function_dump(ir_function*, char *ind, int (*oprintf)(const char*,...));
+
+static ir_value*       ir_block_create_general_instr(ir_block *self, lex_ctx_t, const char *label,
+                                        int op, ir_value *a, ir_value *b, int outype);
+static void            ir_block_delete(ir_block*);
+static ir_block*       ir_block_new(struct ir_function_s *owner, const char *label);
+static bool GMQCC_WARN ir_block_create_store(ir_block*, lex_ctx_t, ir_value *target, ir_value *what);
+static bool            ir_block_set_label(ir_block*, const char *label);
+static void            ir_block_dump(ir_block*, char *ind, int (*oprintf)(const char*,...));
+
+static bool            ir_instr_op(ir_instr*, int op, ir_value *value, bool writing);
+static void            ir_instr_delete(ir_instr*);
+static void            ir_instr_dump(ir_instr* in, char *ind, int (*oprintf)(const char*,...));
+/* error functions */
+
+static void irerror(lex_ctx_t ctx, const char *msg, ...)
+{
+    va_list ap;
+    va_start(ap, msg);
+    con_cvprintmsg(ctx, LVL_ERROR, "internal error", msg, ap);
+    va_end(ap);
+}
+
+static bool GMQCC_WARN irwarning(lex_ctx_t 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;
+}
+
+/***********************************************************************
+ * Vector utility functions
+ */
+
+static bool GMQCC_WARN vec_ir_value_find(ir_value **vec, const ir_value *what, size_t *idx)
+{
+    size_t i;
+    size_t len = vec_size(vec);
+    for (i = 0; i < len; ++i) {
+        if (vec[i] == what) {
+            if (idx) *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool GMQCC_WARN vec_ir_block_find(ir_block **vec, ir_block *what, size_t *idx)
+{
+    size_t i;
+    size_t len = vec_size(vec);
+    for (i = 0; i < len; ++i) {
+        if (vec[i] == what) {
+            if (idx) *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool GMQCC_WARN vec_ir_instr_find(ir_instr **vec, ir_instr *what, size_t *idx)
+{
+    size_t i;
+    size_t len = vec_size(vec);
+    for (i = 0; i < len; ++i) {
+        if (vec[i] == what) {
+            if (idx) *idx = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+/***********************************************************************
+ * IR Builder
+ */
+
+static void ir_block_delete_quick(ir_block* self);
+static void ir_instr_delete_quick(ir_instr *self);
+static void ir_function_delete_quick(ir_function *self);
+
+ir_builder* ir_builder_new(const char *modulename)
+{
+    ir_builder* self;
+    size_t      i;
+
+    self = (ir_builder*)mem_a(sizeof(*self));
+    if (!self)
+        return NULL;
+
+    self->functions   = NULL;
+    self->globals     = NULL;
+    self->fields      = NULL;
+    self->filenames   = NULL;
+    self->filestrings = NULL;
+    self->htglobals   = util_htnew(IR_HT_SIZE);
+    self->htfields    = util_htnew(IR_HT_SIZE);
+    self->htfunctions = util_htnew(IR_HT_SIZE);
+
+    self->extparams       = NULL;
+    self->extparam_protos = NULL;
+
+    self->first_common_globaltemp = 0;
+    self->max_globaltemps         = 0;
+    self->first_common_local      = 0;
+    self->max_locals              = 0;
+
+    self->str_immediate = 0;
+    self->name = NULL;
+    if (!ir_builder_set_name(self, modulename)) {
+        mem_d(self);
+        return NULL;
+    }
+
+    self->nil = ir_value_var("nil", store_value, TYPE_NIL);
+    self->nil->cvq = CV_CONST;
+
+    for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
+        /* we write to them, but they're not supposed to be used outside the IR, so
+         * let's not allow the generation of ir_instrs which use these.
+         * So it's a constant noexpr.
+         */
+        self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR);
+        self->vinstr_temp[i]->cvq = CV_CONST;
+    }
+
+    self->reserved_va_count = NULL;
+    self->coverage_func     = NULL;
+
+    self->code              = code_init();
+
+    return self;
+}
+
+void ir_builder_delete(ir_builder* self)
+{
+    size_t i;
+    util_htdel(self->htglobals);
+    util_htdel(self->htfields);
+    util_htdel(self->htfunctions);
+    mem_d((void*)self->name);
+    for (i = 0; i != vec_size(self->functions); ++i) {
+        ir_function_delete_quick(self->functions[i]);
+    }
+    vec_free(self->functions);
+    for (i = 0; i != vec_size(self->extparams); ++i) {
+        ir_value_delete(self->extparams[i]);
+    }
+    vec_free(self->extparams);
+    vec_free(self->extparam_protos);
+    for (i = 0; i != vec_size(self->globals); ++i) {
+        ir_value_delete(self->globals[i]);
+    }
+    vec_free(self->globals);
+    for (i = 0; i != vec_size(self->fields); ++i) {
+        ir_value_delete(self->fields[i]);
+    }
+    ir_value_delete(self->nil);
+    for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) {
+        ir_value_delete(self->vinstr_temp[i]);
+    }
+    vec_free(self->fields);
+    vec_free(self->filenames);
+    vec_free(self->filestrings);
+
+    code_cleanup(self->code);
+    mem_d(self);
+}
+
+bool ir_builder_set_name(ir_builder *self, const char *name)
+{
+    if (self->name)
+        mem_d((void*)self->name);
+    self->name = util_strdup(name);
+    return !!self->name;
+}
+
+static ir_function* ir_builder_get_function(ir_builder *self, const char *name)
+{
+    return (ir_function*)util_htget(self->htfunctions, name);
+}
+
+ir_function* ir_builder_create_function(ir_builder *self, const char *name, int outtype)
+{
+    ir_function *fn = ir_builder_get_function(self, name);
+    if (fn) {
+        return NULL;
+    }
+
+    fn = ir_function_new(self, outtype);
+    if (!ir_function_set_name(fn, name))
+    {
+        ir_function_delete(fn);
+        return NULL;
+    }
+    vec_push(self->functions, fn);
+    util_htset(self->htfunctions, name, fn);
+
+    fn->value = ir_builder_create_global(self, fn->name, TYPE_FUNCTION);
+    if (!fn->value) {
+        ir_function_delete(fn);
+        return NULL;
+    }
+
+    fn->value->hasvalue = true;
+    fn->value->outtype = outtype;
+    fn->value->constval.vfunc = fn;
+    fn->value->context = fn->context;
+
+    return fn;
+}
+
+static ir_value* ir_builder_get_global(ir_builder *self, const char *name)
+{
+    return (ir_value*)util_htget(self->htglobals, name);
+}
+
+ir_value* ir_builder_create_global(ir_builder *self, const char *name, int vtype)
+{
+    ir_value *ve;
+
+    if (name[0] != '#')
+    {
+        ve = ir_builder_get_global(self, name);
+        if (ve) {
+            return NULL;
+        }
+    }
+
+    ve = ir_value_var(name, store_global, vtype);
+    vec_push(self->globals, ve);
+    util_htset(self->htglobals, name, ve);
+    return ve;
+}
+
+ir_value* ir_builder_get_va_count(ir_builder *self)
+{
+    if (self->reserved_va_count)
+        return self->reserved_va_count;
+    return (self->reserved_va_count = ir_builder_create_global(self, "reserved:va_count", TYPE_FLOAT));
+}
+
+static ir_value* ir_builder_get_field(ir_builder *self, const char *name)
+{
+    return (ir_value*)util_htget(self->htfields, name);
+}
+
+
+ir_value* ir_builder_create_field(ir_builder *self, const char *name, int vtype)
+{
+    ir_value *ve = ir_builder_get_field(self, name);
+    if (ve) {
+        return NULL;
+    }
+
+    ve = ir_value_var(name, store_global, TYPE_FIELD);
+    ve->fieldtype = vtype;
+    vec_push(self->fields, ve);
+    util_htset(self->htfields, name, ve);
+    return ve;
+}
+
+/***********************************************************************
+ *IR Function
+ */
+
+static bool ir_function_naive_phi(ir_function*);
+static void ir_function_enumerate(ir_function*);
+static bool ir_function_calculate_liferanges(ir_function*);
+static bool ir_function_allocate_locals(ir_function*);
+
+ir_function* ir_function_new(ir_builder* owner, int outtype)
+{
+    ir_function *self;
+    self = (ir_function*)mem_a(sizeof(*self));
+
+    if (!self)
+        return NULL;
+
+    memset(self, 0, sizeof(*self));
+
+    self->name = NULL;
+    if (!ir_function_set_name(self, "<@unnamed>")) {
+        mem_d(self);
+        return NULL;
+    }
+    self->flags = 0;
+
+    self->owner = owner;
+    self->context.file = "<@no context>";
+    self->context.line = 0;
+    self->outtype = outtype;
+    self->value = NULL;
+    self->builtin = 0;
+
+    self->params = NULL;
+    self->blocks = NULL;
+    self->values = NULL;
+    self->locals = NULL;
+
+    self->max_varargs = 0;
+
+    self->code_function_def = -1;
+    self->allocated_locals = 0;
+    self->globaltemps      = 0;
+
+    self->run_id = 0;
+    return self;
+}
+
+bool ir_function_set_name(ir_function *self, const char *name)
+{
+    if (self->name)
+        mem_d((void*)self->name);
+    self->name = util_strdup(name);
+    return !!self->name;
+}
+
+static void ir_function_delete_quick(ir_function *self)
+{
+    size_t i;
+    mem_d((void*)self->name);
+
+    for (i = 0; i != vec_size(self->blocks); ++i)
+        ir_block_delete_quick(self->blocks[i]);
+    vec_free(self->blocks);
+
+    vec_free(self->params);
+
+    for (i = 0; i != vec_size(self->values); ++i)
+        ir_value_delete(self->values[i]);
+    vec_free(self->values);
+
+    for (i = 0; i != vec_size(self->locals); ++i)
+        ir_value_delete(self->locals[i]);
+    vec_free(self->locals);
+
+    /* self->value is deleted by the builder */
+
+    mem_d(self);
+}
+
+void ir_function_delete(ir_function *self)
+{
+    size_t i;
+    mem_d((void*)self->name);
+
+    for (i = 0; i != vec_size(self->blocks); ++i)
+        ir_block_delete(self->blocks[i]);
+    vec_free(self->blocks);
+
+    vec_free(self->params);
+
+    for (i = 0; i != vec_size(self->values); ++i)
+        ir_value_delete(self->values[i]);
+    vec_free(self->values);
+
+    for (i = 0; i != vec_size(self->locals); ++i)
+        ir_value_delete(self->locals[i]);
+    vec_free(self->locals);
+
+    /* self->value is deleted by the builder */
+
+    mem_d(self);
+}
+
+static void ir_function_collect_value(ir_function *self, ir_value *v)
+{
+    vec_push(self->values, v);
+}
+
+ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function *self, const char *label)
+{
+    ir_block* bn = ir_block_new(self, label);
+    bn->context = ctx;
+    vec_push(self->blocks, bn);
+
+    if ((self->flags & IR_FLAG_BLOCK_COVERAGE) && self->owner->coverage_func)
+        (void)ir_block_create_call(bn, ctx, NULL, self->owner->coverage_func, false);
+
+    return bn;
+}
+
+static bool instr_is_operation(uint16_t op)
+{
+    return ( (op >= INSTR_MUL_F  && op <= INSTR_GT) ||
+             (op >= INSTR_LOAD_F && op <= INSTR_LOAD_FNC) ||
+             (op == INSTR_ADDRESS) ||
+             (op >= INSTR_NOT_F  && op <= INSTR_NOT_FNC) ||
+             (op >= INSTR_AND    && op <= INSTR_BITOR) ||
+             (op >= INSTR_CALL0  && op <= INSTR_CALL8) ||
+             (op >= VINSTR_BITAND_V && op <= VINSTR_NEG_V) );
+}
+
+static bool ir_function_pass_peephole(ir_function *self)
+{
+    size_t b;
+
+    for (b = 0; b < vec_size(self->blocks); ++b) {
+        size_t    i;
+        ir_block *block = self->blocks[b];
+
+        for (i = 0; i < vec_size(block->instr); ++i) {
+            ir_instr *inst;
+            inst = block->instr[i];
+
+            if (i >= 1 &&
+                (inst->opcode >= INSTR_STORE_F &&
+                 inst->opcode <= INSTR_STORE_FNC))
+            {
+                ir_instr *store;
+                ir_instr *oper;
+                ir_value *value;
+
+                store = inst;
+
+                oper  = block->instr[i-1];
+                if (!instr_is_operation(oper->opcode))
+                    continue;
+
+                /* Don't change semantics of MUL_VF in engines where these may not alias. */
+                if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) {
+                    if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1])
+                        continue;
+                    if (oper->opcode == INSTR_MUL_FV && oper->_ops[1]->memberof == oper->_ops[2])
+                        continue;
+                }
+
+                value = oper->_ops[0];
+
+                /* only do it for SSA values */
+                if (value->store != store_value)
+                    continue;
+
+                /* don't optimize out the temp if it's used later again */
+                if (vec_size(value->reads) != 1)
+                    continue;
+
+                /* The very next store must use this value */
+                if (value->reads[0] != store)
+                    continue;
+
+                /* And of course the store must _read_ from it, so it's in
+                 * OP 1 */
+                if (store->_ops[1] != value)
+                    continue;
+
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                (void)!ir_instr_op(oper, 0, store->_ops[0], true);
+
+                vec_remove(block->instr, i, 1);
+                ir_instr_delete(store);
+            }
+            else if (inst->opcode == VINSTR_COND)
+            {
+                /* COND on a value resulting from a NOT could
+                 * remove the NOT and swap its operands
+                 */
+                while (true) {
+                    ir_block *tmp;
+                    size_t    inotid;
+                    ir_instr *inot;
+                    ir_value *value;
+                    value = inst->_ops[0];
+
+                    if (value->store != store_value ||
+                        vec_size(value->reads) != 1 ||
+                        value->reads[0] != inst)
+                    {
+                        break;
+                    }
+
+                    inot = value->writes[0];
+                    if (inot->_ops[0] != value ||
+                        inot->opcode < INSTR_NOT_F ||
+                        inot->opcode > INSTR_NOT_FNC ||
+                        inot->opcode == INSTR_NOT_V || /* can't do these */
+                        inot->opcode == INSTR_NOT_S)
+                    {
+                        break;
+                    }
+
+                    /* count */
+                    ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                    /* change operand */
+                    (void)!ir_instr_op(inst, 0, inot->_ops[1], false);
+                    /* remove NOT */
+                    tmp = inot->owner;
+                    for (inotid = 0; inotid < vec_size(tmp->instr); ++inotid) {
+                        if (tmp->instr[inotid] == inot)
+                            break;
+                    }
+                    if (inotid >= vec_size(tmp->instr)) {
+                        compile_error(inst->context, "sanity-check failed: failed to find instruction to optimize out");
+                        return false;
+                    }
+                    vec_remove(tmp->instr, inotid, 1);
+                    ir_instr_delete(inot);
+                    /* swap ontrue/onfalse */
+                    tmp = inst->bops[0];
+                    inst->bops[0] = inst->bops[1];
+                    inst->bops[1] = tmp;
+                }
+                continue;
+            }
+        }
+    }
+
+    return true;
+}
+
+static bool ir_function_pass_tailrecursion(ir_function *self)
+{
+    size_t b, p;
+
+    for (b = 0; b < vec_size(self->blocks); ++b) {
+        ir_value *funcval;
+        ir_instr *ret, *call, *store = NULL;
+        ir_block *block = self->blocks[b];
+
+        if (!block->final || vec_size(block->instr) < 2)
+            continue;
+
+        ret = block->instr[vec_size(block->instr)-1];
+        if (ret->opcode != INSTR_DONE && ret->opcode != INSTR_RETURN)
+            continue;
+
+        call = block->instr[vec_size(block->instr)-2];
+        if (call->opcode >= INSTR_STORE_F && call->opcode <= INSTR_STORE_FNC) {
+            /* account for the unoptimized
+             * CALL
+             * STORE %return, %tmp
+             * RETURN %tmp
+             * version
+             */
+            if (vec_size(block->instr) < 3)
+                continue;
+
+            store = call;
+            call = block->instr[vec_size(block->instr)-3];
+        }
+
+        if (call->opcode < INSTR_CALL0 || call->opcode > INSTR_CALL8)
+            continue;
+
+        if (store) {
+            /* optimize out the STORE */
+            if (ret->_ops[0]   &&
+                ret->_ops[0]   == store->_ops[0] &&
+                store->_ops[1] == call->_ops[0])
+            {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                call->_ops[0] = store->_ops[0];
+                vec_remove(block->instr, vec_size(block->instr) - 2, 1);
+                ir_instr_delete(store);
+            }
+            else
+                continue;
+        }
+
+        if (!call->_ops[0])
+            continue;
+
+        funcval = call->_ops[1];
+        if (!funcval)
+            continue;
+        if (funcval->vtype != TYPE_FUNCTION || funcval->constval.vfunc != self)
+            continue;
+
+        /* now we have a CALL and a RET, check if it's a tailcall */
+        if (ret->_ops[0] && call->_ops[0] != ret->_ops[0])
+            continue;
+
+        ++opts_optimizationcount[OPTIM_TAIL_RECURSION];
+        vec_shrinkby(block->instr, 2);
+
+        block->final = false; /* open it back up */
+
+        /* emite parameter-stores */
+        for (p = 0; p < vec_size(call->params); ++p) {
+            /* assert(call->params_count <= self->locals_count); */
+            if (!ir_block_create_store(block, call->context, self->locals[p], call->params[p])) {
+                irerror(call->context, "failed to create tailcall store instruction for parameter %i", (int)p);
+                return false;
+            }
+        }
+        if (!ir_block_create_jump(block, call->context, self->blocks[0])) {
+            irerror(call->context, "failed to create tailcall jump");
+            return false;
+        }
+
+        ir_instr_delete(call);
+        ir_instr_delete(ret);
+    }
+
+    return true;
+}
+
+bool ir_function_finalize(ir_function *self)
+{
+    size_t i;
+
+    if (self->builtin)
+        return true;
+
+    if (OPTS_OPTIMIZATION(OPTIM_PEEPHOLE)) {
+        if (!ir_function_pass_peephole(self)) {
+            irerror(self->context, "generic optimization pass broke something in `%s`", self->name);
+            return false;
+        }
+    }
+
+    if (OPTS_OPTIMIZATION(OPTIM_TAIL_RECURSION)) {
+        if (!ir_function_pass_tailrecursion(self)) {
+            irerror(self->context, "tail-recursion optimization pass broke something in `%s`", self->name);
+            return false;
+        }
+    }
+
+    if (!ir_function_naive_phi(self)) {
+        irerror(self->context, "internal error: ir_function_naive_phi failed");
+        return false;
+    }
+
+    for (i = 0; i < vec_size(self->locals); ++i) {
+        ir_value *v = self->locals[i];
+        if (v->vtype == TYPE_VECTOR ||
+            (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR))
+        {
+            ir_value_vector_member(v, 0);
+            ir_value_vector_member(v, 1);
+            ir_value_vector_member(v, 2);
+        }
+    }
+    for (i = 0; i < vec_size(self->values); ++i) {
+        ir_value *v = self->values[i];
+        if (v->vtype == TYPE_VECTOR ||
+            (v->vtype == TYPE_FIELD && v->outtype == TYPE_VECTOR))
+        {
+            ir_value_vector_member(v, 0);
+            ir_value_vector_member(v, 1);
+            ir_value_vector_member(v, 2);
+        }
+    }
+
+    ir_function_enumerate(self);
+
+    if (!ir_function_calculate_liferanges(self))
+        return false;
+    if (!ir_function_allocate_locals(self))
+        return false;
+    return true;
+}
+
+ir_value* ir_function_create_local(ir_function *self, const char *name, int vtype, bool param)
+{
+    ir_value *ve;
+
+    if (param &&
+        vec_size(self->locals) &&
+        self->locals[vec_size(self->locals)-1]->store != store_param) {
+        irerror(self->context, "cannot add parameters after adding locals");
+        return NULL;
+    }
+
+    ve = ir_value_var(name, (param ? store_param : store_local), vtype);
+    if (param)
+        ve->locked = true;
+    vec_push(self->locals, ve);
+    return ve;
+}
+
+/***********************************************************************
+ *IR Block
+ */
+
+ir_block* ir_block_new(ir_function* owner, const char *name)
+{
+    ir_block *self;
+    self = (ir_block*)mem_a(sizeof(*self));
+    if (!self)
+        return NULL;
+
+    memset(self, 0, sizeof(*self));
+
+    self->label = NULL;
+    if (name && !ir_block_set_label(self, name)) {
+        mem_d(self);
+        return NULL;
+    }
+    self->owner = owner;
+    self->context.file = "<@no context>";
+    self->context.line = 0;
+    self->final = false;
+
+    self->instr   = NULL;
+    self->entries = NULL;
+    self->exits   = NULL;
+
+    self->eid = 0;
+    self->is_return = false;
+
+    self->living = NULL;
+
+    self->generated = false;
+
+    return self;
+}
+
+static void ir_block_delete_quick(ir_block* self)
+{
+    size_t i;
+    if (self->label) mem_d(self->label);
+    for (i = 0; i != vec_size(self->instr); ++i)
+        ir_instr_delete_quick(self->instr[i]);
+    vec_free(self->instr);
+    vec_free(self->entries);
+    vec_free(self->exits);
+    vec_free(self->living);
+    mem_d(self);
+}
+
+void ir_block_delete(ir_block* self)
+{
+    size_t i;
+    if (self->label) mem_d(self->label);
+    for (i = 0; i != vec_size(self->instr); ++i)
+        ir_instr_delete(self->instr[i]);
+    vec_free(self->instr);
+    vec_free(self->entries);
+    vec_free(self->exits);
+    vec_free(self->living);
+    mem_d(self);
+}
+
+bool ir_block_set_label(ir_block *self, const char *name)
+{
+    if (self->label)
+        mem_d((void*)self->label);
+    self->label = util_strdup(name);
+    return !!self->label;
+}
+
+/***********************************************************************
+ *IR Instructions
+ */
+
+static ir_instr* ir_instr_new(lex_ctx_t ctx, ir_block* owner, int op)
+{
+    ir_instr *self;
+    self = (ir_instr*)mem_a(sizeof(*self));
+    if (!self)
+        return NULL;
+
+    self->owner = owner;
+    self->context = ctx;
+    self->opcode = op;
+    self->_ops[0] = NULL;
+    self->_ops[1] = NULL;
+    self->_ops[2] = NULL;
+    self->bops[0] = NULL;
+    self->bops[1] = NULL;
+
+    self->phi    = NULL;
+    self->params = NULL;
+
+    self->eid = 0;
+
+    self->likely = true;
+    return self;
+}
+
+static void ir_instr_delete_quick(ir_instr *self)
+{
+    vec_free(self->phi);
+    vec_free(self->params);
+    mem_d(self);
+}
+
+static void ir_instr_delete(ir_instr *self)
+{
+    size_t i;
+    /* The following calls can only delete from
+     * vectors, we still want to delete this instruction
+     * so ignore the return value. Since with the warn_unused_result attribute
+     * gcc doesn't care about an explicit: (void)foo(); to ignore the result,
+     * I have to improvise here and use if(foo());
+     */
+    for (i = 0; i < vec_size(self->phi); ++i) {
+        size_t idx;
+        if (vec_ir_instr_find(self->phi[i].value->writes, self, &idx))
+            vec_remove(self->phi[i].value->writes, idx, 1);
+        if (vec_ir_instr_find(self->phi[i].value->reads, self, &idx))
+            vec_remove(self->phi[i].value->reads, idx, 1);
+    }
+    vec_free(self->phi);
+    for (i = 0; i < vec_size(self->params); ++i) {
+        size_t idx;
+        if (vec_ir_instr_find(self->params[i]->writes, self, &idx))
+            vec_remove(self->params[i]->writes, idx, 1);
+        if (vec_ir_instr_find(self->params[i]->reads, self, &idx))
+            vec_remove(self->params[i]->reads, idx, 1);
+    }
+    vec_free(self->params);
+    (void)!ir_instr_op(self, 0, NULL, false);
+    (void)!ir_instr_op(self, 1, NULL, false);
+    (void)!ir_instr_op(self, 2, NULL, false);
+    mem_d(self);
+}
+
+static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing)
+{
+    if (v && v->vtype == TYPE_NOEXPR) {
+        irerror(self->context, "tried to use a NOEXPR value");
+        return false;
+    }
+
+    if (self->_ops[op]) {
+        size_t idx;
+        if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx))
+            vec_remove(self->_ops[op]->writes, idx, 1);
+        else if (vec_ir_instr_find(self->_ops[op]->reads, self, &idx))
+            vec_remove(self->_ops[op]->reads, idx, 1);
+    }
+    if (v) {
+        if (writing)
+            vec_push(v->writes, self);
+        else
+            vec_push(v->reads, self);
+    }
+    self->_ops[op] = v;
+    return true;
+}
+
+/***********************************************************************
+ *IR Value
+ */
+
+static void ir_value_code_setaddr(ir_value *self, int32_t gaddr)
+{
+    self->code.globaladdr = gaddr;
+    if (self->members[0]) self->members[0]->code.globaladdr = gaddr;
+    if (self->members[1]) self->members[1]->code.globaladdr = gaddr;
+    if (self->members[2]) self->members[2]->code.globaladdr = gaddr;
+}
+
+static int32_t ir_value_code_addr(const ir_value *self)
+{
+    if (self->store == store_return)
+        return OFS_RETURN + self->code.addroffset;
+    return self->code.globaladdr + self->code.addroffset;
+}
+
+ir_value* ir_value_var(const char *name, int storetype, int vtype)
+{
+    ir_value *self;
+    self = (ir_value*)mem_a(sizeof(*self));
+    self->vtype = vtype;
+    self->fieldtype = TYPE_VOID;
+    self->outtype = TYPE_VOID;
+    self->store = storetype;
+    self->flags = 0;
+
+    self->reads  = NULL;
+    self->writes = NULL;
+
+    self->cvq          = CV_NONE;
+    self->hasvalue     = false;
+    self->context.file = "<@no context>";
+    self->context.line = 0;
+    self->name = NULL;
+    if (name && !ir_value_set_name(self, name)) {
+        irerror(self->context, "out of memory");
+        mem_d(self);
+        return NULL;
+    }
+
+    memset(&self->constval, 0, sizeof(self->constval));
+    memset(&self->code,     0, sizeof(self->code));
+
+    self->members[0] = NULL;
+    self->members[1] = NULL;
+    self->members[2] = NULL;
+    self->memberof = NULL;
+
+    self->unique_life = false;
+    self->locked      = false;
+    self->callparam   = false;
+
+    self->life = NULL;
+    return self;
+}
+
+/*  helper function */
+static ir_value* ir_builder_imm_float(ir_builder *self, float value, bool add_to_list) {
+    ir_value *v = ir_value_var("#IMMEDIATE", store_global, TYPE_FLOAT);
+    v->flags |= IR_FLAG_ERASABLE;
+    v->hasvalue = true;
+    v->cvq = CV_CONST;
+    v->constval.vfloat = value;
+
+    vec_push(self->globals, v);
+    if (add_to_list)
+        vec_push(self->const_floats, v);
+    return v;
+}
+
+ir_value* ir_value_vector_member(ir_value *self, unsigned int member)
+{
+    char     *name;
+    size_t    len;
+    ir_value *m;
+    if (member >= 3)
+        return NULL;
+
+    if (self->members[member])
+        return self->members[member];
+
+    if (self->name) {
+        len = strlen(self->name);
+        name = (char*)mem_a(len + 3);
+        memcpy(name, self->name, len);
+        name[len+0] = '_';
+        name[len+1] = 'x' + member;
+        name[len+2] = '\0';
+    }
+    else
+        name = NULL;
+
+    if (self->vtype == TYPE_VECTOR)
+    {
+        m = ir_value_var(name, self->store, TYPE_FLOAT);
+        if (name)
+            mem_d(name);
+        if (!m)
+            return NULL;
+        m->context = self->context;
+
+        self->members[member] = m;
+        m->code.addroffset = member;
+    }
+    else if (self->vtype == TYPE_FIELD)
+    {
+        if (self->fieldtype != TYPE_VECTOR)
+            return NULL;
+        m = ir_value_var(name, self->store, TYPE_FIELD);
+        if (name)
+            mem_d(name);
+        if (!m)
+            return NULL;
+        m->fieldtype = TYPE_FLOAT;
+        m->context = self->context;
+
+        self->members[member] = m;
+        m->code.addroffset = member;
+    }
+    else
+    {
+        irerror(self->context, "invalid member access on %s", self->name);
+        return NULL;
+    }
+
+    m->memberof = self;
+    return m;
+}
+
+static GMQCC_INLINE size_t ir_value_sizeof(const ir_value *self)
+{
+    if (self->vtype == TYPE_FIELD && self->fieldtype == TYPE_VECTOR)
+        return type_sizeof_[TYPE_VECTOR];
+    return type_sizeof_[self->vtype];
+}
+
+static ir_value* ir_value_out(ir_function *owner, const char *name, int storetype, int vtype)
+{
+    ir_value *v = ir_value_var(name, storetype, vtype);
+    if (!v)
+        return NULL;
+    ir_function_collect_value(owner, v);
+    return v;
+}
+
+void ir_value_delete(ir_value* self)
+{
+    size_t i;
+    if (self->name)
+        mem_d((void*)self->name);
+    if (self->hasvalue)
+    {
+        if (self->vtype == TYPE_STRING)
+            mem_d((void*)self->constval.vstring);
+    }
+    if (!(self->flags & IR_FLAG_SPLIT_VECTOR)) {
+        for (i = 0; i < 3; ++i) {
+            if (self->members[i])
+                ir_value_delete(self->members[i]);
+        }
+    }
+    vec_free(self->reads);
+    vec_free(self->writes);
+    vec_free(self->life);
+    mem_d(self);
+}
+
+bool ir_value_set_name(ir_value *self, const char *name)
+{
+    if (self->name)
+        mem_d((void*)self->name);
+    self->name = util_strdup(name);
+    return !!self->name;
+}
+
+bool ir_value_set_float(ir_value *self, float f)
+{
+    if (self->vtype != TYPE_FLOAT)
+        return false;
+    self->constval.vfloat = f;
+    self->hasvalue = true;
+    return true;
+}
+
+bool ir_value_set_func(ir_value *self, int f)
+{
+    if (self->vtype != TYPE_FUNCTION)
+        return false;
+    self->constval.vint = f;
+    self->hasvalue = true;
+    return true;
+}
+
+bool ir_value_set_vector(ir_value *self, vec3_t v)
+{
+    if (self->vtype != TYPE_VECTOR)
+        return false;
+    self->constval.vvec = v;
+    self->hasvalue = true;
+    return true;
+}
+
+bool ir_value_set_field(ir_value *self, ir_value *fld)
+{
+    if (self->vtype != TYPE_FIELD)
+        return false;
+    self->constval.vpointer = fld;
+    self->hasvalue = true;
+    return true;
+}
+
+bool ir_value_set_string(ir_value *self, const char *str)
+{
+    if (self->vtype != TYPE_STRING)
+        return false;
+    self->constval.vstring = util_strdupe(str);
+    self->hasvalue = true;
+    return true;
+}
+
+#if 0
+bool ir_value_set_int(ir_value *self, int i)
+{
+    if (self->vtype != TYPE_INTEGER)
+        return false;
+    self->constval.vint = i;
+    self->hasvalue = true;
+    return true;
+}
+#endif
+
+bool ir_value_lives(ir_value *self, size_t at)
+{
+    size_t i;
+    for (i = 0; i < vec_size(self->life); ++i)
+    {
+        ir_life_entry_t *life = &self->life[i];
+        if (life->start <= at && at <= life->end)
+            return true;
+        if (life->start > at) /* since it's ordered */
+            return false;
+    }
+    return false;
+}
+
+static bool ir_value_life_insert(ir_value *self, size_t idx, ir_life_entry_t e)
+{
+    size_t k;
+    vec_push(self->life, e);
+    for (k = vec_size(self->life)-1; k > idx; --k)
+        self->life[k] = self->life[k-1];
+    self->life[idx] = e;
+    return true;
+}
+
+static bool ir_value_life_merge(ir_value *self, size_t s)
+{
+    size_t i;
+    const size_t vs = vec_size(self->life);
+    ir_life_entry_t *life = NULL;
+    ir_life_entry_t *before = NULL;
+    ir_life_entry_t new_entry;
+
+    /* Find the first range >= s */
+    for (i = 0; i < vs; ++i)
+    {
+        before = life;
+        life = &self->life[i];
+        if (life->start > s)
+            break;
+    }
+    /* nothing found? append */
+    if (i == vs) {
+        ir_life_entry_t e;
+        if (life && life->end+1 == s)
+        {
+            /* previous life range can be merged in */
+            life->end++;
+            return true;
+        }
+        if (life && life->end >= s)
+            return false;
+        e.start = e.end = s;
+        vec_push(self->life, e);
+        return true;
+    }
+    /* found */
+    if (before)
+    {
+        if (before->end + 1 == s &&
+            life->start - 1 == s)
+        {
+            /* merge */
+            before->end = life->end;
+            vec_remove(self->life, i, 1);
+            return true;
+        }
+        if (before->end + 1 == s)
+        {
+            /* extend before */
+            before->end++;
+            return true;
+        }
+        /* already contained */
+        if (before->end >= s)
+            return false;
+    }
+    /* extend */
+    if (life->start - 1 == s)
+    {
+        life->start--;
+        return true;
+    }
+    /* insert a new entry */
+    new_entry.start = new_entry.end = s;
+    return ir_value_life_insert(self, i, new_entry);
+}
+
+static bool ir_value_life_merge_into(ir_value *self, const ir_value *other)
+{
+    size_t i, myi;
+
+    if (!vec_size(other->life))
+        return true;
+
+    if (!vec_size(self->life)) {
+        size_t count = vec_size(other->life);
+        ir_life_entry_t *life = vec_add(self->life, count);
+        memcpy(life, other->life, count * sizeof(*life));
+        return true;
+    }
+
+    myi = 0;
+    for (i = 0; i < vec_size(other->life); ++i)
+    {
+        const ir_life_entry_t *life = &other->life[i];
+        while (true)
+        {
+            ir_life_entry_t *entry = &self->life[myi];
+
+            if (life->end+1 < entry->start)
+            {
+                /* adding an interval before entry */
+                if (!ir_value_life_insert(self, myi, *life))
+                    return false;
+                ++myi;
+                break;
+            }
+
+            if (life->start <  entry->start &&
+                life->end+1 >= entry->start)
+            {
+                /* starts earlier and overlaps */
+                entry->start = life->start;
+            }
+
+            if (life->end   >  entry->end &&
+                life->start <= entry->end+1)
+            {
+                /* ends later and overlaps */
+                entry->end = life->end;
+            }
+
+            /* see if our change combines it with the next ranges */
+            while (myi+1 < vec_size(self->life) &&
+                   entry->end+1 >= self->life[1+myi].start)
+            {
+                /* overlaps with (myi+1) */
+                if (entry->end < self->life[1+myi].end)
+                    entry->end = self->life[1+myi].end;
+                vec_remove(self->life, myi+1, 1);
+                entry = &self->life[myi];
+            }
+
+            /* see if we're after the entry */
+            if (life->start > entry->end)
+            {
+                ++myi;
+                /* append if we're at the end */
+                if (myi >= vec_size(self->life)) {
+                    vec_push(self->life, *life);
+                    break;
+                }
+                /* otherweise check the next range */
+                continue;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
+static bool ir_values_overlap(const ir_value *a, const ir_value *b)
+{
+    /* For any life entry in A see if it overlaps with
+     * any life entry in B.
+     * Note that the life entries are orderes, so we can make a
+     * more efficient algorithm there than naively translating the
+     * statement above.
+     */
+
+    ir_life_entry_t *la, *lb, *enda, *endb;
+
+    /* first of all, if either has no life range, they cannot clash */
+    if (!vec_size(a->life) || !vec_size(b->life))
+        return false;
+
+    la = a->life;
+    lb = b->life;
+    enda = la + vec_size(a->life);
+    endb = lb + vec_size(b->life);
+    while (true)
+    {
+        /* check if the entries overlap, for that,
+         * both must start before the other one ends.
+         */
+        if (la->start < lb->end &&
+            lb->start < la->end)
+        {
+            return true;
+        }
+
+        /* entries are ordered
+         * one entry is earlier than the other
+         * that earlier entry will be moved forward
+         */
+        if (la->start < lb->start)
+        {
+            /* order: A B, move A forward
+             * check if we hit the end with A
+             */
+            if (++la == enda)
+                break;
+        }
+        else /* if (lb->start < la->start)  actually <= */
+        {
+            /* order: B A, move B forward
+             * check if we hit the end with B
+             */
+            if (++lb == endb)
+                break;
+        }
+    }
+    return false;
+}
+
+/***********************************************************************
+ *IR main operations
+ */
+
+static bool ir_check_unreachable(ir_block *self)
+{
+    /* The IR should never have to deal with unreachable code */
+    if (!self->final/* || OPTS_FLAG(ALLOW_UNREACHABLE_CODE)*/)
+        return true;
+    irerror(self->context, "unreachable statement (%s)", self->label);
+    return false;
+}
+
+bool ir_block_create_store_op(ir_block *self, lex_ctx_t ctx, int op, ir_value *target, ir_value *what)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    if (target->store == store_value &&
+        (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC))
+    {
+        irerror(self->context, "cannot store to an SSA value");
+        irerror(self->context, "trying to store: %s <- %s", target->name, what->name);
+        irerror(self->context, "instruction: %s", util_instr_str[op]);
+        return false;
+    }
+
+    in = ir_instr_new(ctx, self, op);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, target, (op < INSTR_STOREP_F || op > INSTR_STOREP_FNC)) ||
+        !ir_instr_op(in, 1, what, false))
+    {
+        ir_instr_delete(in);
+        return false;
+    }
+    vec_push(self->instr, in);
+    return true;
+}
+
+bool ir_block_create_state_op(ir_block *self, lex_ctx_t ctx, ir_value *frame, ir_value *think)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    in = ir_instr_new(ctx, self, INSTR_STATE);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, frame, false) ||
+        !ir_instr_op(in, 1, think, false))
+    {
+        ir_instr_delete(in);
+        return false;
+    }
+    vec_push(self->instr, in);
+    return true;
+}
+
+static bool ir_block_create_store(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
+{
+    int op = 0;
+    int vtype;
+    if (target->vtype == TYPE_VARIANT)
+        vtype = what->vtype;
+    else
+        vtype = target->vtype;
+
+#if 0
+    if      (vtype == TYPE_FLOAT   && what->vtype == TYPE_INTEGER)
+        op = INSTR_CONV_ITOF;
+    else if (vtype == TYPE_INTEGER && what->vtype == TYPE_FLOAT)
+        op = INSTR_CONV_FTOI;
+#endif
+        op = type_store_instr[vtype];
+
+    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
+        if (op == INSTR_STORE_FLD && what->fieldtype == TYPE_VECTOR)
+            op = INSTR_STORE_V;
+    }
+
+    return ir_block_create_store_op(self, ctx, op, target, what);
+}
+
+bool ir_block_create_storep(ir_block *self, lex_ctx_t ctx, ir_value *target, ir_value *what)
+{
+    int op = 0;
+    int vtype;
+
+    if (target->vtype != TYPE_POINTER)
+        return false;
+
+    /* storing using pointer - target is a pointer, type must be
+     * inferred from source
+     */
+    vtype = what->vtype;
+
+    op = type_storep_instr[vtype];
+    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS)) {
+        if (op == INSTR_STOREP_FLD && what->fieldtype == TYPE_VECTOR)
+            op = INSTR_STOREP_V;
+    }
+
+    return ir_block_create_store_op(self, ctx, op, target, what);
+}
+
+bool ir_block_create_return(ir_block *self, lex_ctx_t ctx, ir_value *v)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+
+    self->final = true;
+
+    self->is_return = true;
+    in = ir_instr_new(ctx, self, INSTR_RETURN);
+    if (!in)
+        return false;
+
+    if (v && !ir_instr_op(in, 0, v, false)) {
+        ir_instr_delete(in);
+        return false;
+    }
+
+    vec_push(self->instr, in);
+    return true;
+}
+
+bool ir_block_create_if(ir_block *self, lex_ctx_t ctx, ir_value *v,
+                        ir_block *ontrue, ir_block *onfalse)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+    self->final = true;
+    /*in = ir_instr_new(ctx, self, (v->vtype == TYPE_STRING ? INSTR_IF_S : INSTR_IF_F));*/
+    in = ir_instr_new(ctx, self, VINSTR_COND);
+    if (!in)
+        return false;
+
+    if (!ir_instr_op(in, 0, v, false)) {
+        ir_instr_delete(in);
+        return false;
+    }
+
+    in->bops[0] = ontrue;
+    in->bops[1] = onfalse;
+
+    vec_push(self->instr, in);
+
+    vec_push(self->exits, ontrue);
+    vec_push(self->exits, onfalse);
+    vec_push(ontrue->entries,  self);
+    vec_push(onfalse->entries, self);
+    return true;
+}
+
+bool ir_block_create_jump(ir_block *self, lex_ctx_t ctx, ir_block *to)
+{
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return false;
+    self->final = true;
+    in = ir_instr_new(ctx, self, VINSTR_JUMP);
+    if (!in)
+        return false;
+
+    in->bops[0] = to;
+    vec_push(self->instr, in);
+
+    vec_push(self->exits, to);
+    vec_push(to->entries, self);
+    return true;
+}
+
+bool ir_block_create_goto(ir_block *self, lex_ctx_t ctx, ir_block *to)
+{
+    self->owner->flags |= IR_FLAG_HAS_GOTO;
+    return ir_block_create_jump(self, ctx, to);
+}
+
+ir_instr* ir_block_create_phi(ir_block *self, lex_ctx_t ctx, const char *label, int ot)
+{
+    ir_value *out;
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return NULL;
+    in = ir_instr_new(ctx, self, VINSTR_PHI);
+    if (!in)
+        return NULL;
+    out = ir_value_out(self->owner, label, store_value, ot);
+    if (!out) {
+        ir_instr_delete(in);
+        return NULL;
+    }
+    if (!ir_instr_op(in, 0, out, true)) {
+        ir_instr_delete(in);
+        ir_value_delete(out);
+        return NULL;
+    }
+    vec_push(self->instr, in);
+    return in;
+}
+
+ir_value* ir_phi_value(ir_instr *self)
+{
+    return self->_ops[0];
+}
+
+void ir_phi_add(ir_instr* self, ir_block *b, ir_value *v)
+{
+    ir_phi_entry_t pe;
+
+    if (!vec_ir_block_find(self->owner->entries, b, NULL)) {
+        /* Must not be possible to cause this, otherwise the AST
+         * is doing something wrong.
+         */
+        irerror(self->context, "Invalid entry block for PHI");
+        exit(EXIT_FAILURE);
+    }
+
+    pe.value = v;
+    pe.from = b;
+    vec_push(v->reads, self);
+    vec_push(self->phi, pe);
+}
+
+/* call related code */
+ir_instr* ir_block_create_call(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *func, bool noreturn)
+{
+    ir_value *out;
+    ir_instr *in;
+    if (!ir_check_unreachable(self))
+        return NULL;
+    in = ir_instr_new(ctx, self, (noreturn ? VINSTR_NRCALL : INSTR_CALL0));
+    if (!in)
+        return NULL;
+    if (noreturn) {
+        self->final = true;
+        self->is_return = true;
+    }
+    out = ir_value_out(self->owner, label, (func->outtype == TYPE_VOID) ? store_return : store_value, func->outtype);
+    if (!out) {
+        ir_instr_delete(in);
+        return NULL;
+    }
+    if (!ir_instr_op(in, 0, out, true) ||
+        !ir_instr_op(in, 1, func, false))
+    {
+        ir_instr_delete(in);
+        ir_value_delete(out);
+        return NULL;
+    }
+    vec_push(self->instr, in);
+    /*
+    if (noreturn) {
+        if (!ir_block_create_return(self, ctx, NULL)) {
+            compile_error(ctx, "internal error: failed to generate dummy-return instruction");
+            ir_instr_delete(in);
+            return NULL;
+        }
+    }
+    */
+    return in;
+}
+
+ir_value* ir_call_value(ir_instr *self)
+{
+    return self->_ops[0];
+}
+
+void ir_call_param(ir_instr* self, ir_value *v)
+{
+    vec_push(self->params, v);
+    vec_push(v->reads, self);
+}
+
+/* binary op related code */
+
+ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx,
+                                const char *label, int opcode,
+                                ir_value *left, ir_value *right)
+{
+    int ot = TYPE_VOID;
+    switch (opcode) {
+        case INSTR_ADD_F:
+        case INSTR_SUB_F:
+        case INSTR_DIV_F:
+        case INSTR_MUL_F:
+        case INSTR_MUL_V:
+        case INSTR_AND:
+        case INSTR_OR:
+#if 0
+        case INSTR_AND_I:
+        case INSTR_AND_IF:
+        case INSTR_AND_FI:
+        case INSTR_OR_I:
+        case INSTR_OR_IF:
+        case INSTR_OR_FI:
+#endif
+        case INSTR_BITAND:
+        case INSTR_BITOR:
+        case VINSTR_BITXOR:
+#if 0
+        case INSTR_SUB_S: /* -- offset of string as float */
+        case INSTR_MUL_IF:
+        case INSTR_MUL_FI:
+        case INSTR_DIV_IF:
+        case INSTR_DIV_FI:
+        case INSTR_BITOR_IF:
+        case INSTR_BITOR_FI:
+        case INSTR_BITAND_FI:
+        case INSTR_BITAND_IF:
+        case INSTR_EQ_I:
+        case INSTR_NE_I:
+#endif
+            ot = TYPE_FLOAT;
+            break;
+#if 0
+        case INSTR_ADD_I:
+        case INSTR_ADD_IF:
+        case INSTR_ADD_FI:
+        case INSTR_SUB_I:
+        case INSTR_SUB_FI:
+        case INSTR_SUB_IF:
+        case INSTR_MUL_I:
+        case INSTR_DIV_I:
+        case INSTR_BITAND_I:
+        case INSTR_BITOR_I:
+        case INSTR_XOR_I:
+        case INSTR_RSHIFT_I:
+        case INSTR_LSHIFT_I:
+            ot = TYPE_INTEGER;
+            break;
+#endif
+        case INSTR_ADD_V:
+        case INSTR_SUB_V:
+        case INSTR_MUL_VF:
+        case INSTR_MUL_FV:
+        case VINSTR_BITAND_V:
+        case VINSTR_BITOR_V:
+        case VINSTR_BITXOR_V:
+        case VINSTR_BITAND_VF:
+        case VINSTR_BITOR_VF:
+        case VINSTR_BITXOR_VF:
+        case VINSTR_CROSS:
+#if 0
+        case INSTR_DIV_VF:
+        case INSTR_MUL_IV:
+        case INSTR_MUL_VI:
+#endif
+            ot = TYPE_VECTOR;
+            break;
+#if 0
+        case INSTR_ADD_SF:
+            ot = TYPE_POINTER;
+            break;
+#endif
+    /*
+     * after the following default case, the value of opcode can never
+     * be 1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65
+     */
+        default:
+            /* ranges: */
+            /* boolean operations result in floats */
+
+            /*
+             * opcode >= 10 takes true branch opcode is at least 10
+             * opcode <= 23 takes false branch opcode is at least 24
+             */
+            if (opcode >= INSTR_EQ_F && opcode <= INSTR_GT)
+                ot = TYPE_FLOAT;
+
+            /*
+             * At condition "opcode <= 23", the value of "opcode" must be
+             * at least 24.
+             * At condition "opcode <= 23", the value of "opcode" cannot be
+             * equal to any of {1, 2, 3, 4, 5, 6, 7, 8, 9, 62, 63, 64, 65}.
+             * The condition "opcode <= 23" cannot be true.
+             *
+             * Thus ot=2 (TYPE_FLOAT) can never be true
+             */
+#if 0
+            else if (opcode >= INSTR_LE && opcode <= INSTR_GT)
+                ot = TYPE_FLOAT;
+            else if (opcode >= INSTR_LE_I && opcode <= INSTR_EQ_FI)
+                ot = TYPE_FLOAT;
+#endif
+            break;
+    };
+    if (ot == TYPE_VOID) {
+        /* The AST or parser were supposed to check this! */
+        return NULL;
+    }
+
+    return ir_block_create_general_instr(self, ctx, label, opcode, left, right, ot);
+}
+
+ir_value* ir_block_create_unary(ir_block *self, lex_ctx_t ctx,
+                                const char *label, int opcode,
+                                ir_value *operand)
+{
+    int ot = TYPE_FLOAT;
+    switch (opcode) {
+        case INSTR_NOT_F:
+        case INSTR_NOT_V:
+        case INSTR_NOT_S:
+        case INSTR_NOT_ENT:
+        case INSTR_NOT_FNC: /*
+        case INSTR_NOT_I:   */
+            ot = TYPE_FLOAT;
+            break;
+
+        /*
+         * Negation for virtual instructions is emulated with 0-value. Thankfully
+         * the operand for 0 already exists so we just source it from here.
+         */
+        case VINSTR_NEG_F:
+            return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_F, NULL, operand, ot);
+        case VINSTR_NEG_V:
+            return ir_block_create_general_instr(self, ctx, label, INSTR_SUB_V, NULL, operand, TYPE_VECTOR);
+
+        default:
+            ot = operand->vtype;
+            break;
+    };
+    if (ot == TYPE_VOID) {
+        /* The AST or parser were supposed to check this! */
+        return NULL;
+    }
+
+    /* let's use the general instruction creator and pass NULL for OPB */
+    return ir_block_create_general_instr(self, ctx, label, opcode, operand, NULL, ot);
+}
+
+static ir_value* ir_block_create_general_instr(ir_block *self, lex_ctx_t ctx, const char *label,
+                                        int op, ir_value *a, ir_value *b, int outype)
+{
+    ir_instr *instr;
+    ir_value *out;
+
+    out = ir_value_out(self->owner, label, store_value, outype);
+    if (!out)
+        return NULL;
+
+    instr = ir_instr_new(ctx, self, op);
+    if (!instr) {
+        ir_value_delete(out);
+        return NULL;
+    }
+
+    if (!ir_instr_op(instr, 0, out, true) ||
+        !ir_instr_op(instr, 1, a, false) ||
+        !ir_instr_op(instr, 2, b, false) )
+    {
+        goto on_error;
+    }
+
+    vec_push(self->instr, instr);
+
+    return out;
+on_error:
+    ir_instr_delete(instr);
+    ir_value_delete(out);
+    return NULL;
+}
+
+ir_value* ir_block_create_fieldaddress(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field)
+{
+    ir_value *v;
+
+    /* Support for various pointer types todo if so desired */
+    if (ent->vtype != TYPE_ENTITY)
+        return NULL;
+
+    if (field->vtype != TYPE_FIELD)
+        return NULL;
+
+    v = ir_block_create_general_instr(self, ctx, label, INSTR_ADDRESS, ent, field, TYPE_POINTER);
+    v->fieldtype = field->fieldtype;
+    return v;
+}
+
+ir_value* ir_block_create_load_from_ent(ir_block *self, lex_ctx_t ctx, const char *label, ir_value *ent, ir_value *field, int outype)
+{
+    int op;
+    if (ent->vtype != TYPE_ENTITY)
+        return NULL;
+
+    /* at some point we could redirect for TYPE_POINTER... but that could lead to carelessness */
+    if (field->vtype != TYPE_FIELD)
+        return NULL;
+
+    switch (outype)
+    {
+        case TYPE_FLOAT:    op = INSTR_LOAD_F;   break;
+        case TYPE_VECTOR:   op = INSTR_LOAD_V;   break;
+        case TYPE_STRING:   op = INSTR_LOAD_S;   break;
+        case TYPE_FIELD:    op = INSTR_LOAD_FLD; break;
+        case TYPE_ENTITY:   op = INSTR_LOAD_ENT; break;
+        case TYPE_FUNCTION: op = INSTR_LOAD_FNC; break;
+#if 0
+        case TYPE_POINTER: op = INSTR_LOAD_I;   break;
+        case TYPE_INTEGER: op = INSTR_LOAD_I;   break;
+#endif
+        default:
+            irerror(self->context, "invalid type for ir_block_create_load_from_ent: %s", type_name[outype]);
+            return NULL;
+    }
+
+    return ir_block_create_general_instr(self, ctx, label, op, ent, field, outype);
+}
+
+/* PHI resolving breaks the SSA, and must thus be the last
+ * step before life-range calculation.
+ */
+
+static bool ir_block_naive_phi(ir_block *self);
+bool ir_function_naive_phi(ir_function *self)
+{
+    size_t i;
+
+    for (i = 0; i < vec_size(self->blocks); ++i)
+    {
+        if (!ir_block_naive_phi(self->blocks[i]))
+            return false;
+    }
+    return true;
+}
+
+static bool ir_block_naive_phi(ir_block *self)
+{
+    size_t i, p; /*, w;*/
+    /* FIXME: optionally, create_phi can add the phis
+     * to a list so we don't need to loop through blocks
+     * - anyway: "don't optimize YET"
+     */
+    for (i = 0; i < vec_size(self->instr); ++i)
+    {
+        ir_instr *instr = self->instr[i];
+        if (instr->opcode != VINSTR_PHI)
+            continue;
+
+        vec_remove(self->instr, i, 1);
+        --i; /* NOTE: i+1 below */
+
+        for (p = 0; p < vec_size(instr->phi); ++p)
+        {
+            ir_value *v = instr->phi[p].value;
+            ir_block *b = instr->phi[p].from;
+
+            if (v->store == store_value &&
+                vec_size(v->reads) == 1 &&
+                vec_size(v->writes) == 1)
+            {
+                /* replace the value */
+                if (!ir_instr_op(v->writes[0], 0, instr->_ops[0], true))
+                    return false;
+            }
+            else
+            {
+                /* force a move instruction */
+                ir_instr *prevjump = vec_last(b->instr);
+                vec_pop(b->instr);
+                b->final = false;
+                instr->_ops[0]->store = store_global;
+                if (!ir_block_create_store(b, instr->context, instr->_ops[0], v))
+                    return false;
+                instr->_ops[0]->store = store_value;
+                vec_push(b->instr, prevjump);
+                b->final = true;
+            }
+        }
+        ir_instr_delete(instr);
+    }
+    return true;
+}
+
+/***********************************************************************
+ *IR Temp allocation code
+ * Propagating value life ranges by walking through the function backwards
+ * until no more changes are made.
+ * In theory this should happen once more than once for every nested loop
+ * level.
+ * Though this implementation might run an additional time for if nests.
+ */
+
+/* Enumerate instructions used by value's life-ranges
+ */
+static void ir_block_enumerate(ir_block *self, size_t *_eid)
+{
+    size_t i;
+    size_t eid = *_eid;
+    for (i = 0; i < vec_size(self->instr); ++i)
+    {
+        self->instr[i]->eid = eid++;
+    }
+    *_eid = eid;
+}
+
+/* Enumerate blocks and instructions.
+ * The block-enumeration is unordered!
+ * We do not really use the block enumreation, however
+ * the instruction enumeration is important for life-ranges.
+ */
+void ir_function_enumerate(ir_function *self)
+{
+    size_t i;
+    size_t instruction_id = 0;
+    for (i = 0; i < vec_size(self->blocks); ++i)
+    {
+        /* each block now gets an additional "entry" instruction id
+         * we can use to avoid point-life issues
+         */
+        self->blocks[i]->entry_id = instruction_id;
+        ++instruction_id;
+
+        self->blocks[i]->eid = i;
+        ir_block_enumerate(self->blocks[i], &instruction_id);
+    }
+}
+
+/* Local-value allocator
+ * After finishing creating the liferange of all values used in a function
+ * we can allocate their global-positions.
+ * This is the counterpart to register-allocation in register machines.
+ */
+typedef struct {
+    ir_value **locals;
+    size_t    *sizes;
+    size_t    *positions;
+    bool      *unique;
+} function_allocator;
+
+static bool function_allocator_alloc(function_allocator *alloc, ir_value *var)
+{
+    ir_value *slot;
+    size_t vsize = ir_value_sizeof(var);
+
+    var->code.local = vec_size(alloc->locals);
+
+    slot = ir_value_var("reg", store_global, var->vtype);
+    if (!slot)
+        return false;
+
+    if (!ir_value_life_merge_into(slot, var))
+        goto localerror;
+
+    vec_push(alloc->locals, slot);
+    vec_push(alloc->sizes, vsize);
+    vec_push(alloc->unique, var->unique_life);
+
+    return true;
+
+localerror:
+    ir_value_delete(slot);
+    return false;
+}
+
+static bool ir_function_allocator_assign(ir_function *self, function_allocator *alloc, ir_value *v)
+{
+    size_t a;
+    ir_value *slot;
+
+    if (v->unique_life)
+        return function_allocator_alloc(alloc, v);
+
+    for (a = 0; a < vec_size(alloc->locals); ++a)
+    {
+        /* if it's reserved for a unique liferange: skip */
+        if (alloc->unique[a])
+            continue;
+
+        slot = alloc->locals[a];
+
+        /* never resize parameters
+         * will be required later when overlapping temps + locals
+         */
+        if (a < vec_size(self->params) &&
+            alloc->sizes[a] < ir_value_sizeof(v))
+        {
+            continue;
+        }
+
+        if (ir_values_overlap(v, slot))
+            continue;
+
+        if (!ir_value_life_merge_into(slot, v))
+            return false;
+
+        /* adjust size for this slot */
+        if (alloc->sizes[a] < ir_value_sizeof(v))
+            alloc->sizes[a] = ir_value_sizeof(v);
+
+        v->code.local = a;
+        return true;
+    }
+    if (a >= vec_size(alloc->locals)) {
+        if (!function_allocator_alloc(alloc, v))
+            return false;
+    }
+    return true;
+}
+
+bool ir_function_allocate_locals(ir_function *self)
+{
+    size_t i;
+    bool   retval = true;
+    size_t pos;
+    bool   opt_gt = OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS);
+
+    ir_value *v;
+
+    function_allocator lockalloc, globalloc;
+
+    if (!vec_size(self->locals) && !vec_size(self->values))
+        return true;
+
+    globalloc.locals    = NULL;
+    globalloc.sizes     = NULL;
+    globalloc.positions = NULL;
+    globalloc.unique    = NULL;
+    lockalloc.locals    = NULL;
+    lockalloc.sizes     = NULL;
+    lockalloc.positions = NULL;
+    lockalloc.unique    = NULL;
+
+    for (i = 0; i < vec_size(self->locals); ++i)
+    {
+        v = self->locals[i];
+        if ((self->flags & IR_FLAG_MASK_NO_LOCAL_TEMPS) || !OPTS_OPTIMIZATION(OPTIM_LOCAL_TEMPS)) {
+            v->locked      = true;
+            v->unique_life = true;
+        }
+        else if (i >= vec_size(self->params))
+            break;
+        else
+            v->locked = true; /* lock parameters locals */
+        if (!function_allocator_alloc((v->locked || !opt_gt ? &lockalloc : &globalloc), v))
+            goto error;
+    }
+    for (; i < vec_size(self->locals); ++i)
+    {
+        v = self->locals[i];
+        if (!vec_size(v->life))
+            continue;
+        if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v))
+            goto error;
+    }
+
+    /* Allocate a slot for any value that still exists */
+    for (i = 0; i < vec_size(self->values); ++i)
+    {
+        v = self->values[i];
+
+        if (!vec_size(v->life))
+            continue;
+
+        /* CALL optimization:
+         * If the value is a parameter-temp: 1 write, 1 read from a CALL
+         * and it's not "locked", write it to the OFS_PARM directly.
+         */
+        if (OPTS_OPTIMIZATION(OPTIM_CALL_STORES) && !v->locked && !v->unique_life) {
+            if (vec_size(v->reads) == 1 && vec_size(v->writes) == 1 &&
+                (v->reads[0]->opcode == VINSTR_NRCALL ||
+                 (v->reads[0]->opcode >= INSTR_CALL0 && v->reads[0]->opcode <= INSTR_CALL8)
+                )
+               )
+            {
+                size_t    param;
+                ir_instr *call = v->reads[0];
+                if (!vec_ir_value_find(call->params, v, &param)) {
+                    irerror(call->context, "internal error: unlocked parameter %s not found", v->name);
+                    goto error;
+                }
+                ++opts_optimizationcount[OPTIM_CALL_STORES];
+                v->callparam = true;
+                if (param < 8)
+                    ir_value_code_setaddr(v, OFS_PARM0 + 3*param);
+                else {
+                    size_t nprotos = vec_size(self->owner->extparam_protos);
+                    ir_value *ep;
+                    param -= 8;
+                    if (nprotos > param)
+                        ep = self->owner->extparam_protos[param];
+                    else
+                    {
+                        ep = ir_gen_extparam_proto(self->owner);
+                        while (++nprotos <= param)
+                            ep = ir_gen_extparam_proto(self->owner);
+                    }
+                    ir_instr_op(v->writes[0], 0, ep, true);
+                    call->params[param+8] = ep;
+                }
+                continue;
+            }
+            if (vec_size(v->writes) == 1 && v->writes[0]->opcode == INSTR_CALL0)
+            {
+                v->store = store_return;
+                if (v->members[0]) v->members[0]->store = store_return;
+                if (v->members[1]) v->members[1]->store = store_return;
+                if (v->members[2]) v->members[2]->store = store_return;
+                ++opts_optimizationcount[OPTIM_CALL_STORES];
+                continue;
+            }
+        }
+
+        if (!ir_function_allocator_assign(self, (v->locked || !opt_gt ? &lockalloc : &globalloc), v))
+            goto error;
+    }
+
+    if (!lockalloc.sizes && !globalloc.sizes) {
+        goto cleanup;
+    }
+    vec_push(lockalloc.positions, 0);
+    vec_push(globalloc.positions, 0);
+
+    /* Adjust slot positions based on sizes */
+    if (lockalloc.sizes) {
+        pos = (vec_size(lockalloc.sizes) ? lockalloc.positions[0] : 0);
+        for (i = 1; i < vec_size(lockalloc.sizes); ++i)
+        {
+            pos = lockalloc.positions[i-1] + lockalloc.sizes[i-1];
+            vec_push(lockalloc.positions, pos);
+        }
+        self->allocated_locals = pos + vec_last(lockalloc.sizes);
+    }
+    if (globalloc.sizes) {
+        pos = (vec_size(globalloc.sizes) ? globalloc.positions[0] : 0);
+        for (i = 1; i < vec_size(globalloc.sizes); ++i)
+        {
+            pos = globalloc.positions[i-1] + globalloc.sizes[i-1];
+            vec_push(globalloc.positions, pos);
+        }
+        self->globaltemps = pos + vec_last(globalloc.sizes);
+    }
+
+    /* Locals need to know their new position */
+    for (i = 0; i < vec_size(self->locals); ++i) {
+        v = self->locals[i];
+        if (v->locked || !opt_gt)
+            v->code.local = lockalloc.positions[v->code.local];
+        else
+            v->code.local = globalloc.positions[v->code.local];
+    }
+    /* Take over the actual slot positions on values */
+    for (i = 0; i < vec_size(self->values); ++i) {
+        v = self->values[i];
+        if (v->locked || !opt_gt)
+            v->code.local = lockalloc.positions[v->code.local];
+        else
+            v->code.local = globalloc.positions[v->code.local];
+    }
+
+    goto cleanup;
+
+error:
+    retval = false;
+cleanup:
+    for (i = 0; i < vec_size(lockalloc.locals); ++i)
+        ir_value_delete(lockalloc.locals[i]);
+    for (i = 0; i < vec_size(globalloc.locals); ++i)
+        ir_value_delete(globalloc.locals[i]);
+    vec_free(globalloc.unique);
+    vec_free(globalloc.locals);
+    vec_free(globalloc.sizes);
+    vec_free(globalloc.positions);
+    vec_free(lockalloc.unique);
+    vec_free(lockalloc.locals);
+    vec_free(lockalloc.sizes);
+    vec_free(lockalloc.positions);
+    return retval;
+}
+
+/* Get information about which operand
+ * is read from, or written to.
+ */
+static void ir_op_read_write(int op, size_t *read, size_t *write)
+{
+    switch (op)
+    {
+    case VINSTR_JUMP:
+    case INSTR_GOTO:
+        *write = 0;
+        *read = 0;
+        break;
+    case INSTR_IF:
+    case INSTR_IFNOT:
+#if 0
+    case INSTR_IF_S:
+    case INSTR_IFNOT_S:
+#endif
+    case INSTR_RETURN:
+    case VINSTR_COND:
+        *write = 0;
+        *read = 1;
+        break;
+    case INSTR_STOREP_F:
+    case INSTR_STOREP_V:
+    case INSTR_STOREP_S:
+    case INSTR_STOREP_ENT:
+    case INSTR_STOREP_FLD:
+    case INSTR_STOREP_FNC:
+        *write = 0;
+        *read  = 7;
+        break;
+    default:
+        *write = 1;
+        *read = 6;
+        break;
+    };
+}
+
+static bool ir_block_living_add_instr(ir_block *self, size_t eid)
+{
+    size_t       i;
+    const size_t vs = vec_size(self->living);
+    bool         changed = false;
+    for (i = 0; i != vs; ++i)
+    {
+        if (ir_value_life_merge(self->living[i], eid))
+            changed = true;
+    }
+    return changed;
+}
+
+static bool ir_block_living_lock(ir_block *self)
+{
+    size_t i;
+    bool changed = false;
+    for (i = 0; i != vec_size(self->living); ++i)
+    {
+        if (!self->living[i]->locked) {
+            self->living[i]->locked = true;
+            changed = true;
+        }
+    }
+    return changed;
+}
+
+static bool ir_block_life_propagate(ir_block *self, bool *changed)
+{
+    ir_instr *instr;
+    ir_value *value;
+    size_t i, o, p, mem, cnt;
+    /* bitmasks which operands are read from or written to */
+    size_t read, write;
+    char dbg_ind[16];
+    dbg_ind[0] = '#';
+    dbg_ind[1] = '0';
+    (void)dbg_ind;
+
+    vec_free(self->living);
+
+    p = vec_size(self->exits);
+    for (i = 0; i < p; ++i) {
+        ir_block *prev = self->exits[i];
+        cnt = vec_size(prev->living);
+        for (o = 0; o < cnt; ++o) {
+            if (!vec_ir_value_find(self->living, prev->living[o], NULL))
+                vec_push(self->living, prev->living[o]);
+        }
+    }
+
+    i = vec_size(self->instr);
+    while (i)
+    { --i;
+        instr = self->instr[i];
+
+        /* See which operands are read and write operands */
+        ir_op_read_write(instr->opcode, &read, &write);
+
+        /* Go through the 3 main operands
+         * writes first, then reads
+         */
+        for (o = 0; o < 3; ++o)
+        {
+            if (!instr->_ops[o]) /* no such operand */
+                continue;
+
+            value = instr->_ops[o];
+
+            /* We only care about locals */
+            /* we also calculate parameter liferanges so that locals
+             * can take up parameter slots */
+            if (value->store != store_value &&
+                value->store != store_local &&
+                value->store != store_param)
+                continue;
+
+            /* write operands */
+            /* When we write to a local, we consider it "dead" for the
+             * remaining upper part of the function, since in SSA a value
+             * can only be written once (== created)
+             */
+            if (write & (1<<o))
+            {
+                size_t idx;
+                bool in_living = vec_ir_value_find(self->living, value, &idx);
+                if (!in_living)
+                {
+                    /* If the value isn't alive it hasn't been read before... */
+                    /* TODO: See if the warning can be emitted during parsing or AST processing
+                     * otherwise have warning printed here.
+                     * IF printing a warning here: include filecontext_t,
+                     * and make sure it's only printed once
+                     * since this function is run multiple times.
+                     */
+                    /* con_err( "Value only written %s\n", value->name); */
+                    if (ir_value_life_merge(value, instr->eid))
+                        *changed = true;
+                } else {
+                    /* since 'living' won't contain it
+                     * anymore, merge the value, since
+                     * (A) doesn't.
+                     */
+                    if (ir_value_life_merge(value, instr->eid))
+                        *changed = true;
+                    /* Then remove */
+                    vec_remove(self->living, idx, 1);
+                }
+                /* Removing a vector removes all members */
+                for (mem = 0; mem < 3; ++mem) {
+                    if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], &idx)) {
+                        if (ir_value_life_merge(value->members[mem], instr->eid))
+                            *changed = true;
+                        vec_remove(self->living, idx, 1);
+                    }
+                }
+                /* Removing the last member removes the vector */
+                if (value->memberof) {
+                    value = value->memberof;
+                    for (mem = 0; mem < 3; ++mem) {
+                        if (value->members[mem] && vec_ir_value_find(self->living, value->members[mem], NULL))
+                            break;
+                    }
+                    if (mem == 3 && vec_ir_value_find(self->living, value, &idx)) {
+                        if (ir_value_life_merge(value, instr->eid))
+                            *changed = true;
+                        vec_remove(self->living, idx, 1);
+                    }
+                }
+            }
+        }
+
+        /* These operations need a special case as they can break when using
+         * same source and destination operand otherwise, as the engine may
+         * read the source multiple times. */
+        if (instr->opcode == INSTR_MUL_VF ||
+            instr->opcode == VINSTR_BITAND_VF ||
+            instr->opcode == VINSTR_BITOR_VF ||
+            instr->opcode == VINSTR_BITXOR ||
+            instr->opcode == VINSTR_BITXOR_VF ||
+            instr->opcode == VINSTR_BITXOR_V ||
+            instr->opcode == VINSTR_CROSS)
+        {
+            value = instr->_ops[2];
+            /* the float source will get an additional lifetime */
+            if (ir_value_life_merge(value, instr->eid+1))
+                *changed = true;
+            if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1))
+                *changed = true;
+        }
+
+        if (instr->opcode == INSTR_MUL_FV ||
+            instr->opcode == INSTR_LOAD_V ||
+            instr->opcode == VINSTR_BITXOR ||
+            instr->opcode == VINSTR_BITXOR_VF ||
+            instr->opcode == VINSTR_BITXOR_V ||
+            instr->opcode == VINSTR_CROSS)
+        {
+            value = instr->_ops[1];
+            /* the float source will get an additional lifetime */
+            if (ir_value_life_merge(value, instr->eid+1))
+                *changed = true;
+            if (value->memberof && ir_value_life_merge(value->memberof, instr->eid+1))
+                *changed = true;
+        }
+
+        for (o = 0; o < 3; ++o)
+        {
+            if (!instr->_ops[o]) /* no such operand */
+                continue;
+
+            value = instr->_ops[o];
+
+            /* We only care about locals */
+            /* we also calculate parameter liferanges so that locals
+             * can take up parameter slots */
+            if (value->store != store_value &&
+                value->store != store_local &&
+                value->store != store_param)
+                continue;
+
+            /* read operands */
+            if (read & (1<<o))
+            {
+                if (!vec_ir_value_find(self->living, value, NULL))
+                    vec_push(self->living, value);
+                /* reading adds the full vector */
+                if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
+                    vec_push(self->living, value->memberof);
+                for (mem = 0; mem < 3; ++mem) {
+                    if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
+                        vec_push(self->living, value->members[mem]);
+                }
+            }
+        }
+        /* PHI operands are always read operands */
+        for (p = 0; p < vec_size(instr->phi); ++p)
+        {
+            value = instr->phi[p].value;
+            if (!vec_ir_value_find(self->living, value, NULL))
+                vec_push(self->living, value);
+            /* reading adds the full vector */
+            if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
+                vec_push(self->living, value->memberof);
+            for (mem = 0; mem < 3; ++mem) {
+                if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
+                    vec_push(self->living, value->members[mem]);
+            }
+        }
+
+        /* on a call, all these values must be "locked" */
+        if (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8) {
+            if (ir_block_living_lock(self))
+                *changed = true;
+        }
+        /* call params are read operands too */
+        for (p = 0; p < vec_size(instr->params); ++p)
+        {
+            value = instr->params[p];
+            if (!vec_ir_value_find(self->living, value, NULL))
+                vec_push(self->living, value);
+            /* reading adds the full vector */
+            if (value->memberof && !vec_ir_value_find(self->living, value->memberof, NULL))
+                vec_push(self->living, value->memberof);
+            for (mem = 0; mem < 3; ++mem) {
+                if (value->members[mem] && !vec_ir_value_find(self->living, value->members[mem], NULL))
+                    vec_push(self->living, value->members[mem]);
+            }
+        }
+
+        /* (A) */
+        if (ir_block_living_add_instr(self, instr->eid))
+            *changed = true;
+    }
+    /* the "entry" instruction ID */
+    if (ir_block_living_add_instr(self, self->entry_id))
+        *changed = true;
+
+    return true;
+}
+
+bool ir_function_calculate_liferanges(ir_function *self)
+{
+    size_t i, s;
+    bool changed;
+
+    /* parameters live at 0 */
+    for (i = 0; i < vec_size(self->params); ++i)
+        if (!ir_value_life_merge(self->locals[i], 0))
+            compile_error(self->context, "internal error: failed value-life merging");
+
+    do {
+        self->run_id++;
+        changed = false;
+        i = vec_size(self->blocks);
+        while (i--) {
+            ir_block_life_propagate(self->blocks[i], &changed);
+        }
+    } while (changed);
+
+    if (vec_size(self->blocks)) {
+        ir_block *block = self->blocks[0];
+        for (i = 0; i < vec_size(block->living); ++i) {
+            ir_value *v = block->living[i];
+            if (v->store != store_local)
+                continue;
+            if (v->vtype == TYPE_VECTOR)
+                continue;
+            self->flags |= IR_FLAG_HAS_UNINITIALIZED;
+            /* find the instruction reading from it */
+            for (s = 0; s < vec_size(v->reads); ++s) {
+                if (v->reads[s]->eid == v->life[0].end)
+                    break;
+            }
+            if (s < vec_size(v->reads)) {
+                if (irwarning(v->context, WARN_USED_UNINITIALIZED,
+                              "variable `%s` may be used uninitialized in this function\n"
+                              " -> %s:%i",
+                              v->name,
+                              v->reads[s]->context.file, v->reads[s]->context.line)
+                   )
+                {
+                    return false;
+                }
+                continue;
+            }
+            if (v->memberof) {
+                ir_value *vec = v->memberof;
+                for (s = 0; s < vec_size(vec->reads); ++s) {
+                    if (vec->reads[s]->eid == v->life[0].end)
+                        break;
+                }
+                if (s < vec_size(vec->reads)) {
+                    if (irwarning(v->context, WARN_USED_UNINITIALIZED,
+                                  "variable `%s` may be used uninitialized in this function\n"
+                                  " -> %s:%i",
+                                  v->name,
+                                  vec->reads[s]->context.file, vec->reads[s]->context.line)
+                       )
+                    {
+                        return false;
+                    }
+                    continue;
+                }
+            }
+            if (irwarning(v->context, WARN_USED_UNINITIALIZED,
+                          "variable `%s` may be used uninitialized in this function", v->name))
+            {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+/***********************************************************************
+ *IR Code-Generation
+ *
+ * Since the IR has the convention of putting 'write' operands
+ * at the beginning, we have to rotate the operands of instructions
+ * properly in order to generate valid QCVM code.
+ *
+ * Having destinations at a fixed position is more convenient. In QC
+ * this is *mostly* OPC,  but FTE adds at least 2 instructions which
+ * read from from OPA,  and store to OPB rather than OPC.   Which is
+ * partially the reason why the implementation of these instructions
+ * in darkplaces has been delayed for so long.
+ *
+ * Breaking conventions is annoying...
+ */
+static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal);
+
+static bool gen_global_field(code_t *code, ir_value *global)
+{
+    if (global->hasvalue)
+    {
+        ir_value *fld = global->constval.vpointer;
+        if (!fld) {
+            irerror(global->context, "Invalid field constant with no field: %s", global->name);
+            return false;
+        }
+
+        /* copy the field's value */
+        ir_value_code_setaddr(global, vec_size(code->globals));
+        vec_push(code->globals, fld->code.fieldaddr);
+        if (global->fieldtype == TYPE_VECTOR) {
+            vec_push(code->globals, fld->code.fieldaddr+1);
+            vec_push(code->globals, fld->code.fieldaddr+2);
+        }
+    }
+    else
+    {
+        ir_value_code_setaddr(global, vec_size(code->globals));
+        vec_push(code->globals, 0);
+        if (global->fieldtype == TYPE_VECTOR) {
+            vec_push(code->globals, 0);
+            vec_push(code->globals, 0);
+        }
+    }
+    if (global->code.globaladdr < 0)
+        return false;
+    return true;
+}
+
+static bool gen_global_pointer(code_t *code, ir_value *global)
+{
+    if (global->hasvalue)
+    {
+        ir_value *target = global->constval.vpointer;
+        if (!target) {
+            irerror(global->context, "Invalid pointer constant: %s", global->name);
+            /* NULL pointers are pointing to the NULL constant, which also
+             * sits at address 0, but still has an ir_value for itself.
+             */
+            return false;
+        }
+
+        /* Here, relocations ARE possible - in fteqcc-enhanced-qc:
+         * void() foo; <- proto
+         * void() *fooptr = &foo;
+         * void() foo = { code }
+         */
+        if (!target->code.globaladdr) {
+            /* FIXME: Check for the constant nullptr ir_value!
+             * because then code.globaladdr being 0 is valid.
+             */
+            irerror(global->context, "FIXME: Relocation support");
+            return false;
+        }
+
+        ir_value_code_setaddr(global, vec_size(code->globals));
+        vec_push(code->globals, target->code.globaladdr);
+    }
+    else
+    {
+        ir_value_code_setaddr(global, vec_size(code->globals));
+        vec_push(code->globals, 0);
+    }
+    if (global->code.globaladdr < 0)
+        return false;
+    return true;
+}
+
+static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *block)
+{
+    prog_section_statement_t stmt;
+    ir_instr *instr;
+    ir_block *target;
+    ir_block *ontrue;
+    ir_block *onfalse;
+    size_t    stidx;
+    size_t    i;
+    int       j;
+
+    block->generated = true;
+    block->code_start = vec_size(code->statements);
+    for (i = 0; i < vec_size(block->instr); ++i)
+    {
+        instr = block->instr[i];
+
+        if (instr->opcode == VINSTR_PHI) {
+            irerror(block->context, "cannot generate virtual instruction (phi)");
+            return false;
+        }
+
+        if (instr->opcode == VINSTR_JUMP) {
+            target = instr->bops[0];
+            /* for uncoditional jumps, if the target hasn't been generated
+             * yet, we generate them right here.
+             */
+            if (!target->generated)
+                return gen_blocks_recursive(code, func, target);
+
+            /* otherwise we generate a jump instruction */
+            stmt.opcode = INSTR_GOTO;
+            stmt.o1.s1 = (target->code_start) - vec_size(code->statements);
+            stmt.o2.s1 = 0;
+            stmt.o3.s1 = 0;
+            if (stmt.o1.s1 != 1)
+                code_push_statement(code, &stmt, instr->context);
+
+            /* no further instructions can be in this block */
+            return true;
+        }
+
+        if (instr->opcode == VINSTR_BITXOR) {
+            stmt.opcode = INSTR_BITOR;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+            stmt.opcode = INSTR_BITAND;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+            stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
+            code_push_statement(code, &stmt, instr->context);
+            stmt.opcode = INSTR_SUB_F;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
+            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_BITAND_V) {
+            stmt.opcode = INSTR_BITAND;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_BITOR_V) {
+            stmt.opcode = INSTR_BITOR;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o2.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_BITXOR_V) {
+            for (j = 0; j < 3; ++j) {
+                stmt.opcode = INSTR_BITOR;
+                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
+                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j;
+                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
+                code_push_statement(code, &stmt, instr->context);
+                stmt.opcode = INSTR_BITAND;
+                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
+                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j;
+                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
+                code_push_statement(code, &stmt, instr->context);
+            }
+            stmt.opcode = INSTR_SUB_V;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
+            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_BITAND_VF) {
+            stmt.opcode = INSTR_BITAND;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_BITOR_VF) {
+            stmt.opcode = INSTR_BITOR;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+            ++stmt.o1.s1;
+            ++stmt.o3.s1;
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_BITXOR_VF) {
+            for (j = 0; j < 3; ++j) {
+                stmt.opcode = INSTR_BITOR;
+                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
+                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
+                code_push_statement(code, &stmt, instr->context);
+                stmt.opcode = INSTR_BITAND;
+                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j;
+                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]);
+                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
+                code_push_statement(code, &stmt, instr->context);
+            }
+            stmt.opcode = INSTR_SUB_V;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
+            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_CROSS) {
+            stmt.opcode = INSTR_MUL_F;
+            for (j = 0; j < 3; ++j) {
+                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 1) % 3;
+                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 2) % 3;
+                stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j;
+                code_push_statement(code, &stmt, instr->context);
+                stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + (j + 2) % 3;
+                stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + (j + 1) % 3;
+                stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j;
+                code_push_statement(code, &stmt, instr->context);
+            }
+            stmt.opcode = INSTR_SUB_V;
+            stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]);
+            stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]);
+            stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]);
+            code_push_statement(code, &stmt, instr->context);
+
+            /* instruction generated */
+            continue;
+        }
+
+        if (instr->opcode == VINSTR_COND) {
+            ontrue  = instr->bops[0];
+            onfalse = instr->bops[1];
+            /* TODO: have the AST signal which block should
+             * come first: eg. optimize IFs without ELSE...
+             */
+
+            stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
+            stmt.o2.u1 = 0;
+            stmt.o3.s1 = 0;
+
+            if (ontrue->generated) {
+                stmt.opcode = INSTR_IF;
+                stmt.o2.s1 = (ontrue->code_start) - vec_size(code->statements);
+                if (stmt.o2.s1 != 1)
+                    code_push_statement(code, &stmt, instr->context);
+            }
+            if (onfalse->generated) {
+                stmt.opcode = INSTR_IFNOT;
+                stmt.o2.s1 = (onfalse->code_start) - vec_size(code->statements);
+                if (stmt.o2.s1 != 1)
+                    code_push_statement(code, &stmt, instr->context);
+            }
+            if (!ontrue->generated) {
+                if (onfalse->generated)
+                    return gen_blocks_recursive(code, func, ontrue);
+            }
+            if (!onfalse->generated) {
+                if (ontrue->generated)
+                    return gen_blocks_recursive(code, func, onfalse);
+            }
+            /* neither ontrue nor onfalse exist */
+            stmt.opcode = INSTR_IFNOT;
+            if (!instr->likely) {
+                /* Honor the likelyhood hint */
+                ir_block *tmp = onfalse;
+                stmt.opcode = INSTR_IF;
+                onfalse = ontrue;
+                ontrue = tmp;
+            }
+            stidx = vec_size(code->statements);
+            code_push_statement(code, &stmt, instr->context);
+            /* on false we jump, so add ontrue-path */
+            if (!gen_blocks_recursive(code, func, ontrue))
+                return false;
+            /* fixup the jump address */
+            code->statements[stidx].o2.s1 = vec_size(code->statements) - stidx;
+            /* generate onfalse path */
+            if (onfalse->generated) {
+                /* fixup the jump address */
+                code->statements[stidx].o2.s1 = (onfalse->code_start) - (stidx);
+                if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) {
+                    code->statements[stidx] = code->statements[stidx+1];
+                    if (code->statements[stidx].o1.s1 < 0)
+                        code->statements[stidx].o1.s1++;
+                    code_pop_statement(code);
+                }
+                stmt.opcode = vec_last(code->statements).opcode;
+                if (stmt.opcode == INSTR_GOTO ||
+                    stmt.opcode == INSTR_IF ||
+                    stmt.opcode == INSTR_IFNOT ||
+                    stmt.opcode == INSTR_RETURN ||
+                    stmt.opcode == INSTR_DONE)
+                {
+                    /* no use jumping from here */
+                    return true;
+                }
+                /* may have been generated in the previous recursive call */
+                stmt.opcode = INSTR_GOTO;
+                stmt.o1.s1 = (onfalse->code_start) - vec_size(code->statements);
+                stmt.o2.s1 = 0;
+                stmt.o3.s1 = 0;
+                if (stmt.o1.s1 != 1)
+                    code_push_statement(code, &stmt, instr->context);
+                return true;
+            }
+            else if (stidx+2 == vec_size(code->statements) && code->statements[stidx].o2.s1 == 1) {
+                code->statements[stidx] = code->statements[stidx+1];
+                if (code->statements[stidx].o1.s1 < 0)
+                    code->statements[stidx].o1.s1++;
+                code_pop_statement(code);
+            }
+            /* if not, generate now */
+            return gen_blocks_recursive(code, func, onfalse);
+        }
+
+        if ( (instr->opcode >= INSTR_CALL0 && instr->opcode <= INSTR_CALL8)
+           || instr->opcode == VINSTR_NRCALL)
+        {
+            size_t p, first;
+            ir_value *retvalue;
+
+            first = vec_size(instr->params);
+            if (first > 8)
+                first = 8;
+            for (p = 0; p < first; ++p)
+            {
+                ir_value *param = instr->params[p];
+                if (param->callparam)
+                    continue;
+
+                stmt.opcode = INSTR_STORE_F;
+                stmt.o3.u1 = 0;
+
+                if (param->vtype == TYPE_FIELD)
+                    stmt.opcode = field_store_instr[param->fieldtype];
+                else if (param->vtype == TYPE_NIL)
+                    stmt.opcode = INSTR_STORE_V;
+                else
+                    stmt.opcode = type_store_instr[param->vtype];
+                stmt.o1.u1 = ir_value_code_addr(param);
+                stmt.o2.u1 = OFS_PARM0 + 3 * p;
+
+                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
+                    /* fetch 3 separate floats */
+                    stmt.opcode = INSTR_STORE_F;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
+                    code_push_statement(code, &stmt, instr->context);
+                }
+                else
+                    code_push_statement(code, &stmt, instr->context);
+            }
+            /* Now handle extparams */
+            first = vec_size(instr->params);
+            for (; p < first; ++p)
+            {
+                ir_builder *ir = func->owner;
+                ir_value *param = instr->params[p];
+                ir_value *targetparam;
+
+                if (param->callparam)
+                    continue;
+
+                if (p-8 >= vec_size(ir->extparams))
+                    ir_gen_extparam(ir);
+
+                targetparam = ir->extparams[p-8];
+
+                stmt.opcode = INSTR_STORE_F;
+                stmt.o3.u1 = 0;
+
+                if (param->vtype == TYPE_FIELD)
+                    stmt.opcode = field_store_instr[param->fieldtype];
+                else if (param->vtype == TYPE_NIL)
+                    stmt.opcode = INSTR_STORE_V;
+                else
+                    stmt.opcode = type_store_instr[param->vtype];
+                stmt.o1.u1 = ir_value_code_addr(param);
+                stmt.o2.u1 = ir_value_code_addr(targetparam);
+                if (param->vtype == TYPE_VECTOR && (param->flags & IR_FLAG_SPLIT_VECTOR)) {
+                    /* fetch 3 separate floats */
+                    stmt.opcode = INSTR_STORE_F;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[0]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[1]);
+                    code_push_statement(code, &stmt, instr->context);
+                    stmt.o2.u1++;
+                    stmt.o1.u1 = ir_value_code_addr(param->members[2]);
+                    code_push_statement(code, &stmt, instr->context);
+                }
+                else
+                    code_push_statement(code, &stmt, instr->context);
+            }
+
+            stmt.opcode = INSTR_CALL0 + vec_size(instr->params);
+            if (stmt.opcode > INSTR_CALL8)
+                stmt.opcode = INSTR_CALL8;
+            stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o2.u1 = 0;
+            stmt.o3.u1 = 0;
+            code_push_statement(code, &stmt, instr->context);
+
+            retvalue = instr->_ops[0];
+            if (retvalue && retvalue->store != store_return &&
+                (retvalue->store == store_global || vec_size(retvalue->life)))
+            {
+                /* not to be kept in OFS_RETURN */
+                if (retvalue->vtype == TYPE_FIELD && OPTS_FLAG(ADJUST_VECTOR_FIELDS))
+                    stmt.opcode = field_store_instr[retvalue->fieldtype];
+                else
+                    stmt.opcode = type_store_instr[retvalue->vtype];
+                stmt.o1.u1 = OFS_RETURN;
+                stmt.o2.u1 = ir_value_code_addr(retvalue);
+                stmt.o3.u1 = 0;
+                code_push_statement(code, &stmt, instr->context);
+            }
+            continue;
+        }
+
+        if (instr->opcode == INSTR_STATE) {
+            stmt.opcode = instr->opcode;
+            if (instr->_ops[0])
+                stmt.o1.u1 = ir_value_code_addr(instr->_ops[0]);
+            if (instr->_ops[1])
+                stmt.o2.u1 = ir_value_code_addr(instr->_ops[1]);
+            stmt.o3.u1 = 0;
+            code_push_statement(code, &stmt, instr->context);
+            continue;
+        }
+
+        stmt.opcode = instr->opcode;
+        stmt.o1.u1 = 0;
+        stmt.o2.u1 = 0;
+        stmt.o3.u1 = 0;
+
+        /* This is the general order of operands */
+        if (instr->_ops[0])
+            stmt.o3.u1 = ir_value_code_addr(instr->_ops[0]);
+
+        if (instr->_ops[1])
+            stmt.o1.u1 = ir_value_code_addr(instr->_ops[1]);
+
+        if (instr->_ops[2])
+            stmt.o2.u1 = ir_value_code_addr(instr->_ops[2]);
+
+        if (stmt.opcode == INSTR_RETURN || stmt.opcode == INSTR_DONE)
+        {
+            stmt.o1.u1 = stmt.o3.u1;
+            stmt.o3.u1 = 0;
+        }
+        else if ((stmt.opcode >= INSTR_STORE_F &&
+                  stmt.opcode <= INSTR_STORE_FNC) ||
+                 (stmt.opcode >= INSTR_STOREP_F &&
+                  stmt.opcode <= INSTR_STOREP_FNC))
+        {
+            /* 2-operand instructions with A -> B */
+            stmt.o2.u1 = stmt.o3.u1;
+            stmt.o3.u1 = 0;
+
+            /* tiny optimization, don't output
+             * STORE a, a
+             */
+            if (stmt.o2.u1 == stmt.o1.u1 &&
+                OPTS_OPTIMIZATION(OPTIM_PEEPHOLE))
+            {
+                ++opts_optimizationcount[OPTIM_PEEPHOLE];
+                continue;
+            }
+        }
+        code_push_statement(code, &stmt, instr->context);
+    }
+    return true;
+}
+
+static bool gen_function_code(code_t *code, ir_function *self)
+{
+    ir_block *block;
+    prog_section_statement_t stmt, *retst;
+
+    /* Starting from entry point, we generate blocks "as they come"
+     * for now. Dead blocks will not be translated obviously.
+     */
+    if (!vec_size(self->blocks)) {
+        irerror(self->context, "Function '%s' declared without body.", self->name);
+        return false;
+    }
+
+    block = self->blocks[0];
+    if (block->generated)
+        return true;
+
+    if (!gen_blocks_recursive(code, self, block)) {
+        irerror(self->context, "failed to generate blocks for '%s'", self->name);
+        return false;
+    }
+
+    /* code_write and qcvm -disasm need to know that the function ends here */
+    retst = &vec_last(code->statements);
+    if (OPTS_OPTIMIZATION(OPTIM_VOID_RETURN) &&
+        self->outtype == TYPE_VOID &&
+        retst->opcode == INSTR_RETURN &&
+        !retst->o1.u1 && !retst->o2.u1 && !retst->o3.u1)
+    {
+        retst->opcode = INSTR_DONE;
+        ++opts_optimizationcount[OPTIM_VOID_RETURN];
+    } else {
+        lex_ctx_t last;
+
+        stmt.opcode = INSTR_DONE;
+        stmt.o1.u1  = 0;
+        stmt.o2.u1  = 0;
+        stmt.o3.u1  = 0;
+        last.line   = vec_last(code->linenums);
+        last.column = vec_last(code->columnnums);
+
+        code_push_statement(code, &stmt, last);
+    }
+    return true;
+}
+
+static qcint_t ir_builder_filestring(ir_builder *ir, const char *filename)
+{
+    /* NOTE: filename pointers are copied, we never strdup them,
+     * thus we can use pointer-comparison to find the string.
+     */
+    size_t i;
+    qcint_t  str;
+
+    for (i = 0; i < vec_size(ir->filenames); ++i) {
+        if (ir->filenames[i] == filename)
+            return ir->filestrings[i];
+    }
+
+    str = code_genstring(ir->code, filename);
+    vec_push(ir->filenames, filename);
+    vec_push(ir->filestrings, str);
+    return str;
+}
+
+static bool gen_global_function(ir_builder *ir, ir_value *global)
+{
+    prog_section_function_t fun;
+    ir_function            *irfun;
+
+    size_t i;
+
+    if (!global->hasvalue || (!global->constval.vfunc))
+    {
+        irerror(global->context, "Invalid state of function-global: not constant: %s", global->name);
+        return false;
+    }
+
+    irfun = global->constval.vfunc;
+
+    fun.name    = global->code.name;
+    fun.file    = ir_builder_filestring(ir, global->context.file);
+    fun.profile = 0; /* always 0 */
+    fun.nargs   = vec_size(irfun->params);
+    if (fun.nargs > 8)
+        fun.nargs = 8;
+
+    for (i = 0;i < 8; ++i) {
+        if ((int32_t)i >= fun.nargs)
+            fun.argsize[i] = 0;
+        else
+            fun.argsize[i] = type_sizeof_[irfun->params[i]];
+    }
+
+    fun.firstlocal = 0;
+    fun.locals     = irfun->allocated_locals;
+
+    if (irfun->builtin)
+        fun.entry = irfun->builtin+1;
+    else {
+        irfun->code_function_def = vec_size(ir->code->functions);
+        fun.entry                = vec_size(ir->code->statements);
+    }
+
+    vec_push(ir->code->functions, fun);
+    return true;
+}
+
+static ir_value* ir_gen_extparam_proto(ir_builder *ir)
+{
+    ir_value *global;
+    char      name[128];
+
+    util_snprintf(name, sizeof(name), "EXTPARM#%i", (int)(vec_size(ir->extparam_protos)));
+    global = ir_value_var(name, store_global, TYPE_VECTOR);
+
+    vec_push(ir->extparam_protos, global);
+    return global;
+}
+
+static void ir_gen_extparam(ir_builder *ir)
+{
+    prog_section_def_t def;
+    ir_value          *global;
+
+    if (vec_size(ir->extparam_protos) < vec_size(ir->extparams)+1)
+        global = ir_gen_extparam_proto(ir);
+    else
+        global = ir->extparam_protos[vec_size(ir->extparams)];
+
+    def.name   = code_genstring(ir->code, global->name);
+    def.type   = TYPE_VECTOR;
+    def.offset = vec_size(ir->code->globals);
+
+    vec_push(ir->code->defs, def);
+
+    ir_value_code_setaddr(global, def.offset);
+
+    vec_push(ir->code->globals, 0);
+    vec_push(ir->code->globals, 0);
+    vec_push(ir->code->globals, 0);
+
+    vec_push(ir->extparams, global);
+}
+
+static bool gen_function_extparam_copy(code_t *code, ir_function *self)
+{
+    size_t i, ext, numparams;
+
+    ir_builder *ir = self->owner;
+    ir_value   *ep;
+    prog_section_statement_t stmt;
+
+    numparams = vec_size(self->params);
+    if (!numparams)
+        return true;
+
+    stmt.opcode = INSTR_STORE_F;
+    stmt.o3.s1 = 0;
+    for (i = 8; i < numparams; ++i) {
+        ext = i - 8;
+        if (ext >= vec_size(ir->extparams))
+            ir_gen_extparam(ir);
+
+        ep = ir->extparams[ext];
+
+        stmt.opcode = type_store_instr[self->locals[i]->vtype];
+        if (self->locals[i]->vtype == TYPE_FIELD &&
+            self->locals[i]->fieldtype == TYPE_VECTOR)
+        {
+            stmt.opcode = INSTR_STORE_V;
+        }
+        stmt.o1.u1 = ir_value_code_addr(ep);
+        stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
+        code_push_statement(code, &stmt, self->context);
+    }
+
+    return true;
+}
+
+static bool gen_function_varargs_copy(code_t *code, ir_function *self)
+{
+    size_t i, ext, numparams, maxparams;
+
+    ir_builder *ir = self->owner;
+    ir_value   *ep;
+    prog_section_statement_t stmt;
+
+    numparams = vec_size(self->params);
+    if (!numparams)
+        return true;
+
+    stmt.opcode = INSTR_STORE_V;
+    stmt.o3.s1 = 0;
+    maxparams = numparams + self->max_varargs;
+    for (i = numparams; i < maxparams; ++i) {
+        if (i < 8) {
+            stmt.o1.u1 = OFS_PARM0 + 3*i;
+            stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
+            code_push_statement(code, &stmt, self->context);
+            continue;
+        }
+        ext = i - 8;
+        while (ext >= vec_size(ir->extparams))
+            ir_gen_extparam(ir);
+
+        ep = ir->extparams[ext];
+
+        stmt.o1.u1 = ir_value_code_addr(ep);
+        stmt.o2.u1 = ir_value_code_addr(self->locals[i]);
+        code_push_statement(code, &stmt, self->context);
+    }
+
+    return true;
+}
+
+static bool gen_function_locals(ir_builder *ir, ir_value *global)
+{
+    prog_section_function_t *def;
+    ir_function             *irfun;
+    size_t                   i;
+    uint32_t                 firstlocal, firstglobal;
+
+    irfun = global->constval.vfunc;
+    def   = ir->code->functions + irfun->code_function_def;
+
+    if (OPTS_OPTION_BOOL(OPTION_G) ||
+        !OPTS_OPTIMIZATION(OPTIM_OVERLAP_LOCALS)        ||
+        (irfun->flags & IR_FLAG_MASK_NO_OVERLAP))
+    {
+        firstlocal = def->firstlocal = vec_size(ir->code->globals);
+    } else {
+        firstlocal = def->firstlocal = ir->first_common_local;
+        ++opts_optimizationcount[OPTIM_OVERLAP_LOCALS];
+    }
+
+    firstglobal = (OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS) ? ir->first_common_globaltemp : firstlocal);
+
+    for (i = vec_size(ir->code->globals); i < firstlocal + irfun->allocated_locals; ++i)
+        vec_push(ir->code->globals, 0);
+    for (i = 0; i < vec_size(irfun->locals); ++i) {
+        ir_value *v = irfun->locals[i];
+        if (v->locked || !OPTS_OPTIMIZATION(OPTIM_GLOBAL_TEMPS)) {
+            ir_value_code_setaddr(v, firstlocal + v->code.local);
+            if (!ir_builder_gen_global(ir, irfun->locals[i], true)) {
+                irerror(irfun->locals[i]->context, "failed to generate local %s", irfun->locals[i]->name);
+                return false;
+            }
+        }
+        else
+            ir_value_code_setaddr(v, firstglobal + v->code.local);
+    }
+    for (i = 0; i < vec_size(irfun->values); ++i)
+    {
+        ir_value *v = irfun->values[i];
+        if (v->callparam)
+            continue;
+        if (v->locked)
+            ir_value_code_setaddr(v, firstlocal + v->code.local);
+        else
+            ir_value_code_setaddr(v, firstglobal + v->code.local);
+    }
+    return true;
+}
+
+static bool gen_global_function_code(ir_builder *ir, ir_value *global)
+{
+    prog_section_function_t *fundef;
+    ir_function             *irfun;
+
+    (void)ir;
+
+    irfun = global->constval.vfunc;
+    if (!irfun) {
+        if (global->cvq == CV_NONE) {
+            if (irwarning(global->context, WARN_IMPLICIT_FUNCTION_POINTER,
+                          "function `%s` has no body and in QC implicitly becomes a function-pointer",
+                          global->name))
+            {
+                /* Not bailing out just now. If this happens a lot you don't want to have
+                 * to rerun gmqcc for each such function.
+                 */
+
+                /* return false; */
+            }
+        }
+        /* this was a function pointer, don't generate code for those */
+        return true;
+    }
+
+    if (irfun->builtin)
+        return true;
+
+    /*
+     * If there is no definition and the thing is eraseable, we can ignore
+     * outputting the function to begin with.
+     */
+    if (global->flags & IR_FLAG_ERASABLE && irfun->code_function_def < 0) {
+        return true;
+    }
+
+    if (irfun->code_function_def < 0) {
+        irerror(irfun->context, "`%s`: IR global wasn't generated, failed to access function-def", irfun->name);
+        return false;
+    }
+    fundef = &ir->code->functions[irfun->code_function_def];
+
+    fundef->entry = vec_size(ir->code->statements);
+    if (!gen_function_locals(ir, global)) {
+        irerror(irfun->context, "Failed to generate locals for function %s", irfun->name);
+        return false;
+    }
+    if (!gen_function_extparam_copy(ir->code, irfun)) {
+        irerror(irfun->context, "Failed to generate extparam-copy code for function %s", irfun->name);
+        return false;
+    }
+    if (irfun->max_varargs && !gen_function_varargs_copy(ir->code, irfun)) {
+        irerror(irfun->context, "Failed to generate vararg-copy code for function %s", irfun->name);
+        return false;
+    }
+    if (!gen_function_code(ir->code, irfun)) {
+        irerror(irfun->context, "Failed to generate code for function %s", irfun->name);
+        return false;
+    }
+    return true;
+}
+
+static void gen_vector_defs(code_t *code, prog_section_def_t def, const char *name)
+{
+    char  *component;
+    size_t len, i;
+
+    if (!name || name[0] == '#' || OPTS_FLAG(SINGLE_VECTOR_DEFS))
+        return;
+
+    def.type = TYPE_FLOAT;
+
+    len = strlen(name);
+
+    component = (char*)mem_a(len+3);
+    memcpy(component, name, len);
+    len += 2;
+    component[len-0] = 0;
+    component[len-2] = '_';
+
+    component[len-1] = 'x';
+
+    for (i = 0; i < 3; ++i) {
+        def.name = code_genstring(code, component);
+        vec_push(code->defs, def);
+        def.offset++;
+        component[len-1]++;
+    }
+
+    mem_d(component);
+}
+
+static void gen_vector_fields(code_t *code, prog_section_field_t fld, const char *name)
+{
+    char  *component;
+    size_t len, i;
+
+    if (!name || OPTS_FLAG(SINGLE_VECTOR_DEFS))
+        return;
+
+    fld.type = TYPE_FLOAT;
+
+    len = strlen(name);
+
+    component = (char*)mem_a(len+3);
+    memcpy(component, name, len);
+    len += 2;
+    component[len-0] = 0;
+    component[len-2] = '_';
+
+    component[len-1] = 'x';
+
+    for (i = 0; i < 3; ++i) {
+        fld.name = code_genstring(code, component);
+        vec_push(code->fields, fld);
+        fld.offset++;
+        component[len-1]++;
+    }
+
+    mem_d(component);
+}
+
+static bool ir_builder_gen_global(ir_builder *self, ir_value *global, bool islocal)
+{
+    size_t             i;
+    int32_t           *iptr;
+    prog_section_def_t def;
+    bool               pushdef = opts.optimizeoff;
+
+    /* we don't generate split-vectors */
+    if (global->vtype == TYPE_VECTOR && (global->flags & IR_FLAG_SPLIT_VECTOR))
+        return true;
+
+    def.type   = global->vtype;
+    def.offset = vec_size(self->code->globals);
+    def.name   = 0;
+    if (OPTS_OPTION_BOOL(OPTION_G) || !islocal)
+    {
+        pushdef = true;
+
+        /*
+         * if we're eraseable and the function isn't referenced ignore outputting
+         * the function.
+         */
+        if (global->flags & IR_FLAG_ERASABLE && vec_size(global->reads) == 0) {
+            return true;
+        }
+
+        if (OPTS_OPTIMIZATION(OPTIM_STRIP_CONSTANT_NAMES) &&
+            !(global->flags & IR_FLAG_INCLUDE_DEF) &&
+            (global->name[0] == '#' || global->cvq == CV_CONST))
+        {
+            pushdef = false;
+        }
+
+        if (pushdef) {
+            if (global->name[0] == '#') {
+                if (!self->str_immediate)
+                    self->str_immediate = code_genstring(self->code, "IMMEDIATE");
+                def.name = global->code.name = self->str_immediate;
+            }
+            else
+                def.name = global->code.name = code_genstring(self->code, global->name);
+        }
+        else
+            def.name   = 0;
+        if (islocal) {
+            def.offset = ir_value_code_addr(global);
+            vec_push(self->code->defs, def);
+            if (global->vtype == TYPE_VECTOR)
+                gen_vector_defs(self->code, def, global->name);
+            else if (global->vtype == TYPE_FIELD && global->fieldtype == TYPE_VECTOR)
+                gen_vector_defs(self->code, def, global->name);
+            return true;
+        }
+    }
+    if (islocal)
+        return true;
+
+    switch (global->vtype)
+    {
+    case TYPE_VOID:
+        if (!strcmp(global->name, "end_sys_globals")) {
+            /* TODO: remember this point... all the defs before this one
+             * should be checksummed and added to progdefs.h when we generate it.
+             */
+        }
+        else if (!strcmp(global->name, "end_sys_fields")) {
+            /* TODO: same as above but for entity-fields rather than globsl
+             */
+        }
+        else if(irwarning(global->context, WARN_VOID_VARIABLES, "unrecognized variable of type void `%s`",
+                          global->name))
+        {
+            /* Not bailing out */
+            /* return false; */
+        }
+        /* I'd argue setting it to 0 is sufficient, but maybe some depend on knowing how far
+         * the system fields actually go? Though the engine knows this anyway...
+         * Maybe this could be an -foption
+         * fteqcc creates data for end_sys_* - of size 1, so let's do the same
+         */
+        ir_value_code_setaddr(global, vec_size(self->code->globals));
+        vec_push(self->code->globals, 0);
+        /* Add the def */
+        if (pushdef) vec_push(self->code->defs, def);
+        return true;
+    case TYPE_POINTER:
+        if (pushdef) vec_push(self->code->defs, def);
+        return gen_global_pointer(self->code, global);
+    case TYPE_FIELD:
+        if (pushdef) {
+            vec_push(self->code->defs, def);
+            if (global->fieldtype == TYPE_VECTOR)
+                gen_vector_defs(self->code, def, global->name);
+        }
+        return gen_global_field(self->code, global);
+    case TYPE_ENTITY:
+        /* fall through */
+    case TYPE_FLOAT:
+    {
+        ir_value_code_setaddr(global, vec_size(self->code->globals));
+        if (global->hasvalue) {
+            iptr = (int32_t*)&global->constval.ivec[0];
+            vec_push(self->code->globals, *iptr);
+        } else {
+            vec_push(self->code->globals, 0);
+        }
+        if (!islocal && global->cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+        if (pushdef) vec_push(self->code->defs, def);
+
+        return global->code.globaladdr >= 0;
+    }
+    case TYPE_STRING:
+    {
+        ir_value_code_setaddr(global, vec_size(self->code->globals));
+        if (global->hasvalue) {
+            uint32_t load = code_genstring(self->code, global->constval.vstring);
+            vec_push(self->code->globals, load);
+        } else {
+            vec_push(self->code->globals, 0);
+        }
+        if (!islocal && global->cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+        if (pushdef) vec_push(self->code->defs, def);
+        return global->code.globaladdr >= 0;
+    }
+    case TYPE_VECTOR:
+    {
+        size_t d;
+        ir_value_code_setaddr(global, vec_size(self->code->globals));
+        if (global->hasvalue) {
+            iptr = (int32_t*)&global->constval.ivec[0];
+            vec_push(self->code->globals, iptr[0]);
+            if (global->code.globaladdr < 0)
+                return false;
+            for (d = 1; d < type_sizeof_[global->vtype]; ++d) {
+                vec_push(self->code->globals, iptr[d]);
+            }
+        } else {
+            vec_push(self->code->globals, 0);
+            if (global->code.globaladdr < 0)
+                return false;
+            for (d = 1; d < type_sizeof_[global->vtype]; ++d) {
+                vec_push(self->code->globals, 0);
+            }
+        }
+        if (!islocal && global->cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+
+        if (pushdef) {
+            vec_push(self->code->defs, def);
+            def.type &= ~DEF_SAVEGLOBAL;
+            gen_vector_defs(self->code, def, global->name);
+        }
+        return global->code.globaladdr >= 0;
+    }
+    case TYPE_FUNCTION:
+        ir_value_code_setaddr(global, vec_size(self->code->globals));
+        if (!global->hasvalue) {
+            vec_push(self->code->globals, 0);
+            if (global->code.globaladdr < 0)
+                return false;
+        } else {
+            vec_push(self->code->globals, vec_size(self->code->functions));
+            if (!gen_global_function(self, global))
+                return false;
+        }
+        if (!islocal && global->cvq != CV_CONST)
+            def.type |= DEF_SAVEGLOBAL;
+        if (pushdef) vec_push(self->code->defs, def);
+        return true;
+    case TYPE_VARIANT:
+        /* assume biggest type */
+            ir_value_code_setaddr(global, vec_size(self->code->globals));
+            vec_push(self->code->globals, 0);
+            for (i = 1; i < type_sizeof_[TYPE_VARIANT]; ++i)
+                vec_push(self->code->globals, 0);
+            return true;
+    default:
+        /* refuse to create 'void' type or any other fancy business. */
+        irerror(global->context, "Invalid type for global variable `%s`: %s",
+                global->name, type_name[global->vtype]);
+        return false;
+    }
+}
+
+static GMQCC_INLINE void ir_builder_prepare_field(code_t *code, ir_value *field)
+{
+    field->code.fieldaddr = code_alloc_field(code, type_sizeof_[field->fieldtype]);
+}
+
+static bool ir_builder_gen_field(ir_builder *self, ir_value *field)
+{
+    prog_section_def_t def;
+    prog_section_field_t fld;
+
+    (void)self;
+
+    def.type   = (uint16_t)field->vtype;
+    def.offset = (uint16_t)vec_size(self->code->globals);
+
+    /* create a global named the same as the field */
+    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
+        /* in our standard, the global gets a dot prefix */
+        size_t len = strlen(field->name);
+        char name[1024];
+
+        /* we really don't want to have to allocate this, and 1024
+         * bytes is more than enough for a variable/field name
+         */
+        if (len+2 >= sizeof(name)) {
+            irerror(field->context, "invalid field name size: %u", (unsigned int)len);
+            return false;
+        }
+
+        name[0] = '.';
+        memcpy(name+1, field->name, len); /* no strncpy - we used strlen above */
+        name[len+1] = 0;
+
+        def.name = code_genstring(self->code, name);
+        fld.name = def.name + 1; /* we reuse that string table entry */
+    } else {
+        /* in plain QC, there cannot be a global with the same name,
+         * and so we also name the global the same.
+         * FIXME: fteqcc should create a global as well
+         * check if it actually uses the same name. Probably does
+         */
+        def.name = code_genstring(self->code, field->name);
+        fld.name = def.name;
+    }
+
+    field->code.name = def.name;
+
+    vec_push(self->code->defs, def);
+
+    fld.type = field->fieldtype;
+
+    if (fld.type == TYPE_VOID) {
+        irerror(field->context, "field is missing a type: %s - don't know its size", field->name);
+        return false;
+    }
+
+    fld.offset = field->code.fieldaddr;
+
+    vec_push(self->code->fields, fld);
+
+    ir_value_code_setaddr(field, vec_size(self->code->globals));
+    vec_push(self->code->globals, fld.offset);
+    if (fld.type == TYPE_VECTOR) {
+        vec_push(self->code->globals, fld.offset+1);
+        vec_push(self->code->globals, fld.offset+2);
+    }
+
+    if (field->fieldtype == TYPE_VECTOR) {
+        gen_vector_defs  (self->code, def, field->name);
+        gen_vector_fields(self->code, fld, field->name);
+    }
+
+    return field->code.globaladdr >= 0;
+}
+
+static void ir_builder_collect_reusables(ir_builder *builder) {
+    size_t i;
+    ir_value **reusables = NULL;
+    for (i = 0; i < vec_size(builder->globals); ++i) {
+        ir_value *value = builder->globals[i];
+        if (value->vtype != TYPE_FLOAT || !value->hasvalue)
+            continue;
+        if (value->cvq == CV_CONST || (value->name && value->name[0] == '#')) {
+            vec_push(reusables, value);
+        }
+    }
+    builder->const_floats = reusables;
+}
+
+static void ir_builder_split_vector(ir_builder *self, ir_value *vec) {
+    size_t i, count;
+    ir_value* found[3] = { NULL, NULL, NULL };
+
+    /* must not be written to */
+    if (vec_size(vec->writes))
+        return;
+    /* must not be trying to access individual members */
+    if (vec->members[0] || vec->members[1] || vec->members[2])
+        return;
+    /* should be actually used otherwise it won't be generated anyway */
+    count = vec_size(vec->reads);
+    if (!count)
+        return;
+
+    /* may only be used directly as function parameters, so if we find some other instruction cancel */
+    for (i = 0; i != count; ++i) {
+        /* we only split vectors if they're used directly as parameter to a call only! */
+        ir_instr *user = vec->reads[i];
+        if ((user->opcode < INSTR_CALL0 || user->opcode > INSTR_CALL8) && user->opcode != VINSTR_NRCALL)
+            return;
+    }
+
+    vec->flags |= IR_FLAG_SPLIT_VECTOR;
+
+    /* find existing floats making up the split */
+    count = vec_size(self->const_floats);
+    for (i = 0; i != count; ++i) {
+        ir_value *c = self->const_floats[i];
+        if (!found[0] && c->constval.vfloat == vec->constval.vvec.x)
+            found[0] = c;
+        if (!found[1] && c->constval.vfloat == vec->constval.vvec.y)
+            found[1] = c;
+        if (!found[2] && c->constval.vfloat == vec->constval.vvec.z)
+            found[2] = c;
+        if (found[0] && found[1] && found[2])
+            break;
+    }
+
+    /* generate floats for not yet found components */
+    if (!found[0])
+        found[0] = ir_builder_imm_float(self, vec->constval.vvec.x, true);
+    if (!found[1]) {
+        if (vec->constval.vvec.y == vec->constval.vvec.x)
+            found[1] = found[0];
+        else
+            found[1] = ir_builder_imm_float(self, vec->constval.vvec.y, true);
+    }
+    if (!found[2]) {
+        if (vec->constval.vvec.z == vec->constval.vvec.x)
+            found[2] = found[0];
+        else if (vec->constval.vvec.z == vec->constval.vvec.y)
+            found[2] = found[1];
+        else
+            found[2] = ir_builder_imm_float(self, vec->constval.vvec.z, true);
+    }
+
+    /* the .members array should be safe to use here. */
+    vec->members[0] = found[0];
+    vec->members[1] = found[1];
+    vec->members[2] = found[2];
+
+    /* register the readers for these floats */
+    count = vec_size(vec->reads);
+    for (i = 0; i != count; ++i) {
+        vec_push(found[0]->reads, vec->reads[i]);
+        vec_push(found[1]->reads, vec->reads[i]);
+        vec_push(found[2]->reads, vec->reads[i]);
+    }
+}
+
+static void ir_builder_split_vectors(ir_builder *self) {
+    size_t i, count = vec_size(self->globals);
+    for (i = 0; i != count; ++i) {
+        ir_value *v = self->globals[i];
+        if (v->vtype != TYPE_VECTOR || !v->name || v->name[0] != '#')
+            continue;
+        ir_builder_split_vector(self, self->globals[i]);
+    }
+}
+
+bool ir_builder_generate(ir_builder *self, const char *filename)
+{
+    prog_section_statement_t stmt;
+    size_t i;
+    char  *lnofile = NULL;
+
+    if (OPTS_FLAG(SPLIT_VECTOR_PARAMETERS)) {
+        ir_builder_collect_reusables(self);
+        if (vec_size(self->const_floats) > 0)
+            ir_builder_split_vectors(self);
+    }
+
+    for (i = 0; i < vec_size(self->fields); ++i)
+    {
+        ir_builder_prepare_field(self->code, self->fields[i]);
+    }
+
+    for (i = 0; i < vec_size(self->globals); ++i)
+    {
+        if (!ir_builder_gen_global(self, self->globals[i], false)) {
+            return false;
+        }
+        if (self->globals[i]->vtype == TYPE_FUNCTION) {
+            ir_function *func = self->globals[i]->constval.vfunc;
+            if (func && self->max_locals < func->allocated_locals &&
+                !(func->flags & IR_FLAG_MASK_NO_OVERLAP))
+            {
+                self->max_locals = func->allocated_locals;
+            }
+            if (func && self->max_globaltemps < func->globaltemps)
+                self->max_globaltemps = func->globaltemps;
+        }
+    }
+
+    for (i = 0; i < vec_size(self->fields); ++i)
+    {
+        if (!ir_builder_gen_field(self, self->fields[i])) {
+            return false;
+        }
+    }
+
+    /* generate nil */
+    ir_value_code_setaddr(self->nil, vec_size(self->code->globals));
+    vec_push(self->code->globals, 0);
+    vec_push(self->code->globals, 0);
+    vec_push(self->code->globals, 0);
+
+    /* generate virtual-instruction temps */
+    for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) {
+        ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals));
+        vec_push(self->code->globals, 0);
+        vec_push(self->code->globals, 0);
+        vec_push(self->code->globals, 0);
+    }
+
+    /* generate global temps */
+    self->first_common_globaltemp = vec_size(self->code->globals);
+    for (i = 0; i < self->max_globaltemps; ++i) {
+        vec_push(self->code->globals, 0);
+    }
+    /* generate common locals */
+    self->first_common_local = vec_size(self->code->globals);
+    for (i = 0; i < self->max_locals; ++i) {
+        vec_push(self->code->globals, 0);
+    }
+
+    /* generate function code */
+    for (i = 0; i < vec_size(self->globals); ++i)
+    {
+        if (self->globals[i]->vtype == TYPE_FUNCTION) {
+            if (!gen_global_function_code(self, self->globals[i])) {
+                return false;
+            }
+        }
+    }
+
+    if (vec_size(self->code->globals) >= 65536) {
+        irerror(vec_last(self->globals)->context, "This progs file would require more globals than the metadata can handle (%u). Bailing out.", (unsigned int)vec_size(self->code->globals));
+        return false;
+    }
+
+    /* DP errors if the last instruction is not an INSTR_DONE. */
+    if (vec_last(self->code->statements).opcode != INSTR_DONE)
+    {
+        lex_ctx_t last;
+
+        stmt.opcode = INSTR_DONE;
+        stmt.o1.u1  = 0;
+        stmt.o2.u1  = 0;
+        stmt.o3.u1  = 0;
+        last.line   = vec_last(self->code->linenums);
+        last.column = vec_last(self->code->columnnums);
+
+        code_push_statement(self->code, &stmt, last);
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+        return true;
+
+    if (vec_size(self->code->statements) != vec_size(self->code->linenums)) {
+        con_err("Linecounter wrong: %lu != %lu\n",
+                (unsigned long)vec_size(self->code->statements),
+                (unsigned long)vec_size(self->code->linenums));
+    } else if (OPTS_FLAG(LNO)) {
+        char  *dot;
+        size_t filelen = strlen(filename);
+
+        memcpy(vec_add(lnofile, filelen+1), filename, filelen+1);
+        dot = strrchr(lnofile, '.');
+        if (!dot) {
+            vec_pop(lnofile);
+        } else {
+            vec_shrinkto(lnofile, dot - lnofile);
+        }
+        memcpy(vec_add(lnofile, 5), ".lno", 5);
+    }
+
+    if (!code_write(self->code, filename, lnofile)) {
+        vec_free(lnofile);
+        return false;
+    }
+
+    vec_free(lnofile);
+    return true;
+}
+
+/***********************************************************************
+ *IR DEBUG Dump functions...
+ */
+
+#define IND_BUFSZ 1024
+
+static const char *qc_opname(int op)
+{
+    if (op < 0) return "<INVALID>";
+    if (op < VINSTR_END)
+        return util_instr_str[op];
+    switch (op) {
+        case VINSTR_END:       return "END";
+        case VINSTR_PHI:       return "PHI";
+        case VINSTR_JUMP:      return "JUMP";
+        case VINSTR_COND:      return "COND";
+        case VINSTR_BITXOR:    return "BITXOR";
+        case VINSTR_BITAND_V:  return "BITAND_V";
+        case VINSTR_BITOR_V:   return "BITOR_V";
+        case VINSTR_BITXOR_V:  return "BITXOR_V";
+        case VINSTR_BITAND_VF: return "BITAND_VF";
+        case VINSTR_BITOR_VF:  return "BITOR_VF";
+        case VINSTR_BITXOR_VF: return "BITXOR_VF";
+        case VINSTR_CROSS:     return "CROSS";
+        case VINSTR_NEG_F:     return "NEG_F";
+        case VINSTR_NEG_V:     return "NEG_V";
+        default:               return "<UNK>";
+    }
+}
+
+void ir_builder_dump(ir_builder *b, int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    char indent[IND_BUFSZ];
+    indent[0] = '\t';
+    indent[1] = 0;
+
+    oprintf("module %s\n", b->name);
+    for (i = 0; i < vec_size(b->globals); ++i)
+    {
+        oprintf("global ");
+        if (b->globals[i]->hasvalue)
+            oprintf("%s = ", b->globals[i]->name);
+        ir_value_dump(b->globals[i], oprintf);
+        oprintf("\n");
+    }
+    for (i = 0; i < vec_size(b->functions); ++i)
+        ir_function_dump(b->functions[i], indent, oprintf);
+    oprintf("endmodule %s\n", b->name);
+}
+
+static const char *storenames[] = {
+    "[global]", "[local]", "[param]", "[value]", "[return]"
+};
+
+void ir_function_dump(ir_function *f, char *ind,
+                      int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    if (f->builtin != 0) {
+        oprintf("%sfunction %s = builtin %i\n", ind, f->name, -f->builtin);
+        return;
+    }
+    oprintf("%sfunction %s\n", ind, f->name);
+    util_strncat(ind, "\t", IND_BUFSZ-1);
+    if (vec_size(f->locals))
+    {
+        oprintf("%s%i locals:\n", ind, (int)vec_size(f->locals));
+        for (i = 0; i < vec_size(f->locals); ++i) {
+            oprintf("%s\t", ind);
+            ir_value_dump(f->locals[i], oprintf);
+            oprintf("\n");
+        }
+    }
+    oprintf("%sliferanges:\n", ind);
+    for (i = 0; i < vec_size(f->locals); ++i) {
+        const char *attr = "";
+        size_t l, m;
+        ir_value *v = f->locals[i];
+        if (v->unique_life && v->locked)
+            attr = "unique,locked ";
+        else if (v->unique_life)
+            attr = "unique ";
+        else if (v->locked)
+            attr = "locked ";
+        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype],
+                storenames[v->store],
+                attr, (v->callparam ? "callparam " : ""),
+                (int)v->code.local);
+        if (!v->life)
+            oprintf("[null]");
+        for (l = 0; l < vec_size(v->life); ++l) {
+            oprintf("[%i,%i] ", v->life[l].start, v->life[l].end);
+        }
+        oprintf("\n");
+        for (m = 0; m < 3; ++m) {
+            ir_value *vm = v->members[m];
+            if (!vm)
+                continue;
+            oprintf("%s\t%s: @%i ", ind, vm->name, (int)vm->code.local);
+            for (l = 0; l < vec_size(vm->life); ++l) {
+                oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end);
+            }
+            oprintf("\n");
+        }
+    }
+    for (i = 0; i < vec_size(f->values); ++i) {
+        const char *attr = "";
+        size_t l, m;
+        ir_value *v = f->values[i];
+        if (v->unique_life && v->locked)
+            attr = "unique,locked ";
+        else if (v->unique_life)
+            attr = "unique ";
+        else if (v->locked)
+            attr = "locked ";
+        oprintf("%s\t%s: %s %s %s%s@%i ", ind, v->name, type_name[v->vtype],
+                storenames[v->store],
+                attr, (v->callparam ? "callparam " : ""),
+                (int)v->code.local);
+        if (!v->life)
+            oprintf("[null]");
+        for (l = 0; l < vec_size(v->life); ++l) {
+            oprintf("[%i,%i] ", v->life[l].start, v->life[l].end);
+        }
+        oprintf("\n");
+        for (m = 0; m < 3; ++m) {
+            ir_value *vm = v->members[m];
+            if (!vm)
+                continue;
+            if (vm->unique_life && vm->locked)
+                attr = "unique,locked ";
+            else if (vm->unique_life)
+                attr = "unique ";
+            else if (vm->locked)
+                attr = "locked ";
+            oprintf("%s\t%s: %s@%i ", ind, vm->name, attr, (int)vm->code.local);
+            for (l = 0; l < vec_size(vm->life); ++l) {
+                oprintf("[%i,%i] ", vm->life[l].start, vm->life[l].end);
+            }
+            oprintf("\n");
+        }
+    }
+    if (vec_size(f->blocks))
+    {
+        oprintf("%slife passes: %i\n", ind, (int)f->run_id);
+        for (i = 0; i < vec_size(f->blocks); ++i) {
+            ir_block_dump(f->blocks[i], ind, oprintf);
+        }
+
+    }
+    ind[strlen(ind)-1] = 0;
+    oprintf("%sendfunction %s\n", ind, f->name);
+}
+
+void ir_block_dump(ir_block* b, char *ind,
+                   int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    oprintf("%s:%s\n", ind, b->label);
+    util_strncat(ind, "\t", IND_BUFSZ-1);
+
+    if (b->instr && b->instr[0])
+        oprintf("%s (%i) [entry]\n", ind, (int)(b->instr[0]->eid-1));
+    for (i = 0; i < vec_size(b->instr); ++i)
+        ir_instr_dump(b->instr[i], ind, oprintf);
+    ind[strlen(ind)-1] = 0;
+}
+
+static void dump_phi(ir_instr *in, int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    oprintf("%s <- phi ", in->_ops[0]->name);
+    for (i = 0; i < vec_size(in->phi); ++i)
+    {
+        oprintf("([%s] : %s) ", in->phi[i].from->label,
+                                in->phi[i].value->name);
+    }
+    oprintf("\n");
+}
+
+void ir_instr_dump(ir_instr *in, char *ind,
+                       int (*oprintf)(const char*, ...))
+{
+    size_t i;
+    const char *comma = NULL;
+
+    oprintf("%s (%i) ", ind, (int)in->eid);
+
+    if (in->opcode == VINSTR_PHI) {
+        dump_phi(in, oprintf);
+        return;
+    }
+
+    util_strncat(ind, "\t", IND_BUFSZ-1);
+
+    if (in->_ops[0] && (in->_ops[1] || in->_ops[2])) {
+        ir_value_dump(in->_ops[0], oprintf);
+        if (in->_ops[1] || in->_ops[2])
+            oprintf(" <- ");
+    }
+    if (in->opcode == INSTR_CALL0 || in->opcode == VINSTR_NRCALL) {
+        oprintf("CALL%i\t", vec_size(in->params));
+    } else
+        oprintf("%s\t", qc_opname(in->opcode));
+
+    if (in->_ops[0] && !(in->_ops[1] || in->_ops[2])) {
+        ir_value_dump(in->_ops[0], oprintf);
+        comma = ",\t";
+    }
+    else
+    {
+        for (i = 1; i != 3; ++i) {
+            if (in->_ops[i]) {
+                if (comma)
+                    oprintf(comma);
+                ir_value_dump(in->_ops[i], oprintf);
+                comma = ",\t";
+            }
+        }
+    }
+    if (in->bops[0]) {
+        if (comma)
+            oprintf(comma);
+        oprintf("[%s]", in->bops[0]->label);
+        comma = ",\t";
+    }
+    if (in->bops[1])
+        oprintf("%s[%s]", comma, in->bops[1]->label);
+    if (vec_size(in->params)) {
+        oprintf("\tparams: ");
+        for (i = 0; i != vec_size(in->params); ++i) {
+            oprintf("%s, ", in->params[i]->name);
+        }
+    }
+    oprintf("\n");
+    ind[strlen(ind)-1] = 0;
+}
+
+static void ir_value_dump_string(const char *str, int (*oprintf)(const char*, ...))
+{
+    oprintf("\"");
+    for (; *str; ++str) {
+        switch (*str) {
+            case '\n': oprintf("\\n"); break;
+            case '\r': oprintf("\\r"); break;
+            case '\t': oprintf("\\t"); break;
+            case '\v': oprintf("\\v"); break;
+            case '\f': oprintf("\\f"); break;
+            case '\b': oprintf("\\b"); break;
+            case '\a': oprintf("\\a"); break;
+            case '\\': oprintf("\\\\"); break;
+            case '"': oprintf("\\\""); break;
+            default: oprintf("%c", *str); break;
+        }
+    }
+    oprintf("\"");
+}
+
+void ir_value_dump(ir_value* v, int (*oprintf)(const char*, ...))
+{
+    if (v->hasvalue) {
+        switch (v->vtype) {
+            default:
+            case TYPE_VOID:
+                oprintf("(void)");
+                break;
+            case TYPE_FUNCTION:
+                oprintf("fn:%s", v->name);
+                break;
+            case TYPE_FLOAT:
+                oprintf("%g", v->constval.vfloat);
+                break;
+            case TYPE_VECTOR:
+                oprintf("'%g %g %g'",
+                        v->constval.vvec.x,
+                        v->constval.vvec.y,
+                        v->constval.vvec.z);
+                break;
+            case TYPE_ENTITY:
+                oprintf("(entity)");
+                break;
+            case TYPE_STRING:
+                ir_value_dump_string(v->constval.vstring, oprintf);
+                break;
+#if 0
+            case TYPE_INTEGER:
+                oprintf("%i", v->constval.vint);
+                break;
+#endif
+            case TYPE_POINTER:
+                oprintf("&%s",
+                    v->constval.vpointer->name);
+                break;
+        }
+    } else {
+        oprintf("%s", v->name);
+    }
+}
+
+void ir_value_dump_life(const ir_value *self, int (*oprintf)(const char*,...))
+{
+    size_t i;
+    oprintf("Life of %12s:", self->name);
+    for (i = 0; i < vec_size(self->life); ++i)
+    {
+        oprintf(" + [%i, %i]\n", self->life[i].start, self->life[i].end);
+    }
+}
diff --git a/lexer.c b/lexer.c
deleted file mode 100644 (file)
index 3dc6981..0000000
--- a/lexer.c
+++ /dev/null
@@ -1,1423 +0,0 @@
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-#include "lexer.h"
-
-/*
- * List of Keywords
- */
-
-/* original */
-static const char *keywords_qc[] = {
-    "for", "do", "while",
-    "if", "else",
-    "local",
-    "return",
-    "const"
-};
-/* For fte/gmgqcc */
-static const char *keywords_fg[] = {
-    "switch", "case", "default",
-    "struct", "union",
-    "break", "continue",
-    "typedef",
-    "goto",
-
-    "__builtin_debug_printtype"
-};
-
-/*
- * Lexer code
- */
-static char* *lex_filenames;
-
-static void lexerror(lex_file *lex, const char *fmt, ...)
-{
-    va_list ap;
-
-    va_start(ap, fmt);
-    if (lex)
-        con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
-    else
-        con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
-    va_end(ap);
-}
-
-static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
-{
-    bool      r;
-    lex_ctx_t ctx;
-    va_list   ap;
-
-    ctx.file   = lex->name;
-    ctx.line   = lex->sline;
-    ctx.column = lex->column;
-
-    va_start(ap, fmt);
-    r = vcompile_warning(ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-static void lex_token_new(lex_file *lex)
-{
-    if (lex->tok.value)
-        vec_shrinkto(lex->tok.value, 0);
-
-    lex->tok.constval.t  = 0;
-    lex->tok.ctx.line    = lex->sline;
-    lex->tok.ctx.file    = lex->name;
-    lex->tok.ctx.column  = lex->column;
-}
-
-static void lex_ungetch(lex_file *lex, int ch);
-static int lex_getch(lex_file *lex);
-
-lex_file* lex_open(const char *file)
-{
-    lex_file  *lex;
-    FILE *in = fopen(file, "rb");
-    uint32_t   read;
-
-    if (!in) {
-        lexerror(NULL, "open failed: '%s'\n", file);
-        return NULL;
-    }
-
-    lex = (lex_file*)mem_a(sizeof(*lex));
-    if (!lex) {
-        fclose(in);
-        lexerror(NULL, "out of memory\n");
-        return NULL;
-    }
-
-    memset(lex, 0, sizeof(*lex));
-
-    lex->file    = in;
-    lex->name    = util_strdup(file);
-    lex->line    = 1; /* we start counting at 1 */
-    lex->column  = 0;
-    lex->peekpos = 0;
-    lex->eof     = false;
-
-    /* handle BOM */
-    if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) {
-        lex_ungetch(lex, (read & 0x0000FF));
-        lex_ungetch(lex, (read & 0x00FF00) >> 8);
-        lex_ungetch(lex, (read & 0xFF0000) >> 16);
-    } else {
-        /*
-         * otherwise the lexer has advanced 3 bytes for the BOM, we need
-         * to set the column back to 0
-         */
-        lex->column = 0;
-    }
-
-    vec_push(lex_filenames, lex->name);
-    return lex;
-}
-
-lex_file* lex_open_string(const char *str, size_t len, const char *name)
-{
-    lex_file *lex;
-
-    lex = (lex_file*)mem_a(sizeof(*lex));
-    if (!lex) {
-        lexerror(NULL, "out of memory\n");
-        return NULL;
-    }
-
-    memset(lex, 0, sizeof(*lex));
-
-    lex->file = NULL;
-    lex->open_string        = str;
-    lex->open_string_length = len;
-    lex->open_string_pos    = 0;
-
-    lex->name    = util_strdup(name ? name : "<string-source>");
-    lex->line    = 1; /* we start counting at 1 */
-    lex->peekpos = 0;
-    lex->eof     = false;
-    lex->column  = 0;
-
-    vec_push(lex_filenames, lex->name);
-
-    return lex;
-}
-
-void lex_cleanup(void)
-{
-    size_t i;
-    for (i = 0; i < vec_size(lex_filenames); ++i)
-        mem_d(lex_filenames[i]);
-    vec_free(lex_filenames);
-}
-
-void lex_close(lex_file *lex)
-{
-    size_t i;
-    for (i = 0; i < vec_size(lex->frames); ++i)
-        mem_d(lex->frames[i].name);
-    vec_free(lex->frames);
-
-    if (lex->modelname)
-        vec_free(lex->modelname);
-
-    if (lex->file)
-        fclose(lex->file);
-
-    vec_free(lex->tok.value);
-
-    /* mem_d(lex->name); collected in lex_filenames */
-    mem_d(lex);
-}
-
-
-
-static int lex_fgetc(lex_file *lex)
-{
-    if (lex->file) {
-        lex->column++;
-        return fgetc(lex->file);
-    }
-    if (lex->open_string) {
-        if (lex->open_string_pos >= lex->open_string_length)
-            return EOF;
-        lex->column++;
-        return lex->open_string[lex->open_string_pos++];
-    }
-    return EOF;
-}
-
-/* Get or put-back data
- * The following to functions do NOT understand what kind of data they
- * are working on.
- * The are merely wrapping get/put in order to count line numbers.
- */
-static int lex_try_trigraph(lex_file *lex, int old)
-{
-    int c2, c3;
-    c2 = lex_fgetc(lex);
-    if (!lex->push_line && c2 == '\n') {
-        lex->line++;
-        lex->column = 0;
-    }
-
-    if (c2 != '?') {
-        lex_ungetch(lex, c2);
-        return old;
-    }
-
-    c3 = lex_fgetc(lex);
-    if (!lex->push_line && c3 == '\n') {
-        lex->line++;
-        lex->column = 0;
-    }
-
-    switch (c3) {
-        case '=': return '#';
-        case '/': return '\\';
-        case '\'': return '^';
-        case '(': return '[';
-        case ')': return ']';
-        case '!': return '|';
-        case '<': return '{';
-        case '>': return '}';
-        case '-': return '~';
-        default:
-            lex_ungetch(lex, c3);
-            lex_ungetch(lex, c2);
-            return old;
-    }
-}
-
-static int lex_try_digraph(lex_file *lex, int ch)
-{
-    int c2;
-    c2 = lex_fgetc(lex);
-    /* we just used fgetc() so count lines
-     * need to offset a \n the ungetch would recognize
-     */
-    if (!lex->push_line && c2 == '\n')
-        lex->line++;
-    if      (ch == '<' && c2 == ':')
-        return '[';
-    else if (ch == ':' && c2 == '>')
-        return ']';
-    else if (ch == '<' && c2 == '%')
-        return '{';
-    else if (ch == '%' && c2 == '>')
-        return '}';
-    else if (ch == '%' && c2 == ':')
-        return '#';
-    lex_ungetch(lex, c2);
-    return ch;
-}
-
-static int lex_getch(lex_file *lex)
-{
-    int ch;
-
-    if (lex->peekpos) {
-        lex->peekpos--;
-        if (!lex->push_line && lex->peek[lex->peekpos] == '\n') {
-            lex->line++;
-            lex->column = 0;
-        }
-        return lex->peek[lex->peekpos];
-    }
-
-    ch = lex_fgetc(lex);
-    if (!lex->push_line && ch == '\n') {
-        lex->line++;
-        lex->column = 0;
-    }
-    else if (ch == '?')
-        return lex_try_trigraph(lex, ch);
-    else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
-        return lex_try_digraph(lex, ch);
-    return ch;
-}
-
-static void lex_ungetch(lex_file *lex, int ch)
-{
-    lex->peek[lex->peekpos++] = ch;
-    lex->column--;
-    if (!lex->push_line && ch == '\n') {
-        lex->line--;
-        lex->column = 0;
-    }
-}
-
-/* classify characters
- * some additions to the is*() functions of ctype.h
- */
-
-/* Idents are alphanumberic, but they start with alpha or _ */
-static bool isident_start(int ch)
-{
-    return util_isalpha(ch) || ch == '_';
-}
-
-static bool isident(int ch)
-{
-    return isident_start(ch) || util_isdigit(ch);
-}
-
-/* isxdigit_only is used when we already know it's not a digit
- * and want to see if it's a hex digit anyway.
- */
-static bool isxdigit_only(int ch)
-{
-    return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
-}
-
-/* Append a character to the token buffer */
-static void lex_tokench(lex_file *lex, int ch)
-{
-    vec_push(lex->tok.value, ch);
-}
-
-/* Append a trailing null-byte */
-static void lex_endtoken(lex_file *lex)
-{
-    vec_push(lex->tok.value, 0);
-    vec_shrinkby(lex->tok.value, 1);
-}
-
-static bool lex_try_pragma(lex_file *lex)
-{
-    int ch;
-    char *pragma  = NULL;
-    char *command = NULL;
-    char *param   = NULL;
-    size_t line;
-
-    if (lex->flags.preprocessing)
-        return false;
-
-    line = lex->line;
-
-    ch = lex_getch(lex);
-    if (ch != '#') {
-        lex_ungetch(lex, ch);
-        return false;
-    }
-
-    for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
-        vec_push(pragma, ch);
-    vec_push(pragma, 0);
-
-    if (ch != ' ' || strcmp(pragma, "pragma")) {
-        lex_ungetch(lex, ch);
-        goto unroll;
-    }
-
-    for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
-        vec_push(command, ch);
-    vec_push(command, 0);
-
-    if (ch != '(') {
-        lex_ungetch(lex, ch);
-        goto unroll;
-    }
-
-    for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
-        vec_push(param, ch);
-    vec_push(param, 0);
-
-    if (ch != ')') {
-        lex_ungetch(lex, ch);
-        goto unroll;
-    }
-
-    if (!strcmp(command, "push")) {
-        if (!strcmp(param, "line")) {
-            lex->push_line++;
-            if (lex->push_line == 1)
-                --line;
-        }
-        else
-            goto unroll;
-    }
-    else if (!strcmp(command, "pop")) {
-        if (!strcmp(param, "line")) {
-            if (lex->push_line)
-                lex->push_line--;
-            if (lex->push_line == 0)
-                --line;
-        }
-        else
-            goto unroll;
-    }
-    else if (!strcmp(command, "file")) {
-        lex->name = util_strdup(param);
-        vec_push(lex_filenames, lex->name);
-    }
-    else if (!strcmp(command, "line")) {
-        line = strtol(param, NULL, 0)-1;
-    }
-    else
-        goto unroll;
-
-    lex->line = line;
-    while (ch != '\n' && ch != EOF)
-        ch = lex_getch(lex);
-    vec_free(command);
-    vec_free(param);
-    vec_free(pragma);
-    return true;
-
-unroll:
-    if (command) {
-        vec_pop(command);
-        while (vec_size(command)) {
-            lex_ungetch(lex, (unsigned char)vec_last(command));
-            vec_pop(command);
-        }
-        vec_free(command);
-        lex_ungetch(lex, ' ');
-    }
-    if (param) {
-        vec_pop(param);
-        while (vec_size(param)) {
-            lex_ungetch(lex, (unsigned char)vec_last(param));
-            vec_pop(param);
-        }
-        vec_free(param);
-        lex_ungetch(lex, ' ');
-    }
-    if (pragma) {
-        vec_pop(pragma);
-        while (vec_size(pragma)) {
-            lex_ungetch(lex, (unsigned char)vec_last(pragma));
-            vec_pop(pragma);
-        }
-        vec_free(pragma);
-    }
-    lex_ungetch(lex, '#');
-
-    lex->line = line;
-    return false;
-}
-
-/* Skip whitespace and comments and return the first
- * non-white character.
- * As this makes use of the above getch() ungetch() functions,
- * we don't need to care at all about line numbering anymore.
- *
- * In theory, this function should only be used at the beginning
- * of lexing, or when we *know* the next character is part of the token.
- * Otherwise, if the parser throws an error, the linenumber may not be
- * the line of the error, but the line of the next token AFTER the error.
- *
- * This is currently only problematic when using c-like string-continuation,
- * since comments and whitespaces are allowed between 2 such strings.
- * Example:
-printf(   "line one\n"
-// A comment
-          "A continuation of the previous string"
-// This line is skipped
-      , foo);
-
- * In this case, if the parse decides it didn't actually want a string,
- * and uses lex->line to print an error, it will show the ', foo);' line's
- * linenumber.
- *
- * On the other hand, the parser is supposed to remember the line of the next
- * token's beginning. In this case we would want skipwhite() to be called
- * AFTER reading a token, so that the parser, before reading the NEXT token,
- * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
- *
- * THIS SOLUTION
- *    here is to store the line of the first character after skipping
- *    the initial whitespace in lex->sline, this happens in lex_do.
- */
-static int lex_skipwhite(lex_file *lex, bool hadwhite)
-{
-    int ch = 0;
-    bool haswhite = hadwhite;
-
-    do
-    {
-        ch = lex_getch(lex);
-        while (ch != EOF && util_isspace(ch)) {
-            if (ch == '\n') {
-                if (lex_try_pragma(lex))
-                    continue;
-            }
-            if (lex->flags.preprocessing) {
-                if (ch == '\n') {
-                    /* end-of-line */
-                    /* see if there was whitespace first */
-                    if (haswhite) { /* (vec_size(lex->tok.value)) { */
-                        lex_ungetch(lex, ch);
-                        lex_endtoken(lex);
-                        return TOKEN_WHITE;
-                    }
-                    /* otherwise return EOL */
-                    return TOKEN_EOL;
-                }
-                haswhite = true;
-                lex_tokench(lex, ch);
-            }
-            ch = lex_getch(lex);
-        }
-
-        if (ch == '/') {
-            ch = lex_getch(lex);
-            if (ch == '/')
-            {
-                /* one line comment */
-                ch = lex_getch(lex);
-
-                if (lex->flags.preprocessing) {
-                    haswhite = true;
-                    lex_tokench(lex, ' ');
-                    lex_tokench(lex, ' ');
-                }
-
-                while (ch != EOF && ch != '\n') {
-                    if (lex->flags.preprocessing)
-                        lex_tokench(lex, ' '); /* ch); */
-                    ch = lex_getch(lex);
-                }
-                if (lex->flags.preprocessing) {
-                    lex_ungetch(lex, '\n');
-                    lex_endtoken(lex);
-                    return TOKEN_WHITE;
-                }
-                continue;
-            }
-            if (ch == '*')
-            {
-                /* multiline comment */
-                if (lex->flags.preprocessing) {
-                    haswhite = true;
-                    lex_tokench(lex, ' ');
-                    lex_tokench(lex, ' ');
-                }
-
-                while (ch != EOF)
-                {
-                    ch = lex_getch(lex);
-                    if (ch == '*') {
-                        ch = lex_getch(lex);
-                        if (ch == '/') {
-                            if (lex->flags.preprocessing) {
-                                lex_tokench(lex, ' ');
-                                lex_tokench(lex, ' ');
-                            }
-                            break;
-                        }
-                        lex_ungetch(lex, ch);
-                    }
-                    if (lex->flags.preprocessing) {
-                        if (ch == '\n')
-                            lex_tokench(lex, '\n');
-                        else
-                            lex_tokench(lex, ' ');
-                    }
-                }
-                ch = ' '; /* cause TRUE in the isspace check */
-                continue;
-            }
-            /* Otherwise roll back to the slash and break out of the loop */
-            lex_ungetch(lex, ch);
-            ch = '/';
-            break;
-        }
-    } while (ch != EOF && util_isspace(ch));
-
-    if (haswhite) {
-        lex_endtoken(lex);
-        lex_ungetch(lex, ch);
-        return TOKEN_WHITE;
-    }
-    return ch;
-}
-
-/* Get a token */
-static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
-{
-    int ch;
-
-    ch = lex_getch(lex);
-    while (ch != EOF && isident(ch))
-    {
-        lex_tokench(lex, ch);
-        ch = lex_getch(lex);
-    }
-
-    /* last ch was not an ident ch: */
-    lex_ungetch(lex, ch);
-
-    return true;
-}
-
-/* read one ident for the frame list */
-static int lex_parse_frame(lex_file *lex)
-{
-    int ch;
-
-    lex_token_new(lex);
-
-    ch = lex_getch(lex);
-    while (ch != EOF && ch != '\n' && util_isspace(ch))
-        ch = lex_getch(lex);
-
-    if (ch == '\n')
-        return 1;
-
-    if (!isident_start(ch)) {
-        lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
-        return -1;
-    }
-
-    lex_tokench(lex, ch);
-    if (!lex_finish_ident(lex))
-        return -1;
-    lex_endtoken(lex);
-    return 0;
-}
-
-/* read a list of $frames */
-static bool lex_finish_frames(lex_file *lex)
-{
-    do {
-        size_t i;
-        int    rc;
-        frame_macro m;
-
-        rc = lex_parse_frame(lex);
-        if (rc > 0) /* end of line */
-            return true;
-        if (rc < 0) /* error */
-            return false;
-
-        for (i = 0; i < vec_size(lex->frames); ++i) {
-            if (!strcmp(lex->tok.value, lex->frames[i].name)) {
-                lex->frames[i].value = lex->framevalue++;
-                if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
-                    return false;
-                break;
-            }
-        }
-        if (i < vec_size(lex->frames))
-            continue;
-
-        m.value = lex->framevalue++;
-        m.name = util_strdup(lex->tok.value);
-        vec_shrinkto(lex->tok.value, 0);
-        vec_push(lex->frames, m);
-    } while (true);
-
-    return false;
-}
-
-static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
-{
-    utf8ch_t chr = 0;
-    int ch = 0, texttype = 0;
-    int nextch;
-    bool hex;
-    bool oct;
-    char u8buf[8]; /* way more than enough */
-    int  u8len, uc;
-
-    while (ch != EOF)
-    {
-        ch = lex_getch(lex);
-        if (ch == quote)
-            return TOKEN_STRINGCONST;
-
-        if (lex->flags.preprocessing && ch == '\\') {
-            lex_tokench(lex, ch);
-            ch = lex_getch(lex);
-            if (ch == EOF) {
-                lexerror(lex, "unexpected end of file");
-                lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
-                return (lex->tok.ttype = TOKEN_ERROR);
-            }
-            lex_tokench(lex, ch);
-        }
-        else if (ch == '\\') {
-            ch = lex_getch(lex);
-            if (ch == EOF) {
-                lexerror(lex, "unexpected end of file");
-                lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
-                return (lex->tok.ttype = TOKEN_ERROR);
-            }
-
-            switch (ch) {
-            case '\\': break;
-            case '\'': break;
-            case '"':  break;
-            case 'a': ch = '\a'; break;
-            case 'r': ch = '\r'; break;
-            case 'n': ch = '\n'; break;
-            case 't': ch = '\t'; break;
-            case 'f': ch = '\f'; break;
-            case 'v': ch = '\v'; break;
-            case 'x':
-            case 'X':
-                /* same procedure as in fteqcc */
-                ch = 0;
-                nextch = lex_getch(lex);
-                if      (nextch >= '0' && nextch <= '9')
-                    ch += nextch - '0';
-                else if (nextch >= 'a' && nextch <= 'f')
-                    ch += nextch - 'a' + 10;
-                else if (nextch >= 'A' && nextch <= 'F')
-                    ch += nextch - 'A' + 10;
-                else {
-                    lexerror(lex, "bad character code");
-                    lex_ungetch(lex, nextch);
-                    return (lex->tok.ttype = TOKEN_ERROR);
-                }
-
-                ch *= 0x10;
-                nextch = lex_getch(lex);
-                if      (nextch >= '0' && nextch <= '9')
-                    ch += nextch - '0';
-                else if (nextch >= 'a' && nextch <= 'f')
-                    ch += nextch - 'a' + 10;
-                else if (nextch >= 'A' && nextch <= 'F')
-                    ch += nextch - 'A' + 10;
-                else {
-                    lexerror(lex, "bad character code");
-                    lex_ungetch(lex, nextch);
-                    return (lex->tok.ttype = TOKEN_ERROR);
-                }
-                break;
-
-            /* fteqcc support */
-            case '0': case '1': case '2': case '3':
-            case '4': case '5': case '6': case '7':
-            case '8': case '9':
-                ch = 18 + ch - '0';
-                break;
-            case '<':  ch = 29; break;
-            case '-':  ch = 30; break;
-            case '>':  ch = 31; break;
-            case '[':  ch = 16; break;
-            case ']':  ch = 17; break;
-            case '{':
-                chr = 0;
-                nextch = lex_getch(lex);
-                hex = (nextch == 'x');
-                oct = (nextch == '0');
-                if (!hex && !oct)
-                    lex_ungetch(lex, nextch);
-                for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
-                    if (!hex && !oct) {
-                        if (nextch >= '0' && nextch <= '9')
-                            chr = chr * 10 + nextch - '0';
-                        else {
-                            lexerror(lex, "bad character code");
-                            return (lex->tok.ttype = TOKEN_ERROR);
-                        }
-                    } else if (!oct) {
-                        if (nextch >= '0' && nextch <= '9')
-                            chr = chr * 0x10 + nextch - '0';
-                        else if (nextch >= 'a' && nextch <= 'f')
-                            chr = chr * 0x10 + nextch - 'a' + 10;
-                        else if (nextch >= 'A' && nextch <= 'F')
-                            chr = chr * 0x10 + nextch - 'A' + 10;
-                        else {
-                            lexerror(lex, "bad character code");
-                            return (lex->tok.ttype = TOKEN_ERROR);
-                        }
-                    } else {
-                        if (nextch >= '0' && nextch <= '9')
-                            chr = chr * 8 + chr - '0';
-                        else {
-                            lexerror(lex, "bad character code");
-                            return (lex->tok.ttype = TOKEN_ERROR);
-                        }
-                    }
-                    if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
-                    {
-                        lexerror(lex, "character code out of range");
-                        return (lex->tok.ttype = TOKEN_ERROR);
-                    }
-                }
-                if (OPTS_FLAG(UTF8) && chr >= 128) {
-                    u8len = utf8_from(u8buf, chr);
-                    if (!u8len)
-                        ch = 0;
-                    else {
-                        --u8len;
-                        lex->column += u8len;
-                        for (uc = 0; uc < u8len; ++uc)
-                            lex_tokench(lex, u8buf[uc]);
-                        /*
-                         * the last character will be inserted with the tokench() call
-                         * below the switch
-                         */
-                        ch = u8buf[uc];
-                    }
-                }
-                else
-                    ch = chr;
-                break;
-
-            /* high bit text */
-            case 'b': case 's':
-                texttype ^= 128;
-                continue;
-
-            case '\n':
-                ch = '\n';
-                break;
-
-            default:
-                lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
-                /* so we just add the character plus backslash no matter what it actually is */
-                lex_tokench(lex, '\\');
-            }
-            /* add the character finally */
-            lex_tokench(lex, ch | texttype);
-        }
-        else
-            lex_tokench(lex, ch);
-    }
-    lexerror(lex, "unexpected end of file within string constant");
-    lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
-    return (lex->tok.ttype = TOKEN_ERROR);
-}
-
-static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
-{
-    bool ishex = false;
-
-    int  ch = lastch;
-
-    /* parse a number... */
-    if (ch == '.')
-        lex->tok.ttype = TOKEN_FLOATCONST;
-    else
-        lex->tok.ttype = TOKEN_INTCONST;
-
-    lex_tokench(lex, ch);
-
-    ch = lex_getch(lex);
-    if (ch != '.' && !util_isdigit(ch))
-    {
-        if (lastch != '0' || ch != 'x')
-        {
-            /* end of the number or EOF */
-            lex_ungetch(lex, ch);
-            lex_endtoken(lex);
-
-            lex->tok.constval.i = lastch - '0';
-            return lex->tok.ttype;
-        }
-
-        ishex = true;
-    }
-
-    /* EOF would have been caught above */
-
-    if (ch != '.')
-    {
-        lex_tokench(lex, ch);
-        ch = lex_getch(lex);
-        while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
-        {
-            lex_tokench(lex, ch);
-            ch = lex_getch(lex);
-        }
-    }
-    /* NOT else, '.' can come from above as well */
-    if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
-    {
-        /* Allow floating comma in non-hex mode */
-        lex->tok.ttype = TOKEN_FLOATCONST;
-        lex_tokench(lex, ch);
-
-        /* continue digits-only */
-        ch = lex_getch(lex);
-        while (util_isdigit(ch))
-        {
-            lex_tokench(lex, ch);
-            ch = lex_getch(lex);
-        }
-    }
-    /* put back the last character */
-    /* but do not put back the trailing 'f' or a float */
-    if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
-        ch = lex_getch(lex);
-
-    /* generally we don't want words to follow numbers: */
-    if (isident(ch)) {
-        lexerror(lex, "unexpected trailing characters after number");
-        return (lex->tok.ttype = TOKEN_ERROR);
-    }
-    lex_ungetch(lex, ch);
-
-    lex_endtoken(lex);
-    if (lex->tok.ttype == TOKEN_FLOATCONST)
-        lex->tok.constval.f = strtod(lex->tok.value, NULL);
-    else
-        lex->tok.constval.i = strtol(lex->tok.value, NULL, 0);
-    return lex->tok.ttype;
-}
-
-int lex_do(lex_file *lex)
-{
-    int ch, nextch, thirdch;
-    bool hadwhite = false;
-
-    lex_token_new(lex);
-
-    while (true) {
-        ch = lex_skipwhite(lex, hadwhite);
-        hadwhite = true;
-        if (!lex->flags.mergelines || ch != '\\')
-            break;
-        ch = lex_getch(lex);
-        if (ch == '\r')
-            ch = lex_getch(lex);
-        if (ch != '\n') {
-            lex_ungetch(lex, ch);
-            ch = '\\';
-            break;
-        }
-        /* we reached a linemerge */
-        lex_tokench(lex, '\n');
-        continue;
-    }
-
-    if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
-        return (lex->tok.ttype = ch);
-    }
-
-    lex->sline = lex->line;
-    lex->tok.ctx.line = lex->sline;
-    lex->tok.ctx.file = lex->name;
-
-    if (lex->eof)
-        return (lex->tok.ttype = TOKEN_FATAL);
-
-    if (ch == EOF) {
-        lex->eof = true;
-        return (lex->tok.ttype = TOKEN_EOF);
-    }
-
-    /* modelgen / spiritgen commands */
-    if (ch == '$' && !lex->flags.preprocessing) {
-        const char *v;
-        size_t frame;
-
-        ch = lex_getch(lex);
-        if (!isident_start(ch)) {
-            lexerror(lex, "hanging '$' modelgen/spritegen command line");
-            return lex_do(lex);
-        }
-        lex_tokench(lex, ch);
-        if (!lex_finish_ident(lex))
-            return (lex->tok.ttype = TOKEN_ERROR);
-        lex_endtoken(lex);
-        /* skip the known commands */
-        v = lex->tok.value;
-
-        if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
-        {
-            /* frame/framesave command works like an enum
-             * similar to fteqcc we handle this in the lexer.
-             * The reason for this is that it is sensitive to newlines,
-             * which the parser is unaware of
-             */
-            if (!lex_finish_frames(lex))
-                 return (lex->tok.ttype = TOKEN_ERROR);
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "framevalue"))
-        {
-            ch = lex_getch(lex);
-            while (ch != EOF && util_isspace(ch) && ch != '\n')
-                ch = lex_getch(lex);
-
-            if (!util_isdigit(ch)) {
-                lexerror(lex, "$framevalue requires an integer parameter");
-                return lex_do(lex);
-            }
-
-            lex_token_new(lex);
-            lex->tok.ttype = lex_finish_digit(lex, ch);
-            lex_endtoken(lex);
-            if (lex->tok.ttype != TOKEN_INTCONST) {
-                lexerror(lex, "$framevalue requires an integer parameter");
-                return lex_do(lex);
-            }
-            lex->framevalue = lex->tok.constval.i;
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "framerestore"))
-        {
-            int rc;
-
-            lex_token_new(lex);
-
-            rc = lex_parse_frame(lex);
-
-            if (rc > 0) {
-                lexerror(lex, "$framerestore requires a framename parameter");
-                return lex_do(lex);
-            }
-            if (rc < 0)
-                return (lex->tok.ttype = TOKEN_FATAL);
-
-            v = lex->tok.value;
-            for (frame = 0; frame < vec_size(lex->frames); ++frame) {
-                if (!strcmp(v, lex->frames[frame].name)) {
-                    lex->framevalue = lex->frames[frame].value;
-                    return lex_do(lex);
-                }
-            }
-            lexerror(lex, "unknown framename `%s`", v);
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "modelname"))
-        {
-            int rc;
-
-            lex_token_new(lex);
-
-            rc = lex_parse_frame(lex);
-
-            if (rc > 0) {
-                lexerror(lex, "$modelname requires a parameter");
-                return lex_do(lex);
-            }
-            if (rc < 0)
-                return (lex->tok.ttype = TOKEN_FATAL);
-
-            if (lex->modelname) {
-                frame_macro m;
-                m.value = lex->framevalue;
-                m.name = lex->modelname;
-                lex->modelname = NULL;
-                vec_push(lex->frames, m);
-            }
-            lex->modelname = lex->tok.value;
-            lex->tok.value = NULL;
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "flush"))
-        {
-            size_t fi;
-            for (fi = 0; fi < vec_size(lex->frames); ++fi)
-                mem_d(lex->frames[fi].name);
-            vec_free(lex->frames);
-            /* skip line (fteqcc does it too) */
-            ch = lex_getch(lex);
-            while (ch != EOF && ch != '\n')
-                ch = lex_getch(lex);
-            return lex_do(lex);
-        }
-
-        if (!strcmp(v, "cd") ||
-            !strcmp(v, "origin") ||
-            !strcmp(v, "base") ||
-            !strcmp(v, "flags") ||
-            !strcmp(v, "scale") ||
-            !strcmp(v, "skin"))
-        {
-            /* skip line */
-            ch = lex_getch(lex);
-            while (ch != EOF && ch != '\n')
-                ch = lex_getch(lex);
-            return lex_do(lex);
-        }
-
-        for (frame = 0; frame < vec_size(lex->frames); ++frame) {
-            if (!strcmp(v, lex->frames[frame].name)) {
-                lex->tok.constval.i = lex->frames[frame].value;
-                return (lex->tok.ttype = TOKEN_INTCONST);
-            }
-        }
-
-        lexerror(lex, "invalid frame macro");
-        return lex_do(lex);
-    }
-
-    /* single-character tokens */
-    switch (ch)
-    {
-        case '[':
-            nextch = lex_getch(lex);
-            if (nextch == '[') {
-                lex_tokench(lex, ch);
-                lex_tokench(lex, nextch);
-                lex_endtoken(lex);
-                return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN);
-            }
-            lex_ungetch(lex, nextch);
-            /* FALL THROUGH */
-        case '(':
-        case ':':
-        case '?':
-            lex_tokench(lex, ch);
-            lex_endtoken(lex);
-            if (lex->flags.noops)
-                return (lex->tok.ttype = ch);
-            else
-                return (lex->tok.ttype = TOKEN_OPERATOR);
-
-        case ']':
-            if (lex->flags.noops) {
-                nextch = lex_getch(lex);
-                if (nextch == ']') {
-                    lex_tokench(lex, ch);
-                    lex_tokench(lex, nextch);
-                    lex_endtoken(lex);
-                    return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE);
-                }
-                lex_ungetch(lex, nextch);
-            }
-            /* FALL THROUGH */
-        case ')':
-        case ';':
-        case '{':
-        case '}':
-
-        case '#':
-            lex_tokench(lex, ch);
-            lex_endtoken(lex);
-            return (lex->tok.ttype = ch);
-        default:
-            break;
-    }
-
-    if (ch == '.') {
-        nextch = lex_getch(lex);
-        /* digits starting with a dot */
-        if (util_isdigit(nextch)) {
-            lex_ungetch(lex, nextch);
-            lex->tok.ttype = lex_finish_digit(lex, ch);
-            lex_endtoken(lex);
-            return lex->tok.ttype;
-        }
-        lex_ungetch(lex, nextch);
-    }
-
-    if (lex->flags.noops)
-    {
-        /* Detect characters early which are normally
-         * operators OR PART of an operator.
-         */
-        switch (ch)
-        {
-            case '*':
-            case '/':
-            case '<':
-            case '>':
-            case '=':
-            case '&':
-            case '|':
-            case '^':
-            case '~':
-            case ',':
-            case '!':
-                lex_tokench(lex, ch);
-                lex_endtoken(lex);
-                return (lex->tok.ttype = ch);
-            default:
-                break;
-        }
-    }
-
-    if (ch == '.')
-    {
-        lex_tokench(lex, ch);
-        /* peak ahead once */
-        nextch = lex_getch(lex);
-        if (nextch != '.') {
-            lex_ungetch(lex, nextch);
-            lex_endtoken(lex);
-            if (lex->flags.noops)
-                return (lex->tok.ttype = ch);
-            else
-                return (lex->tok.ttype = TOKEN_OPERATOR);
-        }
-        /* peak ahead again */
-        nextch = lex_getch(lex);
-        if (nextch != '.') {
-            lex_ungetch(lex, nextch);
-            lex_ungetch(lex, '.');
-            lex_endtoken(lex);
-            if (lex->flags.noops)
-                return (lex->tok.ttype = ch);
-            else
-                return (lex->tok.ttype = TOKEN_OPERATOR);
-        }
-        /* fill the token to be "..." */
-        lex_tokench(lex, ch);
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_DOTS);
-    }
-
-    if (ch == ',' || ch == '.') {
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
-        ch == '>' || ch == '<' || /* <<, >>, <=, >=  and >< as well! */
-        ch == '=' || ch == '!' || /* <=>, ==, !=                     */
-        ch == '&' || ch == '|' || /* &&, ||, &=, |=                  */
-        ch == '~' || ch == '^'    /* ~=, ~, ^                        */
-    )  {
-        lex_tokench(lex, ch);
-        nextch = lex_getch(lex);
-
-        if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>'))
-            lex_tokench(lex, nextch);
-        else if (nextch == ch && ch != '!') {
-            lex_tokench(lex, nextch);
-            if ((thirdch = lex_getch(lex)) == '=')
-                lex_tokench(lex, thirdch);
-            else
-                lex_ungetch(lex, thirdch);
-        } else if (ch == '<' && nextch == '=') {
-            lex_tokench(lex, nextch);
-            if ((thirdch = lex_getch(lex)) == '>')
-                lex_tokench(lex, thirdch);
-            else
-                lex_ungetch(lex, thirdch);
-
-        } else if (ch == '-' && nextch == '>') {
-            lex_tokench(lex, nextch);
-        } else if (ch == '&' && nextch == '~') {
-            thirdch = lex_getch(lex);
-            if (thirdch != '=') {
-                lex_ungetch(lex, thirdch);
-                lex_ungetch(lex, nextch);
-            }
-            else {
-                lex_tokench(lex, nextch);
-                lex_tokench(lex, thirdch);
-            }
-        }
-        else if (lex->flags.preprocessing &&
-                 ch == '-' && util_isdigit(nextch))
-        {
-            lex->tok.ttype = lex_finish_digit(lex, nextch);
-            if (lex->tok.ttype == TOKEN_INTCONST)
-                lex->tok.constval.i = -lex->tok.constval.i;
-            else
-                lex->tok.constval.f = -lex->tok.constval.f;
-            lex_endtoken(lex);
-            return lex->tok.ttype;
-        } else {
-            lex_ungetch(lex, nextch);
-        }
-
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (ch == '*' || ch == '/') /* *=, /= */
-    {
-        lex_tokench(lex, ch);
-
-        nextch = lex_getch(lex);
-        if (nextch == '=' || nextch == '*') {
-            lex_tokench(lex, nextch);
-        } else
-            lex_ungetch(lex, nextch);
-
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (ch == '%') {
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = TOKEN_OPERATOR);
-    }
-
-    if (isident_start(ch))
-    {
-        const char *v;
-
-        lex_tokench(lex, ch);
-        if (!lex_finish_ident(lex)) {
-            /* error? */
-            return (lex->tok.ttype = TOKEN_ERROR);
-        }
-        lex_endtoken(lex);
-        lex->tok.ttype = TOKEN_IDENT;
-
-        v = lex->tok.value;
-        if (!strcmp(v, "void")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_VOID;
-        } else if (!strcmp(v, "int")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_INTEGER;
-        } else if (!strcmp(v, "float")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_FLOAT;
-        } else if (!strcmp(v, "string")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_STRING;
-        } else if (!strcmp(v, "entity")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_ENTITY;
-        } else if (!strcmp(v, "vector")) {
-            lex->tok.ttype = TOKEN_TYPENAME;
-            lex->tok.constval.t = TYPE_VECTOR;
-        } else if (!strcmp(v, "_length")) {
-            lex->tok.ttype = TOKEN_OPERATOR;
-        } else {
-            size_t kw;
-            for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
-                if (!strcmp(v, keywords_qc[kw]))
-                    return (lex->tok.ttype = TOKEN_KEYWORD);
-            }
-            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
-                for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) {
-                    if (!strcmp(v, keywords_fg[kw]))
-                        return (lex->tok.ttype = TOKEN_KEYWORD);
-                }
-            }
-        }
-
-        return lex->tok.ttype;
-    }
-
-    if (ch == '"')
-    {
-        lex->flags.nodigraphs = true;
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        lex->tok.ttype = lex_finish_string(lex, '"');
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
-        {
-            /* Allow c style "string" "continuation" */
-            ch = lex_skipwhite(lex, false);
-            if (ch != '"') {
-                lex_ungetch(lex, ch);
-                break;
-            }
-
-            lex->tok.ttype = lex_finish_string(lex, '"');
-        }
-        lex->flags.nodigraphs = false;
-        lex_endtoken(lex);
-        return lex->tok.ttype;
-    }
-
-    if (ch == '\'')
-    {
-        /* we parse character constants like string,
-         * but return TOKEN_CHARCONST, or a vector type if it fits...
-         * Likewise actual unescaping has to be done by the parser.
-         * The difference is we don't allow 'char' 'continuation'.
-         */
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        lex->tok.ttype = lex_finish_string(lex, '\'');
-        if (lex->flags.preprocessing)
-            lex_tokench(lex, ch);
-        lex_endtoken(lex);
-
-        lex->tok.ttype = TOKEN_CHARCONST;
-
-        /* It's a vector if we can successfully scan 3 floats */
-        if (util_sscanf(lex->tok.value, " %f %f %f ",
-                   &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
-
-        {
-             lex->tok.ttype = TOKEN_VECTORCONST;
-        }
-        else
-        {
-            if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
-                utf8ch_t u8char;
-                /* check for a valid utf8 character */
-                if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) {
-                    if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
-                                ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
-                                                  : "multibyte character: `%s`" ),
-                                lex->tok.value))
-                        return (lex->tok.ttype = TOKEN_ERROR);
-                }
-                else
-                    lex->tok.constval.i = u8char;
-            }
-            else
-                lex->tok.constval.i = lex->tok.value[0];
-        }
-
-        return lex->tok.ttype;
-    }
-
-    if (util_isdigit(ch))
-    {
-        lex->tok.ttype = lex_finish_digit(lex, ch);
-        lex_endtoken(lex);
-        return lex->tok.ttype;
-    }
-
-    if (lex->flags.preprocessing) {
-        lex_tokench(lex, ch);
-        lex_endtoken(lex);
-        return (lex->tok.ttype = ch);
-    }
-
-    lexerror(lex, "unknown token: `%c`", ch);
-    return (lex->tok.ttype = TOKEN_ERROR);
-}
diff --git a/lexer.cpp b/lexer.cpp
new file mode 100644 (file)
index 0000000..3dc6981
--- /dev/null
+++ b/lexer.cpp
@@ -0,0 +1,1423 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "gmqcc.h"
+#include "lexer.h"
+
+/*
+ * List of Keywords
+ */
+
+/* original */
+static const char *keywords_qc[] = {
+    "for", "do", "while",
+    "if", "else",
+    "local",
+    "return",
+    "const"
+};
+/* For fte/gmgqcc */
+static const char *keywords_fg[] = {
+    "switch", "case", "default",
+    "struct", "union",
+    "break", "continue",
+    "typedef",
+    "goto",
+
+    "__builtin_debug_printtype"
+};
+
+/*
+ * Lexer code
+ */
+static char* *lex_filenames;
+
+static void lexerror(lex_file *lex, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    if (lex)
+        con_vprintmsg(LVL_ERROR, lex->name, lex->sline, lex->column, "parse error", fmt, ap);
+    else
+        con_vprintmsg(LVL_ERROR, "", 0, 0, "parse error", fmt, ap);
+    va_end(ap);
+}
+
+static bool lexwarn(lex_file *lex, int warntype, const char *fmt, ...)
+{
+    bool      r;
+    lex_ctx_t ctx;
+    va_list   ap;
+
+    ctx.file   = lex->name;
+    ctx.line   = lex->sline;
+    ctx.column = lex->column;
+
+    va_start(ap, fmt);
+    r = vcompile_warning(ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+static void lex_token_new(lex_file *lex)
+{
+    if (lex->tok.value)
+        vec_shrinkto(lex->tok.value, 0);
+
+    lex->tok.constval.t  = 0;
+    lex->tok.ctx.line    = lex->sline;
+    lex->tok.ctx.file    = lex->name;
+    lex->tok.ctx.column  = lex->column;
+}
+
+static void lex_ungetch(lex_file *lex, int ch);
+static int lex_getch(lex_file *lex);
+
+lex_file* lex_open(const char *file)
+{
+    lex_file  *lex;
+    FILE *in = fopen(file, "rb");
+    uint32_t   read;
+
+    if (!in) {
+        lexerror(NULL, "open failed: '%s'\n", file);
+        return NULL;
+    }
+
+    lex = (lex_file*)mem_a(sizeof(*lex));
+    if (!lex) {
+        fclose(in);
+        lexerror(NULL, "out of memory\n");
+        return NULL;
+    }
+
+    memset(lex, 0, sizeof(*lex));
+
+    lex->file    = in;
+    lex->name    = util_strdup(file);
+    lex->line    = 1; /* we start counting at 1 */
+    lex->column  = 0;
+    lex->peekpos = 0;
+    lex->eof     = false;
+
+    /* handle BOM */
+    if ((read = (lex_getch(lex) << 16) | (lex_getch(lex) << 8) | lex_getch(lex)) != 0xEFBBBF) {
+        lex_ungetch(lex, (read & 0x0000FF));
+        lex_ungetch(lex, (read & 0x00FF00) >> 8);
+        lex_ungetch(lex, (read & 0xFF0000) >> 16);
+    } else {
+        /*
+         * otherwise the lexer has advanced 3 bytes for the BOM, we need
+         * to set the column back to 0
+         */
+        lex->column = 0;
+    }
+
+    vec_push(lex_filenames, lex->name);
+    return lex;
+}
+
+lex_file* lex_open_string(const char *str, size_t len, const char *name)
+{
+    lex_file *lex;
+
+    lex = (lex_file*)mem_a(sizeof(*lex));
+    if (!lex) {
+        lexerror(NULL, "out of memory\n");
+        return NULL;
+    }
+
+    memset(lex, 0, sizeof(*lex));
+
+    lex->file = NULL;
+    lex->open_string        = str;
+    lex->open_string_length = len;
+    lex->open_string_pos    = 0;
+
+    lex->name    = util_strdup(name ? name : "<string-source>");
+    lex->line    = 1; /* we start counting at 1 */
+    lex->peekpos = 0;
+    lex->eof     = false;
+    lex->column  = 0;
+
+    vec_push(lex_filenames, lex->name);
+
+    return lex;
+}
+
+void lex_cleanup(void)
+{
+    size_t i;
+    for (i = 0; i < vec_size(lex_filenames); ++i)
+        mem_d(lex_filenames[i]);
+    vec_free(lex_filenames);
+}
+
+void lex_close(lex_file *lex)
+{
+    size_t i;
+    for (i = 0; i < vec_size(lex->frames); ++i)
+        mem_d(lex->frames[i].name);
+    vec_free(lex->frames);
+
+    if (lex->modelname)
+        vec_free(lex->modelname);
+
+    if (lex->file)
+        fclose(lex->file);
+
+    vec_free(lex->tok.value);
+
+    /* mem_d(lex->name); collected in lex_filenames */
+    mem_d(lex);
+}
+
+
+
+static int lex_fgetc(lex_file *lex)
+{
+    if (lex->file) {
+        lex->column++;
+        return fgetc(lex->file);
+    }
+    if (lex->open_string) {
+        if (lex->open_string_pos >= lex->open_string_length)
+            return EOF;
+        lex->column++;
+        return lex->open_string[lex->open_string_pos++];
+    }
+    return EOF;
+}
+
+/* Get or put-back data
+ * The following to functions do NOT understand what kind of data they
+ * are working on.
+ * The are merely wrapping get/put in order to count line numbers.
+ */
+static int lex_try_trigraph(lex_file *lex, int old)
+{
+    int c2, c3;
+    c2 = lex_fgetc(lex);
+    if (!lex->push_line && c2 == '\n') {
+        lex->line++;
+        lex->column = 0;
+    }
+
+    if (c2 != '?') {
+        lex_ungetch(lex, c2);
+        return old;
+    }
+
+    c3 = lex_fgetc(lex);
+    if (!lex->push_line && c3 == '\n') {
+        lex->line++;
+        lex->column = 0;
+    }
+
+    switch (c3) {
+        case '=': return '#';
+        case '/': return '\\';
+        case '\'': return '^';
+        case '(': return '[';
+        case ')': return ']';
+        case '!': return '|';
+        case '<': return '{';
+        case '>': return '}';
+        case '-': return '~';
+        default:
+            lex_ungetch(lex, c3);
+            lex_ungetch(lex, c2);
+            return old;
+    }
+}
+
+static int lex_try_digraph(lex_file *lex, int ch)
+{
+    int c2;
+    c2 = lex_fgetc(lex);
+    /* we just used fgetc() so count lines
+     * need to offset a \n the ungetch would recognize
+     */
+    if (!lex->push_line && c2 == '\n')
+        lex->line++;
+    if      (ch == '<' && c2 == ':')
+        return '[';
+    else if (ch == ':' && c2 == '>')
+        return ']';
+    else if (ch == '<' && c2 == '%')
+        return '{';
+    else if (ch == '%' && c2 == '>')
+        return '}';
+    else if (ch == '%' && c2 == ':')
+        return '#';
+    lex_ungetch(lex, c2);
+    return ch;
+}
+
+static int lex_getch(lex_file *lex)
+{
+    int ch;
+
+    if (lex->peekpos) {
+        lex->peekpos--;
+        if (!lex->push_line && lex->peek[lex->peekpos] == '\n') {
+            lex->line++;
+            lex->column = 0;
+        }
+        return lex->peek[lex->peekpos];
+    }
+
+    ch = lex_fgetc(lex);
+    if (!lex->push_line && ch == '\n') {
+        lex->line++;
+        lex->column = 0;
+    }
+    else if (ch == '?')
+        return lex_try_trigraph(lex, ch);
+    else if (!lex->flags.nodigraphs && (ch == '<' || ch == ':' || ch == '%'))
+        return lex_try_digraph(lex, ch);
+    return ch;
+}
+
+static void lex_ungetch(lex_file *lex, int ch)
+{
+    lex->peek[lex->peekpos++] = ch;
+    lex->column--;
+    if (!lex->push_line && ch == '\n') {
+        lex->line--;
+        lex->column = 0;
+    }
+}
+
+/* classify characters
+ * some additions to the is*() functions of ctype.h
+ */
+
+/* Idents are alphanumberic, but they start with alpha or _ */
+static bool isident_start(int ch)
+{
+    return util_isalpha(ch) || ch == '_';
+}
+
+static bool isident(int ch)
+{
+    return isident_start(ch) || util_isdigit(ch);
+}
+
+/* isxdigit_only is used when we already know it's not a digit
+ * and want to see if it's a hex digit anyway.
+ */
+static bool isxdigit_only(int ch)
+{
+    return (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
+}
+
+/* Append a character to the token buffer */
+static void lex_tokench(lex_file *lex, int ch)
+{
+    vec_push(lex->tok.value, ch);
+}
+
+/* Append a trailing null-byte */
+static void lex_endtoken(lex_file *lex)
+{
+    vec_push(lex->tok.value, 0);
+    vec_shrinkby(lex->tok.value, 1);
+}
+
+static bool lex_try_pragma(lex_file *lex)
+{
+    int ch;
+    char *pragma  = NULL;
+    char *command = NULL;
+    char *param   = NULL;
+    size_t line;
+
+    if (lex->flags.preprocessing)
+        return false;
+
+    line = lex->line;
+
+    ch = lex_getch(lex);
+    if (ch != '#') {
+        lex_ungetch(lex, ch);
+        return false;
+    }
+
+    for (ch = lex_getch(lex); vec_size(pragma) < 8 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
+        vec_push(pragma, ch);
+    vec_push(pragma, 0);
+
+    if (ch != ' ' || strcmp(pragma, "pragma")) {
+        lex_ungetch(lex, ch);
+        goto unroll;
+    }
+
+    for (ch = lex_getch(lex); vec_size(command) < 32 && ch >= 'a' && ch <= 'z'; ch = lex_getch(lex))
+        vec_push(command, ch);
+    vec_push(command, 0);
+
+    if (ch != '(') {
+        lex_ungetch(lex, ch);
+        goto unroll;
+    }
+
+    for (ch = lex_getch(lex); vec_size(param) < 1024 && ch != ')' && ch != '\n'; ch = lex_getch(lex))
+        vec_push(param, ch);
+    vec_push(param, 0);
+
+    if (ch != ')') {
+        lex_ungetch(lex, ch);
+        goto unroll;
+    }
+
+    if (!strcmp(command, "push")) {
+        if (!strcmp(param, "line")) {
+            lex->push_line++;
+            if (lex->push_line == 1)
+                --line;
+        }
+        else
+            goto unroll;
+    }
+    else if (!strcmp(command, "pop")) {
+        if (!strcmp(param, "line")) {
+            if (lex->push_line)
+                lex->push_line--;
+            if (lex->push_line == 0)
+                --line;
+        }
+        else
+            goto unroll;
+    }
+    else if (!strcmp(command, "file")) {
+        lex->name = util_strdup(param);
+        vec_push(lex_filenames, lex->name);
+    }
+    else if (!strcmp(command, "line")) {
+        line = strtol(param, NULL, 0)-1;
+    }
+    else
+        goto unroll;
+
+    lex->line = line;
+    while (ch != '\n' && ch != EOF)
+        ch = lex_getch(lex);
+    vec_free(command);
+    vec_free(param);
+    vec_free(pragma);
+    return true;
+
+unroll:
+    if (command) {
+        vec_pop(command);
+        while (vec_size(command)) {
+            lex_ungetch(lex, (unsigned char)vec_last(command));
+            vec_pop(command);
+        }
+        vec_free(command);
+        lex_ungetch(lex, ' ');
+    }
+    if (param) {
+        vec_pop(param);
+        while (vec_size(param)) {
+            lex_ungetch(lex, (unsigned char)vec_last(param));
+            vec_pop(param);
+        }
+        vec_free(param);
+        lex_ungetch(lex, ' ');
+    }
+    if (pragma) {
+        vec_pop(pragma);
+        while (vec_size(pragma)) {
+            lex_ungetch(lex, (unsigned char)vec_last(pragma));
+            vec_pop(pragma);
+        }
+        vec_free(pragma);
+    }
+    lex_ungetch(lex, '#');
+
+    lex->line = line;
+    return false;
+}
+
+/* Skip whitespace and comments and return the first
+ * non-white character.
+ * As this makes use of the above getch() ungetch() functions,
+ * we don't need to care at all about line numbering anymore.
+ *
+ * In theory, this function should only be used at the beginning
+ * of lexing, or when we *know* the next character is part of the token.
+ * Otherwise, if the parser throws an error, the linenumber may not be
+ * the line of the error, but the line of the next token AFTER the error.
+ *
+ * This is currently only problematic when using c-like string-continuation,
+ * since comments and whitespaces are allowed between 2 such strings.
+ * Example:
+printf(   "line one\n"
+// A comment
+          "A continuation of the previous string"
+// This line is skipped
+      , foo);
+
+ * In this case, if the parse decides it didn't actually want a string,
+ * and uses lex->line to print an error, it will show the ', foo);' line's
+ * linenumber.
+ *
+ * On the other hand, the parser is supposed to remember the line of the next
+ * token's beginning. In this case we would want skipwhite() to be called
+ * AFTER reading a token, so that the parser, before reading the NEXT token,
+ * doesn't store teh *comment's* linenumber, but the actual token's linenumber.
+ *
+ * THIS SOLUTION
+ *    here is to store the line of the first character after skipping
+ *    the initial whitespace in lex->sline, this happens in lex_do.
+ */
+static int lex_skipwhite(lex_file *lex, bool hadwhite)
+{
+    int ch = 0;
+    bool haswhite = hadwhite;
+
+    do
+    {
+        ch = lex_getch(lex);
+        while (ch != EOF && util_isspace(ch)) {
+            if (ch == '\n') {
+                if (lex_try_pragma(lex))
+                    continue;
+            }
+            if (lex->flags.preprocessing) {
+                if (ch == '\n') {
+                    /* end-of-line */
+                    /* see if there was whitespace first */
+                    if (haswhite) { /* (vec_size(lex->tok.value)) { */
+                        lex_ungetch(lex, ch);
+                        lex_endtoken(lex);
+                        return TOKEN_WHITE;
+                    }
+                    /* otherwise return EOL */
+                    return TOKEN_EOL;
+                }
+                haswhite = true;
+                lex_tokench(lex, ch);
+            }
+            ch = lex_getch(lex);
+        }
+
+        if (ch == '/') {
+            ch = lex_getch(lex);
+            if (ch == '/')
+            {
+                /* one line comment */
+                ch = lex_getch(lex);
+
+                if (lex->flags.preprocessing) {
+                    haswhite = true;
+                    lex_tokench(lex, ' ');
+                    lex_tokench(lex, ' ');
+                }
+
+                while (ch != EOF && ch != '\n') {
+                    if (lex->flags.preprocessing)
+                        lex_tokench(lex, ' '); /* ch); */
+                    ch = lex_getch(lex);
+                }
+                if (lex->flags.preprocessing) {
+                    lex_ungetch(lex, '\n');
+                    lex_endtoken(lex);
+                    return TOKEN_WHITE;
+                }
+                continue;
+            }
+            if (ch == '*')
+            {
+                /* multiline comment */
+                if (lex->flags.preprocessing) {
+                    haswhite = true;
+                    lex_tokench(lex, ' ');
+                    lex_tokench(lex, ' ');
+                }
+
+                while (ch != EOF)
+                {
+                    ch = lex_getch(lex);
+                    if (ch == '*') {
+                        ch = lex_getch(lex);
+                        if (ch == '/') {
+                            if (lex->flags.preprocessing) {
+                                lex_tokench(lex, ' ');
+                                lex_tokench(lex, ' ');
+                            }
+                            break;
+                        }
+                        lex_ungetch(lex, ch);
+                    }
+                    if (lex->flags.preprocessing) {
+                        if (ch == '\n')
+                            lex_tokench(lex, '\n');
+                        else
+                            lex_tokench(lex, ' ');
+                    }
+                }
+                ch = ' '; /* cause TRUE in the isspace check */
+                continue;
+            }
+            /* Otherwise roll back to the slash and break out of the loop */
+            lex_ungetch(lex, ch);
+            ch = '/';
+            break;
+        }
+    } while (ch != EOF && util_isspace(ch));
+
+    if (haswhite) {
+        lex_endtoken(lex);
+        lex_ungetch(lex, ch);
+        return TOKEN_WHITE;
+    }
+    return ch;
+}
+
+/* Get a token */
+static bool GMQCC_WARN lex_finish_ident(lex_file *lex)
+{
+    int ch;
+
+    ch = lex_getch(lex);
+    while (ch != EOF && isident(ch))
+    {
+        lex_tokench(lex, ch);
+        ch = lex_getch(lex);
+    }
+
+    /* last ch was not an ident ch: */
+    lex_ungetch(lex, ch);
+
+    return true;
+}
+
+/* read one ident for the frame list */
+static int lex_parse_frame(lex_file *lex)
+{
+    int ch;
+
+    lex_token_new(lex);
+
+    ch = lex_getch(lex);
+    while (ch != EOF && ch != '\n' && util_isspace(ch))
+        ch = lex_getch(lex);
+
+    if (ch == '\n')
+        return 1;
+
+    if (!isident_start(ch)) {
+        lexerror(lex, "invalid framename, must start with one of a-z or _, got %c", ch);
+        return -1;
+    }
+
+    lex_tokench(lex, ch);
+    if (!lex_finish_ident(lex))
+        return -1;
+    lex_endtoken(lex);
+    return 0;
+}
+
+/* read a list of $frames */
+static bool lex_finish_frames(lex_file *lex)
+{
+    do {
+        size_t i;
+        int    rc;
+        frame_macro m;
+
+        rc = lex_parse_frame(lex);
+        if (rc > 0) /* end of line */
+            return true;
+        if (rc < 0) /* error */
+            return false;
+
+        for (i = 0; i < vec_size(lex->frames); ++i) {
+            if (!strcmp(lex->tok.value, lex->frames[i].name)) {
+                lex->frames[i].value = lex->framevalue++;
+                if (lexwarn(lex, WARN_FRAME_MACROS, "duplicate frame macro defined: `%s`", lex->tok.value))
+                    return false;
+                break;
+            }
+        }
+        if (i < vec_size(lex->frames))
+            continue;
+
+        m.value = lex->framevalue++;
+        m.name = util_strdup(lex->tok.value);
+        vec_shrinkto(lex->tok.value, 0);
+        vec_push(lex->frames, m);
+    } while (true);
+
+    return false;
+}
+
+static int GMQCC_WARN lex_finish_string(lex_file *lex, int quote)
+{
+    utf8ch_t chr = 0;
+    int ch = 0, texttype = 0;
+    int nextch;
+    bool hex;
+    bool oct;
+    char u8buf[8]; /* way more than enough */
+    int  u8len, uc;
+
+    while (ch != EOF)
+    {
+        ch = lex_getch(lex);
+        if (ch == quote)
+            return TOKEN_STRINGCONST;
+
+        if (lex->flags.preprocessing && ch == '\\') {
+            lex_tokench(lex, ch);
+            ch = lex_getch(lex);
+            if (ch == EOF) {
+                lexerror(lex, "unexpected end of file");
+                lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
+                return (lex->tok.ttype = TOKEN_ERROR);
+            }
+            lex_tokench(lex, ch);
+        }
+        else if (ch == '\\') {
+            ch = lex_getch(lex);
+            if (ch == EOF) {
+                lexerror(lex, "unexpected end of file");
+                lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
+                return (lex->tok.ttype = TOKEN_ERROR);
+            }
+
+            switch (ch) {
+            case '\\': break;
+            case '\'': break;
+            case '"':  break;
+            case 'a': ch = '\a'; break;
+            case 'r': ch = '\r'; break;
+            case 'n': ch = '\n'; break;
+            case 't': ch = '\t'; break;
+            case 'f': ch = '\f'; break;
+            case 'v': ch = '\v'; break;
+            case 'x':
+            case 'X':
+                /* same procedure as in fteqcc */
+                ch = 0;
+                nextch = lex_getch(lex);
+                if      (nextch >= '0' && nextch <= '9')
+                    ch += nextch - '0';
+                else if (nextch >= 'a' && nextch <= 'f')
+                    ch += nextch - 'a' + 10;
+                else if (nextch >= 'A' && nextch <= 'F')
+                    ch += nextch - 'A' + 10;
+                else {
+                    lexerror(lex, "bad character code");
+                    lex_ungetch(lex, nextch);
+                    return (lex->tok.ttype = TOKEN_ERROR);
+                }
+
+                ch *= 0x10;
+                nextch = lex_getch(lex);
+                if      (nextch >= '0' && nextch <= '9')
+                    ch += nextch - '0';
+                else if (nextch >= 'a' && nextch <= 'f')
+                    ch += nextch - 'a' + 10;
+                else if (nextch >= 'A' && nextch <= 'F')
+                    ch += nextch - 'A' + 10;
+                else {
+                    lexerror(lex, "bad character code");
+                    lex_ungetch(lex, nextch);
+                    return (lex->tok.ttype = TOKEN_ERROR);
+                }
+                break;
+
+            /* fteqcc support */
+            case '0': case '1': case '2': case '3':
+            case '4': case '5': case '6': case '7':
+            case '8': case '9':
+                ch = 18 + ch - '0';
+                break;
+            case '<':  ch = 29; break;
+            case '-':  ch = 30; break;
+            case '>':  ch = 31; break;
+            case '[':  ch = 16; break;
+            case ']':  ch = 17; break;
+            case '{':
+                chr = 0;
+                nextch = lex_getch(lex);
+                hex = (nextch == 'x');
+                oct = (nextch == '0');
+                if (!hex && !oct)
+                    lex_ungetch(lex, nextch);
+                for (nextch = lex_getch(lex); nextch != '}'; nextch = lex_getch(lex)) {
+                    if (!hex && !oct) {
+                        if (nextch >= '0' && nextch <= '9')
+                            chr = chr * 10 + nextch - '0';
+                        else {
+                            lexerror(lex, "bad character code");
+                            return (lex->tok.ttype = TOKEN_ERROR);
+                        }
+                    } else if (!oct) {
+                        if (nextch >= '0' && nextch <= '9')
+                            chr = chr * 0x10 + nextch - '0';
+                        else if (nextch >= 'a' && nextch <= 'f')
+                            chr = chr * 0x10 + nextch - 'a' + 10;
+                        else if (nextch >= 'A' && nextch <= 'F')
+                            chr = chr * 0x10 + nextch - 'A' + 10;
+                        else {
+                            lexerror(lex, "bad character code");
+                            return (lex->tok.ttype = TOKEN_ERROR);
+                        }
+                    } else {
+                        if (nextch >= '0' && nextch <= '9')
+                            chr = chr * 8 + chr - '0';
+                        else {
+                            lexerror(lex, "bad character code");
+                            return (lex->tok.ttype = TOKEN_ERROR);
+                        }
+                    }
+                    if (chr > 0x10FFFF || (!OPTS_FLAG(UTF8) && chr > 255))
+                    {
+                        lexerror(lex, "character code out of range");
+                        return (lex->tok.ttype = TOKEN_ERROR);
+                    }
+                }
+                if (OPTS_FLAG(UTF8) && chr >= 128) {
+                    u8len = utf8_from(u8buf, chr);
+                    if (!u8len)
+                        ch = 0;
+                    else {
+                        --u8len;
+                        lex->column += u8len;
+                        for (uc = 0; uc < u8len; ++uc)
+                            lex_tokench(lex, u8buf[uc]);
+                        /*
+                         * the last character will be inserted with the tokench() call
+                         * below the switch
+                         */
+                        ch = u8buf[uc];
+                    }
+                }
+                else
+                    ch = chr;
+                break;
+
+            /* high bit text */
+            case 'b': case 's':
+                texttype ^= 128;
+                continue;
+
+            case '\n':
+                ch = '\n';
+                break;
+
+            default:
+                lexwarn(lex, WARN_UNKNOWN_CONTROL_SEQUENCE, "unrecognized control sequence: \\%c", ch);
+                /* so we just add the character plus backslash no matter what it actually is */
+                lex_tokench(lex, '\\');
+            }
+            /* add the character finally */
+            lex_tokench(lex, ch | texttype);
+        }
+        else
+            lex_tokench(lex, ch);
+    }
+    lexerror(lex, "unexpected end of file within string constant");
+    lex_ungetch(lex, EOF); /* next token to be TOKEN_EOF */
+    return (lex->tok.ttype = TOKEN_ERROR);
+}
+
+static int GMQCC_WARN lex_finish_digit(lex_file *lex, int lastch)
+{
+    bool ishex = false;
+
+    int  ch = lastch;
+
+    /* parse a number... */
+    if (ch == '.')
+        lex->tok.ttype = TOKEN_FLOATCONST;
+    else
+        lex->tok.ttype = TOKEN_INTCONST;
+
+    lex_tokench(lex, ch);
+
+    ch = lex_getch(lex);
+    if (ch != '.' && !util_isdigit(ch))
+    {
+        if (lastch != '0' || ch != 'x')
+        {
+            /* end of the number or EOF */
+            lex_ungetch(lex, ch);
+            lex_endtoken(lex);
+
+            lex->tok.constval.i = lastch - '0';
+            return lex->tok.ttype;
+        }
+
+        ishex = true;
+    }
+
+    /* EOF would have been caught above */
+
+    if (ch != '.')
+    {
+        lex_tokench(lex, ch);
+        ch = lex_getch(lex);
+        while (util_isdigit(ch) || (ishex && isxdigit_only(ch)))
+        {
+            lex_tokench(lex, ch);
+            ch = lex_getch(lex);
+        }
+    }
+    /* NOT else, '.' can come from above as well */
+    if (lex->tok.ttype != TOKEN_FLOATCONST && ch == '.' && !ishex)
+    {
+        /* Allow floating comma in non-hex mode */
+        lex->tok.ttype = TOKEN_FLOATCONST;
+        lex_tokench(lex, ch);
+
+        /* continue digits-only */
+        ch = lex_getch(lex);
+        while (util_isdigit(ch))
+        {
+            lex_tokench(lex, ch);
+            ch = lex_getch(lex);
+        }
+    }
+    /* put back the last character */
+    /* but do not put back the trailing 'f' or a float */
+    if (lex->tok.ttype == TOKEN_FLOATCONST && ch == 'f')
+        ch = lex_getch(lex);
+
+    /* generally we don't want words to follow numbers: */
+    if (isident(ch)) {
+        lexerror(lex, "unexpected trailing characters after number");
+        return (lex->tok.ttype = TOKEN_ERROR);
+    }
+    lex_ungetch(lex, ch);
+
+    lex_endtoken(lex);
+    if (lex->tok.ttype == TOKEN_FLOATCONST)
+        lex->tok.constval.f = strtod(lex->tok.value, NULL);
+    else
+        lex->tok.constval.i = strtol(lex->tok.value, NULL, 0);
+    return lex->tok.ttype;
+}
+
+int lex_do(lex_file *lex)
+{
+    int ch, nextch, thirdch;
+    bool hadwhite = false;
+
+    lex_token_new(lex);
+
+    while (true) {
+        ch = lex_skipwhite(lex, hadwhite);
+        hadwhite = true;
+        if (!lex->flags.mergelines || ch != '\\')
+            break;
+        ch = lex_getch(lex);
+        if (ch == '\r')
+            ch = lex_getch(lex);
+        if (ch != '\n') {
+            lex_ungetch(lex, ch);
+            ch = '\\';
+            break;
+        }
+        /* we reached a linemerge */
+        lex_tokench(lex, '\n');
+        continue;
+    }
+
+    if (lex->flags.preprocessing && (ch == TOKEN_WHITE || ch == TOKEN_EOL || ch == TOKEN_FATAL)) {
+        return (lex->tok.ttype = ch);
+    }
+
+    lex->sline = lex->line;
+    lex->tok.ctx.line = lex->sline;
+    lex->tok.ctx.file = lex->name;
+
+    if (lex->eof)
+        return (lex->tok.ttype = TOKEN_FATAL);
+
+    if (ch == EOF) {
+        lex->eof = true;
+        return (lex->tok.ttype = TOKEN_EOF);
+    }
+
+    /* modelgen / spiritgen commands */
+    if (ch == '$' && !lex->flags.preprocessing) {
+        const char *v;
+        size_t frame;
+
+        ch = lex_getch(lex);
+        if (!isident_start(ch)) {
+            lexerror(lex, "hanging '$' modelgen/spritegen command line");
+            return lex_do(lex);
+        }
+        lex_tokench(lex, ch);
+        if (!lex_finish_ident(lex))
+            return (lex->tok.ttype = TOKEN_ERROR);
+        lex_endtoken(lex);
+        /* skip the known commands */
+        v = lex->tok.value;
+
+        if (!strcmp(v, "frame") || !strcmp(v, "framesave"))
+        {
+            /* frame/framesave command works like an enum
+             * similar to fteqcc we handle this in the lexer.
+             * The reason for this is that it is sensitive to newlines,
+             * which the parser is unaware of
+             */
+            if (!lex_finish_frames(lex))
+                 return (lex->tok.ttype = TOKEN_ERROR);
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "framevalue"))
+        {
+            ch = lex_getch(lex);
+            while (ch != EOF && util_isspace(ch) && ch != '\n')
+                ch = lex_getch(lex);
+
+            if (!util_isdigit(ch)) {
+                lexerror(lex, "$framevalue requires an integer parameter");
+                return lex_do(lex);
+            }
+
+            lex_token_new(lex);
+            lex->tok.ttype = lex_finish_digit(lex, ch);
+            lex_endtoken(lex);
+            if (lex->tok.ttype != TOKEN_INTCONST) {
+                lexerror(lex, "$framevalue requires an integer parameter");
+                return lex_do(lex);
+            }
+            lex->framevalue = lex->tok.constval.i;
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "framerestore"))
+        {
+            int rc;
+
+            lex_token_new(lex);
+
+            rc = lex_parse_frame(lex);
+
+            if (rc > 0) {
+                lexerror(lex, "$framerestore requires a framename parameter");
+                return lex_do(lex);
+            }
+            if (rc < 0)
+                return (lex->tok.ttype = TOKEN_FATAL);
+
+            v = lex->tok.value;
+            for (frame = 0; frame < vec_size(lex->frames); ++frame) {
+                if (!strcmp(v, lex->frames[frame].name)) {
+                    lex->framevalue = lex->frames[frame].value;
+                    return lex_do(lex);
+                }
+            }
+            lexerror(lex, "unknown framename `%s`", v);
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "modelname"))
+        {
+            int rc;
+
+            lex_token_new(lex);
+
+            rc = lex_parse_frame(lex);
+
+            if (rc > 0) {
+                lexerror(lex, "$modelname requires a parameter");
+                return lex_do(lex);
+            }
+            if (rc < 0)
+                return (lex->tok.ttype = TOKEN_FATAL);
+
+            if (lex->modelname) {
+                frame_macro m;
+                m.value = lex->framevalue;
+                m.name = lex->modelname;
+                lex->modelname = NULL;
+                vec_push(lex->frames, m);
+            }
+            lex->modelname = lex->tok.value;
+            lex->tok.value = NULL;
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "flush"))
+        {
+            size_t fi;
+            for (fi = 0; fi < vec_size(lex->frames); ++fi)
+                mem_d(lex->frames[fi].name);
+            vec_free(lex->frames);
+            /* skip line (fteqcc does it too) */
+            ch = lex_getch(lex);
+            while (ch != EOF && ch != '\n')
+                ch = lex_getch(lex);
+            return lex_do(lex);
+        }
+
+        if (!strcmp(v, "cd") ||
+            !strcmp(v, "origin") ||
+            !strcmp(v, "base") ||
+            !strcmp(v, "flags") ||
+            !strcmp(v, "scale") ||
+            !strcmp(v, "skin"))
+        {
+            /* skip line */
+            ch = lex_getch(lex);
+            while (ch != EOF && ch != '\n')
+                ch = lex_getch(lex);
+            return lex_do(lex);
+        }
+
+        for (frame = 0; frame < vec_size(lex->frames); ++frame) {
+            if (!strcmp(v, lex->frames[frame].name)) {
+                lex->tok.constval.i = lex->frames[frame].value;
+                return (lex->tok.ttype = TOKEN_INTCONST);
+            }
+        }
+
+        lexerror(lex, "invalid frame macro");
+        return lex_do(lex);
+    }
+
+    /* single-character tokens */
+    switch (ch)
+    {
+        case '[':
+            nextch = lex_getch(lex);
+            if (nextch == '[') {
+                lex_tokench(lex, ch);
+                lex_tokench(lex, nextch);
+                lex_endtoken(lex);
+                return (lex->tok.ttype = TOKEN_ATTRIBUTE_OPEN);
+            }
+            lex_ungetch(lex, nextch);
+            /* FALL THROUGH */
+        case '(':
+        case ':':
+        case '?':
+            lex_tokench(lex, ch);
+            lex_endtoken(lex);
+            if (lex->flags.noops)
+                return (lex->tok.ttype = ch);
+            else
+                return (lex->tok.ttype = TOKEN_OPERATOR);
+
+        case ']':
+            if (lex->flags.noops) {
+                nextch = lex_getch(lex);
+                if (nextch == ']') {
+                    lex_tokench(lex, ch);
+                    lex_tokench(lex, nextch);
+                    lex_endtoken(lex);
+                    return (lex->tok.ttype = TOKEN_ATTRIBUTE_CLOSE);
+                }
+                lex_ungetch(lex, nextch);
+            }
+            /* FALL THROUGH */
+        case ')':
+        case ';':
+        case '{':
+        case '}':
+
+        case '#':
+            lex_tokench(lex, ch);
+            lex_endtoken(lex);
+            return (lex->tok.ttype = ch);
+        default:
+            break;
+    }
+
+    if (ch == '.') {
+        nextch = lex_getch(lex);
+        /* digits starting with a dot */
+        if (util_isdigit(nextch)) {
+            lex_ungetch(lex, nextch);
+            lex->tok.ttype = lex_finish_digit(lex, ch);
+            lex_endtoken(lex);
+            return lex->tok.ttype;
+        }
+        lex_ungetch(lex, nextch);
+    }
+
+    if (lex->flags.noops)
+    {
+        /* Detect characters early which are normally
+         * operators OR PART of an operator.
+         */
+        switch (ch)
+        {
+            case '*':
+            case '/':
+            case '<':
+            case '>':
+            case '=':
+            case '&':
+            case '|':
+            case '^':
+            case '~':
+            case ',':
+            case '!':
+                lex_tokench(lex, ch);
+                lex_endtoken(lex);
+                return (lex->tok.ttype = ch);
+            default:
+                break;
+        }
+    }
+
+    if (ch == '.')
+    {
+        lex_tokench(lex, ch);
+        /* peak ahead once */
+        nextch = lex_getch(lex);
+        if (nextch != '.') {
+            lex_ungetch(lex, nextch);
+            lex_endtoken(lex);
+            if (lex->flags.noops)
+                return (lex->tok.ttype = ch);
+            else
+                return (lex->tok.ttype = TOKEN_OPERATOR);
+        }
+        /* peak ahead again */
+        nextch = lex_getch(lex);
+        if (nextch != '.') {
+            lex_ungetch(lex, nextch);
+            lex_ungetch(lex, '.');
+            lex_endtoken(lex);
+            if (lex->flags.noops)
+                return (lex->tok.ttype = ch);
+            else
+                return (lex->tok.ttype = TOKEN_OPERATOR);
+        }
+        /* fill the token to be "..." */
+        lex_tokench(lex, ch);
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_DOTS);
+    }
+
+    if (ch == ',' || ch == '.') {
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (ch == '+' || ch == '-' || /* ++, --, +=, -=  and -> as well! */
+        ch == '>' || ch == '<' || /* <<, >>, <=, >=  and >< as well! */
+        ch == '=' || ch == '!' || /* <=>, ==, !=                     */
+        ch == '&' || ch == '|' || /* &&, ||, &=, |=                  */
+        ch == '~' || ch == '^'    /* ~=, ~, ^                        */
+    )  {
+        lex_tokench(lex, ch);
+        nextch = lex_getch(lex);
+
+        if ((nextch == '=' && ch != '<') || (nextch == '<' && ch == '>'))
+            lex_tokench(lex, nextch);
+        else if (nextch == ch && ch != '!') {
+            lex_tokench(lex, nextch);
+            if ((thirdch = lex_getch(lex)) == '=')
+                lex_tokench(lex, thirdch);
+            else
+                lex_ungetch(lex, thirdch);
+        } else if (ch == '<' && nextch == '=') {
+            lex_tokench(lex, nextch);
+            if ((thirdch = lex_getch(lex)) == '>')
+                lex_tokench(lex, thirdch);
+            else
+                lex_ungetch(lex, thirdch);
+
+        } else if (ch == '-' && nextch == '>') {
+            lex_tokench(lex, nextch);
+        } else if (ch == '&' && nextch == '~') {
+            thirdch = lex_getch(lex);
+            if (thirdch != '=') {
+                lex_ungetch(lex, thirdch);
+                lex_ungetch(lex, nextch);
+            }
+            else {
+                lex_tokench(lex, nextch);
+                lex_tokench(lex, thirdch);
+            }
+        }
+        else if (lex->flags.preprocessing &&
+                 ch == '-' && util_isdigit(nextch))
+        {
+            lex->tok.ttype = lex_finish_digit(lex, nextch);
+            if (lex->tok.ttype == TOKEN_INTCONST)
+                lex->tok.constval.i = -lex->tok.constval.i;
+            else
+                lex->tok.constval.f = -lex->tok.constval.f;
+            lex_endtoken(lex);
+            return lex->tok.ttype;
+        } else {
+            lex_ungetch(lex, nextch);
+        }
+
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (ch == '*' || ch == '/') /* *=, /= */
+    {
+        lex_tokench(lex, ch);
+
+        nextch = lex_getch(lex);
+        if (nextch == '=' || nextch == '*') {
+            lex_tokench(lex, nextch);
+        } else
+            lex_ungetch(lex, nextch);
+
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (ch == '%') {
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = TOKEN_OPERATOR);
+    }
+
+    if (isident_start(ch))
+    {
+        const char *v;
+
+        lex_tokench(lex, ch);
+        if (!lex_finish_ident(lex)) {
+            /* error? */
+            return (lex->tok.ttype = TOKEN_ERROR);
+        }
+        lex_endtoken(lex);
+        lex->tok.ttype = TOKEN_IDENT;
+
+        v = lex->tok.value;
+        if (!strcmp(v, "void")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_VOID;
+        } else if (!strcmp(v, "int")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_INTEGER;
+        } else if (!strcmp(v, "float")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_FLOAT;
+        } else if (!strcmp(v, "string")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_STRING;
+        } else if (!strcmp(v, "entity")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_ENTITY;
+        } else if (!strcmp(v, "vector")) {
+            lex->tok.ttype = TOKEN_TYPENAME;
+            lex->tok.constval.t = TYPE_VECTOR;
+        } else if (!strcmp(v, "_length")) {
+            lex->tok.ttype = TOKEN_OPERATOR;
+        } else {
+            size_t kw;
+            for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_qc); ++kw) {
+                if (!strcmp(v, keywords_qc[kw]))
+                    return (lex->tok.ttype = TOKEN_KEYWORD);
+            }
+            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC) {
+                for (kw = 0; kw < GMQCC_ARRAY_COUNT(keywords_fg); ++kw) {
+                    if (!strcmp(v, keywords_fg[kw]))
+                        return (lex->tok.ttype = TOKEN_KEYWORD);
+                }
+            }
+        }
+
+        return lex->tok.ttype;
+    }
+
+    if (ch == '"')
+    {
+        lex->flags.nodigraphs = true;
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        lex->tok.ttype = lex_finish_string(lex, '"');
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        while (!lex->flags.preprocessing && lex->tok.ttype == TOKEN_STRINGCONST)
+        {
+            /* Allow c style "string" "continuation" */
+            ch = lex_skipwhite(lex, false);
+            if (ch != '"') {
+                lex_ungetch(lex, ch);
+                break;
+            }
+
+            lex->tok.ttype = lex_finish_string(lex, '"');
+        }
+        lex->flags.nodigraphs = false;
+        lex_endtoken(lex);
+        return lex->tok.ttype;
+    }
+
+    if (ch == '\'')
+    {
+        /* we parse character constants like string,
+         * but return TOKEN_CHARCONST, or a vector type if it fits...
+         * Likewise actual unescaping has to be done by the parser.
+         * The difference is we don't allow 'char' 'continuation'.
+         */
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        lex->tok.ttype = lex_finish_string(lex, '\'');
+        if (lex->flags.preprocessing)
+            lex_tokench(lex, ch);
+        lex_endtoken(lex);
+
+        lex->tok.ttype = TOKEN_CHARCONST;
+
+        /* It's a vector if we can successfully scan 3 floats */
+        if (util_sscanf(lex->tok.value, " %f %f %f ",
+                   &lex->tok.constval.v.x, &lex->tok.constval.v.y, &lex->tok.constval.v.z) == 3)
+
+        {
+             lex->tok.ttype = TOKEN_VECTORCONST;
+        }
+        else
+        {
+            if (!lex->flags.preprocessing && strlen(lex->tok.value) > 1) {
+                utf8ch_t u8char;
+                /* check for a valid utf8 character */
+                if (!OPTS_FLAG(UTF8) || !utf8_to(&u8char, (const unsigned char *)lex->tok.value, 8)) {
+                    if (lexwarn(lex, WARN_MULTIBYTE_CHARACTER,
+                                ( OPTS_FLAG(UTF8) ? "invalid multibyte character sequence `%s`"
+                                                  : "multibyte character: `%s`" ),
+                                lex->tok.value))
+                        return (lex->tok.ttype = TOKEN_ERROR);
+                }
+                else
+                    lex->tok.constval.i = u8char;
+            }
+            else
+                lex->tok.constval.i = lex->tok.value[0];
+        }
+
+        return lex->tok.ttype;
+    }
+
+    if (util_isdigit(ch))
+    {
+        lex->tok.ttype = lex_finish_digit(lex, ch);
+        lex_endtoken(lex);
+        return lex->tok.ttype;
+    }
+
+    if (lex->flags.preprocessing) {
+        lex_tokench(lex, ch);
+        lex_endtoken(lex);
+        return (lex->tok.ttype = ch);
+    }
+
+    lexerror(lex, "unknown token: `%c`", ch);
+    return (lex->tok.ttype = TOKEN_ERROR);
+}
diff --git a/main.c b/main.c
deleted file mode 100644 (file)
index ac715d5..0000000
--- a/main.c
+++ /dev/null
@@ -1,755 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-
-#include "gmqcc.h"
-#include "lexer.h"
-
-/* TODO: cleanup this whole file .. it's a fuckign mess */
-
-/* set by the standard */
-const oper_info *operators      = NULL;
-size_t           operator_count = 0;
-static bool      opts_output_wasset = false;
-
-typedef struct { char *filename; int   type;  } argitem;
-typedef struct { char *name;     char *value; } ppitem;
-static argitem *items = NULL;
-static ppitem  *ppems = NULL;
-
-#define TYPE_QC  0
-#define TYPE_ASM 1
-#define TYPE_SRC 2
-
-static const char *app_name;
-
-static void version(void) {
-    con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING,
-        GMQCC_VERSION_MAJOR,
-        GMQCC_VERSION_MINOR,
-        GMQCC_VERSION_PATCH,
-        __DATE__,
-        __TIME__
-    );
-}
-
-static int usage(void) {
-    con_out("usage: %s [options] [files...]", app_name);
-    con_out("options:\n"
-            "  -h, --help             show this help message\n"
-            "  -debug                 turns on compiler debug messages\n");
-    con_out("  -o, --output=file      output file, defaults to progs.dat\n"
-            "  -s filename            add a progs.src file to be used\n");
-    con_out("  -E                     stop after preprocessing\n");
-    con_out("  -q, --quiet            be less verbose\n");
-    con_out("  -config file           use the specified ini file\n");
-    con_out("  -std=standard          select one of the following standards\n"
-            "       -std=qcc          original QuakeC\n"
-            "       -std=fteqcc       fteqcc QuakeC\n"
-            "       -std=gmqcc        this compiler (default)\n");
-    con_out("  -f<flag>               enable a flag\n"
-            "  -fno-<flag>            disable a flag\n"
-            "  -fhelp                 list possible flags\n");
-    con_out("  -W<warning>            enable a warning\n"
-            "  -Wno-<warning>         disable a warning\n"
-            "  -Wall                  enable all warnings\n");
-    con_out("  -Werror                treat warnings as errors\n"
-            "  -Werror-<warning>      treat a warning as error\n"
-            "  -Wno-error-<warning>   opposite of the above\n");
-    con_out("  -Whelp                 list possible warnings\n");
-    con_out("  -O<number>             optimization level\n"
-            "  -O<name>               enable specific optimization\n"
-            "  -Ono-<name>            disable specific optimization\n"
-            "  -Ohelp                 list optimizations\n");
-    con_out("  -force-crc=num         force a specific checksum into the header\n");
-    con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
-    con_out("  -coverage              add coverage support\n");
-    return -1;
-}
-
-/* command line parsing */
-static bool options_witharg(int *argc_, char ***argv_, char **out) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    if (argv[0][2]) {
-        *out = argv[0]+2;
-        return true;
-    }
-    /* eat up the next */
-    if (argc < 2) /* no parameter was provided */
-        return false;
-
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-
-static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    size_t len = strlen(optname);
-
-    if (strncmp(argv[0]+ds, optname, len))
-        return false;
-
-    /* it's --optname, check how the parameter is supplied */
-    if (argv[0][ds+len] == '=') {
-        /* using --opt=param */
-        *out = argv[0]+ds+len+1;
-        return true;
-    }
-
-    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
-        return false;
-
-    /* using --opt param */
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) {
-    return options_long_witharg_all(optname, argc_, argv_, out, 2, true);
-}
-static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) {
-    return options_long_witharg_all(optname, argc_, argv_, out, 1, false);
-}
-
-static bool options_parse(int argc, char **argv) {
-    bool argend = false;
-    size_t itr;
-    char buffer[1024];
-    char *config = NULL;
-
-    while (!argend && argc > 1) {
-        char *argarg;
-        argitem item;
-        ppitem macro;
-
-        ++argv;
-        --argc;
-
-        if (argv[0][0] == '-') {
-            /* All gcc-type long options */
-            if (options_long_gcc("std", &argc, &argv, &argarg)) {
-                if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) {
-
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,          true);
-                    opts_set(opts.flags, CORRECT_LOGIC,                 true);
-                    opts_set(opts.flags, SHORT_LOGIC,                   true);
-                    opts_set(opts.flags, UNTYPED_NIL,                   true);
-                    opts_set(opts.flags, VARIADIC_ARGS,                 true);
-                    opts_set(opts.flags, FALSE_EMPTY_STRINGS,           false);
-                    opts_set(opts.flags, TRUE_EMPTY_STRINGS,            true);
-                    opts_set(opts.flags, LOOP_LABELS,                   true);
-                    opts_set(opts.flags, TRANSLATABLE_STRINGS,          true);
-                    opts_set(opts.flags, INITIALIZED_NONCONSTANTS,      true);
-                    opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true);
-                    opts_set(opts.werror, WARN_MISSING_RETURN_VALUES,   true);
-                    opts_set(opts.flags,  EXPRESSIONS_FOR_BUILTINS,     true);
-                    opts_set(opts.warn,   WARN_BREAKDEF,                true);
-
-
-
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC;
-
-                } else if (!strcmp(argarg, "qcc")) {
-
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
-                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true);
-
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC;
-
-                } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) {
-
-                    opts_set(opts.flags, FTEPP,                    true);
-                    opts_set(opts.flags, TRANSLATABLE_STRINGS,     true);
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,     false);
-                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES,    true);
-                    opts_set(opts.flags, CORRECT_TERNARY,          false);
-                    opts_set(opts.warn, WARN_TERNARY_PRECEDENCE,   true);
-                    opts_set(opts.warn, WARN_BREAKDEF,             true);
-
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC;
-
-                } else if (!strcmp(argarg, "qccx")) {
-
-                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
-                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX;
-
-                } else {
-                    con_out("Unknown standard: %s\n", argarg);
-                    return false;
-                }
-                continue;
-            }
-            if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
-
-                OPTS_OPTION_BOOL(OPTION_FORCECRC)   = true;
-                OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0);
-                continue;
-            }
-            if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
-                OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0);
-                opts_set(opts.flags, EMULATE_STATE, true);
-                continue;
-            }
-            if (options_long_gcc("config", &argc, &argv, &argarg)) {
-                config = argarg;
-                continue;
-            }
-            if (options_long_gcc("progsrc", &argc, &argv, &argarg)) {
-                OPTS_OPTION_STR(OPTION_PROGSRC) = argarg;
-                continue;
-            }
-
-            /* show defaults (like pathscale) */
-            if (!strcmp(argv[0]+1, "show-defaults")) {
-                for (itr = 0; itr < COUNT_FLAGS; ++itr) {
-                    if (!OPTS_FLAG(itr))
-                        continue;
-
-                    memset(buffer, 0, sizeof(buffer));
-                    util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1);
-
-                    con_out("-f%s ", buffer);
-                }
-                for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
-                    if (!OPTS_WARN(itr))
-                        continue;
-
-                    memset(buffer, 0, sizeof(buffer));
-                    util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1);
-                    con_out("-W%s ", buffer);
-                }
-                con_out("\n");
-                exit(0);
-            }
-
-            if (!strcmp(argv[0]+1, "debug")) {
-                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "dump")) {
-                OPTS_OPTION_BOOL(OPTION_DUMP)  = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "dumpfin")) {
-                OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "nocolor")) {
-                con_color(0);
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "coverage")) {
-                OPTS_OPTION_BOOL(OPTION_COVERAGE) = true;
-                continue;
-            }
-
-            switch (argv[0][1]) {
-                /* -h, show usage but exit with 0 */
-                case 'h':
-                    usage();
-                    exit(0);
-                    /* break; never reached because of exit(0) */
-
-                case 'v':
-                    version();
-                    exit(0);
-
-                case 'E':
-                    OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true;
-                    opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */
-                    break;
-
-                /* debug turns on -flno */
-                case 'g':
-                    opts_setflag("LNO", true);
-                    OPTS_OPTION_BOOL(OPTION_G) = true;
-                    break;
-
-                case 'q':
-                    OPTS_OPTION_BOOL(OPTION_QUIET) = true;
-                    break;
-
-                case 'D':
-                    if (!strlen(argv[0]+2)) {
-                        con_err("expected name after -D\n");
-                        exit(0);
-                    }
-
-                    if (!(argarg = strchr(argv[0] + 2, '='))) {
-                        macro.name  = util_strdup(argv[0]+2);
-                        macro.value = NULL;
-                    } else {
-                        *argarg='\0'; /* terminate for name */
-                        macro.name  = util_strdup(argv[0]+2);
-                        macro.value = util_strdup(argarg+1);
-                    }
-                    vec_push(ppems, macro);
-                    break;
-
-                /* handle all -fflags */
-                case 'f':
-                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
-                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
-                        con_out("Possible flags:\n\n");
-                        for (itr = 0; itr < COUNT_FLAGS; ++itr) {
-                            util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
-                            con_out(" -f%s\n", buffer);
-                        }
-                        exit(0);
-                    }
-                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
-                        if (!opts_setflag(argv[0]+5, false)) {
-                            con_out("unknown flag: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!opts_setflag(argv[0]+2, true)) {
-                        con_out("unknown flag: %s\n", argv[0]+2);
-                        return false;
-                    }
-                    break;
-                case 'W':
-                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
-                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
-                        con_out("Possible warnings:\n");
-                        for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
-                            util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
-                            con_out(" -W%s\n", buffer);
-                            if (itr == WARN_DEBUG)
-                                con_out("   Warnings included by -Wall:\n");
-                        }
-                        exit(0);
-                    }
-                    else if (!strcmp(argv[0]+2, "NO_ERROR") ||
-                             !strcmp(argv[0]+2, "NO_ERROR_ALL"))
-                    {
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
-                            opts.werror[itr] = 0;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "ERROR") ||
-                             !strcmp(argv[0]+2, "ERROR_ALL"))
-                    {
-                        opts_backup_non_Werror_all();
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
-                            opts.werror[itr] = 0xFFFFFFFFL;
-                        opts_restore_non_Werror_all();
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "NONE")) {
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
-                            opts.warn[itr] = 0;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "ALL")) {
-                        opts_backup_non_Wall();
-                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
-                            opts.warn[itr] = 0xFFFFFFFFL;
-                        opts_restore_non_Wall();
-                        break;
-                    }
-                    else if (!strncmp(argv[0]+2, "ERROR_", 6)) {
-                        if (!opts_setwerror(argv[0]+8, true)) {
-                            con_out("unknown warning: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) {
-                        if (!opts_setwerror(argv[0]+11, false)) {
-                            con_out("unknown warning: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
-                        if (!opts_setwarn(argv[0]+5, false)) {
-                            con_out("unknown warning: %s\n", argv[0]+2);
-                            return false;
-                        }
-                    }
-                    else if (!opts_setwarn(argv[0]+2, true)) {
-                        con_out("unknown warning: %s\n", argv[0]+2);
-                        return false;
-                    }
-                    break;
-
-                case 'O':
-                    if (!options_witharg(&argc, &argv, &argarg)) {
-                        con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n");
-                        return false;
-                    }
-                    if (util_isdigit(argarg[0])) {
-                        uint32_t val = (uint32_t)strtol(argarg, NULL, 10);
-                        OPTS_OPTION_U32(OPTION_O) = val;
-                        opts_setoptimlevel(val);
-                    } else {
-                        util_strtocmd(argarg, argarg, strlen(argarg)+1);
-                        if (!strcmp(argarg, "HELP")) {
-                            con_out("Possible optimizations:\n");
-                            for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
-                                util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer));
-                                con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]);
-                            }
-                            exit(0);
-                        }
-                        else if (!strcmp(argarg, "ALL"))
-                            opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999);
-                        else if (!strncmp(argarg, "NO_", 3)) {
-                            /* constant folding cannot be turned off for obvious reasons */
-                            if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) {
-                                con_out("unknown optimization: %s\n", argarg+3);
-                                return false;
-                            }
-                        }
-                        else {
-                            if (!opts_setoptim(argarg, true)) {
-                                con_out("unknown optimization: %s\n", argarg);
-                                return false;
-                            }
-                        }
-                    }
-                    break;
-
-                case 'o':
-                    if (!options_witharg(&argc, &argv, &argarg)) {
-                        con_out("option -o requires an argument: the output file name\n");
-                        return false;
-                    }
-                    OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
-                    opts_output_wasset = true;
-                    break;
-
-                case 'a':
-                case 's':
-                    item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC;
-                    if (!options_witharg(&argc, &argv, &argarg)) {
-                        con_out("option -a requires a filename %s\n",
-                                (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list"));
-                        return false;
-                    }
-                    item.filename = argarg;
-                    vec_push(items, item);
-                    break;
-
-                case '-':
-                    if (!argv[0][2]) {
-                        /* anything following -- is considered a non-option argument */
-                        argend = true;
-                        break;
-                    }
-            /* All long options without arguments */
-                    else if (!strcmp(argv[0]+2, "help")) {
-                        usage();
-                        exit(0);
-                    }
-                    else if (!strcmp(argv[0]+2, "version")) {
-                        version();
-                        exit(0);
-                    }
-                    else if (!strcmp(argv[0]+2, "quiet")) {
-                        OPTS_OPTION_BOOL(OPTION_QUIET) = true;
-                        break;
-                    }
-                    else if (!strcmp(argv[0]+2, "add-info")) {
-                        OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true;
-                        break;
-                    }
-                    else {
-            /* All long options with arguments */
-                        if (options_long_witharg("output", &argc, &argv, &argarg)) {
-                            OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
-                            opts_output_wasset = true;
-                        } else {
-                            con_out("Unknown parameter: %s\n", argv[0]);
-                            return false;
-                        }
-                    }
-                    break;
-
-                default:
-                    con_out("Unknown parameter: %s\n", argv[0]);
-                    return false;
-            }
-        }
-        else
-        {
-            /* it's a QC filename */
-            item.filename = argv[0];
-            item.type     = TYPE_QC;
-            vec_push(items, item);
-        }
-    }
-    opts_ini_init(config);
-    return true;
-}
-
-/* returns the line number, or -1 on error */
-static bool progs_nextline(char **out, size_t *alen, FILE *src) {
-    int    len;
-    char  *line;
-    char  *start;
-    char  *end;
-
-    line = *out;
-    len  = util_getline(&line, alen, src);
-    if (len == -1)
-        return false;
-
-    /* start at first non-blank */
-    for (start = line; util_isspace(*start); ++start) {}
-    /* end at the first non-blank */
-    for (end = start; *end && !util_isspace(*end);  ++end)   {}
-
-    *out = line;
-    /* move the actual filename to the beginning */
-    while (start != end) {
-        *line++ = *start++;
-    }
-    *line = 0;
-    return true;
-}
-
-int main(int argc, char **argv) {
-    size_t          itr;
-    int             retval           = 0;
-    bool            operators_free   = false;
-    bool            progs_src        = false;
-    FILE       *outfile         = NULL;
-    struct parser_s *parser          = NULL;
-    struct ftepp_s  *ftepp           = NULL;
-
-    app_name = argv[0];
-    con_init ();
-    opts_init("progs.dat", COMPILER_QCC, (1024 << 3));
-
-    util_seed(time(0));
-
-    if (!options_parse(argc, argv)) {
-        return usage();
-    }
-
-    if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) {
-        con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive");
-        exit(EXIT_FAILURE);
-    }
-
-    /* the standard decides which set of operators to use */
-    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
-        operators      = c_operators;
-        operator_count = GMQCC_ARRAY_COUNT(c_operators);
-    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
-        operators      = fte_operators;
-        operator_count = GMQCC_ARRAY_COUNT(fte_operators);
-    } else {
-        operators      = qcc_operators;
-        operator_count = GMQCC_ARRAY_COUNT(qcc_operators);
-    }
-
-    if (operators == fte_operators) {
-        /* fix ternary? */
-        if (OPTS_FLAG(CORRECT_TERNARY)) {
-            oper_info *newops;
-            if (operators[operator_count-2].id != opid1(',') ||
-                operators[operator_count-1].id != opid2(':','?'))
-            {
-                con_err("internal error: operator precedence table wasn't updated correctly!\n");
-                exit(EXIT_FAILURE);
-            }
-            operators_free = true;
-            newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count);
-            memcpy(newops, operators, sizeof(operators[0]) * operator_count);
-            memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0]));
-            memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0]));
-            newops[operator_count-2].prec = newops[operator_count-1].prec+1;
-            operators = newops;
-        }
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_DUMP)) {
-        for (itr = 0; itr < COUNT_FLAGS; ++itr)
-            con_out("Flag %s = %i\n",    opts_flag_list[itr].name, OPTS_FLAG(itr));
-        for (itr = 0; itr < COUNT_WARNINGS; ++itr)
-            con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
-
-        con_out("output             = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT));
-        con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O));
-        con_out("standard           = %u\n", OPTS_OPTION_U32(OPTION_STANDARD));
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-        if (opts_output_wasset) {
-            outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb");
-            if (!outfile) {
-                con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT));
-                retval = 1;
-                goto cleanup;
-            }
-        }
-        else {
-            outfile = con_default_out();
-        }
-    }
-
-    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-        if (!(parser = parser_create())) {
-            con_err("failed to initialize parser\n");
-            retval = 1;
-            goto cleanup;
-        }
-    }
-
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
-        if (!(ftepp = ftepp_create())) {
-            con_err("failed to initialize parser\n");
-            retval = 1;
-            goto cleanup;
-        }
-    }
-
-    /* add macros */
-    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
-        for (itr = 0; itr < vec_size(ppems); itr++) {
-            ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value);
-            mem_d(ppems[itr].name);
-
-            /* can be null */
-            if (ppems[itr].value)
-                mem_d(ppems[itr].value);
-        }
-    }
-
-    if (!vec_size(items)) {
-        FILE *src;
-        char      *line    = NULL;
-        size_t     linelen = 0;
-        bool       hasline = false;
-
-        progs_src = true;
-
-        src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb");
-        if (!src) {
-            con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC));
-            retval = 1;
-            goto cleanup;
-        }
-
-        while (progs_nextline(&line, &linelen, src)) {
-            argitem item;
-
-            if (!line[0] || (line[0] == '/' && line[1] == '/'))
-                continue;
-
-            if (hasline) {
-                item.filename = util_strdup(line);
-                item.type     = TYPE_QC;
-                vec_push(items, item);
-            } else if (!opts_output_wasset) {
-                OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line);
-                hasline                        = true;
-            }
-        }
-
-        fclose(src);
-        mem_d(line);
-    }
-
-    if (vec_size(items)) {
-        if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
-            !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-        {
-            con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
-            con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
-        }
-
-        for (itr = 0; itr < vec_size(items); ++itr) {
-            if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
-                !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-            {
-                con_out("  item: %s (%s)\n",
-                       items[itr].filename,
-                       ( (items[itr].type == TYPE_QC ? "qc" :
-                         (items[itr].type == TYPE_ASM ? "asm" :
-                         (items[itr].type == TYPE_SRC ? "progs.src" :
-                         ("unknown"))))));
-            }
-
-            if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-                const char *out;
-                if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
-                    retval = 1;
-                    goto cleanup;
-                }
-                out = ftepp_get(ftepp);
-                if (out)
-                    fprintf(outfile, "%s", out);
-                ftepp_flush(ftepp);
-            }
-            else {
-                if (OPTS_FLAG(FTEPP)) {
-                    const char *data;
-                    if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
-                        retval = 1;
-                        goto cleanup;
-                    }
-                    data = ftepp_get(ftepp);
-                    if (vec_size(data)) {
-                        if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) {
-                            retval = 1;
-                            goto cleanup;
-                        }
-                    }
-                    ftepp_flush(ftepp);
-                }
-                else {
-                    if (!parser_compile_file(parser, items[itr].filename)) {
-                        retval = 1;
-                        goto cleanup;
-                    }
-                }
-            }
-
-            if (progs_src) {
-                mem_d(items[itr].filename);
-                items[itr].filename = NULL;
-            }
-        }
-
-        ftepp_finish(ftepp);
-        ftepp = NULL;
-        if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
-            if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) {
-                retval = 1;
-                goto cleanup;
-            }
-        }
-    }
-
-cleanup:
-    if (ftepp)
-        ftepp_finish(ftepp);
-    con_close();
-    vec_free(items);
-    vec_free(ppems);
-
-    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY))
-        if(parser) parser_cleanup(parser);
-
-    /* free allocated option strings */
-    for (itr = 0; itr < OPTION_COUNT; itr++)
-        if (OPTS_OPTION_DUPED(itr))
-            mem_d(OPTS_OPTION_STR(itr));
-
-    if (operators_free)
-        mem_d((void*)operators);
-
-    lex_cleanup();
-
-    if (!retval && compile_errors)
-        retval = 1;
-    return retval;
-}
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..ac715d5
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,755 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "gmqcc.h"
+#include "lexer.h"
+
+/* TODO: cleanup this whole file .. it's a fuckign mess */
+
+/* set by the standard */
+const oper_info *operators      = NULL;
+size_t           operator_count = 0;
+static bool      opts_output_wasset = false;
+
+typedef struct { char *filename; int   type;  } argitem;
+typedef struct { char *name;     char *value; } ppitem;
+static argitem *items = NULL;
+static ppitem  *ppems = NULL;
+
+#define TYPE_QC  0
+#define TYPE_ASM 1
+#define TYPE_SRC 2
+
+static const char *app_name;
+
+static void version(void) {
+    con_out("GMQCC %d.%d.%d Built %s %s\n" GMQCC_DEV_VERSION_STRING,
+        GMQCC_VERSION_MAJOR,
+        GMQCC_VERSION_MINOR,
+        GMQCC_VERSION_PATCH,
+        __DATE__,
+        __TIME__
+    );
+}
+
+static int usage(void) {
+    con_out("usage: %s [options] [files...]", app_name);
+    con_out("options:\n"
+            "  -h, --help             show this help message\n"
+            "  -debug                 turns on compiler debug messages\n");
+    con_out("  -o, --output=file      output file, defaults to progs.dat\n"
+            "  -s filename            add a progs.src file to be used\n");
+    con_out("  -E                     stop after preprocessing\n");
+    con_out("  -q, --quiet            be less verbose\n");
+    con_out("  -config file           use the specified ini file\n");
+    con_out("  -std=standard          select one of the following standards\n"
+            "       -std=qcc          original QuakeC\n"
+            "       -std=fteqcc       fteqcc QuakeC\n"
+            "       -std=gmqcc        this compiler (default)\n");
+    con_out("  -f<flag>               enable a flag\n"
+            "  -fno-<flag>            disable a flag\n"
+            "  -fhelp                 list possible flags\n");
+    con_out("  -W<warning>            enable a warning\n"
+            "  -Wno-<warning>         disable a warning\n"
+            "  -Wall                  enable all warnings\n");
+    con_out("  -Werror                treat warnings as errors\n"
+            "  -Werror-<warning>      treat a warning as error\n"
+            "  -Wno-error-<warning>   opposite of the above\n");
+    con_out("  -Whelp                 list possible warnings\n");
+    con_out("  -O<number>             optimization level\n"
+            "  -O<name>               enable specific optimization\n"
+            "  -Ono-<name>            disable specific optimization\n"
+            "  -Ohelp                 list optimizations\n");
+    con_out("  -force-crc=num         force a specific checksum into the header\n");
+    con_out("  -state-fps=num         emulate OP_STATE with the specified FPS\n");
+    con_out("  -coverage              add coverage support\n");
+    return -1;
+}
+
+/* command line parsing */
+static bool options_witharg(int *argc_, char ***argv_, char **out) {
+    int  argc   = *argc_;
+    char **argv = *argv_;
+
+    if (argv[0][2]) {
+        *out = argv[0]+2;
+        return true;
+    }
+    /* eat up the next */
+    if (argc < 2) /* no parameter was provided */
+        return false;
+
+    *out = argv[1];
+    --*argc_;
+    ++*argv_;
+    return true;
+}
+
+static bool options_long_witharg_all(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
+    int  argc   = *argc_;
+    char **argv = *argv_;
+
+    size_t len = strlen(optname);
+
+    if (strncmp(argv[0]+ds, optname, len))
+        return false;
+
+    /* it's --optname, check how the parameter is supplied */
+    if (argv[0][ds+len] == '=') {
+        /* using --opt=param */
+        *out = argv[0]+ds+len+1;
+        return true;
+    }
+
+    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
+        return false;
+
+    /* using --opt param */
+    *out = argv[1];
+    --*argc_;
+    ++*argv_;
+    return true;
+}
+static bool options_long_witharg(const char *optname, int *argc_, char ***argv_, char **out) {
+    return options_long_witharg_all(optname, argc_, argv_, out, 2, true);
+}
+static bool options_long_gcc(const char *optname, int *argc_, char ***argv_, char **out) {
+    return options_long_witharg_all(optname, argc_, argv_, out, 1, false);
+}
+
+static bool options_parse(int argc, char **argv) {
+    bool argend = false;
+    size_t itr;
+    char buffer[1024];
+    char *config = NULL;
+
+    while (!argend && argc > 1) {
+        char *argarg;
+        argitem item;
+        ppitem macro;
+
+        ++argv;
+        --argc;
+
+        if (argv[0][0] == '-') {
+            /* All gcc-type long options */
+            if (options_long_gcc("std", &argc, &argv, &argarg)) {
+                if (!strcmp(argarg, "gmqcc") || !strcmp(argarg, "default")) {
+
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,          true);
+                    opts_set(opts.flags, CORRECT_LOGIC,                 true);
+                    opts_set(opts.flags, SHORT_LOGIC,                   true);
+                    opts_set(opts.flags, UNTYPED_NIL,                   true);
+                    opts_set(opts.flags, VARIADIC_ARGS,                 true);
+                    opts_set(opts.flags, FALSE_EMPTY_STRINGS,           false);
+                    opts_set(opts.flags, TRUE_EMPTY_STRINGS,            true);
+                    opts_set(opts.flags, LOOP_LABELS,                   true);
+                    opts_set(opts.flags, TRANSLATABLE_STRINGS,          true);
+                    opts_set(opts.flags, INITIALIZED_NONCONSTANTS,      true);
+                    opts_set(opts.werror, WARN_INVALID_PARAMETER_COUNT, true);
+                    opts_set(opts.werror, WARN_MISSING_RETURN_VALUES,   true);
+                    opts_set(opts.flags,  EXPRESSIONS_FOR_BUILTINS,     true);
+                    opts_set(opts.warn,   WARN_BREAKDEF,                true);
+
+
+
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_GMQCC;
+
+                } else if (!strcmp(argarg, "qcc")) {
+
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
+                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES, true);
+
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCC;
+
+                } else if (!strcmp(argarg, "fte") || !strcmp(argarg, "fteqcc")) {
+
+                    opts_set(opts.flags, FTEPP,                    true);
+                    opts_set(opts.flags, TRANSLATABLE_STRINGS,     true);
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,     false);
+                    opts_set(opts.flags, ASSIGN_FUNCTION_TYPES,    true);
+                    opts_set(opts.flags, CORRECT_TERNARY,          false);
+                    opts_set(opts.warn, WARN_TERNARY_PRECEDENCE,   true);
+                    opts_set(opts.warn, WARN_BREAKDEF,             true);
+
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_FTEQCC;
+
+                } else if (!strcmp(argarg, "qccx")) {
+
+                    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,  false);
+                    OPTS_OPTION_U32(OPTION_STANDARD) = COMPILER_QCCX;
+
+                } else {
+                    con_out("Unknown standard: %s\n", argarg);
+                    return false;
+                }
+                continue;
+            }
+            if (options_long_gcc("force-crc", &argc, &argv, &argarg)) {
+
+                OPTS_OPTION_BOOL(OPTION_FORCECRC)   = true;
+                OPTS_OPTION_U16 (OPTION_FORCED_CRC) = strtol(argarg, NULL, 0);
+                continue;
+            }
+            if (options_long_gcc("state-fps", &argc, &argv, &argarg)) {
+                OPTS_OPTION_U32(OPTION_STATE_FPS) = strtol(argarg, NULL, 0);
+                opts_set(opts.flags, EMULATE_STATE, true);
+                continue;
+            }
+            if (options_long_gcc("config", &argc, &argv, &argarg)) {
+                config = argarg;
+                continue;
+            }
+            if (options_long_gcc("progsrc", &argc, &argv, &argarg)) {
+                OPTS_OPTION_STR(OPTION_PROGSRC) = argarg;
+                continue;
+            }
+
+            /* show defaults (like pathscale) */
+            if (!strcmp(argv[0]+1, "show-defaults")) {
+                for (itr = 0; itr < COUNT_FLAGS; ++itr) {
+                    if (!OPTS_FLAG(itr))
+                        continue;
+
+                    memset(buffer, 0, sizeof(buffer));
+                    util_strtononcmd(opts_flag_list[itr].name, buffer, strlen(opts_flag_list[itr].name) + 1);
+
+                    con_out("-f%s ", buffer);
+                }
+                for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
+                    if (!OPTS_WARN(itr))
+                        continue;
+
+                    memset(buffer, 0, sizeof(buffer));
+                    util_strtononcmd(opts_warn_list[itr].name, buffer, strlen(opts_warn_list[itr].name) + 1);
+                    con_out("-W%s ", buffer);
+                }
+                con_out("\n");
+                exit(0);
+            }
+
+            if (!strcmp(argv[0]+1, "debug")) {
+                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "dump")) {
+                OPTS_OPTION_BOOL(OPTION_DUMP)  = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "dumpfin")) {
+                OPTS_OPTION_BOOL(OPTION_DUMPFIN) = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "nocolor")) {
+                con_color(0);
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "coverage")) {
+                OPTS_OPTION_BOOL(OPTION_COVERAGE) = true;
+                continue;
+            }
+
+            switch (argv[0][1]) {
+                /* -h, show usage but exit with 0 */
+                case 'h':
+                    usage();
+                    exit(0);
+                    /* break; never reached because of exit(0) */
+
+                case 'v':
+                    version();
+                    exit(0);
+
+                case 'E':
+                    OPTS_OPTION_BOOL(OPTION_PP_ONLY) = true;
+                    opts_set(opts.flags, FTEPP_PREDEFS, true); /* predefs on for -E */
+                    break;
+
+                /* debug turns on -flno */
+                case 'g':
+                    opts_setflag("LNO", true);
+                    OPTS_OPTION_BOOL(OPTION_G) = true;
+                    break;
+
+                case 'q':
+                    OPTS_OPTION_BOOL(OPTION_QUIET) = true;
+                    break;
+
+                case 'D':
+                    if (!strlen(argv[0]+2)) {
+                        con_err("expected name after -D\n");
+                        exit(0);
+                    }
+
+                    if (!(argarg = strchr(argv[0] + 2, '='))) {
+                        macro.name  = util_strdup(argv[0]+2);
+                        macro.value = NULL;
+                    } else {
+                        *argarg='\0'; /* terminate for name */
+                        macro.name  = util_strdup(argv[0]+2);
+                        macro.value = util_strdup(argarg+1);
+                    }
+                    vec_push(ppems, macro);
+                    break;
+
+                /* handle all -fflags */
+                case 'f':
+                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
+                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
+                        con_out("Possible flags:\n\n");
+                        for (itr = 0; itr < COUNT_FLAGS; ++itr) {
+                            util_strtononcmd(opts_flag_list[itr].name, buffer, sizeof(buffer));
+                            con_out(" -f%s\n", buffer);
+                        }
+                        exit(0);
+                    }
+                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
+                        if (!opts_setflag(argv[0]+5, false)) {
+                            con_out("unknown flag: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!opts_setflag(argv[0]+2, true)) {
+                        con_out("unknown flag: %s\n", argv[0]+2);
+                        return false;
+                    }
+                    break;
+                case 'W':
+                    util_strtocmd(argv[0]+2, argv[0]+2, strlen(argv[0]+2)+1);
+                    if (!strcmp(argv[0]+2, "HELP") || *(argv[0]+2) == '?') {
+                        con_out("Possible warnings:\n");
+                        for (itr = 0; itr < COUNT_WARNINGS; ++itr) {
+                            util_strtononcmd(opts_warn_list[itr].name, buffer, sizeof(buffer));
+                            con_out(" -W%s\n", buffer);
+                            if (itr == WARN_DEBUG)
+                                con_out("   Warnings included by -Wall:\n");
+                        }
+                        exit(0);
+                    }
+                    else if (!strcmp(argv[0]+2, "NO_ERROR") ||
+                             !strcmp(argv[0]+2, "NO_ERROR_ALL"))
+                    {
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
+                            opts.werror[itr] = 0;
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "ERROR") ||
+                             !strcmp(argv[0]+2, "ERROR_ALL"))
+                    {
+                        opts_backup_non_Werror_all();
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.werror); ++itr)
+                            opts.werror[itr] = 0xFFFFFFFFL;
+                        opts_restore_non_Werror_all();
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "NONE")) {
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
+                            opts.warn[itr] = 0;
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "ALL")) {
+                        opts_backup_non_Wall();
+                        for (itr = 0; itr < GMQCC_ARRAY_COUNT(opts.warn); ++itr)
+                            opts.warn[itr] = 0xFFFFFFFFL;
+                        opts_restore_non_Wall();
+                        break;
+                    }
+                    else if (!strncmp(argv[0]+2, "ERROR_", 6)) {
+                        if (!opts_setwerror(argv[0]+8, true)) {
+                            con_out("unknown warning: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!strncmp(argv[0]+2, "NO_ERROR_", 9)) {
+                        if (!opts_setwerror(argv[0]+11, false)) {
+                            con_out("unknown warning: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!strncmp(argv[0]+2, "NO_", 3)) {
+                        if (!opts_setwarn(argv[0]+5, false)) {
+                            con_out("unknown warning: %s\n", argv[0]+2);
+                            return false;
+                        }
+                    }
+                    else if (!opts_setwarn(argv[0]+2, true)) {
+                        con_out("unknown warning: %s\n", argv[0]+2);
+                        return false;
+                    }
+                    break;
+
+                case 'O':
+                    if (!options_witharg(&argc, &argv, &argarg)) {
+                        con_out("option -O requires a numerical argument, or optimization name with an optional 'no-' prefix\n");
+                        return false;
+                    }
+                    if (util_isdigit(argarg[0])) {
+                        uint32_t val = (uint32_t)strtol(argarg, NULL, 10);
+                        OPTS_OPTION_U32(OPTION_O) = val;
+                        opts_setoptimlevel(val);
+                    } else {
+                        util_strtocmd(argarg, argarg, strlen(argarg)+1);
+                        if (!strcmp(argarg, "HELP")) {
+                            con_out("Possible optimizations:\n");
+                            for (itr = 0; itr < COUNT_OPTIMIZATIONS; ++itr) {
+                                util_strtononcmd(opts_opt_list[itr].name, buffer, sizeof(buffer));
+                                con_out(" -O%-20s (-O%u)\n", buffer, opts_opt_oflag[itr]);
+                            }
+                            exit(0);
+                        }
+                        else if (!strcmp(argarg, "ALL"))
+                            opts_setoptimlevel(OPTS_OPTION_U32(OPTION_O) = 9999);
+                        else if (!strncmp(argarg, "NO_", 3)) {
+                            /* constant folding cannot be turned off for obvious reasons */
+                            if (!strcmp(argarg, "NO_CONST_FOLD") || !opts_setoptim(argarg+3, false)) {
+                                con_out("unknown optimization: %s\n", argarg+3);
+                                return false;
+                            }
+                        }
+                        else {
+                            if (!opts_setoptim(argarg, true)) {
+                                con_out("unknown optimization: %s\n", argarg);
+                                return false;
+                            }
+                        }
+                    }
+                    break;
+
+                case 'o':
+                    if (!options_witharg(&argc, &argv, &argarg)) {
+                        con_out("option -o requires an argument: the output file name\n");
+                        return false;
+                    }
+                    OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
+                    opts_output_wasset = true;
+                    break;
+
+                case 'a':
+                case 's':
+                    item.type = argv[0][1] == 'a' ? TYPE_ASM : TYPE_SRC;
+                    if (!options_witharg(&argc, &argv, &argarg)) {
+                        con_out("option -a requires a filename %s\n",
+                                (argv[0][1] == 'a' ? "containing QC-asm" : "containing a progs.src formatted list"));
+                        return false;
+                    }
+                    item.filename = argarg;
+                    vec_push(items, item);
+                    break;
+
+                case '-':
+                    if (!argv[0][2]) {
+                        /* anything following -- is considered a non-option argument */
+                        argend = true;
+                        break;
+                    }
+            /* All long options without arguments */
+                    else if (!strcmp(argv[0]+2, "help")) {
+                        usage();
+                        exit(0);
+                    }
+                    else if (!strcmp(argv[0]+2, "version")) {
+                        version();
+                        exit(0);
+                    }
+                    else if (!strcmp(argv[0]+2, "quiet")) {
+                        OPTS_OPTION_BOOL(OPTION_QUIET) = true;
+                        break;
+                    }
+                    else if (!strcmp(argv[0]+2, "add-info")) {
+                        OPTS_OPTION_BOOL(OPTION_ADD_INFO) = true;
+                        break;
+                    }
+                    else {
+            /* All long options with arguments */
+                        if (options_long_witharg("output", &argc, &argv, &argarg)) {
+                            OPTS_OPTION_STR(OPTION_OUTPUT) = argarg;
+                            opts_output_wasset = true;
+                        } else {
+                            con_out("Unknown parameter: %s\n", argv[0]);
+                            return false;
+                        }
+                    }
+                    break;
+
+                default:
+                    con_out("Unknown parameter: %s\n", argv[0]);
+                    return false;
+            }
+        }
+        else
+        {
+            /* it's a QC filename */
+            item.filename = argv[0];
+            item.type     = TYPE_QC;
+            vec_push(items, item);
+        }
+    }
+    opts_ini_init(config);
+    return true;
+}
+
+/* returns the line number, or -1 on error */
+static bool progs_nextline(char **out, size_t *alen, FILE *src) {
+    int    len;
+    char  *line;
+    char  *start;
+    char  *end;
+
+    line = *out;
+    len  = util_getline(&line, alen, src);
+    if (len == -1)
+        return false;
+
+    /* start at first non-blank */
+    for (start = line; util_isspace(*start); ++start) {}
+    /* end at the first non-blank */
+    for (end = start; *end && !util_isspace(*end);  ++end)   {}
+
+    *out = line;
+    /* move the actual filename to the beginning */
+    while (start != end) {
+        *line++ = *start++;
+    }
+    *line = 0;
+    return true;
+}
+
+int main(int argc, char **argv) {
+    size_t          itr;
+    int             retval           = 0;
+    bool            operators_free   = false;
+    bool            progs_src        = false;
+    FILE       *outfile         = NULL;
+    struct parser_s *parser          = NULL;
+    struct ftepp_s  *ftepp           = NULL;
+
+    app_name = argv[0];
+    con_init ();
+    opts_init("progs.dat", COMPILER_QCC, (1024 << 3));
+
+    util_seed(time(0));
+
+    if (!options_parse(argc, argv)) {
+        return usage();
+    }
+
+    if (OPTS_FLAG(TRUE_EMPTY_STRINGS) && OPTS_FLAG(FALSE_EMPTY_STRINGS)) {
+        con_err("-ftrue-empty-strings and -ffalse-empty-strings are mutually exclusive");
+        exit(EXIT_FAILURE);
+    }
+
+    /* the standard decides which set of operators to use */
+    if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_GMQCC) {
+        operators      = c_operators;
+        operator_count = GMQCC_ARRAY_COUNT(c_operators);
+    } else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC) {
+        operators      = fte_operators;
+        operator_count = GMQCC_ARRAY_COUNT(fte_operators);
+    } else {
+        operators      = qcc_operators;
+        operator_count = GMQCC_ARRAY_COUNT(qcc_operators);
+    }
+
+    if (operators == fte_operators) {
+        /* fix ternary? */
+        if (OPTS_FLAG(CORRECT_TERNARY)) {
+            oper_info *newops;
+            if (operators[operator_count-2].id != opid1(',') ||
+                operators[operator_count-1].id != opid2(':','?'))
+            {
+                con_err("internal error: operator precedence table wasn't updated correctly!\n");
+                exit(EXIT_FAILURE);
+            }
+            operators_free = true;
+            newops = (oper_info*)mem_a(sizeof(operators[0]) * operator_count);
+            memcpy(newops, operators, sizeof(operators[0]) * operator_count);
+            memcpy(&newops[operator_count-2], &operators[operator_count-1], sizeof(newops[0]));
+            memcpy(&newops[operator_count-1], &operators[operator_count-2], sizeof(newops[0]));
+            newops[operator_count-2].prec = newops[operator_count-1].prec+1;
+            operators = newops;
+        }
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_DUMP)) {
+        for (itr = 0; itr < COUNT_FLAGS; ++itr)
+            con_out("Flag %s = %i\n",    opts_flag_list[itr].name, OPTS_FLAG(itr));
+        for (itr = 0; itr < COUNT_WARNINGS; ++itr)
+            con_out("Warning %s = %i\n", opts_warn_list[itr].name, OPTS_WARN(itr));
+
+        con_out("output             = %s\n", OPTS_OPTION_STR(OPTION_OUTPUT));
+        con_out("optimization level = %u\n", OPTS_OPTION_U32(OPTION_O));
+        con_out("standard           = %u\n", OPTS_OPTION_U32(OPTION_STANDARD));
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+        if (opts_output_wasset) {
+            outfile = fopen(OPTS_OPTION_STR(OPTION_OUTPUT), "wb");
+            if (!outfile) {
+                con_err("failed to open `%s` for writing\n", OPTS_OPTION_STR(OPTION_OUTPUT));
+                retval = 1;
+                goto cleanup;
+            }
+        }
+        else {
+            outfile = con_default_out();
+        }
+    }
+
+    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+        if (!(parser = parser_create())) {
+            con_err("failed to initialize parser\n");
+            retval = 1;
+            goto cleanup;
+        }
+    }
+
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
+        if (!(ftepp = ftepp_create())) {
+            con_err("failed to initialize parser\n");
+            retval = 1;
+            goto cleanup;
+        }
+    }
+
+    /* add macros */
+    if (OPTS_OPTION_BOOL(OPTION_PP_ONLY) || OPTS_FLAG(FTEPP)) {
+        for (itr = 0; itr < vec_size(ppems); itr++) {
+            ftepp_add_macro(ftepp, ppems[itr].name, ppems[itr].value);
+            mem_d(ppems[itr].name);
+
+            /* can be null */
+            if (ppems[itr].value)
+                mem_d(ppems[itr].value);
+        }
+    }
+
+    if (!vec_size(items)) {
+        FILE *src;
+        char      *line    = NULL;
+        size_t     linelen = 0;
+        bool       hasline = false;
+
+        progs_src = true;
+
+        src = fopen(OPTS_OPTION_STR(OPTION_PROGSRC), "rb");
+        if (!src) {
+            con_err("failed to open `%s` for reading\n", OPTS_OPTION_STR(OPTION_PROGSRC));
+            retval = 1;
+            goto cleanup;
+        }
+
+        while (progs_nextline(&line, &linelen, src)) {
+            argitem item;
+
+            if (!line[0] || (line[0] == '/' && line[1] == '/'))
+                continue;
+
+            if (hasline) {
+                item.filename = util_strdup(line);
+                item.type     = TYPE_QC;
+                vec_push(items, item);
+            } else if (!opts_output_wasset) {
+                OPTS_OPTION_DUP(OPTION_OUTPUT) = util_strdup(line);
+                hasline                        = true;
+            }
+        }
+
+        fclose(src);
+        mem_d(line);
+    }
+
+    if (vec_size(items)) {
+        if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
+            !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+        {
+            con_out("Mode: %s\n", (progs_src ? "progs.src" : "manual"));
+            con_out("There are %lu items to compile:\n", (unsigned long)vec_size(items));
+        }
+
+        for (itr = 0; itr < vec_size(items); ++itr) {
+            if (!OPTS_OPTION_BOOL(OPTION_QUIET) &&
+                !OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+            {
+                con_out("  item: %s (%s)\n",
+                       items[itr].filename,
+                       ( (items[itr].type == TYPE_QC ? "qc" :
+                         (items[itr].type == TYPE_ASM ? "asm" :
+                         (items[itr].type == TYPE_SRC ? "progs.src" :
+                         ("unknown"))))));
+            }
+
+            if (OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+                const char *out;
+                if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
+                    retval = 1;
+                    goto cleanup;
+                }
+                out = ftepp_get(ftepp);
+                if (out)
+                    fprintf(outfile, "%s", out);
+                ftepp_flush(ftepp);
+            }
+            else {
+                if (OPTS_FLAG(FTEPP)) {
+                    const char *data;
+                    if (!ftepp_preprocess_file(ftepp, items[itr].filename)) {
+                        retval = 1;
+                        goto cleanup;
+                    }
+                    data = ftepp_get(ftepp);
+                    if (vec_size(data)) {
+                        if (!parser_compile_string(parser, items[itr].filename, data, vec_size(data))) {
+                            retval = 1;
+                            goto cleanup;
+                        }
+                    }
+                    ftepp_flush(ftepp);
+                }
+                else {
+                    if (!parser_compile_file(parser, items[itr].filename)) {
+                        retval = 1;
+                        goto cleanup;
+                    }
+                }
+            }
+
+            if (progs_src) {
+                mem_d(items[itr].filename);
+                items[itr].filename = NULL;
+            }
+        }
+
+        ftepp_finish(ftepp);
+        ftepp = NULL;
+        if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY)) {
+            if (!parser_finish(parser, OPTS_OPTION_STR(OPTION_OUTPUT))) {
+                retval = 1;
+                goto cleanup;
+            }
+        }
+    }
+
+cleanup:
+    if (ftepp)
+        ftepp_finish(ftepp);
+    con_close();
+    vec_free(items);
+    vec_free(ppems);
+
+    if (!OPTS_OPTION_BOOL(OPTION_PP_ONLY))
+        if(parser) parser_cleanup(parser);
+
+    /* free allocated option strings */
+    for (itr = 0; itr < OPTION_COUNT; itr++)
+        if (OPTS_OPTION_DUPED(itr))
+            mem_d(OPTS_OPTION_STR(itr));
+
+    if (operators_free)
+        mem_d((void*)operators);
+
+    lex_cleanup();
+
+    if (!retval && compile_errors)
+        retval = 1;
+    return retval;
+}
diff --git a/opts.c b/opts.c
deleted file mode 100644 (file)
index bc72824..0000000
--- a/opts.c
+++ /dev/null
@@ -1,424 +0,0 @@
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-
-const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = {
-# define GMQCC_TYPE_OPTIMIZATIONS
-# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O,
-#  include "opts.def"
-    0
-};
-
-const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = {
-# define GMQCC_TYPE_OPTIMIZATIONS
-# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) },
-#  include "opts.def"
-    { NULL, LONGBIT(0) }
-};
-
-const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = {
-# define GMQCC_TYPE_WARNS
-# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) },
-#  include "opts.def"
-    { NULL, LONGBIT(0) }
-};
-
-const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = {
-# define GMQCC_TYPE_FLAGS
-# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) },
-#  include "opts.def"
-    { NULL, LONGBIT(0) }
-};
-
-unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS];
-opts_cmd_t   opts; /* command line options */
-
-static void opts_setdefault(void) {
-    memset(&opts, 0, sizeof(opts_cmd_t));
-    OPTS_OPTION_STR(OPTION_PROGSRC)     = "progs.src";
-
-    /* warnings */
-    opts_set(opts.warn,  WARN_UNUSED_VARIABLE,           true);
-    opts_set(opts.warn,  WARN_USED_UNINITIALIZED,        true);
-    opts_set(opts.warn,  WARN_UNKNOWN_CONTROL_SEQUENCE,  true);
-    opts_set(opts.warn,  WARN_EXTENSIONS,                true);
-    opts_set(opts.warn,  WARN_FIELD_REDECLARED,          true);
-    opts_set(opts.warn,  WARN_MISSING_RETURN_VALUES,     true);
-    opts_set(opts.warn,  WARN_INVALID_PARAMETER_COUNT,   true);
-    opts_set(opts.warn,  WARN_LOCAL_CONSTANTS,           true);
-    opts_set(opts.warn,  WARN_VOID_VARIABLES,            true);
-    opts_set(opts.warn,  WARN_IMPLICIT_FUNCTION_POINTER, true);
-    opts_set(opts.warn,  WARN_VARIADIC_FUNCTION,         true);
-    opts_set(opts.warn,  WARN_FRAME_MACROS,              true);
-    opts_set(opts.warn,  WARN_EFFECTLESS_STATEMENT,      true);
-    opts_set(opts.warn,  WARN_END_SYS_FIELDS,            true);
-    opts_set(opts.warn,  WARN_ASSIGN_FUNCTION_TYPES,     true);
-    opts_set(opts.warn,  WARN_CPP,                       true);
-    opts_set(opts.warn,  WARN_MULTIFILE_IF,              true);
-    opts_set(opts.warn,  WARN_DOUBLE_DECLARATION,        true);
-    opts_set(opts.warn,  WARN_CONST_VAR,                 true);
-    opts_set(opts.warn,  WARN_MULTIBYTE_CHARACTER,       true);
-    opts_set(opts.warn,  WARN_UNKNOWN_PRAGMAS,           true);
-    opts_set(opts.warn,  WARN_UNREACHABLE_CODE,          true);
-    opts_set(opts.warn,  WARN_UNKNOWN_ATTRIBUTE,         true);
-    opts_set(opts.warn,  WARN_RESERVED_NAMES,            true);
-    opts_set(opts.warn,  WARN_UNINITIALIZED_CONSTANT,    true);
-    opts_set(opts.warn,  WARN_DEPRECATED,                true);
-    opts_set(opts.warn,  WARN_PARENTHESIS,               true);
-    opts_set(opts.warn,  WARN_CONST_OVERWRITE,           true);
-    opts_set(opts.warn,  WARN_DIRECTIVE_INMACRO,         true);
-    opts_set(opts.warn,  WARN_BUILTINS,                  true);
-    opts_set(opts.warn,  WARN_INEXACT_COMPARES,          true);
-
-    /* flags */
-    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,           true);
-    opts_set(opts.flags, CORRECT_TERNARY,                true);
-    opts_set(opts.flags, BAIL_ON_WERROR,                 true);
-    opts_set(opts.flags, LEGACY_VECTOR_MATHS,            true);
-    opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG,    true);
-
-    /* options */
-    OPTS_OPTION_U32(OPTION_STATE_FPS) = 10;
-}
-
-void opts_backup_non_Wall() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.warn_backup, i, OPTS_WARN(i));
-}
-
-void opts_restore_non_Wall() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i));
-}
-
-void opts_backup_non_Werror_all() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.werror_backup, i, OPTS_WERROR(i));
-}
-
-void opts_restore_non_Werror_all() {
-    size_t i;
-    for (i = 0; i <= WARN_DEBUG; ++i)
-        opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i));
-}
-
-void opts_init(const char *output, int standard, size_t arraysize) {
-    opts_setdefault();
-
-    OPTS_OPTION_STR(OPTION_OUTPUT)         = output;
-    OPTS_OPTION_U32(OPTION_STANDARD)       = standard;
-    OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize;
-}
-
-static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) {
-    size_t i;
-
-    for (i = 0; i < listsize; ++i) {
-        if (!strcmp(name, list[i].name)) {
-            longbit lb = list[i].bit;
-
-            if (on)
-                flags[lb.idx] |= (1<<(lb.bit));
-            else
-                flags[lb.idx] &= ~(1<<(lb.bit));
-
-            return true;
-        }
-    }
-    return false;
-}
-bool opts_setflag  (const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.flags,        opts_flag_list, COUNT_FLAGS);
-}
-bool opts_setwarn  (const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.warn,         opts_warn_list, COUNT_WARNINGS);
-}
-bool opts_setwerror(const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.werror,       opts_warn_list, COUNT_WARNINGS);
-}
-bool opts_setoptim (const char *name, bool on) {
-    return opts_setflag_all(name, on, opts.optimization, opts_opt_list,  COUNT_OPTIMIZATIONS);
-}
-
-void opts_set(uint32_t *flags, size_t idx, bool on) {
-    longbit lb;
-    LONGBIT_SET(lb, idx);
-
-    if (on)
-        flags[lb.idx] |= (1<<(lb.bit));
-    else
-        flags[lb.idx] &= ~(1<<(lb.bit));
-}
-
-void opts_setoptimlevel(unsigned int level) {
-    size_t i;
-    for (i = 0; i < COUNT_OPTIMIZATIONS; ++i)
-        opts_set(opts.optimization, i, level >= opts_opt_oflag[i]);
-
-    if (!level)
-        opts.optimizeoff = true;
-}
-
-/*
- * Standard configuration parser and subsystem.  Yes, optionally you may
- * create ini files or cfg (the driver accepts both) for a project opposed
- * to supplying just a progs.src (since you also may need to supply command
- * line arguments or set the options of the compiler) [which cannot be done
- * from a progs.src.
- */
-static char *opts_ini_rstrip(char *s) {
-    char *p = s + strlen(s) - 1;
-    while (p > s && util_isspace(*p))
-        *p = '\0', p--;
-    return s;
-}
-
-static char *opts_ini_lskip(const char *s) {
-    while (*s && util_isspace(*s))
-        s++;
-    return (char*)s;
-}
-
-static char *opts_ini_next(const char *s, char c) {
-    bool last = false;
-    while (*s && *s != c && !(last && *s == ';'))
-        last = !!util_isspace(*s), s++;
-
-    return (char*)s;
-}
-
-static size_t opts_ini_parse (
-    FILE *filehandle,
-    char *(*loadhandle)(const char *, const char *, const char *, char **),
-    char **errorhandle,
-    char **parse_file
-) {
-    size_t linesize;
-    size_t lineno             = 1;
-    size_t error              = 0;
-    char  *line               = NULL;
-    char   section_data[2048] = "";
-    char   oldname_data[2048] = "";
-
-    /* parsing and reading variables */
-    char *parse_beg;
-    char *parse_end;
-    char *read_name;
-    char *read_value;
-
-    while (util_getline(&line, &linesize, filehandle) != EOF) {
-        parse_beg = line;
-
-        /* handle BOM */
-        if (lineno == 1 && (
-                (unsigned char)parse_beg[0] == 0xEF &&
-                (unsigned char)parse_beg[1] == 0xBB &&
-                (unsigned char)parse_beg[2] == 0xBF
-            )
-        ) {
-            parse_beg ++; /* 0xEF */
-            parse_beg ++; /* 0xBB */
-            parse_beg ++; /* 0xBF */
-        }
-
-        if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') {
-            /* ignore '#' is a perl extension */
-        } else if (*parse_beg == '[') {
-            /* section found */
-            if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') {
-                * parse_end = '\0'; /* terminate bro */
-                util_strncpy(section_data, parse_beg + 1, sizeof(section_data));
-                section_data[sizeof(section_data) - 1] = '\0';
-                *oldname_data                          = '\0';
-            } else if (!error) {
-                /* otherwise set error to the current line number */
-                error = lineno;
-            }
-        } else if (*parse_beg && *parse_beg != ';') {
-            /* not a comment, must be a name value pair :) */
-            if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=')
-                parse_end = opts_ini_next(parse_beg, ':');
-
-            if (*parse_end == '=' || *parse_end == ':') {
-                *parse_end = '\0'; /* terminate bro */
-                read_name  = opts_ini_rstrip(parse_beg);
-                read_value = opts_ini_lskip(parse_end + 1);
-                if (*(parse_end = opts_ini_next(read_value, '\0')) == ';')
-                    * parse_end = '\0';
-                opts_ini_rstrip(read_value);
-
-                /* valid name value pair, lets call down to handler */
-                util_strncpy(oldname_data, read_name, sizeof(oldname_data));
-                oldname_data[sizeof(oldname_data) - 1] ='\0';
-
-                if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error)
-                    error = lineno;
-            } else if (!strcmp(section_data, "includes")) {
-                /* Includes are special */
-                if (*(parse_end = opts_ini_next(parse_beg, '=')) == '='
-                ||  *(parse_end = opts_ini_next(parse_beg, ':')) == ':') {
-                    static const char *invalid_include = "invalid use of include";
-                    vec_append(*errorhandle, strlen(invalid_include), invalid_include);
-                    error = lineno;
-                } else {
-                    read_name = opts_ini_rstrip(parse_beg);
-                    if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error)
-                        error = lineno;
-                }
-            } else if (!error) {
-                /* otherwise set error to the current line number */
-                error = lineno;
-            }
-        }
-        lineno++;
-    }
-    mem_d(line);
-    return error;
-
-}
-
-/*
- * returns true/false for a char that contains ("true" or "false" or numeric 0/1)
- */
-static bool opts_ini_bool(const char *value) {
-    if (!strcmp(value, "true"))  return true;
-    if (!strcmp(value, "false")) return false;
-    return !!strtol(value, NULL, 10);
-}
-
-static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) {
-    char *error = NULL;
-    bool  found = false;
-
-    /*
-     * undef all of these because they may still be defined like in my
-     * case they where.
-     */
-    #undef GMQCC_TYPE_FLAGS
-    #undef GMQCC_TYPE_OPTIMIZATIONS
-    #undef GMQCC_TYPE_WARNS
-
-    /* deal with includes */
-    if (!strcmp(section, "includes")) {
-        static const char *include_error_beg = "failed to open file `";
-        static const char *include_error_end = "' for inclusion";
-        FILE *file = fopen(value, "r");
-        found = true;
-        if (!file) {
-            vec_append(error, strlen(include_error_beg), include_error_beg);
-            vec_append(error, strlen(value), value);
-            vec_append(error, strlen(include_error_end), include_error_end);
-        } else {
-            if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0)
-                found = false;
-            /* Change the file name */
-            mem_d(*parse_file);
-            *parse_file = util_strdup(value);
-            fclose(file);
-        }
-    }
-
-    /* flags */
-    #define GMQCC_TYPE_FLAGS
-    #define GMQCC_DEFINE_FLAG(X)                                       \
-    if (!strcmp(section, "flags") && !strcmp(name, #X)) {              \
-        opts_set(opts.flags, X, opts_ini_bool(value));                 \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* warnings */
-    #define GMQCC_TYPE_WARNS
-    #define GMQCC_DEFINE_FLAG(X)                                       \
-    if (!strcmp(section, "warnings") && !strcmp(name, #X)) {           \
-        opts_set(opts.warn, WARN_##X, opts_ini_bool(value));           \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* Werror-individuals */
-    #define GMQCC_TYPE_WARNS
-    #define GMQCC_DEFINE_FLAG(X)                                       \
-    if (!strcmp(section, "errors") && !strcmp(name, #X)) {             \
-        opts_set(opts.werror, WARN_##X, opts_ini_bool(value));         \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* optimizations */
-    #define GMQCC_TYPE_OPTIMIZATIONS
-    #define GMQCC_DEFINE_FLAG(X,Y)                                     \
-    if (!strcmp(section, "optimizations") && !strcmp(name, #X)) {      \
-        opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value));  \
-        found = true;                                                  \
-    }
-    #include "opts.def"
-
-    /* nothing was found ever! */
-    if (!found) {
-        if (strcmp(section, "includes") &&
-            strcmp(section, "flags")    &&
-            strcmp(section, "warnings") &&
-            strcmp(section, "optimizations"))
-        {
-            static const char *invalid_section = "invalid_section `";
-            vec_append(error, strlen(invalid_section), invalid_section);
-            vec_append(error, strlen(section), section);
-            vec_push(error, '`');
-        } else if (strcmp(section, "includes")) {
-            static const char *invalid_variable = "invalid_variable `";
-            static const char *in_section = "` in section: `";
-            vec_append(error, strlen(invalid_variable), invalid_variable);
-            vec_append(error, strlen(name), name);
-            vec_append(error, strlen(in_section), in_section);
-            vec_append(error, strlen(section), section);
-            vec_push(error, '`');
-        } else {
-            static const char *expected_something = "expected something";
-            vec_append(error, strlen(expected_something), expected_something);
-        }
-    }
-    vec_push(error, '\0');
-    return error;
-}
-
-/*
- * Actual loading subsystem, this finds the ini or cfg file, and properly
- * loads it and executes it to set compiler options.
- */
-void opts_ini_init(const char *file) {
-    /*
-     * Possible matches are:
-     *  gmqcc.ini
-     *  gmqcc.cfg
-     */
-    char       *error = NULL;
-    char       *parse_file = NULL;
-    size_t     line;
-    FILE  *ini;
-
-    if (!file) {
-        /* try ini */
-        if (!(ini = fopen((file = "gmqcc.ini"), "r")))
-            /* try cfg */
-            if (!(ini = fopen((file = "gmqcc.cfg"), "r")))
-                return;
-    } else if (!(ini = fopen(file, "r")))
-        return;
-
-    con_out("found ini file `%s`\n", file);
-
-    parse_file = util_strdup(file);
-    if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) {
-        /* there was a parse error with the ini file */
-        con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error);
-        vec_free(error);
-    }
-    mem_d(parse_file);
-
-    fclose(ini);
-}
diff --git a/opts.cpp b/opts.cpp
new file mode 100644 (file)
index 0000000..bc72824
--- /dev/null
+++ b/opts.cpp
@@ -0,0 +1,424 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "gmqcc.h"
+
+const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = {
+# define GMQCC_TYPE_OPTIMIZATIONS
+# define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O,
+#  include "opts.def"
+    0
+};
+
+const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = {
+# define GMQCC_TYPE_OPTIMIZATIONS
+# define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) },
+#  include "opts.def"
+    { NULL, LONGBIT(0) }
+};
+
+const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = {
+# define GMQCC_TYPE_WARNS
+# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) },
+#  include "opts.def"
+    { NULL, LONGBIT(0) }
+};
+
+const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = {
+# define GMQCC_TYPE_FLAGS
+# define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) },
+#  include "opts.def"
+    { NULL, LONGBIT(0) }
+};
+
+unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS];
+opts_cmd_t   opts; /* command line options */
+
+static void opts_setdefault(void) {
+    memset(&opts, 0, sizeof(opts_cmd_t));
+    OPTS_OPTION_STR(OPTION_PROGSRC)     = "progs.src";
+
+    /* warnings */
+    opts_set(opts.warn,  WARN_UNUSED_VARIABLE,           true);
+    opts_set(opts.warn,  WARN_USED_UNINITIALIZED,        true);
+    opts_set(opts.warn,  WARN_UNKNOWN_CONTROL_SEQUENCE,  true);
+    opts_set(opts.warn,  WARN_EXTENSIONS,                true);
+    opts_set(opts.warn,  WARN_FIELD_REDECLARED,          true);
+    opts_set(opts.warn,  WARN_MISSING_RETURN_VALUES,     true);
+    opts_set(opts.warn,  WARN_INVALID_PARAMETER_COUNT,   true);
+    opts_set(opts.warn,  WARN_LOCAL_CONSTANTS,           true);
+    opts_set(opts.warn,  WARN_VOID_VARIABLES,            true);
+    opts_set(opts.warn,  WARN_IMPLICIT_FUNCTION_POINTER, true);
+    opts_set(opts.warn,  WARN_VARIADIC_FUNCTION,         true);
+    opts_set(opts.warn,  WARN_FRAME_MACROS,              true);
+    opts_set(opts.warn,  WARN_EFFECTLESS_STATEMENT,      true);
+    opts_set(opts.warn,  WARN_END_SYS_FIELDS,            true);
+    opts_set(opts.warn,  WARN_ASSIGN_FUNCTION_TYPES,     true);
+    opts_set(opts.warn,  WARN_CPP,                       true);
+    opts_set(opts.warn,  WARN_MULTIFILE_IF,              true);
+    opts_set(opts.warn,  WARN_DOUBLE_DECLARATION,        true);
+    opts_set(opts.warn,  WARN_CONST_VAR,                 true);
+    opts_set(opts.warn,  WARN_MULTIBYTE_CHARACTER,       true);
+    opts_set(opts.warn,  WARN_UNKNOWN_PRAGMAS,           true);
+    opts_set(opts.warn,  WARN_UNREACHABLE_CODE,          true);
+    opts_set(opts.warn,  WARN_UNKNOWN_ATTRIBUTE,         true);
+    opts_set(opts.warn,  WARN_RESERVED_NAMES,            true);
+    opts_set(opts.warn,  WARN_UNINITIALIZED_CONSTANT,    true);
+    opts_set(opts.warn,  WARN_DEPRECATED,                true);
+    opts_set(opts.warn,  WARN_PARENTHESIS,               true);
+    opts_set(opts.warn,  WARN_CONST_OVERWRITE,           true);
+    opts_set(opts.warn,  WARN_DIRECTIVE_INMACRO,         true);
+    opts_set(opts.warn,  WARN_BUILTINS,                  true);
+    opts_set(opts.warn,  WARN_INEXACT_COMPARES,          true);
+
+    /* flags */
+    opts_set(opts.flags, ADJUST_VECTOR_FIELDS,           true);
+    opts_set(opts.flags, CORRECT_TERNARY,                true);
+    opts_set(opts.flags, BAIL_ON_WERROR,                 true);
+    opts_set(opts.flags, LEGACY_VECTOR_MATHS,            true);
+    opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG,    true);
+
+    /* options */
+    OPTS_OPTION_U32(OPTION_STATE_FPS) = 10;
+}
+
+void opts_backup_non_Wall() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.warn_backup, i, OPTS_WARN(i));
+}
+
+void opts_restore_non_Wall() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i));
+}
+
+void opts_backup_non_Werror_all() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.werror_backup, i, OPTS_WERROR(i));
+}
+
+void opts_restore_non_Werror_all() {
+    size_t i;
+    for (i = 0; i <= WARN_DEBUG; ++i)
+        opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i));
+}
+
+void opts_init(const char *output, int standard, size_t arraysize) {
+    opts_setdefault();
+
+    OPTS_OPTION_STR(OPTION_OUTPUT)         = output;
+    OPTS_OPTION_U32(OPTION_STANDARD)       = standard;
+    OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize;
+}
+
+static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) {
+    size_t i;
+
+    for (i = 0; i < listsize; ++i) {
+        if (!strcmp(name, list[i].name)) {
+            longbit lb = list[i].bit;
+
+            if (on)
+                flags[lb.idx] |= (1<<(lb.bit));
+            else
+                flags[lb.idx] &= ~(1<<(lb.bit));
+
+            return true;
+        }
+    }
+    return false;
+}
+bool opts_setflag  (const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.flags,        opts_flag_list, COUNT_FLAGS);
+}
+bool opts_setwarn  (const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.warn,         opts_warn_list, COUNT_WARNINGS);
+}
+bool opts_setwerror(const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.werror,       opts_warn_list, COUNT_WARNINGS);
+}
+bool opts_setoptim (const char *name, bool on) {
+    return opts_setflag_all(name, on, opts.optimization, opts_opt_list,  COUNT_OPTIMIZATIONS);
+}
+
+void opts_set(uint32_t *flags, size_t idx, bool on) {
+    longbit lb;
+    LONGBIT_SET(lb, idx);
+
+    if (on)
+        flags[lb.idx] |= (1<<(lb.bit));
+    else
+        flags[lb.idx] &= ~(1<<(lb.bit));
+}
+
+void opts_setoptimlevel(unsigned int level) {
+    size_t i;
+    for (i = 0; i < COUNT_OPTIMIZATIONS; ++i)
+        opts_set(opts.optimization, i, level >= opts_opt_oflag[i]);
+
+    if (!level)
+        opts.optimizeoff = true;
+}
+
+/*
+ * Standard configuration parser and subsystem.  Yes, optionally you may
+ * create ini files or cfg (the driver accepts both) for a project opposed
+ * to supplying just a progs.src (since you also may need to supply command
+ * line arguments or set the options of the compiler) [which cannot be done
+ * from a progs.src.
+ */
+static char *opts_ini_rstrip(char *s) {
+    char *p = s + strlen(s) - 1;
+    while (p > s && util_isspace(*p))
+        *p = '\0', p--;
+    return s;
+}
+
+static char *opts_ini_lskip(const char *s) {
+    while (*s && util_isspace(*s))
+        s++;
+    return (char*)s;
+}
+
+static char *opts_ini_next(const char *s, char c) {
+    bool last = false;
+    while (*s && *s != c && !(last && *s == ';'))
+        last = !!util_isspace(*s), s++;
+
+    return (char*)s;
+}
+
+static size_t opts_ini_parse (
+    FILE *filehandle,
+    char *(*loadhandle)(const char *, const char *, const char *, char **),
+    char **errorhandle,
+    char **parse_file
+) {
+    size_t linesize;
+    size_t lineno             = 1;
+    size_t error              = 0;
+    char  *line               = NULL;
+    char   section_data[2048] = "";
+    char   oldname_data[2048] = "";
+
+    /* parsing and reading variables */
+    char *parse_beg;
+    char *parse_end;
+    char *read_name;
+    char *read_value;
+
+    while (util_getline(&line, &linesize, filehandle) != EOF) {
+        parse_beg = line;
+
+        /* handle BOM */
+        if (lineno == 1 && (
+                (unsigned char)parse_beg[0] == 0xEF &&
+                (unsigned char)parse_beg[1] == 0xBB &&
+                (unsigned char)parse_beg[2] == 0xBF
+            )
+        ) {
+            parse_beg ++; /* 0xEF */
+            parse_beg ++; /* 0xBB */
+            parse_beg ++; /* 0xBF */
+        }
+
+        if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') {
+            /* ignore '#' is a perl extension */
+        } else if (*parse_beg == '[') {
+            /* section found */
+            if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') {
+                * parse_end = '\0'; /* terminate bro */
+                util_strncpy(section_data, parse_beg + 1, sizeof(section_data));
+                section_data[sizeof(section_data) - 1] = '\0';
+                *oldname_data                          = '\0';
+            } else if (!error) {
+                /* otherwise set error to the current line number */
+                error = lineno;
+            }
+        } else if (*parse_beg && *parse_beg != ';') {
+            /* not a comment, must be a name value pair :) */
+            if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=')
+                parse_end = opts_ini_next(parse_beg, ':');
+
+            if (*parse_end == '=' || *parse_end == ':') {
+                *parse_end = '\0'; /* terminate bro */
+                read_name  = opts_ini_rstrip(parse_beg);
+                read_value = opts_ini_lskip(parse_end + 1);
+                if (*(parse_end = opts_ini_next(read_value, '\0')) == ';')
+                    * parse_end = '\0';
+                opts_ini_rstrip(read_value);
+
+                /* valid name value pair, lets call down to handler */
+                util_strncpy(oldname_data, read_name, sizeof(oldname_data));
+                oldname_data[sizeof(oldname_data) - 1] ='\0';
+
+                if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error)
+                    error = lineno;
+            } else if (!strcmp(section_data, "includes")) {
+                /* Includes are special */
+                if (*(parse_end = opts_ini_next(parse_beg, '=')) == '='
+                ||  *(parse_end = opts_ini_next(parse_beg, ':')) == ':') {
+                    static const char *invalid_include = "invalid use of include";
+                    vec_append(*errorhandle, strlen(invalid_include), invalid_include);
+                    error = lineno;
+                } else {
+                    read_name = opts_ini_rstrip(parse_beg);
+                    if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error)
+                        error = lineno;
+                }
+            } else if (!error) {
+                /* otherwise set error to the current line number */
+                error = lineno;
+            }
+        }
+        lineno++;
+    }
+    mem_d(line);
+    return error;
+
+}
+
+/*
+ * returns true/false for a char that contains ("true" or "false" or numeric 0/1)
+ */
+static bool opts_ini_bool(const char *value) {
+    if (!strcmp(value, "true"))  return true;
+    if (!strcmp(value, "false")) return false;
+    return !!strtol(value, NULL, 10);
+}
+
+static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) {
+    char *error = NULL;
+    bool  found = false;
+
+    /*
+     * undef all of these because they may still be defined like in my
+     * case they where.
+     */
+    #undef GMQCC_TYPE_FLAGS
+    #undef GMQCC_TYPE_OPTIMIZATIONS
+    #undef GMQCC_TYPE_WARNS
+
+    /* deal with includes */
+    if (!strcmp(section, "includes")) {
+        static const char *include_error_beg = "failed to open file `";
+        static const char *include_error_end = "' for inclusion";
+        FILE *file = fopen(value, "r");
+        found = true;
+        if (!file) {
+            vec_append(error, strlen(include_error_beg), include_error_beg);
+            vec_append(error, strlen(value), value);
+            vec_append(error, strlen(include_error_end), include_error_end);
+        } else {
+            if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0)
+                found = false;
+            /* Change the file name */
+            mem_d(*parse_file);
+            *parse_file = util_strdup(value);
+            fclose(file);
+        }
+    }
+
+    /* flags */
+    #define GMQCC_TYPE_FLAGS
+    #define GMQCC_DEFINE_FLAG(X)                                       \
+    if (!strcmp(section, "flags") && !strcmp(name, #X)) {              \
+        opts_set(opts.flags, X, opts_ini_bool(value));                 \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* warnings */
+    #define GMQCC_TYPE_WARNS
+    #define GMQCC_DEFINE_FLAG(X)                                       \
+    if (!strcmp(section, "warnings") && !strcmp(name, #X)) {           \
+        opts_set(opts.warn, WARN_##X, opts_ini_bool(value));           \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* Werror-individuals */
+    #define GMQCC_TYPE_WARNS
+    #define GMQCC_DEFINE_FLAG(X)                                       \
+    if (!strcmp(section, "errors") && !strcmp(name, #X)) {             \
+        opts_set(opts.werror, WARN_##X, opts_ini_bool(value));         \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* optimizations */
+    #define GMQCC_TYPE_OPTIMIZATIONS
+    #define GMQCC_DEFINE_FLAG(X,Y)                                     \
+    if (!strcmp(section, "optimizations") && !strcmp(name, #X)) {      \
+        opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value));  \
+        found = true;                                                  \
+    }
+    #include "opts.def"
+
+    /* nothing was found ever! */
+    if (!found) {
+        if (strcmp(section, "includes") &&
+            strcmp(section, "flags")    &&
+            strcmp(section, "warnings") &&
+            strcmp(section, "optimizations"))
+        {
+            static const char *invalid_section = "invalid_section `";
+            vec_append(error, strlen(invalid_section), invalid_section);
+            vec_append(error, strlen(section), section);
+            vec_push(error, '`');
+        } else if (strcmp(section, "includes")) {
+            static const char *invalid_variable = "invalid_variable `";
+            static const char *in_section = "` in section: `";
+            vec_append(error, strlen(invalid_variable), invalid_variable);
+            vec_append(error, strlen(name), name);
+            vec_append(error, strlen(in_section), in_section);
+            vec_append(error, strlen(section), section);
+            vec_push(error, '`');
+        } else {
+            static const char *expected_something = "expected something";
+            vec_append(error, strlen(expected_something), expected_something);
+        }
+    }
+    vec_push(error, '\0');
+    return error;
+}
+
+/*
+ * Actual loading subsystem, this finds the ini or cfg file, and properly
+ * loads it and executes it to set compiler options.
+ */
+void opts_ini_init(const char *file) {
+    /*
+     * Possible matches are:
+     *  gmqcc.ini
+     *  gmqcc.cfg
+     */
+    char       *error = NULL;
+    char       *parse_file = NULL;
+    size_t     line;
+    FILE  *ini;
+
+    if (!file) {
+        /* try ini */
+        if (!(ini = fopen((file = "gmqcc.ini"), "r")))
+            /* try cfg */
+            if (!(ini = fopen((file = "gmqcc.cfg"), "r")))
+                return;
+    } else if (!(ini = fopen(file, "r")))
+        return;
+
+    con_out("found ini file `%s`\n", file);
+
+    parse_file = util_strdup(file);
+    if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) {
+        /* there was a parse error with the ini file */
+        con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error);
+        vec_free(error);
+    }
+    mem_d(parse_file);
+
+    fclose(ini);
+}
diff --git a/parser.c b/parser.c
deleted file mode 100644 (file)
index 0370146..0000000
--- a/parser.c
+++ /dev/null
@@ -1,6380 +0,0 @@
-#include <string.h>
-#include <math.h>
-
-#include "parser.h"
-
-#define PARSER_HT_LOCALS  2
-#define PARSER_HT_SIZE    512
-#define TYPEDEF_HT_SIZE   512
-
-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 void parser_addglobal(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 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, bool truthvalue, bool with_labels);
-static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels);
-static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname);
-static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname);
-static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg);
-
-static void parseerror(parser_t *parser, const char *fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    vcompile_error(parser->lex->tok.ctx, fmt, ap);
-    va_end(ap);
-}
-
-/* returns true if it counts as an error */
-static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, ...)
-{
-    bool    r;
-    va_list ap;
-    va_start(ap, fmt);
-    r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap);
-    va_end(ap);
-    return r;
-}
-
-/**********************************************************************
- * parsing
- */
-
-static bool parser_next(parser_t *parser)
-{
-    /* lex_do kills the previous token */
-    parser->tok = lex_do(parser->lex);
-    if (parser->tok == TOKEN_EOF)
-        return true;
-    if (parser->tok >= TOKEN_ERROR) {
-        parseerror(parser, "lex error");
-        return false;
-    }
-    return true;
-}
-
-#define parser_tokval(p) ((p)->lex->tok.value)
-#define parser_token(p)  (&((p)->lex->tok))
-
-char *parser_strdup(const char *str)
-{
-    if (str && !*str) {
-        /* actually dup empty strings */
-        char *out = (char*)mem_a(1);
-        *out = 0;
-        return out;
-    }
-    return util_strdup(str);
-}
-
-static ast_expression* parser_find_field(parser_t *parser, const char *name)
-{
-    return ( ast_expression*)util_htget(parser->htfields, name);
-}
-
-static ast_expression* parser_find_label(parser_t *parser, const char *name)
-{
-    size_t i;
-    for(i = 0; i < vec_size(parser->labels); i++)
-        if (!strcmp(parser->labels[i]->name, name))
-            return (ast_expression*)parser->labels[i];
-    return NULL;
-}
-
-ast_expression* parser_find_global(parser_t *parser, const char *name)
-{
-    ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser));
-    if (var)
-        return var;
-    return (ast_expression*)util_htget(parser->htglobals, name);
-}
-
-static ast_expression* parser_find_param(parser_t *parser, const char *name)
-{
-    size_t i;
-    ast_value *fun;
-    if (!parser->function)
-        return NULL;
-    fun = parser->function->vtype;
-    for (i = 0; i < vec_size(fun->expression.params); ++i) {
-        if (!strcmp(fun->expression.params[i]->name, name))
-            return (ast_expression*)(fun->expression.params[i]);
-    }
-    return NULL;
-}
-
-static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam)
-{
-    size_t          i, hash;
-    ast_expression *e;
-
-    hash = util_hthash(parser->htglobals, name);
-
-    *isparam = false;
-    for (i = vec_size(parser->variables); i > upto;) {
-        --i;
-        if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) )
-            return e;
-    }
-    *isparam = true;
-    return parser_find_param(parser, name);
-}
-
-static ast_expression* parser_find_var(parser_t *parser, const char *name)
-{
-    bool dummy;
-    ast_expression *v;
-    v         = parser_find_local(parser, name, 0, &dummy);
-    if (!v) v = parser_find_global(parser, name);
-    return v;
-}
-
-static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto)
-{
-    size_t     i, hash;
-    ast_value *e;
-    hash = util_hthash(parser->typedefs[0], name);
-
-    for (i = vec_size(parser->typedefs); i > upto;) {
-        --i;
-        if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) )
-            return e;
-    }
-    return NULL;
-}
-
-typedef struct
-{
-    size_t etype; /* 0 = expression, others are operators */
-    bool            isparen;
-    size_t          off;
-    ast_expression *out;
-    ast_block      *block; /* for commas and function calls */
-    lex_ctx_t ctx;
-} sy_elem;
-
-enum {
-    PAREN_EXPR,
-    PAREN_FUNC,
-    PAREN_INDEX,
-    PAREN_TERNARY1,
-    PAREN_TERNARY2
-};
-typedef struct
-{
-    sy_elem        *out;
-    sy_elem        *ops;
-    size_t         *argc;
-    unsigned int   *paren;
-} shunt;
-
-static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) {
-    sy_elem e;
-    e.etype = 0;
-    e.off   = 0;
-    e.out   = v;
-    e.block = NULL;
-    e.ctx   = ctx;
-    e.isparen = false;
-    return e;
-}
-
-static sy_elem syblock(lex_ctx_t ctx, ast_block *v) {
-    sy_elem e;
-    e.etype = 0;
-    e.off   = 0;
-    e.out   = (ast_expression*)v;
-    e.block = v;
-    e.ctx   = ctx;
-    e.isparen = false;
-    return e;
-}
-
-static sy_elem syop(lex_ctx_t ctx, const oper_info *op) {
-    sy_elem e;
-    e.etype = 1 + (op - operators);
-    e.off   = 0;
-    e.out   = NULL;
-    e.block = NULL;
-    e.ctx   = ctx;
-    e.isparen = false;
-    return e;
-}
-
-static sy_elem syparen(lex_ctx_t ctx, size_t off) {
-    sy_elem e;
-    e.etype = 0;
-    e.off   = off;
-    e.out   = NULL;
-    e.block = NULL;
-    e.ctx   = ctx;
-    e.isparen = true;
-    return e;
-}
-
-/* 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]).
- */
-static bool rotate_entfield_array_index_nodes(ast_expression **out)
-{
-    ast_array_index *index, *oldindex;
-    ast_entfield    *entfield;
-
-    ast_value       *field;
-    ast_expression  *sub;
-    ast_expression  *entity;
-
-    lex_ctx_t ctx = ast_ctx(*out);
-
-    if (!ast_istype(*out, ast_array_index))
-        return false;
-    index = (ast_array_index*)*out;
-
-    if (!ast_istype(index->array, ast_entfield))
-        return false;
-    entfield = (ast_entfield*)index->array;
-
-    if (!ast_istype(entfield->field, ast_value))
-        return false;
-    field = (ast_value*)entfield->field;
-
-    sub    = index->index;
-    entity = entfield->entity;
-
-    oldindex = index;
-
-    index = ast_array_index_new(ctx, (ast_expression*)field, sub);
-    entfield = ast_entfield_new(ctx, entity, (ast_expression*)index);
-    *out = (ast_expression*)entfield;
-
-    oldindex->array = NULL;
-    oldindex->index = NULL;
-    ast_delete(oldindex);
-
-    return true;
-}
-
-static bool check_write_to(lex_ctx_t ctx, ast_expression *expr)
-{
-    if (ast_istype(expr, ast_value)) {
-        ast_value *val = (ast_value*)expr;
-        if (val->cvq == CV_CONST) {
-            if (val->name[0] == '#') {
-                compile_error(ctx, "invalid assignment to a literal constant");
-                return false;
-            }
-            /*
-             * To work around quakeworld we must elide the error and make it
-             * a warning instead.
-             */
-            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC)
-                compile_error(ctx, "assignment to constant `%s`", val->name);
-            else
-                (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->name);
-            return false;
-        }
-    }
-    return true;
-}
-
-static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
-{
-    const oper_info *op;
-    lex_ctx_t ctx;
-    ast_expression *out = NULL;
-    ast_expression *exprs[3];
-    ast_block      *blocks[3];
-    ast_binstore   *asbinstore;
-    size_t i, assignop, addop, subop;
-    qcint_t  generated_op = 0;
-
-    char ty1[1024];
-    char ty2[1024];
-
-    if (!vec_size(sy->ops)) {
-        parseerror(parser, "internal error: missing operator");
-        return false;
-    }
-
-    if (vec_last(sy->ops).isparen) {
-        parseerror(parser, "unmatched parenthesis");
-        return false;
-    }
-
-    op = &operators[vec_last(sy->ops).etype - 1];
-    ctx = vec_last(sy->ops).ctx;
-
-    if (vec_size(sy->out) < op->operands) {
-        if (op->flags & OP_PREFIX)
-            compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id);
-        else /* this should have errored previously already */
-            compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id);
-        return false;
-    }
-
-    vec_shrinkby(sy->ops, 1);
-
-    /* op(:?) has no input and no output */
-    if (!op->operands)
-        return true;
-
-    vec_shrinkby(sy->out, op->operands);
-    for (i = 0; i < op->operands; ++i) {
-        exprs[i]  = sy->out[vec_size(sy->out)+i].out;
-        blocks[i] = sy->out[vec_size(sy->out)+i].block;
-
-        if (exprs[i]->vtype == TYPE_NOEXPR &&
-            !(i != 0 && op->id == opid2('?',':')) &&
-            !(i == 1 && op->id == opid1('.')))
-        {
-            if (ast_istype(exprs[i], ast_label))
-                compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier");
-            else
-                compile_error(ast_ctx(exprs[i]), "not an expression");
-            (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i);
-        }
-    }
-
-    if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) {
-        compile_error(ctx, "internal error: operator cannot be applied on empty blocks");
-        return false;
-    }
-
-#define NotSameType(T) \
-             (exprs[0]->vtype != exprs[1]->vtype || \
-              exprs[0]->vtype != T)
-
-    switch (op->id)
-    {
-        default:
-            compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id);
-            return false;
-
-        case opid1('.'):
-            if (exprs[0]->vtype == TYPE_VECTOR &&
-                exprs[1]->vtype == TYPE_NOEXPR)
-            {
-                if      (exprs[1] == (ast_expression*)parser->const_vec[0])
-                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL);
-                else if (exprs[1] == (ast_expression*)parser->const_vec[1])
-                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL);
-                else if (exprs[1] == (ast_expression*)parser->const_vec[2])
-                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL);
-                else {
-                    compile_error(ctx, "access to invalid vector component");
-                    return false;
-                }
-            }
-            else if (exprs[0]->vtype == TYPE_ENTITY) {
-                if (exprs[1]->vtype != TYPE_FIELD) {
-                    compile_error(ast_ctx(exprs[1]), "type error: right hand of member-operand should be an entity-field");
-                    return false;
-                }
-                out = (ast_expression*)ast_entfield_new(ctx, exprs[0], exprs[1]);
-            }
-            else if (exprs[0]->vtype == TYPE_VECTOR) {
-                compile_error(ast_ctx(exprs[1]), "vectors cannot be accessed this way");
-                return false;
-            }
-            else {
-                compile_error(ast_ctx(exprs[1]), "type error: member-of operator on something that is not an entity or vector");
-                return false;
-            }
-            break;
-
-        case opid1('['):
-            if (exprs[0]->vtype != TYPE_ARRAY &&
-                !(exprs[0]->vtype == TYPE_FIELD &&
-                  exprs[0]->next->vtype == TYPE_ARRAY))
-            {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "cannot index value of type %s", ty1);
-                return false;
-            }
-            if (exprs[1]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[1]), "index must be of type float, not %s", ty1);
-                return false;
-            }
-            out = (ast_expression*)ast_array_index_new(ctx, exprs[0], exprs[1]);
-            rotate_entfield_array_index_nodes(&out);
-            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;
-            } else {
-                blocks[0] = ast_block_new(ctx);
-                if (!ast_block_add_expr(blocks[0], exprs[0]) ||
-                    !ast_block_add_expr(blocks[0], exprs[1]))
-                {
-                    return false;
-                }
-            }
-            ast_block_set_type(blocks[0], exprs[1]);
-
-            vec_push(sy->out, syblock(ctx, blocks[0]));
-            return true;
-
-        case opid2('+','P'):
-            out = exprs[0];
-            break;
-        case opid2('-','P'):
-            if ((out = fold_op(parser->fold, op, exprs)))
-                break;
-
-            if (exprs[0]->vtype != TYPE_FLOAT &&
-                exprs[0]->vtype != TYPE_VECTOR) {
-                    compile_error(ctx, "invalid types used in unary expression: cannot negate type %s",
-                                  type_name[exprs[0]->vtype]);
-                return false;
-            }
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_F, exprs[0]);
-            else
-                out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_V, exprs[0]);
-            break;
-
-        case opid2('!','P'):
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]);
-                        break;
-                    case TYPE_VECTOR:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[0]);
-                        break;
-                    case TYPE_STRING:
-                        if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
-                            out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]);
-                        else
-                            out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[0]);
-                        break;
-                    /* we don't constant-fold NOT for these types */
-                    case TYPE_ENTITY:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_ENT, exprs[0]);
-                        break;
-                    case TYPE_FUNCTION:
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_FNC, exprs[0]);
-                        break;
-                    default:
-                    compile_error(ctx, "invalid types used in expression: cannot logically negate type %s",
-                                  type_name[exprs[0]->vtype]);
-                    return false;
-                }
-            }
-            break;
-
-        case opid1('+'):
-            if (exprs[0]->vtype != exprs[1]->vtype ||
-               (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) )
-            {
-                compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        out = fold_binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]);
-                        break;
-                    case TYPE_VECTOR:
-                        out = fold_binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]);
-                        break;
-                    default:
-                        compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
-                                      type_name[exprs[0]->vtype],
-                                      type_name[exprs[1]->vtype]);
-                        return false;
-                }
-            }
-            break;
-        case opid1('-'):
-            if  (exprs[0]->vtype != exprs[1]->vtype ||
-                (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT))
-            {
-                compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
-                              type_name[exprs[1]->vtype],
-                              type_name[exprs[0]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        out = fold_binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]);
-                        break;
-                    case TYPE_VECTOR:
-                        out = fold_binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]);
-                        break;
-                    default:
-                        compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
-                                      type_name[exprs[1]->vtype],
-                                      type_name[exprs[0]->vtype]);
-                        return false;
-                }
-            }
-            break;
-        case opid1('*'):
-            if (exprs[0]->vtype != exprs[1]->vtype &&
-                !(exprs[0]->vtype == TYPE_VECTOR &&
-                  exprs[1]->vtype == TYPE_FLOAT) &&
-                !(exprs[1]->vtype == TYPE_VECTOR &&
-                  exprs[0]->vtype == TYPE_FLOAT)
-                )
-            {
-                compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
-                              type_name[exprs[1]->vtype],
-                              type_name[exprs[0]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                switch (exprs[0]->vtype) {
-                    case TYPE_FLOAT:
-                        if (exprs[1]->vtype == TYPE_VECTOR)
-                            out = fold_binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]);
-                        else
-                            out = fold_binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]);
-                        break;
-                    case TYPE_VECTOR:
-                        if (exprs[1]->vtype == TYPE_FLOAT)
-                            out = fold_binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]);
-                        else
-                            out = fold_binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
-                        break;
-                    default:
-                        compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
-                                      type_name[exprs[1]->vtype],
-                                      type_name[exprs[0]->vtype]);
-                        return false;
-                }
-            }
-            break;
-
-        case opid1('/'):
-            if (exprs[1]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                if (exprs[0]->vtype == TYPE_FLOAT)
-                    out = fold_binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]);
-                else {
-                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
-                    return false;
-                }
-            }
-            break;
-
-        case opid1('%'):
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s",
-                    type_name[exprs[0]->vtype],
-                    type_name[exprs[1]->vtype]);
-                return false;
-            } else if (!(out = fold_op(parser->fold, op, exprs))) {
-                /* generate a call to __builtin_mod */
-                ast_expression *mod  = intrin_func(parser->intrin, "mod");
-                ast_call       *call = NULL;
-                if (!mod) return false; /* can return null for missing floor */
-
-                call = ast_call_new(parser_ctx(parser), mod);
-                vec_push(call->params, exprs[0]);
-                vec_push(call->params, exprs[1]);
-
-                out = (ast_expression*)call;
-            }
-            break;
-
-        case opid2('%','='):
-            compile_error(ctx, "%= is unimplemented");
-            return false;
-
-        case opid1('|'):
-        case opid1('&'):
-        case opid1('^'):
-            if ( !(exprs[0]->vtype == TYPE_FLOAT  && exprs[1]->vtype == TYPE_FLOAT) &&
-                 !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) &&
-                 !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR))
-            {
-                compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                /*
-                 * IF the first expression is float, the following will be too
-                 * since scalar ^ vector is not allowed.
-                 */
-                if (exprs[0]->vtype == TYPE_FLOAT) {
-                    out = fold_binary(ctx,
-                        (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND),
-                        exprs[0], exprs[1]);
-                } else {
-                    /*
-                     * The first is a vector: vector is allowed to bitop with vector and
-                     * with scalar, branch here for the second operand.
-                     */
-                    if (exprs[1]->vtype == TYPE_VECTOR) {
-                        /*
-                         * Bitop all the values of the vector components against the
-                         * vectors components in question.
-                         */
-                        out = fold_binary(ctx,
-                            (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V),
-                            exprs[0], exprs[1]);
-                    } else {
-                        out = fold_binary(ctx,
-                            (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF),
-                            exprs[0], exprs[1]);
-                    }
-                }
-            }
-            break;
-
-        case opid2('<','<'):
-        case opid2('>','>'):
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s",
-                    type_name[exprs[0]->vtype],
-                    type_name[exprs[1]->vtype]);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                ast_expression *shift = intrin_func(parser->intrin, (op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift");
-                ast_call       *call  = ast_call_new(parser_ctx(parser), shift);
-                vec_push(call->params, exprs[0]);
-                vec_push(call->params, exprs[1]);
-                out = (ast_expression*)call;
-            }
-            break;
-
-        case opid3('<','<','='):
-        case opid3('>','>','='):
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s",
-                    type_name[exprs[0]->vtype],
-                    type_name[exprs[1]->vtype]);
-                return false;
-            }
-
-            if(!(out = fold_op(parser->fold, op, exprs))) {
-                ast_expression *shift = intrin_func(parser->intrin, (op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift");
-                ast_call       *call  = ast_call_new(parser_ctx(parser), shift);
-                vec_push(call->params, exprs[0]);
-                vec_push(call->params, exprs[1]);
-                out = (ast_expression*)ast_store_new(
-                    parser_ctx(parser),
-                    INSTR_STORE_F,
-                    exprs[0],
-                    (ast_expression*)call
-                );
-            }
-
-            break;
-
-        case opid2('|','|'):
-            generated_op += 1; /* INSTR_OR */
-        case opid2('&','&'):
-            generated_op += INSTR_AND;
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                if (OPTS_FLAG(PERL_LOGIC) && !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));
-                    compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2);
-                    return false;
-                }
-                for (i = 0; i < 2; ++i) {
-                    if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->vtype == TYPE_VECTOR) {
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[i]);
-                        if (!out) break;
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out);
-                        if (!out) break;
-                        exprs[i] = out; out = NULL;
-                        if (OPTS_FLAG(PERL_LOGIC)) {
-                            /* here we want to keep the right expressions' type */
-                            break;
-                        }
-                    }
-                    else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->vtype == TYPE_STRING) {
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[i]);
-                        if (!out) break;
-                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out);
-                        if (!out) break;
-                        exprs[i] = out; out = NULL;
-                        if (OPTS_FLAG(PERL_LOGIC)) {
-                            /* here we want to keep the right expressions' type */
-                            break;
-                        }
-                    }
-                }
-                out = fold_binary(ctx, generated_op, exprs[0], exprs[1]);
-            }
-            break;
-
-        case opid2('?',':'):
-            if (vec_last(sy->paren) != PAREN_TERNARY2) {
-                compile_error(ctx, "mismatched parenthesis/ternary");
-                return false;
-            }
-            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));
-                compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]);
-            break;
-
-        case opid2('*', '*'):
-            if (NotSameType(TYPE_FLOAT)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in exponentiation: %s and %s",
-                    ty1, ty2);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser->intrin, "pow"));
-                vec_push(gencall->params, exprs[0]);
-                vec_push(gencall->params, exprs[1]);
-                out = (ast_expression*)gencall;
-            }
-            break;
-
-        case opid2('>', '<'):
-            if (NotSameType(TYPE_VECTOR)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in cross product: %s and %s",
-                    ty1, ty2);
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                out = fold_binary(
-                        parser_ctx(parser),
-                        VINSTR_CROSS,
-                        exprs[0],
-                        exprs[1]
-                );
-            }
-
-            break;
-
-        case opid3('<','=','>'): /* -1, 0, or 1 */
-            if (NotSameType(TYPE_FLOAT)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in comparision: %s and %s",
-                    ty1, ty2);
-
-                return false;
-            }
-
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                /* This whole block is NOT fold_binary safe */
-                ast_binary *eq = ast_binary_new(ctx, INSTR_EQ_F, exprs[0], exprs[1]);
-
-                eq->refs = AST_REF_NONE;
-
-                    /* if (lt) { */
-                out = (ast_expression*)ast_ternary_new(ctx,
-                        (ast_expression*)ast_binary_new(ctx, INSTR_LT, exprs[0], exprs[1]),
-                        /* out = -1 */
-                        (ast_expression*)parser->fold->imm_float[2],
-                    /* } else { */
-                        /* if (eq) { */
-                        (ast_expression*)ast_ternary_new(ctx, (ast_expression*)eq,
-                            /* out = 0 */
-                            (ast_expression*)parser->fold->imm_float[0],
-                        /* } else { */
-                            /* out = 1 */
-                            (ast_expression*)parser->fold->imm_float[1]
-                        /* } */
-                        )
-                    /* } */
-                    );
-
-            }
-            break;
-
-        case opid1('>'):
-            generated_op += 1; /* INSTR_GT */
-        case opid1('<'):
-            generated_op += 1; /* INSTR_LT */
-        case opid2('>', '='):
-            generated_op += 1; /* INSTR_GE */
-        case opid2('<', '='):
-            generated_op += INSTR_LE;
-            if (NotSameType(TYPE_FLOAT)) {
-                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = fold_binary(ctx, generated_op, exprs[0], exprs[1]);
-            break;
-        case opid2('!', '='):
-            if (exprs[0]->vtype != exprs[1]->vtype) {
-                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = fold_binary(ctx, type_ne_instr[exprs[0]->vtype], exprs[0], exprs[1]);
-            break;
-        case opid2('=', '='):
-            if (exprs[0]->vtype != exprs[1]->vtype) {
-                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
-                              type_name[exprs[0]->vtype],
-                              type_name[exprs[1]->vtype]);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs)))
-                out = fold_binary(ctx, type_eq_instr[exprs[0]->vtype], exprs[0], exprs[1]);
-            break;
-
-        case opid1('='):
-            if (ast_istype(exprs[0], ast_entfield)) {
-                ast_expression *field = ((ast_entfield*)exprs[0])->field;
-                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
-                    exprs[0]->vtype == TYPE_FIELD &&
-                    exprs[0]->next->vtype == TYPE_VECTOR)
-                {
-                    assignop = type_storep_instr[TYPE_VECTOR];
-                }
-                else
-                    assignop = type_storep_instr[exprs[0]->vtype];
-                if (assignop == VINSTR_END || !ast_compare_type(field->next, exprs[1]))
-                {
-                    ast_type_to_string(field->next, ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
-                        field->next->vtype == TYPE_FUNCTION &&
-                        exprs[1]->vtype == TYPE_FUNCTION)
-                    {
-                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
-                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                    }
-                    else
-                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                }
-            }
-            else
-            {
-                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
-                    exprs[0]->vtype == TYPE_FIELD &&
-                    exprs[0]->next->vtype == TYPE_VECTOR)
-                {
-                    assignop = type_store_instr[TYPE_VECTOR];
-                }
-                else {
-                    assignop = type_store_instr[exprs[0]->vtype];
-                }
-
-                if (assignop == VINSTR_END) {
-                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                    compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                }
-                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) &&
-                        exprs[0]->vtype == TYPE_FUNCTION &&
-                        exprs[1]->vtype == TYPE_FUNCTION)
-                    {
-                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
-                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                    }
-                    else
-                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
-                }
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            /* When we're a vector of part of an entity field we use STOREP */
-            if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->owner, ast_entfield))
-                assignop = INSTR_STOREP_F;
-            out = (ast_expression*)ast_store_new(ctx, assignop, exprs[0], exprs[1]);
-            break;
-        case opid3('+','+','P'):
-        case opid3('-','-','P'):
-            /* prefix ++ */
-            if (exprs[0]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for prefix increment: %s", ty1);
-                return false;
-            }
-            if (op->id == opid3('+','+','P'))
-                addop = INSTR_ADD_F;
-            else
-                addop = INSTR_SUB_F;
-            (void)check_write_to(ast_ctx(exprs[0]), exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield)) {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            } else {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            }
-            break;
-        case opid3('S','+','+'):
-        case opid3('S','-','-'):
-            /* prefix ++ */
-            if (exprs[0]->vtype != TYPE_FLOAT) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for suffix increment: %s", ty1);
-                return false;
-            }
-            if (op->id == opid3('S','+','+')) {
-                addop = INSTR_ADD_F;
-                subop = INSTR_SUB_F;
-            } else {
-                addop = INSTR_SUB_F;
-                subop = INSTR_ADD_F;
-            }
-            (void)check_write_to(ast_ctx(exprs[0]), exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield)) {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            } else {
-                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop,
-                                                        exprs[0],
-                                                        (ast_expression*)parser->fold->imm_float[1]);
-            }
-            if (!out)
-                return false;
-            out = fold_binary(ctx, subop,
-                              out,
-                              (ast_expression*)parser->fold->imm_float[1]);
-
-            break;
-        case opid2('+','='):
-        case opid2('-','='):
-            if (exprs[0]->vtype != exprs[1]->vtype ||
-                (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) )
-            {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            switch (exprs[0]->vtype) {
-                case TYPE_FLOAT:
-                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                            (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F),
-                                                            exprs[0], exprs[1]);
-                    break;
-                case TYPE_VECTOR:
-                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                            (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V),
-                                                            exprs[0], exprs[1]);
-                    break;
-                default:
-                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
-                                  type_name[exprs[0]->vtype],
-                                  type_name[exprs[1]->vtype]);
-                    return false;
-            };
-            break;
-        case opid2('*','='):
-        case opid2('/','='):
-            if (exprs[1]->vtype != TYPE_FLOAT ||
-                !(exprs[0]->vtype == TYPE_FLOAT ||
-                  exprs[0]->vtype == TYPE_VECTOR))
-            {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            switch (exprs[0]->vtype) {
-                case TYPE_FLOAT:
-                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                            (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F),
-                                                            exprs[0], exprs[1]);
-                    break;
-                case TYPE_VECTOR:
-                    if (op->id == opid2('*','=')) {
-                        out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF,
-                                                                exprs[0], exprs[1]);
-                    } else {
-                        out = fold_binary(ctx, INSTR_DIV_F,
-                                         (ast_expression*)parser->fold->imm_float[1],
-                                         exprs[1]);
-                        if (!out) {
-                            compile_error(ctx, "internal error: failed to generate division");
-                            return false;
-                        }
-                        out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF,
-                                                                exprs[0], out);
-                    }
-                    break;
-                default:
-                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
-                                  type_name[exprs[0]->vtype],
-                                  type_name[exprs[1]->vtype]);
-                    return false;
-            };
-            break;
-        case opid2('&','='):
-        case opid2('|','='):
-        case opid2('^','='):
-            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                        (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR),
-                                                        exprs[0], exprs[1]);
-            else
-                out = (ast_expression*)ast_binstore_new(ctx, assignop,
-                                                        (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V),
-                                                        exprs[0], exprs[1]);
-            break;
-        case opid3('&','~','='):
-            /* This is like: a &= ~(b);
-             * But QC has no bitwise-not, so we implement it as
-             * a -= a & (b);
-             */
-            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
-                compile_error(ctx, "invalid types used in expression: %s and %s",
-                              ty1, ty2);
-                return false;
-            }
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->vtype];
-            else
-                assignop = type_store_instr[exprs[0]->vtype];
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                out = fold_binary(ctx, INSTR_BITAND, exprs[0], exprs[1]);
-            else
-                out = fold_binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]);
-            if (!out)
-                return false;
-            (void)check_write_to(ctx, exprs[0]);
-            if (exprs[0]->vtype == TYPE_FLOAT)
-                asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out);
-            else
-                asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out);
-            asbinstore->keep_dest = true;
-            out = (ast_expression*)asbinstore;
-            break;
-
-        case opid3('l', 'e', 'n'):
-            if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1);
-                return false;
-            }
-            /* strings must be const, arrays are statically sized */
-            if (exprs[0]->vtype == TYPE_STRING &&
-                !(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST))
-            {
-                compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression");
-                return false;
-            }
-            out = fold_op(parser->fold, op, exprs);
-            break;
-
-        case opid2('~', 'P'):
-            if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
-                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
-                compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1);
-                return false;
-            }
-            if (!(out = fold_op(parser->fold, op, exprs))) {
-                if (exprs[0]->vtype == TYPE_FLOAT) {
-                    out = fold_binary(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]);
-                } else {
-                    out = fold_binary(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]);
-                }
-            }
-            break;
-    }
-#undef NotSameType
-    if (!out) {
-        compile_error(ctx, "failed to apply operator %s", op->op);
-        return false;
-    }
-
-    vec_push(sy->out, syexp(ctx, out));
-    return true;
-}
-
-static bool parser_close_call(parser_t *parser, shunt *sy)
-{
-    /* was a function call */
-    ast_expression *fun;
-    ast_value      *funval = NULL;
-    ast_call       *call;
-
-    size_t          fid;
-    size_t          paramcount, i;
-    bool            fold = true;
-
-    fid = vec_last(sy->ops).off;
-    vec_shrinkby(sy->ops, 1);
-
-    /* out[fid] is the function
-     * everything above is parameters...
-     */
-    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) < 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;
-    }
-
-    /*
-     * TODO handle this at the intrinsic level with an ast_intrinsic
-     * node and codegen.
-     */
-    if ((fun = sy->out[fid].out) == intrin_debug_typestring(parser->intrin)) {
-        char ty[1024];
-        if (fid+2 != vec_size(sy->out) ||
-            vec_last(sy->out).block)
-        {
-            parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter");
-            return false;
-        }
-        ast_type_to_string(vec_last(sy->out).out, ty, sizeof(ty));
-        ast_unref(vec_last(sy->out).out);
-        sy->out[fid] = syexp(ast_ctx(vec_last(sy->out).out),
-                             (ast_expression*)fold_constgen_string(parser->fold, ty, false));
-        vec_shrinkby(sy->out, 1);
-        return true;
-    }
-
-    /*
-     * Now we need to determine if the function that is being called is
-     * an intrinsic so we can evaluate if the arguments to it are constant
-     * and than fruitfully fold them.
-     */
-#define fold_can_1(X)  \
-    (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \
-                ((ast_expression*)(X))->vtype != TYPE_FUNCTION)
-
-    if (fid + 1 < vec_size(sy->out))
-        ++paramcount;
-
-    for (i = 0; i < paramcount; ++i) {
-        if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) {
-            fold = false;
-            break;
-        }
-    }
-
-    /*
-     * All is well which ends well, if we make it into here we can ignore the
-     * intrinsic call and just evaluate it i.e constant fold it.
-     */
-    if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->intrinsic) {
-        ast_expression **exprs  = NULL;
-        ast_expression *foldval = NULL;
-
-        for (i = 0; i < paramcount; i++)
-            vec_push(exprs, sy->out[fid+1 + i].out);
-
-        if (!(foldval = intrin_fold(parser->intrin, (ast_value*)fun, exprs))) {
-            vec_free(exprs);
-            goto fold_leave;
-        }
-
-        /*
-         * Blub: what sorts of unreffing and resizing of
-         * sy->out should I be doing here?
-         */
-        sy->out[fid] = syexp(foldval->node.context, foldval);
-        vec_shrinkby(sy->out, paramcount);
-        vec_free(exprs);
-
-        return true;
-    }
-
-    fold_leave:
-    call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun);
-
-    if (!call)
-        return false;
-
-    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, parser->function->vtype->expression.varparam);
-    if (parser->max_param_count < paramcount)
-        parser->max_param_count = paramcount;
-
-    if (ast_istype(fun, ast_value)) {
-        funval = (ast_value*)fun;
-        if ((fun->flags & AST_FLAG_VARIADIC) &&
-            !(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin))
-        {
-            call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount, false);
-        }
-    }
-
-    /* overwrite fid, the function, with a call */
-    sy->out[fid] = syexp(call->expression.node.context, (ast_expression*)call);
-
-    if (fun->vtype != TYPE_FUNCTION) {
-        parseerror(parser, "not a function (%s)", type_name[fun->vtype]);
-        return false;
-    }
-
-    if (!fun->next) {
-        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->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->params) != paramcount &&
-            !((fun->flags & AST_FLAG_VARIADIC) &&
-              vec_size(fun->params) < paramcount))
-        {
-            const char *fewmany = (vec_size(fun->params) > paramcount) ? "few" : "many";
-            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->params), (int)paramcount,
-                                     fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line);
-            else
-                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->params), (int)paramcount,
-                                     ast_ctx(fun).file, (int)ast_ctx(fun).line);
-        }
-    }
-
-    return true;
-}
-
-static bool parser_close_paren(parser_t *parser, shunt *sy)
-{
-    if (!vec_size(sy->ops)) {
-        parseerror(parser, "unmatched closing paren");
-        return false;
-    }
-
-    while (vec_size(sy->ops)) {
-        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);
-                break;
-            }
-            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;
-            }
-            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;
-    }
-    return true;
-}
-
-static void parser_reclassify_token(parser_t *parser)
-{
-    size_t i;
-    if (parser->tok >= TOKEN_START)
-        return;
-    for (i = 0; i < operator_count; ++i) {
-        if (!strcmp(parser_tokval(parser), operators[i].op)) {
-            parser->tok = TOKEN_OPERATOR;
-            return;
-        }
-    }
-}
-
-static ast_expression* parse_vararg_do(parser_t *parser)
-{
-    ast_expression *idx, *out;
-    ast_value      *typevar;
-    ast_value      *funtype = parser->function->vtype;
-    lex_ctx_t         ctx     = parser_ctx(parser);
-
-    if (!parser->function->varargs) {
-        parseerror(parser, "function has no variable argument list");
-        return NULL;
-    }
-
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected parameter index and type in parenthesis");
-        return NULL;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "error parsing parameter index");
-        return NULL;
-    }
-
-    idx = parse_expression_leave(parser, true, false, false);
-    if (!idx)
-        return NULL;
-
-    if (parser->tok != ',') {
-        if (parser->tok != ')') {
-            ast_unref(idx);
-            parseerror(parser, "expected comma after parameter index");
-            return NULL;
-        }
-        /* vararg piping: ...(start) */
-        out = (ast_expression*)ast_argpipe_new(ctx, idx);
-        return out;
-    }
-
-    if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) {
-        ast_unref(idx);
-        parseerror(parser, "expected typename for vararg");
-        return NULL;
-    }
-
-    typevar = parse_typename(parser, NULL, NULL, NULL);
-    if (!typevar) {
-        ast_unref(idx);
-        return NULL;
-    }
-
-    if (parser->tok != ')') {
-        ast_unref(idx);
-        ast_delete(typevar);
-        parseerror(parser, "expected closing paren");
-        return NULL;
-    }
-
-    if (funtype->expression.varparam &&
-        !ast_compare_type((ast_expression*)typevar, (ast_expression*)funtype->expression.varparam))
-    {
-        char ty1[1024];
-        char ty2[1024];
-        ast_type_to_string((ast_expression*)typevar, ty1, sizeof(ty1));
-        ast_type_to_string((ast_expression*)funtype->expression.varparam, ty2, sizeof(ty2));
-        compile_error(ast_ctx(typevar),
-                      "function was declared to take varargs of type `%s`, requested type is: %s",
-                      ty2, ty1);
-    }
-
-    out = (ast_expression*)ast_array_index_new(ctx, (ast_expression*)(parser->function->varargs), idx);
-    ast_type_adopt(out, typevar);
-    ast_delete(typevar);
-    return out;
-}
-
-static ast_expression* parse_vararg(parser_t *parser)
-{
-    bool           old_noops = parser->lex->flags.noops;
-
-    ast_expression *out;
-
-    parser->lex->flags.noops = true;
-    out = parse_vararg_do(parser);
-
-    parser->lex->flags.noops = old_noops;
-    return out;
-}
-
-/* not to be exposed */
-bool ftepp_predef_exists(const char *name);
-static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
-{
-    if (OPTS_FLAG(TRANSLATABLE_STRINGS) &&
-        parser->tok == TOKEN_IDENT &&
-        !strcmp(parser_tokval(parser), "_"))
-    {
-        /* 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");
-            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 = (ast_value*)fold_constgen_string(parser->fold, 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");
-            return false;
-        }
-        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_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f), false);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
-        ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i), false);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_STRINGCONST) {
-        ast_expression *val = fold_constgen_string(parser->fold, parser_tokval(parser), false);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), val));
-        return true;
-    }
-    else if (parser->tok == TOKEN_VECTORCONST) {
-        ast_expression *val = fold_constgen_vector(parser->fold, parser_token(parser)->constval.v);
-        if (!val)
-            return false;
-        vec_push(sy->out, syexp(parser_ctx(parser), 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('.'))
-        {
-            /* When adding more intrinsics, fix the above condition */
-            prev = NULL;
-        }
-        if (prev && prev->vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1])
-        {
-            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 && !strcmp(parser_tokval(parser), "__FUNC__"))
-            var = (ast_expression*)fold_constgen_string(parser->fold, parser->function->name, false);
-        if (!var) {
-            /*
-             * now we try for the real intrinsic hashtable. If the string
-             * begins with __builtin, we simply skip past it, otherwise we
-             * use the identifier as is.
-             */
-            if (!strncmp(parser_tokval(parser), "__builtin_", 10)) {
-                var = intrin_func(parser->intrin, parser_tokval(parser));
-            }
-
-            /*
-             * Try it again, intrin_func deals with the alias method as well
-             * the first one masks for __builtin though, we emit warning here.
-             */
-            if (!var) {
-                if ((var = intrin_func(parser->intrin, parser_tokval(parser)))) {
-                    (void)!compile_warning(
-                        parser_ctx(parser),
-                        WARN_BUILTINS,
-                        "using implicitly defined builtin `__builtin_%s' for `%s'",
-                        parser_tokval(parser),
-                        parser_tokval(parser)
-                    );
-                }
-            }
-
-
-            if (!var) {
-                /*
-                 * 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) && ftepp_predef_exists(parser_tokval(parser))) {
-                    parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
-                    return false;
-                }
-
-                parseerror(parser, "unexpected identifier: %s", parser_tokval(parser));
-                return false;
-            }
-        }
-        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++;
-            }
-        }
-        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_parenthesis = 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 `%s`", parser_tokval(parser));
-            goto onerr;
-        }
-
-        if (parser->tok == TOKEN_OPERATOR)
-        {
-            /* classify the operator */
-            const oper_info *op;
-            const oper_info *olast = NULL;
-            size_t o;
-            for (o = 0; o < operator_count; ++o) {
-                if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) &&
-                    /* !(operators[o].flags & OP_SUFFIX) && / * remove this */
-                    !strcmp(parser_tokval(parser), operators[o].op))
-                {
-                    break;
-                }
-            }
-            if (o == operator_count) {
-                compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser));
-                goto onerr;
-            }
-            /* found an operator */
-            op = &operators[o];
-
-            /* when declaring variables, a comma starts a new variable */
-            if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) {
-                /* fixup the token */
-                parser->tok = ',';
-                break;
-            }
-
-            /* a colon without a pervious question mark cannot be a ternary */
-            if (!ternaries && op->id == opid2(':','?')) {
-                parser->tok = ':';
-                break;
-            }
-
-            if (op->id == opid1(',')) {
-                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).isparen)
-                olast = &operators[vec_last(sy.ops).etype-1];
-
-            /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */
-            while (olast && op->prec < olast->prec)
-            {
-                if (!parser_sy_apply_operator(parser, &sy))
-                    goto onerr;
-                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
-                    olast = &operators[vec_last(sy.ops).etype-1];
-                else
-                    olast = NULL;
-            }
-
-#define IsAssignOp(x) (\
-                (x) == opid1('=') || \
-                (x) == opid2('+','=') || \
-                (x) == opid2('-','=') || \
-                (x) == opid2('*','=') || \
-                (x) == opid2('/','=') || \
-                (x) == opid2('%','=') || \
-                (x) == opid2('&','=') || \
-                (x) == opid2('|','=') || \
-                (x) == opid3('&','~','=') \
-                )
-            if (warn_parenthesis) {
-                if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) ||
-                     (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) ||
-                     (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id))
-                   )
-                {
-                    (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value");
-                    warn_parenthesis = false;
-                }
-
-                if (olast && olast->id != op->id) {
-                    if ((op->id    == opid1('&') || op->id    == opid1('|') || op->id    == opid1('^')) &&
-                        (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^')))
-                    {
-                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations");
-                        warn_parenthesis = false;
-                    }
-                    else if ((op->id    == opid2('&','&') || op->id    == opid2('|','|')) &&
-                             (olast->id == opid2('&','&') || olast->id == opid2('|','|')))
-                    {
-                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations");
-                        warn_parenthesis = false;
-                    }
-                }
-            }
-
-            while (olast && (
-                    (op->prec < olast->prec) ||
-                    (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) )
-            {
-                if (!parser_sy_apply_operator(parser, &sy))
-                    goto onerr;
-                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
-                    olast = &operators[vec_last(sy.ops).etype-1];
-                else
-                    olast = NULL;
-            }
-
-            if (op->id == opid1('(')) {
-                if (wantop) {
-                    size_t sycount = vec_size(sy.out);
-                    /* we expected an operator, this is the function-call operator */
-                    vec_push(sy.paren, PAREN_FUNC);
-                    vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1));
-                    vec_push(sy.argc, 0);
-                } else {
-                    vec_push(sy.paren, PAREN_EXPR);
-                    vec_push(sy.ops, syparen(parser_ctx(parser), 0));
-                }
-                wantop = false;
-            } else if (op->id == opid1('[')) {
-                if (!wantop) {
-                    parseerror(parser, "unexpected array subscript");
-                    goto onerr;
-                }
-                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), 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), 0));
-                wantop = false;
-                ++ternaries;
-                vec_push(sy.paren, PAREN_TERNARY1);
-            } else if (op->id == opid2(':','?')) {
-                if (!vec_size(sy.paren)) {
-                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
-                    goto onerr;
-                }
-                if (vec_last(sy.paren) != PAREN_TERNARY1) {
-                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
-                    goto onerr;
-                }
-                if (!parser_close_paren(parser, &sy))
-                    goto onerr;
-                vec_push(sy.ops, syop(parser_ctx(parser), op));
-                wantop = false;
-                --ternaries;
-            } else {
-                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;
-            wantop = true;
-        }
-        else {
-            /* in this case we might want to allow constant string concatenation */
-            bool concatenated = false;
-            if (parser->tok == TOKEN_STRINGCONST && vec_size(sy.out)) {
-                ast_expression *lexpr = vec_last(sy.out).out;
-                if (ast_istype(lexpr, ast_value)) {
-                    ast_value *last = (ast_value*)lexpr;
-                    if (last->isimm == true && last->cvq == CV_CONST &&
-                        last->hasvalue && last->expression.vtype == TYPE_STRING)
-                    {
-                        char *newstr = NULL;
-                        util_asprintf(&newstr, "%s%s", last->constval.vstring, parser_tokval(parser));
-                        vec_last(sy.out).out = (ast_expression*)fold_constgen_string(parser->fold, newstr, false);
-                        mem_d(newstr);
-                        concatenated = true;
-                    }
-                }
-            }
-            if (!concatenated) {
-                parseerror(parser, "expected operator or end of statement");
-                goto onerr;
-            }
-        }
-
-        if (!parser_next(parser)) {
-            goto onerr;
-        }
-        if (parser->tok == ';' ||
-            ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) &&
-            (parser->tok == ']' || parser->tok == ')' || parser->tok == '}')))
-        {
-            break;
-        }
-    }
-
-    while (vec_size(sy.ops)) {
-        if (!parser_sy_apply_operator(parser, &sy))
-            goto onerr;
-    }
-
-    parser->lex->flags.noops = true;
-    if (vec_size(sy.out) != 1) {
-        parseerror(parser, "expression expected");
-        expr = NULL;
-    } else
-        expr = sy.out[0].out;
-    vec_free(sy.out);
-    vec_free(sy.ops);
-    if (vec_size(sy.paren)) {
-        parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren));
-        return NULL;
-    }
-    vec_free(sy.paren);
-    vec_free(sy.argc);
-    return expr;
-
-onerr:
-    parser->lex->flags.noops = true;
-    for (i = 0; i < vec_size(sy.out); ++i) {
-        if (sy.out[i].out)
-            ast_unref(sy.out[i].out);
-    }
-    vec_free(sy.out);
-    vec_free(sy.ops);
-    vec_free(sy.paren);
-    vec_free(sy.argc);
-    return NULL;
-}
-
-static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels)
-{
-    ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels);
-    if (!e)
-        return NULL;
-    if (parser->tok != ';') {
-        parseerror(parser, "semicolon expected after expression");
-        ast_unref(e);
-        return NULL;
-    }
-    if (!parser_next(parser)) {
-        ast_unref(e);
-        return NULL;
-    }
-    return e;
-}
-
-static void parser_enterblock(parser_t *parser)
-{
-    vec_push(parser->variables, util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->_blocklocals, vec_size(parser->_locals));
-    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
-    vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs));
-    vec_push(parser->_block_ctx, parser_ctx(parser));
-}
-
-static bool parser_leaveblock(parser_t *parser)
-{
-    bool   rv = true;
-    size_t locals, typedefs;
-
-    if (vec_size(parser->variables) <= PARSER_HT_LOCALS) {
-        parseerror(parser, "internal error: parser_leaveblock with no block");
-        return false;
-    }
-
-    util_htdel(vec_last(parser->variables));
-
-    vec_pop(parser->variables);
-    if (!vec_size(parser->_blocklocals)) {
-        parseerror(parser, "internal error: parser_leaveblock with no block (2)");
-        return false;
-    }
-
-    locals = vec_last(parser->_blocklocals);
-    vec_pop(parser->_blocklocals);
-    while (vec_size(parser->_locals) != locals) {
-        ast_expression *e = vec_last(parser->_locals);
-        ast_value      *v = (ast_value*)e;
-        vec_pop(parser->_locals);
-        if (ast_istype(e, ast_value) && !v->uses) {
-            if (compile_warning(ast_ctx(v), WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->name))
-                rv = false;
-        }
-    }
-
-    typedefs = vec_last(parser->_blocktypedefs);
-    while (vec_size(parser->_typedefs) != typedefs) {
-        ast_delete(vec_last(parser->_typedefs));
-        vec_pop(parser->_typedefs);
-    }
-    util_htdel(vec_last(parser->typedefs));
-    vec_pop(parser->typedefs);
-
-    vec_pop(parser->_block_ctx);
-
-    return rv;
-}
-
-static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e)
-{
-    vec_push(parser->_locals, e);
-    util_htset(vec_last(parser->variables), name, (void*)e);
-}
-
-static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e)
-{
-    vec_push(parser->globals, e);
-    util_htset(parser->htglobals, name, e);
-}
-
-static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot)
-{
-    bool       ifnot = false;
-    ast_unary *unary;
-    ast_expression *prev;
-
-    if (cond->vtype == TYPE_VOID || cond->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->vtype == TYPE_STRING)
-    {
-        prev = cond;
-        cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_S, cond);
-        if (!cond) {
-            ast_unref(prev);
-            parseerror(parser, "internal error: failed to process condition");
-            return NULL;
-        }
-        ifnot = !ifnot;
-    }
-    else if (OPTS_FLAG(CORRECT_LOGIC) && cond->vtype == TYPE_VECTOR)
-    {
-        /* vector types need to be cast to true booleans */
-        ast_binary *bin = (ast_binary*)cond;
-        if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->op == INSTR_AND || bin->op == INSTR_OR))
-        {
-            /* in perl-logic, AND and OR take care of the -fcorrect-logic */
-            prev = cond;
-            cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_V, cond);
-            if (!cond) {
-                ast_unref(prev);
-                parseerror(parser, "internal error: failed to process condition");
-                return NULL;
-            }
-            ifnot = !ifnot;
-        }
-    }
-
-    unary = (ast_unary*)cond;
-    /* ast_istype dereferences cond, should test here for safety */
-    while (cond && ast_istype(cond, ast_unary) && unary->op == INSTR_NOT_F)
-    {
-        cond = unary->operand;
-        unary->operand = NULL;
-        ast_delete(unary);
-        ifnot = !ifnot;
-        unary = (ast_unary*)cond;
-    }
-
-    if (!cond)
-        parseerror(parser, "internal error: failed to process condition");
-
-    if (ifnot) *_ifnot = !*_ifnot;
-    return cond;
-}
-
-static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_ifthen *ifthen;
-    ast_expression *cond, *ontrue = NULL, *onfalse = NULL;
-    bool ifnot = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    /* skip the 'if', parse an optional 'not' and check for an opening paren */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected condition or 'not'");
-        return false;
-    }
-    if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) {
-        ifnot = true;
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected condition in parenthesis");
-            return false;
-        }
-    }
-    if (parser->tok != '(') {
-        parseerror(parser, "expected 'if' condition in parenthesis");
-        return false;
-    }
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'if' condition after opening paren");
-        return false;
-    }
-    /* parse the condition */
-    cond = parse_expression_leave(parser, false, true, false);
-    if (!cond)
-        return false;
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'if' condition");
-        ast_unref(cond);
-        return false;
-    }
-    /* parse into the 'then' branch */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected statement for on-true branch of 'if'");
-        ast_unref(cond);
-        return false;
-    }
-    if (!parse_statement_or_block(parser, &ontrue)) {
-        ast_unref(cond);
-        return false;
-    }
-    if (!ontrue)
-        ontrue = (ast_expression*)ast_block_new(parser_ctx(parser));
-    /* check for an else */
-    if (!strcmp(parser_tokval(parser), "else")) {
-        /* parse into the 'else' branch */
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected on-false branch after 'else'");
-            ast_delete(ontrue);
-            ast_unref(cond);
-            return false;
-        }
-        if (!parse_statement_or_block(parser, &onfalse)) {
-            ast_delete(ontrue);
-            ast_unref(cond);
-            return false;
-        }
-    }
-
-    cond = process_condition(parser, cond, &ifnot);
-    if (!cond) {
-        if (ontrue)  ast_delete(ontrue);
-        if (onfalse) ast_delete(onfalse);
-        return false;
-    }
-
-    if (ifnot)
-        ifthen = ast_ifthen_new(ctx, cond, onfalse, ontrue);
-    else
-        ifthen = ast_ifthen_new(ctx, cond, ontrue, onfalse);
-    *out = (ast_expression*)ifthen;
-    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;
-
-    bool ifnot = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    /* 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, true, false);
-    if (!cond)
-        return false;
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'while' condition");
-        ast_unref(cond);
-        return false;
-    }
-    /* parse into the 'then' branch */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected while-loop body");
-        ast_unref(cond);
-        return false;
-    }
-    if (!parse_statement_or_block(parser, &ontrue)) {
-        ast_unref(cond);
-        return false;
-    }
-
-    cond = process_condition(parser, cond, &ifnot);
-    if (!cond) {
-        ast_unref(ontrue);
-        return false;
-    }
-    aloop = ast_loop_new(ctx, NULL, cond, ifnot, NULL, false, NULL, ontrue);
-    *out = (ast_expression*)aloop;
-    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;
-        /*
-         * Test for NULL otherwise ast_delete dereferences null pointer
-         * and boom.
-         */
-        if (*out)
-            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;
-
-    bool ifnot = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    if (!parse_statement_or_block(parser, &ontrue))
-        return false;
-
-    /* expect the "while" */
-    if (parser->tok != TOKEN_KEYWORD ||
-        strcmp(parser_tokval(parser), "while"))
-    {
-        parseerror(parser, "expected 'while' and condition");
-        ast_delete(ontrue);
-        return false;
-    }
-
-    /* skip the 'while' and check for opening paren */
-    if (!parser_next(parser) || parser->tok != '(') {
-        parseerror(parser, "expected 'while' condition in parenthesis");
-        ast_delete(ontrue);
-        return false;
-    }
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'while' condition after opening paren");
-        ast_delete(ontrue);
-        return false;
-    }
-    /* parse the condition */
-    cond = parse_expression_leave(parser, false, true, false);
-    if (!cond)
-        return false;
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'while' condition");
-        ast_delete(ontrue);
-        ast_unref(cond);
-        return false;
-    }
-    /* parse on */
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "expected semicolon after condition");
-        ast_delete(ontrue);
-        ast_unref(cond);
-        return false;
-    }
-
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error");
-        ast_delete(ontrue);
-        ast_unref(cond);
-        return false;
-    }
-
-    cond = process_condition(parser, cond, &ifnot);
-    if (!cond) {
-        ast_delete(ontrue);
-        return false;
-    }
-    aloop = ast_loop_new(ctx, NULL, NULL, false, cond, ifnot, NULL, ontrue);
-    *out = (ast_expression*)aloop;
-    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;
-    ast_value      *typevar;
-
-    bool ifnot  = false;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    parser_enterblock(parser);
-
-    initexpr  = NULL;
-    cond      = NULL;
-    increment = NULL;
-    ontrue    = NULL;
-
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected 'for' initializer after opening paren");
-        goto onerr;
-    }
-
-    typevar = NULL;
-    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, true, CV_VAR, typevar, false, false, 0, NULL))
-            goto onerr;
-    }
-    else if (parser->tok != ';')
-    {
-        initexpr = parse_expression_leave(parser, false, false, false);
-        if (!initexpr)
-            goto onerr;
-
-        /* move on to condition */
-        if (parser->tok != ';') {
-            parseerror(parser, "expected semicolon after for-loop initializer");
-            goto onerr;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected for-loop condition");
-            goto onerr;
-        }
-    }
-
-    /* parse the condition */
-    if (parser->tok != ';') {
-        cond = parse_expression_leave(parser, false, true, false);
-        if (!cond)
-            goto onerr;
-    }
-
-    /* move on to incrementor */
-    if (parser->tok != ';') {
-        parseerror(parser, "expected semicolon after for-loop initializer");
-        goto onerr;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected for-loop condition");
-        goto onerr;
-    }
-
-    /* parse the incrementor */
-    if (parser->tok != ')') {
-        lex_ctx_t condctx = parser_ctx(parser);
-        increment = parse_expression_leave(parser, false, false, false);
-        if (!increment)
-            goto onerr;
-        if (!ast_side_effects(increment)) {
-            if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
-                goto onerr;
-        }
-    }
-
-    /* closing paren */
-    if (parser->tok != ')') {
-        parseerror(parser, "expected closing paren after 'for-loop' incrementor");
-        goto onerr;
-    }
-    /* parse into the 'then' branch */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected for-loop body");
-        goto onerr;
-    }
-    if (!parse_statement_or_block(parser, &ontrue))
-        goto onerr;
-
-    if (cond) {
-        cond = process_condition(parser, cond, &ifnot);
-        if (!cond)
-            goto onerr;
-    }
-    aloop = ast_loop_new(ctx, initexpr, cond, ifnot, NULL, false, increment, ontrue);
-    *out = (ast_expression*)aloop;
-
-    if (!parser_leaveblock(parser)) {
-        ast_delete(aloop);
-        return false;
-    }
-    return true;
-onerr:
-    if (initexpr)  ast_unref(initexpr);
-    if (cond)      ast_unref(cond);
-    if (increment) ast_unref(increment);
-    (void)!parser_leaveblock(parser);
-    return false;
-}
-
-static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out)
-{
-    ast_expression *exp      = NULL;
-    ast_expression *var      = NULL;
-    ast_return     *ret      = NULL;
-    ast_value      *retval   = parser->function->return_value;
-    ast_value      *expected = parser->function->vtype;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected return expression");
-        return false;
-    }
-
-    /* return assignments */
-    if (parser->tok == '=') {
-        if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) {
-            parseerror(parser, "return assignments not activated, try using -freturn-assigments");
-            return false;
-        }
-
-        if (type_store_instr[expected->expression.next->vtype] == VINSTR_END) {
-            char ty1[1024];
-            ast_type_to_string(expected->expression.next, ty1, sizeof(ty1));
-            parseerror(parser, "invalid return type: `%s'", ty1);
-            return false;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected return assignment expression");
-            return false;
-        }
-
-        if (!(exp = parse_expression_leave(parser, false, false, false)))
-            return false;
-
-        /* prepare the return value */
-        if (!retval) {
-            retval = ast_value_new(ctx, "#LOCAL_RETURN", TYPE_VOID);
-            ast_type_adopt(retval, expected->expression.next);
-            parser->function->return_value = retval;
-        }
-
-        if (!ast_compare_type(exp, (ast_expression*)retval)) {
-            char ty1[1024], ty2[1024];
-            ast_type_to_string(exp, ty1, sizeof(ty1));
-            ast_type_to_string(&retval->expression, ty2, sizeof(ty2));
-            parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2);
-        }
-
-        /* store to 'return' local variable */
-        var = (ast_expression*)ast_store_new(
-            ctx,
-            type_store_instr[expected->expression.next->vtype],
-            (ast_expression*)retval, exp);
-
-        if (!var) {
-            ast_unref(exp);
-            return false;
-        }
-
-        if (parser->tok != ';')
-            parseerror(parser, "missing semicolon after return assignment");
-        else if (!parser_next(parser))
-            parseerror(parser, "parse error after return assignment");
-
-        *out = var;
-        return true;
-    }
-
-    if (parser->tok != ';') {
-        exp = parse_expression(parser, false, false);
-        if (!exp)
-            return false;
-
-        if (exp->vtype != TYPE_NIL &&
-            exp->vtype != ((ast_expression*)expected)->next->vtype)
-        {
-            parseerror(parser, "return with invalid expression");
-        }
-
-        ret = ast_return_new(ctx, exp);
-        if (!ret) {
-            ast_unref(exp);
-            return false;
-        }
-    } else {
-        if (!parser_next(parser))
-            parseerror(parser, "parse error");
-
-        if (!retval && expected->expression.next->vtype != TYPE_VOID)
-        {
-            (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
-        }
-        ret = ast_return_new(ctx, (ast_expression*)retval);
-    }
-    *out = (ast_expression*)ret;
-    return true;
-}
-
-static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue)
-{
-    size_t       i;
-    unsigned int levels = 0;
-    lex_ctx_t      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 (!vec_size(loops)) {
-        if (is_continue)
-            parseerror(parser, "`continue` can only be used inside loops");
-        else
-            parseerror(parser, "`break` can only be used inside loops or switches");
-    }
-
-    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;
-    }
-
-    if (!parser_next(parser))
-        parseerror(parser, "parse error");
-
-    *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
- */
-typedef struct {
-    const char *name;
-    size_t      flag;
-} attribute_t;
-
-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_attrib   = false;
-    bool had_static   = false;
-    uint32_t flags    = 0;
-
-    static attribute_t attributes[] = {
-        { "noreturn",   AST_FLAG_NORETURN   },
-        { "inline",     AST_FLAG_INLINE     },
-        { "eraseable",  AST_FLAG_ERASEABLE  },
-        { "accumulate", AST_FLAG_ACCUMULATE },
-        { "last",       AST_FLAG_FINAL_DECL }
-    };
-
-   *cvq = CV_NONE;
-
-    for (;;) {
-        size_t i;
-        if (parser->tok == TOKEN_ATTRIBUTE_OPEN) {
-            had_attrib = true;
-            /* parse an attribute */
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected attribute after `[[`");
-                *cvq = CV_WRONG;
-                return false;
-            }
-
-            for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) {
-                if (!strcmp(parser_tokval(parser), attributes[i].name)) {
-                    flags |= attributes[i].flag;
-                    if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                        parseerror(parser, "`%s` attribute has no parameters, expected `]]`",
-                            attributes[i].name);
-                        *cvq = CV_WRONG;
-                        return false;
-                    }
-                    break;
-                }
-            }
-
-            if (i != GMQCC_ARRAY_COUNT(attributes))
-                goto leave;
-
-
-            if (!strcmp(parser_tokval(parser), "noref")) {
-                had_noref = true;
-                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), "alias") && !(flags & AST_FLAG_ALIAS)) {
-                flags   |= AST_FLAG_ALIAS;
-                *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, "`alias` 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, "`alias` attribute expected `)` after parameter");
-                        goto argerr;
-                    }
-
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "parse error in attribute");
-                        goto argerr;
-                    }
-                }
-
-                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                    parseerror(parser, "`alias` attribute expected `]]`");
-                    goto argerr;
-                }
-            }
-            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 if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) {
-                flags |= AST_FLAG_COVERAGE;
-                if (!parser_next(parser)) {
-                    error_in_coverage:
-                    parseerror(parser, "parse error in coverage attribute");
-                    *cvq = CV_WRONG;
-                    return false;
-                }
-                if (parser->tok == '(') {
-                    if (!parser_next(parser)) {
-                        bad_coverage_arg:
-                        parseerror(parser, "invalid parameter for coverage() attribute\n"
-                                           "valid are: block");
-                        *cvq = CV_WRONG;
-                        return false;
-                    }
-                    if (parser->tok != ')') {
-                        do {
-                            if (parser->tok != TOKEN_IDENT)
-                                goto bad_coverage_arg;
-                            if (!strcmp(parser_tokval(parser), "block"))
-                                flags |= AST_FLAG_BLOCK_COVERAGE;
-                            else if (!strcmp(parser_tokval(parser), "none"))
-                                flags &= ~(AST_FLAG_COVERAGE_MASK);
-                            else
-                                goto bad_coverage_arg;
-                            if (!parser_next(parser))
-                                goto error_in_coverage;
-                            if (parser->tok == ',') {
-                                if (!parser_next(parser))
-                                    goto error_in_coverage;
-                            }
-                        } while (parser->tok != ')');
-                    }
-                    if (parser->tok != ')' || !parser_next(parser))
-                        goto error_in_coverage;
-                } else {
-                    /* without parameter [[coverage]] equals [[coverage(block)]] */
-                    flags |= AST_FLAG_BLOCK_COVERAGE;
-                }
-            }
-            else
-            {
-                /* Skip tokens until we hit a ]] */
-                (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser));
-                while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
-                    if (!parser_next(parser)) {
-                        parseerror(parser, "error inside attribute");
-                        *cvq = CV_WRONG;
-                        return false;
-                    }
-                }
-            }
-        }
-        else if (with_local && !strcmp(parser_tokval(parser), "static"))
-            had_static = true;
-        else if (!strcmp(parser_tokval(parser), "const"))
-            had_const = true;
-        else if (!strcmp(parser_tokval(parser), "var"))
-            had_var = true;
-        else if (with_local && !strcmp(parser_tokval(parser), "local"))
-            had_var = true;
-        else if (!strcmp(parser_tokval(parser), "noref"))
-            had_noref = true;
-        else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) {
-            return false;
-        }
-        else
-            break;
-
-        leave:
-        if (!parser_next(parser))
-            goto onerr;
-    }
-    if (had_const)
-        *cvq = CV_CONST;
-    else if (had_var)
-        *cvq = CV_VAR;
-    else
-        *cvq = CV_NONE;
-    *noref     = had_noref;
-    *is_static = had_static;
-    *_flags    = flags;
-    return true;
-onerr:
-    parseerror(parser, "parse error after variable qualifier");
-    *cvq = CV_WRONG;
-    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;
-    ast_value      *typevar;
-    ast_switch     *switchnode;
-    ast_switch_case swcase;
-
-    int  cvq;
-    bool noref, is_static;
-    uint32_t qflags = 0;
-
-    lex_ctx_t ctx = parser_ctx(parser);
-
-    (void)block; /* not touching */
-    (void)opval;
-
-    /* parse into the expression */
-    if (!parser_next(parser)) {
-        parseerror(parser, "expected switch operand");
-        return false;
-    }
-    /* parse the operand */
-    operand = parse_expression_leave(parser, false, false, false);
-    if (!operand)
-        return false;
-
-    switchnode = ast_switch_new(ctx, operand);
-
-    /* closing paren */
-    if (parser->tok != ')') {
-        ast_delete(switchnode);
-        parseerror(parser, "expected closing paren after 'switch' operand");
-        return false;
-    }
-
-    /* parse over the opening paren */
-    if (!parser_next(parser) || parser->tok != '{') {
-        ast_delete(switchnode);
-        parseerror(parser, "expected list of cases");
-        return false;
-    }
-
-    if (!parser_next(parser)) {
-        ast_delete(switchnode);
-        parseerror(parser, "expected 'case' or 'default'");
-        return false;
-    }
-
-    /* new block; allow some variables to be declared here */
-    parser_enterblock(parser);
-    while (true) {
-        typevar = NULL;
-        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, true, CV_NONE, typevar, false, false, 0, NULL)) {
-                ast_delete(switchnode);
-                return false;
-            }
-            continue;
-        }
-        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, true, cvq, NULL, noref, is_static, qflags, NULL)) {
-                ast_delete(switchnode);
-                return false;
-            }
-            continue;
-        }
-        break;
-    }
-
-    /* case list! */
-    while (parser->tok != '}') {
-        ast_block *caseblock;
-
-        if (!strcmp(parser_tokval(parser), "case")) {
-            if (!parser_next(parser)) {
-                ast_delete(switchnode);
-                parseerror(parser, "expected expression for case");
-                return false;
-            }
-            swcase.value = parse_expression_leave(parser, false, false, false);
-            if (!swcase.value) {
-                ast_delete(switchnode);
-                parseerror(parser, "expected expression for case");
-                return false;
-            }
-            if (!OPTS_FLAG(RELAXED_SWITCH)) {
-                if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */
-                    parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch");
-                    ast_unref(operand);
-                    return false;
-                }
-            }
-        }
-        else if (!strcmp(parser_tokval(parser), "default")) {
-            swcase.value = NULL;
-            if (!parser_next(parser)) {
-                ast_delete(switchnode);
-                parseerror(parser, "expected colon");
-                return false;
-            }
-        }
-        else {
-            ast_delete(switchnode);
-            parseerror(parser, "expected 'case' or 'default'");
-            return false;
-        }
-
-        /* Now the colon and body */
-        if (parser->tok != ':') {
-            if (swcase.value) ast_unref(swcase.value);
-            ast_delete(switchnode);
-            parseerror(parser, "expected colon");
-            return false;
-        }
-
-        if (!parser_next(parser)) {
-            if (swcase.value) ast_unref(swcase.value);
-            ast_delete(switchnode);
-            parseerror(parser, "expected statements or case");
-            return false;
-        }
-        caseblock = ast_block_new(parser_ctx(parser));
-        if (!caseblock) {
-            if (swcase.value) ast_unref(swcase.value);
-            ast_delete(switchnode);
-            return false;
-        }
-        swcase.code = (ast_expression*)caseblock;
-        vec_push(switchnode->cases, swcase);
-        while (true) {
-            ast_expression *expr;
-            if (parser->tok == '}')
-                break;
-            if (parser->tok == TOKEN_KEYWORD) {
-                if (!strcmp(parser_tokval(parser), "case") ||
-                    !strcmp(parser_tokval(parser), "default"))
-                {
-                    break;
-                }
-            }
-            if (!parse_statement(parser, caseblock, &expr, true)) {
-                ast_delete(switchnode);
-                return false;
-            }
-            if (!expr)
-                continue;
-            if (!ast_block_add_expr(caseblock, expr)) {
-                ast_delete(switchnode);
-                return false;
-            }
-        }
-    }
-
-    parser_leaveblock(parser);
-
-    /* closing paren */
-    if (parser->tok != '}') {
-        ast_delete(switchnode);
-        parseerror(parser, "expected closing paren of case list");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        ast_delete(switchnode);
-        parseerror(parser, "parse error after switch");
-        return false;
-    }
-    *out = (ast_expression*)switchnode;
-    return true;
-}
-
-/* parse computed goto sides */
-static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) {
-    ast_expression *on_true;
-    ast_expression *on_false;
-    ast_expression *cond;
-
-    if (!*side)
-        return NULL;
-
-    if (ast_istype(*side, ast_ternary)) {
-        ast_ternary *tern = (ast_ternary*)*side;
-        on_true  = parse_goto_computed(parser, &tern->on_true);
-        on_false = parse_goto_computed(parser, &tern->on_false);
-
-        if (!on_true || !on_false) {
-            parseerror(parser, "expected label or expression in ternary");
-            if (on_true) ast_unref(on_true);
-            if (on_false) ast_unref(on_false);
-            return NULL;
-        }
-
-        cond = tern->cond;
-        tern->cond = NULL;
-        ast_delete(tern);
-        *side = NULL;
-        return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false);
-    } else if (ast_istype(*side, ast_label)) {
-        ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name);
-        ast_goto_set_label(gt, ((ast_label*)*side));
-        *side = NULL;
-        return (ast_expression*)gt;
-    }
-    return NULL;
-}
-
-static bool parse_goto(parser_t *parser, ast_expression **out)
-{
-    ast_goto       *gt = NULL;
-    ast_expression *lbl;
-
-    if (!parser_next(parser))
-        return false;
-
-    if (parser->tok != TOKEN_IDENT) {
-        ast_expression *expression;
-
-        /* could be an expression i.e computed goto :-) */
-        if (parser->tok != '(') {
-            parseerror(parser, "expected label name after `goto`");
-            return false;
-        }
-
-        /* failed to parse expression for goto */
-        if (!(expression = parse_expression(parser, false, true)) ||
-            !(*out = parse_goto_computed(parser, &expression))) {
-            parseerror(parser, "invalid goto expression");
-            if(expression)
-                ast_unref(expression);
-            return false;
-        }
-
-        return true;
-    }
-
-    /* not computed goto */
-    gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser));
-    lbl = parser_find_label(parser, gt->name);
-    if (lbl) {
-        if (!ast_istype(lbl, ast_label)) {
-            parseerror(parser, "internal error: label is not an ast_label");
-            ast_delete(gt);
-            return false;
-        }
-        ast_goto_set_label(gt, (ast_label*)lbl);
-    }
-    else
-        vec_push(parser->gotos, gt);
-
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "semicolon expected after goto label");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after goto");
-        return false;
-    }
-
-    *out = (ast_expression*)gt;
-    return true;
-}
-
-static bool parse_skipwhite(parser_t *parser)
-{
-    do {
-        if (!parser_next(parser))
-            return false;
-    } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR);
-    return parser->tok < TOKEN_ERROR;
-}
-
-static bool parse_eol(parser_t *parser)
-{
-    if (!parse_skipwhite(parser))
-        return false;
-    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;
-    }
-    if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) {
-        parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser));
-        return false;
-    }
-
-    if (!strcmp(parser_tokval(parser), "noref")) {
-        if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) {
-            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
-    {
-        (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser));
-
-        /* skip to eol */
-        while (!parse_eol(parser)) {
-            parser_next(parser);
-        }
-
-        return true;
-    }
-
-    return true;
-}
-
-static bool parse_pragma(parser_t *parser)
-{
-    bool rv;
-    parser->lex->flags.preprocessing = true;
-    parser->lex->flags.mergelines = true;
-    rv = parse_pragma_do(parser);
-    if (parser->tok != TOKEN_EOL) {
-        parseerror(parser, "junk after pragma");
-        rv = false;
-    }
-    parser->lex->flags.preprocessing = false;
-    parser->lex->flags.mergelines = false;
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after pragma");
-        rv = false;
-    }
-    return rv;
-}
-
-static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
-{
-    bool       noref, is_static;
-    int        cvq     = CV_NONE;
-    uint32_t   qflags  = 0;
-    ast_value *typevar = NULL;
-    char      *vstring = NULL;
-
-    *out = NULL;
-
-    if (parser->tok == TOKEN_IDENT)
-        typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
-
-    if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS)
-    {
-        /* local variable */
-        if (!block) {
-            parseerror(parser, "cannot declare a variable from here");
-            return false;
-        }
-        if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-            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, 0, NULL))
-            return false;
-        return true;
-    }
-    else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring))
-    {
-        if (cvq == CV_WRONG)
-            return false;
-        return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring);
-    }
-    else if (parser->tok == TOKEN_KEYWORD)
-    {
-        if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype"))
-        {
-            char ty[1024];
-            ast_value *tdef;
-
-            if (!parser_next(parser)) {
-                parseerror(parser, "parse error after __builtin_debug_printtype");
-                return false;
-            }
-
-            if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0)))
-            {
-                ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty));
-                con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty);
-                if (!parser_next(parser)) {
-                    parseerror(parser, "parse error after __builtin_debug_printtype typename argument");
-                    return false;
-                }
-            }
-            else
-            {
-                if (!parse_statement(parser, block, out, allow_cases))
-                    return false;
-                if (!*out)
-                    con_out("__builtin_debug_printtype: got no output node\n");
-                else
-                {
-                    ast_type_to_string(*out, ty, sizeof(ty));
-                    con_out("__builtin_debug_printtype: `%s`\n", ty);
-                }
-            }
-            return true;
-        }
-        else if (!strcmp(parser_tokval(parser), "return"))
-        {
-            return parse_return(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "if"))
-        {
-            return parse_if(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "while"))
-        {
-            return parse_while(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "do"))
-        {
-            return parse_dowhile(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "for"))
-        {
-            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;
-            }
-            return parse_for(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "break"))
-        {
-            return parse_break_continue(parser, block, out, false);
-        }
-        else if (!strcmp(parser_tokval(parser), "continue"))
-        {
-            return parse_break_continue(parser, block, out, true);
-        }
-        else if (!strcmp(parser_tokval(parser), "switch"))
-        {
-            return parse_switch(parser, block, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "case") ||
-                 !strcmp(parser_tokval(parser), "default"))
-        {
-            if (!allow_cases) {
-                parseerror(parser, "unexpected 'case' label");
-                return false;
-            }
-            return true;
-        }
-        else if (!strcmp(parser_tokval(parser), "goto"))
-        {
-            return parse_goto(parser, out);
-        }
-        else if (!strcmp(parser_tokval(parser), "typedef"))
-        {
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected type definition after 'typedef'");
-                return false;
-            }
-            return parse_typedef(parser);
-        }
-        parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser));
-        return false;
-    }
-    else if (parser->tok == '{')
-    {
-        ast_block *inner;
-        inner = parse_block(parser);
-        if (!inner)
-            return false;
-        *out = (ast_expression*)inner;
-        return true;
-    }
-    else if (parser->tok == ':')
-    {
-        size_t i;
-        ast_label *label;
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected label name");
-            return false;
-        }
-        if (parser->tok != TOKEN_IDENT) {
-            parseerror(parser, "label must be an identifier");
-            return false;
-        }
-        label = (ast_label*)parser_find_label(parser, parser_tokval(parser));
-        if (label) {
-            if (!label->undefined) {
-                parseerror(parser, "label `%s` already defined", label->name);
-                return false;
-            }
-            label->undefined = false;
-        }
-        else {
-            label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false);
-            vec_push(parser->labels, label);
-        }
-        *out = (ast_expression*)label;
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error after label");
-            return false;
-        }
-        for (i = 0; i < vec_size(parser->gotos); ++i) {
-            if (!strcmp(parser->gotos[i]->name, label->name)) {
-                ast_goto_set_label(parser->gotos[i], label);
-                vec_remove(parser->gotos, i, 1);
-                --i;
-            }
-        }
-        return true;
-    }
-    else if (parser->tok == ';')
-    {
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error after empty statement");
-            return false;
-        }
-        return true;
-    }
-    else
-    {
-        lex_ctx_t ctx = parser_ctx(parser);
-        ast_expression *exp = parse_expression(parser, false, false);
-        if (!exp)
-            return false;
-        *out = exp;
-        if (!ast_side_effects(exp)) {
-            if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
-                return false;
-        }
-        return true;
-    }
-}
-
-static bool parse_enum(parser_t *parser)
-{
-    bool        flag = false;
-    bool        reverse = false;
-    qcfloat_t     num = 0;
-    ast_value **values = NULL;
-    ast_value  *var = NULL;
-    ast_value  *asvalue;
-
-    ast_expression *old;
-
-    if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) {
-        parseerror(parser, "expected `{` or `:` after `enum` keyword");
-        return false;
-    }
-
-    /* enumeration attributes (can add more later) */
-    if (parser->tok == ':') {
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT){
-            parseerror(parser, "expected `flag` or `reverse` for enumeration attribute");
-            return false;
-        }
-
-        /* attributes? */
-        if (!strcmp(parser_tokval(parser), "flag")) {
-            num  = 1;
-            flag = true;
-        }
-        else if (!strcmp(parser_tokval(parser), "reverse")) {
-            reverse = true;
-        }
-        else {
-            parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser));
-            return false;
-        }
-
-        if (!parser_next(parser) || parser->tok != '{') {
-            parseerror(parser, "expected `{` after enum attribute ");
-            return false;
-        }
-    }
-
-    while (true) {
-        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
-            if (parser->tok == '}') {
-                /* allow an empty enum */
-                break;
-            }
-            parseerror(parser, "expected identifier or `}`");
-            goto onerror;
-        }
-
-        old = parser_find_field(parser, parser_tokval(parser));
-        if (!old)
-            old = parser_find_global(parser, parser_tokval(parser));
-        if (old) {
-            parseerror(parser, "value `%s` has already been declared here: %s:%i",
-                       parser_tokval(parser), ast_ctx(old).file, ast_ctx(old).line);
-            goto onerror;
-        }
-
-        var = ast_value_new(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT);
-        vec_push(values, var);
-        var->cvq             = CV_CONST;
-        var->hasvalue        = true;
-
-        /* for flagged enumerations increment in POTs of TWO */
-        var->constval.vfloat = (flag) ? (num *= 2) : (num ++);
-        parser_addglobal(parser, var->name, (ast_expression*)var);
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected `=`, `}` or comma after identifier");
-            goto onerror;
-        }
-
-        if (parser->tok == ',')
-            continue;
-        if (parser->tok == '}')
-            break;
-        if (parser->tok != '=') {
-            parseerror(parser, "expected `=`, `}` or comma after identifier");
-            goto onerror;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected expression after `=`");
-            goto onerror;
-        }
-
-        /* We got a value! */
-        old = parse_expression_leave(parser, true, false, false);
-        asvalue = (ast_value*)old;
-        if (!ast_istype(old, ast_value) || asvalue->cvq != CV_CONST || !asvalue->hasvalue) {
-            compile_error(ast_ctx(var), "constant value or expression expected");
-            goto onerror;
-        }
-        num = (var->constval.vfloat = asvalue->constval.vfloat) + 1;
-
-        if (parser->tok == '}')
-            break;
-        if (parser->tok != ',') {
-            parseerror(parser, "expected `}` or comma after expression");
-            goto onerror;
-        }
-    }
-
-    /* patch them all (for reversed attribute) */
-    if (reverse) {
-        size_t i;
-        for (i = 0; i < vec_size(values); i++)
-            values[i]->constval.vfloat = vec_size(values) - i - 1;
-    }
-
-    if (parser->tok != '}') {
-        parseerror(parser, "internal error: breaking without `}`");
-        goto onerror;
-    }
-
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "expected semicolon after enumeration");
-        goto onerror;
-    }
-
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after enumeration");
-        goto onerror;
-    }
-
-    vec_free(values);
-    return true;
-
-onerror:
-    vec_free(values);
-    return false;
-}
-
-static bool parse_block_into(parser_t *parser, ast_block *block)
-{
-    bool   retval = true;
-
-    parser_enterblock(parser);
-
-    if (!parser_next(parser)) { /* skip the '{' */
-        parseerror(parser, "expected function body");
-        goto cleanup;
-    }
-
-    while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
-    {
-        ast_expression *expr = NULL;
-        if (parser->tok == '}')
-            break;
-
-        if (!parse_statement(parser, block, &expr, false)) {
-            /* parseerror(parser, "parse error"); */
-            block = NULL;
-            goto cleanup;
-        }
-        if (!expr)
-            continue;
-        if (!ast_block_add_expr(block, expr)) {
-            ast_delete(block);
-            block = NULL;
-            goto cleanup;
-        }
-    }
-
-    if (parser->tok != '}') {
-        block = NULL;
-    } else {
-        (void)parser_next(parser);
-    }
-
-cleanup:
-    if (!parser_leaveblock(parser))
-        retval = false;
-    return retval && !!block;
-}
-
-static ast_block* parse_block(parser_t *parser)
-{
-    ast_block *block;
-    block = ast_block_new(parser_ctx(parser));
-    if (!block)
-        return NULL;
-    if (!parse_block_into(parser, block)) {
-        ast_block_delete(block);
-        return NULL;
-    }
-    return block;
-}
-
-static bool parse_statement_or_block(parser_t *parser, ast_expression **out)
-{
-    if (parser->tok == '{') {
-        *out = (ast_expression*)parse_block(parser);
-        return !!*out;
-    }
-    return parse_statement(parser, NULL, out, false);
-}
-
-static bool create_vector_members(ast_value *var, ast_member **me)
-{
-    size_t i;
-    size_t len = strlen(var->name);
-
-    for (i = 0; i < 3; ++i) {
-        char *name = (char*)mem_a(len+3);
-        memcpy(name, var->name, len);
-        name[len+0] = '_';
-        name[len+1] = 'x'+i;
-        name[len+2] = 0;
-        me[i] = ast_member_new(ast_ctx(var), (ast_expression*)var, i, name);
-        mem_d(name);
-        if (!me[i])
-            break;
-    }
-    if (i == 3)
-        return true;
-
-    /* unroll */
-    do { ast_member_delete(me[--i]); } while(i);
-    return false;
-}
-
-static bool parse_function_body(parser_t *parser, ast_value *var)
-{
-    ast_block      *block = NULL;
-    ast_function   *func;
-    ast_function   *old;
-    size_t          parami;
-
-    ast_expression *framenum  = NULL;
-    ast_expression *nextthink = NULL;
-    /* None of the following have to be deleted */
-    ast_expression *fld_think = NULL, *fld_nextthink = NULL, *fld_frame = NULL;
-    ast_expression *gbl_time = NULL, *gbl_self = NULL;
-    bool            has_frame_think;
-
-    bool retval = true;
-
-    has_frame_think = false;
-    old = parser->function;
-
-    if (var->expression.flags & AST_FLAG_ALIAS) {
-        parseerror(parser, "function aliases cannot have bodies");
-        return false;
-    }
-
-    if (vec_size(parser->gotos) || vec_size(parser->labels)) {
-        parseerror(parser, "gotos/labels leaking");
-        return false;
-    }
-
-    if (!OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC) {
-        if (parsewarning(parser, WARN_VARIADIC_FUNCTION,
-                         "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)"))
-        {
-            return false;
-        }
-    }
-
-    if (parser->tok == '[') {
-        /* got a frame definition: [ framenum, nextthink ]
-         * this translates to:
-         * self.frame = framenum;
-         * self.nextthink = time + 0.1;
-         * self.think = nextthink;
-         */
-        nextthink = NULL;
-
-        fld_think     = parser_find_field(parser, "think");
-        fld_nextthink = parser_find_field(parser, "nextthink");
-        fld_frame     = parser_find_field(parser, "frame");
-        if (!fld_think || !fld_nextthink || !fld_frame) {
-            parseerror(parser, "cannot use [frame,think] notation without the required fields");
-            parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`");
-            return false;
-        }
-        gbl_time      = parser_find_global(parser, "time");
-        gbl_self      = parser_find_global(parser, "self");
-        if (!gbl_time || !gbl_self) {
-            parseerror(parser, "cannot use [frame,think] notation without the required globals");
-            parseerror(parser, "please declare the following globals: `time`, `self`");
-            return false;
-        }
-
-        if (!parser_next(parser))
-            return false;
-
-        framenum = parse_expression_leave(parser, true, false, false);
-        if (!framenum) {
-            parseerror(parser, "expected a framenumber constant in[frame,think] notation");
-            return false;
-        }
-        if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->hasvalue) {
-            ast_unref(framenum);
-            parseerror(parser, "framenumber in [frame,think] notation must be a constant");
-            return false;
-        }
-
-        if (parser->tok != ',') {
-            ast_unref(framenum);
-            parseerror(parser, "expected comma after frame number in [frame,think] notation");
-            parseerror(parser, "Got a %i\n", parser->tok);
-            return false;
-        }
-
-        if (!parser_next(parser)) {
-            ast_unref(framenum);
-            return false;
-        }
-
-        if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser)))
-        {
-            /* qc allows the use of not-yet-declared functions here
-             * - this automatically creates a prototype */
-            ast_value      *thinkfunc;
-            ast_expression *functype = fld_think->next;
-
-            thinkfunc = ast_value_new(parser_ctx(parser), parser_tokval(parser), functype->vtype);
-            if (!thinkfunc) { /* || !ast_type_adopt(thinkfunc, functype)*/
-                ast_unref(framenum);
-                parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser));
-                return false;
-            }
-            ast_type_adopt(thinkfunc, functype);
-
-            if (!parser_next(parser)) {
-                ast_unref(framenum);
-                ast_delete(thinkfunc);
-                return false;
-            }
-
-            parser_addglobal(parser, thinkfunc->name, (ast_expression*)thinkfunc);
-
-            nextthink = (ast_expression*)thinkfunc;
-
-        } else {
-            nextthink = parse_expression_leave(parser, true, false, false);
-            if (!nextthink) {
-                ast_unref(framenum);
-                parseerror(parser, "expected a think-function in [frame,think] notation");
-                return false;
-            }
-        }
-
-        if (!ast_istype(nextthink, ast_value)) {
-            parseerror(parser, "think-function in [frame,think] notation must be a constant");
-            retval = false;
-        }
-
-        if (retval && parser->tok != ']') {
-            parseerror(parser, "expected closing `]` for [frame,think] notation");
-            retval = false;
-        }
-
-        if (retval && !parser_next(parser)) {
-            retval = false;
-        }
-
-        if (retval && parser->tok != '{') {
-            parseerror(parser, "a function body has to be declared after a [frame,think] declaration");
-            retval = false;
-        }
-
-        if (!retval) {
-            ast_unref(nextthink);
-            ast_unref(framenum);
-            return false;
-        }
-
-        has_frame_think = true;
-    }
-
-    block = ast_block_new(parser_ctx(parser));
-    if (!block) {
-        parseerror(parser, "failed to allocate block");
-        if (has_frame_think) {
-            ast_unref(nextthink);
-            ast_unref(framenum);
-        }
-        return false;
-    }
-
-    if (has_frame_think) {
-        if (!OPTS_FLAG(EMULATE_STATE)) {
-            ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink);
-            if (!ast_block_add_expr(block, (ast_expression*)state_op)) {
-                parseerror(parser, "failed to generate state op for [frame,think]");
-                ast_unref(nextthink);
-                ast_unref(framenum);
-                ast_delete(block);
-                return false;
-            }
-        } else {
-            /* emulate OP_STATE in code: */
-            lex_ctx_t ctx;
-            ast_expression *self_frame;
-            ast_expression *self_nextthink;
-            ast_expression *self_think;
-            ast_expression *time_plus_1;
-            ast_store *store_frame;
-            ast_store *store_nextthink;
-            ast_store *store_think;
-
-            float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
-
-            ctx = parser_ctx(parser);
-            self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
-            self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
-            self_think     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think);
-
-            time_plus_1    = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F,
-                             gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false));
-
-            if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
-                if (self_frame)     ast_delete(self_frame);
-                if (self_nextthink) ast_delete(self_nextthink);
-                if (self_think)     ast_delete(self_think);
-                if (time_plus_1)    ast_delete(time_plus_1);
-                retval = false;
-            }
-
-            if (retval)
-            {
-                store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
-                store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
-                store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
-
-                if (!store_frame) {
-                    ast_delete(self_frame);
-                    retval = false;
-                }
-                if (!store_nextthink) {
-                    ast_delete(self_nextthink);
-                    retval = false;
-                }
-                if (!store_think) {
-                    ast_delete(self_think);
-                    retval = false;
-                }
-                if (!retval) {
-                    if (store_frame)     ast_delete(store_frame);
-                    if (store_nextthink) ast_delete(store_nextthink);
-                    if (store_think)     ast_delete(store_think);
-                    retval = false;
-                }
-                if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
-                    !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
-                    !ast_block_add_expr(block, (ast_expression*)store_think))
-                {
-                    retval = false;
-                }
-            }
-
-            if (!retval) {
-                parseerror(parser, "failed to generate code for [frame,think]");
-                ast_unref(nextthink);
-                ast_unref(framenum);
-                ast_delete(block);
-                return false;
-            }
-        }
-    }
-
-    if (var->hasvalue) {
-        if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) {
-            parseerror(parser, "function `%s` declared with multiple bodies", var->name);
-            ast_block_delete(block);
-            goto enderr;
-        }
-        func = var->constval.vfunc;
-
-        if (!func) {
-            parseerror(parser, "internal error: NULL function: `%s`", var->name);
-            ast_block_delete(block);
-            goto enderr;
-        }
-    } else {
-        func = ast_function_new(ast_ctx(var), var->name, var);
-
-        if (!func) {
-            parseerror(parser, "failed to allocate function for `%s`", var->name);
-            ast_block_delete(block);
-            goto enderr;
-        }
-        vec_push(parser->functions, func);
-    }
-
-    parser_enterblock(parser);
-
-    for (parami = 0; parami < vec_size(var->expression.params); ++parami) {
-        size_t     e;
-        ast_value *param = var->expression.params[parami];
-        ast_member *me[3];
-
-        if (param->expression.vtype != TYPE_VECTOR &&
-            (param->expression.vtype != TYPE_FIELD ||
-             param->expression.next->vtype != TYPE_VECTOR))
-        {
-            continue;
-        }
-
-        if (!create_vector_members(param, me)) {
-            ast_block_delete(block);
-            goto enderrfn;
-        }
-
-        for (e = 0; e < 3; ++e) {
-            parser_addlocal(parser, me[e]->name, (ast_expression*)me[e]);
-            ast_block_collect(block, (ast_expression*)me[e]);
-        }
-    }
-
-    if (var->argcounter && !func->argc) {
-        ast_value *argc = ast_value_new(ast_ctx(var), var->argcounter, TYPE_FLOAT);
-        parser_addlocal(parser, argc->name, (ast_expression*)argc);
-        func->argc = argc;
-    }
-
-    if (OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC && !func->varargs) {
-        char name[1024];
-        ast_value *varargs = ast_value_new(ast_ctx(var), "reserved:va_args", TYPE_ARRAY);
-        varargs->expression.flags |= AST_FLAG_IS_VARARG;
-        varargs->expression.next = (ast_expression*)ast_value_new(ast_ctx(var), NULL, TYPE_VECTOR);
-        varargs->expression.count = 0;
-        util_snprintf(name, sizeof(name), "%s##va##SET", var->name);
-        if (!parser_create_array_setter_proto(parser, varargs, name)) {
-            ast_delete(varargs);
-            ast_block_delete(block);
-            goto enderrfn;
-        }
-        util_snprintf(name, sizeof(name), "%s##va##GET", var->name);
-        if (!parser_create_array_getter_proto(parser, varargs, varargs->expression.next, name)) {
-            ast_delete(varargs);
-            ast_block_delete(block);
-            goto enderrfn;
-        }
-        func->varargs     = varargs;
-        func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false);
-    }
-
-    parser->function = func;
-    if (!parse_block_into(parser, block)) {
-        ast_block_delete(block);
-        goto enderrfn;
-    }
-
-    vec_push(func->blocks, block);
-
-    parser->function = old;
-    if (!parser_leaveblock(parser))
-        retval = false;
-    if (vec_size(parser->variables) != PARSER_HT_LOCALS) {
-        parseerror(parser, "internal error: local scopes left");
-        retval = false;
-    }
-
-    if (parser->tok == ';')
-        return parser_next(parser);
-    else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
-        parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)");
-    return retval;
-
-enderrfn:
-    (void)!parser_leaveblock(parser);
-    vec_pop(parser->functions);
-    ast_function_delete(func);
-    var->constval.vfunc = NULL;
-
-enderr:
-    parser->function = old;
-    return false;
-}
-
-static ast_expression *array_accessor_split(
-    parser_t  *parser,
-    ast_value *array,
-    ast_value *index,
-    size_t     middle,
-    ast_expression *left,
-    ast_expression *right
-    )
-{
-    ast_ifthen *ifthen;
-    ast_binary *cmp;
-
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (!left || !right) {
-        if (left)  ast_delete(left);
-        if (right) ast_delete(right);
-        return NULL;
-    }
-
-    cmp = ast_binary_new(ctx, INSTR_LT,
-                         (ast_expression*)index,
-                         (ast_expression*)fold_constgen_float(parser->fold, middle, false));
-    if (!cmp) {
-        ast_delete(left);
-        ast_delete(right);
-        parseerror(parser, "internal error: failed to create comparison for array setter");
-        return NULL;
-    }
-
-    ifthen = ast_ifthen_new(ctx, (ast_expression*)cmp, left, right);
-    if (!ifthen) {
-        ast_delete(cmp); /* will delete left and right */
-        parseerror(parser, "internal error: failed to create conditional jump for array setter");
-        return NULL;
-    }
-
-    return (ast_expression*)ifthen;
-}
-
-static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend)
-{
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (from+1 == afterend) {
-        /* set this value */
-        ast_block       *block;
-        ast_return      *ret;
-        ast_array_index *subscript;
-        ast_store       *st;
-        int assignop = type_store_instr[value->expression.vtype];
-
-        if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
-            assignop = INSTR_STORE_V;
-
-        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
-        if (!subscript)
-            return NULL;
-
-        st = ast_store_new(ctx, assignop, (ast_expression*)subscript, (ast_expression*)value);
-        if (!st) {
-            ast_delete(subscript);
-            return NULL;
-        }
-
-        block = ast_block_new(ctx);
-        if (!block) {
-            ast_delete(st);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)st)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        ret = ast_return_new(ctx, NULL);
-        if (!ret) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)ret)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        return (ast_expression*)block;
-    } else {
-        ast_expression *left, *right;
-        size_t diff = afterend - from;
-        size_t middle = from + diff/2;
-        left  = array_setter_node(parser, array, index, value, from, middle);
-        right = array_setter_node(parser, array, index, value, middle, afterend);
-        return array_accessor_split(parser, array, index, middle, left, right);
-    }
-}
-
-static ast_expression *array_field_setter_node(
-    parser_t  *parser,
-    ast_value *array,
-    ast_value *entity,
-    ast_value *index,
-    ast_value *value,
-    size_t     from,
-    size_t     afterend)
-{
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (from+1 == afterend) {
-        /* set this value */
-        ast_block       *block;
-        ast_return      *ret;
-        ast_entfield    *entfield;
-        ast_array_index *subscript;
-        ast_store       *st;
-        int assignop = type_storep_instr[value->expression.vtype];
-
-        if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
-            assignop = INSTR_STOREP_V;
-
-        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
-        if (!subscript)
-            return NULL;
-
-        subscript->expression.next = ast_type_copy(ast_ctx(subscript), (ast_expression*)subscript);
-        subscript->expression.vtype = TYPE_FIELD;
-
-        entfield = ast_entfield_new_force(ctx,
-                                          (ast_expression*)entity,
-                                          (ast_expression*)subscript,
-                                          (ast_expression*)subscript);
-        if (!entfield) {
-            ast_delete(subscript);
-            return NULL;
-        }
-
-        st = ast_store_new(ctx, assignop, (ast_expression*)entfield, (ast_expression*)value);
-        if (!st) {
-            ast_delete(entfield);
-            return NULL;
-        }
-
-        block = ast_block_new(ctx);
-        if (!block) {
-            ast_delete(st);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)st)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        ret = ast_return_new(ctx, NULL);
-        if (!ret) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        if (!ast_block_add_expr(block, (ast_expression*)ret)) {
-            ast_delete(block);
-            return NULL;
-        }
-
-        return (ast_expression*)block;
-    } else {
-        ast_expression *left, *right;
-        size_t diff = afterend - from;
-        size_t middle = from + diff/2;
-        left  = array_field_setter_node(parser, array, entity, index, value, from, middle);
-        right = array_field_setter_node(parser, array, entity, index, value, middle, afterend);
-        return array_accessor_split(parser, array, index, middle, left, right);
-    }
-}
-
-static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend)
-{
-    lex_ctx_t ctx = ast_ctx(array);
-
-    if (from+1 == afterend) {
-        ast_return      *ret;
-        ast_array_index *subscript;
-
-        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
-        if (!subscript)
-            return NULL;
-
-        ret = ast_return_new(ctx, (ast_expression*)subscript);
-        if (!ret) {
-            ast_delete(subscript);
-            return NULL;
-        }
-
-        return (ast_expression*)ret;
-    } else {
-        ast_expression *left, *right;
-        size_t diff = afterend - from;
-        size_t middle = from + diff/2;
-        left  = array_getter_node(parser, array, index, from, middle);
-        right = array_getter_node(parser, array, index, middle, afterend);
-        return array_accessor_split(parser, array, index, middle, left, right);
-    }
-}
-
-static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out)
-{
-    ast_function   *func = NULL;
-    ast_value      *fval = NULL;
-    ast_block      *body = NULL;
-
-    fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION);
-    if (!fval) {
-        parseerror(parser, "failed to create accessor function value");
-        return false;
-    }
-    fval->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
-
-    func = ast_function_new(ast_ctx(array), funcname, fval);
-    if (!func) {
-        ast_delete(fval);
-        parseerror(parser, "failed to create accessor function node");
-        return false;
-    }
-
-    body = ast_block_new(ast_ctx(array));
-    if (!body) {
-        parseerror(parser, "failed to create block for array accessor");
-        ast_delete(fval);
-        ast_delete(func);
-        return false;
-    }
-
-    vec_push(func->blocks, body);
-    *out = fval;
-
-    vec_push(parser->accessors, fval);
-
-    return true;
-}
-
-static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname)
-{
-    ast_value      *index = NULL;
-    ast_value      *value = NULL;
-    ast_function   *func;
-    ast_value      *fval;
-
-    if (!ast_istype(array->expression.next, ast_value)) {
-        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
-        return NULL;
-    }
-
-    if (!parser_create_array_accessor(parser, array, funcname, &fval))
-        return NULL;
-    func = fval->constval.vfunc;
-    fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "<void>", TYPE_VOID);
-
-    index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT);
-    value = ast_value_copy((ast_value*)array->expression.next);
-
-    if (!index || !value) {
-        parseerror(parser, "failed to create locals for array accessor");
-        goto cleanup;
-    }
-    (void)!ast_value_set_name(value, "value"); /* not important */
-    vec_push(fval->expression.params, index);
-    vec_push(fval->expression.params, value);
-
-    array->setter = fval;
-    return fval;
-cleanup:
-    if (index) ast_delete(index);
-    if (value) ast_delete(value);
-    ast_delete(func);
-    ast_delete(fval);
-    return NULL;
-}
-
-static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array)
-{
-    ast_expression *root = NULL;
-    root = array_setter_node(parser, array,
-                             array->setter->expression.params[0],
-                             array->setter->expression.params[1],
-                             0, array->expression.count);
-    if (!root) {
-        parseerror(parser, "failed to build accessor search tree");
-        return false;
-    }
-    if (!ast_block_add_expr(array->setter->constval.vfunc->blocks[0], root)) {
-        ast_delete(root);
-        return false;
-    }
-    return true;
-}
-
-static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname)
-{
-    if (!parser_create_array_setter_proto(parser, array, funcname))
-        return false;
-    return parser_create_array_setter_impl(parser, array);
-}
-
-static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname)
-{
-    ast_expression *root = NULL;
-    ast_value      *entity = NULL;
-    ast_value      *index = NULL;
-    ast_value      *value = NULL;
-    ast_function   *func;
-    ast_value      *fval;
-
-    if (!ast_istype(array->expression.next, ast_value)) {
-        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
-        return false;
-    }
-
-    if (!parser_create_array_accessor(parser, array, funcname, &fval))
-        return false;
-    func = fval->constval.vfunc;
-    fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "<void>", TYPE_VOID);
-
-    entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY);
-    index  = ast_value_new(ast_ctx(array), "index",  TYPE_FLOAT);
-    value  = ast_value_copy((ast_value*)array->expression.next);
-    if (!entity || !index || !value) {
-        parseerror(parser, "failed to create locals for array accessor");
-        goto cleanup;
-    }
-    (void)!ast_value_set_name(value, "value"); /* not important */
-    vec_push(fval->expression.params, entity);
-    vec_push(fval->expression.params, index);
-    vec_push(fval->expression.params, value);
-
-    root = array_field_setter_node(parser, array, entity, index, value, 0, array->expression.count);
-    if (!root) {
-        parseerror(parser, "failed to build accessor search tree");
-        goto cleanup;
-    }
-
-    array->setter = fval;
-    return ast_block_add_expr(func->blocks[0], root);
-cleanup:
-    if (entity) ast_delete(entity);
-    if (index)  ast_delete(index);
-    if (value)  ast_delete(value);
-    if (root)   ast_delete(root);
-    ast_delete(func);
-    ast_delete(fval);
-    return false;
-}
-
-static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
-{
-    ast_value      *index = NULL;
-    ast_value      *fval;
-    ast_function   *func;
-
-    /* NOTE: checking array->expression.next rather than elemtype since
-     * for fields elemtype is a temporary fieldtype.
-     */
-    if (!ast_istype(array->expression.next, ast_value)) {
-        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
-        return NULL;
-    }
-
-    if (!parser_create_array_accessor(parser, array, funcname, &fval))
-        return NULL;
-    func = fval->constval.vfunc;
-    fval->expression.next = ast_type_copy(ast_ctx(array), elemtype);
-
-    index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT);
-
-    if (!index) {
-        parseerror(parser, "failed to create locals for array accessor");
-        goto cleanup;
-    }
-    vec_push(fval->expression.params, index);
-
-    array->getter = fval;
-    return fval;
-cleanup:
-    if (index) ast_delete(index);
-    ast_delete(func);
-    ast_delete(fval);
-    return NULL;
-}
-
-static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array)
-{
-    ast_expression *root = NULL;
-
-    root = array_getter_node(parser, array, array->getter->expression.params[0], 0, array->expression.count);
-    if (!root) {
-        parseerror(parser, "failed to build accessor search tree");
-        return false;
-    }
-    if (!ast_block_add_expr(array->getter->constval.vfunc->blocks[0], root)) {
-        ast_delete(root);
-        return false;
-    }
-    return true;
-}
-
-static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
-{
-    if (!parser_create_array_getter_proto(parser, array, elemtype, funcname))
-        return false;
-    return parser_create_array_getter_impl(parser, array);
-}
-
-static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
-{
-    lex_ctx_t     ctx;
-    size_t      i;
-    ast_value **params;
-    ast_value  *param;
-    ast_value  *fval;
-    bool        first = true;
-    bool        variadic = false;
-    ast_value  *varparam = NULL;
-    char       *argcounter = NULL;
-
-    ctx = parser_ctx(parser);
-
-    /* for the sake of less code we parse-in in this function */
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "expected parameter list");
-        return NULL;
-    }
-
-    params = NULL;
-
-    /* parse variables until we hit a closing paren */
-    while (parser->tok != ')') {
-        bool is_varargs = false;
-
-        if (!first) {
-            /* there must be commas between them */
-            if (parser->tok != ',') {
-                parseerror(parser, "expected comma or end of parameter list");
-                goto on_error;
-            }
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected parameter");
-                goto on_error;
-            }
-        }
-        first = false;
-
-        param = parse_typename(parser, NULL, NULL, &is_varargs);
-        if (!param && !is_varargs)
-            goto on_error;
-        if (is_varargs) {
-            /* '...' indicates a varargs function */
-            variadic = true;
-            if (parser->tok != ')' && parser->tok != TOKEN_IDENT) {
-                parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                goto on_error;
-            }
-            if (parser->tok == TOKEN_IDENT) {
-                argcounter = util_strdup(parser_tokval(parser));
-                if (!parser_next(parser) || parser->tok != ')') {
-                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                    goto on_error;
-                }
-            }
-        } else {
-            vec_push(params, param);
-            if (param->expression.vtype >= TYPE_VARIANT) {
-                char tname[1024]; /* typename is reserved in C++ */
-                ast_type_to_string((ast_expression*)param, tname, sizeof(tname));
-                parseerror(parser, "type not supported as part of a parameter list: %s", tname);
-                goto on_error;
-            }
-            /* type-restricted varargs */
-            if (parser->tok == TOKEN_DOTS) {
-                variadic = true;
-                varparam = vec_last(params);
-                vec_pop(params);
-                if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) {
-                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                    goto on_error;
-                }
-                if (parser->tok == TOKEN_IDENT) {
-                    argcounter = util_strdup(parser_tokval(parser));
-                    ast_value_set_name(param, argcounter);
-                    if (!parser_next(parser) || parser->tok != ')') {
-                        parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
-                        goto on_error;
-                    }
-                }
-            }
-            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->name[0] == '<') {
-                parseerror(parser, "parameter name omitted");
-                goto on_error;
-            }
-        }
-    }
-
-    if (vec_size(params) == 1 && params[0]->expression.vtype == TYPE_VOID)
-        vec_free(params);
-
-    /* sanity check */
-    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 */
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after typename");
-        goto on_error;
-    }
-
-    /* now turn 'var' into a function type */
-    fval = ast_value_new(ctx, "<type()>", TYPE_FUNCTION);
-    fval->expression.next     = (ast_expression*)var;
-    if (variadic)
-        fval->expression.flags |= AST_FLAG_VARIADIC;
-    var = fval;
-
-    var->expression.params   = params;
-    var->expression.varparam = (ast_expression*)varparam;
-    var->argcounter          = argcounter;
-    params = NULL;
-
-    return var;
-
-on_error:
-    if (argcounter)
-        mem_d(argcounter);
-    if (varparam)
-        ast_delete(varparam);
-    ast_delete(var);
-    for (i = 0; i < vec_size(params); ++i)
-        ast_delete(params[i]);
-    vec_free(params);
-    return NULL;
-}
-
-static ast_value *parse_arraysize(parser_t *parser, ast_value *var)
-{
-    ast_expression *cexp;
-    ast_value      *cval, *tmp;
-    lex_ctx_t ctx;
-
-    ctx = parser_ctx(parser);
-
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "expected array-size");
-        return NULL;
-    }
-
-    if (parser->tok != ']') {
-        cexp = parse_expression_leave(parser, true, false, false);
-
-        if (!cexp || !ast_istype(cexp, ast_value)) {
-            if (cexp)
-                ast_unref(cexp);
-            ast_delete(var);
-            parseerror(parser, "expected array-size as constant positive integer");
-            return NULL;
-        }
-        cval = (ast_value*)cexp;
-    }
-    else {
-        cexp = NULL;
-        cval = NULL;
-    }
-
-    tmp = ast_value_new(ctx, "<type[]>", TYPE_ARRAY);
-    tmp->expression.next = (ast_expression*)var;
-    var = tmp;
-
-    if (cval) {
-        if (cval->expression.vtype == TYPE_INTEGER)
-            tmp->expression.count = cval->constval.vint;
-        else if (cval->expression.vtype == TYPE_FLOAT)
-            tmp->expression.count = cval->constval.vfloat;
-        else {
-            ast_unref(cexp);
-            ast_delete(var);
-            parseerror(parser, "array-size must be a positive integer constant");
-            return NULL;
-        }
-
-        ast_unref(cexp);
-    } else {
-        var->expression.count = -1;
-        var->expression.flags |= AST_FLAG_ARRAY_INIT;
-    }
-
-    if (parser->tok != ']') {
-        ast_delete(var);
-        parseerror(parser, "expected ']' after array-size");
-        return NULL;
-    }
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "error after parsing array size");
-        return NULL;
-    }
-    return var;
-}
-
-/* Parse a complete typename.
- * for single-variables (ie. function parameters or typedefs) storebase should be NULL
- * but when parsing variables separated by comma
- * 'storebase' should point to where the base-type should be kept.
- * The base type makes up every bit of type information which comes *before* the
- * variable name.
- *
- * NOTE: The value must either be named, have a NULL name, or a name starting
- *       with '<'. In the first case, this will be the actual variable or type
- *       name, in the other cases it is assumed that the name will appear
- *       later, and an error is generated otherwise.
- *
- * The following will be parsed in its entirety:
- *     void() foo()
- * The 'basetype' in this case is 'void()'
- * and if there's a comma after it, say:
- *     void() foo(), bar
- * then the type-information 'void()' can be stored in 'storebase'
- */
-static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg)
-{
-    ast_value *var, *tmp;
-    lex_ctx_t    ctx;
-
-    const char *name = NULL;
-    bool        isfield  = false;
-    bool        wasarray = false;
-    size_t      morefields = 0;
-
-    bool        vararg = (parser->tok == TOKEN_DOTS);
-
-    ctx = parser_ctx(parser);
-
-    /* types may start with a dot */
-    if (parser->tok == '.' || parser->tok == TOKEN_DOTS) {
-        isfield = true;
-        if (parser->tok == TOKEN_DOTS)
-            morefields += 2;
-        /* if we parsed a dot we need a typename now */
-        if (!parser_next(parser)) {
-            parseerror(parser, "expected typename for field definition");
-            return NULL;
-        }
-
-        /* Further dots are handled seperately because they won't be part of the
-         * basetype
-         */
-        while (true) {
-            if (parser->tok == '.')
-                ++morefields;
-            else if (parser->tok == TOKEN_DOTS)
-                morefields += 3;
-            else
-                break;
-            vararg = false;
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected typename for field definition");
-                return NULL;
-            }
-        }
-    }
-    if (parser->tok == TOKEN_IDENT)
-        cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0);
-    if (!cached_typedef && parser->tok != TOKEN_TYPENAME) {
-        if (vararg && is_vararg) {
-            *is_vararg = true;
-            return NULL;
-        }
-        parseerror(parser, "expected typename");
-        return NULL;
-    }
-
-    /* generate the basic type value */
-    if (cached_typedef) {
-        var = ast_value_copy(cached_typedef);
-        ast_value_set_name(var, "<type(from_def)>");
-    } else
-        var = ast_value_new(ctx, "<type>", parser_token(parser)->constval.t);
-
-    for (; morefields; --morefields) {
-        tmp = ast_value_new(ctx, "<.type>", TYPE_FIELD);
-        tmp->expression.next = (ast_expression*)var;
-        var = tmp;
-    }
-
-    /* do not yet turn into a field - remember:
-     * .void() foo; is a field too
-     * .void()() foo; is a function
-     */
-
-    /* parse on */
-    if (!parser_next(parser)) {
-        ast_delete(var);
-        parseerror(parser, "parse error after typename");
-        return NULL;
-    }
-
-    /* an opening paren now starts the parameter-list of a function
-     * this is where original-QC has parameter lists.
-     * We allow a single parameter list here.
-     * Much like fteqcc we don't allow `float()() x`
-     */
-    if (parser->tok == '(') {
-        var = parse_parameter_list(parser, var);
-        if (!var)
-            return NULL;
-    }
-
-    /* store the base if requested */
-    if (storebase) {
-        *storebase = ast_value_copy(var);
-        if (isfield) {
-            tmp = ast_value_new(ctx, "<type:f>", TYPE_FIELD);
-            tmp->expression.next = (ast_expression*)*storebase;
-            *storebase = tmp;
-        }
-    }
-
-    /* there may be a name now */
-    if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) {
-        if (!strcmp(parser_tokval(parser), "break"))
-            (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)");
-        else if (parser->tok == TOKEN_KEYWORD)
-            goto leave;
-
-        name = util_strdup(parser_tokval(parser));
-
-        /* parse on */
-        if (!parser_next(parser)) {
-            ast_delete(var);
-            mem_d(name);
-            parseerror(parser, "error after variable or field declaration");
-            return NULL;
-        }
-    }
-
-    leave:
-    /* now this may be an array */
-    if (parser->tok == '[') {
-        wasarray = true;
-        var = parse_arraysize(parser, var);
-        if (!var) {
-            if (name) mem_d(name);
-            return NULL;
-        }
-    }
-
-    /* This is the point where we can turn it into a field */
-    if (isfield) {
-        /* turn it into a field if desired */
-        tmp = ast_value_new(ctx, "<type:f>", TYPE_FIELD);
-        tmp->expression.next = (ast_expression*)var;
-        var = tmp;
-    }
-
-    /* now there may be function parens again */
-    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");
-    while (parser->tok == '(') {
-        var = parse_parameter_list(parser, var);
-        if (!var) {
-            if (name) mem_d(name);
-            return NULL;
-        }
-    }
-
-    /* finally name it */
-    if (name) {
-        if (!ast_value_set_name(var, name)) {
-            ast_delete(var);
-            mem_d(name);
-            parseerror(parser, "internal error: failed to set name");
-            return NULL;
-        }
-        /* free the name, ast_value_set_name duplicates */
-        mem_d(name);
-    }
-
-    return var;
-}
-
-static bool parse_typedef(parser_t *parser)
-{
-    ast_value      *typevar, *oldtype;
-    ast_expression *old;
-
-    typevar = parse_typename(parser, NULL, NULL, NULL);
-
-    if (!typevar)
-        return false;
-
-    /* while parsing types, the ast_value's get named '<something>' */
-    if (!typevar->name || typevar->name[0] == '<') {
-        parseerror(parser, "missing name in typedef");
-        ast_delete(typevar);
-        return false;
-    }
-
-    if ( (old = parser_find_var(parser, typevar->name)) ) {
-        parseerror(parser, "cannot define a type with the same name as a variable: %s\n"
-                   " -> `%s` has been declared here: %s:%i",
-                   typevar->name, ast_ctx(old).file, ast_ctx(old).line);
-        ast_delete(typevar);
-        return false;
-    }
-
-    if ( (oldtype = parser_find_typedef(parser, typevar->name, vec_last(parser->_blocktypedefs))) ) {
-        parseerror(parser, "type `%s` has already been declared here: %s:%i",
-                   typevar->name, ast_ctx(oldtype).file, ast_ctx(oldtype).line);
-        ast_delete(typevar);
-        return false;
-    }
-
-    vec_push(parser->_typedefs, typevar);
-    util_htset(vec_last(parser->typedefs), typevar->name, typevar);
-
-    if (parser->tok != ';') {
-        parseerror(parser, "expected semicolon after typedef");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after typedef");
-        return false;
-    }
-
-    return true;
-}
-
-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 create_array_accessors(parser_t *parser, ast_value *var)
-{
-    char name[1024];
-    util_snprintf(name, sizeof(name), "%s##SET", var->name);
-    if (!parser_create_array_setter(parser, var, name))
-        return false;
-    util_snprintf(name, sizeof(name), "%s##GET", var->name);
-    if (!parser_create_array_getter(parser, var, var->expression.next, name))
-        return false;
-    return true;
-}
-
-static bool parse_array(parser_t *parser, ast_value *array)
-{
-    size_t i;
-    if (array->initlist) {
-        parseerror(parser, "array already initialized elsewhere");
-        return false;
-    }
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error in array initializer");
-        return false;
-    }
-    i = 0;
-    while (parser->tok != '}') {
-        ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false);
-        if (!v)
-            return false;
-        if (!ast_istype(v, ast_value) || !v->hasvalue || v->cvq != CV_CONST) {
-            ast_unref(v);
-            parseerror(parser, "initializing element must be a compile time constant");
-            return false;
-        }
-        vec_push(array->initlist, v->constval);
-        if (v->expression.vtype == TYPE_STRING) {
-            array->initlist[i].vstring = util_strdupe(array->initlist[i].vstring);
-            ++i;
-        }
-        ast_unref(v);
-        if (parser->tok == '}')
-            break;
-        if (parser->tok != ',' || !parser_next(parser)) {
-            parseerror(parser, "expected comma or '}' in element list");
-            return false;
-        }
-    }
-    if (!parser_next(parser) || parser->tok != ';') {
-        parseerror(parser, "expected semicolon after initializer, got %s");
-        return false;
-    }
-    /*
-    if (!parser_next(parser)) {
-        parseerror(parser, "parse error after initializer");
-        return false;
-    }
-    */
-
-    if (array->expression.flags & AST_FLAG_ARRAY_INIT) {
-        if (array->expression.count != (size_t)-1) {
-            parseerror(parser, "array `%s' has already been initialized with %u elements",
-                       array->name, (unsigned)array->expression.count);
-        }
-        array->expression.count = vec_size(array->initlist);
-        if (!create_array_accessors(parser, array))
-            return false;
-    }
-    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;
-    ast_expression *old;
-    bool       was_end;
-    size_t     i;
-
-    ast_value *basetype = NULL;
-    bool      retval    = true;
-    bool      isparam   = false;
-    bool      isvector  = false;
-    bool      cleanvar  = true;
-    bool      wasarray  = false;
-
-    ast_member *me[3] = { NULL, NULL, NULL };
-    ast_member *last_me[3] = { NULL, NULL, NULL };
-
-    if (!localblock && is_static)
-        parseerror(parser, "`static` qualifier is not supported in global scope");
-
-    /* get the first complete variable */
-    var = parse_typename(parser, &basetype, cached_typedef, NULL);
-    if (!var) {
-        if (basetype)
-            ast_delete(basetype);
-        return false;
-    }
-
-    /* while parsing types, the ast_value's get named '<something>' */
-    if (!var->name || var->name[0] == '<') {
-        parseerror(parser, "declaration does not declare anything");
-        if (basetype)
-            ast_delete(basetype);
-        return false;
-    }
-
-    while (true) {
-        proto = NULL;
-        wasarray = false;
-
-        /* Part 0: finish the type */
-        if (parser->tok == '(') {
-            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) {
-                retval = false;
-                goto cleanup;
-            }
-        }
-        /* we only allow 1-dimensional arrays */
-        if (parser->tok == '[') {
-            wasarray = true;
-            var = parse_arraysize(parser, var);
-            if (!var) {
-                retval = false;
-                goto cleanup;
-            }
-        }
-        if (parser->tok == '(' && wasarray) {
-            parseerror(parser, "arrays as part of a return type is not supported");
-            /* we'll still parse the type completely for now */
-        }
-        /* for functions returning functions */
-        while (parser->tok == '(') {
-            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) {
-                retval = false;
-                goto cleanup;
-            }
-        }
-
-        var->cvq = qualifier;
-        if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */
-            var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
-        var->expression.flags |= qflags;
-
-        /*
-         * store the vstring back to var for alias and
-         * deprecation messages.
-         */
-        if (var->expression.flags & AST_FLAG_DEPRECATED ||
-            var->expression.flags & AST_FLAG_ALIAS)
-            var->desc = vstring;
-
-        if (parser_find_global(parser, var->name) && var->expression.flags & AST_FLAG_ALIAS) {
-            parseerror(parser, "function aliases cannot be forward declared");
-            retval = false;
-            goto cleanup;
-        }
-
-
-        /* 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;
-            if (!strcmp(var->name, "end_sys_globals")) {
-                var->uses++;
-                parser->crc_globals = vec_size(parser->globals);
-                was_end = true;
-            }
-            else if (!strcmp(var->name, "end_sys_fields")) {
-                var->uses++;
-                parser->crc_fields = vec_size(parser->fields);
-                was_end = true;
-            }
-            if (was_end && var->expression.vtype == TYPE_FIELD) {
-                if (parsewarning(parser, WARN_END_SYS_FIELDS,
-                                 "global '%s' hint should not be a field",
-                                 parser_tokval(parser)))
-                {
-                    retval = false;
-                    goto cleanup;
-                }
-            }
-
-            if (!nofields && var->expression.vtype == TYPE_FIELD)
-            {
-                /* deal with field declarations */
-                old = parser_find_field(parser, var->name);
-                if (old) {
-                    if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i",
-                                     var->name, ast_ctx(old).file, (int)ast_ctx(old).line))
-                    {
-                        retval = false;
-                        goto cleanup;
-                    }
-                    ast_delete(var);
-                    var = NULL;
-                    goto skipvar;
-                    /*
-                    parseerror(parser, "field `%s` already declared here: %s:%i",
-                               var->name, ast_ctx(old).file, ast_ctx(old).line);
-                    retval = false;
-                    goto cleanup;
-                    */
-                }
-                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");
-                    parseerror(parser, "field `%s` already declared here: %s:%i",
-                               var->name, ast_ctx(old).file, ast_ctx(old).line);
-                    retval = false;
-                    goto cleanup;
-                }
-            }
-            else
-            {
-                /* deal with other globals */
-                old = parser_find_global(parser, var->name);
-                if (old && var->expression.vtype == TYPE_FUNCTION && old->vtype == TYPE_FUNCTION)
-                {
-                    /* This is a function which had a prototype */
-                    if (!ast_istype(old, ast_value)) {
-                        parseerror(parser, "internal error: prototype is not an ast_value");
-                        retval = false;
-                        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,
-                                   ast_ctx(proto).file, ast_ctx(proto).line);
-                        retval = false;
-                        goto cleanup;
-                    }
-                    /* 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;
-                }
-                else
-                {
-                    /* other globals */
-                    if (old) {
-                        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;
-                        }
-                        if (old->flags & AST_FLAG_FINAL_DECL) {
-                            parseerror(parser, "cannot redeclare variable `%s`, declared final 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;
-                        }
-                        if (!parser_check_qualifiers(parser, var, proto)) {
-                            retval = false;
-                            proto = NULL;
-                            goto cleanup;
-                        }
-                        proto->expression.flags |= var->expression.flags;
-                        /* copy the context for finals,
-                         * so the error can show where it was actually made 'final'
-                         */
-                        if (proto->expression.flags & AST_FLAG_FINAL_DECL)
-                            ast_ctx(old) = ast_ctx(var);
-                        ast_delete(var);
-                        var = proto;
-                    }
-                    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");
-                        parseerror(parser, "global `%s` already declared here: %s:%i",
-                                   var->name, ast_ctx(old).file, ast_ctx(old).line);
-                        retval = false;
-                        goto cleanup;
-                    }
-                }
-            }
-        }
-        else /* it's not a global */
-        {
-            old = parser_find_local(parser, var->name, vec_size(parser->variables)-1, &isparam);
-            if (old && !isparam) {
-                parseerror(parser, "local `%s` already declared here: %s:%i",
-                           var->name, ast_ctx(old).file, (int)ast_ctx(old).line);
-                retval = false;
-                goto cleanup;
-            }
-            /* doing this here as the above is just for a single scope */
-            old = parser_find_local(parser, var->name, 0, &isparam);
-            if (old && isparam) {
-                if (parsewarning(parser, WARN_LOCAL_SHADOWS,
-                                 "local `%s` is shadowing a parameter", var->name))
-                {
-                    parseerror(parser, "local `%s` already declared here: %s:%i",
-                               var->name, ast_ctx(old).file, (int)ast_ctx(old).line);
-                    retval = false;
-                    goto cleanup;
-                }
-                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
-                    ast_delete(var);
-                    if (ast_istype(old, ast_value))
-                        var = proto = (ast_value*)old;
-                    else {
-                        var = NULL;
-                        goto skipvar;
-                    }
-                }
-            }
-        }
-
-        /* 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.
-         */
-        if (!proto) {
-            if (var->expression.vtype == TYPE_VECTOR)
-                isvector = true;
-            else if (var->expression.vtype == TYPE_FIELD &&
-                     var->expression.next->vtype == TYPE_VECTOR)
-                isvector = true;
-
-            if (isvector) {
-                if (!create_vector_members(var, me)) {
-                    retval = false;
-                    goto cleanup;
-                }
-            }
-
-            if (!localblock) {
-                /* deal with global variables, fields, functions */
-                if (!nofields && var->expression.vtype == TYPE_FIELD && parser->tok != '=') {
-                    var->isfield = true;
-                    vec_push(parser->fields, (ast_expression*)var);
-                    util_htset(parser->htfields, var->name, var);
-                    if (isvector) {
-                        for (i = 0; i < 3; ++i) {
-                            vec_push(parser->fields, (ast_expression*)me[i]);
-                            util_htset(parser->htfields, me[i]->name, me[i]);
-                        }
-                    }
-                }
-                else {
-                    if (!(var->expression.flags & AST_FLAG_ALIAS)) {
-                        parser_addglobal(parser, var->name, (ast_expression*)var);
-                        if (isvector) {
-                            for (i = 0; i < 3; ++i) {
-                                parser_addglobal(parser, me[i]->name, (ast_expression*)me[i]);
-                            }
-                        }
-                    } else {
-                        ast_expression *find  = parser_find_global(parser, var->desc);
-
-                        if (!find) {
-                            compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->desc, var->name);
-                            return false;
-                        }
-
-                        if (!ast_compare_type((ast_expression*)var, find)) {
-                            char ty1[1024];
-                            char ty2[1024];
-
-                            ast_type_to_string(find,                  ty1, sizeof(ty1));
-                            ast_type_to_string((ast_expression*)var,  ty2, sizeof(ty2));
-
-                            compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`",
-                                ty1, ty2, var->name
-                            );
-                            return false;
-                        }
-
-                        util_htset(parser->aliases, var->name, find);
-
-                        /* generate aliases for vector components */
-                        if (isvector) {
-                            char *buffer[3];
-
-                            util_asprintf(&buffer[0], "%s_x", var->desc);
-                            util_asprintf(&buffer[1], "%s_y", var->desc);
-                            util_asprintf(&buffer[2], "%s_z", var->desc);
-
-                            util_htset(parser->aliases, me[0]->name, parser_find_global(parser, buffer[0]));
-                            util_htset(parser->aliases, me[1]->name, parser_find_global(parser, buffer[1]));
-                            util_htset(parser->aliases, me[2]->name, parser_find_global(parser, buffer[2]));
-
-                            mem_d(buffer[0]);
-                            mem_d(buffer[1]);
-                            mem_d(buffer[2]);
-                        }
-                    }
-                }
-            } else {
-                if (is_static) {
-                    /* a static adds itself to be generated like any other global
-                     * but is added to the local namespace instead
-                     */
-                    char   *defname = NULL;
-                    size_t  prefix_len, ln;
-                    size_t  sn, sn_size;
-
-                    ln = strlen(parser->function->name);
-                    vec_append(defname, ln, parser->function->name);
-
-                    vec_append(defname, 2, "::");
-                    /* remember the length up to here */
-                    prefix_len = vec_size(defname);
-
-                    /* Add it to the local scope */
-                    util_htset(vec_last(parser->variables), var->name, (void*)var);
-
-                    /* now rename the global */
-                    ln = strlen(var->name);
-                    vec_append(defname, ln, var->name);
-                    /* if a variable of that name already existed, add the
-                     * counter value.
-                     * The counter is incremented either way.
-                     */
-                    sn_size = vec_size(parser->function->static_names);
-                    for (sn = 0; sn != sn_size; ++sn) {
-                        if (strcmp(parser->function->static_names[sn], var->name) == 0)
-                            break;
-                    }
-                    if (sn != sn_size) {
-                        char *num = NULL;
-                        int   len = util_asprintf(&num, "#%u", parser->function->static_count);
-                        vec_append(defname, len, num);
-                        mem_d(num);
-                    }
-                    else
-                        vec_push(parser->function->static_names, util_strdup(var->name));
-                    parser->function->static_count++;
-                    ast_value_set_name(var, defname);
-
-                    /* push it to the to-be-generated globals */
-                    vec_push(parser->globals, (ast_expression*)var);
-
-                    /* same game for the vector members */
-                    if (isvector) {
-                        for (i = 0; i < 3; ++i) {
-                            util_htset(vec_last(parser->variables), me[i]->name, (void*)(me[i]));
-
-                            vec_shrinkto(defname, prefix_len);
-                            ln = strlen(me[i]->name);
-                            vec_append(defname, ln, me[i]->name);
-                            ast_member_set_name(me[i], defname);
-
-                            vec_push(parser->globals, (ast_expression*)me[i]);
-                        }
-                    }
-                    vec_free(defname);
-                } else {
-                    vec_push(localblock->locals, var);
-                    parser_addlocal(parser, var->name, (ast_expression*)var);
-                    if (isvector) {
-                        for (i = 0; i < 3; ++i) {
-                            parser_addlocal(parser, me[i]->name, (ast_expression*)me[i]);
-                            ast_block_collect(localblock, (ast_expression*)me[i]);
-                        }
-                    }
-                }
-            }
-        }
-        memcpy(last_me, me, sizeof(me));
-        me[0] = me[1] = me[2] = NULL;
-        cleanvar = false;
-        /* Part 2.2
-         * deal with arrays
-         */
-        if (var->expression.vtype == TYPE_ARRAY) {
-            if (var->expression.count != (size_t)-1) {
-                if (!create_array_accessors(parser, var))
-                    goto cleanup;
-            }
-        }
-        else if (!localblock && !nofields &&
-                 var->expression.vtype == TYPE_FIELD &&
-                 var->expression.next->vtype == TYPE_ARRAY)
-        {
-            char name[1024];
-            ast_expression *telem;
-            ast_value      *tfield;
-            ast_value      *array = (ast_value*)var->expression.next;
-
-            if (!ast_istype(var->expression.next, ast_value)) {
-                parseerror(parser, "internal error: field element type must be an ast_value");
-                goto cleanup;
-            }
-
-            util_snprintf(name, sizeof(name), "%s##SETF", var->name);
-            if (!parser_create_array_field_setter(parser, array, name))
-                goto cleanup;
-
-            telem = ast_type_copy(ast_ctx(var), array->expression.next);
-            tfield = ast_value_new(ast_ctx(var), "<.type>", TYPE_FIELD);
-            tfield->expression.next = telem;
-            util_snprintf(name, sizeof(name), "%s##GETFP", var->name);
-            if (!parser_create_array_getter(parser, array, (ast_expression*)tfield, name)) {
-                ast_delete(tfield);
-                goto cleanup;
-            }
-            ast_delete(tfield);
-        }
-
-skipvar:
-        if (parser->tok == ';') {
-            ast_delete(basetype);
-            if (!parser_next(parser)) {
-                parseerror(parser, "error after variable declaration");
-                return false;
-            }
-            return true;
-        }
-
-        if (parser->tok == ',')
-            goto another;
-
-        /*
-        if (!var || (!localblock && !nofields && basetype->expression.vtype == TYPE_FIELD)) {
-        */
-        if (!var) {
-            parseerror(parser, "missing comma or semicolon while parsing variables");
-            break;
-        }
-
-        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) )
-            {
-                break;
-            }
-        }
-
-        if (parser->tok != '{' || var->expression.vtype != TYPE_FUNCTION) {
-            if (parser->tok != '=') {
-                parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser));
-                break;
-            }
-
-            if (!parser_next(parser)) {
-                parseerror(parser, "error parsing initializer");
-                break;
-            }
-        }
-        else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
-            parseerror(parser, "expected '=' before function body in this standard");
-        }
-
-        if (parser->tok == '#') {
-            ast_function *func   = NULL;
-            ast_value    *number = NULL;
-            float         fractional;
-            float         integral;
-            int           builtin_num;
-
-            if (localblock) {
-                parseerror(parser, "cannot declare builtins within functions");
-                break;
-            }
-            if (var->expression.vtype != TYPE_FUNCTION) {
-                parseerror(parser, "unexpected builtin number, '%s' is not a function", var->name);
-                break;
-            }
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected builtin number");
-                break;
-            }
-
-            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) {
-                number = (ast_value*)parse_expression_leave(parser, true, false, false);
-                if (!number) {
-                    parseerror(parser, "builtin number expected");
-                    break;
-                }
-                if (!ast_istype(number, ast_value) || !number->hasvalue || number->cvq != CV_CONST)
-                {
-                    ast_unref(number);
-                    parseerror(parser, "builtin number must be a compile time constant");
-                    break;
-                }
-                if (number->expression.vtype == TYPE_INTEGER)
-                    builtin_num = number->constval.vint;
-                else if (number->expression.vtype == TYPE_FLOAT)
-                    builtin_num = number->constval.vfloat;
-                else {
-                    ast_unref(number);
-                    parseerror(parser, "builtin number must be an integer constant");
-                    break;
-                }
-                ast_unref(number);
-
-                fractional = modff(builtin_num, &integral);
-                if (builtin_num < 0 || fractional != 0) {
-                    parseerror(parser, "builtin number must be an integer greater than zero");
-                    break;
-                }
-
-                /* we only want the integral part anyways */
-                builtin_num = integral;
-            } else if (parser->tok == TOKEN_INTCONST) {
-                builtin_num = parser_token(parser)->constval.i;
-            } else {
-                parseerror(parser, "builtin number must be a compile time constant");
-                break;
-            }
-
-            if (var->hasvalue) {
-                (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION,
-                                    "builtin `%s` has already been defined\n"
-                                    " -> previous declaration here: %s:%i",
-                                    var->name, ast_ctx(var).file, (int)ast_ctx(var).line);
-            }
-            else
-            {
-                func = ast_function_new(ast_ctx(var), var->name, var);
-                if (!func) {
-                    parseerror(parser, "failed to allocate function for `%s`", var->name);
-                    break;
-                }
-                vec_push(parser->functions, func);
-
-                func->builtin = -builtin_num-1;
-            }
-
-            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)
-                    ? (parser->tok != ',' && parser->tok != ';')
-                    : (!parser_next(parser)))
-            {
-                parseerror(parser, "expected comma or semicolon");
-                if (func)
-                    ast_function_delete(func);
-                var->constval.vfunc = NULL;
-                break;
-            }
-        }
-        else if (var->expression.vtype == TYPE_ARRAY && parser->tok == '{')
-        {
-            if (localblock) {
-                /* Note that fteqcc and most others don't even *have*
-                 * local arrays, so this is not a high priority.
-                 */
-                parseerror(parser, "TODO: initializers for local arrays");
-                break;
-            }
-
-            var->hasvalue = true;
-            if (!parse_array(parser, var))
-                break;
-        }
-        else if (var->expression.vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '['))
-        {
-            if (localblock) {
-                parseerror(parser, "cannot declare functions within functions");
-                break;
-            }
-
-            if (proto)
-                ast_ctx(proto) = parser_ctx(parser);
-
-            if (!parse_function_body(parser, var))
-                break;
-            ast_delete(basetype);
-            for (i = 0; i < vec_size(parser->gotos); ++i)
-                parseerror(parser, "undefined label: `%s`", parser->gotos[i]->name);
-            vec_free(parser->gotos);
-            vec_free(parser->labels);
-            return true;
-        } else {
-            ast_expression *cexp;
-            ast_value      *cval;
-            bool            folded_const = false;
-
-            cexp = parse_expression_leave(parser, true, false, false);
-            if (!cexp)
-                break;
-            cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : NULL;
-
-            /* deal with foldable constants: */
-            if (localblock &&
-                var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield)
-            {
-                /* remove it from the current locals */
-                if (isvector) {
-                    for (i = 0; i < 3; ++i) {
-                        vec_pop(parser->_locals);
-                        vec_pop(localblock->collect);
-                    }
-                }
-                /* do sanity checking, this function really needs refactoring */
-                if (vec_last(parser->_locals) != (ast_expression*)var)
-                    parseerror(parser, "internal error: unexpected change in local variable handling");
-                else
-                    vec_pop(parser->_locals);
-                if (vec_last(localblock->locals) != var)
-                    parseerror(parser, "internal error: unexpected change in local variable handling (2)");
-                else
-                    vec_pop(localblock->locals);
-                /* push it to the to-be-generated globals */
-                vec_push(parser->globals, (ast_expression*)var);
-                if (isvector)
-                    for (i = 0; i < 3; ++i)
-                        vec_push(parser->globals, (ast_expression*)last_me[i]);
-                folded_const = true;
-            }
-
-            if (folded_const || !localblock || is_static) {
-                if (cval != parser->nil &&
-                    (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
-                   )
-                {
-                    parseerror(parser, "initializer is non constant");
-                }
-                else
-                {
-                    if (!is_static &&
-                        !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
-                        qualifier != CV_VAR)
-                    {
-                        var->cvq = CV_CONST;
-                    }
-                    if (cval == parser->nil)
-                        var->expression.flags |= AST_FLAG_INITIALIZED;
-                    else
-                    {
-                        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 {
-                int cvq;
-                shunt sy = { NULL, NULL, NULL, NULL };
-                cvq = var->cvq;
-                var->cvq = CV_NONE;
-                vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var));
-                vec_push(sy.out, syexp(ast_ctx(cexp), (ast_expression*)cexp));
-                vec_push(sy.ops, syop(ast_ctx(var), parser->assign_op));
-                if (!parser_sy_apply_operator(parser, &sy))
-                    ast_unref(cexp);
-                else {
-                    if (vec_size(sy.out) != 1 && vec_size(sy.ops) != 0)
-                        parseerror(parser, "internal error: leaked operands");
-                    if (!ast_block_add_expr(localblock, (ast_expression*)sy.out[0].out))
-                        break;
-                }
-                vec_free(sy.out);
-                vec_free(sy.ops);
-                vec_free(sy.argc);
-                var->cvq = cvq;
-            }
-            /* a constant initialized to an inexact value should be marked inexact:
-             * const float x = <inexact>; should propagate the inexact flag
-             */
-            if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
-                if (cval && cval->hasvalue && cval->cvq == CV_CONST)
-                    var->inexact = cval->inexact;
-            }
-        }
-
-another:
-        if (parser->tok == ',') {
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected another variable");
-                break;
-            }
-
-            if (parser->tok != TOKEN_IDENT) {
-                parseerror(parser, "expected another variable");
-                break;
-            }
-            var = ast_value_copy(basetype);
-            cleanvar = true;
-            ast_value_set_name(var, parser_tokval(parser));
-            if (!parser_next(parser)) {
-                parseerror(parser, "error parsing variable declaration");
-                break;
-            }
-            continue;
-        }
-
-        if (parser->tok != ';') {
-            parseerror(parser, "missing semicolon after variables");
-            break;
-        }
-
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error after variable declaration");
-            break;
-        }
-
-        ast_delete(basetype);
-        return true;
-    }
-
-    if (cleanvar && var)
-        ast_delete(var);
-    ast_delete(basetype);
-    return false;
-
-cleanup:
-    ast_delete(basetype);
-    if (cleanvar && var)
-        ast_delete(var);
-    if (me[0]) ast_member_delete(me[0]);
-    if (me[1]) ast_member_delete(me[1]);
-    if (me[2]) ast_member_delete(me[2]);
-    return retval;
-}
-
-static bool parser_global_statement(parser_t *parser)
-{
-    int        cvq       = CV_WRONG;
-    bool       noref     = 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 == '.' || parser->tok == TOKEN_DOTS)
-    {
-        return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL);
-    }
-    else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring))
-    {
-        if (cvq == CV_WRONG)
-            return false;
-        return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring);
-    }
-    else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum"))
-    {
-        return parse_enum(parser);
-    }
-    else if (parser->tok == TOKEN_KEYWORD)
-    {
-        if (!strcmp(parser_tokval(parser), "typedef")) {
-            if (!parser_next(parser)) {
-                parseerror(parser, "expected type definition after 'typedef'");
-                return false;
-            }
-            return parse_typedef(parser);
-        }
-        parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser));
-        return false;
-    }
-    else if (parser->tok == '#')
-    {
-        return parse_pragma(parser);
-    }
-    else if (parser->tok == '$')
-    {
-        if (!parser_next(parser)) {
-            parseerror(parser, "parse error");
-            return false;
-        }
-    }
-    else
-    {
-        parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value);
-        return false;
-    }
-    return true;
-}
-
-static uint16_t progdefs_crc_sum(uint16_t old, const char *str)
-{
-    return util_crc16(old, str, strlen(str));
-}
-
-static void progdefs_crc_file(const char *str)
-{
-    /* write to progdefs.h here */
-    (void)str;
-}
-
-static uint16_t progdefs_crc_both(uint16_t old, const char *str)
-{
-    old = progdefs_crc_sum(old, str);
-    progdefs_crc_file(str);
-    return old;
-}
-
-static void generate_checksum(parser_t *parser, ir_builder *ir)
-{
-    uint16_t   crc = 0xFFFF;
-    size_t     i;
-    ast_value *value;
-
-    crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{");
-    crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n");
-    /*
-    progdefs_crc_file("\tint\tpad;\n");
-    progdefs_crc_file("\tint\tofs_return[3];\n");
-    progdefs_crc_file("\tint\tofs_parm0[3];\n");
-    progdefs_crc_file("\tint\tofs_parm1[3];\n");
-    progdefs_crc_file("\tint\tofs_parm2[3];\n");
-    progdefs_crc_file("\tint\tofs_parm3[3];\n");
-    progdefs_crc_file("\tint\tofs_parm4[3];\n");
-    progdefs_crc_file("\tint\tofs_parm5[3];\n");
-    progdefs_crc_file("\tint\tofs_parm6[3];\n");
-    progdefs_crc_file("\tint\tofs_parm7[3];\n");
-    */
-    for (i = 0; i < parser->crc_globals; ++i) {
-        if (!ast_istype(parser->globals[i], ast_value))
-            continue;
-        value = (ast_value*)(parser->globals[i]);
-        switch (value->expression.vtype) {
-            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
-            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
-            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
-            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
-            default:
-                crc = progdefs_crc_both(crc, "\tint\t");
-                break;
-        }
-        crc = progdefs_crc_both(crc, value->name);
-        crc = progdefs_crc_both(crc, ";\n");
-    }
-    crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n");
-    for (i = 0; i < parser->crc_fields; ++i) {
-        if (!ast_istype(parser->fields[i], ast_value))
-            continue;
-        value = (ast_value*)(parser->fields[i]);
-        switch (value->expression.next->vtype) {
-            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
-            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
-            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
-            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
-            default:
-                crc = progdefs_crc_both(crc, "\tint\t");
-                break;
-        }
-        crc = progdefs_crc_both(crc, value->name);
-        crc = progdefs_crc_both(crc, ";\n");
-    }
-    crc = progdefs_crc_both(crc, "} entvars_t;\n\n");
-    ir->code->crc = crc;
-}
-
-parser_t *parser_create()
-{
-    parser_t *parser;
-    lex_ctx_t empty_ctx;
-    size_t i;
-
-    parser = (parser_t*)mem_a(sizeof(parser_t));
-    if (!parser)
-        return NULL;
-
-    memset(parser, 0, sizeof(*parser));
-
-    for (i = 0; i < operator_count; ++i) {
-        if (operators[i].id == opid1('=')) {
-            parser->assign_op = operators+i;
-            break;
-        }
-    }
-    if (!parser->assign_op) {
-        con_err("internal error: initializing parser: failed to find assign operator\n");
-        mem_d(parser);
-        return NULL;
-    }
-
-    vec_push(parser->variables, parser->htfields  = util_htnew(PARSER_HT_SIZE));
-    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);
-
-    parser->aliases = util_htnew(PARSER_HT_SIZE);
-
-    empty_ctx.file   = "<internal>";
-    empty_ctx.line   = 0;
-    empty_ctx.column = 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);
-
-    parser->max_param_count = 1;
-
-    parser->const_vec[0] = ast_value_new(empty_ctx, "<vector.x>", TYPE_NOEXPR);
-    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_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;
-        parser->reserved_version->expression.flags |= AST_FLAG_INCLUDE_DEF;
-        parser->reserved_version->constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING);
-    } else {
-        parser->reserved_version = NULL;
-    }
-
-    parser->fold   = fold_init  (parser);
-    parser->intrin = intrin_init(parser);
-    return parser;
-}
-
-static bool parser_compile(parser_t *parser)
-{
-    /* initial lexer/parser state */
-    parser->lex->flags.noops = true;
-
-    if (parser_next(parser))
-    {
-        while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
-        {
-            if (!parser_global_statement(parser)) {
-                if (parser->tok == TOKEN_EOF)
-                    parseerror(parser, "unexpected end of file");
-                else if (compile_errors)
-                    parseerror(parser, "there have been errors, bailing out");
-                lex_close(parser->lex);
-                parser->lex = NULL;
-                return false;
-            }
-        }
-    } else {
-        parseerror(parser, "parse error");
-        lex_close(parser->lex);
-        parser->lex = NULL;
-        return false;
-    }
-
-    lex_close(parser->lex);
-    parser->lex = NULL;
-
-    return !compile_errors;
-}
-
-bool parser_compile_file(parser_t *parser, const char *filename)
-{
-    parser->lex = lex_open(filename);
-    if (!parser->lex) {
-        con_err("failed to open file \"%s\"\n", filename);
-        return false;
-    }
-    return parser_compile(parser);
-}
-
-bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len)
-{
-    parser->lex = lex_open_string(str, len, name);
-    if (!parser->lex) {
-        con_err("failed to create lexer for string \"%s\"\n", name);
-        return false;
-    }
-    return parser_compile(parser);
-}
-
-static void parser_remove_ast(parser_t *parser)
-{
-    size_t i;
-    if (parser->ast_cleaned)
-        return;
-    parser->ast_cleaned = true;
-    for (i = 0; i < vec_size(parser->accessors); ++i) {
-        ast_delete(parser->accessors[i]->constval.vfunc);
-        parser->accessors[i]->constval.vfunc = NULL;
-        ast_delete(parser->accessors[i]);
-    }
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        ast_delete(parser->functions[i]);
-    }
-    for (i = 0; i < vec_size(parser->fields); ++i) {
-        ast_delete(parser->fields[i]);
-    }
-    for (i = 0; i < vec_size(parser->globals); ++i) {
-        ast_delete(parser->globals[i]);
-    }
-    vec_free(parser->accessors);
-    vec_free(parser->functions);
-    vec_free(parser->globals);
-    vec_free(parser->fields);
-
-    for (i = 0; i < vec_size(parser->variables); ++i)
-        util_htdel(parser->variables[i]);
-    vec_free(parser->variables);
-    vec_free(parser->_blocklocals);
-    vec_free(parser->_locals);
-
-    for (i = 0; i < vec_size(parser->_typedefs); ++i)
-        ast_delete(parser->_typedefs[i]);
-    vec_free(parser->_typedefs);
-    for (i = 0; i < vec_size(parser->typedefs); ++i)
-        util_htdel(parser->typedefs[i]);
-    vec_free(parser->typedefs);
-    vec_free(parser->_blocktypedefs);
-
-    vec_free(parser->_block_ctx);
-
-    vec_free(parser->labels);
-    vec_free(parser->gotos);
-    vec_free(parser->breaks);
-    vec_free(parser->continues);
-
-    ast_value_delete(parser->nil);
-
-    ast_value_delete(parser->const_vec[0]);
-    ast_value_delete(parser->const_vec[1]);
-    ast_value_delete(parser->const_vec[2]);
-
-    if (parser->reserved_version)
-        ast_value_delete(parser->reserved_version);
-
-    util_htdel(parser->aliases);
-    fold_cleanup(parser->fold);
-    intrin_cleanup(parser->intrin);
-}
-
-void parser_cleanup(parser_t *parser)
-{
-    parser_remove_ast(parser);
-    mem_d(parser);
-}
-
-static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) {
-    size_t          i;
-    ast_expression *expr;
-    ast_value      *cov;
-    ast_function   *func;
-
-    if (!OPTS_OPTION_BOOL(OPTION_COVERAGE))
-        return true;
-
-    func = NULL;
-    for (i = 0; i != vec_size(parser->functions); ++i) {
-        if (!strcmp(parser->functions[i]->name, "coverage")) {
-            func = parser->functions[i];
-            break;
-        }
-    }
-    if (!func) {
-        if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) {
-            con_out("coverage support requested but no coverage() builtin declared\n");
-            ir_builder_delete(ir);
-            return false;
-        }
-        return true;
-    }
-
-    cov  = func->vtype;
-    expr = (ast_expression*)cov;
-
-    if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) {
-        char ty[1024];
-        ast_type_to_string(expr, ty, sizeof(ty));
-        con_out("invalid type for coverage(): %s\n", ty);
-        ir_builder_delete(ir);
-        return false;
-    }
-
-    ir->coverage_func = func->ir_func->value;
-    return true;
-}
-
-bool parser_finish(parser_t *parser, const char *output)
-{
-    size_t          i;
-    ir_builder     *ir;
-    bool            retval = true;
-
-    if (compile_errors) {
-        con_out("*** there were compile errors\n");
-        return false;
-    }
-
-    ir = ir_builder_new("gmqcc_out");
-    if (!ir) {
-        con_out("failed to allocate builder\n");
-        return false;
-    }
-
-    for (i = 0; i < vec_size(parser->fields); ++i) {
-        ast_value *field;
-        bool hasvalue;
-        if (!ast_istype(parser->fields[i], ast_value))
-            continue;
-        field = (ast_value*)parser->fields[i];
-        hasvalue = field->hasvalue;
-        field->hasvalue = false;
-        if (!ast_global_codegen((ast_value*)field, ir, true)) {
-            con_out("failed to generate field %s\n", field->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-        if (hasvalue) {
-            ir_value *ifld;
-            ast_expression *subtype;
-            field->hasvalue = true;
-            subtype = field->expression.next;
-            ifld = ir_builder_create_field(ir, field->name, subtype->vtype);
-            if (subtype->vtype == TYPE_FIELD)
-                ifld->fieldtype = subtype->next->vtype;
-            else if (subtype->vtype == TYPE_FUNCTION)
-                ifld->outtype = subtype->next->vtype;
-            (void)!ir_value_set_field(field->ir_v, ifld);
-        }
-    }
-    for (i = 0; i < vec_size(parser->globals); ++i) {
-        ast_value *asvalue;
-        if (!ast_istype(parser->globals[i], ast_value))
-            continue;
-        asvalue = (ast_value*)(parser->globals[i]);
-        if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) {
-            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);
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    /* Build function vararg accessor ast tree now before generating
-     * immediates, because the accessors may add new immediates
-     */
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        ast_function *f = parser->functions[i];
-        if (f->varargs) {
-            if (parser->max_param_count > vec_size(f->vtype->expression.params)) {
-                f->varargs->expression.count = parser->max_param_count - vec_size(f->vtype->expression.params);
-                if (!parser_create_array_setter_impl(parser, f->varargs)) {
-                    con_out("failed to generate vararg setter for %s\n", f->name);
-                    ir_builder_delete(ir);
-                    return false;
-                }
-                if (!parser_create_array_getter_impl(parser, f->varargs)) {
-                    con_out("failed to generate vararg getter for %s\n", f->name);
-                    ir_builder_delete(ir);
-                    return false;
-                }
-            } else {
-                ast_delete(f->varargs);
-                f->varargs = NULL;
-            }
-        }
-    }
-    /* Now we can generate immediates */
-    if (!fold_generate(parser->fold, ir))
-        return false;
-
-    /* before generating any functions we need to set the coverage_func */
-    if (!parser_set_coverage_func(parser, ir))
-        return false;
-
-    for (i = 0; i < vec_size(parser->globals); ++i) {
-        ast_value *asvalue;
-        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;
-        }
-    }
-    for (i = 0; i < vec_size(parser->fields); ++i) {
-        ast_value *asvalue;
-        asvalue = (ast_value*)(parser->fields[i]->next);
-
-        if (!ast_istype((ast_expression*)asvalue, ast_value))
-            continue;
-        if (asvalue->expression.vtype != TYPE_ARRAY)
-            continue;
-        if (!ast_generate_accessors(asvalue, ir)) {
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    if (parser->reserved_version &&
-        !ast_global_codegen(parser->reserved_version, ir, false))
-    {
-        con_out("failed to generate reserved::version");
-        ir_builder_delete(ir);
-        return false;
-    }
-    for (i = 0; i < vec_size(parser->functions); ++i) {
-        ast_function *f = parser->functions[i];
-        if (!ast_function_codegen(f, ir)) {
-            con_out("failed to generate function %s\n", f->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-
-    generate_checksum(parser, ir);
-
-    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)) {
-            con_out("failed to finalize function %s\n", parser->functions[i]->name);
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    parser_remove_ast(parser);
-
-    if (compile_Werrors) {
-        con_out("*** there were warnings treated as errors\n");
-        compile_show_werrors();
-        retval = false;
-    }
-
-    if (retval) {
-        if (OPTS_OPTION_BOOL(OPTION_DUMPFIN))
-            ir_builder_dump(ir, con_out);
-
-        if (!ir_builder_generate(ir, output)) {
-            con_out("*** failed to generate output file\n");
-            ir_builder_delete(ir);
-            return false;
-        }
-    }
-    ir_builder_delete(ir);
-    return retval;
-}
diff --git a/parser.cpp b/parser.cpp
new file mode 100644 (file)
index 0000000..0370146
--- /dev/null
@@ -0,0 +1,6380 @@
+#include <string.h>
+#include <math.h>
+
+#include "parser.h"
+
+#define PARSER_HT_LOCALS  2
+#define PARSER_HT_SIZE    512
+#define TYPEDEF_HT_SIZE   512
+
+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 void parser_addglobal(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 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, bool truthvalue, bool with_labels);
+static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels);
+static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname);
+static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname);
+static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg);
+
+static void parseerror(parser_t *parser, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vcompile_error(parser->lex->tok.ctx, fmt, ap);
+    va_end(ap);
+}
+
+/* returns true if it counts as an error */
+static bool GMQCC_WARN parsewarning(parser_t *parser, int warntype, const char *fmt, ...)
+{
+    bool    r;
+    va_list ap;
+    va_start(ap, fmt);
+    r = vcompile_warning(parser->lex->tok.ctx, warntype, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+/**********************************************************************
+ * parsing
+ */
+
+static bool parser_next(parser_t *parser)
+{
+    /* lex_do kills the previous token */
+    parser->tok = lex_do(parser->lex);
+    if (parser->tok == TOKEN_EOF)
+        return true;
+    if (parser->tok >= TOKEN_ERROR) {
+        parseerror(parser, "lex error");
+        return false;
+    }
+    return true;
+}
+
+#define parser_tokval(p) ((p)->lex->tok.value)
+#define parser_token(p)  (&((p)->lex->tok))
+
+char *parser_strdup(const char *str)
+{
+    if (str && !*str) {
+        /* actually dup empty strings */
+        char *out = (char*)mem_a(1);
+        *out = 0;
+        return out;
+    }
+    return util_strdup(str);
+}
+
+static ast_expression* parser_find_field(parser_t *parser, const char *name)
+{
+    return ( ast_expression*)util_htget(parser->htfields, name);
+}
+
+static ast_expression* parser_find_label(parser_t *parser, const char *name)
+{
+    size_t i;
+    for(i = 0; i < vec_size(parser->labels); i++)
+        if (!strcmp(parser->labels[i]->name, name))
+            return (ast_expression*)parser->labels[i];
+    return NULL;
+}
+
+ast_expression* parser_find_global(parser_t *parser, const char *name)
+{
+    ast_expression *var = (ast_expression*)util_htget(parser->aliases, parser_tokval(parser));
+    if (var)
+        return var;
+    return (ast_expression*)util_htget(parser->htglobals, name);
+}
+
+static ast_expression* parser_find_param(parser_t *parser, const char *name)
+{
+    size_t i;
+    ast_value *fun;
+    if (!parser->function)
+        return NULL;
+    fun = parser->function->vtype;
+    for (i = 0; i < vec_size(fun->expression.params); ++i) {
+        if (!strcmp(fun->expression.params[i]->name, name))
+            return (ast_expression*)(fun->expression.params[i]);
+    }
+    return NULL;
+}
+
+static ast_expression* parser_find_local(parser_t *parser, const char *name, size_t upto, bool *isparam)
+{
+    size_t          i, hash;
+    ast_expression *e;
+
+    hash = util_hthash(parser->htglobals, name);
+
+    *isparam = false;
+    for (i = vec_size(parser->variables); i > upto;) {
+        --i;
+        if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) )
+            return e;
+    }
+    *isparam = true;
+    return parser_find_param(parser, name);
+}
+
+static ast_expression* parser_find_var(parser_t *parser, const char *name)
+{
+    bool dummy;
+    ast_expression *v;
+    v         = parser_find_local(parser, name, 0, &dummy);
+    if (!v) v = parser_find_global(parser, name);
+    return v;
+}
+
+static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t upto)
+{
+    size_t     i, hash;
+    ast_value *e;
+    hash = util_hthash(parser->typedefs[0], name);
+
+    for (i = vec_size(parser->typedefs); i > upto;) {
+        --i;
+        if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) )
+            return e;
+    }
+    return NULL;
+}
+
+typedef struct
+{
+    size_t etype; /* 0 = expression, others are operators */
+    bool            isparen;
+    size_t          off;
+    ast_expression *out;
+    ast_block      *block; /* for commas and function calls */
+    lex_ctx_t ctx;
+} sy_elem;
+
+enum {
+    PAREN_EXPR,
+    PAREN_FUNC,
+    PAREN_INDEX,
+    PAREN_TERNARY1,
+    PAREN_TERNARY2
+};
+typedef struct
+{
+    sy_elem        *out;
+    sy_elem        *ops;
+    size_t         *argc;
+    unsigned int   *paren;
+} shunt;
+
+static sy_elem syexp(lex_ctx_t ctx, ast_expression *v) {
+    sy_elem e;
+    e.etype = 0;
+    e.off   = 0;
+    e.out   = v;
+    e.block = NULL;
+    e.ctx   = ctx;
+    e.isparen = false;
+    return e;
+}
+
+static sy_elem syblock(lex_ctx_t ctx, ast_block *v) {
+    sy_elem e;
+    e.etype = 0;
+    e.off   = 0;
+    e.out   = (ast_expression*)v;
+    e.block = v;
+    e.ctx   = ctx;
+    e.isparen = false;
+    return e;
+}
+
+static sy_elem syop(lex_ctx_t ctx, const oper_info *op) {
+    sy_elem e;
+    e.etype = 1 + (op - operators);
+    e.off   = 0;
+    e.out   = NULL;
+    e.block = NULL;
+    e.ctx   = ctx;
+    e.isparen = false;
+    return e;
+}
+
+static sy_elem syparen(lex_ctx_t ctx, size_t off) {
+    sy_elem e;
+    e.etype = 0;
+    e.off   = off;
+    e.out   = NULL;
+    e.block = NULL;
+    e.ctx   = ctx;
+    e.isparen = true;
+    return e;
+}
+
+/* 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]).
+ */
+static bool rotate_entfield_array_index_nodes(ast_expression **out)
+{
+    ast_array_index *index, *oldindex;
+    ast_entfield    *entfield;
+
+    ast_value       *field;
+    ast_expression  *sub;
+    ast_expression  *entity;
+
+    lex_ctx_t ctx = ast_ctx(*out);
+
+    if (!ast_istype(*out, ast_array_index))
+        return false;
+    index = (ast_array_index*)*out;
+
+    if (!ast_istype(index->array, ast_entfield))
+        return false;
+    entfield = (ast_entfield*)index->array;
+
+    if (!ast_istype(entfield->field, ast_value))
+        return false;
+    field = (ast_value*)entfield->field;
+
+    sub    = index->index;
+    entity = entfield->entity;
+
+    oldindex = index;
+
+    index = ast_array_index_new(ctx, (ast_expression*)field, sub);
+    entfield = ast_entfield_new(ctx, entity, (ast_expression*)index);
+    *out = (ast_expression*)entfield;
+
+    oldindex->array = NULL;
+    oldindex->index = NULL;
+    ast_delete(oldindex);
+
+    return true;
+}
+
+static bool check_write_to(lex_ctx_t ctx, ast_expression *expr)
+{
+    if (ast_istype(expr, ast_value)) {
+        ast_value *val = (ast_value*)expr;
+        if (val->cvq == CV_CONST) {
+            if (val->name[0] == '#') {
+                compile_error(ctx, "invalid assignment to a literal constant");
+                return false;
+            }
+            /*
+             * To work around quakeworld we must elide the error and make it
+             * a warning instead.
+             */
+            if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_QCC)
+                compile_error(ctx, "assignment to constant `%s`", val->name);
+            else
+                (void)!compile_warning(ctx, WARN_CONST_OVERWRITE, "assignment to constant `%s`", val->name);
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
+{
+    const oper_info *op;
+    lex_ctx_t ctx;
+    ast_expression *out = NULL;
+    ast_expression *exprs[3];
+    ast_block      *blocks[3];
+    ast_binstore   *asbinstore;
+    size_t i, assignop, addop, subop;
+    qcint_t  generated_op = 0;
+
+    char ty1[1024];
+    char ty2[1024];
+
+    if (!vec_size(sy->ops)) {
+        parseerror(parser, "internal error: missing operator");
+        return false;
+    }
+
+    if (vec_last(sy->ops).isparen) {
+        parseerror(parser, "unmatched parenthesis");
+        return false;
+    }
+
+    op = &operators[vec_last(sy->ops).etype - 1];
+    ctx = vec_last(sy->ops).ctx;
+
+    if (vec_size(sy->out) < op->operands) {
+        if (op->flags & OP_PREFIX)
+            compile_error(ctx, "expected expression after unary operator `%s`", op->op, (int)op->id);
+        else /* this should have errored previously already */
+            compile_error(ctx, "expected expression after operator `%s`", op->op, (int)op->id);
+        return false;
+    }
+
+    vec_shrinkby(sy->ops, 1);
+
+    /* op(:?) has no input and no output */
+    if (!op->operands)
+        return true;
+
+    vec_shrinkby(sy->out, op->operands);
+    for (i = 0; i < op->operands; ++i) {
+        exprs[i]  = sy->out[vec_size(sy->out)+i].out;
+        blocks[i] = sy->out[vec_size(sy->out)+i].block;
+
+        if (exprs[i]->vtype == TYPE_NOEXPR &&
+            !(i != 0 && op->id == opid2('?',':')) &&
+            !(i == 1 && op->id == opid1('.')))
+        {
+            if (ast_istype(exprs[i], ast_label))
+                compile_error(ast_ctx(exprs[i]), "expected expression, got an unknown identifier");
+            else
+                compile_error(ast_ctx(exprs[i]), "not an expression");
+            (void)!compile_warning(ast_ctx(exprs[i]), WARN_DEBUG, "expression %u\n", (unsigned int)i);
+        }
+    }
+
+    if (blocks[0] && !vec_size(blocks[0]->exprs) && op->id != opid1(',')) {
+        compile_error(ctx, "internal error: operator cannot be applied on empty blocks");
+        return false;
+    }
+
+#define NotSameType(T) \
+             (exprs[0]->vtype != exprs[1]->vtype || \
+              exprs[0]->vtype != T)
+
+    switch (op->id)
+    {
+        default:
+            compile_error(ctx, "internal error: unhandled operator: %s (%i)", op->op, (int)op->id);
+            return false;
+
+        case opid1('.'):
+            if (exprs[0]->vtype == TYPE_VECTOR &&
+                exprs[1]->vtype == TYPE_NOEXPR)
+            {
+                if      (exprs[1] == (ast_expression*)parser->const_vec[0])
+                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 0, NULL);
+                else if (exprs[1] == (ast_expression*)parser->const_vec[1])
+                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 1, NULL);
+                else if (exprs[1] == (ast_expression*)parser->const_vec[2])
+                    out = (ast_expression*)ast_member_new(ctx, exprs[0], 2, NULL);
+                else {
+                    compile_error(ctx, "access to invalid vector component");
+                    return false;
+                }
+            }
+            else if (exprs[0]->vtype == TYPE_ENTITY) {
+                if (exprs[1]->vtype != TYPE_FIELD) {
+                    compile_error(ast_ctx(exprs[1]), "type error: right hand of member-operand should be an entity-field");
+                    return false;
+                }
+                out = (ast_expression*)ast_entfield_new(ctx, exprs[0], exprs[1]);
+            }
+            else if (exprs[0]->vtype == TYPE_VECTOR) {
+                compile_error(ast_ctx(exprs[1]), "vectors cannot be accessed this way");
+                return false;
+            }
+            else {
+                compile_error(ast_ctx(exprs[1]), "type error: member-of operator on something that is not an entity or vector");
+                return false;
+            }
+            break;
+
+        case opid1('['):
+            if (exprs[0]->vtype != TYPE_ARRAY &&
+                !(exprs[0]->vtype == TYPE_FIELD &&
+                  exprs[0]->next->vtype == TYPE_ARRAY))
+            {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "cannot index value of type %s", ty1);
+                return false;
+            }
+            if (exprs[1]->vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[1]), "index must be of type float, not %s", ty1);
+                return false;
+            }
+            out = (ast_expression*)ast_array_index_new(ctx, exprs[0], exprs[1]);
+            rotate_entfield_array_index_nodes(&out);
+            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;
+            } else {
+                blocks[0] = ast_block_new(ctx);
+                if (!ast_block_add_expr(blocks[0], exprs[0]) ||
+                    !ast_block_add_expr(blocks[0], exprs[1]))
+                {
+                    return false;
+                }
+            }
+            ast_block_set_type(blocks[0], exprs[1]);
+
+            vec_push(sy->out, syblock(ctx, blocks[0]));
+            return true;
+
+        case opid2('+','P'):
+            out = exprs[0];
+            break;
+        case opid2('-','P'):
+            if ((out = fold_op(parser->fold, op, exprs)))
+                break;
+
+            if (exprs[0]->vtype != TYPE_FLOAT &&
+                exprs[0]->vtype != TYPE_VECTOR) {
+                    compile_error(ctx, "invalid types used in unary expression: cannot negate type %s",
+                                  type_name[exprs[0]->vtype]);
+                return false;
+            }
+            if (exprs[0]->vtype == TYPE_FLOAT)
+                out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_F, exprs[0]);
+            else
+                out = (ast_expression*)ast_unary_new(ctx, VINSTR_NEG_V, exprs[0]);
+            break;
+
+        case opid2('!','P'):
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                switch (exprs[0]->vtype) {
+                    case TYPE_FLOAT:
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]);
+                        break;
+                    case TYPE_VECTOR:
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[0]);
+                        break;
+                    case TYPE_STRING:
+                        if (OPTS_FLAG(TRUE_EMPTY_STRINGS))
+                            out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, exprs[0]);
+                        else
+                            out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[0]);
+                        break;
+                    /* we don't constant-fold NOT for these types */
+                    case TYPE_ENTITY:
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_ENT, exprs[0]);
+                        break;
+                    case TYPE_FUNCTION:
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_FNC, exprs[0]);
+                        break;
+                    default:
+                    compile_error(ctx, "invalid types used in expression: cannot logically negate type %s",
+                                  type_name[exprs[0]->vtype]);
+                    return false;
+                }
+            }
+            break;
+
+        case opid1('+'):
+            if (exprs[0]->vtype != exprs[1]->vtype ||
+               (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) )
+            {
+                compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
+                              type_name[exprs[0]->vtype],
+                              type_name[exprs[1]->vtype]);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                switch (exprs[0]->vtype) {
+                    case TYPE_FLOAT:
+                        out = fold_binary(ctx, INSTR_ADD_F, exprs[0], exprs[1]);
+                        break;
+                    case TYPE_VECTOR:
+                        out = fold_binary(ctx, INSTR_ADD_V, exprs[0], exprs[1]);
+                        break;
+                    default:
+                        compile_error(ctx, "invalid types used in expression: cannot add type %s and %s",
+                                      type_name[exprs[0]->vtype],
+                                      type_name[exprs[1]->vtype]);
+                        return false;
+                }
+            }
+            break;
+        case opid1('-'):
+            if  (exprs[0]->vtype != exprs[1]->vtype ||
+                (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT))
+            {
+                compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
+                              type_name[exprs[1]->vtype],
+                              type_name[exprs[0]->vtype]);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                switch (exprs[0]->vtype) {
+                    case TYPE_FLOAT:
+                        out = fold_binary(ctx, INSTR_SUB_F, exprs[0], exprs[1]);
+                        break;
+                    case TYPE_VECTOR:
+                        out = fold_binary(ctx, INSTR_SUB_V, exprs[0], exprs[1]);
+                        break;
+                    default:
+                        compile_error(ctx, "invalid types used in expression: cannot subtract type %s from %s",
+                                      type_name[exprs[1]->vtype],
+                                      type_name[exprs[0]->vtype]);
+                        return false;
+                }
+            }
+            break;
+        case opid1('*'):
+            if (exprs[0]->vtype != exprs[1]->vtype &&
+                !(exprs[0]->vtype == TYPE_VECTOR &&
+                  exprs[1]->vtype == TYPE_FLOAT) &&
+                !(exprs[1]->vtype == TYPE_VECTOR &&
+                  exprs[0]->vtype == TYPE_FLOAT)
+                )
+            {
+                compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
+                              type_name[exprs[1]->vtype],
+                              type_name[exprs[0]->vtype]);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                switch (exprs[0]->vtype) {
+                    case TYPE_FLOAT:
+                        if (exprs[1]->vtype == TYPE_VECTOR)
+                            out = fold_binary(ctx, INSTR_MUL_FV, exprs[0], exprs[1]);
+                        else
+                            out = fold_binary(ctx, INSTR_MUL_F, exprs[0], exprs[1]);
+                        break;
+                    case TYPE_VECTOR:
+                        if (exprs[1]->vtype == TYPE_FLOAT)
+                            out = fold_binary(ctx, INSTR_MUL_VF, exprs[0], exprs[1]);
+                        else
+                            out = fold_binary(ctx, INSTR_MUL_V, exprs[0], exprs[1]);
+                        break;
+                    default:
+                        compile_error(ctx, "invalid types used in expression: cannot multiply types %s and %s",
+                                      type_name[exprs[1]->vtype],
+                                      type_name[exprs[0]->vtype]);
+                        return false;
+                }
+            }
+            break;
+
+        case opid1('/'):
+            if (exprs[1]->vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                if (exprs[0]->vtype == TYPE_FLOAT)
+                    out = fold_binary(ctx, INSTR_DIV_F, exprs[0], exprs[1]);
+                else {
+                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    compile_error(ctx, "invalid types used in expression: cannot divide types %s and %s", ty1, ty2);
+                    return false;
+                }
+            }
+            break;
+
+        case opid1('%'):
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform modulo operation between types %s and %s",
+                    type_name[exprs[0]->vtype],
+                    type_name[exprs[1]->vtype]);
+                return false;
+            } else if (!(out = fold_op(parser->fold, op, exprs))) {
+                /* generate a call to __builtin_mod */
+                ast_expression *mod  = intrin_func(parser->intrin, "mod");
+                ast_call       *call = NULL;
+                if (!mod) return false; /* can return null for missing floor */
+
+                call = ast_call_new(parser_ctx(parser), mod);
+                vec_push(call->params, exprs[0]);
+                vec_push(call->params, exprs[1]);
+
+                out = (ast_expression*)call;
+            }
+            break;
+
+        case opid2('%','='):
+            compile_error(ctx, "%= is unimplemented");
+            return false;
+
+        case opid1('|'):
+        case opid1('&'):
+        case opid1('^'):
+            if ( !(exprs[0]->vtype == TYPE_FLOAT  && exprs[1]->vtype == TYPE_FLOAT) &&
+                 !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) &&
+                 !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR))
+            {
+                compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s",
+                              type_name[exprs[0]->vtype],
+                              type_name[exprs[1]->vtype]);
+                return false;
+            }
+
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                /*
+                 * IF the first expression is float, the following will be too
+                 * since scalar ^ vector is not allowed.
+                 */
+                if (exprs[0]->vtype == TYPE_FLOAT) {
+                    out = fold_binary(ctx,
+                        (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND),
+                        exprs[0], exprs[1]);
+                } else {
+                    /*
+                     * The first is a vector: vector is allowed to bitop with vector and
+                     * with scalar, branch here for the second operand.
+                     */
+                    if (exprs[1]->vtype == TYPE_VECTOR) {
+                        /*
+                         * Bitop all the values of the vector components against the
+                         * vectors components in question.
+                         */
+                        out = fold_binary(ctx,
+                            (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V),
+                            exprs[0], exprs[1]);
+                    } else {
+                        out = fold_binary(ctx,
+                            (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF),
+                            exprs[0], exprs[1]);
+                    }
+                }
+            }
+            break;
+
+        case opid2('<','<'):
+        case opid2('>','>'):
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform shift between types %s and %s",
+                    type_name[exprs[0]->vtype],
+                    type_name[exprs[1]->vtype]);
+                return false;
+            }
+
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                ast_expression *shift = intrin_func(parser->intrin, (op->id == opid2('<','<')) ? "__builtin_lshift" : "__builtin_rshift");
+                ast_call       *call  = ast_call_new(parser_ctx(parser), shift);
+                vec_push(call->params, exprs[0]);
+                vec_push(call->params, exprs[1]);
+                out = (ast_expression*)call;
+            }
+            break;
+
+        case opid3('<','<','='):
+        case opid3('>','>','='):
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform shift operation between types %s and %s",
+                    type_name[exprs[0]->vtype],
+                    type_name[exprs[1]->vtype]);
+                return false;
+            }
+
+            if(!(out = fold_op(parser->fold, op, exprs))) {
+                ast_expression *shift = intrin_func(parser->intrin, (op->id == opid3('<','<','=')) ? "__builtin_lshift" : "__builtin_rshift");
+                ast_call       *call  = ast_call_new(parser_ctx(parser), shift);
+                vec_push(call->params, exprs[0]);
+                vec_push(call->params, exprs[1]);
+                out = (ast_expression*)ast_store_new(
+                    parser_ctx(parser),
+                    INSTR_STORE_F,
+                    exprs[0],
+                    (ast_expression*)call
+                );
+            }
+
+            break;
+
+        case opid2('|','|'):
+            generated_op += 1; /* INSTR_OR */
+        case opid2('&','&'):
+            generated_op += INSTR_AND;
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                if (OPTS_FLAG(PERL_LOGIC) && !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));
+                    compile_error(ctx, "invalid types for logical operation with -fperl-logic: %s and %s", ty1, ty2);
+                    return false;
+                }
+                for (i = 0; i < 2; ++i) {
+                    if (OPTS_FLAG(CORRECT_LOGIC) && exprs[i]->vtype == TYPE_VECTOR) {
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_V, exprs[i]);
+                        if (!out) break;
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out);
+                        if (!out) break;
+                        exprs[i] = out; out = NULL;
+                        if (OPTS_FLAG(PERL_LOGIC)) {
+                            /* here we want to keep the right expressions' type */
+                            break;
+                        }
+                    }
+                    else if (OPTS_FLAG(FALSE_EMPTY_STRINGS) && exprs[i]->vtype == TYPE_STRING) {
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_S, exprs[i]);
+                        if (!out) break;
+                        out = (ast_expression*)ast_unary_new(ctx, INSTR_NOT_F, out);
+                        if (!out) break;
+                        exprs[i] = out; out = NULL;
+                        if (OPTS_FLAG(PERL_LOGIC)) {
+                            /* here we want to keep the right expressions' type */
+                            break;
+                        }
+                    }
+                }
+                out = fold_binary(ctx, generated_op, exprs[0], exprs[1]);
+            }
+            break;
+
+        case opid2('?',':'):
+            if (vec_last(sy->paren) != PAREN_TERNARY2) {
+                compile_error(ctx, "mismatched parenthesis/ternary");
+                return false;
+            }
+            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));
+                compile_error(ctx, "operands of ternary expression must have the same type, got %s and %s", ty1, ty2);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs)))
+                out = (ast_expression*)ast_ternary_new(ctx, exprs[0], exprs[1], exprs[2]);
+            break;
+
+        case opid2('*', '*'):
+            if (NotSameType(TYPE_FLOAT)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in exponentiation: %s and %s",
+                    ty1, ty2);
+                return false;
+            }
+
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                ast_call *gencall = ast_call_new(parser_ctx(parser), intrin_func(parser->intrin, "pow"));
+                vec_push(gencall->params, exprs[0]);
+                vec_push(gencall->params, exprs[1]);
+                out = (ast_expression*)gencall;
+            }
+            break;
+
+        case opid2('>', '<'):
+            if (NotSameType(TYPE_VECTOR)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in cross product: %s and %s",
+                    ty1, ty2);
+                return false;
+            }
+
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                out = fold_binary(
+                        parser_ctx(parser),
+                        VINSTR_CROSS,
+                        exprs[0],
+                        exprs[1]
+                );
+            }
+
+            break;
+
+        case opid3('<','=','>'): /* -1, 0, or 1 */
+            if (NotSameType(TYPE_FLOAT)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in comparision: %s and %s",
+                    ty1, ty2);
+
+                return false;
+            }
+
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                /* This whole block is NOT fold_binary safe */
+                ast_binary *eq = ast_binary_new(ctx, INSTR_EQ_F, exprs[0], exprs[1]);
+
+                eq->refs = AST_REF_NONE;
+
+                    /* if (lt) { */
+                out = (ast_expression*)ast_ternary_new(ctx,
+                        (ast_expression*)ast_binary_new(ctx, INSTR_LT, exprs[0], exprs[1]),
+                        /* out = -1 */
+                        (ast_expression*)parser->fold->imm_float[2],
+                    /* } else { */
+                        /* if (eq) { */
+                        (ast_expression*)ast_ternary_new(ctx, (ast_expression*)eq,
+                            /* out = 0 */
+                            (ast_expression*)parser->fold->imm_float[0],
+                        /* } else { */
+                            /* out = 1 */
+                            (ast_expression*)parser->fold->imm_float[1]
+                        /* } */
+                        )
+                    /* } */
+                    );
+
+            }
+            break;
+
+        case opid1('>'):
+            generated_op += 1; /* INSTR_GT */
+        case opid1('<'):
+            generated_op += 1; /* INSTR_LT */
+        case opid2('>', '='):
+            generated_op += 1; /* INSTR_GE */
+        case opid2('<', '='):
+            generated_op += INSTR_LE;
+            if (NotSameType(TYPE_FLOAT)) {
+                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
+                              type_name[exprs[0]->vtype],
+                              type_name[exprs[1]->vtype]);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs)))
+                out = fold_binary(ctx, generated_op, exprs[0], exprs[1]);
+            break;
+        case opid2('!', '='):
+            if (exprs[0]->vtype != exprs[1]->vtype) {
+                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
+                              type_name[exprs[0]->vtype],
+                              type_name[exprs[1]->vtype]);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs)))
+                out = fold_binary(ctx, type_ne_instr[exprs[0]->vtype], exprs[0], exprs[1]);
+            break;
+        case opid2('=', '='):
+            if (exprs[0]->vtype != exprs[1]->vtype) {
+                compile_error(ctx, "invalid types used in expression: cannot perform comparison between types %s and %s",
+                              type_name[exprs[0]->vtype],
+                              type_name[exprs[1]->vtype]);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs)))
+                out = fold_binary(ctx, type_eq_instr[exprs[0]->vtype], exprs[0], exprs[1]);
+            break;
+
+        case opid1('='):
+            if (ast_istype(exprs[0], ast_entfield)) {
+                ast_expression *field = ((ast_entfield*)exprs[0])->field;
+                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
+                    exprs[0]->vtype == TYPE_FIELD &&
+                    exprs[0]->next->vtype == TYPE_VECTOR)
+                {
+                    assignop = type_storep_instr[TYPE_VECTOR];
+                }
+                else
+                    assignop = type_storep_instr[exprs[0]->vtype];
+                if (assignop == VINSTR_END || !ast_compare_type(field->next, exprs[1]))
+                {
+                    ast_type_to_string(field->next, ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    if (OPTS_FLAG(ASSIGN_FUNCTION_TYPES) &&
+                        field->next->vtype == TYPE_FUNCTION &&
+                        exprs[1]->vtype == TYPE_FUNCTION)
+                    {
+                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
+                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                    }
+                    else
+                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                }
+            }
+            else
+            {
+                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
+                    exprs[0]->vtype == TYPE_FIELD &&
+                    exprs[0]->next->vtype == TYPE_VECTOR)
+                {
+                    assignop = type_store_instr[TYPE_VECTOR];
+                }
+                else {
+                    assignop = type_store_instr[exprs[0]->vtype];
+                }
+
+                if (assignop == VINSTR_END) {
+                    ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                    ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                    compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                }
+                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) &&
+                        exprs[0]->vtype == TYPE_FUNCTION &&
+                        exprs[1]->vtype == TYPE_FUNCTION)
+                    {
+                        (void)!compile_warning(ctx, WARN_ASSIGN_FUNCTION_TYPES,
+                                               "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                    }
+                    else
+                        compile_error(ctx, "invalid types in assignment: cannot assign %s to %s", ty2, ty1);
+                }
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            /* When we're a vector of part of an entity field we use STOREP */
+            if (ast_istype(exprs[0], ast_member) && ast_istype(((ast_member*)exprs[0])->owner, ast_entfield))
+                assignop = INSTR_STOREP_F;
+            out = (ast_expression*)ast_store_new(ctx, assignop, exprs[0], exprs[1]);
+            break;
+        case opid3('+','+','P'):
+        case opid3('-','-','P'):
+            /* prefix ++ */
+            if (exprs[0]->vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "invalid type for prefix increment: %s", ty1);
+                return false;
+            }
+            if (op->id == opid3('+','+','P'))
+                addop = INSTR_ADD_F;
+            else
+                addop = INSTR_SUB_F;
+            (void)check_write_to(ast_ctx(exprs[0]), exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield)) {
+                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop,
+                                                        exprs[0],
+                                                        (ast_expression*)parser->fold->imm_float[1]);
+            } else {
+                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop,
+                                                        exprs[0],
+                                                        (ast_expression*)parser->fold->imm_float[1]);
+            }
+            break;
+        case opid3('S','+','+'):
+        case opid3('S','-','-'):
+            /* prefix ++ */
+            if (exprs[0]->vtype != TYPE_FLOAT) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "invalid type for suffix increment: %s", ty1);
+                return false;
+            }
+            if (op->id == opid3('S','+','+')) {
+                addop = INSTR_ADD_F;
+                subop = INSTR_SUB_F;
+            } else {
+                addop = INSTR_SUB_F;
+                subop = INSTR_ADD_F;
+            }
+            (void)check_write_to(ast_ctx(exprs[0]), exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield)) {
+                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STOREP_F, addop,
+                                                        exprs[0],
+                                                        (ast_expression*)parser->fold->imm_float[1]);
+            } else {
+                out = (ast_expression*)ast_binstore_new(ctx, INSTR_STORE_F, addop,
+                                                        exprs[0],
+                                                        (ast_expression*)parser->fold->imm_float[1]);
+            }
+            if (!out)
+                return false;
+            out = fold_binary(ctx, subop,
+                              out,
+                              (ast_expression*)parser->fold->imm_float[1]);
+
+            break;
+        case opid2('+','='):
+        case opid2('-','='):
+            if (exprs[0]->vtype != exprs[1]->vtype ||
+                (exprs[0]->vtype != TYPE_VECTOR && exprs[0]->vtype != TYPE_FLOAT) )
+            {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->vtype];
+            else
+                assignop = type_store_instr[exprs[0]->vtype];
+            switch (exprs[0]->vtype) {
+                case TYPE_FLOAT:
+                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
+                                                            (op->id == opid2('+','=') ? INSTR_ADD_F : INSTR_SUB_F),
+                                                            exprs[0], exprs[1]);
+                    break;
+                case TYPE_VECTOR:
+                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
+                                                            (op->id == opid2('+','=') ? INSTR_ADD_V : INSTR_SUB_V),
+                                                            exprs[0], exprs[1]);
+                    break;
+                default:
+                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
+                                  type_name[exprs[0]->vtype],
+                                  type_name[exprs[1]->vtype]);
+                    return false;
+            };
+            break;
+        case opid2('*','='):
+        case opid2('/','='):
+            if (exprs[1]->vtype != TYPE_FLOAT ||
+                !(exprs[0]->vtype == TYPE_FLOAT ||
+                  exprs[0]->vtype == TYPE_VECTOR))
+            {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->vtype];
+            else
+                assignop = type_store_instr[exprs[0]->vtype];
+            switch (exprs[0]->vtype) {
+                case TYPE_FLOAT:
+                    out = (ast_expression*)ast_binstore_new(ctx, assignop,
+                                                            (op->id == opid2('*','=') ? INSTR_MUL_F : INSTR_DIV_F),
+                                                            exprs[0], exprs[1]);
+                    break;
+                case TYPE_VECTOR:
+                    if (op->id == opid2('*','=')) {
+                        out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF,
+                                                                exprs[0], exprs[1]);
+                    } else {
+                        out = fold_binary(ctx, INSTR_DIV_F,
+                                         (ast_expression*)parser->fold->imm_float[1],
+                                         exprs[1]);
+                        if (!out) {
+                            compile_error(ctx, "internal error: failed to generate division");
+                            return false;
+                        }
+                        out = (ast_expression*)ast_binstore_new(ctx, assignop, INSTR_MUL_VF,
+                                                                exprs[0], out);
+                    }
+                    break;
+                default:
+                    compile_error(ctx, "invalid types used in expression: cannot add or subtract type %s and %s",
+                                  type_name[exprs[0]->vtype],
+                                  type_name[exprs[1]->vtype]);
+                    return false;
+            };
+            break;
+        case opid2('&','='):
+        case opid2('|','='):
+        case opid2('^','='):
+            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            (void)check_write_to(ctx, exprs[0]);
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->vtype];
+            else
+                assignop = type_store_instr[exprs[0]->vtype];
+            if (exprs[0]->vtype == TYPE_FLOAT)
+                out = (ast_expression*)ast_binstore_new(ctx, assignop,
+                                                        (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR),
+                                                        exprs[0], exprs[1]);
+            else
+                out = (ast_expression*)ast_binstore_new(ctx, assignop,
+                                                        (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V),
+                                                        exprs[0], exprs[1]);
+            break;
+        case opid3('&','~','='):
+            /* This is like: a &= ~(b);
+             * But QC has no bitwise-not, so we implement it as
+             * a -= a & (b);
+             */
+            if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                ast_type_to_string(exprs[1], ty2, sizeof(ty2));
+                compile_error(ctx, "invalid types used in expression: %s and %s",
+                              ty1, ty2);
+                return false;
+            }
+            if (ast_istype(exprs[0], ast_entfield))
+                assignop = type_storep_instr[exprs[0]->vtype];
+            else
+                assignop = type_store_instr[exprs[0]->vtype];
+            if (exprs[0]->vtype == TYPE_FLOAT)
+                out = fold_binary(ctx, INSTR_BITAND, exprs[0], exprs[1]);
+            else
+                out = fold_binary(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]);
+            if (!out)
+                return false;
+            (void)check_write_to(ctx, exprs[0]);
+            if (exprs[0]->vtype == TYPE_FLOAT)
+                asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out);
+            else
+                asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out);
+            asbinstore->keep_dest = true;
+            out = (ast_expression*)asbinstore;
+            break;
+
+        case opid3('l', 'e', 'n'):
+            if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1);
+                return false;
+            }
+            /* strings must be const, arrays are statically sized */
+            if (exprs[0]->vtype == TYPE_STRING &&
+                !(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST))
+            {
+                compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression");
+                return false;
+            }
+            out = fold_op(parser->fold, op, exprs);
+            break;
+
+        case opid2('~', 'P'):
+            if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1);
+                return false;
+            }
+            if (!(out = fold_op(parser->fold, op, exprs))) {
+                if (exprs[0]->vtype == TYPE_FLOAT) {
+                    out = fold_binary(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]);
+                } else {
+                    out = fold_binary(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]);
+                }
+            }
+            break;
+    }
+#undef NotSameType
+    if (!out) {
+        compile_error(ctx, "failed to apply operator %s", op->op);
+        return false;
+    }
+
+    vec_push(sy->out, syexp(ctx, out));
+    return true;
+}
+
+static bool parser_close_call(parser_t *parser, shunt *sy)
+{
+    /* was a function call */
+    ast_expression *fun;
+    ast_value      *funval = NULL;
+    ast_call       *call;
+
+    size_t          fid;
+    size_t          paramcount, i;
+    bool            fold = true;
+
+    fid = vec_last(sy->ops).off;
+    vec_shrinkby(sy->ops, 1);
+
+    /* out[fid] is the function
+     * everything above is parameters...
+     */
+    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) < 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;
+    }
+
+    /*
+     * TODO handle this at the intrinsic level with an ast_intrinsic
+     * node and codegen.
+     */
+    if ((fun = sy->out[fid].out) == intrin_debug_typestring(parser->intrin)) {
+        char ty[1024];
+        if (fid+2 != vec_size(sy->out) ||
+            vec_last(sy->out).block)
+        {
+            parseerror(parser, "intrinsic __builtin_debug_typestring requires exactly 1 parameter");
+            return false;
+        }
+        ast_type_to_string(vec_last(sy->out).out, ty, sizeof(ty));
+        ast_unref(vec_last(sy->out).out);
+        sy->out[fid] = syexp(ast_ctx(vec_last(sy->out).out),
+                             (ast_expression*)fold_constgen_string(parser->fold, ty, false));
+        vec_shrinkby(sy->out, 1);
+        return true;
+    }
+
+    /*
+     * Now we need to determine if the function that is being called is
+     * an intrinsic so we can evaluate if the arguments to it are constant
+     * and than fruitfully fold them.
+     */
+#define fold_can_1(X)  \
+    (ast_istype(((ast_expression*)(X)), ast_value) && (X)->hasvalue && ((X)->cvq == CV_CONST) && \
+                ((ast_expression*)(X))->vtype != TYPE_FUNCTION)
+
+    if (fid + 1 < vec_size(sy->out))
+        ++paramcount;
+
+    for (i = 0; i < paramcount; ++i) {
+        if (!fold_can_1((ast_value*)sy->out[fid + 1 + i].out)) {
+            fold = false;
+            break;
+        }
+    }
+
+    /*
+     * All is well which ends well, if we make it into here we can ignore the
+     * intrinsic call and just evaluate it i.e constant fold it.
+     */
+    if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->intrinsic) {
+        ast_expression **exprs  = NULL;
+        ast_expression *foldval = NULL;
+
+        for (i = 0; i < paramcount; i++)
+            vec_push(exprs, sy->out[fid+1 + i].out);
+
+        if (!(foldval = intrin_fold(parser->intrin, (ast_value*)fun, exprs))) {
+            vec_free(exprs);
+            goto fold_leave;
+        }
+
+        /*
+         * Blub: what sorts of unreffing and resizing of
+         * sy->out should I be doing here?
+         */
+        sy->out[fid] = syexp(foldval->node.context, foldval);
+        vec_shrinkby(sy->out, paramcount);
+        vec_free(exprs);
+
+        return true;
+    }
+
+    fold_leave:
+    call = ast_call_new(sy->ops[vec_size(sy->ops)].ctx, fun);
+
+    if (!call)
+        return false;
+
+    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, parser->function->vtype->expression.varparam);
+    if (parser->max_param_count < paramcount)
+        parser->max_param_count = paramcount;
+
+    if (ast_istype(fun, ast_value)) {
+        funval = (ast_value*)fun;
+        if ((fun->flags & AST_FLAG_VARIADIC) &&
+            !(/*funval->cvq == CV_CONST && */ funval->hasvalue && funval->constval.vfunc->builtin))
+        {
+            call->va_count = (ast_expression*)fold_constgen_float(parser->fold, (qcfloat_t)paramcount, false);
+        }
+    }
+
+    /* overwrite fid, the function, with a call */
+    sy->out[fid] = syexp(call->expression.node.context, (ast_expression*)call);
+
+    if (fun->vtype != TYPE_FUNCTION) {
+        parseerror(parser, "not a function (%s)", type_name[fun->vtype]);
+        return false;
+    }
+
+    if (!fun->next) {
+        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->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->params) != paramcount &&
+            !((fun->flags & AST_FLAG_VARIADIC) &&
+              vec_size(fun->params) < paramcount))
+        {
+            const char *fewmany = (vec_size(fun->params) > paramcount) ? "few" : "many";
+            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->params), (int)paramcount,
+                                     fval->name, ast_ctx(fun).file, (int)ast_ctx(fun).line);
+            else
+                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->params), (int)paramcount,
+                                     ast_ctx(fun).file, (int)ast_ctx(fun).line);
+        }
+    }
+
+    return true;
+}
+
+static bool parser_close_paren(parser_t *parser, shunt *sy)
+{
+    if (!vec_size(sy->ops)) {
+        parseerror(parser, "unmatched closing paren");
+        return false;
+    }
+
+    while (vec_size(sy->ops)) {
+        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);
+                break;
+            }
+            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;
+            }
+            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;
+    }
+    return true;
+}
+
+static void parser_reclassify_token(parser_t *parser)
+{
+    size_t i;
+    if (parser->tok >= TOKEN_START)
+        return;
+    for (i = 0; i < operator_count; ++i) {
+        if (!strcmp(parser_tokval(parser), operators[i].op)) {
+            parser->tok = TOKEN_OPERATOR;
+            return;
+        }
+    }
+}
+
+static ast_expression* parse_vararg_do(parser_t *parser)
+{
+    ast_expression *idx, *out;
+    ast_value      *typevar;
+    ast_value      *funtype = parser->function->vtype;
+    lex_ctx_t         ctx     = parser_ctx(parser);
+
+    if (!parser->function->varargs) {
+        parseerror(parser, "function has no variable argument list");
+        return NULL;
+    }
+
+    if (!parser_next(parser) || parser->tok != '(') {
+        parseerror(parser, "expected parameter index and type in parenthesis");
+        return NULL;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "error parsing parameter index");
+        return NULL;
+    }
+
+    idx = parse_expression_leave(parser, true, false, false);
+    if (!idx)
+        return NULL;
+
+    if (parser->tok != ',') {
+        if (parser->tok != ')') {
+            ast_unref(idx);
+            parseerror(parser, "expected comma after parameter index");
+            return NULL;
+        }
+        /* vararg piping: ...(start) */
+        out = (ast_expression*)ast_argpipe_new(ctx, idx);
+        return out;
+    }
+
+    if (!parser_next(parser) || (parser->tok != TOKEN_IDENT && parser->tok != TOKEN_TYPENAME)) {
+        ast_unref(idx);
+        parseerror(parser, "expected typename for vararg");
+        return NULL;
+    }
+
+    typevar = parse_typename(parser, NULL, NULL, NULL);
+    if (!typevar) {
+        ast_unref(idx);
+        return NULL;
+    }
+
+    if (parser->tok != ')') {
+        ast_unref(idx);
+        ast_delete(typevar);
+        parseerror(parser, "expected closing paren");
+        return NULL;
+    }
+
+    if (funtype->expression.varparam &&
+        !ast_compare_type((ast_expression*)typevar, (ast_expression*)funtype->expression.varparam))
+    {
+        char ty1[1024];
+        char ty2[1024];
+        ast_type_to_string((ast_expression*)typevar, ty1, sizeof(ty1));
+        ast_type_to_string((ast_expression*)funtype->expression.varparam, ty2, sizeof(ty2));
+        compile_error(ast_ctx(typevar),
+                      "function was declared to take varargs of type `%s`, requested type is: %s",
+                      ty2, ty1);
+    }
+
+    out = (ast_expression*)ast_array_index_new(ctx, (ast_expression*)(parser->function->varargs), idx);
+    ast_type_adopt(out, typevar);
+    ast_delete(typevar);
+    return out;
+}
+
+static ast_expression* parse_vararg(parser_t *parser)
+{
+    bool           old_noops = parser->lex->flags.noops;
+
+    ast_expression *out;
+
+    parser->lex->flags.noops = true;
+    out = parse_vararg_do(parser);
+
+    parser->lex->flags.noops = old_noops;
+    return out;
+}
+
+/* not to be exposed */
+bool ftepp_predef_exists(const char *name);
+static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
+{
+    if (OPTS_FLAG(TRANSLATABLE_STRINGS) &&
+        parser->tok == TOKEN_IDENT &&
+        !strcmp(parser_tokval(parser), "_"))
+    {
+        /* 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");
+            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 = (ast_value*)fold_constgen_string(parser->fold, 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");
+            return false;
+        }
+        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_expression *val = fold_constgen_float(parser->fold, (parser_token(parser)->constval.f), false);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_INTCONST || parser->tok == TOKEN_CHARCONST) {
+        ast_expression *val = fold_constgen_float(parser->fold, (qcfloat_t)(parser_token(parser)->constval.i), false);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_STRINGCONST) {
+        ast_expression *val = fold_constgen_string(parser->fold, parser_tokval(parser), false);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), val));
+        return true;
+    }
+    else if (parser->tok == TOKEN_VECTORCONST) {
+        ast_expression *val = fold_constgen_vector(parser->fold, parser_token(parser)->constval.v);
+        if (!val)
+            return false;
+        vec_push(sy->out, syexp(parser_ctx(parser), 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('.'))
+        {
+            /* When adding more intrinsics, fix the above condition */
+            prev = NULL;
+        }
+        if (prev && prev->vtype == TYPE_VECTOR && ctoken[0] >= 'x' && ctoken[0] <= 'z' && !ctoken[1])
+        {
+            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 && !strcmp(parser_tokval(parser), "__FUNC__"))
+            var = (ast_expression*)fold_constgen_string(parser->fold, parser->function->name, false);
+        if (!var) {
+            /*
+             * now we try for the real intrinsic hashtable. If the string
+             * begins with __builtin, we simply skip past it, otherwise we
+             * use the identifier as is.
+             */
+            if (!strncmp(parser_tokval(parser), "__builtin_", 10)) {
+                var = intrin_func(parser->intrin, parser_tokval(parser));
+            }
+
+            /*
+             * Try it again, intrin_func deals with the alias method as well
+             * the first one masks for __builtin though, we emit warning here.
+             */
+            if (!var) {
+                if ((var = intrin_func(parser->intrin, parser_tokval(parser)))) {
+                    (void)!compile_warning(
+                        parser_ctx(parser),
+                        WARN_BUILTINS,
+                        "using implicitly defined builtin `__builtin_%s' for `%s'",
+                        parser_tokval(parser),
+                        parser_tokval(parser)
+                    );
+                }
+            }
+
+
+            if (!var) {
+                /*
+                 * 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) && ftepp_predef_exists(parser_tokval(parser))) {
+                    parseerror(parser, "unexpected identifier: %s (use -fftepp-predef to enable pre-defined macros)", parser_tokval(parser));
+                    return false;
+                }
+
+                parseerror(parser, "unexpected identifier: %s", parser_tokval(parser));
+                return false;
+            }
+        }
+        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++;
+            }
+        }
+        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_parenthesis = 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 `%s`", parser_tokval(parser));
+            goto onerr;
+        }
+
+        if (parser->tok == TOKEN_OPERATOR)
+        {
+            /* classify the operator */
+            const oper_info *op;
+            const oper_info *olast = NULL;
+            size_t o;
+            for (o = 0; o < operator_count; ++o) {
+                if (((!(operators[o].flags & OP_PREFIX) == !!wantop)) &&
+                    /* !(operators[o].flags & OP_SUFFIX) && / * remove this */
+                    !strcmp(parser_tokval(parser), operators[o].op))
+                {
+                    break;
+                }
+            }
+            if (o == operator_count) {
+                compile_error(parser_ctx(parser), "unexpected operator: %s", parser_tokval(parser));
+                goto onerr;
+            }
+            /* found an operator */
+            op = &operators[o];
+
+            /* when declaring variables, a comma starts a new variable */
+            if (op->id == opid1(',') && !vec_size(sy.paren) && stopatcomma) {
+                /* fixup the token */
+                parser->tok = ',';
+                break;
+            }
+
+            /* a colon without a pervious question mark cannot be a ternary */
+            if (!ternaries && op->id == opid2(':','?')) {
+                parser->tok = ':';
+                break;
+            }
+
+            if (op->id == opid1(',')) {
+                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).isparen)
+                olast = &operators[vec_last(sy.ops).etype-1];
+
+            /* first only apply higher precedences, assoc_left+equal comes after we warn about precedence rules */
+            while (olast && op->prec < olast->prec)
+            {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
+                    olast = &operators[vec_last(sy.ops).etype-1];
+                else
+                    olast = NULL;
+            }
+
+#define IsAssignOp(x) (\
+                (x) == opid1('=') || \
+                (x) == opid2('+','=') || \
+                (x) == opid2('-','=') || \
+                (x) == opid2('*','=') || \
+                (x) == opid2('/','=') || \
+                (x) == opid2('%','=') || \
+                (x) == opid2('&','=') || \
+                (x) == opid2('|','=') || \
+                (x) == opid3('&','~','=') \
+                )
+            if (warn_parenthesis) {
+                if ( (olast && IsAssignOp(olast->id) && (op->id == opid2('&','&') || op->id == opid2('|','|'))) ||
+                     (olast && IsAssignOp(op->id) && (olast->id == opid2('&','&') || olast->id == opid2('|','|'))) ||
+                     (truthvalue && !vec_size(sy.paren) && IsAssignOp(op->id))
+                   )
+                {
+                    (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around assignment used as truth value");
+                    warn_parenthesis = false;
+                }
+
+                if (olast && olast->id != op->id) {
+                    if ((op->id    == opid1('&') || op->id    == opid1('|') || op->id    == opid1('^')) &&
+                        (olast->id == opid1('&') || olast->id == opid1('|') || olast->id == opid1('^')))
+                    {
+                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around bitwise operations");
+                        warn_parenthesis = false;
+                    }
+                    else if ((op->id    == opid2('&','&') || op->id    == opid2('|','|')) &&
+                             (olast->id == opid2('&','&') || olast->id == opid2('|','|')))
+                    {
+                        (void)!parsewarning(parser, WARN_PARENTHESIS, "suggesting parenthesis around logical operations");
+                        warn_parenthesis = false;
+                    }
+                }
+            }
+
+            while (olast && (
+                    (op->prec < olast->prec) ||
+                    (op->assoc == ASSOC_LEFT && op->prec <= olast->prec) ) )
+            {
+                if (!parser_sy_apply_operator(parser, &sy))
+                    goto onerr;
+                if (vec_size(sy.ops) && !vec_last(sy.ops).isparen)
+                    olast = &operators[vec_last(sy.ops).etype-1];
+                else
+                    olast = NULL;
+            }
+
+            if (op->id == opid1('(')) {
+                if (wantop) {
+                    size_t sycount = vec_size(sy.out);
+                    /* we expected an operator, this is the function-call operator */
+                    vec_push(sy.paren, PAREN_FUNC);
+                    vec_push(sy.ops, syparen(parser_ctx(parser), sycount-1));
+                    vec_push(sy.argc, 0);
+                } else {
+                    vec_push(sy.paren, PAREN_EXPR);
+                    vec_push(sy.ops, syparen(parser_ctx(parser), 0));
+                }
+                wantop = false;
+            } else if (op->id == opid1('[')) {
+                if (!wantop) {
+                    parseerror(parser, "unexpected array subscript");
+                    goto onerr;
+                }
+                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), 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), 0));
+                wantop = false;
+                ++ternaries;
+                vec_push(sy.paren, PAREN_TERNARY1);
+            } else if (op->id == opid2(':','?')) {
+                if (!vec_size(sy.paren)) {
+                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
+                    goto onerr;
+                }
+                if (vec_last(sy.paren) != PAREN_TERNARY1) {
+                    parseerror(parser, "unexpected colon outside ternary expression (missing parenthesis?)");
+                    goto onerr;
+                }
+                if (!parser_close_paren(parser, &sy))
+                    goto onerr;
+                vec_push(sy.ops, syop(parser_ctx(parser), op));
+                wantop = false;
+                --ternaries;
+            } else {
+                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;
+            wantop = true;
+        }
+        else {
+            /* in this case we might want to allow constant string concatenation */
+            bool concatenated = false;
+            if (parser->tok == TOKEN_STRINGCONST && vec_size(sy.out)) {
+                ast_expression *lexpr = vec_last(sy.out).out;
+                if (ast_istype(lexpr, ast_value)) {
+                    ast_value *last = (ast_value*)lexpr;
+                    if (last->isimm == true && last->cvq == CV_CONST &&
+                        last->hasvalue && last->expression.vtype == TYPE_STRING)
+                    {
+                        char *newstr = NULL;
+                        util_asprintf(&newstr, "%s%s", last->constval.vstring, parser_tokval(parser));
+                        vec_last(sy.out).out = (ast_expression*)fold_constgen_string(parser->fold, newstr, false);
+                        mem_d(newstr);
+                        concatenated = true;
+                    }
+                }
+            }
+            if (!concatenated) {
+                parseerror(parser, "expected operator or end of statement");
+                goto onerr;
+            }
+        }
+
+        if (!parser_next(parser)) {
+            goto onerr;
+        }
+        if (parser->tok == ';' ||
+            ((!vec_size(sy.paren) || (vec_size(sy.paren) == 1 && vec_last(sy.paren) == PAREN_TERNARY2)) &&
+            (parser->tok == ']' || parser->tok == ')' || parser->tok == '}')))
+        {
+            break;
+        }
+    }
+
+    while (vec_size(sy.ops)) {
+        if (!parser_sy_apply_operator(parser, &sy))
+            goto onerr;
+    }
+
+    parser->lex->flags.noops = true;
+    if (vec_size(sy.out) != 1) {
+        parseerror(parser, "expression expected");
+        expr = NULL;
+    } else
+        expr = sy.out[0].out;
+    vec_free(sy.out);
+    vec_free(sy.ops);
+    if (vec_size(sy.paren)) {
+        parseerror(parser, "internal error: vec_size(sy.paren) = %lu", (unsigned long)vec_size(sy.paren));
+        return NULL;
+    }
+    vec_free(sy.paren);
+    vec_free(sy.argc);
+    return expr;
+
+onerr:
+    parser->lex->flags.noops = true;
+    for (i = 0; i < vec_size(sy.out); ++i) {
+        if (sy.out[i].out)
+            ast_unref(sy.out[i].out);
+    }
+    vec_free(sy.out);
+    vec_free(sy.ops);
+    vec_free(sy.paren);
+    vec_free(sy.argc);
+    return NULL;
+}
+
+static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool with_labels)
+{
+    ast_expression *e = parse_expression_leave(parser, stopatcomma, false, with_labels);
+    if (!e)
+        return NULL;
+    if (parser->tok != ';') {
+        parseerror(parser, "semicolon expected after expression");
+        ast_unref(e);
+        return NULL;
+    }
+    if (!parser_next(parser)) {
+        ast_unref(e);
+        return NULL;
+    }
+    return e;
+}
+
+static void parser_enterblock(parser_t *parser)
+{
+    vec_push(parser->variables, util_htnew(PARSER_HT_SIZE));
+    vec_push(parser->_blocklocals, vec_size(parser->_locals));
+    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
+    vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs));
+    vec_push(parser->_block_ctx, parser_ctx(parser));
+}
+
+static bool parser_leaveblock(parser_t *parser)
+{
+    bool   rv = true;
+    size_t locals, typedefs;
+
+    if (vec_size(parser->variables) <= PARSER_HT_LOCALS) {
+        parseerror(parser, "internal error: parser_leaveblock with no block");
+        return false;
+    }
+
+    util_htdel(vec_last(parser->variables));
+
+    vec_pop(parser->variables);
+    if (!vec_size(parser->_blocklocals)) {
+        parseerror(parser, "internal error: parser_leaveblock with no block (2)");
+        return false;
+    }
+
+    locals = vec_last(parser->_blocklocals);
+    vec_pop(parser->_blocklocals);
+    while (vec_size(parser->_locals) != locals) {
+        ast_expression *e = vec_last(parser->_locals);
+        ast_value      *v = (ast_value*)e;
+        vec_pop(parser->_locals);
+        if (ast_istype(e, ast_value) && !v->uses) {
+            if (compile_warning(ast_ctx(v), WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->name))
+                rv = false;
+        }
+    }
+
+    typedefs = vec_last(parser->_blocktypedefs);
+    while (vec_size(parser->_typedefs) != typedefs) {
+        ast_delete(vec_last(parser->_typedefs));
+        vec_pop(parser->_typedefs);
+    }
+    util_htdel(vec_last(parser->typedefs));
+    vec_pop(parser->typedefs);
+
+    vec_pop(parser->_block_ctx);
+
+    return rv;
+}
+
+static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e)
+{
+    vec_push(parser->_locals, e);
+    util_htset(vec_last(parser->variables), name, (void*)e);
+}
+
+static void parser_addglobal(parser_t *parser, const char *name, ast_expression *e)
+{
+    vec_push(parser->globals, e);
+    util_htset(parser->htglobals, name, e);
+}
+
+static ast_expression* process_condition(parser_t *parser, ast_expression *cond, bool *_ifnot)
+{
+    bool       ifnot = false;
+    ast_unary *unary;
+    ast_expression *prev;
+
+    if (cond->vtype == TYPE_VOID || cond->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->vtype == TYPE_STRING)
+    {
+        prev = cond;
+        cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_S, cond);
+        if (!cond) {
+            ast_unref(prev);
+            parseerror(parser, "internal error: failed to process condition");
+            return NULL;
+        }
+        ifnot = !ifnot;
+    }
+    else if (OPTS_FLAG(CORRECT_LOGIC) && cond->vtype == TYPE_VECTOR)
+    {
+        /* vector types need to be cast to true booleans */
+        ast_binary *bin = (ast_binary*)cond;
+        if (!OPTS_FLAG(PERL_LOGIC) || !ast_istype(cond, ast_binary) || !(bin->op == INSTR_AND || bin->op == INSTR_OR))
+        {
+            /* in perl-logic, AND and OR take care of the -fcorrect-logic */
+            prev = cond;
+            cond = (ast_expression*)ast_unary_new(ast_ctx(cond), INSTR_NOT_V, cond);
+            if (!cond) {
+                ast_unref(prev);
+                parseerror(parser, "internal error: failed to process condition");
+                return NULL;
+            }
+            ifnot = !ifnot;
+        }
+    }
+
+    unary = (ast_unary*)cond;
+    /* ast_istype dereferences cond, should test here for safety */
+    while (cond && ast_istype(cond, ast_unary) && unary->op == INSTR_NOT_F)
+    {
+        cond = unary->operand;
+        unary->operand = NULL;
+        ast_delete(unary);
+        ifnot = !ifnot;
+        unary = (ast_unary*)cond;
+    }
+
+    if (!cond)
+        parseerror(parser, "internal error: failed to process condition");
+
+    if (ifnot) *_ifnot = !*_ifnot;
+    return cond;
+}
+
+static bool parse_if(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_ifthen *ifthen;
+    ast_expression *cond, *ontrue = NULL, *onfalse = NULL;
+    bool ifnot = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    /* skip the 'if', parse an optional 'not' and check for an opening paren */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected condition or 'not'");
+        return false;
+    }
+    if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "not")) {
+        ifnot = true;
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected condition in parenthesis");
+            return false;
+        }
+    }
+    if (parser->tok != '(') {
+        parseerror(parser, "expected 'if' condition in parenthesis");
+        return false;
+    }
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'if' condition after opening paren");
+        return false;
+    }
+    /* parse the condition */
+    cond = parse_expression_leave(parser, false, true, false);
+    if (!cond)
+        return false;
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'if' condition");
+        ast_unref(cond);
+        return false;
+    }
+    /* parse into the 'then' branch */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected statement for on-true branch of 'if'");
+        ast_unref(cond);
+        return false;
+    }
+    if (!parse_statement_or_block(parser, &ontrue)) {
+        ast_unref(cond);
+        return false;
+    }
+    if (!ontrue)
+        ontrue = (ast_expression*)ast_block_new(parser_ctx(parser));
+    /* check for an else */
+    if (!strcmp(parser_tokval(parser), "else")) {
+        /* parse into the 'else' branch */
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected on-false branch after 'else'");
+            ast_delete(ontrue);
+            ast_unref(cond);
+            return false;
+        }
+        if (!parse_statement_or_block(parser, &onfalse)) {
+            ast_delete(ontrue);
+            ast_unref(cond);
+            return false;
+        }
+    }
+
+    cond = process_condition(parser, cond, &ifnot);
+    if (!cond) {
+        if (ontrue)  ast_delete(ontrue);
+        if (onfalse) ast_delete(onfalse);
+        return false;
+    }
+
+    if (ifnot)
+        ifthen = ast_ifthen_new(ctx, cond, onfalse, ontrue);
+    else
+        ifthen = ast_ifthen_new(ctx, cond, ontrue, onfalse);
+    *out = (ast_expression*)ifthen;
+    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;
+
+    bool ifnot = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    /* 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, true, false);
+    if (!cond)
+        return false;
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'while' condition");
+        ast_unref(cond);
+        return false;
+    }
+    /* parse into the 'then' branch */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected while-loop body");
+        ast_unref(cond);
+        return false;
+    }
+    if (!parse_statement_or_block(parser, &ontrue)) {
+        ast_unref(cond);
+        return false;
+    }
+
+    cond = process_condition(parser, cond, &ifnot);
+    if (!cond) {
+        ast_unref(ontrue);
+        return false;
+    }
+    aloop = ast_loop_new(ctx, NULL, cond, ifnot, NULL, false, NULL, ontrue);
+    *out = (ast_expression*)aloop;
+    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;
+        /*
+         * Test for NULL otherwise ast_delete dereferences null pointer
+         * and boom.
+         */
+        if (*out)
+            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;
+
+    bool ifnot = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    if (!parse_statement_or_block(parser, &ontrue))
+        return false;
+
+    /* expect the "while" */
+    if (parser->tok != TOKEN_KEYWORD ||
+        strcmp(parser_tokval(parser), "while"))
+    {
+        parseerror(parser, "expected 'while' and condition");
+        ast_delete(ontrue);
+        return false;
+    }
+
+    /* skip the 'while' and check for opening paren */
+    if (!parser_next(parser) || parser->tok != '(') {
+        parseerror(parser, "expected 'while' condition in parenthesis");
+        ast_delete(ontrue);
+        return false;
+    }
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'while' condition after opening paren");
+        ast_delete(ontrue);
+        return false;
+    }
+    /* parse the condition */
+    cond = parse_expression_leave(parser, false, true, false);
+    if (!cond)
+        return false;
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'while' condition");
+        ast_delete(ontrue);
+        ast_unref(cond);
+        return false;
+    }
+    /* parse on */
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "expected semicolon after condition");
+        ast_delete(ontrue);
+        ast_unref(cond);
+        return false;
+    }
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error");
+        ast_delete(ontrue);
+        ast_unref(cond);
+        return false;
+    }
+
+    cond = process_condition(parser, cond, &ifnot);
+    if (!cond) {
+        ast_delete(ontrue);
+        return false;
+    }
+    aloop = ast_loop_new(ctx, NULL, NULL, false, cond, ifnot, NULL, ontrue);
+    *out = (ast_expression*)aloop;
+    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;
+    ast_value      *typevar;
+
+    bool ifnot  = false;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    parser_enterblock(parser);
+
+    initexpr  = NULL;
+    cond      = NULL;
+    increment = NULL;
+    ontrue    = NULL;
+
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected 'for' initializer after opening paren");
+        goto onerr;
+    }
+
+    typevar = NULL;
+    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, true, CV_VAR, typevar, false, false, 0, NULL))
+            goto onerr;
+    }
+    else if (parser->tok != ';')
+    {
+        initexpr = parse_expression_leave(parser, false, false, false);
+        if (!initexpr)
+            goto onerr;
+
+        /* move on to condition */
+        if (parser->tok != ';') {
+            parseerror(parser, "expected semicolon after for-loop initializer");
+            goto onerr;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected for-loop condition");
+            goto onerr;
+        }
+    }
+
+    /* parse the condition */
+    if (parser->tok != ';') {
+        cond = parse_expression_leave(parser, false, true, false);
+        if (!cond)
+            goto onerr;
+    }
+
+    /* move on to incrementor */
+    if (parser->tok != ';') {
+        parseerror(parser, "expected semicolon after for-loop initializer");
+        goto onerr;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected for-loop condition");
+        goto onerr;
+    }
+
+    /* parse the incrementor */
+    if (parser->tok != ')') {
+        lex_ctx_t condctx = parser_ctx(parser);
+        increment = parse_expression_leave(parser, false, false, false);
+        if (!increment)
+            goto onerr;
+        if (!ast_side_effects(increment)) {
+            if (compile_warning(condctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+                goto onerr;
+        }
+    }
+
+    /* closing paren */
+    if (parser->tok != ')') {
+        parseerror(parser, "expected closing paren after 'for-loop' incrementor");
+        goto onerr;
+    }
+    /* parse into the 'then' branch */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected for-loop body");
+        goto onerr;
+    }
+    if (!parse_statement_or_block(parser, &ontrue))
+        goto onerr;
+
+    if (cond) {
+        cond = process_condition(parser, cond, &ifnot);
+        if (!cond)
+            goto onerr;
+    }
+    aloop = ast_loop_new(ctx, initexpr, cond, ifnot, NULL, false, increment, ontrue);
+    *out = (ast_expression*)aloop;
+
+    if (!parser_leaveblock(parser)) {
+        ast_delete(aloop);
+        return false;
+    }
+    return true;
+onerr:
+    if (initexpr)  ast_unref(initexpr);
+    if (cond)      ast_unref(cond);
+    if (increment) ast_unref(increment);
+    (void)!parser_leaveblock(parser);
+    return false;
+}
+
+static bool parse_return(parser_t *parser, ast_block *block, ast_expression **out)
+{
+    ast_expression *exp      = NULL;
+    ast_expression *var      = NULL;
+    ast_return     *ret      = NULL;
+    ast_value      *retval   = parser->function->return_value;
+    ast_value      *expected = parser->function->vtype;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected return expression");
+        return false;
+    }
+
+    /* return assignments */
+    if (parser->tok == '=') {
+        if (!OPTS_FLAG(RETURN_ASSIGNMENTS)) {
+            parseerror(parser, "return assignments not activated, try using -freturn-assigments");
+            return false;
+        }
+
+        if (type_store_instr[expected->expression.next->vtype] == VINSTR_END) {
+            char ty1[1024];
+            ast_type_to_string(expected->expression.next, ty1, sizeof(ty1));
+            parseerror(parser, "invalid return type: `%s'", ty1);
+            return false;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected return assignment expression");
+            return false;
+        }
+
+        if (!(exp = parse_expression_leave(parser, false, false, false)))
+            return false;
+
+        /* prepare the return value */
+        if (!retval) {
+            retval = ast_value_new(ctx, "#LOCAL_RETURN", TYPE_VOID);
+            ast_type_adopt(retval, expected->expression.next);
+            parser->function->return_value = retval;
+        }
+
+        if (!ast_compare_type(exp, (ast_expression*)retval)) {
+            char ty1[1024], ty2[1024];
+            ast_type_to_string(exp, ty1, sizeof(ty1));
+            ast_type_to_string(&retval->expression, ty2, sizeof(ty2));
+            parseerror(parser, "invalid type for return value: `%s', expected `%s'", ty1, ty2);
+        }
+
+        /* store to 'return' local variable */
+        var = (ast_expression*)ast_store_new(
+            ctx,
+            type_store_instr[expected->expression.next->vtype],
+            (ast_expression*)retval, exp);
+
+        if (!var) {
+            ast_unref(exp);
+            return false;
+        }
+
+        if (parser->tok != ';')
+            parseerror(parser, "missing semicolon after return assignment");
+        else if (!parser_next(parser))
+            parseerror(parser, "parse error after return assignment");
+
+        *out = var;
+        return true;
+    }
+
+    if (parser->tok != ';') {
+        exp = parse_expression(parser, false, false);
+        if (!exp)
+            return false;
+
+        if (exp->vtype != TYPE_NIL &&
+            exp->vtype != ((ast_expression*)expected)->next->vtype)
+        {
+            parseerror(parser, "return with invalid expression");
+        }
+
+        ret = ast_return_new(ctx, exp);
+        if (!ret) {
+            ast_unref(exp);
+            return false;
+        }
+    } else {
+        if (!parser_next(parser))
+            parseerror(parser, "parse error");
+
+        if (!retval && expected->expression.next->vtype != TYPE_VOID)
+        {
+            (void)!parsewarning(parser, WARN_MISSING_RETURN_VALUES, "return without value");
+        }
+        ret = ast_return_new(ctx, (ast_expression*)retval);
+    }
+    *out = (ast_expression*)ret;
+    return true;
+}
+
+static bool parse_break_continue(parser_t *parser, ast_block *block, ast_expression **out, bool is_continue)
+{
+    size_t       i;
+    unsigned int levels = 0;
+    lex_ctx_t      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 (!vec_size(loops)) {
+        if (is_continue)
+            parseerror(parser, "`continue` can only be used inside loops");
+        else
+            parseerror(parser, "`break` can only be used inside loops or switches");
+    }
+
+    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;
+    }
+
+    if (!parser_next(parser))
+        parseerror(parser, "parse error");
+
+    *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
+ */
+typedef struct {
+    const char *name;
+    size_t      flag;
+} attribute_t;
+
+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_attrib   = false;
+    bool had_static   = false;
+    uint32_t flags    = 0;
+
+    static attribute_t attributes[] = {
+        { "noreturn",   AST_FLAG_NORETURN   },
+        { "inline",     AST_FLAG_INLINE     },
+        { "eraseable",  AST_FLAG_ERASEABLE  },
+        { "accumulate", AST_FLAG_ACCUMULATE },
+        { "last",       AST_FLAG_FINAL_DECL }
+    };
+
+   *cvq = CV_NONE;
+
+    for (;;) {
+        size_t i;
+        if (parser->tok == TOKEN_ATTRIBUTE_OPEN) {
+            had_attrib = true;
+            /* parse an attribute */
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected attribute after `[[`");
+                *cvq = CV_WRONG;
+                return false;
+            }
+
+            for (i = 0; i < GMQCC_ARRAY_COUNT(attributes); i++) {
+                if (!strcmp(parser_tokval(parser), attributes[i].name)) {
+                    flags |= attributes[i].flag;
+                    if (!parser_next(parser) || parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                        parseerror(parser, "`%s` attribute has no parameters, expected `]]`",
+                            attributes[i].name);
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                    break;
+                }
+            }
+
+            if (i != GMQCC_ARRAY_COUNT(attributes))
+                goto leave;
+
+
+            if (!strcmp(parser_tokval(parser), "noref")) {
+                had_noref = true;
+                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), "alias") && !(flags & AST_FLAG_ALIAS)) {
+                flags   |= AST_FLAG_ALIAS;
+                *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, "`alias` 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, "`alias` attribute expected `)` after parameter");
+                        goto argerr;
+                    }
+
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "parse error in attribute");
+                        goto argerr;
+                    }
+                }
+
+                if (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    parseerror(parser, "`alias` attribute expected `]]`");
+                    goto argerr;
+                }
+            }
+            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 if (!strcmp(parser_tokval(parser), "coverage") && !(flags & AST_FLAG_COVERAGE)) {
+                flags |= AST_FLAG_COVERAGE;
+                if (!parser_next(parser)) {
+                    error_in_coverage:
+                    parseerror(parser, "parse error in coverage attribute");
+                    *cvq = CV_WRONG;
+                    return false;
+                }
+                if (parser->tok == '(') {
+                    if (!parser_next(parser)) {
+                        bad_coverage_arg:
+                        parseerror(parser, "invalid parameter for coverage() attribute\n"
+                                           "valid are: block");
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                    if (parser->tok != ')') {
+                        do {
+                            if (parser->tok != TOKEN_IDENT)
+                                goto bad_coverage_arg;
+                            if (!strcmp(parser_tokval(parser), "block"))
+                                flags |= AST_FLAG_BLOCK_COVERAGE;
+                            else if (!strcmp(parser_tokval(parser), "none"))
+                                flags &= ~(AST_FLAG_COVERAGE_MASK);
+                            else
+                                goto bad_coverage_arg;
+                            if (!parser_next(parser))
+                                goto error_in_coverage;
+                            if (parser->tok == ',') {
+                                if (!parser_next(parser))
+                                    goto error_in_coverage;
+                            }
+                        } while (parser->tok != ')');
+                    }
+                    if (parser->tok != ')' || !parser_next(parser))
+                        goto error_in_coverage;
+                } else {
+                    /* without parameter [[coverage]] equals [[coverage(block)]] */
+                    flags |= AST_FLAG_BLOCK_COVERAGE;
+                }
+            }
+            else
+            {
+                /* Skip tokens until we hit a ]] */
+                (void)!parsewarning(parser, WARN_UNKNOWN_ATTRIBUTE, "unknown attribute starting with `%s`", parser_tokval(parser));
+                while (parser->tok != TOKEN_ATTRIBUTE_CLOSE) {
+                    if (!parser_next(parser)) {
+                        parseerror(parser, "error inside attribute");
+                        *cvq = CV_WRONG;
+                        return false;
+                    }
+                }
+            }
+        }
+        else if (with_local && !strcmp(parser_tokval(parser), "static"))
+            had_static = true;
+        else if (!strcmp(parser_tokval(parser), "const"))
+            had_const = true;
+        else if (!strcmp(parser_tokval(parser), "var"))
+            had_var = true;
+        else if (with_local && !strcmp(parser_tokval(parser), "local"))
+            had_var = true;
+        else if (!strcmp(parser_tokval(parser), "noref"))
+            had_noref = true;
+        else if (!had_const && !had_var && !had_noref && !had_attrib && !had_static && !flags) {
+            return false;
+        }
+        else
+            break;
+
+        leave:
+        if (!parser_next(parser))
+            goto onerr;
+    }
+    if (had_const)
+        *cvq = CV_CONST;
+    else if (had_var)
+        *cvq = CV_VAR;
+    else
+        *cvq = CV_NONE;
+    *noref     = had_noref;
+    *is_static = had_static;
+    *_flags    = flags;
+    return true;
+onerr:
+    parseerror(parser, "parse error after variable qualifier");
+    *cvq = CV_WRONG;
+    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;
+    ast_value      *typevar;
+    ast_switch     *switchnode;
+    ast_switch_case swcase;
+
+    int  cvq;
+    bool noref, is_static;
+    uint32_t qflags = 0;
+
+    lex_ctx_t ctx = parser_ctx(parser);
+
+    (void)block; /* not touching */
+    (void)opval;
+
+    /* parse into the expression */
+    if (!parser_next(parser)) {
+        parseerror(parser, "expected switch operand");
+        return false;
+    }
+    /* parse the operand */
+    operand = parse_expression_leave(parser, false, false, false);
+    if (!operand)
+        return false;
+
+    switchnode = ast_switch_new(ctx, operand);
+
+    /* closing paren */
+    if (parser->tok != ')') {
+        ast_delete(switchnode);
+        parseerror(parser, "expected closing paren after 'switch' operand");
+        return false;
+    }
+
+    /* parse over the opening paren */
+    if (!parser_next(parser) || parser->tok != '{') {
+        ast_delete(switchnode);
+        parseerror(parser, "expected list of cases");
+        return false;
+    }
+
+    if (!parser_next(parser)) {
+        ast_delete(switchnode);
+        parseerror(parser, "expected 'case' or 'default'");
+        return false;
+    }
+
+    /* new block; allow some variables to be declared here */
+    parser_enterblock(parser);
+    while (true) {
+        typevar = NULL;
+        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, true, CV_NONE, typevar, false, false, 0, NULL)) {
+                ast_delete(switchnode);
+                return false;
+            }
+            continue;
+        }
+        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, true, cvq, NULL, noref, is_static, qflags, NULL)) {
+                ast_delete(switchnode);
+                return false;
+            }
+            continue;
+        }
+        break;
+    }
+
+    /* case list! */
+    while (parser->tok != '}') {
+        ast_block *caseblock;
+
+        if (!strcmp(parser_tokval(parser), "case")) {
+            if (!parser_next(parser)) {
+                ast_delete(switchnode);
+                parseerror(parser, "expected expression for case");
+                return false;
+            }
+            swcase.value = parse_expression_leave(parser, false, false, false);
+            if (!swcase.value) {
+                ast_delete(switchnode);
+                parseerror(parser, "expected expression for case");
+                return false;
+            }
+            if (!OPTS_FLAG(RELAXED_SWITCH)) {
+                if (!ast_istype(swcase.value, ast_value)) { /* || ((ast_value*)swcase.value)->cvq != CV_CONST) { */
+                    parseerror(parser, "case on non-constant values need to be explicitly enabled via -frelaxed-switch");
+                    ast_unref(operand);
+                    return false;
+                }
+            }
+        }
+        else if (!strcmp(parser_tokval(parser), "default")) {
+            swcase.value = NULL;
+            if (!parser_next(parser)) {
+                ast_delete(switchnode);
+                parseerror(parser, "expected colon");
+                return false;
+            }
+        }
+        else {
+            ast_delete(switchnode);
+            parseerror(parser, "expected 'case' or 'default'");
+            return false;
+        }
+
+        /* Now the colon and body */
+        if (parser->tok != ':') {
+            if (swcase.value) ast_unref(swcase.value);
+            ast_delete(switchnode);
+            parseerror(parser, "expected colon");
+            return false;
+        }
+
+        if (!parser_next(parser)) {
+            if (swcase.value) ast_unref(swcase.value);
+            ast_delete(switchnode);
+            parseerror(parser, "expected statements or case");
+            return false;
+        }
+        caseblock = ast_block_new(parser_ctx(parser));
+        if (!caseblock) {
+            if (swcase.value) ast_unref(swcase.value);
+            ast_delete(switchnode);
+            return false;
+        }
+        swcase.code = (ast_expression*)caseblock;
+        vec_push(switchnode->cases, swcase);
+        while (true) {
+            ast_expression *expr;
+            if (parser->tok == '}')
+                break;
+            if (parser->tok == TOKEN_KEYWORD) {
+                if (!strcmp(parser_tokval(parser), "case") ||
+                    !strcmp(parser_tokval(parser), "default"))
+                {
+                    break;
+                }
+            }
+            if (!parse_statement(parser, caseblock, &expr, true)) {
+                ast_delete(switchnode);
+                return false;
+            }
+            if (!expr)
+                continue;
+            if (!ast_block_add_expr(caseblock, expr)) {
+                ast_delete(switchnode);
+                return false;
+            }
+        }
+    }
+
+    parser_leaveblock(parser);
+
+    /* closing paren */
+    if (parser->tok != '}') {
+        ast_delete(switchnode);
+        parseerror(parser, "expected closing paren of case list");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        ast_delete(switchnode);
+        parseerror(parser, "parse error after switch");
+        return false;
+    }
+    *out = (ast_expression*)switchnode;
+    return true;
+}
+
+/* parse computed goto sides */
+static ast_expression *parse_goto_computed(parser_t *parser, ast_expression **side) {
+    ast_expression *on_true;
+    ast_expression *on_false;
+    ast_expression *cond;
+
+    if (!*side)
+        return NULL;
+
+    if (ast_istype(*side, ast_ternary)) {
+        ast_ternary *tern = (ast_ternary*)*side;
+        on_true  = parse_goto_computed(parser, &tern->on_true);
+        on_false = parse_goto_computed(parser, &tern->on_false);
+
+        if (!on_true || !on_false) {
+            parseerror(parser, "expected label or expression in ternary");
+            if (on_true) ast_unref(on_true);
+            if (on_false) ast_unref(on_false);
+            return NULL;
+        }
+
+        cond = tern->cond;
+        tern->cond = NULL;
+        ast_delete(tern);
+        *side = NULL;
+        return (ast_expression*)ast_ifthen_new(parser_ctx(parser), cond, on_true, on_false);
+    } else if (ast_istype(*side, ast_label)) {
+        ast_goto *gt = ast_goto_new(parser_ctx(parser), ((ast_label*)*side)->name);
+        ast_goto_set_label(gt, ((ast_label*)*side));
+        *side = NULL;
+        return (ast_expression*)gt;
+    }
+    return NULL;
+}
+
+static bool parse_goto(parser_t *parser, ast_expression **out)
+{
+    ast_goto       *gt = NULL;
+    ast_expression *lbl;
+
+    if (!parser_next(parser))
+        return false;
+
+    if (parser->tok != TOKEN_IDENT) {
+        ast_expression *expression;
+
+        /* could be an expression i.e computed goto :-) */
+        if (parser->tok != '(') {
+            parseerror(parser, "expected label name after `goto`");
+            return false;
+        }
+
+        /* failed to parse expression for goto */
+        if (!(expression = parse_expression(parser, false, true)) ||
+            !(*out = parse_goto_computed(parser, &expression))) {
+            parseerror(parser, "invalid goto expression");
+            if(expression)
+                ast_unref(expression);
+            return false;
+        }
+
+        return true;
+    }
+
+    /* not computed goto */
+    gt = ast_goto_new(parser_ctx(parser), parser_tokval(parser));
+    lbl = parser_find_label(parser, gt->name);
+    if (lbl) {
+        if (!ast_istype(lbl, ast_label)) {
+            parseerror(parser, "internal error: label is not an ast_label");
+            ast_delete(gt);
+            return false;
+        }
+        ast_goto_set_label(gt, (ast_label*)lbl);
+    }
+    else
+        vec_push(parser->gotos, gt);
+
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "semicolon expected after goto label");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after goto");
+        return false;
+    }
+
+    *out = (ast_expression*)gt;
+    return true;
+}
+
+static bool parse_skipwhite(parser_t *parser)
+{
+    do {
+        if (!parser_next(parser))
+            return false;
+    } while (parser->tok == TOKEN_WHITE && parser->tok < TOKEN_ERROR);
+    return parser->tok < TOKEN_ERROR;
+}
+
+static bool parse_eol(parser_t *parser)
+{
+    if (!parse_skipwhite(parser))
+        return false;
+    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;
+    }
+    if (!parse_skipwhite(parser) || parser->tok != TOKEN_IDENT) {
+        parseerror(parser, "expected pragma, got `%s`", parser_tokval(parser));
+        return false;
+    }
+
+    if (!strcmp(parser_tokval(parser), "noref")) {
+        if (!parse_skipwhite(parser) || parser->tok != TOKEN_INTCONST) {
+            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
+    {
+        (void)!parsewarning(parser, WARN_UNKNOWN_PRAGMAS, "ignoring #pragma %s", parser_tokval(parser));
+
+        /* skip to eol */
+        while (!parse_eol(parser)) {
+            parser_next(parser);
+        }
+
+        return true;
+    }
+
+    return true;
+}
+
+static bool parse_pragma(parser_t *parser)
+{
+    bool rv;
+    parser->lex->flags.preprocessing = true;
+    parser->lex->flags.mergelines = true;
+    rv = parse_pragma_do(parser);
+    if (parser->tok != TOKEN_EOL) {
+        parseerror(parser, "junk after pragma");
+        rv = false;
+    }
+    parser->lex->flags.preprocessing = false;
+    parser->lex->flags.mergelines = false;
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after pragma");
+        rv = false;
+    }
+    return rv;
+}
+
+static bool parse_statement(parser_t *parser, ast_block *block, ast_expression **out, bool allow_cases)
+{
+    bool       noref, is_static;
+    int        cvq     = CV_NONE;
+    uint32_t   qflags  = 0;
+    ast_value *typevar = NULL;
+    char      *vstring = NULL;
+
+    *out = NULL;
+
+    if (parser->tok == TOKEN_IDENT)
+        typevar = parser_find_typedef(parser, parser_tokval(parser), 0);
+
+    if (typevar || parser->tok == TOKEN_TYPENAME || parser->tok == '.' || parser->tok == TOKEN_DOTS)
+    {
+        /* local variable */
+        if (!block) {
+            parseerror(parser, "cannot declare a variable from here");
+            return false;
+        }
+        if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+            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, 0, NULL))
+            return false;
+        return true;
+    }
+    else if (parse_qualifiers(parser, !!block, &cvq, &noref, &is_static, &qflags, &vstring))
+    {
+        if (cvq == CV_WRONG)
+            return false;
+        return parse_variable(parser, block, false, cvq, NULL, noref, is_static, qflags, vstring);
+    }
+    else if (parser->tok == TOKEN_KEYWORD)
+    {
+        if (!strcmp(parser_tokval(parser), "__builtin_debug_printtype"))
+        {
+            char ty[1024];
+            ast_value *tdef;
+
+            if (!parser_next(parser)) {
+                parseerror(parser, "parse error after __builtin_debug_printtype");
+                return false;
+            }
+
+            if (parser->tok == TOKEN_IDENT && (tdef = parser_find_typedef(parser, parser_tokval(parser), 0)))
+            {
+                ast_type_to_string((ast_expression*)tdef, ty, sizeof(ty));
+                con_out("__builtin_debug_printtype: `%s`=`%s`\n", tdef->name, ty);
+                if (!parser_next(parser)) {
+                    parseerror(parser, "parse error after __builtin_debug_printtype typename argument");
+                    return false;
+                }
+            }
+            else
+            {
+                if (!parse_statement(parser, block, out, allow_cases))
+                    return false;
+                if (!*out)
+                    con_out("__builtin_debug_printtype: got no output node\n");
+                else
+                {
+                    ast_type_to_string(*out, ty, sizeof(ty));
+                    con_out("__builtin_debug_printtype: `%s`\n", ty);
+                }
+            }
+            return true;
+        }
+        else if (!strcmp(parser_tokval(parser), "return"))
+        {
+            return parse_return(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "if"))
+        {
+            return parse_if(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "while"))
+        {
+            return parse_while(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "do"))
+        {
+            return parse_dowhile(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "for"))
+        {
+            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;
+            }
+            return parse_for(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "break"))
+        {
+            return parse_break_continue(parser, block, out, false);
+        }
+        else if (!strcmp(parser_tokval(parser), "continue"))
+        {
+            return parse_break_continue(parser, block, out, true);
+        }
+        else if (!strcmp(parser_tokval(parser), "switch"))
+        {
+            return parse_switch(parser, block, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "case") ||
+                 !strcmp(parser_tokval(parser), "default"))
+        {
+            if (!allow_cases) {
+                parseerror(parser, "unexpected 'case' label");
+                return false;
+            }
+            return true;
+        }
+        else if (!strcmp(parser_tokval(parser), "goto"))
+        {
+            return parse_goto(parser, out);
+        }
+        else if (!strcmp(parser_tokval(parser), "typedef"))
+        {
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected type definition after 'typedef'");
+                return false;
+            }
+            return parse_typedef(parser);
+        }
+        parseerror(parser, "Unexpected keyword: `%s'", parser_tokval(parser));
+        return false;
+    }
+    else if (parser->tok == '{')
+    {
+        ast_block *inner;
+        inner = parse_block(parser);
+        if (!inner)
+            return false;
+        *out = (ast_expression*)inner;
+        return true;
+    }
+    else if (parser->tok == ':')
+    {
+        size_t i;
+        ast_label *label;
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected label name");
+            return false;
+        }
+        if (parser->tok != TOKEN_IDENT) {
+            parseerror(parser, "label must be an identifier");
+            return false;
+        }
+        label = (ast_label*)parser_find_label(parser, parser_tokval(parser));
+        if (label) {
+            if (!label->undefined) {
+                parseerror(parser, "label `%s` already defined", label->name);
+                return false;
+            }
+            label->undefined = false;
+        }
+        else {
+            label = ast_label_new(parser_ctx(parser), parser_tokval(parser), false);
+            vec_push(parser->labels, label);
+        }
+        *out = (ast_expression*)label;
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error after label");
+            return false;
+        }
+        for (i = 0; i < vec_size(parser->gotos); ++i) {
+            if (!strcmp(parser->gotos[i]->name, label->name)) {
+                ast_goto_set_label(parser->gotos[i], label);
+                vec_remove(parser->gotos, i, 1);
+                --i;
+            }
+        }
+        return true;
+    }
+    else if (parser->tok == ';')
+    {
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error after empty statement");
+            return false;
+        }
+        return true;
+    }
+    else
+    {
+        lex_ctx_t ctx = parser_ctx(parser);
+        ast_expression *exp = parse_expression(parser, false, false);
+        if (!exp)
+            return false;
+        *out = exp;
+        if (!ast_side_effects(exp)) {
+            if (compile_warning(ctx, WARN_EFFECTLESS_STATEMENT, "statement has no effect"))
+                return false;
+        }
+        return true;
+    }
+}
+
+static bool parse_enum(parser_t *parser)
+{
+    bool        flag = false;
+    bool        reverse = false;
+    qcfloat_t     num = 0;
+    ast_value **values = NULL;
+    ast_value  *var = NULL;
+    ast_value  *asvalue;
+
+    ast_expression *old;
+
+    if (!parser_next(parser) || (parser->tok != '{' && parser->tok != ':')) {
+        parseerror(parser, "expected `{` or `:` after `enum` keyword");
+        return false;
+    }
+
+    /* enumeration attributes (can add more later) */
+    if (parser->tok == ':') {
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT){
+            parseerror(parser, "expected `flag` or `reverse` for enumeration attribute");
+            return false;
+        }
+
+        /* attributes? */
+        if (!strcmp(parser_tokval(parser), "flag")) {
+            num  = 1;
+            flag = true;
+        }
+        else if (!strcmp(parser_tokval(parser), "reverse")) {
+            reverse = true;
+        }
+        else {
+            parseerror(parser, "invalid attribute `%s` for enumeration", parser_tokval(parser));
+            return false;
+        }
+
+        if (!parser_next(parser) || parser->tok != '{') {
+            parseerror(parser, "expected `{` after enum attribute ");
+            return false;
+        }
+    }
+
+    while (true) {
+        if (!parser_next(parser) || parser->tok != TOKEN_IDENT) {
+            if (parser->tok == '}') {
+                /* allow an empty enum */
+                break;
+            }
+            parseerror(parser, "expected identifier or `}`");
+            goto onerror;
+        }
+
+        old = parser_find_field(parser, parser_tokval(parser));
+        if (!old)
+            old = parser_find_global(parser, parser_tokval(parser));
+        if (old) {
+            parseerror(parser, "value `%s` has already been declared here: %s:%i",
+                       parser_tokval(parser), ast_ctx(old).file, ast_ctx(old).line);
+            goto onerror;
+        }
+
+        var = ast_value_new(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT);
+        vec_push(values, var);
+        var->cvq             = CV_CONST;
+        var->hasvalue        = true;
+
+        /* for flagged enumerations increment in POTs of TWO */
+        var->constval.vfloat = (flag) ? (num *= 2) : (num ++);
+        parser_addglobal(parser, var->name, (ast_expression*)var);
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected `=`, `}` or comma after identifier");
+            goto onerror;
+        }
+
+        if (parser->tok == ',')
+            continue;
+        if (parser->tok == '}')
+            break;
+        if (parser->tok != '=') {
+            parseerror(parser, "expected `=`, `}` or comma after identifier");
+            goto onerror;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected expression after `=`");
+            goto onerror;
+        }
+
+        /* We got a value! */
+        old = parse_expression_leave(parser, true, false, false);
+        asvalue = (ast_value*)old;
+        if (!ast_istype(old, ast_value) || asvalue->cvq != CV_CONST || !asvalue->hasvalue) {
+            compile_error(ast_ctx(var), "constant value or expression expected");
+            goto onerror;
+        }
+        num = (var->constval.vfloat = asvalue->constval.vfloat) + 1;
+
+        if (parser->tok == '}')
+            break;
+        if (parser->tok != ',') {
+            parseerror(parser, "expected `}` or comma after expression");
+            goto onerror;
+        }
+    }
+
+    /* patch them all (for reversed attribute) */
+    if (reverse) {
+        size_t i;
+        for (i = 0; i < vec_size(values); i++)
+            values[i]->constval.vfloat = vec_size(values) - i - 1;
+    }
+
+    if (parser->tok != '}') {
+        parseerror(parser, "internal error: breaking without `}`");
+        goto onerror;
+    }
+
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "expected semicolon after enumeration");
+        goto onerror;
+    }
+
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after enumeration");
+        goto onerror;
+    }
+
+    vec_free(values);
+    return true;
+
+onerror:
+    vec_free(values);
+    return false;
+}
+
+static bool parse_block_into(parser_t *parser, ast_block *block)
+{
+    bool   retval = true;
+
+    parser_enterblock(parser);
+
+    if (!parser_next(parser)) { /* skip the '{' */
+        parseerror(parser, "expected function body");
+        goto cleanup;
+    }
+
+    while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
+    {
+        ast_expression *expr = NULL;
+        if (parser->tok == '}')
+            break;
+
+        if (!parse_statement(parser, block, &expr, false)) {
+            /* parseerror(parser, "parse error"); */
+            block = NULL;
+            goto cleanup;
+        }
+        if (!expr)
+            continue;
+        if (!ast_block_add_expr(block, expr)) {
+            ast_delete(block);
+            block = NULL;
+            goto cleanup;
+        }
+    }
+
+    if (parser->tok != '}') {
+        block = NULL;
+    } else {
+        (void)parser_next(parser);
+    }
+
+cleanup:
+    if (!parser_leaveblock(parser))
+        retval = false;
+    return retval && !!block;
+}
+
+static ast_block* parse_block(parser_t *parser)
+{
+    ast_block *block;
+    block = ast_block_new(parser_ctx(parser));
+    if (!block)
+        return NULL;
+    if (!parse_block_into(parser, block)) {
+        ast_block_delete(block);
+        return NULL;
+    }
+    return block;
+}
+
+static bool parse_statement_or_block(parser_t *parser, ast_expression **out)
+{
+    if (parser->tok == '{') {
+        *out = (ast_expression*)parse_block(parser);
+        return !!*out;
+    }
+    return parse_statement(parser, NULL, out, false);
+}
+
+static bool create_vector_members(ast_value *var, ast_member **me)
+{
+    size_t i;
+    size_t len = strlen(var->name);
+
+    for (i = 0; i < 3; ++i) {
+        char *name = (char*)mem_a(len+3);
+        memcpy(name, var->name, len);
+        name[len+0] = '_';
+        name[len+1] = 'x'+i;
+        name[len+2] = 0;
+        me[i] = ast_member_new(ast_ctx(var), (ast_expression*)var, i, name);
+        mem_d(name);
+        if (!me[i])
+            break;
+    }
+    if (i == 3)
+        return true;
+
+    /* unroll */
+    do { ast_member_delete(me[--i]); } while(i);
+    return false;
+}
+
+static bool parse_function_body(parser_t *parser, ast_value *var)
+{
+    ast_block      *block = NULL;
+    ast_function   *func;
+    ast_function   *old;
+    size_t          parami;
+
+    ast_expression *framenum  = NULL;
+    ast_expression *nextthink = NULL;
+    /* None of the following have to be deleted */
+    ast_expression *fld_think = NULL, *fld_nextthink = NULL, *fld_frame = NULL;
+    ast_expression *gbl_time = NULL, *gbl_self = NULL;
+    bool            has_frame_think;
+
+    bool retval = true;
+
+    has_frame_think = false;
+    old = parser->function;
+
+    if (var->expression.flags & AST_FLAG_ALIAS) {
+        parseerror(parser, "function aliases cannot have bodies");
+        return false;
+    }
+
+    if (vec_size(parser->gotos) || vec_size(parser->labels)) {
+        parseerror(parser, "gotos/labels leaking");
+        return false;
+    }
+
+    if (!OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC) {
+        if (parsewarning(parser, WARN_VARIADIC_FUNCTION,
+                         "variadic function with implementation will not be able to access additional parameters (try -fvariadic-args)"))
+        {
+            return false;
+        }
+    }
+
+    if (parser->tok == '[') {
+        /* got a frame definition: [ framenum, nextthink ]
+         * this translates to:
+         * self.frame = framenum;
+         * self.nextthink = time + 0.1;
+         * self.think = nextthink;
+         */
+        nextthink = NULL;
+
+        fld_think     = parser_find_field(parser, "think");
+        fld_nextthink = parser_find_field(parser, "nextthink");
+        fld_frame     = parser_find_field(parser, "frame");
+        if (!fld_think || !fld_nextthink || !fld_frame) {
+            parseerror(parser, "cannot use [frame,think] notation without the required fields");
+            parseerror(parser, "please declare the following entityfields: `frame`, `think`, `nextthink`");
+            return false;
+        }
+        gbl_time      = parser_find_global(parser, "time");
+        gbl_self      = parser_find_global(parser, "self");
+        if (!gbl_time || !gbl_self) {
+            parseerror(parser, "cannot use [frame,think] notation without the required globals");
+            parseerror(parser, "please declare the following globals: `time`, `self`");
+            return false;
+        }
+
+        if (!parser_next(parser))
+            return false;
+
+        framenum = parse_expression_leave(parser, true, false, false);
+        if (!framenum) {
+            parseerror(parser, "expected a framenumber constant in[frame,think] notation");
+            return false;
+        }
+        if (!ast_istype(framenum, ast_value) || !( (ast_value*)framenum )->hasvalue) {
+            ast_unref(framenum);
+            parseerror(parser, "framenumber in [frame,think] notation must be a constant");
+            return false;
+        }
+
+        if (parser->tok != ',') {
+            ast_unref(framenum);
+            parseerror(parser, "expected comma after frame number in [frame,think] notation");
+            parseerror(parser, "Got a %i\n", parser->tok);
+            return false;
+        }
+
+        if (!parser_next(parser)) {
+            ast_unref(framenum);
+            return false;
+        }
+
+        if (parser->tok == TOKEN_IDENT && !parser_find_var(parser, parser_tokval(parser)))
+        {
+            /* qc allows the use of not-yet-declared functions here
+             * - this automatically creates a prototype */
+            ast_value      *thinkfunc;
+            ast_expression *functype = fld_think->next;
+
+            thinkfunc = ast_value_new(parser_ctx(parser), parser_tokval(parser), functype->vtype);
+            if (!thinkfunc) { /* || !ast_type_adopt(thinkfunc, functype)*/
+                ast_unref(framenum);
+                parseerror(parser, "failed to create implicit prototype for `%s`", parser_tokval(parser));
+                return false;
+            }
+            ast_type_adopt(thinkfunc, functype);
+
+            if (!parser_next(parser)) {
+                ast_unref(framenum);
+                ast_delete(thinkfunc);
+                return false;
+            }
+
+            parser_addglobal(parser, thinkfunc->name, (ast_expression*)thinkfunc);
+
+            nextthink = (ast_expression*)thinkfunc;
+
+        } else {
+            nextthink = parse_expression_leave(parser, true, false, false);
+            if (!nextthink) {
+                ast_unref(framenum);
+                parseerror(parser, "expected a think-function in [frame,think] notation");
+                return false;
+            }
+        }
+
+        if (!ast_istype(nextthink, ast_value)) {
+            parseerror(parser, "think-function in [frame,think] notation must be a constant");
+            retval = false;
+        }
+
+        if (retval && parser->tok != ']') {
+            parseerror(parser, "expected closing `]` for [frame,think] notation");
+            retval = false;
+        }
+
+        if (retval && !parser_next(parser)) {
+            retval = false;
+        }
+
+        if (retval && parser->tok != '{') {
+            parseerror(parser, "a function body has to be declared after a [frame,think] declaration");
+            retval = false;
+        }
+
+        if (!retval) {
+            ast_unref(nextthink);
+            ast_unref(framenum);
+            return false;
+        }
+
+        has_frame_think = true;
+    }
+
+    block = ast_block_new(parser_ctx(parser));
+    if (!block) {
+        parseerror(parser, "failed to allocate block");
+        if (has_frame_think) {
+            ast_unref(nextthink);
+            ast_unref(framenum);
+        }
+        return false;
+    }
+
+    if (has_frame_think) {
+        if (!OPTS_FLAG(EMULATE_STATE)) {
+            ast_state *state_op = ast_state_new(parser_ctx(parser), framenum, nextthink);
+            if (!ast_block_add_expr(block, (ast_expression*)state_op)) {
+                parseerror(parser, "failed to generate state op for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                ast_delete(block);
+                return false;
+            }
+        } else {
+            /* emulate OP_STATE in code: */
+            lex_ctx_t ctx;
+            ast_expression *self_frame;
+            ast_expression *self_nextthink;
+            ast_expression *self_think;
+            ast_expression *time_plus_1;
+            ast_store *store_frame;
+            ast_store *store_nextthink;
+            ast_store *store_think;
+
+            float frame_delta = 1.0f / (float)OPTS_OPTION_U32(OPTION_STATE_FPS);
+
+            ctx = parser_ctx(parser);
+            self_frame     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_frame);
+            self_nextthink = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_nextthink);
+            self_think     = (ast_expression*)ast_entfield_new(ctx, gbl_self, fld_think);
+
+            time_plus_1    = (ast_expression*)ast_binary_new(ctx, INSTR_ADD_F,
+                             gbl_time, (ast_expression*)fold_constgen_float(parser->fold, frame_delta, false));
+
+            if (!self_frame || !self_nextthink || !self_think || !time_plus_1) {
+                if (self_frame)     ast_delete(self_frame);
+                if (self_nextthink) ast_delete(self_nextthink);
+                if (self_think)     ast_delete(self_think);
+                if (time_plus_1)    ast_delete(time_plus_1);
+                retval = false;
+            }
+
+            if (retval)
+            {
+                store_frame     = ast_store_new(ctx, INSTR_STOREP_F,   self_frame,     framenum);
+                store_nextthink = ast_store_new(ctx, INSTR_STOREP_F,   self_nextthink, time_plus_1);
+                store_think     = ast_store_new(ctx, INSTR_STOREP_FNC, self_think,     nextthink);
+
+                if (!store_frame) {
+                    ast_delete(self_frame);
+                    retval = false;
+                }
+                if (!store_nextthink) {
+                    ast_delete(self_nextthink);
+                    retval = false;
+                }
+                if (!store_think) {
+                    ast_delete(self_think);
+                    retval = false;
+                }
+                if (!retval) {
+                    if (store_frame)     ast_delete(store_frame);
+                    if (store_nextthink) ast_delete(store_nextthink);
+                    if (store_think)     ast_delete(store_think);
+                    retval = false;
+                }
+                if (!ast_block_add_expr(block, (ast_expression*)store_frame) ||
+                    !ast_block_add_expr(block, (ast_expression*)store_nextthink) ||
+                    !ast_block_add_expr(block, (ast_expression*)store_think))
+                {
+                    retval = false;
+                }
+            }
+
+            if (!retval) {
+                parseerror(parser, "failed to generate code for [frame,think]");
+                ast_unref(nextthink);
+                ast_unref(framenum);
+                ast_delete(block);
+                return false;
+            }
+        }
+    }
+
+    if (var->hasvalue) {
+        if (!(var->expression.flags & AST_FLAG_ACCUMULATE)) {
+            parseerror(parser, "function `%s` declared with multiple bodies", var->name);
+            ast_block_delete(block);
+            goto enderr;
+        }
+        func = var->constval.vfunc;
+
+        if (!func) {
+            parseerror(parser, "internal error: NULL function: `%s`", var->name);
+            ast_block_delete(block);
+            goto enderr;
+        }
+    } else {
+        func = ast_function_new(ast_ctx(var), var->name, var);
+
+        if (!func) {
+            parseerror(parser, "failed to allocate function for `%s`", var->name);
+            ast_block_delete(block);
+            goto enderr;
+        }
+        vec_push(parser->functions, func);
+    }
+
+    parser_enterblock(parser);
+
+    for (parami = 0; parami < vec_size(var->expression.params); ++parami) {
+        size_t     e;
+        ast_value *param = var->expression.params[parami];
+        ast_member *me[3];
+
+        if (param->expression.vtype != TYPE_VECTOR &&
+            (param->expression.vtype != TYPE_FIELD ||
+             param->expression.next->vtype != TYPE_VECTOR))
+        {
+            continue;
+        }
+
+        if (!create_vector_members(param, me)) {
+            ast_block_delete(block);
+            goto enderrfn;
+        }
+
+        for (e = 0; e < 3; ++e) {
+            parser_addlocal(parser, me[e]->name, (ast_expression*)me[e]);
+            ast_block_collect(block, (ast_expression*)me[e]);
+        }
+    }
+
+    if (var->argcounter && !func->argc) {
+        ast_value *argc = ast_value_new(ast_ctx(var), var->argcounter, TYPE_FLOAT);
+        parser_addlocal(parser, argc->name, (ast_expression*)argc);
+        func->argc = argc;
+    }
+
+    if (OPTS_FLAG(VARIADIC_ARGS) && var->expression.flags & AST_FLAG_VARIADIC && !func->varargs) {
+        char name[1024];
+        ast_value *varargs = ast_value_new(ast_ctx(var), "reserved:va_args", TYPE_ARRAY);
+        varargs->expression.flags |= AST_FLAG_IS_VARARG;
+        varargs->expression.next = (ast_expression*)ast_value_new(ast_ctx(var), NULL, TYPE_VECTOR);
+        varargs->expression.count = 0;
+        util_snprintf(name, sizeof(name), "%s##va##SET", var->name);
+        if (!parser_create_array_setter_proto(parser, varargs, name)) {
+            ast_delete(varargs);
+            ast_block_delete(block);
+            goto enderrfn;
+        }
+        util_snprintf(name, sizeof(name), "%s##va##GET", var->name);
+        if (!parser_create_array_getter_proto(parser, varargs, varargs->expression.next, name)) {
+            ast_delete(varargs);
+            ast_block_delete(block);
+            goto enderrfn;
+        }
+        func->varargs     = varargs;
+        func->fixedparams = (ast_value*)fold_constgen_float(parser->fold, vec_size(var->expression.params), false);
+    }
+
+    parser->function = func;
+    if (!parse_block_into(parser, block)) {
+        ast_block_delete(block);
+        goto enderrfn;
+    }
+
+    vec_push(func->blocks, block);
+
+    parser->function = old;
+    if (!parser_leaveblock(parser))
+        retval = false;
+    if (vec_size(parser->variables) != PARSER_HT_LOCALS) {
+        parseerror(parser, "internal error: local scopes left");
+        retval = false;
+    }
+
+    if (parser->tok == ';')
+        return parser_next(parser);
+    else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC)
+        parseerror(parser, "missing semicolon after function body (mandatory with -std=qcc)");
+    return retval;
+
+enderrfn:
+    (void)!parser_leaveblock(parser);
+    vec_pop(parser->functions);
+    ast_function_delete(func);
+    var->constval.vfunc = NULL;
+
+enderr:
+    parser->function = old;
+    return false;
+}
+
+static ast_expression *array_accessor_split(
+    parser_t  *parser,
+    ast_value *array,
+    ast_value *index,
+    size_t     middle,
+    ast_expression *left,
+    ast_expression *right
+    )
+{
+    ast_ifthen *ifthen;
+    ast_binary *cmp;
+
+    lex_ctx_t ctx = ast_ctx(array);
+
+    if (!left || !right) {
+        if (left)  ast_delete(left);
+        if (right) ast_delete(right);
+        return NULL;
+    }
+
+    cmp = ast_binary_new(ctx, INSTR_LT,
+                         (ast_expression*)index,
+                         (ast_expression*)fold_constgen_float(parser->fold, middle, false));
+    if (!cmp) {
+        ast_delete(left);
+        ast_delete(right);
+        parseerror(parser, "internal error: failed to create comparison for array setter");
+        return NULL;
+    }
+
+    ifthen = ast_ifthen_new(ctx, (ast_expression*)cmp, left, right);
+    if (!ifthen) {
+        ast_delete(cmp); /* will delete left and right */
+        parseerror(parser, "internal error: failed to create conditional jump for array setter");
+        return NULL;
+    }
+
+    return (ast_expression*)ifthen;
+}
+
+static ast_expression *array_setter_node(parser_t *parser, ast_value *array, ast_value *index, ast_value *value, size_t from, size_t afterend)
+{
+    lex_ctx_t ctx = ast_ctx(array);
+
+    if (from+1 == afterend) {
+        /* set this value */
+        ast_block       *block;
+        ast_return      *ret;
+        ast_array_index *subscript;
+        ast_store       *st;
+        int assignop = type_store_instr[value->expression.vtype];
+
+        if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
+            assignop = INSTR_STORE_V;
+
+        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
+        if (!subscript)
+            return NULL;
+
+        st = ast_store_new(ctx, assignop, (ast_expression*)subscript, (ast_expression*)value);
+        if (!st) {
+            ast_delete(subscript);
+            return NULL;
+        }
+
+        block = ast_block_new(ctx);
+        if (!block) {
+            ast_delete(st);
+            return NULL;
+        }
+
+        if (!ast_block_add_expr(block, (ast_expression*)st)) {
+            ast_delete(block);
+            return NULL;
+        }
+
+        ret = ast_return_new(ctx, NULL);
+        if (!ret) {
+            ast_delete(block);
+            return NULL;
+        }
+
+        if (!ast_block_add_expr(block, (ast_expression*)ret)) {
+            ast_delete(block);
+            return NULL;
+        }
+
+        return (ast_expression*)block;
+    } else {
+        ast_expression *left, *right;
+        size_t diff = afterend - from;
+        size_t middle = from + diff/2;
+        left  = array_setter_node(parser, array, index, value, from, middle);
+        right = array_setter_node(parser, array, index, value, middle, afterend);
+        return array_accessor_split(parser, array, index, middle, left, right);
+    }
+}
+
+static ast_expression *array_field_setter_node(
+    parser_t  *parser,
+    ast_value *array,
+    ast_value *entity,
+    ast_value *index,
+    ast_value *value,
+    size_t     from,
+    size_t     afterend)
+{
+    lex_ctx_t ctx = ast_ctx(array);
+
+    if (from+1 == afterend) {
+        /* set this value */
+        ast_block       *block;
+        ast_return      *ret;
+        ast_entfield    *entfield;
+        ast_array_index *subscript;
+        ast_store       *st;
+        int assignop = type_storep_instr[value->expression.vtype];
+
+        if (value->expression.vtype == TYPE_FIELD && value->expression.next->vtype == TYPE_VECTOR)
+            assignop = INSTR_STOREP_V;
+
+        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
+        if (!subscript)
+            return NULL;
+
+        subscript->expression.next = ast_type_copy(ast_ctx(subscript), (ast_expression*)subscript);
+        subscript->expression.vtype = TYPE_FIELD;
+
+        entfield = ast_entfield_new_force(ctx,
+                                          (ast_expression*)entity,
+                                          (ast_expression*)subscript,
+                                          (ast_expression*)subscript);
+        if (!entfield) {
+            ast_delete(subscript);
+            return NULL;
+        }
+
+        st = ast_store_new(ctx, assignop, (ast_expression*)entfield, (ast_expression*)value);
+        if (!st) {
+            ast_delete(entfield);
+            return NULL;
+        }
+
+        block = ast_block_new(ctx);
+        if (!block) {
+            ast_delete(st);
+            return NULL;
+        }
+
+        if (!ast_block_add_expr(block, (ast_expression*)st)) {
+            ast_delete(block);
+            return NULL;
+        }
+
+        ret = ast_return_new(ctx, NULL);
+        if (!ret) {
+            ast_delete(block);
+            return NULL;
+        }
+
+        if (!ast_block_add_expr(block, (ast_expression*)ret)) {
+            ast_delete(block);
+            return NULL;
+        }
+
+        return (ast_expression*)block;
+    } else {
+        ast_expression *left, *right;
+        size_t diff = afterend - from;
+        size_t middle = from + diff/2;
+        left  = array_field_setter_node(parser, array, entity, index, value, from, middle);
+        right = array_field_setter_node(parser, array, entity, index, value, middle, afterend);
+        return array_accessor_split(parser, array, index, middle, left, right);
+    }
+}
+
+static ast_expression *array_getter_node(parser_t *parser, ast_value *array, ast_value *index, size_t from, size_t afterend)
+{
+    lex_ctx_t ctx = ast_ctx(array);
+
+    if (from+1 == afterend) {
+        ast_return      *ret;
+        ast_array_index *subscript;
+
+        subscript = ast_array_index_new(ctx, (ast_expression*)array, (ast_expression*)fold_constgen_float(parser->fold, from, false));
+        if (!subscript)
+            return NULL;
+
+        ret = ast_return_new(ctx, (ast_expression*)subscript);
+        if (!ret) {
+            ast_delete(subscript);
+            return NULL;
+        }
+
+        return (ast_expression*)ret;
+    } else {
+        ast_expression *left, *right;
+        size_t diff = afterend - from;
+        size_t middle = from + diff/2;
+        left  = array_getter_node(parser, array, index, from, middle);
+        right = array_getter_node(parser, array, index, middle, afterend);
+        return array_accessor_split(parser, array, index, middle, left, right);
+    }
+}
+
+static bool parser_create_array_accessor(parser_t *parser, ast_value *array, const char *funcname, ast_value **out)
+{
+    ast_function   *func = NULL;
+    ast_value      *fval = NULL;
+    ast_block      *body = NULL;
+
+    fval = ast_value_new(ast_ctx(array), funcname, TYPE_FUNCTION);
+    if (!fval) {
+        parseerror(parser, "failed to create accessor function value");
+        return false;
+    }
+    fval->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
+
+    func = ast_function_new(ast_ctx(array), funcname, fval);
+    if (!func) {
+        ast_delete(fval);
+        parseerror(parser, "failed to create accessor function node");
+        return false;
+    }
+
+    body = ast_block_new(ast_ctx(array));
+    if (!body) {
+        parseerror(parser, "failed to create block for array accessor");
+        ast_delete(fval);
+        ast_delete(func);
+        return false;
+    }
+
+    vec_push(func->blocks, body);
+    *out = fval;
+
+    vec_push(parser->accessors, fval);
+
+    return true;
+}
+
+static ast_value* parser_create_array_setter_proto(parser_t *parser, ast_value *array, const char *funcname)
+{
+    ast_value      *index = NULL;
+    ast_value      *value = NULL;
+    ast_function   *func;
+    ast_value      *fval;
+
+    if (!ast_istype(array->expression.next, ast_value)) {
+        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
+        return NULL;
+    }
+
+    if (!parser_create_array_accessor(parser, array, funcname, &fval))
+        return NULL;
+    func = fval->constval.vfunc;
+    fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "<void>", TYPE_VOID);
+
+    index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT);
+    value = ast_value_copy((ast_value*)array->expression.next);
+
+    if (!index || !value) {
+        parseerror(parser, "failed to create locals for array accessor");
+        goto cleanup;
+    }
+    (void)!ast_value_set_name(value, "value"); /* not important */
+    vec_push(fval->expression.params, index);
+    vec_push(fval->expression.params, value);
+
+    array->setter = fval;
+    return fval;
+cleanup:
+    if (index) ast_delete(index);
+    if (value) ast_delete(value);
+    ast_delete(func);
+    ast_delete(fval);
+    return NULL;
+}
+
+static bool parser_create_array_setter_impl(parser_t *parser, ast_value *array)
+{
+    ast_expression *root = NULL;
+    root = array_setter_node(parser, array,
+                             array->setter->expression.params[0],
+                             array->setter->expression.params[1],
+                             0, array->expression.count);
+    if (!root) {
+        parseerror(parser, "failed to build accessor search tree");
+        return false;
+    }
+    if (!ast_block_add_expr(array->setter->constval.vfunc->blocks[0], root)) {
+        ast_delete(root);
+        return false;
+    }
+    return true;
+}
+
+static bool parser_create_array_setter(parser_t *parser, ast_value *array, const char *funcname)
+{
+    if (!parser_create_array_setter_proto(parser, array, funcname))
+        return false;
+    return parser_create_array_setter_impl(parser, array);
+}
+
+static bool parser_create_array_field_setter(parser_t *parser, ast_value *array, const char *funcname)
+{
+    ast_expression *root = NULL;
+    ast_value      *entity = NULL;
+    ast_value      *index = NULL;
+    ast_value      *value = NULL;
+    ast_function   *func;
+    ast_value      *fval;
+
+    if (!ast_istype(array->expression.next, ast_value)) {
+        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
+        return false;
+    }
+
+    if (!parser_create_array_accessor(parser, array, funcname, &fval))
+        return false;
+    func = fval->constval.vfunc;
+    fval->expression.next = (ast_expression*)ast_value_new(ast_ctx(array), "<void>", TYPE_VOID);
+
+    entity = ast_value_new(ast_ctx(array), "entity", TYPE_ENTITY);
+    index  = ast_value_new(ast_ctx(array), "index",  TYPE_FLOAT);
+    value  = ast_value_copy((ast_value*)array->expression.next);
+    if (!entity || !index || !value) {
+        parseerror(parser, "failed to create locals for array accessor");
+        goto cleanup;
+    }
+    (void)!ast_value_set_name(value, "value"); /* not important */
+    vec_push(fval->expression.params, entity);
+    vec_push(fval->expression.params, index);
+    vec_push(fval->expression.params, value);
+
+    root = array_field_setter_node(parser, array, entity, index, value, 0, array->expression.count);
+    if (!root) {
+        parseerror(parser, "failed to build accessor search tree");
+        goto cleanup;
+    }
+
+    array->setter = fval;
+    return ast_block_add_expr(func->blocks[0], root);
+cleanup:
+    if (entity) ast_delete(entity);
+    if (index)  ast_delete(index);
+    if (value)  ast_delete(value);
+    if (root)   ast_delete(root);
+    ast_delete(func);
+    ast_delete(fval);
+    return false;
+}
+
+static ast_value* parser_create_array_getter_proto(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
+{
+    ast_value      *index = NULL;
+    ast_value      *fval;
+    ast_function   *func;
+
+    /* NOTE: checking array->expression.next rather than elemtype since
+     * for fields elemtype is a temporary fieldtype.
+     */
+    if (!ast_istype(array->expression.next, ast_value)) {
+        parseerror(parser, "internal error: array accessor needs to build an ast_value with a copy of the element type");
+        return NULL;
+    }
+
+    if (!parser_create_array_accessor(parser, array, funcname, &fval))
+        return NULL;
+    func = fval->constval.vfunc;
+    fval->expression.next = ast_type_copy(ast_ctx(array), elemtype);
+
+    index = ast_value_new(ast_ctx(array), "index", TYPE_FLOAT);
+
+    if (!index) {
+        parseerror(parser, "failed to create locals for array accessor");
+        goto cleanup;
+    }
+    vec_push(fval->expression.params, index);
+
+    array->getter = fval;
+    return fval;
+cleanup:
+    if (index) ast_delete(index);
+    ast_delete(func);
+    ast_delete(fval);
+    return NULL;
+}
+
+static bool parser_create_array_getter_impl(parser_t *parser, ast_value *array)
+{
+    ast_expression *root = NULL;
+
+    root = array_getter_node(parser, array, array->getter->expression.params[0], 0, array->expression.count);
+    if (!root) {
+        parseerror(parser, "failed to build accessor search tree");
+        return false;
+    }
+    if (!ast_block_add_expr(array->getter->constval.vfunc->blocks[0], root)) {
+        ast_delete(root);
+        return false;
+    }
+    return true;
+}
+
+static bool parser_create_array_getter(parser_t *parser, ast_value *array, const ast_expression *elemtype, const char *funcname)
+{
+    if (!parser_create_array_getter_proto(parser, array, elemtype, funcname))
+        return false;
+    return parser_create_array_getter_impl(parser, array);
+}
+
+static ast_value *parse_parameter_list(parser_t *parser, ast_value *var)
+{
+    lex_ctx_t     ctx;
+    size_t      i;
+    ast_value **params;
+    ast_value  *param;
+    ast_value  *fval;
+    bool        first = true;
+    bool        variadic = false;
+    ast_value  *varparam = NULL;
+    char       *argcounter = NULL;
+
+    ctx = parser_ctx(parser);
+
+    /* for the sake of less code we parse-in in this function */
+    if (!parser_next(parser)) {
+        ast_delete(var);
+        parseerror(parser, "expected parameter list");
+        return NULL;
+    }
+
+    params = NULL;
+
+    /* parse variables until we hit a closing paren */
+    while (parser->tok != ')') {
+        bool is_varargs = false;
+
+        if (!first) {
+            /* there must be commas between them */
+            if (parser->tok != ',') {
+                parseerror(parser, "expected comma or end of parameter list");
+                goto on_error;
+            }
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected parameter");
+                goto on_error;
+            }
+        }
+        first = false;
+
+        param = parse_typename(parser, NULL, NULL, &is_varargs);
+        if (!param && !is_varargs)
+            goto on_error;
+        if (is_varargs) {
+            /* '...' indicates a varargs function */
+            variadic = true;
+            if (parser->tok != ')' && parser->tok != TOKEN_IDENT) {
+                parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                goto on_error;
+            }
+            if (parser->tok == TOKEN_IDENT) {
+                argcounter = util_strdup(parser_tokval(parser));
+                if (!parser_next(parser) || parser->tok != ')') {
+                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                    goto on_error;
+                }
+            }
+        } else {
+            vec_push(params, param);
+            if (param->expression.vtype >= TYPE_VARIANT) {
+                char tname[1024]; /* typename is reserved in C++ */
+                ast_type_to_string((ast_expression*)param, tname, sizeof(tname));
+                parseerror(parser, "type not supported as part of a parameter list: %s", tname);
+                goto on_error;
+            }
+            /* type-restricted varargs */
+            if (parser->tok == TOKEN_DOTS) {
+                variadic = true;
+                varparam = vec_last(params);
+                vec_pop(params);
+                if (!parser_next(parser) || (parser->tok != ')' && parser->tok != TOKEN_IDENT)) {
+                    parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                    goto on_error;
+                }
+                if (parser->tok == TOKEN_IDENT) {
+                    argcounter = util_strdup(parser_tokval(parser));
+                    ast_value_set_name(param, argcounter);
+                    if (!parser_next(parser) || parser->tok != ')') {
+                        parseerror(parser, "`...` must be the last parameter of a variadic function declaration");
+                        goto on_error;
+                    }
+                }
+            }
+            if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_FTEQCC && param->name[0] == '<') {
+                parseerror(parser, "parameter name omitted");
+                goto on_error;
+            }
+        }
+    }
+
+    if (vec_size(params) == 1 && params[0]->expression.vtype == TYPE_VOID)
+        vec_free(params);
+
+    /* sanity check */
+    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 */
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after typename");
+        goto on_error;
+    }
+
+    /* now turn 'var' into a function type */
+    fval = ast_value_new(ctx, "<type()>", TYPE_FUNCTION);
+    fval->expression.next     = (ast_expression*)var;
+    if (variadic)
+        fval->expression.flags |= AST_FLAG_VARIADIC;
+    var = fval;
+
+    var->expression.params   = params;
+    var->expression.varparam = (ast_expression*)varparam;
+    var->argcounter          = argcounter;
+    params = NULL;
+
+    return var;
+
+on_error:
+    if (argcounter)
+        mem_d(argcounter);
+    if (varparam)
+        ast_delete(varparam);
+    ast_delete(var);
+    for (i = 0; i < vec_size(params); ++i)
+        ast_delete(params[i]);
+    vec_free(params);
+    return NULL;
+}
+
+static ast_value *parse_arraysize(parser_t *parser, ast_value *var)
+{
+    ast_expression *cexp;
+    ast_value      *cval, *tmp;
+    lex_ctx_t ctx;
+
+    ctx = parser_ctx(parser);
+
+    if (!parser_next(parser)) {
+        ast_delete(var);
+        parseerror(parser, "expected array-size");
+        return NULL;
+    }
+
+    if (parser->tok != ']') {
+        cexp = parse_expression_leave(parser, true, false, false);
+
+        if (!cexp || !ast_istype(cexp, ast_value)) {
+            if (cexp)
+                ast_unref(cexp);
+            ast_delete(var);
+            parseerror(parser, "expected array-size as constant positive integer");
+            return NULL;
+        }
+        cval = (ast_value*)cexp;
+    }
+    else {
+        cexp = NULL;
+        cval = NULL;
+    }
+
+    tmp = ast_value_new(ctx, "<type[]>", TYPE_ARRAY);
+    tmp->expression.next = (ast_expression*)var;
+    var = tmp;
+
+    if (cval) {
+        if (cval->expression.vtype == TYPE_INTEGER)
+            tmp->expression.count = cval->constval.vint;
+        else if (cval->expression.vtype == TYPE_FLOAT)
+            tmp->expression.count = cval->constval.vfloat;
+        else {
+            ast_unref(cexp);
+            ast_delete(var);
+            parseerror(parser, "array-size must be a positive integer constant");
+            return NULL;
+        }
+
+        ast_unref(cexp);
+    } else {
+        var->expression.count = -1;
+        var->expression.flags |= AST_FLAG_ARRAY_INIT;
+    }
+
+    if (parser->tok != ']') {
+        ast_delete(var);
+        parseerror(parser, "expected ']' after array-size");
+        return NULL;
+    }
+    if (!parser_next(parser)) {
+        ast_delete(var);
+        parseerror(parser, "error after parsing array size");
+        return NULL;
+    }
+    return var;
+}
+
+/* Parse a complete typename.
+ * for single-variables (ie. function parameters or typedefs) storebase should be NULL
+ * but when parsing variables separated by comma
+ * 'storebase' should point to where the base-type should be kept.
+ * The base type makes up every bit of type information which comes *before* the
+ * variable name.
+ *
+ * NOTE: The value must either be named, have a NULL name, or a name starting
+ *       with '<'. In the first case, this will be the actual variable or type
+ *       name, in the other cases it is assumed that the name will appear
+ *       later, and an error is generated otherwise.
+ *
+ * The following will be parsed in its entirety:
+ *     void() foo()
+ * The 'basetype' in this case is 'void()'
+ * and if there's a comma after it, say:
+ *     void() foo(), bar
+ * then the type-information 'void()' can be stored in 'storebase'
+ */
+static ast_value *parse_typename(parser_t *parser, ast_value **storebase, ast_value *cached_typedef, bool *is_vararg)
+{
+    ast_value *var, *tmp;
+    lex_ctx_t    ctx;
+
+    const char *name = NULL;
+    bool        isfield  = false;
+    bool        wasarray = false;
+    size_t      morefields = 0;
+
+    bool        vararg = (parser->tok == TOKEN_DOTS);
+
+    ctx = parser_ctx(parser);
+
+    /* types may start with a dot */
+    if (parser->tok == '.' || parser->tok == TOKEN_DOTS) {
+        isfield = true;
+        if (parser->tok == TOKEN_DOTS)
+            morefields += 2;
+        /* if we parsed a dot we need a typename now */
+        if (!parser_next(parser)) {
+            parseerror(parser, "expected typename for field definition");
+            return NULL;
+        }
+
+        /* Further dots are handled seperately because they won't be part of the
+         * basetype
+         */
+        while (true) {
+            if (parser->tok == '.')
+                ++morefields;
+            else if (parser->tok == TOKEN_DOTS)
+                morefields += 3;
+            else
+                break;
+            vararg = false;
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected typename for field definition");
+                return NULL;
+            }
+        }
+    }
+    if (parser->tok == TOKEN_IDENT)
+        cached_typedef = parser_find_typedef(parser, parser_tokval(parser), 0);
+    if (!cached_typedef && parser->tok != TOKEN_TYPENAME) {
+        if (vararg && is_vararg) {
+            *is_vararg = true;
+            return NULL;
+        }
+        parseerror(parser, "expected typename");
+        return NULL;
+    }
+
+    /* generate the basic type value */
+    if (cached_typedef) {
+        var = ast_value_copy(cached_typedef);
+        ast_value_set_name(var, "<type(from_def)>");
+    } else
+        var = ast_value_new(ctx, "<type>", parser_token(parser)->constval.t);
+
+    for (; morefields; --morefields) {
+        tmp = ast_value_new(ctx, "<.type>", TYPE_FIELD);
+        tmp->expression.next = (ast_expression*)var;
+        var = tmp;
+    }
+
+    /* do not yet turn into a field - remember:
+     * .void() foo; is a field too
+     * .void()() foo; is a function
+     */
+
+    /* parse on */
+    if (!parser_next(parser)) {
+        ast_delete(var);
+        parseerror(parser, "parse error after typename");
+        return NULL;
+    }
+
+    /* an opening paren now starts the parameter-list of a function
+     * this is where original-QC has parameter lists.
+     * We allow a single parameter list here.
+     * Much like fteqcc we don't allow `float()() x`
+     */
+    if (parser->tok == '(') {
+        var = parse_parameter_list(parser, var);
+        if (!var)
+            return NULL;
+    }
+
+    /* store the base if requested */
+    if (storebase) {
+        *storebase = ast_value_copy(var);
+        if (isfield) {
+            tmp = ast_value_new(ctx, "<type:f>", TYPE_FIELD);
+            tmp->expression.next = (ast_expression*)*storebase;
+            *storebase = tmp;
+        }
+    }
+
+    /* there may be a name now */
+    if (parser->tok == TOKEN_IDENT || parser->tok == TOKEN_KEYWORD) {
+        if (!strcmp(parser_tokval(parser), "break"))
+            (void)!parsewarning(parser, WARN_BREAKDEF, "break definition ignored (suggest removing it)");
+        else if (parser->tok == TOKEN_KEYWORD)
+            goto leave;
+
+        name = util_strdup(parser_tokval(parser));
+
+        /* parse on */
+        if (!parser_next(parser)) {
+            ast_delete(var);
+            mem_d(name);
+            parseerror(parser, "error after variable or field declaration");
+            return NULL;
+        }
+    }
+
+    leave:
+    /* now this may be an array */
+    if (parser->tok == '[') {
+        wasarray = true;
+        var = parse_arraysize(parser, var);
+        if (!var) {
+            if (name) mem_d(name);
+            return NULL;
+        }
+    }
+
+    /* This is the point where we can turn it into a field */
+    if (isfield) {
+        /* turn it into a field if desired */
+        tmp = ast_value_new(ctx, "<type:f>", TYPE_FIELD);
+        tmp->expression.next = (ast_expression*)var;
+        var = tmp;
+    }
+
+    /* now there may be function parens again */
+    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");
+    while (parser->tok == '(') {
+        var = parse_parameter_list(parser, var);
+        if (!var) {
+            if (name) mem_d(name);
+            return NULL;
+        }
+    }
+
+    /* finally name it */
+    if (name) {
+        if (!ast_value_set_name(var, name)) {
+            ast_delete(var);
+            mem_d(name);
+            parseerror(parser, "internal error: failed to set name");
+            return NULL;
+        }
+        /* free the name, ast_value_set_name duplicates */
+        mem_d(name);
+    }
+
+    return var;
+}
+
+static bool parse_typedef(parser_t *parser)
+{
+    ast_value      *typevar, *oldtype;
+    ast_expression *old;
+
+    typevar = parse_typename(parser, NULL, NULL, NULL);
+
+    if (!typevar)
+        return false;
+
+    /* while parsing types, the ast_value's get named '<something>' */
+    if (!typevar->name || typevar->name[0] == '<') {
+        parseerror(parser, "missing name in typedef");
+        ast_delete(typevar);
+        return false;
+    }
+
+    if ( (old = parser_find_var(parser, typevar->name)) ) {
+        parseerror(parser, "cannot define a type with the same name as a variable: %s\n"
+                   " -> `%s` has been declared here: %s:%i",
+                   typevar->name, ast_ctx(old).file, ast_ctx(old).line);
+        ast_delete(typevar);
+        return false;
+    }
+
+    if ( (oldtype = parser_find_typedef(parser, typevar->name, vec_last(parser->_blocktypedefs))) ) {
+        parseerror(parser, "type `%s` has already been declared here: %s:%i",
+                   typevar->name, ast_ctx(oldtype).file, ast_ctx(oldtype).line);
+        ast_delete(typevar);
+        return false;
+    }
+
+    vec_push(parser->_typedefs, typevar);
+    util_htset(vec_last(parser->typedefs), typevar->name, typevar);
+
+    if (parser->tok != ';') {
+        parseerror(parser, "expected semicolon after typedef");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after typedef");
+        return false;
+    }
+
+    return true;
+}
+
+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 create_array_accessors(parser_t *parser, ast_value *var)
+{
+    char name[1024];
+    util_snprintf(name, sizeof(name), "%s##SET", var->name);
+    if (!parser_create_array_setter(parser, var, name))
+        return false;
+    util_snprintf(name, sizeof(name), "%s##GET", var->name);
+    if (!parser_create_array_getter(parser, var, var->expression.next, name))
+        return false;
+    return true;
+}
+
+static bool parse_array(parser_t *parser, ast_value *array)
+{
+    size_t i;
+    if (array->initlist) {
+        parseerror(parser, "array already initialized elsewhere");
+        return false;
+    }
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error in array initializer");
+        return false;
+    }
+    i = 0;
+    while (parser->tok != '}') {
+        ast_value *v = (ast_value*)parse_expression_leave(parser, true, false, false);
+        if (!v)
+            return false;
+        if (!ast_istype(v, ast_value) || !v->hasvalue || v->cvq != CV_CONST) {
+            ast_unref(v);
+            parseerror(parser, "initializing element must be a compile time constant");
+            return false;
+        }
+        vec_push(array->initlist, v->constval);
+        if (v->expression.vtype == TYPE_STRING) {
+            array->initlist[i].vstring = util_strdupe(array->initlist[i].vstring);
+            ++i;
+        }
+        ast_unref(v);
+        if (parser->tok == '}')
+            break;
+        if (parser->tok != ',' || !parser_next(parser)) {
+            parseerror(parser, "expected comma or '}' in element list");
+            return false;
+        }
+    }
+    if (!parser_next(parser) || parser->tok != ';') {
+        parseerror(parser, "expected semicolon after initializer, got %s");
+        return false;
+    }
+    /*
+    if (!parser_next(parser)) {
+        parseerror(parser, "parse error after initializer");
+        return false;
+    }
+    */
+
+    if (array->expression.flags & AST_FLAG_ARRAY_INIT) {
+        if (array->expression.count != (size_t)-1) {
+            parseerror(parser, "array `%s' has already been initialized with %u elements",
+                       array->name, (unsigned)array->expression.count);
+        }
+        array->expression.count = vec_size(array->initlist);
+        if (!create_array_accessors(parser, array))
+            return false;
+    }
+    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;
+    ast_expression *old;
+    bool       was_end;
+    size_t     i;
+
+    ast_value *basetype = NULL;
+    bool      retval    = true;
+    bool      isparam   = false;
+    bool      isvector  = false;
+    bool      cleanvar  = true;
+    bool      wasarray  = false;
+
+    ast_member *me[3] = { NULL, NULL, NULL };
+    ast_member *last_me[3] = { NULL, NULL, NULL };
+
+    if (!localblock && is_static)
+        parseerror(parser, "`static` qualifier is not supported in global scope");
+
+    /* get the first complete variable */
+    var = parse_typename(parser, &basetype, cached_typedef, NULL);
+    if (!var) {
+        if (basetype)
+            ast_delete(basetype);
+        return false;
+    }
+
+    /* while parsing types, the ast_value's get named '<something>' */
+    if (!var->name || var->name[0] == '<') {
+        parseerror(parser, "declaration does not declare anything");
+        if (basetype)
+            ast_delete(basetype);
+        return false;
+    }
+
+    while (true) {
+        proto = NULL;
+        wasarray = false;
+
+        /* Part 0: finish the type */
+        if (parser->tok == '(') {
+            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) {
+                retval = false;
+                goto cleanup;
+            }
+        }
+        /* we only allow 1-dimensional arrays */
+        if (parser->tok == '[') {
+            wasarray = true;
+            var = parse_arraysize(parser, var);
+            if (!var) {
+                retval = false;
+                goto cleanup;
+            }
+        }
+        if (parser->tok == '(' && wasarray) {
+            parseerror(parser, "arrays as part of a return type is not supported");
+            /* we'll still parse the type completely for now */
+        }
+        /* for functions returning functions */
+        while (parser->tok == '(') {
+            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) {
+                retval = false;
+                goto cleanup;
+            }
+        }
+
+        var->cvq = qualifier;
+        if (qflags & AST_FLAG_COVERAGE) /* specified in QC, drop our default */
+            var->expression.flags &= ~(AST_FLAG_COVERAGE_MASK);
+        var->expression.flags |= qflags;
+
+        /*
+         * store the vstring back to var for alias and
+         * deprecation messages.
+         */
+        if (var->expression.flags & AST_FLAG_DEPRECATED ||
+            var->expression.flags & AST_FLAG_ALIAS)
+            var->desc = vstring;
+
+        if (parser_find_global(parser, var->name) && var->expression.flags & AST_FLAG_ALIAS) {
+            parseerror(parser, "function aliases cannot be forward declared");
+            retval = false;
+            goto cleanup;
+        }
+
+
+        /* 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;
+            if (!strcmp(var->name, "end_sys_globals")) {
+                var->uses++;
+                parser->crc_globals = vec_size(parser->globals);
+                was_end = true;
+            }
+            else if (!strcmp(var->name, "end_sys_fields")) {
+                var->uses++;
+                parser->crc_fields = vec_size(parser->fields);
+                was_end = true;
+            }
+            if (was_end && var->expression.vtype == TYPE_FIELD) {
+                if (parsewarning(parser, WARN_END_SYS_FIELDS,
+                                 "global '%s' hint should not be a field",
+                                 parser_tokval(parser)))
+                {
+                    retval = false;
+                    goto cleanup;
+                }
+            }
+
+            if (!nofields && var->expression.vtype == TYPE_FIELD)
+            {
+                /* deal with field declarations */
+                old = parser_find_field(parser, var->name);
+                if (old) {
+                    if (parsewarning(parser, WARN_FIELD_REDECLARED, "field `%s` already declared here: %s:%i",
+                                     var->name, ast_ctx(old).file, (int)ast_ctx(old).line))
+                    {
+                        retval = false;
+                        goto cleanup;
+                    }
+                    ast_delete(var);
+                    var = NULL;
+                    goto skipvar;
+                    /*
+                    parseerror(parser, "field `%s` already declared here: %s:%i",
+                               var->name, ast_ctx(old).file, ast_ctx(old).line);
+                    retval = false;
+                    goto cleanup;
+                    */
+                }
+                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");
+                    parseerror(parser, "field `%s` already declared here: %s:%i",
+                               var->name, ast_ctx(old).file, ast_ctx(old).line);
+                    retval = false;
+                    goto cleanup;
+                }
+            }
+            else
+            {
+                /* deal with other globals */
+                old = parser_find_global(parser, var->name);
+                if (old && var->expression.vtype == TYPE_FUNCTION && old->vtype == TYPE_FUNCTION)
+                {
+                    /* This is a function which had a prototype */
+                    if (!ast_istype(old, ast_value)) {
+                        parseerror(parser, "internal error: prototype is not an ast_value");
+                        retval = false;
+                        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,
+                                   ast_ctx(proto).file, ast_ctx(proto).line);
+                        retval = false;
+                        goto cleanup;
+                    }
+                    /* 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;
+                }
+                else
+                {
+                    /* other globals */
+                    if (old) {
+                        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;
+                        }
+                        if (old->flags & AST_FLAG_FINAL_DECL) {
+                            parseerror(parser, "cannot redeclare variable `%s`, declared final 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;
+                        }
+                        if (!parser_check_qualifiers(parser, var, proto)) {
+                            retval = false;
+                            proto = NULL;
+                            goto cleanup;
+                        }
+                        proto->expression.flags |= var->expression.flags;
+                        /* copy the context for finals,
+                         * so the error can show where it was actually made 'final'
+                         */
+                        if (proto->expression.flags & AST_FLAG_FINAL_DECL)
+                            ast_ctx(old) = ast_ctx(var);
+                        ast_delete(var);
+                        var = proto;
+                    }
+                    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");
+                        parseerror(parser, "global `%s` already declared here: %s:%i",
+                                   var->name, ast_ctx(old).file, ast_ctx(old).line);
+                        retval = false;
+                        goto cleanup;
+                    }
+                }
+            }
+        }
+        else /* it's not a global */
+        {
+            old = parser_find_local(parser, var->name, vec_size(parser->variables)-1, &isparam);
+            if (old && !isparam) {
+                parseerror(parser, "local `%s` already declared here: %s:%i",
+                           var->name, ast_ctx(old).file, (int)ast_ctx(old).line);
+                retval = false;
+                goto cleanup;
+            }
+            /* doing this here as the above is just for a single scope */
+            old = parser_find_local(parser, var->name, 0, &isparam);
+            if (old && isparam) {
+                if (parsewarning(parser, WARN_LOCAL_SHADOWS,
+                                 "local `%s` is shadowing a parameter", var->name))
+                {
+                    parseerror(parser, "local `%s` already declared here: %s:%i",
+                               var->name, ast_ctx(old).file, (int)ast_ctx(old).line);
+                    retval = false;
+                    goto cleanup;
+                }
+                if (OPTS_OPTION_U32(OPTION_STANDARD) != COMPILER_GMQCC) {
+                    ast_delete(var);
+                    if (ast_istype(old, ast_value))
+                        var = proto = (ast_value*)old;
+                    else {
+                        var = NULL;
+                        goto skipvar;
+                    }
+                }
+            }
+        }
+
+        /* 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.
+         */
+        if (!proto) {
+            if (var->expression.vtype == TYPE_VECTOR)
+                isvector = true;
+            else if (var->expression.vtype == TYPE_FIELD &&
+                     var->expression.next->vtype == TYPE_VECTOR)
+                isvector = true;
+
+            if (isvector) {
+                if (!create_vector_members(var, me)) {
+                    retval = false;
+                    goto cleanup;
+                }
+            }
+
+            if (!localblock) {
+                /* deal with global variables, fields, functions */
+                if (!nofields && var->expression.vtype == TYPE_FIELD && parser->tok != '=') {
+                    var->isfield = true;
+                    vec_push(parser->fields, (ast_expression*)var);
+                    util_htset(parser->htfields, var->name, var);
+                    if (isvector) {
+                        for (i = 0; i < 3; ++i) {
+                            vec_push(parser->fields, (ast_expression*)me[i]);
+                            util_htset(parser->htfields, me[i]->name, me[i]);
+                        }
+                    }
+                }
+                else {
+                    if (!(var->expression.flags & AST_FLAG_ALIAS)) {
+                        parser_addglobal(parser, var->name, (ast_expression*)var);
+                        if (isvector) {
+                            for (i = 0; i < 3; ++i) {
+                                parser_addglobal(parser, me[i]->name, (ast_expression*)me[i]);
+                            }
+                        }
+                    } else {
+                        ast_expression *find  = parser_find_global(parser, var->desc);
+
+                        if (!find) {
+                            compile_error(parser_ctx(parser), "undeclared variable `%s` for alias `%s`", var->desc, var->name);
+                            return false;
+                        }
+
+                        if (!ast_compare_type((ast_expression*)var, find)) {
+                            char ty1[1024];
+                            char ty2[1024];
+
+                            ast_type_to_string(find,                  ty1, sizeof(ty1));
+                            ast_type_to_string((ast_expression*)var,  ty2, sizeof(ty2));
+
+                            compile_error(parser_ctx(parser), "incompatible types `%s` and `%s` for alias `%s`",
+                                ty1, ty2, var->name
+                            );
+                            return false;
+                        }
+
+                        util_htset(parser->aliases, var->name, find);
+
+                        /* generate aliases for vector components */
+                        if (isvector) {
+                            char *buffer[3];
+
+                            util_asprintf(&buffer[0], "%s_x", var->desc);
+                            util_asprintf(&buffer[1], "%s_y", var->desc);
+                            util_asprintf(&buffer[2], "%s_z", var->desc);
+
+                            util_htset(parser->aliases, me[0]->name, parser_find_global(parser, buffer[0]));
+                            util_htset(parser->aliases, me[1]->name, parser_find_global(parser, buffer[1]));
+                            util_htset(parser->aliases, me[2]->name, parser_find_global(parser, buffer[2]));
+
+                            mem_d(buffer[0]);
+                            mem_d(buffer[1]);
+                            mem_d(buffer[2]);
+                        }
+                    }
+                }
+            } else {
+                if (is_static) {
+                    /* a static adds itself to be generated like any other global
+                     * but is added to the local namespace instead
+                     */
+                    char   *defname = NULL;
+                    size_t  prefix_len, ln;
+                    size_t  sn, sn_size;
+
+                    ln = strlen(parser->function->name);
+                    vec_append(defname, ln, parser->function->name);
+
+                    vec_append(defname, 2, "::");
+                    /* remember the length up to here */
+                    prefix_len = vec_size(defname);
+
+                    /* Add it to the local scope */
+                    util_htset(vec_last(parser->variables), var->name, (void*)var);
+
+                    /* now rename the global */
+                    ln = strlen(var->name);
+                    vec_append(defname, ln, var->name);
+                    /* if a variable of that name already existed, add the
+                     * counter value.
+                     * The counter is incremented either way.
+                     */
+                    sn_size = vec_size(parser->function->static_names);
+                    for (sn = 0; sn != sn_size; ++sn) {
+                        if (strcmp(parser->function->static_names[sn], var->name) == 0)
+                            break;
+                    }
+                    if (sn != sn_size) {
+                        char *num = NULL;
+                        int   len = util_asprintf(&num, "#%u", parser->function->static_count);
+                        vec_append(defname, len, num);
+                        mem_d(num);
+                    }
+                    else
+                        vec_push(parser->function->static_names, util_strdup(var->name));
+                    parser->function->static_count++;
+                    ast_value_set_name(var, defname);
+
+                    /* push it to the to-be-generated globals */
+                    vec_push(parser->globals, (ast_expression*)var);
+
+                    /* same game for the vector members */
+                    if (isvector) {
+                        for (i = 0; i < 3; ++i) {
+                            util_htset(vec_last(parser->variables), me[i]->name, (void*)(me[i]));
+
+                            vec_shrinkto(defname, prefix_len);
+                            ln = strlen(me[i]->name);
+                            vec_append(defname, ln, me[i]->name);
+                            ast_member_set_name(me[i], defname);
+
+                            vec_push(parser->globals, (ast_expression*)me[i]);
+                        }
+                    }
+                    vec_free(defname);
+                } else {
+                    vec_push(localblock->locals, var);
+                    parser_addlocal(parser, var->name, (ast_expression*)var);
+                    if (isvector) {
+                        for (i = 0; i < 3; ++i) {
+                            parser_addlocal(parser, me[i]->name, (ast_expression*)me[i]);
+                            ast_block_collect(localblock, (ast_expression*)me[i]);
+                        }
+                    }
+                }
+            }
+        }
+        memcpy(last_me, me, sizeof(me));
+        me[0] = me[1] = me[2] = NULL;
+        cleanvar = false;
+        /* Part 2.2
+         * deal with arrays
+         */
+        if (var->expression.vtype == TYPE_ARRAY) {
+            if (var->expression.count != (size_t)-1) {
+                if (!create_array_accessors(parser, var))
+                    goto cleanup;
+            }
+        }
+        else if (!localblock && !nofields &&
+                 var->expression.vtype == TYPE_FIELD &&
+                 var->expression.next->vtype == TYPE_ARRAY)
+        {
+            char name[1024];
+            ast_expression *telem;
+            ast_value      *tfield;
+            ast_value      *array = (ast_value*)var->expression.next;
+
+            if (!ast_istype(var->expression.next, ast_value)) {
+                parseerror(parser, "internal error: field element type must be an ast_value");
+                goto cleanup;
+            }
+
+            util_snprintf(name, sizeof(name), "%s##SETF", var->name);
+            if (!parser_create_array_field_setter(parser, array, name))
+                goto cleanup;
+
+            telem = ast_type_copy(ast_ctx(var), array->expression.next);
+            tfield = ast_value_new(ast_ctx(var), "<.type>", TYPE_FIELD);
+            tfield->expression.next = telem;
+            util_snprintf(name, sizeof(name), "%s##GETFP", var->name);
+            if (!parser_create_array_getter(parser, array, (ast_expression*)tfield, name)) {
+                ast_delete(tfield);
+                goto cleanup;
+            }
+            ast_delete(tfield);
+        }
+
+skipvar:
+        if (parser->tok == ';') {
+            ast_delete(basetype);
+            if (!parser_next(parser)) {
+                parseerror(parser, "error after variable declaration");
+                return false;
+            }
+            return true;
+        }
+
+        if (parser->tok == ',')
+            goto another;
+
+        /*
+        if (!var || (!localblock && !nofields && basetype->expression.vtype == TYPE_FIELD)) {
+        */
+        if (!var) {
+            parseerror(parser, "missing comma or semicolon while parsing variables");
+            break;
+        }
+
+        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) )
+            {
+                break;
+            }
+        }
+
+        if (parser->tok != '{' || var->expression.vtype != TYPE_FUNCTION) {
+            if (parser->tok != '=') {
+                parseerror(parser, "missing semicolon or initializer, got: `%s`", parser_tokval(parser));
+                break;
+            }
+
+            if (!parser_next(parser)) {
+                parseerror(parser, "error parsing initializer");
+                break;
+            }
+        }
+        else if (OPTS_OPTION_U32(OPTION_STANDARD) == COMPILER_QCC) {
+            parseerror(parser, "expected '=' before function body in this standard");
+        }
+
+        if (parser->tok == '#') {
+            ast_function *func   = NULL;
+            ast_value    *number = NULL;
+            float         fractional;
+            float         integral;
+            int           builtin_num;
+
+            if (localblock) {
+                parseerror(parser, "cannot declare builtins within functions");
+                break;
+            }
+            if (var->expression.vtype != TYPE_FUNCTION) {
+                parseerror(parser, "unexpected builtin number, '%s' is not a function", var->name);
+                break;
+            }
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected builtin number");
+                break;
+            }
+
+            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)) {
+                number = (ast_value*)parse_expression_leave(parser, true, false, false);
+                if (!number) {
+                    parseerror(parser, "builtin number expected");
+                    break;
+                }
+                if (!ast_istype(number, ast_value) || !number->hasvalue || number->cvq != CV_CONST)
+                {
+                    ast_unref(number);
+                    parseerror(parser, "builtin number must be a compile time constant");
+                    break;
+                }
+                if (number->expression.vtype == TYPE_INTEGER)
+                    builtin_num = number->constval.vint;
+                else if (number->expression.vtype == TYPE_FLOAT)
+                    builtin_num = number->constval.vfloat;
+                else {
+                    ast_unref(number);
+                    parseerror(parser, "builtin number must be an integer constant");
+                    break;
+                }
+                ast_unref(number);
+
+                fractional = modff(builtin_num, &integral);
+                if (builtin_num < 0 || fractional != 0) {
+                    parseerror(parser, "builtin number must be an integer greater than zero");
+                    break;
+                }
+
+                /* we only want the integral part anyways */
+                builtin_num = integral;
+            } else if (parser->tok == TOKEN_INTCONST) {
+                builtin_num = parser_token(parser)->constval.i;
+            } else {
+                parseerror(parser, "builtin number must be a compile time constant");
+                break;
+            }
+
+            if (var->hasvalue) {
+                (void)!parsewarning(parser, WARN_DOUBLE_DECLARATION,
+                                    "builtin `%s` has already been defined\n"
+                                    " -> previous declaration here: %s:%i",
+                                    var->name, ast_ctx(var).file, (int)ast_ctx(var).line);
+            }
+            else
+            {
+                func = ast_function_new(ast_ctx(var), var->name, var);
+                if (!func) {
+                    parseerror(parser, "failed to allocate function for `%s`", var->name);
+                    break;
+                }
+                vec_push(parser->functions, func);
+
+                func->builtin = -builtin_num-1;
+            }
+
+            if (OPTS_FLAG(EXPRESSIONS_FOR_BUILTINS)
+                    ? (parser->tok != ',' && parser->tok != ';')
+                    : (!parser_next(parser)))
+            {
+                parseerror(parser, "expected comma or semicolon");
+                if (func)
+                    ast_function_delete(func);
+                var->constval.vfunc = NULL;
+                break;
+            }
+        }
+        else if (var->expression.vtype == TYPE_ARRAY && parser->tok == '{')
+        {
+            if (localblock) {
+                /* Note that fteqcc and most others don't even *have*
+                 * local arrays, so this is not a high priority.
+                 */
+                parseerror(parser, "TODO: initializers for local arrays");
+                break;
+            }
+
+            var->hasvalue = true;
+            if (!parse_array(parser, var))
+                break;
+        }
+        else if (var->expression.vtype == TYPE_FUNCTION && (parser->tok == '{' || parser->tok == '['))
+        {
+            if (localblock) {
+                parseerror(parser, "cannot declare functions within functions");
+                break;
+            }
+
+            if (proto)
+                ast_ctx(proto) = parser_ctx(parser);
+
+            if (!parse_function_body(parser, var))
+                break;
+            ast_delete(basetype);
+            for (i = 0; i < vec_size(parser->gotos); ++i)
+                parseerror(parser, "undefined label: `%s`", parser->gotos[i]->name);
+            vec_free(parser->gotos);
+            vec_free(parser->labels);
+            return true;
+        } else {
+            ast_expression *cexp;
+            ast_value      *cval;
+            bool            folded_const = false;
+
+            cexp = parse_expression_leave(parser, true, false, false);
+            if (!cexp)
+                break;
+            cval = ast_istype(cexp, ast_value) ? (ast_value*)cexp : NULL;
+
+            /* deal with foldable constants: */
+            if (localblock &&
+                var->cvq == CV_CONST && cval && cval->hasvalue && cval->cvq == CV_CONST && !cval->isfield)
+            {
+                /* remove it from the current locals */
+                if (isvector) {
+                    for (i = 0; i < 3; ++i) {
+                        vec_pop(parser->_locals);
+                        vec_pop(localblock->collect);
+                    }
+                }
+                /* do sanity checking, this function really needs refactoring */
+                if (vec_last(parser->_locals) != (ast_expression*)var)
+                    parseerror(parser, "internal error: unexpected change in local variable handling");
+                else
+                    vec_pop(parser->_locals);
+                if (vec_last(localblock->locals) != var)
+                    parseerror(parser, "internal error: unexpected change in local variable handling (2)");
+                else
+                    vec_pop(localblock->locals);
+                /* push it to the to-be-generated globals */
+                vec_push(parser->globals, (ast_expression*)var);
+                if (isvector)
+                    for (i = 0; i < 3; ++i)
+                        vec_push(parser->globals, (ast_expression*)last_me[i]);
+                folded_const = true;
+            }
+
+            if (folded_const || !localblock || is_static) {
+                if (cval != parser->nil &&
+                    (!cval || ((!cval->hasvalue || cval->cvq != CV_CONST) && !cval->isfield))
+                   )
+                {
+                    parseerror(parser, "initializer is non constant");
+                }
+                else
+                {
+                    if (!is_static &&
+                        !OPTS_FLAG(INITIALIZED_NONCONSTANTS) &&
+                        qualifier != CV_VAR)
+                    {
+                        var->cvq = CV_CONST;
+                    }
+                    if (cval == parser->nil)
+                        var->expression.flags |= AST_FLAG_INITIALIZED;
+                    else
+                    {
+                        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 {
+                int cvq;
+                shunt sy = { NULL, NULL, NULL, NULL };
+                cvq = var->cvq;
+                var->cvq = CV_NONE;
+                vec_push(sy.out, syexp(ast_ctx(var), (ast_expression*)var));
+                vec_push(sy.out, syexp(ast_ctx(cexp), (ast_expression*)cexp));
+                vec_push(sy.ops, syop(ast_ctx(var), parser->assign_op));
+                if (!parser_sy_apply_operator(parser, &sy))
+                    ast_unref(cexp);
+                else {
+                    if (vec_size(sy.out) != 1 && vec_size(sy.ops) != 0)
+                        parseerror(parser, "internal error: leaked operands");
+                    if (!ast_block_add_expr(localblock, (ast_expression*)sy.out[0].out))
+                        break;
+                }
+                vec_free(sy.out);
+                vec_free(sy.ops);
+                vec_free(sy.argc);
+                var->cvq = cvq;
+            }
+            /* a constant initialized to an inexact value should be marked inexact:
+             * const float x = <inexact>; should propagate the inexact flag
+             */
+            if (var->cvq == CV_CONST && var->expression.vtype == TYPE_FLOAT) {
+                if (cval && cval->hasvalue && cval->cvq == CV_CONST)
+                    var->inexact = cval->inexact;
+            }
+        }
+
+another:
+        if (parser->tok == ',') {
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected another variable");
+                break;
+            }
+
+            if (parser->tok != TOKEN_IDENT) {
+                parseerror(parser, "expected another variable");
+                break;
+            }
+            var = ast_value_copy(basetype);
+            cleanvar = true;
+            ast_value_set_name(var, parser_tokval(parser));
+            if (!parser_next(parser)) {
+                parseerror(parser, "error parsing variable declaration");
+                break;
+            }
+            continue;
+        }
+
+        if (parser->tok != ';') {
+            parseerror(parser, "missing semicolon after variables");
+            break;
+        }
+
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error after variable declaration");
+            break;
+        }
+
+        ast_delete(basetype);
+        return true;
+    }
+
+    if (cleanvar && var)
+        ast_delete(var);
+    ast_delete(basetype);
+    return false;
+
+cleanup:
+    ast_delete(basetype);
+    if (cleanvar && var)
+        ast_delete(var);
+    if (me[0]) ast_member_delete(me[0]);
+    if (me[1]) ast_member_delete(me[1]);
+    if (me[2]) ast_member_delete(me[2]);
+    return retval;
+}
+
+static bool parser_global_statement(parser_t *parser)
+{
+    int        cvq       = CV_WRONG;
+    bool       noref     = 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 == '.' || parser->tok == TOKEN_DOTS)
+    {
+        return parse_variable(parser, NULL, false, CV_NONE, istype, false, false, 0, NULL);
+    }
+    else if (parse_qualifiers(parser, false, &cvq, &noref, &is_static, &qflags, &vstring))
+    {
+        if (cvq == CV_WRONG)
+            return false;
+        return parse_variable(parser, NULL, false, cvq, NULL, noref, is_static, qflags, vstring);
+    }
+    else if (parser->tok == TOKEN_IDENT && !strcmp(parser_tokval(parser), "enum"))
+    {
+        return parse_enum(parser);
+    }
+    else if (parser->tok == TOKEN_KEYWORD)
+    {
+        if (!strcmp(parser_tokval(parser), "typedef")) {
+            if (!parser_next(parser)) {
+                parseerror(parser, "expected type definition after 'typedef'");
+                return false;
+            }
+            return parse_typedef(parser);
+        }
+        parseerror(parser, "unrecognized keyword `%s`", parser_tokval(parser));
+        return false;
+    }
+    else if (parser->tok == '#')
+    {
+        return parse_pragma(parser);
+    }
+    else if (parser->tok == '$')
+    {
+        if (!parser_next(parser)) {
+            parseerror(parser, "parse error");
+            return false;
+        }
+    }
+    else
+    {
+        parseerror(parser, "unexpected token: `%s`", parser->lex->tok.value);
+        return false;
+    }
+    return true;
+}
+
+static uint16_t progdefs_crc_sum(uint16_t old, const char *str)
+{
+    return util_crc16(old, str, strlen(str));
+}
+
+static void progdefs_crc_file(const char *str)
+{
+    /* write to progdefs.h here */
+    (void)str;
+}
+
+static uint16_t progdefs_crc_both(uint16_t old, const char *str)
+{
+    old = progdefs_crc_sum(old, str);
+    progdefs_crc_file(str);
+    return old;
+}
+
+static void generate_checksum(parser_t *parser, ir_builder *ir)
+{
+    uint16_t   crc = 0xFFFF;
+    size_t     i;
+    ast_value *value;
+
+    crc = progdefs_crc_both(crc, "\n/* file generated by qcc, do not modify */\n\ntypedef struct\n{");
+    crc = progdefs_crc_sum(crc, "\tint\tpad[28];\n");
+    /*
+    progdefs_crc_file("\tint\tpad;\n");
+    progdefs_crc_file("\tint\tofs_return[3];\n");
+    progdefs_crc_file("\tint\tofs_parm0[3];\n");
+    progdefs_crc_file("\tint\tofs_parm1[3];\n");
+    progdefs_crc_file("\tint\tofs_parm2[3];\n");
+    progdefs_crc_file("\tint\tofs_parm3[3];\n");
+    progdefs_crc_file("\tint\tofs_parm4[3];\n");
+    progdefs_crc_file("\tint\tofs_parm5[3];\n");
+    progdefs_crc_file("\tint\tofs_parm6[3];\n");
+    progdefs_crc_file("\tint\tofs_parm7[3];\n");
+    */
+    for (i = 0; i < parser->crc_globals; ++i) {
+        if (!ast_istype(parser->globals[i], ast_value))
+            continue;
+        value = (ast_value*)(parser->globals[i]);
+        switch (value->expression.vtype) {
+            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
+            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
+            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
+            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
+            default:
+                crc = progdefs_crc_both(crc, "\tint\t");
+                break;
+        }
+        crc = progdefs_crc_both(crc, value->name);
+        crc = progdefs_crc_both(crc, ";\n");
+    }
+    crc = progdefs_crc_both(crc, "} globalvars_t;\n\ntypedef struct\n{\n");
+    for (i = 0; i < parser->crc_fields; ++i) {
+        if (!ast_istype(parser->fields[i], ast_value))
+            continue;
+        value = (ast_value*)(parser->fields[i]);
+        switch (value->expression.next->vtype) {
+            case TYPE_FLOAT:    crc = progdefs_crc_both(crc, "\tfloat\t"); break;
+            case TYPE_VECTOR:   crc = progdefs_crc_both(crc, "\tvec3_t\t"); break;
+            case TYPE_STRING:   crc = progdefs_crc_both(crc, "\tstring_t\t"); break;
+            case TYPE_FUNCTION: crc = progdefs_crc_both(crc, "\tfunc_t\t"); break;
+            default:
+                crc = progdefs_crc_both(crc, "\tint\t");
+                break;
+        }
+        crc = progdefs_crc_both(crc, value->name);
+        crc = progdefs_crc_both(crc, ";\n");
+    }
+    crc = progdefs_crc_both(crc, "} entvars_t;\n\n");
+    ir->code->crc = crc;
+}
+
+parser_t *parser_create()
+{
+    parser_t *parser;
+    lex_ctx_t empty_ctx;
+    size_t i;
+
+    parser = (parser_t*)mem_a(sizeof(parser_t));
+    if (!parser)
+        return NULL;
+
+    memset(parser, 0, sizeof(*parser));
+
+    for (i = 0; i < operator_count; ++i) {
+        if (operators[i].id == opid1('=')) {
+            parser->assign_op = operators+i;
+            break;
+        }
+    }
+    if (!parser->assign_op) {
+        con_err("internal error: initializing parser: failed to find assign operator\n");
+        mem_d(parser);
+        return NULL;
+    }
+
+    vec_push(parser->variables, parser->htfields  = util_htnew(PARSER_HT_SIZE));
+    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);
+
+    parser->aliases = util_htnew(PARSER_HT_SIZE);
+
+    empty_ctx.file   = "<internal>";
+    empty_ctx.line   = 0;
+    empty_ctx.column = 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);
+
+    parser->max_param_count = 1;
+
+    parser->const_vec[0] = ast_value_new(empty_ctx, "<vector.x>", TYPE_NOEXPR);
+    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_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;
+        parser->reserved_version->expression.flags |= AST_FLAG_INCLUDE_DEF;
+        parser->reserved_version->constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING);
+    } else {
+        parser->reserved_version = NULL;
+    }
+
+    parser->fold   = fold_init  (parser);
+    parser->intrin = intrin_init(parser);
+    return parser;
+}
+
+static bool parser_compile(parser_t *parser)
+{
+    /* initial lexer/parser state */
+    parser->lex->flags.noops = true;
+
+    if (parser_next(parser))
+    {
+        while (parser->tok != TOKEN_EOF && parser->tok < TOKEN_ERROR)
+        {
+            if (!parser_global_statement(parser)) {
+                if (parser->tok == TOKEN_EOF)
+                    parseerror(parser, "unexpected end of file");
+                else if (compile_errors)
+                    parseerror(parser, "there have been errors, bailing out");
+                lex_close(parser->lex);
+                parser->lex = NULL;
+                return false;
+            }
+        }
+    } else {
+        parseerror(parser, "parse error");
+        lex_close(parser->lex);
+        parser->lex = NULL;
+        return false;
+    }
+
+    lex_close(parser->lex);
+    parser->lex = NULL;
+
+    return !compile_errors;
+}
+
+bool parser_compile_file(parser_t *parser, const char *filename)
+{
+    parser->lex = lex_open(filename);
+    if (!parser->lex) {
+        con_err("failed to open file \"%s\"\n", filename);
+        return false;
+    }
+    return parser_compile(parser);
+}
+
+bool parser_compile_string(parser_t *parser, const char *name, const char *str, size_t len)
+{
+    parser->lex = lex_open_string(str, len, name);
+    if (!parser->lex) {
+        con_err("failed to create lexer for string \"%s\"\n", name);
+        return false;
+    }
+    return parser_compile(parser);
+}
+
+static void parser_remove_ast(parser_t *parser)
+{
+    size_t i;
+    if (parser->ast_cleaned)
+        return;
+    parser->ast_cleaned = true;
+    for (i = 0; i < vec_size(parser->accessors); ++i) {
+        ast_delete(parser->accessors[i]->constval.vfunc);
+        parser->accessors[i]->constval.vfunc = NULL;
+        ast_delete(parser->accessors[i]);
+    }
+    for (i = 0; i < vec_size(parser->functions); ++i) {
+        ast_delete(parser->functions[i]);
+    }
+    for (i = 0; i < vec_size(parser->fields); ++i) {
+        ast_delete(parser->fields[i]);
+    }
+    for (i = 0; i < vec_size(parser->globals); ++i) {
+        ast_delete(parser->globals[i]);
+    }
+    vec_free(parser->accessors);
+    vec_free(parser->functions);
+    vec_free(parser->globals);
+    vec_free(parser->fields);
+
+    for (i = 0; i < vec_size(parser->variables); ++i)
+        util_htdel(parser->variables[i]);
+    vec_free(parser->variables);
+    vec_free(parser->_blocklocals);
+    vec_free(parser->_locals);
+
+    for (i = 0; i < vec_size(parser->_typedefs); ++i)
+        ast_delete(parser->_typedefs[i]);
+    vec_free(parser->_typedefs);
+    for (i = 0; i < vec_size(parser->typedefs); ++i)
+        util_htdel(parser->typedefs[i]);
+    vec_free(parser->typedefs);
+    vec_free(parser->_blocktypedefs);
+
+    vec_free(parser->_block_ctx);
+
+    vec_free(parser->labels);
+    vec_free(parser->gotos);
+    vec_free(parser->breaks);
+    vec_free(parser->continues);
+
+    ast_value_delete(parser->nil);
+
+    ast_value_delete(parser->const_vec[0]);
+    ast_value_delete(parser->const_vec[1]);
+    ast_value_delete(parser->const_vec[2]);
+
+    if (parser->reserved_version)
+        ast_value_delete(parser->reserved_version);
+
+    util_htdel(parser->aliases);
+    fold_cleanup(parser->fold);
+    intrin_cleanup(parser->intrin);
+}
+
+void parser_cleanup(parser_t *parser)
+{
+    parser_remove_ast(parser);
+    mem_d(parser);
+}
+
+static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) {
+    size_t          i;
+    ast_expression *expr;
+    ast_value      *cov;
+    ast_function   *func;
+
+    if (!OPTS_OPTION_BOOL(OPTION_COVERAGE))
+        return true;
+
+    func = NULL;
+    for (i = 0; i != vec_size(parser->functions); ++i) {
+        if (!strcmp(parser->functions[i]->name, "coverage")) {
+            func = parser->functions[i];
+            break;
+        }
+    }
+    if (!func) {
+        if (OPTS_OPTION_BOOL(OPTION_COVERAGE)) {
+            con_out("coverage support requested but no coverage() builtin declared\n");
+            ir_builder_delete(ir);
+            return false;
+        }
+        return true;
+    }
+
+    cov  = func->vtype;
+    expr = (ast_expression*)cov;
+
+    if (expr->vtype != TYPE_FUNCTION || vec_size(expr->params) != 0) {
+        char ty[1024];
+        ast_type_to_string(expr, ty, sizeof(ty));
+        con_out("invalid type for coverage(): %s\n", ty);
+        ir_builder_delete(ir);
+        return false;
+    }
+
+    ir->coverage_func = func->ir_func->value;
+    return true;
+}
+
+bool parser_finish(parser_t *parser, const char *output)
+{
+    size_t          i;
+    ir_builder     *ir;
+    bool            retval = true;
+
+    if (compile_errors) {
+        con_out("*** there were compile errors\n");
+        return false;
+    }
+
+    ir = ir_builder_new("gmqcc_out");
+    if (!ir) {
+        con_out("failed to allocate builder\n");
+        return false;
+    }
+
+    for (i = 0; i < vec_size(parser->fields); ++i) {
+        ast_value *field;
+        bool hasvalue;
+        if (!ast_istype(parser->fields[i], ast_value))
+            continue;
+        field = (ast_value*)parser->fields[i];
+        hasvalue = field->hasvalue;
+        field->hasvalue = false;
+        if (!ast_global_codegen((ast_value*)field, ir, true)) {
+            con_out("failed to generate field %s\n", field->name);
+            ir_builder_delete(ir);
+            return false;
+        }
+        if (hasvalue) {
+            ir_value *ifld;
+            ast_expression *subtype;
+            field->hasvalue = true;
+            subtype = field->expression.next;
+            ifld = ir_builder_create_field(ir, field->name, subtype->vtype);
+            if (subtype->vtype == TYPE_FIELD)
+                ifld->fieldtype = subtype->next->vtype;
+            else if (subtype->vtype == TYPE_FUNCTION)
+                ifld->outtype = subtype->next->vtype;
+            (void)!ir_value_set_field(field->ir_v, ifld);
+        }
+    }
+    for (i = 0; i < vec_size(parser->globals); ++i) {
+        ast_value *asvalue;
+        if (!ast_istype(parser->globals[i], ast_value))
+            continue;
+        asvalue = (ast_value*)(parser->globals[i]);
+        if (!asvalue->uses && !asvalue->hasvalue && asvalue->expression.vtype != TYPE_FUNCTION) {
+            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);
+            ir_builder_delete(ir);
+            return false;
+        }
+    }
+    /* Build function vararg accessor ast tree now before generating
+     * immediates, because the accessors may add new immediates
+     */
+    for (i = 0; i < vec_size(parser->functions); ++i) {
+        ast_function *f = parser->functions[i];
+        if (f->varargs) {
+            if (parser->max_param_count > vec_size(f->vtype->expression.params)) {
+                f->varargs->expression.count = parser->max_param_count - vec_size(f->vtype->expression.params);
+                if (!parser_create_array_setter_impl(parser, f->varargs)) {
+                    con_out("failed to generate vararg setter for %s\n", f->name);
+                    ir_builder_delete(ir);
+                    return false;
+                }
+                if (!parser_create_array_getter_impl(parser, f->varargs)) {
+                    con_out("failed to generate vararg getter for %s\n", f->name);
+                    ir_builder_delete(ir);
+                    return false;
+                }
+            } else {
+                ast_delete(f->varargs);
+                f->varargs = NULL;
+            }
+        }
+    }
+    /* Now we can generate immediates */
+    if (!fold_generate(parser->fold, ir))
+        return false;
+
+    /* before generating any functions we need to set the coverage_func */
+    if (!parser_set_coverage_func(parser, ir))
+        return false;
+
+    for (i = 0; i < vec_size(parser->globals); ++i) {
+        ast_value *asvalue;
+        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;
+        }
+    }
+    for (i = 0; i < vec_size(parser->fields); ++i) {
+        ast_value *asvalue;
+        asvalue = (ast_value*)(parser->fields[i]->next);
+
+        if (!ast_istype((ast_expression*)asvalue, ast_value))
+            continue;
+        if (asvalue->expression.vtype != TYPE_ARRAY)
+            continue;
+        if (!ast_generate_accessors(asvalue, ir)) {
+            ir_builder_delete(ir);
+            return false;
+        }
+    }
+    if (parser->reserved_version &&
+        !ast_global_codegen(parser->reserved_version, ir, false))
+    {
+        con_out("failed to generate reserved::version");
+        ir_builder_delete(ir);
+        return false;
+    }
+    for (i = 0; i < vec_size(parser->functions); ++i) {
+        ast_function *f = parser->functions[i];
+        if (!ast_function_codegen(f, ir)) {
+            con_out("failed to generate function %s\n", f->name);
+            ir_builder_delete(ir);
+            return false;
+        }
+    }
+
+    generate_checksum(parser, ir);
+
+    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)) {
+            con_out("failed to finalize function %s\n", parser->functions[i]->name);
+            ir_builder_delete(ir);
+            return false;
+        }
+    }
+    parser_remove_ast(parser);
+
+    if (compile_Werrors) {
+        con_out("*** there were warnings treated as errors\n");
+        compile_show_werrors();
+        retval = false;
+    }
+
+    if (retval) {
+        if (OPTS_OPTION_BOOL(OPTION_DUMPFIN))
+            ir_builder_dump(ir, con_out);
+
+        if (!ir_builder_generate(ir, output)) {
+            con_out("*** failed to generate output file\n");
+            ir_builder_delete(ir);
+            return false;
+        }
+    }
+    ir_builder_delete(ir);
+    return retval;
+}
diff --git a/stat.c b/stat.c
deleted file mode 100644 (file)
index 1e171e8..0000000
--- a/stat.c
+++ /dev/null
@@ -1,249 +0,0 @@
-#include <string.h>
-#include <stdlib.h>
-
-#include "gmqcc.h"
-
-/*
- * strdup does it's own malloc, we need to track malloc. We don't want
- * to overwrite malloc though, infact, we can't really hook it at all
- * without library specific assumptions. So we re implement strdup.
- */
-char *stat_mem_strdup(const char *src, bool empty) {
-    size_t len = 0;
-    char *ptr = NULL;
-
-    if (!src)
-        return NULL;
-
-    len = strlen(src);
-    if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) {
-        memcpy(ptr, src, len);
-        ptr[len] = '\0';
-    }
-
-    return ptr;
-}
-
-/*
- * The reallocate function for resizing vectors.
- */
-void _util_vec_grow(void **a, size_t i, size_t s) {
-    vector_t *d = vec_meta(*a);
-    size_t m = 0;
-    void *p = NULL;
-
-    if (*a) {
-        m = 2 * d->allocated + i;
-        p = mem_r(d, s * m + sizeof(vector_t));
-    } else {
-        m = i + 1;
-        p = mem_a(s * m + sizeof(vector_t));
-        ((vector_t*)p)->used = 0;
-    }
-
-    d = (vector_t*)p;
-    d->allocated = m;
-    *a = d + 1;
-}
-
-void _util_vec_delete(void *data) {
-    mem_d(vec_meta(data));
-}
-
-/*
- * Hash table for generic data, based on dynamic memory allocations
- * all around.  This is the internal interface, please look for
- * EXPOSED INTERFACE comment below
- */
-typedef struct hash_node_t {
-    char               *key;   /* the key for this node in table */
-    void               *value; /* pointer to the data as void*   */
-    struct hash_node_t *next;  /* next node (linked list)        */
-} hash_node_t;
-
-size_t hash(const char *key);
-
-size_t util_hthash(hash_table_t *ht, const char *key) {
-    return hash(key) % ht->size;
-}
-
-static hash_node_t *_util_htnewpair(const char *key, void *value) {
-    hash_node_t *node;
-    if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
-        return NULL;
-
-    if (!(node->key = util_strdupe(key))) {
-        mem_d(node);
-        return NULL;
-    }
-
-    node->value = value;
-    node->next  = NULL;
-
-    return node;
-}
-
-/*
- * EXPOSED INTERFACE for the hashtable implementation
- * util_htnew(size)                             -- to make a new hashtable
- * util_htset(table, key, value, sizeof(value)) -- to set something in the table
- * util_htget(table, key)                       -- to get something from the table
- * util_htdel(table)                            -- to delete the table
- */
-hash_table_t *util_htnew(size_t size) {
-    hash_table_t *hashtable = NULL;
-
-    if (size < 1)
-        return NULL;
-
-    if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
-        return NULL;
-
-    if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
-        mem_d(hashtable);
-        return NULL;
-    }
-
-    hashtable->size = size;
-    memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
-
-    return hashtable;
-}
-
-void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
-    hash_node_t *newnode = NULL;
-    hash_node_t *next    = NULL;
-    hash_node_t *last    = NULL;
-
-    next = ht->table[bin];
-
-    while (next && next->key && strcmp(key, next->key) > 0)
-        last = next, next = next->next;
-
-    /* already in table, do a replace */
-    if (next && next->key && strcmp(key, next->key) == 0) {
-        next->value = value;
-    } else {
-        /* not found, grow a pair man :P */
-        newnode = _util_htnewpair(key, value);
-        if (next == ht->table[bin]) {
-            newnode->next  = next;
-            ht->table[bin] = newnode;
-        } else if (!next) {
-            last->next = newnode;
-        } else {
-            newnode->next = next;
-            last->next = newnode;
-        }
-    }
-}
-
-void util_htset(hash_table_t *ht, const char *key, void *value) {
-    util_htseth(ht, key, util_hthash(ht, key), value);
-}
-
-void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
-    hash_node_t *pair = ht->table[bin];
-
-    while (pair && pair->key && strcmp(key, pair->key) > 0)
-        pair = pair->next;
-
-    if (!pair || !pair->key || strcmp(key, pair->key) != 0)
-        return NULL;
-
-    return pair->value;
-}
-
-void *util_htget(hash_table_t *ht, const char *key) {
-    return util_htgeth(ht, key, util_hthash(ht, key));
-}
-
-void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
-void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) {
-    hash_node_t *pair;
-    size_t len, keylen;
-    int cmp;
-
-    keylen = strlen(key);
-
-    pair = ht->table[bin];
-    while (pair && pair->key) {
-        len = strlen(pair->key);
-        if (len < keylen) {
-            pair = pair->next;
-            continue;
-        }
-        if (keylen == len) {
-            cmp = strcmp(key, pair->key);
-            if (cmp == 0)
-                return pair->value;
-            if (cmp < 0)
-                return NULL;
-            pair = pair->next;
-            continue;
-        }
-        cmp = strcmp(key, pair->key + len - keylen);
-        if (cmp == 0) {
-            uintptr_t up = (uintptr_t)pair->value;
-            up += len - keylen;
-            return (void*)up;
-        }
-        pair = pair->next;
-    }
-    return NULL;
-}
-
-/*
- * Free all allocated data in a hashtable, this is quite the amount
- * of work.
- */
-void util_htrem(hash_table_t *ht, void (*callback)(void *data)) {
-    size_t i = 0;
-
-    for (; i < ht->size; ++i) {
-        hash_node_t *n = ht->table[i];
-        hash_node_t *p;
-
-        /* free in list */
-        while (n) {
-            if (n->key)
-                mem_d(n->key);
-            if (callback)
-                callback(n->value);
-            p = n;
-            n = p->next;
-            mem_d(p);
-        }
-
-    }
-    /* free table */
-    mem_d(ht->table);
-    mem_d(ht);
-}
-
-void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) {
-    hash_node_t **pair = &ht->table[bin];
-    hash_node_t *tmp;
-
-    while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0)
-        pair = &(*pair)->next;
-
-    tmp = *pair;
-    if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0)
-        return;
-
-    if (cb)
-        (*cb)(tmp->value);
-
-    *pair = tmp->next;
-    mem_d(tmp->key);
-    mem_d(tmp);
-}
-
-void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) {
-    util_htrmh(ht, key, util_hthash(ht, key), cb);
-}
-
-void util_htdel(hash_table_t *ht) {
-    util_htrem(ht, NULL);
-}
diff --git a/stat.cpp b/stat.cpp
new file mode 100644 (file)
index 0000000..1e171e8
--- /dev/null
+++ b/stat.cpp
@@ -0,0 +1,249 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "gmqcc.h"
+
+/*
+ * strdup does it's own malloc, we need to track malloc. We don't want
+ * to overwrite malloc though, infact, we can't really hook it at all
+ * without library specific assumptions. So we re implement strdup.
+ */
+char *stat_mem_strdup(const char *src, bool empty) {
+    size_t len = 0;
+    char *ptr = NULL;
+
+    if (!src)
+        return NULL;
+
+    len = strlen(src);
+    if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) {
+        memcpy(ptr, src, len);
+        ptr[len] = '\0';
+    }
+
+    return ptr;
+}
+
+/*
+ * The reallocate function for resizing vectors.
+ */
+void _util_vec_grow(void **a, size_t i, size_t s) {
+    vector_t *d = vec_meta(*a);
+    size_t m = 0;
+    void *p = NULL;
+
+    if (*a) {
+        m = 2 * d->allocated + i;
+        p = mem_r(d, s * m + sizeof(vector_t));
+    } else {
+        m = i + 1;
+        p = mem_a(s * m + sizeof(vector_t));
+        ((vector_t*)p)->used = 0;
+    }
+
+    d = (vector_t*)p;
+    d->allocated = m;
+    *a = d + 1;
+}
+
+void _util_vec_delete(void *data) {
+    mem_d(vec_meta(data));
+}
+
+/*
+ * Hash table for generic data, based on dynamic memory allocations
+ * all around.  This is the internal interface, please look for
+ * EXPOSED INTERFACE comment below
+ */
+typedef struct hash_node_t {
+    char               *key;   /* the key for this node in table */
+    void               *value; /* pointer to the data as void*   */
+    struct hash_node_t *next;  /* next node (linked list)        */
+} hash_node_t;
+
+size_t hash(const char *key);
+
+size_t util_hthash(hash_table_t *ht, const char *key) {
+    return hash(key) % ht->size;
+}
+
+static hash_node_t *_util_htnewpair(const char *key, void *value) {
+    hash_node_t *node;
+    if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t))))
+        return NULL;
+
+    if (!(node->key = util_strdupe(key))) {
+        mem_d(node);
+        return NULL;
+    }
+
+    node->value = value;
+    node->next  = NULL;
+
+    return node;
+}
+
+/*
+ * EXPOSED INTERFACE for the hashtable implementation
+ * util_htnew(size)                             -- to make a new hashtable
+ * util_htset(table, key, value, sizeof(value)) -- to set something in the table
+ * util_htget(table, key)                       -- to get something from the table
+ * util_htdel(table)                            -- to delete the table
+ */
+hash_table_t *util_htnew(size_t size) {
+    hash_table_t *hashtable = NULL;
+
+    if (size < 1)
+        return NULL;
+
+    if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t))))
+        return NULL;
+
+    if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) {
+        mem_d(hashtable);
+        return NULL;
+    }
+
+    hashtable->size = size;
+    memset(hashtable->table, 0, sizeof(hash_node_t*) * size);
+
+    return hashtable;
+}
+
+void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) {
+    hash_node_t *newnode = NULL;
+    hash_node_t *next    = NULL;
+    hash_node_t *last    = NULL;
+
+    next = ht->table[bin];
+
+    while (next && next->key && strcmp(key, next->key) > 0)
+        last = next, next = next->next;
+
+    /* already in table, do a replace */
+    if (next && next->key && strcmp(key, next->key) == 0) {
+        next->value = value;
+    } else {
+        /* not found, grow a pair man :P */
+        newnode = _util_htnewpair(key, value);
+        if (next == ht->table[bin]) {
+            newnode->next  = next;
+            ht->table[bin] = newnode;
+        } else if (!next) {
+            last->next = newnode;
+        } else {
+            newnode->next = next;
+            last->next = newnode;
+        }
+    }
+}
+
+void util_htset(hash_table_t *ht, const char *key, void *value) {
+    util_htseth(ht, key, util_hthash(ht, key), value);
+}
+
+void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) {
+    hash_node_t *pair = ht->table[bin];
+
+    while (pair && pair->key && strcmp(key, pair->key) > 0)
+        pair = pair->next;
+
+    if (!pair || !pair->key || strcmp(key, pair->key) != 0)
+        return NULL;
+
+    return pair->value;
+}
+
+void *util_htget(hash_table_t *ht, const char *key) {
+    return util_htgeth(ht, key, util_hthash(ht, key));
+}
+
+void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin);
+void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) {
+    hash_node_t *pair;
+    size_t len, keylen;
+    int cmp;
+
+    keylen = strlen(key);
+
+    pair = ht->table[bin];
+    while (pair && pair->key) {
+        len = strlen(pair->key);
+        if (len < keylen) {
+            pair = pair->next;
+            continue;
+        }
+        if (keylen == len) {
+            cmp = strcmp(key, pair->key);
+            if (cmp == 0)
+                return pair->value;
+            if (cmp < 0)
+                return NULL;
+            pair = pair->next;
+            continue;
+        }
+        cmp = strcmp(key, pair->key + len - keylen);
+        if (cmp == 0) {
+            uintptr_t up = (uintptr_t)pair->value;
+            up += len - keylen;
+            return (void*)up;
+        }
+        pair = pair->next;
+    }
+    return NULL;
+}
+
+/*
+ * Free all allocated data in a hashtable, this is quite the amount
+ * of work.
+ */
+void util_htrem(hash_table_t *ht, void (*callback)(void *data)) {
+    size_t i = 0;
+
+    for (; i < ht->size; ++i) {
+        hash_node_t *n = ht->table[i];
+        hash_node_t *p;
+
+        /* free in list */
+        while (n) {
+            if (n->key)
+                mem_d(n->key);
+            if (callback)
+                callback(n->value);
+            p = n;
+            n = p->next;
+            mem_d(p);
+        }
+
+    }
+    /* free table */
+    mem_d(ht->table);
+    mem_d(ht);
+}
+
+void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) {
+    hash_node_t **pair = &ht->table[bin];
+    hash_node_t *tmp;
+
+    while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0)
+        pair = &(*pair)->next;
+
+    tmp = *pair;
+    if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0)
+        return;
+
+    if (cb)
+        (*cb)(tmp->value);
+
+    *pair = tmp->next;
+    mem_d(tmp->key);
+    mem_d(tmp);
+}
+
+void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) {
+    util_htrmh(ht, key, util_hthash(ht, key), cb);
+}
+
+void util_htdel(hash_table_t *ht) {
+    util_htrem(ht, NULL);
+}
diff --git a/test.c b/test.c
deleted file mode 100644 (file)
index 2c985f0..0000000
--- a/test.c
+++ /dev/null
@@ -1,1311 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-
-#include <dirent.h>
-#include <unistd.h>
-
-#include "gmqcc.h"
-
-static const char *task_bins[] = {
-    "./gmqcc",
-    "./qcvm"
-};
-
-typedef struct {
-    FILE *handles[3];
-    int pipes[3];
-    int stderr_fd;
-    int stdout_fd;
-    int pid;
-} popen_t;
-
-static FILE **task_popen(const char *command, const char *mode) {
-    int     inhandle  [2];
-    int     outhandle [2];
-    int     errhandle [2];
-    int     trypipe;
-
-    popen_t *data = (popen_t*)mem_a(sizeof(popen_t));
-
-    /*
-     * Parse the command now into a list for execv, this is a pain
-     * in the ass.
-     */
-    char  *line = (char*)command;
-    char **argv = NULL;
-    {
-
-        while (*line != '\0') {
-            while (*line == ' ' || *line == '\t' || *line == '\n')
-                *line++ = '\0';
-            vec_push(argv, line);
-
-            while (*line != '\0' && *line != ' ' &&
-                   *line != '\t' && *line != '\n') line++;
-        }
-        vec_push(argv, (char *)0);
-    }
-
-
-    if ((trypipe = pipe(inhandle))  < 0) goto task_popen_error_0;
-    if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1;
-    if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2;
-
-    if ((data->pid = fork()) > 0) {
-        /* parent */
-        close(inhandle [0]);
-        close(outhandle [1]);
-        close(errhandle [1]);
-        data->pipes[0] = inhandle [1];
-        data->pipes[1] = outhandle[0];
-        data->pipes[2] = errhandle[0];
-        data->handles[0] = fdopen(inhandle [1], "w");
-        data->handles[1] = fdopen(outhandle[0], mode);
-        data->handles[2] = fdopen(errhandle[0], mode);
-
-        /* sigh */
-        vec_free(argv);
-        return data->handles;
-    } else if (data->pid == 0) {
-        /* child */
-        close(inhandle [1]);
-        close(outhandle[0]);
-        close(errhandle[0]);
-
-        /* see piping documentation for this sillyness :P */
-        dup2(inhandle [0], 0);
-        dup2(outhandle[1], 1);
-        dup2(errhandle[1], 2);
-
-        execvp(*argv, argv);
-        exit(EXIT_FAILURE);
-    } else {
-        /* fork failed */
-        goto task_popen_error_3;
-    }
-
-task_popen_error_3: close(errhandle[0]), close(errhandle[1]);
-task_popen_error_2: close(outhandle[0]), close(outhandle[1]);
-task_popen_error_1: close(inhandle [0]), close(inhandle [1]);
-task_popen_error_0:
-
-    vec_free(argv);
-    return NULL;
-}
-
-static int task_pclose(FILE **handles) {
-    popen_t *data   = (popen_t*)handles;
-    int      status = 0;
-
-    close(data->pipes[0]); /* stdin  */
-    close(data->pipes[1]); /* stdout */
-    close(data->pipes[2]); /* stderr */
-
-    waitpid(data->pid, &status, 0);
-
-    mem_d(data);
-
-    return status;
-}
-
-#define TASK_COMPILE    0
-#define TASK_EXECUTE    1
-/*
- * Task template system:
- *  templates are rules for a specific test, used to create a "task" that
- *  is executed with those set of rules (arguments, and what not). Tests
- *  that don't have a template with them cannot become tasks, since without
- *  the information for that test there is no way to properly "test" them.
- *  Rules for these templates are described in a template file, using a
- *  task template language.
- *
- *  The language is a basic finite statemachine, top-down single-line
- *  description language.
- *
- *  The languge is composed entierly of "tags" which describe a string of
- *  text for a task.  Think of it much like a configuration file.  Except
- *  it's been designed to allow flexibility and future support for prodecual
- *  semantics.
- *
- *  The following "tags" are suported by the language
- *
- *      D:
- *          Used to set a description of the current test, this must be
- *          provided, this tag is NOT optional.
- *
- *      T:
- *          Used to set the procedure for the given task, there are two
- *          options for this:
- *              -compile
- *                  This simply performs compilation only
- *              -execute
- *                  This will perform compilation and execution
- *              -fail
- *                  This will perform compilation, but requires
- *                  the compilation to fail in order to succeed.
- *
- *          This must be provided, this tag is NOT optional.
- *
- *      C:
- *          Used to set the compilation flags for the given task, this
- *          must be provided, this tag is NOT optional.
- *
- *      F:  Used to set some test suite flags, currently the only option
- *          is -no-defs (to including of defs.qh)
- *
- *      E:
- *          Used to set the execution flags for the given task. This tag
- *          must be provided if T == -execute, otherwise it's erroneous
- *          as compilation only takes place.
- *
- *      M:
- *          Used to describe a string of text that should be matched from
- *          the output of executing the task.  If this doesn't match the
- *          task fails.  This tag must be provided if T == -execute, otherwise
- *          it's erroneous as compilation only takes place.
- *
- *      I:
- *          Used to specify the INPUT source file to operate on, this must be
- *          provided, this tag is NOT optional
- *
- *
- *  Notes:
- *      These tags have one-time use, using them more than once will result
- *      in template compilation errors.
- *
- *      Lines beginning with # or // in the template file are comments and
- *      are ignored by the template parser.
- *
- *      Whitespace is optional, with exception to the colon ':' between the
- *      tag and it's assignment value/
- *
- *      The template compiler will detect erronrous tags (optional tags
- *      that need not be set), as well as missing tags, and error accordingly
- *      this will result in the task failing.
- */
-typedef struct {
-    char  *description;
-    char  *compileflags;
-    char  *executeflags;
-    char  *proceduretype;
-    char  *sourcefile;
-    char  *tempfilename;
-    char **comparematch;
-    char  *rulesfile;
-    char  *testflags;
-} task_template_t;
-
-/*
- * This is very much like a compiler code generator :-).  This generates
- * a value from some data observed from the compiler.
- */
-static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) {
-    size_t desclen = 0;
-    size_t filelen = 0;
-    char **destval = NULL;
-
-    if (!tmpl)
-        return false;
-
-    switch(tag) {
-        case 'D': destval = &tmpl->description;    break;
-        case 'T': destval = &tmpl->proceduretype;  break;
-        case 'C': destval = &tmpl->compileflags;   break;
-        case 'E': destval = &tmpl->executeflags;   break;
-        case 'I': destval = &tmpl->sourcefile;     break;
-        case 'F': destval = &tmpl->testflags;      break;
-        default:
-            con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error",
-                "invalid tag `%c:` during code generation\n",
-                tag
-            );
-            return false;
-    }
-
-    /*
-     * Ensure if for the given tag, there already exists a
-     * assigned value.
-     */
-    if (*destval) {
-        con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error",
-            "tag `%c:` already assigned value: %s\n",
-            tag, *destval
-        );
-        return false;
-    }
-
-    /*
-     * Strip any whitespace that might exist in the value for assignments
-     * like "D:      foo"
-     */
-    if (value && *value && (*value == ' ' || *value == '\t'))
-        value++;
-    else if (!value)
-        exit(EXIT_FAILURE);
-
-    /*
-     * Value will contain a newline character at the end, we need to strip
-     * this otherwise kaboom, seriously, kaboom :P
-     */
-    if (strchr(value, '\n'))
-        *strrchr(value, '\n')='\0';
-
-    /*
-     * Now allocate and set the actual value for the specific tag. Which
-     * was properly selected and can be accessed with *destval.
-     */
-    *destval = util_strdup(value);
-
-
-    if (*destval == tmpl->description) {
-        /*
-         * Create some padding for the description to align the
-         * printing of the rules file.
-         */
-        if ((desclen = strlen(tmpl->description)) > pad[0])
-            pad[0] = desclen;
-    }
-
-    if ((filelen = strlen(file)) > pad[2])
-        pad[2] = filelen;
-
-    return true;
-}
-
-static bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) {
-    char  *data = NULL;
-    char  *back = NULL;
-    size_t size = 0;
-    size_t line = 1;
-
-    if (!tmpl)
-        return false;
-
-    /* top down parsing */
-    while (util_getline(&back, &size, fp) != EOF) {
-        /* skip whitespace */
-        data = back;
-        if (*data && (*data == ' ' || *data == '\t'))
-            data++;
-
-        switch (*data) {
-            /*
-             * Handle comments inside task tmpl files.  We're strict
-             * about the language for fun :-)
-             */
-            case '/':
-                if (data[1] != '/') {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                        "invalid character `/`, perhaps you meant `//` ?");
-
-                    mem_d(back);
-                    return false;
-                }
-            case '#':
-                break;
-
-            /*
-             * Empty newlines are acceptable as well, so we handle that here
-             * despite being just odd since there should't be that many
-             * empty lines to begin with.
-             */
-            case '\r':
-            case '\n':
-                break;
-
-
-            /*
-             * Now begin the actual "tag" stuff.  This works as you expect
-             * it to.
-             */
-            case 'D':
-            case 'T':
-            case 'C':
-            case 'E':
-            case 'I':
-            case 'F':
-                if (data[1] != ':') {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                        "expected `:` after `%c`",
-                        *data
-                    );
-                    goto failure;
-                }
-                if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error",
-                        "failed to generate for given task\n"
-                    );
-                    goto failure;
-                }
-                break;
-
-            /*
-             * Match requires it's own system since we allow multiple M's
-             * for multi-line matching.
-             */
-            case 'M':
-            {
-                char *value = &data[3];
-                if (data[1] != ':') {
-                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                        "expected `:` after `%c`",
-                        *data
-                    );
-                    goto failure;
-                }
-
-                /*
-                 * Value will contain a newline character at the end, we need to strip
-                 * this otherwise kaboom, seriously, kaboom :P
-                 */
-                if (strrchr(value, '\n'))
-                    *strrchr(value, '\n')='\0';
-                else /* cppcheck: possible null pointer dereference */
-                    exit(EXIT_FAILURE);
-
-                vec_push(tmpl->comparematch, util_strdup(value));
-
-                break;
-            }
-
-            default:
-                con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
-                    "invalid tag `%c`", *data
-                );
-                goto failure;
-            /* no break required */
-        }
-
-        /* update line and free old sata */
-        line++;
-        mem_d(back);
-        back = NULL;
-    }
-    if (back)
-        mem_d(back);
-    return true;
-
-failure:
-    mem_d (back);
-    return false;
-}
-
-/*
- * Nullifies the template data: used during initialization of a new
- * template and free.
- */
-static void task_template_nullify(task_template_t *tmpl) {
-    if (!tmpl)
-        return;
-
-    tmpl->description    = NULL;
-    tmpl->proceduretype  = NULL;
-    tmpl->compileflags   = NULL;
-    tmpl->executeflags   = NULL;
-    tmpl->comparematch   = NULL;
-    tmpl->sourcefile     = NULL;
-    tmpl->tempfilename   = NULL;
-    tmpl->rulesfile      = NULL;
-    tmpl->testflags      = NULL;
-}
-
-static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) {
-    /* a page should be enough */
-    char             fullfile[4096];
-    size_t           filepadd = 0;
-    FILE       *tempfile = NULL;
-    task_template_t *tmpl     = NULL;
-
-    util_snprintf(fullfile,    sizeof(fullfile), "%s/%s", dir, file);
-
-    tempfile = fopen(fullfile, "r");
-    tmpl     = (task_template_t*)mem_a(sizeof(task_template_t));
-    task_template_nullify(tmpl);
-
-    /*
-     * Create some padding for the printing to align the
-     * printing of the rules file to the console.
-     */
-    if ((filepadd = strlen(fullfile)) > pad[1])
-        pad[1] = filepadd;
-
-    tmpl->rulesfile = util_strdup(fullfile);
-
-    /*
-     * Esnure the file even exists for the task, this is pretty useless
-     * to even do.
-     */
-    if (!tempfile) {
-        con_err("template file: %s does not exist or invalid permissions\n",
-            file
-        );
-        goto failure;
-    }
-
-    if (!task_template_parse(file, tmpl, tempfile, pad)) {
-        con_err("template parse error: error during parsing\n");
-        goto failure;
-    }
-
-    /*
-     * Regardless procedure type, the following tags must exist:
-     *  D
-     *  T
-     *  C
-     *  I
-     */
-    if (!tmpl->description) {
-        con_err("template compile error: %s missing `D:` tag\n", file);
-        goto failure;
-    }
-    if (!tmpl->proceduretype) {
-        con_err("template compile error: %s missing `T:` tag\n", file);
-        goto failure;
-    }
-    if (!tmpl->compileflags) {
-        con_err("template compile error: %s missing `C:` tag\n", file);
-        goto failure;
-    }
-    if (!tmpl->sourcefile) {
-        con_err("template compile error: %s missing `I:` tag\n", file);
-        goto failure;
-    }
-
-    /*
-     * Now lets compile the template, compilation is really just
-     * the process of validating the input.
-     */
-    if (!strcmp(tmpl->proceduretype, "-compile")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file);
-        if (tmpl->comparematch)
-            con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file);
-        goto success;
-    } else if (!strcmp(tmpl->proceduretype, "-execute")) {
-        if (!tmpl->executeflags) {
-            /* default to $null */
-            tmpl->executeflags = util_strdup("$null");
-        }
-        if (!tmpl->comparematch) {
-            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
-            goto failure;
-        }
-    } else if (!strcmp(tmpl->proceduretype, "-fail")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file);
-        if (tmpl->comparematch)
-            con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file);
-    } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file);
-        if (!tmpl->comparematch) {
-            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
-            goto failure;
-        }
-    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
-        if (tmpl->executeflags)
-            con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file);
-        if (!tmpl->comparematch) {
-            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
-            goto failure;
-        }
-    } else {
-        con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype);
-        goto failure;
-    }
-
-success:
-    fclose(tempfile);
-    return tmpl;
-
-failure:
-    /*
-     * The file might not exist and we jump here when that doesn't happen
-     * so the check to see if it's not null here is required.
-     */
-    if (tempfile)
-        fclose(tempfile);
-    mem_d (tmpl);
-
-    return NULL;
-}
-
-static void task_template_destroy(task_template_t *tmpl) {
-    if (!tmpl)
-        return;
-
-    if (tmpl->description)    mem_d(tmpl->description);
-    if (tmpl->proceduretype)  mem_d(tmpl->proceduretype);
-    if (tmpl->compileflags)   mem_d(tmpl->compileflags);
-    if (tmpl->executeflags)   mem_d(tmpl->executeflags);
-    if (tmpl->sourcefile)     mem_d(tmpl->sourcefile);
-    if (tmpl->rulesfile)      mem_d(tmpl->rulesfile);
-    if (tmpl->testflags)      mem_d(tmpl->testflags);
-
-    /*
-     * Delete all allocated string for task tmpl then destroy the
-     * main vector.
-     */
-    {
-        size_t i = 0;
-        for (; i < vec_size(tmpl->comparematch); i++)
-            mem_d(tmpl->comparematch[i]);
-
-        vec_free(tmpl->comparematch);
-    }
-
-    /*
-     * Nullify all the template members otherwise NULL comparision
-     * checks will fail if tmpl pointer is reused.
-     */
-    mem_d(tmpl->tempfilename);
-    mem_d(tmpl);
-}
-
-/*
- * Now comes the task manager, this system allows adding tasks in and out
- * of a task list.  This is the executor of the tasks essentially as well.
- */
-typedef struct {
-    task_template_t *tmpl;
-    FILE **runhandles;
-    FILE *stderrlog;
-    FILE *stdoutlog;
-    char *stdoutlogfile;
-    char *stderrlogfile;
-    bool compiled;
-} task_t;
-
-static task_t *task_tasks = NULL;
-
-/*
- * Read a directory and searches for all template files in it
- * which is later used to run all tests.
- */
-static bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
-    bool             success = true;
-    DIR       *dir;
-    struct dirent     *files;
-    struct stat      directory;
-    char             buffer[4096];
-    size_t           found = 0;
-    char           **directories = NULL;
-    char            *claim = util_strdup(curdir);
-    size_t           i;
-
-    vec_push(directories, claim);
-    dir = opendir(claim);
-
-    /*
-     * Generate a list of subdirectories since we'll be checking them too
-     * for tmpl files.
-     */
-    while ((files = readdir(dir))) {
-        util_asprintf(&claim, "%s/%s", curdir, files->d_name);
-        if (stat(claim, &directory) == -1) {
-            closedir(dir);
-            mem_d(claim);
-            return false;
-        }
-
-        if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') {
-            vec_push(directories, claim);
-        } else {
-            mem_d(claim);
-            claim = NULL;
-        }
-    }
-    closedir(dir);
-
-    /*
-     * Now do all the work, by touching all the directories inside
-     * test as well and compile the task templates into data we can
-     * use to run the tests.
-     */
-    for (i = 0; i < vec_size(directories); i++) {
-        dir = opendir(directories[i]);
-
-        while ((files = readdir(dir))) {
-            util_snprintf(buffer, sizeof(buffer), "%s/%s", directories[i], files->d_name);
-            if (stat(buffer, &directory) == -1) {
-                con_err("internal error: stat failed, aborting\n");
-                abort();
-            }
-
-            if (S_ISDIR(directory.st_mode))
-                continue;
-
-            /*
-             * We made it here, which concludes the file/directory is not
-             * actually a directory, so it must be a file :)
-             */
-            if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) {
-                task_template_t *tmpl = task_template_compile(files->d_name, directories[i], pad);
-                char             buf[4096]; /* one page should be enough */
-                const char      *qcflags = NULL;
-                task_t           task;
-
-                found ++;
-                if (!tmpl) {
-                    con_err("error compiling task template: %s\n", files->d_name);
-                    success = false;
-                    continue;
-                }
-                /*
-                 * Generate a temportary file name for the output binary
-                 * so we don't trample over an existing one.
-                 */
-                tmpl->tempfilename = NULL;
-                util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", directories[i], files->d_name);
-
-                /*
-                 * Additional QCFLAGS enviroment variable may be used
-                 * to test compile flags for all tests.  This needs to be
-                 * BEFORE other flags (so that the .tmpl can override them)
-                 */
-                qcflags = getenv("QCFLAGS");
-
-                /*
-                 * Generate the command required to open a pipe to a process
-                 * which will be refered to with a handle in the task for
-                 * reading the data from the pipe.
-                 */
-                if (strcmp(tmpl->proceduretype, "-pp")) {
-                    if (qcflags) {
-                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                directories[i],
-                                tmpl->sourcefile,
-                                qcflags,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        } else {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                curdir,
-                                defs,
-                                directories[i],
-                                tmpl->sourcefile,
-                                qcflags,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        }
-                    } else {
-                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                directories[i],
-                                tmpl->sourcefile,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        } else {
-                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s",
-                                task_bins[TASK_COMPILE],
-                                curdir,
-                                defs,
-                                directories[i],
-                                tmpl->sourcefile,
-                                tmpl->compileflags,
-                                tmpl->tempfilename
-                            );
-                        }
-                    }
-                } else {
-                    /* Preprocessing (qcflags mean shit all here we don't allow them) */
-                    if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
-                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s",
-                            task_bins[TASK_COMPILE],
-                            directories[i],
-                            tmpl->sourcefile,
-                            tmpl->compileflags,
-                            tmpl->tempfilename
-                        );
-                    } else {
-                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s",
-                            task_bins[TASK_COMPILE],
-                            curdir,
-                            defs,
-                            directories[i],
-                            tmpl->sourcefile,
-                            tmpl->compileflags,
-                            tmpl->tempfilename
-                        );
-                    }
-                }
-
-                /*
-                 * The task template was compiled, now lets create a task from
-                 * the template data which has now been propagated.
-                 */
-                task.tmpl = tmpl;
-                if (!(task.runhandles = task_popen(buf, "r"))) {
-                    con_err("error opening pipe to process for test: %s\n", tmpl->description);
-                    success = false;
-                    continue;
-                }
-
-                /*
-                 * Open up some file desciptors for logging the stdout/stderr
-                 * to our own.
-                 */
-                util_snprintf(buf,  sizeof(buf), "%s.stdout", tmpl->tempfilename);
-                task.stdoutlogfile = util_strdup(buf);
-                if (!(task.stdoutlog = fopen(buf, "w"))) {
-                    con_err("error opening %s for stdout\n", buf);
-                    continue;
-                }
-
-                util_snprintf(buf,  sizeof(buf), "%s.stderr", tmpl->tempfilename);
-                task.stderrlogfile = util_strdup(buf);
-                if (!(task.stderrlog = fopen(buf, "w"))) {
-                    con_err("error opening %s for stderr\n", buf);
-                    continue;
-                }
-                vec_push(task_tasks, task);
-            }
-        }
-        closedir(dir);
-        mem_d(directories[i]); /* free claimed memory */
-    }
-    vec_free(directories);
-
-    return success;
-}
-
-/*
- * Task precleanup removes any existing temporary files or log files
- * left behind from a previous invoke of the test-suite.
- */
-static void task_precleanup(const char *curdir) {
-    DIR     *dir;
-    struct dirent  *files;
-    char          buffer[4096];
-
-    dir = opendir(curdir);
-
-    while ((files = readdir(dir))) {
-        if (strstr(files->d_name, "TMP")     ||
-            strstr(files->d_name, ".stdout") ||
-            strstr(files->d_name, ".stderr") ||
-            strstr(files->d_name, ".dat"))
-        {
-            util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
-            if (remove(buffer))
-                con_err("error removing temporary file: %s\n", buffer);
-        }
-    }
-
-    closedir(dir);
-}
-
-static void task_destroy(void) {
-    /*
-     * Free all the data in the task list and finally the list itself
-     * then proceed to cleanup anything else outside the program like
-     * temporary files.
-     */
-    size_t i;
-    for (i = 0; i < vec_size(task_tasks); i++) {
-        /*
-         * Close any open handles to files or processes here.  It's mighty
-         * annoying to have to do all this cleanup work.
-         */
-        if (task_tasks[i].stdoutlog) fclose(task_tasks[i].stdoutlog);
-        if (task_tasks[i].stderrlog) fclose(task_tasks[i].stderrlog);
-
-        /*
-         * Only remove the log files if the test actually compiled otherwise
-         * forget about it (or if it didn't compile, and the procedure type
-         * was set to -fail (meaning it shouldn't compile) .. stil remove)
-         */
-        if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
-            if (remove(task_tasks[i].stdoutlogfile))
-                con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile);
-            if (remove(task_tasks[i].stderrlogfile))
-                con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile);
-
-            (void)!remove(task_tasks[i].tmpl->tempfilename);
-        }
-
-        /* free util_strdup data for log files */
-        mem_d(task_tasks[i].stdoutlogfile);
-        mem_d(task_tasks[i].stderrlogfile);
-
-        task_template_destroy(task_tasks[i].tmpl);
-    }
-    vec_free(task_tasks);
-}
-
-/*
- * This executes the QCVM task for a specificly compiled progs.dat
- * using the template passed into it for call-flags and user defined
- * messages IF the procedure type is -execute, otherwise it matches
- * the preprocessor output.
- */
-static bool task_trymatch(size_t i, char ***line) {
-    bool             success = true;
-    bool             process = true;
-    int              retval  = EXIT_SUCCESS;
-    FILE       *execute;
-    char             buffer[4096];
-    task_template_t *tmpl = task_tasks[i].tmpl;
-
-    memset  (buffer,0,sizeof(buffer));
-
-    if (!strcmp(tmpl->proceduretype, "-execute")) {
-        /*
-         * Drop the execution flags for the QCVM if none where
-         * actually specified.
-         */
-        if (!strcmp(tmpl->executeflags, "$null")) {
-            util_snprintf(buffer,  sizeof(buffer), "%s %s",
-                task_bins[TASK_EXECUTE],
-                tmpl->tempfilename
-            );
-        } else {
-            util_snprintf(buffer,  sizeof(buffer), "%s %s %s",
-                task_bins[TASK_EXECUTE],
-                tmpl->executeflags,
-                tmpl->tempfilename
-            );
-        }
-
-        execute = popen(buffer, "r");
-        if (!execute)
-            return false;
-    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
-        /*
-         * we're preprocessing, which means we need to read int
-         * the produced file and do some really weird shit.
-         */
-        if (!(execute = fopen(tmpl->tempfilename, "r")))
-            return false;
-        process = false;
-    } else {
-        /*
-         * we're testing diagnostic output, which means it will be
-         * in runhandles[2] (stderr) since that is where the compiler
-         * puts it's errors.
-         */
-        if (!(execute = fopen(task_tasks[i].stderrlogfile, "r")))
-            return false;
-        process = false;
-    }
-
-    /*
-     * Now lets read the lines and compare them to the matches we expect
-     * and handle accordingly.
-     */
-    {
-        char  *data    = NULL;
-        size_t size    = 0;
-        size_t compare = 0;
-
-        while (util_getline(&data, &size, execute) != EOF) {
-            if (!strcmp(data, "No main function found\n")) {
-                con_err("test failure: `%s` (No main function found) [%s]\n",
-                    tmpl->description,
-                    tmpl->rulesfile
-                );
-                if (!process)
-                    fclose(execute);
-                else
-                    pclose((FILE*)execute);
-                return false;
-            }
-
-            /*
-             * Trim newlines from data since they will just break our
-             * ability to properly validate matches.
-             */
-            if  (strrchr(data, '\n'))
-                *strrchr(data, '\n') = '\0';
-
-            /*
-             * We remove the file/directory and stuff from the error
-             * match messages when testing diagnostics.
-             */
-            if(!strcmp(tmpl->proceduretype, "-diagnostic")) {
-                if (strstr(data, "there have been errors, bailing out"))
-                    continue; /* ignore it */
-                if (strstr(data, ": error: ")) {
-                    char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9);
-                    mem_d(data);
-                    data = claim;
-                }
-            }
-
-            /*
-             * We need to ignore null lines for when -pp is used (preprocessor), since
-             * the preprocessor is likely to create empty newlines in certain macro
-             * instantations, otherwise it's in the wrong nature to ignore empty newlines.
-             */
-            if (!strcmp(tmpl->proceduretype, "-pp") && !*data)
-                continue;
-
-            if (vec_size(tmpl->comparematch) > compare) {
-                if (strcmp(data, tmpl->comparematch[compare++])) {
-                    success = false;
-                }
-            } else {
-                success = false;
-            }
-
-            /*
-             * Copy to output vector for diagnostics if execution match
-             * fails.
-             */
-            vec_push(*line, data);
-
-            /* reset */
-            data = NULL;
-            size = 0;
-        }
-
-        if (compare != vec_size(tmpl->comparematch))
-            success = false;
-
-        mem_d(data);
-        data = NULL;
-    }
-
-    if (process)
-        retval = pclose((FILE*)execute);
-    else
-        fclose(execute);
-
-    return success && retval == EXIT_SUCCESS;
-}
-
-static const char *task_type(task_template_t *tmpl) {
-    if (!strcmp(tmpl->proceduretype, "-pp"))
-        return "type: preprocessor";
-    if (!strcmp(tmpl->proceduretype, "-execute"))
-        return "type: execution";
-    if (!strcmp(tmpl->proceduretype, "-compile"))
-        return "type: compile";
-    if (!strcmp(tmpl->proceduretype, "-diagnostic"))
-        return "type: diagnostic";
-    return "type: fail";
-}
-
-/*
- * This schedualizes all tasks and actually runs them individually
- * this is generally easy for just -compile variants.  For compile and
- * execution this takes more work since a task needs to be generated
- * from thin air and executed INLINE.
- */
-#include <math.h>
-static size_t task_schedualize(size_t *pad) {
-    char   space[2][64];
-    bool   execute  = false;
-    char  *data     = NULL;
-    char **match    = NULL;
-    size_t size     = 0;
-    size_t i        = 0;
-    size_t j        = 0;
-    size_t failed   = 0;
-    int    status   = 0;
-
-    util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
-
-    for (; i < vec_size(task_tasks); i++) {
-        memset(space[1], 0, sizeof(space[1]));
-        util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1));
-
-        con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), "");
-
-        /*
-         * Generate a task from thin air if it requires execution in
-         * the QCVM.
-         */
-
-        /* diagnostic is not executed, but compare tested instead, like preproessor */
-        execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) ||
-                     (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))      ||
-                     (!strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"));
-
-        /*
-         * We assume it compiled before we actually compiled :).  On error
-         * we change the value
-         */
-        task_tasks[i].compiled = true;
-
-        /*
-         * Read data from stdout first and pipe that stuff into a log file
-         * then we do the same for stderr.
-         */
-        while (util_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) {
-            fputs(data, task_tasks[i].stdoutlog);
-
-            if (strstr(data, "failed to open file")) {
-                task_tasks[i].compiled = false;
-                execute                = false;
-            }
-        }
-        while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) {
-            /*
-             * If a string contains an error we just dissalow execution
-             * of it in the vm.
-             *
-             * TODO: make this more percise, e.g if we print a warning
-             * that refers to a variable named error, or something like
-             * that .. then this will blowup :P
-             */
-            if (strstr(data, "error") && strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) {
-                execute                = false;
-                task_tasks[i].compiled = false;
-            }
-
-            fputs(data, task_tasks[i].stderrlog);
-            fflush(task_tasks[i].stderrlog); /* fast flush for read */
-        }
-
-        if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
-            con_out("failure:   `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]),
-                "(failed to compile)"
-            );
-            failed++;
-            continue;
-        }
-
-        status = task_pclose(task_tasks[i].runhandles);
-        if ((!strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS)
-        ||  ( strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) {
-            con_out("failure:   `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]),
-                "(compiler didn't return exit success)"
-            );
-            failed++;
-            continue;
-        }
-
-        if (!execute) {
-            con_out("succeeded: `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]),
-                task_type(task_tasks[i].tmpl)
-
-            );
-            continue;
-        }
-
-        /*
-         * If we made it here that concludes the task is to be executed
-         * in the virtual machine (or the preprocessor output needs to
-         * be matched).
-         */
-        if (!task_trymatch(i, &match)) {
-            size_t d = 0;
-
-            con_out("failure:   `%s` %*s %*s\n",
-                task_tasks[i].tmpl->description,
-                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-                task_tasks[i].tmpl->rulesfile,
-                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(
-                    (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
-                        ? "(invalid results from execution)"
-                        : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"))
-                            ? "(invalid results from preprocessing)"
-                            : "(invalid results from compiler diagnsotics)"
-                ) - pad[2]),
-                (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
-                    ? "(invalid results from execution)"
-                    : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"))
-                            ? "(invalid results from preprocessing)"
-                            : "(invalid results from compiler diagnsotics)"
-            );
-
-            /*
-             * Print nicely formatted expected match lists to console error
-             * handler for the all the given matches in the template file and
-             * what was actually returned from executing.
-             */
-            con_out("    Expected From %u Matches: (got %u Matches)\n",
-                vec_size(task_tasks[i].tmpl->comparematch),
-                vec_size(match)
-            );
-            for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) {
-                char  *select = task_tasks[i].tmpl->comparematch[d];
-                size_t length = 60 - strlen(select);
-
-                con_out("        Expected: \"%s\"", select);
-                while (length --)
-                    con_out(" ");
-                con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<<nothing else to compare>>" : match[d]);
-            }
-
-            /*
-             * Print the non-expected out (since we are simply not expecting it)
-             * This will help track down bugs in template files that fail to match
-             * something.
-             */
-            if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) {
-                for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) {
-                    con_out("        Expected: Nothing                                                       | Got: \"%s\"\n",
-                        match[d + vec_size(task_tasks[i].tmpl->comparematch)]
-                    );
-                }
-            }
-
-
-            for (j = 0; j < vec_size(match); j++)
-                mem_d(match[j]);
-            vec_free(match);
-            failed++;
-            continue;
-        }
-
-        for (j = 0; j < vec_size(match); j++)
-            mem_d(match[j]);
-        vec_free(match);
-
-        con_out("succeeded: `%s` %*s %*s\n",
-            task_tasks[i].tmpl->description,
-            (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
-            task_tasks[i].tmpl->rulesfile,
-            (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]),
-            task_type(task_tasks[i].tmpl)
-
-        );
-    }
-    mem_d(data);
-    return failed;
-}
-
-/*
- * This is the heart of the whole test-suite process.  This cleans up
- * any existing temporary files left behind as well as log files left
- * behind.  Then it propagates a list of tests from `curdir` by scaning
- * it for template files and compiling them into tasks, in which it
- * schedualizes them (executes them) and actually reports errors and
- * what not.  It then proceeds to destroy the tasks and return memory
- * it's the engine :)
- *
- * It returns true of tests could be propagated, otherwise it returns
- * false.
- *
- * It expects con_init() was called before hand.
- */
-static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
-    size_t             failed       = false;
-    static const char *default_defs = "defs.qh";
-
-    size_t pad[] = {
-        /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
-                    0,                                 0,                        0
-    };
-
-    /*
-     * If the default definition file isn't set to anything.  We will
-     * use the default_defs here, which is "defs.qc"
-     */
-    if (!defs) {
-        defs = default_defs;
-    }
-
-
-    task_precleanup(curdir);
-    if (!task_propagate(curdir, pad, defs)) {
-        con_err("error: failed to propagate tasks\n");
-        task_destroy();
-        return false;
-    }
-    /*
-     * If we made it here all tasks where propagated from their resultant
-     * template file.  So we can start the FILO scheduler, this has been
-     * designed in the most thread-safe way possible for future threading
-     * it's designed to prevent lock contention, and possible syncronization
-     * issues.
-     */
-    failed = task_schedualize(pad);
-    if (failed)
-        con_out("%u out of %u tests failed\n", failed, vec_size(task_tasks));
-    task_destroy();
-
-    return (failed) ? false : true;
-}
-
-/*
- * Fancy GCC-like LONG parsing allows things like --opt=param with
- * assignment operator.  This is used for redirecting stdout/stderr
- * console to specific files of your choice.
- */
-static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
-    int  argc   = *argc_;
-    char **argv = *argv_;
-
-    size_t len = strlen(optname);
-
-    if (strncmp(argv[0]+ds, optname, len))
-        return false;
-
-    /* it's --optname, check how the parameter is supplied */
-    if (argv[0][ds+len] == '=') {
-        *out = argv[0]+ds+len+1;
-        return true;
-    }
-
-    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
-        return false;
-
-    /* using --opt param */
-    *out = argv[1];
-    --*argc_;
-    ++*argv_;
-    return true;
-}
-
-int main(int argc, char **argv) {
-    bool succeed  = false;
-    char *defs = NULL;
-
-    con_init();
-
-    /*
-     * Command line option parsing commences now We only need to support
-     * a few things in the test suite.
-     */
-    while (argc > 1) {
-        ++argv;
-        --argc;
-
-        if (argv[0][0] == '-') {
-            if (parsecmd("defs", &argc, &argv, &defs, 1, false))
-                continue;
-
-            if (!strcmp(argv[0]+1, "debug")) {
-                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
-                continue;
-            }
-            if (!strcmp(argv[0]+1, "nocolor")) {
-                con_color(0);
-                continue;
-            }
-
-            con_err("invalid argument %s\n", argv[0]+1);
-            return -1;
-        }
-    }
-    succeed = test_perform("tests", defs);
-
-    return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
-}
diff --git a/test.cpp b/test.cpp
new file mode 100644 (file)
index 0000000..2c985f0
--- /dev/null
+++ b/test.cpp
@@ -0,0 +1,1311 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include "gmqcc.h"
+
+static const char *task_bins[] = {
+    "./gmqcc",
+    "./qcvm"
+};
+
+typedef struct {
+    FILE *handles[3];
+    int pipes[3];
+    int stderr_fd;
+    int stdout_fd;
+    int pid;
+} popen_t;
+
+static FILE **task_popen(const char *command, const char *mode) {
+    int     inhandle  [2];
+    int     outhandle [2];
+    int     errhandle [2];
+    int     trypipe;
+
+    popen_t *data = (popen_t*)mem_a(sizeof(popen_t));
+
+    /*
+     * Parse the command now into a list for execv, this is a pain
+     * in the ass.
+     */
+    char  *line = (char*)command;
+    char **argv = NULL;
+    {
+
+        while (*line != '\0') {
+            while (*line == ' ' || *line == '\t' || *line == '\n')
+                *line++ = '\0';
+            vec_push(argv, line);
+
+            while (*line != '\0' && *line != ' ' &&
+                   *line != '\t' && *line != '\n') line++;
+        }
+        vec_push(argv, (char *)0);
+    }
+
+
+    if ((trypipe = pipe(inhandle))  < 0) goto task_popen_error_0;
+    if ((trypipe = pipe(outhandle)) < 0) goto task_popen_error_1;
+    if ((trypipe = pipe(errhandle)) < 0) goto task_popen_error_2;
+
+    if ((data->pid = fork()) > 0) {
+        /* parent */
+        close(inhandle [0]);
+        close(outhandle [1]);
+        close(errhandle [1]);
+        data->pipes[0] = inhandle [1];
+        data->pipes[1] = outhandle[0];
+        data->pipes[2] = errhandle[0];
+        data->handles[0] = fdopen(inhandle [1], "w");
+        data->handles[1] = fdopen(outhandle[0], mode);
+        data->handles[2] = fdopen(errhandle[0], mode);
+
+        /* sigh */
+        vec_free(argv);
+        return data->handles;
+    } else if (data->pid == 0) {
+        /* child */
+        close(inhandle [1]);
+        close(outhandle[0]);
+        close(errhandle[0]);
+
+        /* see piping documentation for this sillyness :P */
+        dup2(inhandle [0], 0);
+        dup2(outhandle[1], 1);
+        dup2(errhandle[1], 2);
+
+        execvp(*argv, argv);
+        exit(EXIT_FAILURE);
+    } else {
+        /* fork failed */
+        goto task_popen_error_3;
+    }
+
+task_popen_error_3: close(errhandle[0]), close(errhandle[1]);
+task_popen_error_2: close(outhandle[0]), close(outhandle[1]);
+task_popen_error_1: close(inhandle [0]), close(inhandle [1]);
+task_popen_error_0:
+
+    vec_free(argv);
+    return NULL;
+}
+
+static int task_pclose(FILE **handles) {
+    popen_t *data   = (popen_t*)handles;
+    int      status = 0;
+
+    close(data->pipes[0]); /* stdin  */
+    close(data->pipes[1]); /* stdout */
+    close(data->pipes[2]); /* stderr */
+
+    waitpid(data->pid, &status, 0);
+
+    mem_d(data);
+
+    return status;
+}
+
+#define TASK_COMPILE    0
+#define TASK_EXECUTE    1
+/*
+ * Task template system:
+ *  templates are rules for a specific test, used to create a "task" that
+ *  is executed with those set of rules (arguments, and what not). Tests
+ *  that don't have a template with them cannot become tasks, since without
+ *  the information for that test there is no way to properly "test" them.
+ *  Rules for these templates are described in a template file, using a
+ *  task template language.
+ *
+ *  The language is a basic finite statemachine, top-down single-line
+ *  description language.
+ *
+ *  The languge is composed entierly of "tags" which describe a string of
+ *  text for a task.  Think of it much like a configuration file.  Except
+ *  it's been designed to allow flexibility and future support for prodecual
+ *  semantics.
+ *
+ *  The following "tags" are suported by the language
+ *
+ *      D:
+ *          Used to set a description of the current test, this must be
+ *          provided, this tag is NOT optional.
+ *
+ *      T:
+ *          Used to set the procedure for the given task, there are two
+ *          options for this:
+ *              -compile
+ *                  This simply performs compilation only
+ *              -execute
+ *                  This will perform compilation and execution
+ *              -fail
+ *                  This will perform compilation, but requires
+ *                  the compilation to fail in order to succeed.
+ *
+ *          This must be provided, this tag is NOT optional.
+ *
+ *      C:
+ *          Used to set the compilation flags for the given task, this
+ *          must be provided, this tag is NOT optional.
+ *
+ *      F:  Used to set some test suite flags, currently the only option
+ *          is -no-defs (to including of defs.qh)
+ *
+ *      E:
+ *          Used to set the execution flags for the given task. This tag
+ *          must be provided if T == -execute, otherwise it's erroneous
+ *          as compilation only takes place.
+ *
+ *      M:
+ *          Used to describe a string of text that should be matched from
+ *          the output of executing the task.  If this doesn't match the
+ *          task fails.  This tag must be provided if T == -execute, otherwise
+ *          it's erroneous as compilation only takes place.
+ *
+ *      I:
+ *          Used to specify the INPUT source file to operate on, this must be
+ *          provided, this tag is NOT optional
+ *
+ *
+ *  Notes:
+ *      These tags have one-time use, using them more than once will result
+ *      in template compilation errors.
+ *
+ *      Lines beginning with # or // in the template file are comments and
+ *      are ignored by the template parser.
+ *
+ *      Whitespace is optional, with exception to the colon ':' between the
+ *      tag and it's assignment value/
+ *
+ *      The template compiler will detect erronrous tags (optional tags
+ *      that need not be set), as well as missing tags, and error accordingly
+ *      this will result in the task failing.
+ */
+typedef struct {
+    char  *description;
+    char  *compileflags;
+    char  *executeflags;
+    char  *proceduretype;
+    char  *sourcefile;
+    char  *tempfilename;
+    char **comparematch;
+    char  *rulesfile;
+    char  *testflags;
+} task_template_t;
+
+/*
+ * This is very much like a compiler code generator :-).  This generates
+ * a value from some data observed from the compiler.
+ */
+static bool task_template_generate(task_template_t *tmpl, char tag, const char *file, size_t line, char *value, size_t *pad) {
+    size_t desclen = 0;
+    size_t filelen = 0;
+    char **destval = NULL;
+
+    if (!tmpl)
+        return false;
+
+    switch(tag) {
+        case 'D': destval = &tmpl->description;    break;
+        case 'T': destval = &tmpl->proceduretype;  break;
+        case 'C': destval = &tmpl->compileflags;   break;
+        case 'E': destval = &tmpl->executeflags;   break;
+        case 'I': destval = &tmpl->sourcefile;     break;
+        case 'F': destval = &tmpl->testflags;      break;
+        default:
+            con_printmsg(LVL_ERROR, __FILE__, __LINE__, 0, "internal error",
+                "invalid tag `%c:` during code generation\n",
+                tag
+            );
+            return false;
+    }
+
+    /*
+     * Ensure if for the given tag, there already exists a
+     * assigned value.
+     */
+    if (*destval) {
+        con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "compile error",
+            "tag `%c:` already assigned value: %s\n",
+            tag, *destval
+        );
+        return false;
+    }
+
+    /*
+     * Strip any whitespace that might exist in the value for assignments
+     * like "D:      foo"
+     */
+    if (value && *value && (*value == ' ' || *value == '\t'))
+        value++;
+    else if (!value)
+        exit(EXIT_FAILURE);
+
+    /*
+     * Value will contain a newline character at the end, we need to strip
+     * this otherwise kaboom, seriously, kaboom :P
+     */
+    if (strchr(value, '\n'))
+        *strrchr(value, '\n')='\0';
+
+    /*
+     * Now allocate and set the actual value for the specific tag. Which
+     * was properly selected and can be accessed with *destval.
+     */
+    *destval = util_strdup(value);
+
+
+    if (*destval == tmpl->description) {
+        /*
+         * Create some padding for the description to align the
+         * printing of the rules file.
+         */
+        if ((desclen = strlen(tmpl->description)) > pad[0])
+            pad[0] = desclen;
+    }
+
+    if ((filelen = strlen(file)) > pad[2])
+        pad[2] = filelen;
+
+    return true;
+}
+
+static bool task_template_parse(const char *file, task_template_t *tmpl, FILE *fp, size_t *pad) {
+    char  *data = NULL;
+    char  *back = NULL;
+    size_t size = 0;
+    size_t line = 1;
+
+    if (!tmpl)
+        return false;
+
+    /* top down parsing */
+    while (util_getline(&back, &size, fp) != EOF) {
+        /* skip whitespace */
+        data = back;
+        if (*data && (*data == ' ' || *data == '\t'))
+            data++;
+
+        switch (*data) {
+            /*
+             * Handle comments inside task tmpl files.  We're strict
+             * about the language for fun :-)
+             */
+            case '/':
+                if (data[1] != '/') {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                        "invalid character `/`, perhaps you meant `//` ?");
+
+                    mem_d(back);
+                    return false;
+                }
+            case '#':
+                break;
+
+            /*
+             * Empty newlines are acceptable as well, so we handle that here
+             * despite being just odd since there should't be that many
+             * empty lines to begin with.
+             */
+            case '\r':
+            case '\n':
+                break;
+
+
+            /*
+             * Now begin the actual "tag" stuff.  This works as you expect
+             * it to.
+             */
+            case 'D':
+            case 'T':
+            case 'C':
+            case 'E':
+            case 'I':
+            case 'F':
+                if (data[1] != ':') {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                        "expected `:` after `%c`",
+                        *data
+                    );
+                    goto failure;
+                }
+                if (!task_template_generate(tmpl, *data, file, line, &data[3], pad)) {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl compile error",
+                        "failed to generate for given task\n"
+                    );
+                    goto failure;
+                }
+                break;
+
+            /*
+             * Match requires it's own system since we allow multiple M's
+             * for multi-line matching.
+             */
+            case 'M':
+            {
+                char *value = &data[3];
+                if (data[1] != ':') {
+                    con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                        "expected `:` after `%c`",
+                        *data
+                    );
+                    goto failure;
+                }
+
+                /*
+                 * Value will contain a newline character at the end, we need to strip
+                 * this otherwise kaboom, seriously, kaboom :P
+                 */
+                if (strrchr(value, '\n'))
+                    *strrchr(value, '\n')='\0';
+                else /* cppcheck: possible null pointer dereference */
+                    exit(EXIT_FAILURE);
+
+                vec_push(tmpl->comparematch, util_strdup(value));
+
+                break;
+            }
+
+            default:
+                con_printmsg(LVL_ERROR, file, line, 0, /*TODO: column for match*/ "tmpl parse error",
+                    "invalid tag `%c`", *data
+                );
+                goto failure;
+            /* no break required */
+        }
+
+        /* update line and free old sata */
+        line++;
+        mem_d(back);
+        back = NULL;
+    }
+    if (back)
+        mem_d(back);
+    return true;
+
+failure:
+    mem_d (back);
+    return false;
+}
+
+/*
+ * Nullifies the template data: used during initialization of a new
+ * template and free.
+ */
+static void task_template_nullify(task_template_t *tmpl) {
+    if (!tmpl)
+        return;
+
+    tmpl->description    = NULL;
+    tmpl->proceduretype  = NULL;
+    tmpl->compileflags   = NULL;
+    tmpl->executeflags   = NULL;
+    tmpl->comparematch   = NULL;
+    tmpl->sourcefile     = NULL;
+    tmpl->tempfilename   = NULL;
+    tmpl->rulesfile      = NULL;
+    tmpl->testflags      = NULL;
+}
+
+static task_template_t *task_template_compile(const char *file, const char *dir, size_t *pad) {
+    /* a page should be enough */
+    char             fullfile[4096];
+    size_t           filepadd = 0;
+    FILE       *tempfile = NULL;
+    task_template_t *tmpl     = NULL;
+
+    util_snprintf(fullfile,    sizeof(fullfile), "%s/%s", dir, file);
+
+    tempfile = fopen(fullfile, "r");
+    tmpl     = (task_template_t*)mem_a(sizeof(task_template_t));
+    task_template_nullify(tmpl);
+
+    /*
+     * Create some padding for the printing to align the
+     * printing of the rules file to the console.
+     */
+    if ((filepadd = strlen(fullfile)) > pad[1])
+        pad[1] = filepadd;
+
+    tmpl->rulesfile = util_strdup(fullfile);
+
+    /*
+     * Esnure the file even exists for the task, this is pretty useless
+     * to even do.
+     */
+    if (!tempfile) {
+        con_err("template file: %s does not exist or invalid permissions\n",
+            file
+        );
+        goto failure;
+    }
+
+    if (!task_template_parse(file, tmpl, tempfile, pad)) {
+        con_err("template parse error: error during parsing\n");
+        goto failure;
+    }
+
+    /*
+     * Regardless procedure type, the following tags must exist:
+     *  D
+     *  T
+     *  C
+     *  I
+     */
+    if (!tmpl->description) {
+        con_err("template compile error: %s missing `D:` tag\n", file);
+        goto failure;
+    }
+    if (!tmpl->proceduretype) {
+        con_err("template compile error: %s missing `T:` tag\n", file);
+        goto failure;
+    }
+    if (!tmpl->compileflags) {
+        con_err("template compile error: %s missing `C:` tag\n", file);
+        goto failure;
+    }
+    if (!tmpl->sourcefile) {
+        con_err("template compile error: %s missing `I:` tag\n", file);
+        goto failure;
+    }
+
+    /*
+     * Now lets compile the template, compilation is really just
+     * the process of validating the input.
+     */
+    if (!strcmp(tmpl->proceduretype, "-compile")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only compiling\n", file);
+        if (tmpl->comparematch)
+            con_err("template compile warning: %s erroneous tag `M:` when only compiling\n", file);
+        goto success;
+    } else if (!strcmp(tmpl->proceduretype, "-execute")) {
+        if (!tmpl->executeflags) {
+            /* default to $null */
+            tmpl->executeflags = util_strdup("$null");
+        }
+        if (!tmpl->comparematch) {
+            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
+            goto failure;
+        }
+    } else if (!strcmp(tmpl->proceduretype, "-fail")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only failing\n", file);
+        if (tmpl->comparematch)
+            con_err("template compile warning: %s erroneous tag `M:` when only failing\n", file);
+    } else if (!strcmp(tmpl->proceduretype, "-diagnostic")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only diagnostic\n", file);
+        if (!tmpl->comparematch) {
+            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
+            goto failure;
+        }
+    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
+        if (tmpl->executeflags)
+            con_err("template compile warning: %s erroneous tag `E:` when only preprocessing\n", file);
+        if (!tmpl->comparematch) {
+            con_err("template compile error: %s missing `M:` tag (use `$null` for exclude)\n", file);
+            goto failure;
+        }
+    } else {
+        con_err("template compile error: %s invalid procedure type: %s\n", file, tmpl->proceduretype);
+        goto failure;
+    }
+
+success:
+    fclose(tempfile);
+    return tmpl;
+
+failure:
+    /*
+     * The file might not exist and we jump here when that doesn't happen
+     * so the check to see if it's not null here is required.
+     */
+    if (tempfile)
+        fclose(tempfile);
+    mem_d (tmpl);
+
+    return NULL;
+}
+
+static void task_template_destroy(task_template_t *tmpl) {
+    if (!tmpl)
+        return;
+
+    if (tmpl->description)    mem_d(tmpl->description);
+    if (tmpl->proceduretype)  mem_d(tmpl->proceduretype);
+    if (tmpl->compileflags)   mem_d(tmpl->compileflags);
+    if (tmpl->executeflags)   mem_d(tmpl->executeflags);
+    if (tmpl->sourcefile)     mem_d(tmpl->sourcefile);
+    if (tmpl->rulesfile)      mem_d(tmpl->rulesfile);
+    if (tmpl->testflags)      mem_d(tmpl->testflags);
+
+    /*
+     * Delete all allocated string for task tmpl then destroy the
+     * main vector.
+     */
+    {
+        size_t i = 0;
+        for (; i < vec_size(tmpl->comparematch); i++)
+            mem_d(tmpl->comparematch[i]);
+
+        vec_free(tmpl->comparematch);
+    }
+
+    /*
+     * Nullify all the template members otherwise NULL comparision
+     * checks will fail if tmpl pointer is reused.
+     */
+    mem_d(tmpl->tempfilename);
+    mem_d(tmpl);
+}
+
+/*
+ * Now comes the task manager, this system allows adding tasks in and out
+ * of a task list.  This is the executor of the tasks essentially as well.
+ */
+typedef struct {
+    task_template_t *tmpl;
+    FILE **runhandles;
+    FILE *stderrlog;
+    FILE *stdoutlog;
+    char *stdoutlogfile;
+    char *stderrlogfile;
+    bool compiled;
+} task_t;
+
+static task_t *task_tasks = NULL;
+
+/*
+ * Read a directory and searches for all template files in it
+ * which is later used to run all tests.
+ */
+static bool task_propagate(const char *curdir, size_t *pad, const char *defs) {
+    bool             success = true;
+    DIR       *dir;
+    struct dirent     *files;
+    struct stat      directory;
+    char             buffer[4096];
+    size_t           found = 0;
+    char           **directories = NULL;
+    char            *claim = util_strdup(curdir);
+    size_t           i;
+
+    vec_push(directories, claim);
+    dir = opendir(claim);
+
+    /*
+     * Generate a list of subdirectories since we'll be checking them too
+     * for tmpl files.
+     */
+    while ((files = readdir(dir))) {
+        util_asprintf(&claim, "%s/%s", curdir, files->d_name);
+        if (stat(claim, &directory) == -1) {
+            closedir(dir);
+            mem_d(claim);
+            return false;
+        }
+
+        if (S_ISDIR(directory.st_mode) && files->d_name[0] != '.') {
+            vec_push(directories, claim);
+        } else {
+            mem_d(claim);
+            claim = NULL;
+        }
+    }
+    closedir(dir);
+
+    /*
+     * Now do all the work, by touching all the directories inside
+     * test as well and compile the task templates into data we can
+     * use to run the tests.
+     */
+    for (i = 0; i < vec_size(directories); i++) {
+        dir = opendir(directories[i]);
+
+        while ((files = readdir(dir))) {
+            util_snprintf(buffer, sizeof(buffer), "%s/%s", directories[i], files->d_name);
+            if (stat(buffer, &directory) == -1) {
+                con_err("internal error: stat failed, aborting\n");
+                abort();
+            }
+
+            if (S_ISDIR(directory.st_mode))
+                continue;
+
+            /*
+             * We made it here, which concludes the file/directory is not
+             * actually a directory, so it must be a file :)
+             */
+            if (strcmp(files->d_name + strlen(files->d_name) - 5, ".tmpl") == 0) {
+                task_template_t *tmpl = task_template_compile(files->d_name, directories[i], pad);
+                char             buf[4096]; /* one page should be enough */
+                const char      *qcflags = NULL;
+                task_t           task;
+
+                found ++;
+                if (!tmpl) {
+                    con_err("error compiling task template: %s\n", files->d_name);
+                    success = false;
+                    continue;
+                }
+                /*
+                 * Generate a temportary file name for the output binary
+                 * so we don't trample over an existing one.
+                 */
+                tmpl->tempfilename = NULL;
+                util_asprintf(&tmpl->tempfilename, "%s/TMPDAT.%s.dat", directories[i], files->d_name);
+
+                /*
+                 * Additional QCFLAGS enviroment variable may be used
+                 * to test compile flags for all tests.  This needs to be
+                 * BEFORE other flags (so that the .tmpl can override them)
+                 */
+                qcflags = getenv("QCFLAGS");
+
+                /*
+                 * Generate the command required to open a pipe to a process
+                 * which will be refered to with a handle in the task for
+                 * reading the data from the pipe.
+                 */
+                if (strcmp(tmpl->proceduretype, "-pp")) {
+                    if (qcflags) {
+                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                directories[i],
+                                tmpl->sourcefile,
+                                qcflags,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        } else {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                curdir,
+                                defs,
+                                directories[i],
+                                tmpl->sourcefile,
+                                qcflags,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        }
+                    } else {
+                        if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                directories[i],
+                                tmpl->sourcefile,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        } else {
+                            util_snprintf(buf, sizeof(buf), "%s %s/%s %s/%s %s -o %s",
+                                task_bins[TASK_COMPILE],
+                                curdir,
+                                defs,
+                                directories[i],
+                                tmpl->sourcefile,
+                                tmpl->compileflags,
+                                tmpl->tempfilename
+                            );
+                        }
+                    }
+                } else {
+                    /* Preprocessing (qcflags mean shit all here we don't allow them) */
+                    if (tmpl->testflags && !strcmp(tmpl->testflags, "-no-defs")) {
+                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s -o %s",
+                            task_bins[TASK_COMPILE],
+                            directories[i],
+                            tmpl->sourcefile,
+                            tmpl->compileflags,
+                            tmpl->tempfilename
+                        );
+                    } else {
+                        util_snprintf(buf, sizeof(buf), "%s -E %s/%s %s/%s %s -o %s",
+                            task_bins[TASK_COMPILE],
+                            curdir,
+                            defs,
+                            directories[i],
+                            tmpl->sourcefile,
+                            tmpl->compileflags,
+                            tmpl->tempfilename
+                        );
+                    }
+                }
+
+                /*
+                 * The task template was compiled, now lets create a task from
+                 * the template data which has now been propagated.
+                 */
+                task.tmpl = tmpl;
+                if (!(task.runhandles = task_popen(buf, "r"))) {
+                    con_err("error opening pipe to process for test: %s\n", tmpl->description);
+                    success = false;
+                    continue;
+                }
+
+                /*
+                 * Open up some file desciptors for logging the stdout/stderr
+                 * to our own.
+                 */
+                util_snprintf(buf,  sizeof(buf), "%s.stdout", tmpl->tempfilename);
+                task.stdoutlogfile = util_strdup(buf);
+                if (!(task.stdoutlog = fopen(buf, "w"))) {
+                    con_err("error opening %s for stdout\n", buf);
+                    continue;
+                }
+
+                util_snprintf(buf,  sizeof(buf), "%s.stderr", tmpl->tempfilename);
+                task.stderrlogfile = util_strdup(buf);
+                if (!(task.stderrlog = fopen(buf, "w"))) {
+                    con_err("error opening %s for stderr\n", buf);
+                    continue;
+                }
+                vec_push(task_tasks, task);
+            }
+        }
+        closedir(dir);
+        mem_d(directories[i]); /* free claimed memory */
+    }
+    vec_free(directories);
+
+    return success;
+}
+
+/*
+ * Task precleanup removes any existing temporary files or log files
+ * left behind from a previous invoke of the test-suite.
+ */
+static void task_precleanup(const char *curdir) {
+    DIR     *dir;
+    struct dirent  *files;
+    char          buffer[4096];
+
+    dir = opendir(curdir);
+
+    while ((files = readdir(dir))) {
+        if (strstr(files->d_name, "TMP")     ||
+            strstr(files->d_name, ".stdout") ||
+            strstr(files->d_name, ".stderr") ||
+            strstr(files->d_name, ".dat"))
+        {
+            util_snprintf(buffer, sizeof(buffer), "%s/%s", curdir, files->d_name);
+            if (remove(buffer))
+                con_err("error removing temporary file: %s\n", buffer);
+        }
+    }
+
+    closedir(dir);
+}
+
+static void task_destroy(void) {
+    /*
+     * Free all the data in the task list and finally the list itself
+     * then proceed to cleanup anything else outside the program like
+     * temporary files.
+     */
+    size_t i;
+    for (i = 0; i < vec_size(task_tasks); i++) {
+        /*
+         * Close any open handles to files or processes here.  It's mighty
+         * annoying to have to do all this cleanup work.
+         */
+        if (task_tasks[i].stdoutlog) fclose(task_tasks[i].stdoutlog);
+        if (task_tasks[i].stderrlog) fclose(task_tasks[i].stderrlog);
+
+        /*
+         * Only remove the log files if the test actually compiled otherwise
+         * forget about it (or if it didn't compile, and the procedure type
+         * was set to -fail (meaning it shouldn't compile) .. stil remove)
+         */
+        if (task_tasks[i].compiled || !strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
+            if (remove(task_tasks[i].stdoutlogfile))
+                con_err("error removing stdout log file: %s\n", task_tasks[i].stdoutlogfile);
+            if (remove(task_tasks[i].stderrlogfile))
+                con_err("error removing stderr log file: %s\n", task_tasks[i].stderrlogfile);
+
+            (void)!remove(task_tasks[i].tmpl->tempfilename);
+        }
+
+        /* free util_strdup data for log files */
+        mem_d(task_tasks[i].stdoutlogfile);
+        mem_d(task_tasks[i].stderrlogfile);
+
+        task_template_destroy(task_tasks[i].tmpl);
+    }
+    vec_free(task_tasks);
+}
+
+/*
+ * This executes the QCVM task for a specificly compiled progs.dat
+ * using the template passed into it for call-flags and user defined
+ * messages IF the procedure type is -execute, otherwise it matches
+ * the preprocessor output.
+ */
+static bool task_trymatch(size_t i, char ***line) {
+    bool             success = true;
+    bool             process = true;
+    int              retval  = EXIT_SUCCESS;
+    FILE       *execute;
+    char             buffer[4096];
+    task_template_t *tmpl = task_tasks[i].tmpl;
+
+    memset  (buffer,0,sizeof(buffer));
+
+    if (!strcmp(tmpl->proceduretype, "-execute")) {
+        /*
+         * Drop the execution flags for the QCVM if none where
+         * actually specified.
+         */
+        if (!strcmp(tmpl->executeflags, "$null")) {
+            util_snprintf(buffer,  sizeof(buffer), "%s %s",
+                task_bins[TASK_EXECUTE],
+                tmpl->tempfilename
+            );
+        } else {
+            util_snprintf(buffer,  sizeof(buffer), "%s %s %s",
+                task_bins[TASK_EXECUTE],
+                tmpl->executeflags,
+                tmpl->tempfilename
+            );
+        }
+
+        execute = popen(buffer, "r");
+        if (!execute)
+            return false;
+    } else if (!strcmp(tmpl->proceduretype, "-pp")) {
+        /*
+         * we're preprocessing, which means we need to read int
+         * the produced file and do some really weird shit.
+         */
+        if (!(execute = fopen(tmpl->tempfilename, "r")))
+            return false;
+        process = false;
+    } else {
+        /*
+         * we're testing diagnostic output, which means it will be
+         * in runhandles[2] (stderr) since that is where the compiler
+         * puts it's errors.
+         */
+        if (!(execute = fopen(task_tasks[i].stderrlogfile, "r")))
+            return false;
+        process = false;
+    }
+
+    /*
+     * Now lets read the lines and compare them to the matches we expect
+     * and handle accordingly.
+     */
+    {
+        char  *data    = NULL;
+        size_t size    = 0;
+        size_t compare = 0;
+
+        while (util_getline(&data, &size, execute) != EOF) {
+            if (!strcmp(data, "No main function found\n")) {
+                con_err("test failure: `%s` (No main function found) [%s]\n",
+                    tmpl->description,
+                    tmpl->rulesfile
+                );
+                if (!process)
+                    fclose(execute);
+                else
+                    pclose((FILE*)execute);
+                return false;
+            }
+
+            /*
+             * Trim newlines from data since they will just break our
+             * ability to properly validate matches.
+             */
+            if  (strrchr(data, '\n'))
+                *strrchr(data, '\n') = '\0';
+
+            /*
+             * We remove the file/directory and stuff from the error
+             * match messages when testing diagnostics.
+             */
+            if(!strcmp(tmpl->proceduretype, "-diagnostic")) {
+                if (strstr(data, "there have been errors, bailing out"))
+                    continue; /* ignore it */
+                if (strstr(data, ": error: ")) {
+                    char *claim = util_strdup(data + (strstr(data, ": error: ") - data) + 9);
+                    mem_d(data);
+                    data = claim;
+                }
+            }
+
+            /*
+             * We need to ignore null lines for when -pp is used (preprocessor), since
+             * the preprocessor is likely to create empty newlines in certain macro
+             * instantations, otherwise it's in the wrong nature to ignore empty newlines.
+             */
+            if (!strcmp(tmpl->proceduretype, "-pp") && !*data)
+                continue;
+
+            if (vec_size(tmpl->comparematch) > compare) {
+                if (strcmp(data, tmpl->comparematch[compare++])) {
+                    success = false;
+                }
+            } else {
+                success = false;
+            }
+
+            /*
+             * Copy to output vector for diagnostics if execution match
+             * fails.
+             */
+            vec_push(*line, data);
+
+            /* reset */
+            data = NULL;
+            size = 0;
+        }
+
+        if (compare != vec_size(tmpl->comparematch))
+            success = false;
+
+        mem_d(data);
+        data = NULL;
+    }
+
+    if (process)
+        retval = pclose((FILE*)execute);
+    else
+        fclose(execute);
+
+    return success && retval == EXIT_SUCCESS;
+}
+
+static const char *task_type(task_template_t *tmpl) {
+    if (!strcmp(tmpl->proceduretype, "-pp"))
+        return "type: preprocessor";
+    if (!strcmp(tmpl->proceduretype, "-execute"))
+        return "type: execution";
+    if (!strcmp(tmpl->proceduretype, "-compile"))
+        return "type: compile";
+    if (!strcmp(tmpl->proceduretype, "-diagnostic"))
+        return "type: diagnostic";
+    return "type: fail";
+}
+
+/*
+ * This schedualizes all tasks and actually runs them individually
+ * this is generally easy for just -compile variants.  For compile and
+ * execution this takes more work since a task needs to be generated
+ * from thin air and executed INLINE.
+ */
+#include <math.h>
+static size_t task_schedualize(size_t *pad) {
+    char   space[2][64];
+    bool   execute  = false;
+    char  *data     = NULL;
+    char **match    = NULL;
+    size_t size     = 0;
+    size_t i        = 0;
+    size_t j        = 0;
+    size_t failed   = 0;
+    int    status   = 0;
+
+    util_snprintf(space[0], sizeof(space[0]), "%d", (int)vec_size(task_tasks));
+
+    for (; i < vec_size(task_tasks); i++) {
+        memset(space[1], 0, sizeof(space[1]));
+        util_snprintf(space[1], sizeof(space[1]), "%d", (int)(i + 1));
+
+        con_out("test #%u %*s", i + 1, strlen(space[0]) - strlen(space[1]), "");
+
+        /*
+         * Generate a task from thin air if it requires execution in
+         * the QCVM.
+         */
+
+        /* diagnostic is not executed, but compare tested instead, like preproessor */
+        execute = !! (!strcmp(task_tasks[i].tmpl->proceduretype, "-execute")) ||
+                     (!strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))      ||
+                     (!strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"));
+
+        /*
+         * We assume it compiled before we actually compiled :).  On error
+         * we change the value
+         */
+        task_tasks[i].compiled = true;
+
+        /*
+         * Read data from stdout first and pipe that stuff into a log file
+         * then we do the same for stderr.
+         */
+        while (util_getline(&data, &size, task_tasks[i].runhandles[1]) != EOF) {
+            fputs(data, task_tasks[i].stdoutlog);
+
+            if (strstr(data, "failed to open file")) {
+                task_tasks[i].compiled = false;
+                execute                = false;
+            }
+        }
+        while (util_getline(&data, &size, task_tasks[i].runhandles[2]) != EOF) {
+            /*
+             * If a string contains an error we just dissalow execution
+             * of it in the vm.
+             *
+             * TODO: make this more percise, e.g if we print a warning
+             * that refers to a variable named error, or something like
+             * that .. then this will blowup :P
+             */
+            if (strstr(data, "error") && strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic")) {
+                execute                = false;
+                task_tasks[i].compiled = false;
+            }
+
+            fputs(data, task_tasks[i].stderrlog);
+            fflush(task_tasks[i].stderrlog); /* fast flush for read */
+        }
+
+        if (!task_tasks[i].compiled && strcmp(task_tasks[i].tmpl->proceduretype, "-fail")) {
+            con_out("failure:   `%s` %*s %*s\n",
+                task_tasks[i].tmpl->description,
+                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
+                task_tasks[i].tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(failed to compile)") - pad[2]),
+                "(failed to compile)"
+            );
+            failed++;
+            continue;
+        }
+
+        status = task_pclose(task_tasks[i].runhandles);
+        if ((!strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_SUCCESS)
+        ||  ( strcmp(task_tasks[i].tmpl->proceduretype, "-fail") && status == EXIT_FAILURE)) {
+            con_out("failure:   `%s` %*s %*s\n",
+                task_tasks[i].tmpl->description,
+                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
+                task_tasks[i].tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen("(compiler didn't return exit success)") - pad[2]),
+                "(compiler didn't return exit success)"
+            );
+            failed++;
+            continue;
+        }
+
+        if (!execute) {
+            con_out("succeeded: `%s` %*s %*s\n",
+                task_tasks[i].tmpl->description,
+                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
+                task_tasks[i].tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl)) - pad[2]),
+                task_type(task_tasks[i].tmpl)
+
+            );
+            continue;
+        }
+
+        /*
+         * If we made it here that concludes the task is to be executed
+         * in the virtual machine (or the preprocessor output needs to
+         * be matched).
+         */
+        if (!task_trymatch(i, &match)) {
+            size_t d = 0;
+
+            con_out("failure:   `%s` %*s %*s\n",
+                task_tasks[i].tmpl->description,
+                (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
+                task_tasks[i].tmpl->rulesfile,
+                (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(
+                    (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
+                        ? "(invalid results from execution)"
+                        : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"))
+                            ? "(invalid results from preprocessing)"
+                            : "(invalid results from compiler diagnsotics)"
+                ) - pad[2]),
+                (strcmp(task_tasks[i].tmpl->proceduretype, "-pp"))
+                    ? "(invalid results from execution)"
+                    : (strcmp(task_tasks[i].tmpl->proceduretype, "-diagnostic"))
+                            ? "(invalid results from preprocessing)"
+                            : "(invalid results from compiler diagnsotics)"
+            );
+
+            /*
+             * Print nicely formatted expected match lists to console error
+             * handler for the all the given matches in the template file and
+             * what was actually returned from executing.
+             */
+            con_out("    Expected From %u Matches: (got %u Matches)\n",
+                vec_size(task_tasks[i].tmpl->comparematch),
+                vec_size(match)
+            );
+            for (; d < vec_size(task_tasks[i].tmpl->comparematch); d++) {
+                char  *select = task_tasks[i].tmpl->comparematch[d];
+                size_t length = 60 - strlen(select);
+
+                con_out("        Expected: \"%s\"", select);
+                while (length --)
+                    con_out(" ");
+                con_out("| Got: \"%s\"\n", (d >= vec_size(match)) ? "<<nothing else to compare>>" : match[d]);
+            }
+
+            /*
+             * Print the non-expected out (since we are simply not expecting it)
+             * This will help track down bugs in template files that fail to match
+             * something.
+             */
+            if (vec_size(match) > vec_size(task_tasks[i].tmpl->comparematch)) {
+                for (d = 0; d < vec_size(match) - vec_size(task_tasks[i].tmpl->comparematch); d++) {
+                    con_out("        Expected: Nothing                                                       | Got: \"%s\"\n",
+                        match[d + vec_size(task_tasks[i].tmpl->comparematch)]
+                    );
+                }
+            }
+
+
+            for (j = 0; j < vec_size(match); j++)
+                mem_d(match[j]);
+            vec_free(match);
+            failed++;
+            continue;
+        }
+
+        for (j = 0; j < vec_size(match); j++)
+            mem_d(match[j]);
+        vec_free(match);
+
+        con_out("succeeded: `%s` %*s %*s\n",
+            task_tasks[i].tmpl->description,
+            (pad[0] + pad[1] - strlen(task_tasks[i].tmpl->description)) + (strlen(task_tasks[i].tmpl->rulesfile) - pad[1]),
+            task_tasks[i].tmpl->rulesfile,
+            (pad[1] + pad[2] - strlen(task_tasks[i].tmpl->rulesfile)) + (strlen(task_type(task_tasks[i].tmpl))- pad[2]),
+            task_type(task_tasks[i].tmpl)
+
+        );
+    }
+    mem_d(data);
+    return failed;
+}
+
+/*
+ * This is the heart of the whole test-suite process.  This cleans up
+ * any existing temporary files left behind as well as log files left
+ * behind.  Then it propagates a list of tests from `curdir` by scaning
+ * it for template files and compiling them into tasks, in which it
+ * schedualizes them (executes them) and actually reports errors and
+ * what not.  It then proceeds to destroy the tasks and return memory
+ * it's the engine :)
+ *
+ * It returns true of tests could be propagated, otherwise it returns
+ * false.
+ *
+ * It expects con_init() was called before hand.
+ */
+static GMQCC_WARN bool test_perform(const char *curdir, const char *defs) {
+    size_t             failed       = false;
+    static const char *default_defs = "defs.qh";
+
+    size_t pad[] = {
+        /* test ### [succeed/fail]: `description`      [tests/template.tmpl]     [type] */
+                    0,                                 0,                        0
+    };
+
+    /*
+     * If the default definition file isn't set to anything.  We will
+     * use the default_defs here, which is "defs.qc"
+     */
+    if (!defs) {
+        defs = default_defs;
+    }
+
+
+    task_precleanup(curdir);
+    if (!task_propagate(curdir, pad, defs)) {
+        con_err("error: failed to propagate tasks\n");
+        task_destroy();
+        return false;
+    }
+    /*
+     * If we made it here all tasks where propagated from their resultant
+     * template file.  So we can start the FILO scheduler, this has been
+     * designed in the most thread-safe way possible for future threading
+     * it's designed to prevent lock contention, and possible syncronization
+     * issues.
+     */
+    failed = task_schedualize(pad);
+    if (failed)
+        con_out("%u out of %u tests failed\n", failed, vec_size(task_tasks));
+    task_destroy();
+
+    return (failed) ? false : true;
+}
+
+/*
+ * Fancy GCC-like LONG parsing allows things like --opt=param with
+ * assignment operator.  This is used for redirecting stdout/stderr
+ * console to specific files of your choice.
+ */
+static bool parsecmd(const char *optname, int *argc_, char ***argv_, char **out, int ds, bool split) {
+    int  argc   = *argc_;
+    char **argv = *argv_;
+
+    size_t len = strlen(optname);
+
+    if (strncmp(argv[0]+ds, optname, len))
+        return false;
+
+    /* it's --optname, check how the parameter is supplied */
+    if (argv[0][ds+len] == '=') {
+        *out = argv[0]+ds+len+1;
+        return true;
+    }
+
+    if (!split || argc < ds) /* no parameter was provided, or only single-arg form accepted */
+        return false;
+
+    /* using --opt param */
+    *out = argv[1];
+    --*argc_;
+    ++*argv_;
+    return true;
+}
+
+int main(int argc, char **argv) {
+    bool succeed  = false;
+    char *defs = NULL;
+
+    con_init();
+
+    /*
+     * Command line option parsing commences now We only need to support
+     * a few things in the test suite.
+     */
+    while (argc > 1) {
+        ++argv;
+        --argc;
+
+        if (argv[0][0] == '-') {
+            if (parsecmd("defs", &argc, &argv, &defs, 1, false))
+                continue;
+
+            if (!strcmp(argv[0]+1, "debug")) {
+                OPTS_OPTION_BOOL(OPTION_DEBUG) = true;
+                continue;
+            }
+            if (!strcmp(argv[0]+1, "nocolor")) {
+                con_color(0);
+                continue;
+            }
+
+            con_err("invalid argument %s\n", argv[0]+1);
+            return -1;
+        }
+    }
+    succeed = test_perform("tests", defs);
+
+    return (succeed) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/utf8.c b/utf8.c
deleted file mode 100644 (file)
index 622363c..0000000
--- a/utf8.c
+++ /dev/null
@@ -1,129 +0,0 @@
-#include "gmqcc.h"
-
-/*
- * Based on the flexible and economical utf8 decoder:
- * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
- *
- * This is slightly more economical, the fastest way to decode utf8 is
- * with a lookup table as in:
- *
- * first 1-byte lookup
- * if that fails, 2-byte lookup
- * if that fails, 3-byte lookup
- * if that fails, 4-byte lookup
- *
- * The following table can be generated with some interval trickery.
- * consider an interval [a, b):
- *
- *      a must be 0x80 or b must be 0xc0, lower 3 bits
- *      are clear, thus:
- *          interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23))
- *
- * The failstate can be represented as interval(0x80,0x80), it's
- * odd to see but this is a full state machine.
- *
- * The table than maps the corresponding sections as a serise of
- * intervals.
- *
- * In this table the transition values are pre-multiplied with 16 to
- * save a shift instruction for every byte, we throw away fillers
- * which makes the table smaller.
- *
- * The first section of the table handles bytes with leading C
- * The second section of the table handles bytes with leading D
- * The third section of the table handles bytes with leading E
- * The last section of the table handles bytes with leading F
- *
- * The values themselfs in the table are arranged so that when you
- * left shift them by 6 to shift continuation characters into place, the
- * new top bits tell you:
- *
- *  1 - if you keep going
- *  2 - the range of valid values for the next byte
- */
-static const uint32_t utf8_tab[] = {
-    0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006,
-    0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B,
-    0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010,
-    0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015,
-    0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A,
-    0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F,
-    0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004,
-    0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009,
-    0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E,
-    0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003,
-    0xD30C0004
-};
-
-int utf8_from(char *s, utf8ch_t ch) {
-    if (!s)
-        return 0;
-
-    if ((unsigned)ch < 0x80) {
-        *s = ch;
-        return 1;
-    } else if ((unsigned)ch < 0x800) {
-        *s++ = 0xC0 | (ch >> 6);
-        *s   = 0x80 | (ch & 0x3F);
-        return 2;
-    } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) {
-        *s++ = 0xE0 | (ch >> 12);
-        *s++ = 0x80 | ((ch >> 6) & 0x3F);
-        *s   = 0x80 | (ch & 0x3F);
-        return 3;
-    } else if ((unsigned)ch - 0x10000 < 0x100000) {
-        *s++ = 0xF0 | (ch >> 18);
-        *s++ = 0x80 | ((ch >> 12) & 0x3F);
-        *s++ = 0x80 | ((ch >> 6) & 0x3F);
-        *s   = 0x80 | (ch & 0x3F);
-        return 4;
-    }
-    return 0;
-}
-
-int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) {
-    unsigned c,j;
-
-    if (!s || !n)
-        return 0;
-
-    /* This is consistent with mbtowc behaviour. */
-    if (!i)
-        i = (utf8ch_t*)(void*)&i;
-
-    if (*s < 0x80)
-        return !!(*i = *s);
-    if (*s-0xC2U > 0x32)
-        return 0;
-
-    c = utf8_tab[*s++-0xC2U];
-
-    /*
-     * Avoid excessive checks against n.
-     *
-     * When shifting state `n-1` times does not clear the high bit,
-     * then the value of `n` won't satisfy the condition to read a
-     * character as it will be insufficent.
-     */
-    if (n < 4 && ((c<<(6*n-6)) & (1U << 31)))
-        return 0;
-
-    /*
-     * The upper 6 state bits are negitive integer offset to a bound-check
-     * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f
-     */
-    if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7)
-        return 0;
-
-    for (j=2; j<3; j++) {
-        if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) {
-            *i = c;
-            return j;
-        }
-        if (*s-0x80U >= 0x40)
-            return 0;
-    }
-
-    *i = c<<6 | (*s++-0x80);
-    return 4;
-}
diff --git a/utf8.cpp b/utf8.cpp
new file mode 100644 (file)
index 0000000..622363c
--- /dev/null
+++ b/utf8.cpp
@@ -0,0 +1,129 @@
+#include "gmqcc.h"
+
+/*
+ * Based on the flexible and economical utf8 decoder:
+ * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * This is slightly more economical, the fastest way to decode utf8 is
+ * with a lookup table as in:
+ *
+ * first 1-byte lookup
+ * if that fails, 2-byte lookup
+ * if that fails, 3-byte lookup
+ * if that fails, 4-byte lookup
+ *
+ * The following table can be generated with some interval trickery.
+ * consider an interval [a, b):
+ *
+ *      a must be 0x80 or b must be 0xc0, lower 3 bits
+ *      are clear, thus:
+ *          interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23))
+ *
+ * The failstate can be represented as interval(0x80,0x80), it's
+ * odd to see but this is a full state machine.
+ *
+ * The table than maps the corresponding sections as a serise of
+ * intervals.
+ *
+ * In this table the transition values are pre-multiplied with 16 to
+ * save a shift instruction for every byte, we throw away fillers
+ * which makes the table smaller.
+ *
+ * The first section of the table handles bytes with leading C
+ * The second section of the table handles bytes with leading D
+ * The third section of the table handles bytes with leading E
+ * The last section of the table handles bytes with leading F
+ *
+ * The values themselfs in the table are arranged so that when you
+ * left shift them by 6 to shift continuation characters into place, the
+ * new top bits tell you:
+ *
+ *  1 - if you keep going
+ *  2 - the range of valid values for the next byte
+ */
+static const uint32_t utf8_tab[] = {
+    0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006,
+    0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B,
+    0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010,
+    0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015,
+    0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A,
+    0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F,
+    0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004,
+    0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009,
+    0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E,
+    0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003,
+    0xD30C0004
+};
+
+int utf8_from(char *s, utf8ch_t ch) {
+    if (!s)
+        return 0;
+
+    if ((unsigned)ch < 0x80) {
+        *s = ch;
+        return 1;
+    } else if ((unsigned)ch < 0x800) {
+        *s++ = 0xC0 | (ch >> 6);
+        *s   = 0x80 | (ch & 0x3F);
+        return 2;
+    } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) {
+        *s++ = 0xE0 | (ch >> 12);
+        *s++ = 0x80 | ((ch >> 6) & 0x3F);
+        *s   = 0x80 | (ch & 0x3F);
+        return 3;
+    } else if ((unsigned)ch - 0x10000 < 0x100000) {
+        *s++ = 0xF0 | (ch >> 18);
+        *s++ = 0x80 | ((ch >> 12) & 0x3F);
+        *s++ = 0x80 | ((ch >> 6) & 0x3F);
+        *s   = 0x80 | (ch & 0x3F);
+        return 4;
+    }
+    return 0;
+}
+
+int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) {
+    unsigned c,j;
+
+    if (!s || !n)
+        return 0;
+
+    /* This is consistent with mbtowc behaviour. */
+    if (!i)
+        i = (utf8ch_t*)(void*)&i;
+
+    if (*s < 0x80)
+        return !!(*i = *s);
+    if (*s-0xC2U > 0x32)
+        return 0;
+
+    c = utf8_tab[*s++-0xC2U];
+
+    /*
+     * Avoid excessive checks against n.
+     *
+     * When shifting state `n-1` times does not clear the high bit,
+     * then the value of `n` won't satisfy the condition to read a
+     * character as it will be insufficent.
+     */
+    if (n < 4 && ((c<<(6*n-6)) & (1U << 31)))
+        return 0;
+
+    /*
+     * The upper 6 state bits are negitive integer offset to a bound-check
+     * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f
+     */
+    if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7)
+        return 0;
+
+    for (j=2; j<3; j++) {
+        if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) {
+            *i = c;
+            return j;
+        }
+        if (*s-0x80U >= 0x40)
+            return 0;
+    }
+
+    *i = c<<6 | (*s++-0x80);
+    return 4;
+}
diff --git a/util.c b/util.c
deleted file mode 100644 (file)
index ceed449..0000000
--- a/util.c
+++ /dev/null
@@ -1,732 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-#include "gmqcc.h"
-
-const char *util_instr_str[VINSTR_END] = {
-    "DONE",       "MUL_F",      "MUL_V",      "MUL_FV",
-    "MUL_VF",     "DIV_F",      "ADD_F",      "ADD_V",
-    "SUB_F",      "SUB_V",      "EQ_F",       "EQ_V",
-    "EQ_S",       "EQ_E",       "EQ_FNC",     "NE_F",
-    "NE_V",       "NE_S",       "NE_E",       "NE_FNC",
-    "LE",         "GE",         "LT",         "GT",
-    "LOAD_F",     "LOAD_V",     "LOAD_S",     "LOAD_ENT",
-    "LOAD_FLD",   "LOAD_FNC",   "ADDRESS",    "STORE_F",
-    "STORE_V",    "STORE_S",    "STORE_ENT",  "STORE_FLD",
-    "STORE_FNC",  "STOREP_F",   "STOREP_V",   "STOREP_S",
-    "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN",
-    "NOT_F",      "NOT_V",      "NOT_S",      "NOT_ENT",
-    "NOT_FNC",    "IF",         "IFNOT",      "CALL0",
-    "CALL1",      "CALL2",      "CALL3",      "CALL4",
-    "CALL5",      "CALL6",      "CALL7",      "CALL8",
-    "STATE",      "GOTO",       "AND",        "OR",
-    "BITAND",     "BITOR"
-};
-
-/*
- * only required if big endian .. otherwise no need to swap
- * data.
- */
-#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG || PLATFORM_BYTE_ORDER == -1
-    static GMQCC_INLINE void util_swap16(uint16_t *d, size_t l) {
-        while (l--) {
-            d[l] = (d[l] << 8) | (d[l] >> 8);
-        }
-    }
-
-    static GMQCC_INLINE void util_swap32(uint32_t *d, size_t l) {
-        while (l--) {
-            uint32_t v;
-            v = ((d[l] << 8) & 0xFF00FF00) | ((d[l] >> 8) & 0x00FF00FF);
-            d[l] = (v << 16) | (v >> 16);
-        }
-    }
-
-    /* Some strange system doesn't like constants that big, AND doesn't recognize an ULL suffix
-     * so let's go the safe way
-     */
-    static GMQCC_INLINE void util_swap64(uint32_t *d, size_t l) {
-        while (l--) {
-            uint64_t v;
-            v = ((d[l] << 8) & 0xFF00FF00FF00FF00) | ((d[l] >> 8) & 0x00FF00FF00FF00FF);
-            v = ((v << 16) & 0xFFFF0000FFFF0000) | ((v >> 16) & 0x0000FFFF0000FFFF);
-            d[l] = (v << 32) | (v >> 32);
-        }
-    }
-#endif
-
-void util_endianswap(void *_data, size_t count, unsigned int typesize) {
-#   if PLATFORM_BYTE_ORDER == -1 /* runtime check */
-    if (*((char*)&typesize))
-        return;
-#else
-
-#   if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE
-        /* prevent unused warnings */
-        (void) _data;
-        (void) count;
-        (void) typesize;
-        return;
-#   else
-        switch (typesize) {
-            case 1: return;
-            case 2:
-                util_swap16((uint16_t*)_data, count);
-                return;
-            case 4:
-                util_swap32((uint32_t*)_data, count);
-                return;
-            case 8:
-                util_swap64((uint32_t*)_data, count);
-                return;
-
-            default:
-                con_err ("util_endianswap: I don't know how to swap a %u byte structure!\n", typesize);
-                exit(EXIT_FAILURE); /* please blow the fuck up! */
-        }
-#   endif
-#endif
-}
-
-void util_swap_header(prog_header_t *code_header) {
-    util_endianswap(&code_header->version,              1, sizeof(code_header->version));
-    util_endianswap(&code_header->crc16,                1, sizeof(code_header->crc16));
-    util_endianswap(&code_header->statements.offset,    1, sizeof(code_header->statements.offset));
-    util_endianswap(&code_header->statements.length,    1, sizeof(code_header->statements.length));
-    util_endianswap(&code_header->defs.offset,          1, sizeof(code_header->defs.offset));
-    util_endianswap(&code_header->defs.length,          1, sizeof(code_header->defs.length));
-    util_endianswap(&code_header->fields.offset,        1, sizeof(code_header->fields.offset));
-    util_endianswap(&code_header->fields.length,        1, sizeof(code_header->fields.length));
-    util_endianswap(&code_header->functions.offset,     1, sizeof(code_header->functions.offset));
-    util_endianswap(&code_header->functions.length,     1, sizeof(code_header->functions.length));
-    util_endianswap(&code_header->strings.offset,       1, sizeof(code_header->strings.offset));
-    util_endianswap(&code_header->strings.length,       1, sizeof(code_header->strings.length));
-    util_endianswap(&code_header->globals.offset,       1, sizeof(code_header->globals.offset));
-    util_endianswap(&code_header->globals.length,       1, sizeof(code_header->globals.length));
-    util_endianswap(&code_header->entfield,             1, sizeof(code_header->entfield));
-}
-
-void util_swap_statements(prog_section_statement_t *statements) {
-    size_t i;
-
-    for (i = 0; i < vec_size(statements); ++i) {
-        util_endianswap(&statements[i].opcode,  1, sizeof(statements[i].opcode));
-        util_endianswap(&statements[i].o1,      1, sizeof(statements[i].o1));
-        util_endianswap(&statements[i].o2,      1, sizeof(statements[i].o2));
-        util_endianswap(&statements[i].o3,      1, sizeof(statements[i].o3));
-    }
-}
-
-void util_swap_defs_fields(prog_section_both_t *section) {
-    size_t i;
-
-    for (i = 0; i < vec_size(section); ++i) {
-        util_endianswap(&section[i].type,   1, sizeof(section[i].type));
-        util_endianswap(&section[i].offset, 1, sizeof(section[i].offset));
-        util_endianswap(&section[i].name,   1, sizeof(section[i].name));
-    }
-}
-
-void util_swap_functions(prog_section_function_t *functions) {
-    size_t i;
-
-    for (i = 0; i < vec_size(functions); ++i) {
-        util_endianswap(&functions[i].entry,        1, sizeof(functions[i].entry));
-        util_endianswap(&functions[i].firstlocal,   1, sizeof(functions[i].firstlocal));
-        util_endianswap(&functions[i].locals,       1, sizeof(functions[i].locals));
-        util_endianswap(&functions[i].profile,      1, sizeof(functions[i].profile));
-        util_endianswap(&functions[i].name,         1, sizeof(functions[i].name));
-        util_endianswap(&functions[i].file,         1, sizeof(functions[i].file));
-        util_endianswap(&functions[i].nargs,        1, sizeof(functions[i].nargs));
-        /* Don't swap argsize[] - it's just a byte array, which Quake uses only as such. */
-    }
-}
-
-void util_swap_globals(int32_t *globals) {
-    util_endianswap(globals, vec_size(globals), sizeof(int32_t));
-}
-
-/*
-* Based On:
-*   Slicing-by-8 algorithms by Michael E.
-*       Kounavis and Frank L. Berry from Intel Corp.
-*       http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf
-*
-*   This code was made to be slightly less confusing with macros, which
-*   I suppose is somewhat ironic.
-*
-*   The code had to be changed for non reflected on the output register
-*   since that's the method Quake uses.
-*
-*   The code also had to be changed for CRC16, which is slightly harder
-*   since the CRC32 method in the original Intel paper used a different
-*   bit order convention.
-*
-* Notes about the table:
-*   - It's exactly 4K in size
-*   - 64 elements fit in a cache line
-*   - can do 8 iterations unrolled 8 times for free
-*   - The first 256 elements of the table are standard CRC16 table
-*
-* Table can be generated with the following utility:
-*/
-#if 0
-#include <stdio.h>
-#include <stdint.h>
-int main(void) {
-    for (unsigned i = 0; i < 0x100; ++i) {
-        uint16_t x = i << 8;
-        for (int j = 0; j < 8; ++j)
-            x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0);
-        tab[0][i] = x;
-    }
-    for (unsigned i = 0; i < 0x100; ++i) {
-        uint16_t c = tab[0][i];
-        for (unsigned j = 1; j < 8; ++j) {
-            c = tab[0][c >> 8] ^ (c << 8);
-            tab[j][i] = c;
-        }
-    }
-    printf("static const uint16_t util_crc16_table[8][256] = {");
-    for (int i = 0; i < 8; ++i) {
-        printf("{\n");
-        for (int j = 0; j < 0x100; ++j) {
-            printf((j & 7) ? " " : "    ");
-            printf((j != 0x100-1) ? "0x%04X," : "0x%04X", tab[i][j]);
-            if ((j & 7) == 7)
-                printf("\n");
-        }
-        printf((i != 7) ? "}," : "}");
-    }
-    printf("};\n");
-    return 0;
-}
-#endif
-/*
- * Non-Reflective version is present as well as a reference.
- *
- * TODO:
- *  combine the crc16 into u32s and mask off low high for byte order
- *  to make the arrays smaller.
- */
-
-static const uint16_t util_crc16_table[8][256] = {{
-    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
-    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
-    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
-    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
-    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
-    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
-    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
-    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
-    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
-    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
-    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
-    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
-    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
-    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
-    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
-    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
-    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
-    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
-    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
-    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
-    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
-    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
-    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
-    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
-    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
-    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
-    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
-    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
-    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
-    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
-    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
-    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
-},{
-    0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997,
-    0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E,
-    0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4,
-    0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D,
-    0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71,
-    0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8,
-    0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02,
-    0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB,
-    0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B,
-    0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2,
-    0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728,
-    0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81,
-    0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD,
-    0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14,
-    0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE,
-    0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867,
-    0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F,
-    0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6,
-    0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C,
-    0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5,
-    0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9,
-    0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40,
-    0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A,
-    0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33,
-    0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3,
-    0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A,
-    0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0,
-    0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519,
-    0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925,
-    0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C,
-    0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56,
-    0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF
-},{
-    0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590,
-    0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31,
-    0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3,
-    0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52,
-    0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356,
-    0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7,
-    0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035,
-    0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994,
-    0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D,
-    0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C,
-    0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E,
-    0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF,
-    0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB,
-    0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A,
-    0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98,
-    0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439,
-    0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA,
-    0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B,
-    0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9,
-    0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408,
-    0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C,
-    0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD,
-    0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F,
-    0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE,
-    0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367,
-    0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6,
-    0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004,
-    0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5,
-    0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1,
-    0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00,
-    0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2,
-    0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63
-},{
-    0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D,
-    0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE,
-    0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A,
-    0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49,
-    0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663,
-    0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0,
-    0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4,
-    0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807,
-    0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1,
-    0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72,
-    0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416,
-    0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5,
-    0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF,
-    0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C,
-    0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358,
-    0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B,
-    0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15,
-    0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6,
-    0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2,
-    0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271,
-    0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B,
-    0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98,
-    0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC,
-    0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F,
-    0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289,
-    0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A,
-    0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E,
-    0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED,
-    0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7,
-    0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004,
-    0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60,
-    0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3
-},{
-    0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4,
-    0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9,
-    0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E,
-    0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3,
-    0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60,
-    0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D,
-    0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A,
-    0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917,
-    0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC,
-    0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491,
-    0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6,
-    0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB,
-    0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08,
-    0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25,
-    0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52,
-    0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F,
-    0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504,
-    0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729,
-    0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E,
-    0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373,
-    0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0,
-    0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D,
-    0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA,
-    0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7,
-    0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C,
-    0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641,
-    0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036,
-    0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B,
-    0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8,
-    0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5,
-    0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882,
-    0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF
-},{
-    0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841,
-    0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503,
-    0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5,
-    0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87,
-    0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49,
-    0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B,
-    0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD,
-    0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F,
-    0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251,
-    0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13,
-    0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5,
-    0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597,
-    0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759,
-    0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B,
-    0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD,
-    0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F,
-    0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61,
-    0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123,
-    0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5,
-    0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7,
-    0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969,
-    0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B,
-    0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED,
-    0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF,
-    0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671,
-    0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33,
-    0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5,
-    0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7,
-    0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379,
-    0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B,
-    0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD,
-    0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF
-},{
-    0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944,
-    0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9,
-    0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F,
-    0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92,
-    0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2,
-    0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F,
-    0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9,
-    0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364,
-    0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8,
-    0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705,
-    0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3,
-    0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E,
-    0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E,
-    0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3,
-    0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25,
-    0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88,
-    0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD,
-    0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110,
-    0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6,
-    0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B,
-    0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B,
-    0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6,
-    0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30,
-    0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D,
-    0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51,
-    0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC,
-    0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A,
-    0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87,
-    0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7,
-    0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A,
-    0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC,
-    0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571
-},{
-    0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718,
-    0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2,
-    0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC,
-    0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476,
-    0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70,
-    0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA,
-    0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4,
-    0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E,
-    0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8,
-    0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12,
-    0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C,
-    0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6,
-    0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0,
-    0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A,
-    0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714,
-    0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE,
-    0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99,
-    0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443,
-    0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D,
-    0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7,
-    0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1,
-    0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B,
-    0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45,
-    0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F,
-    0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49,
-    0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293,
-    0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD,
-    0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27,
-    0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721,
-    0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB,
-    0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95,
-    0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F
-}};
-
-/* Non - Reflected */
-uint16_t util_crc16(uint16_t current, const char *GMQCC_RESTRICT k, size_t len) {
-    uint16_t h = current;
-
-    /* don't load twice */
-    const uint8_t *GMQCC_RESTRICT data = (const uint8_t *GMQCC_RESTRICT)k;
-    size_t n;
-
-    /* deal with the first bytes as bytes until we reach an 8 byte boundary */
-    while (len & 7) {
-        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
-        --len;
-    }
-
-    #define SELECT_BULK(X, MOD) util_crc16_table[(X)][data[7-(X)] ^ (MOD)]
-    #define SELECT_DATA(X)      util_crc16_table[(X)][data[7-(X)]]
-
-    for (n = len / 8; n; --n) {
-        h = SELECT_BULK(7, (h >> 8))   ^
-            SELECT_BULK(6, (h & 0xFF)) ^
-            SELECT_DATA(5) ^
-            SELECT_DATA(4) ^
-            SELECT_DATA(3) ^
-            SELECT_DATA(2) ^
-            SELECT_DATA(1) ^
-            SELECT_DATA(0);
-        data += 8;
-        len  -= 8;
-    }
-
-    #undef SELECT_BULK
-    #undef SELECT_DATA
-
-    /* deal with the rest with the byte method */
-    for (n = len & 7; n; --n)
-        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
-
-    return h;
-}
-
-/*
- * modifier is the match to make and the transposition from it, while add is the upper-value that determines the
- * transposition from uppercase to lower case.
- */
-static size_t util_strtransform(const char *in, char *out, size_t outsz, const char *mod, int add) {
-    size_t sz = 1;
-    for (; *in && sz < outsz; ++in, ++out, ++sz) {
-        *out = (*in == mod[0])
-                    ? mod[1]
-                    : (util_isalpha(*in) && ((add > 0) ? util_isupper(*in) : !util_isupper(*in)))
-                        ? *in + add
-                        : *in;
-    }
-    *out = 0;
-    return sz-1;
-}
-
-size_t util_strtocmd(const char *in, char *out, size_t outsz) {
-    return util_strtransform(in, out, outsz, "-_", 'A'-'a');
-}
-size_t util_strtononcmd(const char *in, char *out, size_t outsz) {
-    return util_strtransform(in, out, outsz, "_-", 'a'-'A');
-}
-size_t util_optimizationtostr(const char *in, char *out, size_t outsz) {
-    return util_strtransform(in, out, outsz, "_ ", 'a'-'A');
-}
-
-static int util_vasprintf(char **dat, const char *fmt, va_list args) {
-    int     ret;
-    int     len;
-    char   *tmp = NULL;
-    char    buf[128];
-    va_list cpy;
-
-    va_copy(cpy, args);
-    len = vsnprintf(buf, sizeof(buf), fmt, cpy);
-    va_end (cpy);
-
-    if (len < 0)
-        return len;
-
-    if (len < (int)sizeof(buf)) {
-        *dat = util_strdup(buf);
-        return len;
-    }
-
-    tmp = (char*)mem_a(len + 1);
-    if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) {
-        mem_d(tmp);
-        *dat = NULL;
-        return -1;
-    }
-
-    *dat = tmp;
-    return len;
-}
-
-int util_snprintf(char *str, size_t size, const char *fmt, ...) {
-    va_list  arg;
-    int ret;
-    va_start(arg, fmt);
-    ret = vsnprintf(str, size, fmt, arg);
-    va_end(arg);
-    return ret;
-}
-
-int util_asprintf(char **ret, const char *fmt, ...) {
-    va_list  args;
-    int read;
-    va_start(args, fmt);
-    read = util_vasprintf(ret, fmt, args);
-    va_end(args);
-    return read;
-}
-
-int util_sscanf(const char *str, const char *format, ...) {
-    va_list  args;
-    int read;
-    va_start(args, format);
-    read = vsscanf(str, format, args);
-    va_end(args);
-    return read;
-}
-
-char *util_strncpy(char *dest, const char *src, size_t n) {
-    return strncpy(dest, src, n);
-}
-
-char *util_strncat(char *dest, const char *src, size_t n) {
-    return strncat(dest, src, n);
-}
-
-char *util_strcat(char *dest, const char *src) {
-    return strcat(dest, src);
-}
-
-const char *util_strerror(int err) {
-    return strerror(err);
-}
-
-const struct tm *util_localtime(const time_t *timer) {
-    return localtime(timer);
-}
-
-const char *util_ctime(const time_t *timer) {
-    return ctime(timer);
-}
-
-int util_getline(char **lineptr, size_t *n, FILE *stream) {
-    int   chr;
-    int   ret;
-    char *pos;
-
-    if (!lineptr || !n || !stream)
-        return -1;
-    if (!*lineptr) {
-        if (!(*lineptr = (char*)mem_a((*n=64))))
-            return -1;
-    }
-
-    chr = *n;
-    pos = *lineptr;
-
-    for (;;) {
-        int c = getc(stream);
-
-        if (chr < 2) {
-            *n += (*n > 16) ? *n : 64;
-            chr = *n + *lineptr - pos;
-            if (!(*lineptr = (char*)mem_r(*lineptr,*n)))
-                return -1;
-            pos = *n - chr + *lineptr;
-        }
-
-        if (ferror(stream))
-            return -1;
-        if (c == EOF) {
-            if (pos == *lineptr)
-                return -1;
-            else
-                break;
-        }
-
-        *pos++ = c;
-        chr--;
-        if (c == '\n')
-            break;
-    }
-    *pos = '\0';
-    return (ret = pos - *lineptr);
-}
-
-#ifndef _WIN32
-#include <unistd.h>
-bool util_isatty(FILE *file) {
-    if (file == stdout) return !!isatty(STDOUT_FILENO);
-    if (file == stderr) return !!isatty(STDERR_FILENO);
-    return false;
-}
-#else
-bool util_isatty(FILE *file) {
-    return false;
-}
-#endif
-
-/*
- * A small noncryptographic PRNG based on:
- * http://burtleburtle.net/bob/rand/smallprng.html
- */
-static uint32_t util_rand_state[4] = {
-    0xF1EA5EED, 0x00000000,
-    0x00000000, 0x00000000
-};
-
-#define util_rand_rot(X, Y) (((X)<<(Y))|((X)>>(32-(Y))))
-
-uint32_t util_rand() {
-    uint32_t last;
-
-    last               = util_rand_state[0] - util_rand_rot(util_rand_state[1], 27);
-    util_rand_state[0] = util_rand_state[1] ^ util_rand_rot(util_rand_state[2], 17);
-    util_rand_state[1] = util_rand_state[2] + util_rand_state[3];
-    util_rand_state[2] = util_rand_state[3] + last;
-    util_rand_state[3] = util_rand_state[0] + last;
-
-    return util_rand_state[3];
-}
-
-#undef util_rand_rot
-
-void util_seed(uint32_t value) {
-    size_t i;
-
-    util_rand_state[0] = 0xF1EA5EED;
-    util_rand_state[1] = value;
-    util_rand_state[2] = value;
-    util_rand_state[3] = value;
-
-    for (i = 0; i < 20; ++i)
-        (void)util_rand();
-}
-
-size_t hash(const char *string) {
-    size_t hash = 0;
-    for(; *string; ++string) {
-        hash += *string;
-        hash += (hash << 10);
-        hash ^= (hash >> 6);
-    }
-    hash += hash << 3;
-    hash ^= hash >> 11;
-    hash += hash << 15;
-    return hash;
-}
-
diff --git a/util.cpp b/util.cpp
new file mode 100644 (file)
index 0000000..ceed449
--- /dev/null
+++ b/util.cpp
@@ -0,0 +1,732 @@
+#include <stdlib.h>
+#include <string.h>
+#include "gmqcc.h"
+
+const char *util_instr_str[VINSTR_END] = {
+    "DONE",       "MUL_F",      "MUL_V",      "MUL_FV",
+    "MUL_VF",     "DIV_F",      "ADD_F",      "ADD_V",
+    "SUB_F",      "SUB_V",      "EQ_F",       "EQ_V",
+    "EQ_S",       "EQ_E",       "EQ_FNC",     "NE_F",
+    "NE_V",       "NE_S",       "NE_E",       "NE_FNC",
+    "LE",         "GE",         "LT",         "GT",
+    "LOAD_F",     "LOAD_V",     "LOAD_S",     "LOAD_ENT",
+    "LOAD_FLD",   "LOAD_FNC",   "ADDRESS",    "STORE_F",
+    "STORE_V",    "STORE_S",    "STORE_ENT",  "STORE_FLD",
+    "STORE_FNC",  "STOREP_F",   "STOREP_V",   "STOREP_S",
+    "STOREP_ENT", "STOREP_FLD", "STOREP_FNC", "RETURN",
+    "NOT_F",      "NOT_V",      "NOT_S",      "NOT_ENT",
+    "NOT_FNC",    "IF",         "IFNOT",      "CALL0",
+    "CALL1",      "CALL2",      "CALL3",      "CALL4",
+    "CALL5",      "CALL6",      "CALL7",      "CALL8",
+    "STATE",      "GOTO",       "AND",        "OR",
+    "BITAND",     "BITOR"
+};
+
+/*
+ * only required if big endian .. otherwise no need to swap
+ * data.
+ */
+#if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_BIG || PLATFORM_BYTE_ORDER == -1
+    static GMQCC_INLINE void util_swap16(uint16_t *d, size_t l) {
+        while (l--) {
+            d[l] = (d[l] << 8) | (d[l] >> 8);
+        }
+    }
+
+    static GMQCC_INLINE void util_swap32(uint32_t *d, size_t l) {
+        while (l--) {
+            uint32_t v;
+            v = ((d[l] << 8) & 0xFF00FF00) | ((d[l] >> 8) & 0x00FF00FF);
+            d[l] = (v << 16) | (v >> 16);
+        }
+    }
+
+    /* Some strange system doesn't like constants that big, AND doesn't recognize an ULL suffix
+     * so let's go the safe way
+     */
+    static GMQCC_INLINE void util_swap64(uint32_t *d, size_t l) {
+        while (l--) {
+            uint64_t v;
+            v = ((d[l] << 8) & 0xFF00FF00FF00FF00) | ((d[l] >> 8) & 0x00FF00FF00FF00FF);
+            v = ((v << 16) & 0xFFFF0000FFFF0000) | ((v >> 16) & 0x0000FFFF0000FFFF);
+            d[l] = (v << 32) | (v >> 32);
+        }
+    }
+#endif
+
+void util_endianswap(void *_data, size_t count, unsigned int typesize) {
+#   if PLATFORM_BYTE_ORDER == -1 /* runtime check */
+    if (*((char*)&typesize))
+        return;
+#else
+
+#   if PLATFORM_BYTE_ORDER == GMQCC_BYTE_ORDER_LITTLE
+        /* prevent unused warnings */
+        (void) _data;
+        (void) count;
+        (void) typesize;
+        return;
+#   else
+        switch (typesize) {
+            case 1: return;
+            case 2:
+                util_swap16((uint16_t*)_data, count);
+                return;
+            case 4:
+                util_swap32((uint32_t*)_data, count);
+                return;
+            case 8:
+                util_swap64((uint32_t*)_data, count);
+                return;
+
+            default:
+                con_err ("util_endianswap: I don't know how to swap a %u byte structure!\n", typesize);
+                exit(EXIT_FAILURE); /* please blow the fuck up! */
+        }
+#   endif
+#endif
+}
+
+void util_swap_header(prog_header_t *code_header) {
+    util_endianswap(&code_header->version,              1, sizeof(code_header->version));
+    util_endianswap(&code_header->crc16,                1, sizeof(code_header->crc16));
+    util_endianswap(&code_header->statements.offset,    1, sizeof(code_header->statements.offset));
+    util_endianswap(&code_header->statements.length,    1, sizeof(code_header->statements.length));
+    util_endianswap(&code_header->defs.offset,          1, sizeof(code_header->defs.offset));
+    util_endianswap(&code_header->defs.length,          1, sizeof(code_header->defs.length));
+    util_endianswap(&code_header->fields.offset,        1, sizeof(code_header->fields.offset));
+    util_endianswap(&code_header->fields.length,        1, sizeof(code_header->fields.length));
+    util_endianswap(&code_header->functions.offset,     1, sizeof(code_header->functions.offset));
+    util_endianswap(&code_header->functions.length,     1, sizeof(code_header->functions.length));
+    util_endianswap(&code_header->strings.offset,       1, sizeof(code_header->strings.offset));
+    util_endianswap(&code_header->strings.length,       1, sizeof(code_header->strings.length));
+    util_endianswap(&code_header->globals.offset,       1, sizeof(code_header->globals.offset));
+    util_endianswap(&code_header->globals.length,       1, sizeof(code_header->globals.length));
+    util_endianswap(&code_header->entfield,             1, sizeof(code_header->entfield));
+}
+
+void util_swap_statements(prog_section_statement_t *statements) {
+    size_t i;
+
+    for (i = 0; i < vec_size(statements); ++i) {
+        util_endianswap(&statements[i].opcode,  1, sizeof(statements[i].opcode));
+        util_endianswap(&statements[i].o1,      1, sizeof(statements[i].o1));
+        util_endianswap(&statements[i].o2,      1, sizeof(statements[i].o2));
+        util_endianswap(&statements[i].o3,      1, sizeof(statements[i].o3));
+    }
+}
+
+void util_swap_defs_fields(prog_section_both_t *section) {
+    size_t i;
+
+    for (i = 0; i < vec_size(section); ++i) {
+        util_endianswap(&section[i].type,   1, sizeof(section[i].type));
+        util_endianswap(&section[i].offset, 1, sizeof(section[i].offset));
+        util_endianswap(&section[i].name,   1, sizeof(section[i].name));
+    }
+}
+
+void util_swap_functions(prog_section_function_t *functions) {
+    size_t i;
+
+    for (i = 0; i < vec_size(functions); ++i) {
+        util_endianswap(&functions[i].entry,        1, sizeof(functions[i].entry));
+        util_endianswap(&functions[i].firstlocal,   1, sizeof(functions[i].firstlocal));
+        util_endianswap(&functions[i].locals,       1, sizeof(functions[i].locals));
+        util_endianswap(&functions[i].profile,      1, sizeof(functions[i].profile));
+        util_endianswap(&functions[i].name,         1, sizeof(functions[i].name));
+        util_endianswap(&functions[i].file,         1, sizeof(functions[i].file));
+        util_endianswap(&functions[i].nargs,        1, sizeof(functions[i].nargs));
+        /* Don't swap argsize[] - it's just a byte array, which Quake uses only as such. */
+    }
+}
+
+void util_swap_globals(int32_t *globals) {
+    util_endianswap(globals, vec_size(globals), sizeof(int32_t));
+}
+
+/*
+* Based On:
+*   Slicing-by-8 algorithms by Michael E.
+*       Kounavis and Frank L. Berry from Intel Corp.
+*       http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf
+*
+*   This code was made to be slightly less confusing with macros, which
+*   I suppose is somewhat ironic.
+*
+*   The code had to be changed for non reflected on the output register
+*   since that's the method Quake uses.
+*
+*   The code also had to be changed for CRC16, which is slightly harder
+*   since the CRC32 method in the original Intel paper used a different
+*   bit order convention.
+*
+* Notes about the table:
+*   - It's exactly 4K in size
+*   - 64 elements fit in a cache line
+*   - can do 8 iterations unrolled 8 times for free
+*   - The first 256 elements of the table are standard CRC16 table
+*
+* Table can be generated with the following utility:
+*/
+#if 0
+#include <stdio.h>
+#include <stdint.h>
+int main(void) {
+    for (unsigned i = 0; i < 0x100; ++i) {
+        uint16_t x = i << 8;
+        for (int j = 0; j < 8; ++j)
+            x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0);
+        tab[0][i] = x;
+    }
+    for (unsigned i = 0; i < 0x100; ++i) {
+        uint16_t c = tab[0][i];
+        for (unsigned j = 1; j < 8; ++j) {
+            c = tab[0][c >> 8] ^ (c << 8);
+            tab[j][i] = c;
+        }
+    }
+    printf("static const uint16_t util_crc16_table[8][256] = {");
+    for (int i = 0; i < 8; ++i) {
+        printf("{\n");
+        for (int j = 0; j < 0x100; ++j) {
+            printf((j & 7) ? " " : "    ");
+            printf((j != 0x100-1) ? "0x%04X," : "0x%04X", tab[i][j]);
+            if ((j & 7) == 7)
+                printf("\n");
+        }
+        printf((i != 7) ? "}," : "}");
+    }
+    printf("};\n");
+    return 0;
+}
+#endif
+/*
+ * Non-Reflective version is present as well as a reference.
+ *
+ * TODO:
+ *  combine the crc16 into u32s and mask off low high for byte order
+ *  to make the arrays smaller.
+ */
+
+static const uint16_t util_crc16_table[8][256] = {{
+    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+},{
+    0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997,
+    0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E,
+    0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4,
+    0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D,
+    0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71,
+    0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8,
+    0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02,
+    0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB,
+    0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B,
+    0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2,
+    0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728,
+    0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81,
+    0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD,
+    0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14,
+    0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE,
+    0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867,
+    0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F,
+    0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6,
+    0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C,
+    0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5,
+    0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9,
+    0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40,
+    0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A,
+    0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33,
+    0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3,
+    0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A,
+    0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0,
+    0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519,
+    0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925,
+    0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C,
+    0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56,
+    0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF
+},{
+    0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590,
+    0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31,
+    0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3,
+    0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52,
+    0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356,
+    0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7,
+    0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035,
+    0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994,
+    0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D,
+    0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C,
+    0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E,
+    0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF,
+    0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB,
+    0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A,
+    0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98,
+    0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439,
+    0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA,
+    0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B,
+    0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9,
+    0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408,
+    0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C,
+    0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD,
+    0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F,
+    0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE,
+    0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367,
+    0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6,
+    0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004,
+    0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5,
+    0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1,
+    0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00,
+    0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2,
+    0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63
+},{
+    0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D,
+    0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE,
+    0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A,
+    0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49,
+    0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663,
+    0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0,
+    0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4,
+    0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807,
+    0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1,
+    0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72,
+    0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416,
+    0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5,
+    0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF,
+    0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C,
+    0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358,
+    0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B,
+    0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15,
+    0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6,
+    0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2,
+    0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271,
+    0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B,
+    0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98,
+    0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC,
+    0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F,
+    0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289,
+    0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A,
+    0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E,
+    0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED,
+    0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7,
+    0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004,
+    0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60,
+    0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3
+},{
+    0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4,
+    0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9,
+    0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E,
+    0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3,
+    0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60,
+    0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D,
+    0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A,
+    0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917,
+    0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC,
+    0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491,
+    0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6,
+    0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB,
+    0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08,
+    0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25,
+    0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52,
+    0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F,
+    0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504,
+    0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729,
+    0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E,
+    0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373,
+    0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0,
+    0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D,
+    0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA,
+    0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7,
+    0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C,
+    0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641,
+    0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036,
+    0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B,
+    0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8,
+    0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5,
+    0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882,
+    0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF
+},{
+    0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841,
+    0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503,
+    0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5,
+    0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87,
+    0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49,
+    0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B,
+    0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD,
+    0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F,
+    0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251,
+    0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13,
+    0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5,
+    0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597,
+    0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759,
+    0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B,
+    0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD,
+    0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F,
+    0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61,
+    0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123,
+    0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5,
+    0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7,
+    0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969,
+    0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B,
+    0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED,
+    0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF,
+    0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671,
+    0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33,
+    0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5,
+    0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7,
+    0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379,
+    0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B,
+    0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD,
+    0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF
+},{
+    0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944,
+    0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9,
+    0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F,
+    0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92,
+    0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2,
+    0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F,
+    0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9,
+    0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364,
+    0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8,
+    0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705,
+    0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3,
+    0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E,
+    0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E,
+    0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3,
+    0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25,
+    0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88,
+    0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD,
+    0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110,
+    0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6,
+    0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B,
+    0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B,
+    0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6,
+    0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30,
+    0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D,
+    0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51,
+    0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC,
+    0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A,
+    0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87,
+    0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7,
+    0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A,
+    0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC,
+    0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571
+},{
+    0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718,
+    0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2,
+    0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC,
+    0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476,
+    0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70,
+    0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA,
+    0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4,
+    0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E,
+    0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8,
+    0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12,
+    0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C,
+    0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6,
+    0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0,
+    0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A,
+    0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714,
+    0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE,
+    0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99,
+    0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443,
+    0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D,
+    0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7,
+    0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1,
+    0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B,
+    0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45,
+    0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F,
+    0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49,
+    0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293,
+    0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD,
+    0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27,
+    0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721,
+    0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB,
+    0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95,
+    0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F
+}};
+
+/* Non - Reflected */
+uint16_t util_crc16(uint16_t current, const char *GMQCC_RESTRICT k, size_t len) {
+    uint16_t h = current;
+
+    /* don't load twice */
+    const uint8_t *GMQCC_RESTRICT data = (const uint8_t *GMQCC_RESTRICT)k;
+    size_t n;
+
+    /* deal with the first bytes as bytes until we reach an 8 byte boundary */
+    while (len & 7) {
+        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
+        --len;
+    }
+
+    #define SELECT_BULK(X, MOD) util_crc16_table[(X)][data[7-(X)] ^ (MOD)]
+    #define SELECT_DATA(X)      util_crc16_table[(X)][data[7-(X)]]
+
+    for (n = len / 8; n; --n) {
+        h = SELECT_BULK(7, (h >> 8))   ^
+            SELECT_BULK(6, (h & 0xFF)) ^
+            SELECT_DATA(5) ^
+            SELECT_DATA(4) ^
+            SELECT_DATA(3) ^
+            SELECT_DATA(2) ^
+            SELECT_DATA(1) ^
+            SELECT_DATA(0);
+        data += 8;
+        len  -= 8;
+    }
+
+    #undef SELECT_BULK
+    #undef SELECT_DATA
+
+    /* deal with the rest with the byte method */
+    for (n = len & 7; n; --n)
+        h = (uint16_t)(h << 8) ^ (*util_crc16_table)[(h >> 8) ^ *data++];
+
+    return h;
+}
+
+/*
+ * modifier is the match to make and the transposition from it, while add is the upper-value that determines the
+ * transposition from uppercase to lower case.
+ */
+static size_t util_strtransform(const char *in, char *out, size_t outsz, const char *mod, int add) {
+    size_t sz = 1;
+    for (; *in && sz < outsz; ++in, ++out, ++sz) {
+        *out = (*in == mod[0])
+                    ? mod[1]
+                    : (util_isalpha(*in) && ((add > 0) ? util_isupper(*in) : !util_isupper(*in)))
+                        ? *in + add
+                        : *in;
+    }
+    *out = 0;
+    return sz-1;
+}
+
+size_t util_strtocmd(const char *in, char *out, size_t outsz) {
+    return util_strtransform(in, out, outsz, "-_", 'A'-'a');
+}
+size_t util_strtononcmd(const char *in, char *out, size_t outsz) {
+    return util_strtransform(in, out, outsz, "_-", 'a'-'A');
+}
+size_t util_optimizationtostr(const char *in, char *out, size_t outsz) {
+    return util_strtransform(in, out, outsz, "_ ", 'a'-'A');
+}
+
+static int util_vasprintf(char **dat, const char *fmt, va_list args) {
+    int     ret;
+    int     len;
+    char   *tmp = NULL;
+    char    buf[128];
+    va_list cpy;
+
+    va_copy(cpy, args);
+    len = vsnprintf(buf, sizeof(buf), fmt, cpy);
+    va_end (cpy);
+
+    if (len < 0)
+        return len;
+
+    if (len < (int)sizeof(buf)) {
+        *dat = util_strdup(buf);
+        return len;
+    }
+
+    tmp = (char*)mem_a(len + 1);
+    if ((ret = vsnprintf(tmp, len + 1, fmt, args)) != len) {
+        mem_d(tmp);
+        *dat = NULL;
+        return -1;
+    }
+
+    *dat = tmp;
+    return len;
+}
+
+int util_snprintf(char *str, size_t size, const char *fmt, ...) {
+    va_list  arg;
+    int ret;
+    va_start(arg, fmt);
+    ret = vsnprintf(str, size, fmt, arg);
+    va_end(arg);
+    return ret;
+}
+
+int util_asprintf(char **ret, const char *fmt, ...) {
+    va_list  args;
+    int read;
+    va_start(args, fmt);
+    read = util_vasprintf(ret, fmt, args);
+    va_end(args);
+    return read;
+}
+
+int util_sscanf(const char *str, const char *format, ...) {
+    va_list  args;
+    int read;
+    va_start(args, format);
+    read = vsscanf(str, format, args);
+    va_end(args);
+    return read;
+}
+
+char *util_strncpy(char *dest, const char *src, size_t n) {
+    return strncpy(dest, src, n);
+}
+
+char *util_strncat(char *dest, const char *src, size_t n) {
+    return strncat(dest, src, n);
+}
+
+char *util_strcat(char *dest, const char *src) {
+    return strcat(dest, src);
+}
+
+const char *util_strerror(int err) {
+    return strerror(err);
+}
+
+const struct tm *util_localtime(const time_t *timer) {
+    return localtime(timer);
+}
+
+const char *util_ctime(const time_t *timer) {
+    return ctime(timer);
+}
+
+int util_getline(char **lineptr, size_t *n, FILE *stream) {
+    int   chr;
+    int   ret;
+    char *pos;
+
+    if (!lineptr || !n || !stream)
+        return -1;
+    if (!*lineptr) {
+        if (!(*lineptr = (char*)mem_a((*n=64))))
+            return -1;
+    }
+
+    chr = *n;
+    pos = *lineptr;
+
+    for (;;) {
+        int c = getc(stream);
+
+        if (chr < 2) {
+            *n += (*n > 16) ? *n : 64;
+            chr = *n + *lineptr - pos;
+            if (!(*lineptr = (char*)mem_r(*lineptr,*n)))
+                return -1;
+            pos = *n - chr + *lineptr;
+        }
+
+        if (ferror(stream))
+            return -1;
+        if (c == EOF) {
+            if (pos == *lineptr)
+                return -1;
+            else
+                break;
+        }
+
+        *pos++ = c;
+        chr--;
+        if (c == '\n')
+            break;
+    }
+    *pos = '\0';
+    return (ret = pos - *lineptr);
+}
+
+#ifndef _WIN32
+#include <unistd.h>
+bool util_isatty(FILE *file) {
+    if (file == stdout) return !!isatty(STDOUT_FILENO);
+    if (file == stderr) return !!isatty(STDERR_FILENO);
+    return false;
+}
+#else
+bool util_isatty(FILE *file) {
+    return false;
+}
+#endif
+
+/*
+ * A small noncryptographic PRNG based on:
+ * http://burtleburtle.net/bob/rand/smallprng.html
+ */
+static uint32_t util_rand_state[4] = {
+    0xF1EA5EED, 0x00000000,
+    0x00000000, 0x00000000
+};
+
+#define util_rand_rot(X, Y) (((X)<<(Y))|((X)>>(32-(Y))))
+
+uint32_t util_rand() {
+    uint32_t last;
+
+    last               = util_rand_state[0] - util_rand_rot(util_rand_state[1], 27);
+    util_rand_state[0] = util_rand_state[1] ^ util_rand_rot(util_rand_state[2], 17);
+    util_rand_state[1] = util_rand_state[2] + util_rand_state[3];
+    util_rand_state[2] = util_rand_state[3] + last;
+    util_rand_state[3] = util_rand_state[0] + last;
+
+    return util_rand_state[3];
+}
+
+#undef util_rand_rot
+
+void util_seed(uint32_t value) {
+    size_t i;
+
+    util_rand_state[0] = 0xF1EA5EED;
+    util_rand_state[1] = value;
+    util_rand_state[2] = value;
+    util_rand_state[3] = value;
+
+    for (i = 0; i < 20; ++i)
+        (void)util_rand();
+}
+
+size_t hash(const char *string) {
+    size_t hash = 0;
+    for(; *string; ++string) {
+        hash += *string;
+        hash += (hash << 10);
+        hash ^= (hash >> 6);
+    }
+    hash += hash << 3;
+    hash ^= hash >> 11;
+    hash += hash << 15;
+    return hash;
+}
+