/**
 * MojoShader; generate shader programs from bytecode of compiled
 *  Direct3D shaders.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Ryan C. Gordon.
 */

// !!! FIXME: this needs to be split into separate source files:
// !!! FIXME:  parse, AST, IR, etc. The problem is we need to deal with the
// !!! FIXME:  "Context" struct being passed around everywhere.

#define __MOJOSHADER_INTERNAL__ 1
#include "mojoshader_internal.h"

#if DEBUG_COMPILER_PARSER
#define LEMON_SUPPORT_TRACING 1
#endif

// !!! FIXME: I'd like to lose this. It's really inefficient. Just keep a
// !!! FIXME:  (tail) on these list structures instead?
#define REVERSE_LINKED_LIST(typ, head) { \
    if ((head) && (head->next)) { \
        typ *tmp = NULL; \
        typ *tmp1 = NULL; \
        while (head != NULL) { \
            tmp = head; \
            head = head->next; \
            tmp->next = tmp1; \
            tmp1 = tmp; \
        } \
        head = tmp; \
    } \
}

static inline int operator_is_unary(const MOJOSHADER_astNodeType op)
{
    return ( (op > MOJOSHADER_AST_OP_START_RANGE_UNARY) &&
             (op < MOJOSHADER_AST_OP_END_RANGE_UNARY) );
} // operator_is_unary

static inline int operator_is_binary(const MOJOSHADER_astNodeType op)
{
    return ( (op > MOJOSHADER_AST_OP_START_RANGE_BINARY) &&
             (op < MOJOSHADER_AST_OP_END_RANGE_BINARY) );
} // operator_is_binary

static inline int operator_is_ternary(const MOJOSHADER_astNodeType op)
{
    return ( (op > MOJOSHADER_AST_OP_START_RANGE_TERNARY) &&
             (op < MOJOSHADER_AST_OP_END_RANGE_TERNARY) );
} // operator_is_ternary


typedef union TokenData
{
    int64 i64;
    double dbl;
    const char *string;
    const MOJOSHADER_astDataType *datatype;
} TokenData;


// This tracks data types and variables, and notes when they enter/leave scope.

typedef struct SymbolScope
{
    const char *symbol;
    const MOJOSHADER_astDataType *datatype;
    int index;  // unique positive value within a function, negative if global.
    int referenced;  // non-zero if something looked for this symbol (so we know it's used).
    struct SymbolScope *next;
} SymbolScope;

typedef struct SymbolMap
{
    HashTable *hash;
    SymbolScope *scope;
} SymbolMap;

typedef struct LoopLabels
{
    int start;  // loop's start label during IR build.
    int end;    // loop's end label during IR build.
    struct LoopLabels *prev;
} LoopLabels;

// Compile state, passed around all over the place.

typedef struct Context
{
    int isfail;
    int out_of_memory;
    MOJOSHADER_malloc malloc;
    MOJOSHADER_free free;
    void *malloc_data;
    ErrorList *errors;
    ErrorList *warnings;
    StringCache *strcache;
    const char *sourcefile;  // current source file that we're parsing.
    unsigned int sourceline; // current line in sourcefile that we're parsing.
    SymbolMap usertypes;
    SymbolMap variables;
    MOJOSHADER_astNode *ast;  // Abstract Syntax Tree
    const char *source_profile;
    int is_func_scope; // non-zero if semantic analysis is in function scope.
    int loop_count;
    int switch_count;
    int var_index;  // next variable index for current function.
    int global_var_index;  // next variable index for global scope.
    int user_func_index;  // next function index for user-defined functions.
    int intrinsic_func_index;  // next function index for intrinsic functions.

    MOJOSHADER_irStatement **ir;  // intermediate representation.
    int ir_label_count;  // next unused IR label index.
    int ir_temp_count;  // next unused IR temporary value index.
    int ir_end; // current function's end label during IR build.
    int ir_ret; // temp that holds current function's retval during IR build.
    LoopLabels *ir_loop;  // nested loop boundary labels during IR build.

    // Cache intrinsic types for fast lookup and consistent pointer values.
    MOJOSHADER_astDataType dt_none;
    MOJOSHADER_astDataType dt_bool;
    MOJOSHADER_astDataType dt_int;
    MOJOSHADER_astDataType dt_uint;
    MOJOSHADER_astDataType dt_float;
    MOJOSHADER_astDataType dt_float_snorm;
    MOJOSHADER_astDataType dt_float_unorm;
    MOJOSHADER_astDataType dt_half;
    MOJOSHADER_astDataType dt_double;
    MOJOSHADER_astDataType dt_string;
    MOJOSHADER_astDataType dt_sampler1d;
    MOJOSHADER_astDataType dt_sampler2d;
    MOJOSHADER_astDataType dt_sampler3d;
    MOJOSHADER_astDataType dt_samplercube;
    MOJOSHADER_astDataType dt_samplerstate;
    MOJOSHADER_astDataType dt_samplercompstate;
    MOJOSHADER_astDataType dt_buf_bool;
    MOJOSHADER_astDataType dt_buf_int;
    MOJOSHADER_astDataType dt_buf_uint;
    MOJOSHADER_astDataType dt_buf_half;
    MOJOSHADER_astDataType dt_buf_float;
    MOJOSHADER_astDataType dt_buf_double;
    MOJOSHADER_astDataType dt_buf_float_snorm;
    MOJOSHADER_astDataType dt_buf_float_unorm;

    Buffer *garbage;  // this is sort of hacky.
} Context;


// !!! FIXME: cut and paste between every damned source file follows...
// !!! FIXME: We need to make some sort of ContextBase that applies to all
// !!! FIXME:  files and move this stuff to mojoshader_common.c ...

// Convenience functions for allocators...

static inline void out_of_memory(Context *ctx)
{
    ctx->isfail = ctx->out_of_memory = 1;
} // out_of_memory

static inline void *Malloc(Context *ctx, const size_t len)
{
    void *retval = ctx->malloc((int) len, ctx->malloc_data);
    if (retval == NULL)
        out_of_memory(ctx);
    return retval;
} // Malloc

static inline char *StrDup(Context *ctx, const char *str)
{
    char *retval = (char *) Malloc(ctx, strlen(str) + 1);
    if (retval != NULL)
        strcpy(retval, str);
    return retval;
} // StrDup

static inline void Free(Context *ctx, void *ptr)
{
    ctx->free(ptr, ctx->malloc_data);
} // Free

static void *MallocBridge(int bytes, void *data)
{
    return Malloc((Context *) data, (size_t) bytes);
} // MallocBridge

static void FreeBridge(void *ptr, void *data)
{
    Free((Context *) data, ptr);
} // FreeBridge

static void failf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3);
static void failf(Context *ctx, const char *fmt, ...)
{
    ctx->isfail = 1;
    if (ctx->out_of_memory)
        return;

    va_list ap;
    va_start(ap, fmt);
    errorlist_add_va(ctx->errors, ctx->sourcefile, ctx->sourceline, fmt, ap);
    va_end(ap);
} // failf

static inline void fail(Context *ctx, const char *reason)
{
    failf(ctx, "%s", reason);
} // fail

static void warnf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3);
static void warnf(Context *ctx, const char *fmt, ...)
{
    if (ctx->out_of_memory)
        return;

    va_list ap;
    va_start(ap, fmt);
    errorlist_add_va(ctx->warnings, ctx->sourcefile, ctx->sourceline, fmt, ap);
    va_end(ap);
} // warnf

static inline void warn(Context *ctx, const char *reason)
{
    warnf(ctx, "%s", reason);
} // warn

static inline int isfail(const Context *ctx)
{
    return ctx->isfail;
} // isfail


static void symbolmap_nuke(const void *k, const void *v, void *d) {/*no-op*/}

static int create_symbolmap(Context *ctx, SymbolMap *map)
{
    // !!! FIXME: should compare string pointer, with string in cache.
    map->scope = NULL;
    map->hash = hash_create(ctx, hash_hash_string, hash_keymatch_string,
                            symbolmap_nuke, 1, MallocBridge, FreeBridge, ctx);
    return (map->hash != NULL);
} // create_symbolmap

static int datatypes_match(const MOJOSHADER_astDataType *a,
                           const MOJOSHADER_astDataType *b)
{
    int i;

    if (a == b)
        return 1;
    else if (a->type != b->type)
        return 0;

    switch (a->type)
    {
        case MOJOSHADER_AST_DATATYPE_STRUCT:
            if (a->structure.member_count != b->structure.member_count)
                return 0;
            for (i = 0; i < a->structure.member_count; i++)
            {
                if (!datatypes_match(a->structure.members[i].datatype,
                                     b->structure.members[i].datatype))
                    return 0;
                // stringcache'd, pointer compare is safe.
                else if (a->structure.members[i].identifier !=
                         b->structure.members[i].identifier)
                    return 0;
            } // for
            return 1;

        case MOJOSHADER_AST_DATATYPE_ARRAY:
            if (a->array.elements != b->array.elements)
                return 0;
            else if (!datatypes_match(a->array.base, b->array.base))
                return 0;
            return 1;

        case MOJOSHADER_AST_DATATYPE_VECTOR:
            if (a->vector.elements != b->vector.elements)
                return 0;
            else if (!datatypes_match(a->vector.base, b->vector.base))
                return 0;
            return 1;

        case MOJOSHADER_AST_DATATYPE_MATRIX:
            if (a->matrix.rows != b->matrix.rows)
                return 0;
            else if (a->matrix.columns != b->matrix.columns)
                return 0;
            else if (!datatypes_match(a->matrix.base, b->matrix.base))
                return 0;
            return 1;

        case MOJOSHADER_AST_DATATYPE_BUFFER:
            return datatypes_match(a->buffer.base, b->buffer.base);

        case MOJOSHADER_AST_DATATYPE_FUNCTION:
            if (a->function.num_params != b->function.num_params)
                return 0;
            else if (a->function.intrinsic != b->function.intrinsic)
                return 0;
            else if (!datatypes_match(a->function.retval, b->function.retval))
                return 0;
            for (i = 0; i < a->function.num_params; i++)
            {
                if (!datatypes_match(a->function.params[i], b->function.params[i]))
                    return 0;
            } // for
            return 1;

        case MOJOSHADER_AST_DATATYPE_USER:
            return 0;  // pointers must match, this clearly didn't.

        default:
            assert(0 && "unexpected case");
            return 0;
    } // switch

    return 0;
} // datatypes_match

static void push_symbol(Context *ctx, SymbolMap *map, const char *sym,
                        const MOJOSHADER_astDataType *dt, const int index,
                        const int check_dupes)
{
    if (ctx->out_of_memory)
        return;

    // Decide if this symbol is defined, and if it's in the current scope.
    SymbolScope *item = NULL;
    const void *value = NULL;
    if ((check_dupes) && (sym != NULL) && (hash_find(map->hash, sym, &value)))
    {
        // check the current scope for a dupe.
        // !!! FIXME: note current scope's starting index, see if found
        // !!! FIXME:  item is < index (and thus, a previous scope).
        item = map->scope;
        while ((item) && (item->symbol))
        {
            if ( ((const void *) item) == value )
            {
                failf(ctx, "Symbol '%s' already defined", sym);
                return;
            } // if
            item = item->next;
        } // while
    } // if

    // Add the symbol to our map and scope stack.
    item = (SymbolScope *) Malloc(ctx, sizeof (SymbolScope));
    if (item == NULL)
        return;

    if (sym != NULL)  // sym can be NULL if we're pushing a new scope.
    {
        if (hash_insert(map->hash, sym, item) == -1)
        {
            Free(ctx, item);
            return;
        } // if
    } // if

    item->symbol = sym;  // cached strings, don't copy.
    item->index = index;
    item->datatype = dt;
    item->referenced = 0;
    item->next = map->scope;
    map->scope = item;
} // push_symbol

static void push_usertype(Context *ctx, const char *sym, const MOJOSHADER_astDataType *dt)
{
    if (sym != NULL)
    {
        MOJOSHADER_astDataType *userdt;
        userdt = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*userdt));
        if (userdt != NULL)
        {
            // !!! FIXME: this is hacky.
            if (!buffer_append(ctx->garbage, &userdt, sizeof (userdt)))
            {
                Free(ctx, userdt);
                return;
            } // if

            userdt->type = MOJOSHADER_AST_DATATYPE_USER;
            userdt->user.details = dt;
            userdt->user.name = sym;

            dt = userdt;
        } // if
    } // if

    push_symbol(ctx, &ctx->usertypes, sym, dt, 0, 1);
} // push_usertype

static inline void push_variable(Context *ctx, const char *sym, const MOJOSHADER_astDataType *dt)
{
    int idx = 0;
    if (sym != NULL)
    {
        // leave space for individual member indexes. The IR will need this.
        int additional = 0;
        if (dt->type == MOJOSHADER_AST_DATATYPE_STRUCT)
            additional = dt->structure.member_count;
        if (ctx->is_func_scope)
        {
            idx = ++ctx->var_index;  // these are positive.
            ctx->var_index += additional;
        } // if
        else
        {
            idx = --ctx->global_var_index;  // these are negative.
            ctx->global_var_index -= additional;
        } // else
    } // if

    push_symbol(ctx, &ctx->variables, sym, dt, idx, 1);
} // push_variable

static int push_function(Context *ctx, const char *sym,
                          const MOJOSHADER_astDataType *dt,
                          const int just_declare)
{
    // we don't have any reason to support nested functions at the moment,
    //  so this would be a bug.
    assert(!ctx->is_func_scope);
    assert(dt->type == MOJOSHADER_AST_DATATYPE_FUNCTION);

    // Functions are always global, so no need to search scopes.
    //  Functions overload, though, so we have to continue iterating to
    //  see if it matches anything.
    const void *value = NULL;
    void *iter = NULL;
    while (hash_iter(ctx->variables.hash, sym, &value, &iter))
    {
        // !!! FIXME: this breaks if you predeclare a function.
        // !!! FIXME:  (a declare AFTER defining works, though.)
        // there's already something called this.
        SymbolScope *item = (SymbolScope *) value;
        if (datatypes_match(dt, item->datatype))
        {
            if (!just_declare)
                failf(ctx, "Function '%s' already defined.", sym);
            return item->index;
        } // if
    } // while

    int idx = 0;
    if ((sym != NULL) && (dt != NULL))
    {
        if (!dt->function.intrinsic)
            idx = ++ctx->user_func_index;  // these are positive.
        else
            idx = --ctx->intrinsic_func_index;  // these are negative.
    } // if

    // push_symbol() doesn't check dupes, because we just did.
    push_symbol(ctx, &ctx->variables, sym, dt, idx, 0);

    return idx;
} // push_function

static inline void push_scope(Context *ctx)
{
    push_usertype(ctx, NULL, NULL);
    push_variable(ctx, NULL, NULL);
} // push_scope

static void pop_symbol(Context *ctx, SymbolMap *map)
{
    SymbolScope *item = map->scope;
    if (!item)
        return;
    if (item->symbol)
        hash_remove(map->hash, item->symbol);
    map->scope = item->next;
    Free(ctx, item);
} // pop_symbol

static void pop_symbol_scope(Context *ctx, SymbolMap *map)
{
    while ((map->scope) && (map->scope->symbol))
        pop_symbol(ctx, map);

    assert(map->scope != NULL);
    assert(map->scope->symbol == NULL);
    pop_symbol(ctx, map);
} // pop_symbol_scope

static inline void pop_scope(Context *ctx)
{
    pop_symbol_scope(ctx, &ctx->usertypes);
    pop_symbol_scope(ctx, &ctx->variables);
} // push_scope

static const MOJOSHADER_astDataType *find_symbol(Context *ctx, SymbolMap *map, const char *sym, int *_index)
{
    const void *_item = NULL;
    hash_find(map->hash, sym, &_item);
    SymbolScope *item = (SymbolScope *) _item;
    if (item != NULL)
    {
        item->referenced++;
        if (_index != NULL)
            *_index = item->index;
    } // if
    return item ? item->datatype : NULL;
} // find_symbol

static inline const MOJOSHADER_astDataType *find_usertype(Context *ctx, const char *sym)
{
    return find_symbol(ctx, &ctx->usertypes, sym, NULL);
} // find_usertype

static inline const MOJOSHADER_astDataType *find_variable(Context *ctx, const char *sym, int *_index)
{
    return find_symbol(ctx, &ctx->variables, sym, _index);
} // find_variable

static void destroy_symbolmap(Context *ctx, SymbolMap *map)
{
    while (map->scope)
        pop_symbol(ctx, map);
    hash_destroy(map->hash);
} // destroy_symbolmap


static const MOJOSHADER_astDataType *new_datatype_vector(Context *ctx,
                                            const MOJOSHADER_astDataType *dt,
                                            const int columns)
{
    MOJOSHADER_astDataType *retval;
    retval = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*retval));
    if (retval == NULL)
        return NULL;

    // !!! FIXME: this is hacky.
    // !!! FIXME:  I'd like to cache these anyhow and reuse types.
    if (!buffer_append(ctx->garbage, &retval, sizeof (retval)))
    {
        Free(ctx, retval);
        return NULL;
    } // if

    if ((columns < 1) || (columns > 4))
        fail(ctx, "Vector must have between 1 and 4 elements");

    retval->type = MOJOSHADER_AST_DATATYPE_VECTOR;
    retval->vector.base = dt;
    retval->vector.elements = columns;
    return retval;
} // new_datatype_vector

static const MOJOSHADER_astDataType *new_datatype_matrix(Context *ctx,
                                            const MOJOSHADER_astDataType *dt,
                                            const int rows, const int columns)
{
    MOJOSHADER_astDataType *retval;
    // !!! FIXME: allocate enough for a matrix, but we need to cleanup things that copy without checking for subsize.
    retval = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*retval));
    if (retval == NULL)
        return NULL;

    // !!! FIXME: this is hacky.
    // !!! FIXME:  I'd like to cache these anyhow and reuse types.
    if (!buffer_append(ctx->garbage, &retval, sizeof (retval)))
    {
        Free(ctx, retval);
        return NULL;
    } // if

    if ((rows < 1) || (rows > 4))
        fail(ctx, "Matrix must have between 1 and 4 rows");
    if ((columns < 1) || (columns > 4))
        fail(ctx, "Matrix must have between 1 and 4 columns");

    retval->type = MOJOSHADER_AST_DATATYPE_MATRIX;
    retval->matrix.base = dt;
    retval->matrix.rows = rows;
    retval->matrix.columns = columns;
    return retval;
} // new_datatype_matrix


// !!! FIXME: move this to mojoshader_ast.c
// !!! FIXME: new_* and delete_* should take an allocator, not a context.

// These functions are mostly for construction and cleanup of nodes in the
//  parse tree. Mostly this is simple allocation and initialization, so we
//  can do as little in the lemon code as possible, and then sort it all out
//  afterwards.

#define NEW_AST_NODE(retval, cls, typ) \
    cls *retval = (cls *) Malloc(ctx, sizeof (cls)); \
    do { \
        if (retval == NULL) { return NULL; } \
        retval->ast.type = typ; \
        retval->ast.filename = ctx->sourcefile; \
        retval->ast.line = ctx->sourceline; \
    } while (0)

#define DELETE_AST_NODE(cls) do { \
    if (!cls) return; \
} while (0)


static void delete_compilation_unit(Context*, MOJOSHADER_astCompilationUnit*);
static void delete_statement(Context *ctx, MOJOSHADER_astStatement *stmt);

static MOJOSHADER_astExpression *new_identifier_expr(Context *ctx,
                                                     const char *string)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionIdentifier,
                 MOJOSHADER_AST_OP_IDENTIFIER);
    retval->datatype = NULL;
    retval->identifier = string;  // cached; don't copy string.
    retval->index = 0;
    return (MOJOSHADER_astExpression *) retval;
} // new_identifier_expr

static MOJOSHADER_astExpression *new_callfunc_expr(Context *ctx,
                                        const char *identifier,
                                        MOJOSHADER_astArguments *args)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionCallFunction,
                 MOJOSHADER_AST_OP_CALLFUNC);
    MOJOSHADER_astExpression *expr = new_identifier_expr(ctx, identifier);
    retval->datatype = NULL;
    retval->identifier = (MOJOSHADER_astExpressionIdentifier *) expr;
    retval->args = args;
    return (MOJOSHADER_astExpression *) retval;
} // new_callfunc_expr

static MOJOSHADER_astExpression *new_constructor_expr(Context *ctx,
                                            const MOJOSHADER_astDataType *dt,
                                            MOJOSHADER_astArguments *args)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionConstructor,
                 MOJOSHADER_AST_OP_CONSTRUCTOR);
    retval->datatype = dt;
    retval->args = args;
    return (MOJOSHADER_astExpression *) retval;
} // new_constructor_expr

static MOJOSHADER_astExpression *new_cast_expr(Context *ctx,
                                            const MOJOSHADER_astDataType *dt,
                                            MOJOSHADER_astExpression *operand)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionCast, MOJOSHADER_AST_OP_CAST);
    retval->datatype = dt;
    retval->operand = operand;
    return (MOJOSHADER_astExpression *) retval;
} // new_cast_expr

static MOJOSHADER_astExpression *new_unary_expr(Context *ctx,
                                            const MOJOSHADER_astNodeType op,
                                            MOJOSHADER_astExpression *operand)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionUnary, op);
    assert(operator_is_unary(op));
    retval->datatype = NULL;
    retval->operand = operand;
    return (MOJOSHADER_astExpression *) retval;
} // new_unary_expr

static MOJOSHADER_astExpression *new_binary_expr(Context *ctx,
                                            const MOJOSHADER_astNodeType op,
                                            MOJOSHADER_astExpression *left,
                                            MOJOSHADER_astExpression *right)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionBinary, op);
    assert(operator_is_binary(op));
    retval->datatype = NULL;
    retval->left = left;
    retval->right = right;
    return (MOJOSHADER_astExpression *) retval;
} // new_binary_expr

static MOJOSHADER_astExpression *new_ternary_expr(Context *ctx,
                                            const MOJOSHADER_astNodeType op,
                                            MOJOSHADER_astExpression *left,
                                            MOJOSHADER_astExpression *center,
                                            MOJOSHADER_astExpression *right)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionTernary, op);
    assert(operator_is_ternary(op));
    assert(op == MOJOSHADER_AST_OP_CONDITIONAL);
    retval->datatype = &ctx->dt_bool;
    retval->left = left;
    retval->center = center;
    retval->right = right;
    return (MOJOSHADER_astExpression *) retval;
} // new_ternary_expr

static MOJOSHADER_astExpression *new_deref_struct_expr(Context *ctx,
                                        MOJOSHADER_astExpression *identifier,
                                        const char *member)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionDerefStruct,
                 MOJOSHADER_AST_OP_DEREF_STRUCT);
    retval->datatype = NULL;
    retval->identifier = identifier;
    retval->member = member;  // cached; don't copy string.
    retval->isswizzle = 0;  // may change during semantic analysis.
    retval->member_index = 0;  // set during semantic analysis.
    return (MOJOSHADER_astExpression *) retval;
} // new_deref_struct_expr

static MOJOSHADER_astExpression *new_literal_int_expr(Context *ctx,
                                                       const int value)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionIntLiteral,
                 MOJOSHADER_AST_OP_INT_LITERAL);
    retval->datatype = &ctx->dt_int;
    retval->value = value;
    return (MOJOSHADER_astExpression *) retval;
} // new_literal_int_expr

static MOJOSHADER_astExpression *new_literal_float_expr(Context *ctx,
                                                        const double dbl)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionFloatLiteral,
                 MOJOSHADER_AST_OP_FLOAT_LITERAL);
    retval->datatype = &ctx->dt_float;
    retval->value = dbl;
    return (MOJOSHADER_astExpression *) retval;
} // new_literal_float_expr

static MOJOSHADER_astExpression *new_literal_string_expr(Context *ctx,
                                                         const char *string)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionStringLiteral,
                 MOJOSHADER_AST_OP_STRING_LITERAL);
    retval->datatype = &ctx->dt_string;
    retval->string = string;  // cached; don't copy string.
    return (MOJOSHADER_astExpression *) retval;
} // new_literal_string_expr

static MOJOSHADER_astExpression *new_literal_boolean_expr(Context *ctx,
                                                          const int value)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionBooleanLiteral,
                 MOJOSHADER_AST_OP_BOOLEAN_LITERAL);
    retval->datatype = &ctx->dt_bool;
    retval->value = value;
    return (MOJOSHADER_astExpression *) retval;
} // new_literal_boolean_expr

static void delete_arguments(Context *ctx, MOJOSHADER_astArguments *args);

static void delete_expr(Context *ctx, MOJOSHADER_astExpression *_expr)
{
    MOJOSHADER_astNode *expr = (MOJOSHADER_astNode *) _expr;

    DELETE_AST_NODE(expr);

    if (expr->ast.type == MOJOSHADER_AST_OP_CAST)
        delete_expr(ctx, expr->cast.operand);

    else if (expr->ast.type == MOJOSHADER_AST_OP_CONSTRUCTOR)
        delete_arguments(ctx, expr->constructor.args);

    else if (expr->ast.type == MOJOSHADER_AST_OP_DEREF_STRUCT)
        delete_expr(ctx, expr->derefstruct.identifier);

    else if (operator_is_unary(expr->ast.type))
        delete_expr(ctx, expr->unary.operand);

    else if (operator_is_binary(expr->ast.type))
    {
        delete_expr(ctx, expr->binary.left);
        delete_expr(ctx, expr->binary.right);
    } // else if

    else if (operator_is_ternary(expr->ast.type))
    {
        delete_expr(ctx, expr->ternary.left);
        delete_expr(ctx, expr->ternary.center);
        delete_expr(ctx, expr->ternary.right);
    } // else if

    else if (expr->ast.type == MOJOSHADER_AST_OP_CALLFUNC)
    {
        delete_expr(ctx, (MOJOSHADER_astExpression*)expr->callfunc.identifier);
        delete_arguments(ctx, expr->callfunc.args);
    } // else if

    // rest of operators don't have extra data to free.

    Free(ctx, expr);
} // delete_expr

static MOJOSHADER_astArguments *new_argument(Context *ctx,
                                             MOJOSHADER_astExpression *arg)
{
    NEW_AST_NODE(retval, MOJOSHADER_astArguments, MOJOSHADER_AST_ARGUMENTS);
    retval->argument = arg;
    retval->next = NULL;
    return retval;
} // new_argument

static void delete_arguments(Context *ctx, MOJOSHADER_astArguments *args)
{
    DELETE_AST_NODE(args);
    delete_arguments(ctx, args->next);
    delete_expr(ctx, args->argument);
    Free(ctx, args);
} // delete_arguments

static MOJOSHADER_astFunctionParameters *new_function_param(Context *ctx,
                        const MOJOSHADER_astInputModifier inputmod,
                        const MOJOSHADER_astDataType *dt,
                        const char *identifier, const char *semantic,
                        const MOJOSHADER_astInterpolationModifier interpmod,
                        MOJOSHADER_astExpression *initializer)
{
    NEW_AST_NODE(retval, MOJOSHADER_astFunctionParameters,
                 MOJOSHADER_AST_FUNCTION_PARAMS);
    retval->datatype = dt;
    retval->input_modifier = inputmod;
    retval->identifier = identifier;
    retval->semantic = semantic;
    retval->interpolation_modifier = interpmod;
    retval->initializer = initializer;
    retval->next = NULL;
    return retval;
} // new_function_param

static void delete_function_params(Context *ctx,
                                   MOJOSHADER_astFunctionParameters *params)
{
    DELETE_AST_NODE(params);
    delete_function_params(ctx, params->next);
    delete_expr(ctx, params->initializer);
    Free(ctx, params);
} // delete_function_params

static MOJOSHADER_astFunctionSignature *new_function_signature(Context *ctx,
                                    const MOJOSHADER_astDataType *dt,
                                    const char *identifier,
                                    MOJOSHADER_astFunctionParameters *params)
{
    NEW_AST_NODE(retval, MOJOSHADER_astFunctionSignature,
                 MOJOSHADER_AST_FUNCTION_SIGNATURE);
    retval->datatype = dt;
    retval->identifier = identifier;
    retval->params = params;
    retval->storage_class = MOJOSHADER_AST_FNSTORECLS_NONE;
    retval->semantic = NULL;
    return retval;
} // new_function_signature

static void delete_function_signature(Context *ctx,
                                      MOJOSHADER_astFunctionSignature *sig)
{
    DELETE_AST_NODE(sig);
    delete_function_params(ctx, sig->params);
    Free(ctx, sig);
} // delete_function_signature

static MOJOSHADER_astCompilationUnit *new_function(Context *ctx,
                                MOJOSHADER_astFunctionSignature *declaration,
                                MOJOSHADER_astStatement *definition)
{
    NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitFunction,
                 MOJOSHADER_AST_COMPUNIT_FUNCTION);
    retval->next = NULL;
    retval->declaration = declaration;
    retval->definition = definition;
    retval->index = 0;
    return (MOJOSHADER_astCompilationUnit *) retval;
} // new_function

static void delete_function(Context *ctx,
                            MOJOSHADER_astCompilationUnitFunction *unitfn)
{
    DELETE_AST_NODE(unitfn);
    delete_compilation_unit(ctx, unitfn->next);
    delete_function_signature(ctx, unitfn->declaration);
    delete_statement(ctx, unitfn->definition);
    Free(ctx, unitfn);
} // delete_function

static MOJOSHADER_astScalarOrArray *new_scalar_or_array(Context *ctx,
                                          const char *ident, const int isvec,
                                          MOJOSHADER_astExpression *dim)
{
    NEW_AST_NODE(retval, MOJOSHADER_astScalarOrArray,
                 MOJOSHADER_AST_SCALAR_OR_ARRAY);
    retval->identifier = ident;
    retval->isarray = isvec;
    retval->dimension = dim;
    return retval;
} // new_scalar_or_array

static void delete_scalar_or_array(Context *ctx,MOJOSHADER_astScalarOrArray *s)
{
    DELETE_AST_NODE(s);
    delete_expr(ctx, s->dimension);
    Free(ctx, s);
} // delete_scalar_or_array

static MOJOSHADER_astTypedef *new_typedef(Context *ctx, const int isconst,
                                          const MOJOSHADER_astDataType *dt,
                                          MOJOSHADER_astScalarOrArray *soa)
{
    // we correct this datatype to the final version during semantic analysis.
    NEW_AST_NODE(retval, MOJOSHADER_astTypedef, MOJOSHADER_AST_TYPEDEF);
    retval->datatype = dt;
    retval->isconst = isconst;
    retval->details = soa;
    return retval;
} // new_typedef

static void delete_typedef(Context *ctx, MOJOSHADER_astTypedef *td)
{
    DELETE_AST_NODE(td);
    delete_scalar_or_array(ctx, td->details);
    Free(ctx, td);
} // delete_typedef

static MOJOSHADER_astPackOffset *new_pack_offset(Context *ctx,
                                                 const char *a, const char *b)
{
    NEW_AST_NODE(retval, MOJOSHADER_astPackOffset, MOJOSHADER_AST_PACK_OFFSET);
    retval->ident1 = a;
    retval->ident2 = b;
    return retval;
} // new_pack_offset

static void delete_pack_offset(Context *ctx, MOJOSHADER_astPackOffset *o)
{
    DELETE_AST_NODE(o);
    Free(ctx, o);
} // delete_pack_offset

static MOJOSHADER_astVariableLowLevel *new_variable_lowlevel(Context *ctx,
                                               MOJOSHADER_astPackOffset *po,
                                               const char *reg)
{
    NEW_AST_NODE(retval, MOJOSHADER_astVariableLowLevel,
                 MOJOSHADER_AST_VARIABLE_LOWLEVEL);
    retval->packoffset = po;
    retval->register_name = reg;
    return retval;
} // new_variable_lowlevel

static void delete_variable_lowlevel(Context *ctx,
                                     MOJOSHADER_astVariableLowLevel *vll)
{
    DELETE_AST_NODE(vll);
    delete_pack_offset(ctx, vll->packoffset);
    Free(ctx, vll);
} // delete_variable_lowlevel

static MOJOSHADER_astAnnotations *new_annotation(Context *ctx,
                                        const MOJOSHADER_astDataType *dt,
                                        MOJOSHADER_astExpression *initializer)
{
    NEW_AST_NODE(retval, MOJOSHADER_astAnnotations, MOJOSHADER_AST_ANNOTATION);
    retval->datatype = dt;
    retval->initializer = initializer;
    retval->next = NULL;
    return retval;
} // new_annotation

static void delete_annotation(Context *ctx, MOJOSHADER_astAnnotations *annos)
{
    DELETE_AST_NODE(annos);
    delete_annotation(ctx, annos->next);
    delete_expr(ctx, annos->initializer);
    Free(ctx, annos);
} // delete_annotation

static MOJOSHADER_astVariableDeclaration *new_variable_declaration(
                            Context *ctx, MOJOSHADER_astScalarOrArray *soa,
                            const char *semantic,
                            MOJOSHADER_astAnnotations *annotations,
                            MOJOSHADER_astExpression *init,
                            MOJOSHADER_astVariableLowLevel *vll)
{
    NEW_AST_NODE(retval, MOJOSHADER_astVariableDeclaration,
                 MOJOSHADER_AST_VARIABLE_DECLARATION);
    retval->datatype = NULL;
    retval->attributes = 0;
    retval->anonymous_datatype = NULL;
    retval->details = soa;
    retval->semantic = semantic;
    retval->annotations = annotations;
    retval->initializer = init;
    retval->lowlevel = vll;
    retval->next = NULL;
    return retval;
} // new_variable_declaration

static void delete_variable_declaration(Context *ctx,
                                        MOJOSHADER_astVariableDeclaration *dcl)
{
    DELETE_AST_NODE(dcl);
    delete_variable_declaration(ctx, dcl->next);
    delete_scalar_or_array(ctx, dcl->details);
    delete_annotation(ctx, dcl->annotations);
    delete_expr(ctx, dcl->initializer);
    delete_variable_lowlevel(ctx, dcl->lowlevel);
    Free(ctx, dcl);
} // delete_variable_declaration

static MOJOSHADER_astCompilationUnit *new_global_variable(Context *ctx,
                                      MOJOSHADER_astVariableDeclaration *decl)
{
    NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitVariable,
                 MOJOSHADER_AST_COMPUNIT_VARIABLE);
    retval->next = NULL;
    retval->declaration = decl;
    return (MOJOSHADER_astCompilationUnit *) retval;
} // new_global_variable

static void delete_global_variable(Context *ctx,
                                   MOJOSHADER_astCompilationUnitVariable *var)
{
    DELETE_AST_NODE(var);
    delete_compilation_unit(ctx, var->next);
    delete_variable_declaration(ctx, var->declaration);
    Free(ctx, var);
} // delete_global_variable

static MOJOSHADER_astCompilationUnit *new_global_typedef(Context *ctx,
                                                     MOJOSHADER_astTypedef *td)
{
    NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitTypedef,
                 MOJOSHADER_AST_COMPUNIT_TYPEDEF);
    retval->next = NULL;
    retval->type_info = td;
    return (MOJOSHADER_astCompilationUnit *) retval;
} // new_global_typedef

static void delete_global_typedef(Context *ctx,
                                  MOJOSHADER_astCompilationUnitTypedef *unit)
{
    DELETE_AST_NODE(unit);
    delete_compilation_unit(ctx, unit->next);
    delete_typedef(ctx, unit->type_info);
    Free(ctx, unit);
} // delete_global_typedef

static MOJOSHADER_astStructMembers *new_struct_member(Context *ctx,
                                            MOJOSHADER_astScalarOrArray *soa,
                                            const char *semantic)
{
    NEW_AST_NODE(retval, MOJOSHADER_astStructMembers,
                 MOJOSHADER_AST_STRUCT_MEMBER);
    retval->datatype = NULL;
    retval->semantic = semantic;
    retval->details = soa;
    retval->interpolation_mod = MOJOSHADER_AST_INTERPMOD_NONE;
    retval->next = NULL;
    return retval;
} // new_struct_member

static void delete_struct_member(Context *ctx,
                                 MOJOSHADER_astStructMembers *member)
{
    DELETE_AST_NODE(member);
    delete_struct_member(ctx, member->next);
    delete_scalar_or_array(ctx, member->details);
    Free(ctx, member);
} // delete_struct_member

static MOJOSHADER_astStructDeclaration *new_struct_declaration(Context *ctx,
                                        const char *name,
                                        MOJOSHADER_astStructMembers *members)
{
    NEW_AST_NODE(retval, MOJOSHADER_astStructDeclaration,
                 MOJOSHADER_AST_STRUCT_DECLARATION);
    retval->datatype = NULL;
    retval->name = name;
    retval->members = members;
    return retval;
} // new_struct_declaration

static void delete_struct_declaration(Context *ctx,
                                      MOJOSHADER_astStructDeclaration *decl)
{
    DELETE_AST_NODE(decl);
    delete_struct_member(ctx, decl->members);
    Free(ctx, decl);
} // delete_struct_declaration

static MOJOSHADER_astCompilationUnit *new_global_struct(Context *ctx,
                                           MOJOSHADER_astStructDeclaration *sd)
{
    NEW_AST_NODE(retval, MOJOSHADER_astCompilationUnitStruct,
                 MOJOSHADER_AST_COMPUNIT_STRUCT);
    retval->next = NULL;
    retval->struct_info = sd;
    return (MOJOSHADER_astCompilationUnit *) retval;
} // new_global_struct

static void delete_global_struct(Context *ctx,
                                 MOJOSHADER_astCompilationUnitStruct *unit)
{
    DELETE_AST_NODE(unit);
    delete_compilation_unit(ctx, unit->next);
    delete_struct_declaration(ctx, unit->struct_info);
    Free(ctx, unit);
} // delete_global_struct

static void delete_compilation_unit(Context *ctx,
                                    MOJOSHADER_astCompilationUnit *unit)
{
    if (!unit) return;

    // it's important to not recurse too deeply here, since you may have
    //  thousands of items in this linked list (each line of a massive
    //  function, for example). To avoid this, we iterate the list here,
    //  deleting all children and making them think they have no reason
    //  to recurse in their own delete methods.
    // Please note that everyone should _try_ to delete their "next" member,
    //  just in case, but hopefully this cleaned it out.

    MOJOSHADER_astCompilationUnit *i = unit->next;
    unit->next = NULL;
    while (i)
    {
        MOJOSHADER_astCompilationUnit *next = i->next;
        i->next = NULL;
        delete_compilation_unit(ctx, i);
        i = next;
    } // while

    switch (unit->ast.type)
    {
        #define DELETE_UNIT(typ, cls, fn) \
            case MOJOSHADER_AST_COMPUNIT_##typ: delete_##fn(ctx, (cls *) unit); break;
        DELETE_UNIT(FUNCTION, MOJOSHADER_astCompilationUnitFunction, function);
        DELETE_UNIT(TYPEDEF, MOJOSHADER_astCompilationUnitTypedef, global_typedef);
        DELETE_UNIT(VARIABLE, MOJOSHADER_astCompilationUnitVariable, global_variable);
        DELETE_UNIT(STRUCT, MOJOSHADER_astCompilationUnitStruct, global_struct);
        #undef DELETE_UNIT
        default: assert(0 && "missing cleanup code"); break;
    } // switch

    // don't free (unit) here, the class-specific functions do it.
} // delete_compilation_unit

static MOJOSHADER_astStatement *new_typedef_statement(Context *ctx,
                                                      MOJOSHADER_astTypedef *td)
{
    NEW_AST_NODE(retval, MOJOSHADER_astTypedefStatement,
                 MOJOSHADER_AST_STATEMENT_TYPEDEF);
    retval->next = NULL;
    retval->type_info = td;
    return (MOJOSHADER_astStatement *) retval;
} // new_typedef_statement

static void delete_typedef_statement(Context *ctx,
                                     MOJOSHADER_astTypedefStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_typedef(ctx, stmt->type_info);
    Free(ctx, stmt);
} // delete_typedef_statement

static MOJOSHADER_astStatement *new_return_statement(Context *ctx,
                                                MOJOSHADER_astExpression *expr)
{
    NEW_AST_NODE(retval, MOJOSHADER_astReturnStatement,
                 MOJOSHADER_AST_STATEMENT_RETURN);
    retval->next = NULL;
    retval->expr = expr;
    return (MOJOSHADER_astStatement *) retval;
} // new_return_statement

static void delete_return_statement(Context *ctx,
                                    MOJOSHADER_astReturnStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_expr(ctx, stmt->expr);
    Free(ctx, stmt);
} // delete_return_statement

static MOJOSHADER_astStatement *new_block_statement(Context *ctx,
                                               MOJOSHADER_astStatement *stmts)
{
    NEW_AST_NODE(retval, MOJOSHADER_astBlockStatement,
                 MOJOSHADER_AST_STATEMENT_BLOCK);
    retval->next = NULL;
    retval->statements = stmts;
    return (MOJOSHADER_astStatement *) retval;
} // new_block_statement

static void delete_block_statement(Context *ctx,
                                   MOJOSHADER_astBlockStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->statements);
    delete_statement(ctx, stmt->next);
    Free(ctx, stmt);
} // delete_statement_block

static MOJOSHADER_astStatement *new_for_statement(Context *ctx,
                                    MOJOSHADER_astVariableDeclaration *decl,
                                    MOJOSHADER_astExpression *initializer,
                                    MOJOSHADER_astExpression *looptest,
                                    MOJOSHADER_astExpression *counter,
                                    MOJOSHADER_astStatement *statement)
{
    NEW_AST_NODE(retval, MOJOSHADER_astForStatement,
                 MOJOSHADER_AST_STATEMENT_FOR);
    retval->next = NULL;
    retval->unroll = -1;
    retval->var_decl = decl;
    retval->initializer = initializer;
    retval->looptest = looptest;
    retval->counter = counter;
    retval->statement = statement;
    return (MOJOSHADER_astStatement *) retval;
} // new_for_statement

static void delete_for_statement(Context *ctx,MOJOSHADER_astForStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_variable_declaration(ctx, stmt->var_decl);
    delete_expr(ctx, stmt->initializer);
    delete_expr(ctx, stmt->looptest);
    delete_expr(ctx, stmt->counter);
    delete_statement(ctx, stmt->statement);
    Free(ctx, stmt);
} // delete_for_statement

static MOJOSHADER_astStatement *new_do_statement(Context *ctx,
                                                const int unroll,
                                                MOJOSHADER_astStatement *stmt,
                                                MOJOSHADER_astExpression *expr)
{
    NEW_AST_NODE(retval,MOJOSHADER_astDoStatement,MOJOSHADER_AST_STATEMENT_DO);
    retval->next = NULL;
    retval->unroll = unroll;
    retval->expr = expr;
    retval->statement = stmt;
    return (MOJOSHADER_astStatement *) retval;
} // new_do_statement

static void delete_do_statement(Context *ctx, MOJOSHADER_astDoStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_statement(ctx, stmt->statement);
    delete_expr(ctx, stmt->expr);
    Free(ctx, stmt);
} // delete_do_statement

static MOJOSHADER_astStatement *new_while_statement(Context *ctx,
                                                const int unroll,
                                                MOJOSHADER_astExpression *expr,
                                                MOJOSHADER_astStatement *stmt)
{
    NEW_AST_NODE(retval, MOJOSHADER_astWhileStatement,
                 MOJOSHADER_AST_STATEMENT_WHILE);
    retval->next = NULL;
    retval->unroll = unroll;
    retval->expr = expr;
    retval->statement = stmt;
    return (MOJOSHADER_astStatement *) retval;
} // new_while_statement

static void delete_while_statement(Context *ctx,
                                   MOJOSHADER_astWhileStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_statement(ctx, stmt->statement);
    delete_expr(ctx, stmt->expr);
    Free(ctx, stmt);
} // delete_while_statement

static MOJOSHADER_astStatement *new_if_statement(Context *ctx,
                                            const int attr,
                                            MOJOSHADER_astExpression *expr,
                                            MOJOSHADER_astStatement *stmt,
                                            MOJOSHADER_astStatement *elsestmt)
{
    NEW_AST_NODE(retval,MOJOSHADER_astIfStatement,MOJOSHADER_AST_STATEMENT_IF);
    retval->next = NULL;
    retval->attributes = attr;
    retval->expr = expr;
    retval->statement = stmt;
    retval->else_statement = elsestmt;
    return (MOJOSHADER_astStatement *) retval;
} // new_if_statement

static void delete_if_statement(Context *ctx, MOJOSHADER_astIfStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_expr(ctx, stmt->expr);
    delete_statement(ctx, stmt->statement);
    delete_statement(ctx, stmt->else_statement);
    Free(ctx, stmt);
} // delete_if_statement

static MOJOSHADER_astSwitchCases *new_switch_case(Context *ctx,
                                                MOJOSHADER_astExpression *expr,
                                                MOJOSHADER_astStatement *stmt)
{
    NEW_AST_NODE(retval, MOJOSHADER_astSwitchCases, MOJOSHADER_AST_SWITCH_CASE);
    retval->expr = expr;
    retval->statement = stmt;
    retval->next = NULL;
    return retval;
} // new_switch_case

static void delete_switch_case(Context *ctx, MOJOSHADER_astSwitchCases *sc)
{
    DELETE_AST_NODE(sc);
    delete_switch_case(ctx, sc->next);
    delete_expr(ctx, sc->expr);
    delete_statement(ctx, sc->statement);
    Free(ctx, sc);
} // delete_switch_case

static MOJOSHADER_astStatement *new_empty_statement(Context *ctx)
{
    NEW_AST_NODE(retval, MOJOSHADER_astEmptyStatement,
                 MOJOSHADER_AST_STATEMENT_EMPTY);
    retval->next = NULL;
    return (MOJOSHADER_astStatement *) retval;
} // new_empty_statement

static void delete_empty_statement(Context *ctx,
                                   MOJOSHADER_astEmptyStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    Free(ctx, stmt);
} // delete_empty_statement

static MOJOSHADER_astStatement *new_break_statement(Context *ctx)
{
    NEW_AST_NODE(retval, MOJOSHADER_astBreakStatement,
                 MOJOSHADER_AST_STATEMENT_BREAK);
    retval->next = NULL;
    return (MOJOSHADER_astStatement *) retval;
} // new_break_statement

static void delete_break_statement(Context *ctx,
                                   MOJOSHADER_astBreakStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    Free(ctx, stmt);
} // delete_break_statement

static MOJOSHADER_astStatement *new_continue_statement(Context *ctx)
{
    NEW_AST_NODE(retval, MOJOSHADER_astContinueStatement,
                 MOJOSHADER_AST_STATEMENT_CONTINUE);
    retval->next = NULL;
    return (MOJOSHADER_astStatement *) retval;
} // new_continue_statement

static void delete_continue_statement(Context *ctx,
                                      MOJOSHADER_astContinueStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    Free(ctx, stmt);
} // delete_continue_statement

static MOJOSHADER_astStatement *new_discard_statement(Context *ctx)
{
    NEW_AST_NODE(retval, MOJOSHADER_astDiscardStatement,
                 MOJOSHADER_AST_STATEMENT_DISCARD);
    retval->next = NULL;
    return (MOJOSHADER_astStatement *) retval;
} // new_discard_statement

static void delete_discard_statement(Context *ctx,
                                     MOJOSHADER_astDiscardStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    Free(ctx, stmt);
} // delete_discard_statement

static MOJOSHADER_astStatement *new_expr_statement(Context *ctx,
                                                MOJOSHADER_astExpression *expr)
{
    NEW_AST_NODE(retval, MOJOSHADER_astExpressionStatement,
                 MOJOSHADER_AST_STATEMENT_EXPRESSION);
    retval->next = NULL;
    retval->expr = expr;
    return (MOJOSHADER_astStatement *) retval;
} // new_expr_statement

static void delete_expr_statement(Context *ctx,
                                  MOJOSHADER_astExpressionStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_expr(ctx, stmt->expr);
    Free(ctx, stmt);
} // delete_expr_statement

static MOJOSHADER_astStatement *new_switch_statement(Context *ctx,
                                            const int attr,
                                            MOJOSHADER_astExpression *expr,
                                            MOJOSHADER_astSwitchCases *cases)
{
    NEW_AST_NODE(retval, MOJOSHADER_astSwitchStatement,
                 MOJOSHADER_AST_STATEMENT_SWITCH);
    retval->next = NULL;
    retval->attributes = attr;
    retval->expr = expr;
    retval->cases = cases;
    return (MOJOSHADER_astStatement *) retval;
} // new_switch_statement

static void delete_switch_statement(Context *ctx,
                                    MOJOSHADER_astSwitchStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_expr(ctx, stmt->expr);
    delete_switch_case(ctx, stmt->cases);
    Free(ctx, stmt);
} // delete_switch_statement

static MOJOSHADER_astStatement *new_struct_statement(Context *ctx,
                                        MOJOSHADER_astStructDeclaration *sd)
{
    NEW_AST_NODE(retval, MOJOSHADER_astStructStatement,
                 MOJOSHADER_AST_STATEMENT_STRUCT);
    retval->next = NULL;
    retval->struct_info = sd;
    return (MOJOSHADER_astStatement *) retval;
} // new_struct_statement

static void delete_struct_statement(Context *ctx,
                                    MOJOSHADER_astStructStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_struct_declaration(ctx, stmt->struct_info);
    Free(ctx, stmt);
} // delete_struct_statement

static MOJOSHADER_astStatement *new_vardecl_statement(Context *ctx,
                                        MOJOSHADER_astVariableDeclaration *vd)
{
    NEW_AST_NODE(retval, MOJOSHADER_astVarDeclStatement,
                 MOJOSHADER_AST_STATEMENT_VARDECL);
    retval->next = NULL;
    retval->declaration = vd;
    return (MOJOSHADER_astStatement *) retval;
} // new_vardecl_statement

static void delete_vardecl_statement(Context *ctx,
                                     MOJOSHADER_astVarDeclStatement *stmt)
{
    DELETE_AST_NODE(stmt);
    delete_statement(ctx, stmt->next);
    delete_variable_declaration(ctx, stmt->declaration);
    Free(ctx, stmt);
} // delete_vardecl_statement

static void delete_statement(Context *ctx, MOJOSHADER_astStatement *stmt)
{
    if (!stmt) return;

    // it's important to not recurse too deeply here, since you may have
    //  thousands of items in this linked list (each line of a massive
    //  function, for example). To avoid this, we iterate the list here,
    //  deleting all children and making them think they have no reason
    //  to recurse in their own delete methods.
    // Please note that everyone should _try_ to delete their "next" member,
    //  just in case, but hopefully this cleaned it out.

    MOJOSHADER_astStatement *i = stmt->next;
    stmt->next = NULL;
    while (i)
    {
        MOJOSHADER_astStatement *next = i->next;
        i->next = NULL;
        delete_statement(ctx, i);
        i = next;
    } // while

    switch (stmt->ast.type)
    {
        #define DELETE_STATEMENT(typ, cls, fn) \
            case MOJOSHADER_AST_STATEMENT_##typ: \
                delete_##fn##_statement(ctx, (cls *) stmt); break;
        DELETE_STATEMENT(BLOCK, MOJOSHADER_astBlockStatement, block);
        DELETE_STATEMENT(EMPTY, MOJOSHADER_astEmptyStatement, empty);
        DELETE_STATEMENT(IF, MOJOSHADER_astIfStatement, if);
        DELETE_STATEMENT(SWITCH, MOJOSHADER_astSwitchStatement, switch);
        DELETE_STATEMENT(EXPRESSION, MOJOSHADER_astExpressionStatement, expr);
        DELETE_STATEMENT(FOR, MOJOSHADER_astForStatement, for);
        DELETE_STATEMENT(DO, MOJOSHADER_astDoStatement, do);
        DELETE_STATEMENT(WHILE, MOJOSHADER_astWhileStatement, while);
        DELETE_STATEMENT(RETURN, MOJOSHADER_astReturnStatement, return);
        DELETE_STATEMENT(BREAK, MOJOSHADER_astBreakStatement, break);
        DELETE_STATEMENT(CONTINUE, MOJOSHADER_astContinueStatement, continue);
        DELETE_STATEMENT(DISCARD, MOJOSHADER_astDiscardStatement, discard);
        DELETE_STATEMENT(TYPEDEF, MOJOSHADER_astTypedefStatement, typedef);
        DELETE_STATEMENT(STRUCT, MOJOSHADER_astStructStatement, struct);
        DELETE_STATEMENT(VARDECL, MOJOSHADER_astVarDeclStatement, vardecl);
        #undef DELETE_STATEMENT
        default: assert(0 && "missing cleanup code"); break;
    } // switch
    // don't free (stmt) here, the class-specific functions do it.
} // delete_statement


static const MOJOSHADER_astDataType *get_usertype(const Context *ctx,
                                                  const char *token)
{
    const void *value;  // search all scopes.
    if (!hash_find(ctx->usertypes.hash, token, &value))
        return NULL;
    return value ? ((SymbolScope *) value)->datatype : NULL;
} // get_usertype


// This is where the actual parsing happens. It's Lemon-generated!
#define __MOJOSHADER_HLSL_COMPILER__ 1
#include "mojoshader_parser_hlsl.h"


#if 0
static int expr_is_constant(MOJOSHADER_astExpression *expr)
{
    const MOJOSHADER_astNodeType op = expr->ast.type;
    if (operator_is_unary(op))
        return expr_is_constant(expr->unary.operand);
    else if (operator_is_binary(op))
    {
        return ( expr_is_constant(expr->binary.left) &&
                 expr_is_constant(expr->binary.right) );
    } // else if
    else if (operator_is_ternary(op))
    {
        return ( expr_is_constant(expr->ternary.left) &&
                 expr_is_constant(expr->ternary.center) &&
                 expr_is_constant(expr->ternary.right) );
    } // else if

    return ( (op == MOJOSHADER_AST_OP_INT_LITERAL) ||
             (op == MOJOSHADER_AST_OP_FLOAT_LITERAL) ||
             (op == MOJOSHADER_AST_OP_STRING_LITERAL) ||
             (op == MOJOSHADER_AST_OP_BOOLEAN_LITERAL) );
} // expr_is_constant
#endif

typedef struct AstCalcData
{
    int isflt;
    union
    {
        double f;
        int64 i;
    } value;
} AstCalcData;

// returns 0 if this expression is non-constant, 1 if it is.
//  calculation results land in (data).
static int calc_ast_const_expr(Context *ctx, void *_expr, AstCalcData *data)
{
    const MOJOSHADER_astNode *expr = (MOJOSHADER_astNode *) _expr;
    const MOJOSHADER_astNodeType op = expr->ast.type;

    ctx->sourcefile = expr->ast.filename;
    ctx->sourceline = expr->ast.line;

    if (operator_is_unary(op))
    {
        if (!calc_ast_const_expr(ctx, expr->unary.operand, data))
            return 0;

        if (data->isflt)
        {
            switch (op)
            {
                case MOJOSHADER_AST_OP_NEGATE:
                    data->value.f = -data->value.f;
                    return 1;
                case MOJOSHADER_AST_OP_NOT:
                    data->value.f = !data->value.f;
                    return 1;
                case MOJOSHADER_AST_OP_COMPLEMENT:
                    fail(ctx, "integer operation on floating point value");
                    return 0;
                case MOJOSHADER_AST_OP_CAST:
                    // !!! FIXME: this should work, but it's complicated.
                    assert(0 && "write me");
                    return 0;
                default: break;
            } // switch
        } // if

        else  // integer version
        {
            switch (op)
            {
                case MOJOSHADER_AST_OP_NEGATE:
                    data->value.i = -data->value.i;
                    return 1;
                case MOJOSHADER_AST_OP_NOT:
                    data->value.i = !data->value.i;
                    return 1;
                case MOJOSHADER_AST_OP_COMPLEMENT:
                    data->value.i = ~data->value.i;
                    return 1;
                case MOJOSHADER_AST_OP_CAST:
                    // !!! FIXME: this should work, but it's complicated.
                    assert(0 && "write me");
                    return 0;
                default: break;
            } // switch
        } // else
        assert(0 && "unhandled operation?");
        return 0;
    } // if

    else if (operator_is_binary(op))
    {
        AstCalcData subdata2;
        if ( (!calc_ast_const_expr(ctx, expr->binary.left, data)) ||
             (!calc_ast_const_expr(ctx, expr->binary.right, &subdata2)) )
            return 0;

        // upgrade to float if either operand is float.
        if ((data->isflt) || (subdata2.isflt))
        {
            if (!data->isflt) data->value.f = (double) data->value.i;
            if (!subdata2.isflt) subdata2.value.f = (double) subdata2.value.i;
            data->isflt = subdata2.isflt = 1;
        } // if

        switch (op)
        {
            // gcc doesn't handle commas here, either (fails to parse!).
            case MOJOSHADER_AST_OP_COMMA:
            case MOJOSHADER_AST_OP_ASSIGN:
            case MOJOSHADER_AST_OP_MULASSIGN:
            case MOJOSHADER_AST_OP_DIVASSIGN:
            case MOJOSHADER_AST_OP_MODASSIGN:
            case MOJOSHADER_AST_OP_ADDASSIGN:
            case MOJOSHADER_AST_OP_SUBASSIGN:
            case MOJOSHADER_AST_OP_LSHIFTASSIGN:
            case MOJOSHADER_AST_OP_RSHIFTASSIGN:
            case MOJOSHADER_AST_OP_ANDASSIGN:
            case MOJOSHADER_AST_OP_XORASSIGN:
            case MOJOSHADER_AST_OP_ORASSIGN:
                return 0;  // assignment is non-constant.
            default: break;
        } // switch

        if (data->isflt)
        {
            switch (op)
            {
                case MOJOSHADER_AST_OP_MULTIPLY:
                    data->value.f *= subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_DIVIDE:
                    data->value.f /= subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_ADD:
                    data->value.f += subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_SUBTRACT:
                    data->value.f -= subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_LESSTHAN:
                    data->isflt = 0;
                    data->value.i = data->value.f < subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_GREATERTHAN:
                    data->isflt = 0;
                    data->value.i = data->value.f > subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_LESSTHANOREQUAL:
                    data->isflt = 0;
                    data->value.i = data->value.f <= subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_GREATERTHANOREQUAL:
                    data->isflt = 0;
                    data->value.i = data->value.f >= subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_EQUAL:
                    data->isflt = 0;
                    data->value.i = data->value.f == subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_NOTEQUAL:
                    data->isflt = 0;
                    data->value.i = data->value.f != subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_LOGICALAND:
                    data->isflt = 0;
                    data->value.i = data->value.f && subdata2.value.f;
                    return 1;
                case MOJOSHADER_AST_OP_LOGICALOR:
                    data->isflt = 0;
                    data->value.i = data->value.f || subdata2.value.f;
                    return 1;

                case MOJOSHADER_AST_OP_LSHIFT:
                case MOJOSHADER_AST_OP_RSHIFT:
                case MOJOSHADER_AST_OP_MODULO:
                case MOJOSHADER_AST_OP_BINARYAND:
                case MOJOSHADER_AST_OP_BINARYXOR:
                case MOJOSHADER_AST_OP_BINARYOR:
                    fail(ctx, "integer operation on floating point value");
                    return 0;
                default: break;
            } // switch
        } // if

        else   // integer version.
        {
            switch (op)
            {
                case MOJOSHADER_AST_OP_MULTIPLY:
                    data->value.i *= subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_DIVIDE:
                    data->value.i /= subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_ADD:
                    data->value.i += subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_SUBTRACT:
                    data->value.i -= subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_LESSTHAN:
                    data->value.i = data->value.i < subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_GREATERTHAN:
                    data->value.i = data->value.i > subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_LESSTHANOREQUAL:
                    data->value.i = data->value.i <= subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_GREATERTHANOREQUAL:
                    data->value.i = data->value.i >= subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_EQUAL:
                    data->value.i = data->value.i == subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_NOTEQUAL:
                    data->value.i = data->value.i != subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_LOGICALAND:
                    data->value.i = data->value.i && subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_LOGICALOR:
                    data->value.i = data->value.i || subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_LSHIFT:
                    data->value.i = data->value.i << subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_RSHIFT:
                    data->value.i = data->value.i >> subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_MODULO:
                    data->value.i = data->value.i % subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_BINARYAND:
                    data->value.i = data->value.i & subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_BINARYXOR:
                    data->value.i = data->value.i ^ subdata2.value.i;
                    return 1;
                case MOJOSHADER_AST_OP_BINARYOR:
                    data->value.i = data->value.i | subdata2.value.i;
                    return 1;
                default: break;
            } // switch
        } // else

        assert(0 && "unhandled operation?");
        return 0;
    } // else if

    else if (operator_is_ternary(op))
    {
        AstCalcData subdata2;
        AstCalcData subdata3;

        assert(op == MOJOSHADER_AST_OP_CONDITIONAL);  // only one we have.

        if ( (!calc_ast_const_expr(ctx, expr->ternary.left, data)) ||
             (!calc_ast_const_expr(ctx, expr->ternary.center, &subdata2)) ||
             (!calc_ast_const_expr(ctx, expr->ternary.right, &subdata3)) )
            return 0;

        // first operand should be bool (for the one ternary operator we have).
        if (data->isflt)
        {
            data->isflt = 0;
            data->value.i = (int64) subdata3.value.f;
        } // if

        // upgrade to float if either operand is float.
        if ((subdata2.isflt) || (subdata3.isflt))
        {
            if (!subdata2.isflt) subdata2.value.f = (double) subdata2.value.i;
            if (!subdata3.isflt) subdata3.value.f = (double) subdata3.value.i;
            subdata2.isflt = subdata3.isflt = 1;
        } // if

        data->isflt = subdata2.isflt;
        if (data->isflt)
            data->value.f = data->value.i ? subdata2.value.f : subdata3.value.f;
        else
            data->value.i = data->value.i ? subdata2.value.i : subdata3.value.i;
        return 1;
    } // else if

    else  // not an operator? See if this is a literal value.
    {
        switch (op)
        {
            case MOJOSHADER_AST_OP_INT_LITERAL:
                data->isflt = 0;
                data->value.i = expr->intliteral.value;
                return 1;

            case MOJOSHADER_AST_OP_FLOAT_LITERAL:
                data->isflt = 1;
                data->value.f = expr->floatliteral.value;
                return 1;

            case MOJOSHADER_AST_OP_BOOLEAN_LITERAL:
                data->isflt = 0;
                data->value.i = expr->boolliteral.value ? 1 : 0;
                return 1;

            default: break;
        } // switch
    } // switch

    return 0;  // not constant, or unhandled.
} // calc_ast_const_expr


static const MOJOSHADER_astDataType *reduce_datatype(Context *ctx, const MOJOSHADER_astDataType *dt)
{
    const MOJOSHADER_astDataType *retval = dt;
    while (retval && retval->type == MOJOSHADER_AST_DATATYPE_USER)
    {
        // !!! FIXME: Ugh, const removal.
        MOJOSHADER_astDataTypeUser *user = (MOJOSHADER_astDataTypeUser *) &retval->user;
        if (user->details->type == MOJOSHADER_AST_DATATYPE_NONE)
        {
            // Take this opportunity to fix up some usertype stubs that were
            //  left over from the parse phase. You HAVE to catch these in the
            //  right scope, so be aggressive about calling reduce_datatype()
            //  as soon as things come into view!
            user->details = get_usertype(ctx, user->name);
            assert(user->details != NULL);
        } // if

        retval = user->details;
    } // while

    return retval;
} // reduce_datatype


static inline const MOJOSHADER_astDataType *sanitize_datatype(Context *ctx, const MOJOSHADER_astDataType *dt)
{
    reduce_datatype(ctx, dt);
    return dt;
} // sanitize_datatype


static const MOJOSHADER_astDataType *build_function_datatype(Context *ctx,
                                        const MOJOSHADER_astDataType *rettype,
                                        const int paramcount,
                                        const MOJOSHADER_astDataType **params,
                                        const int intrinsic)
{
    if ( ((paramcount > 0) && (params == NULL)) ||
         ((paramcount == 0) && (params != NULL)) )
        return NULL;

    // !!! FIXME: this is hacky.
    const MOJOSHADER_astDataType **dtparams;
    void *ptr = Malloc(ctx, sizeof (*params) * paramcount);
    if (ptr == NULL)
        return NULL;
    if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr)))
    {
        Free(ctx, ptr);
        return NULL;
    } // if
    dtparams = (const MOJOSHADER_astDataType **) ptr;
    memcpy(dtparams, params, sizeof (*params) * paramcount);

    ptr = Malloc(ctx, sizeof (MOJOSHADER_astDataType));
    if (ptr == NULL)
        return NULL;
    if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr)))
    {
        Free(ctx, ptr);
        return NULL;
    } // if

    MOJOSHADER_astDataType *dt = (MOJOSHADER_astDataType *) ptr;
    dt->type = MOJOSHADER_AST_DATATYPE_FUNCTION;
    dt->function.retval = rettype;
    dt->function.params = dtparams;
    dt->function.num_params = paramcount;
    dt->function.intrinsic = intrinsic;
    return dt;
} // build_function_datatype


static const MOJOSHADER_astDataType *build_datatype(Context *ctx,
                                            const int isconst,
                                            const MOJOSHADER_astDataType *dt,
                                            MOJOSHADER_astScalarOrArray *soa)
{
    MOJOSHADER_astDataType *retval = NULL;

    assert( (soa->isarray && soa->dimension) ||
            (!soa->isarray && !soa->dimension) );

    sanitize_datatype(ctx, dt);

    // see if we can just reuse the exist datatype.
    if (!soa->isarray)
    {
        const int c1 = (dt->type & MOJOSHADER_AST_DATATYPE_CONST) != 0;
        const int c2 = (isconst != 0);
        if (c1 == c2)
            return dt;  // reuse existing datatype!
    } // if

    retval = (MOJOSHADER_astDataType *) Malloc(ctx, sizeof (*retval));
    if (retval == NULL)
        return NULL;

    // !!! FIXME: this is hacky.
    if (!buffer_append(ctx->garbage, &retval, sizeof (retval)))
    {
        Free(ctx, retval);
        return NULL;
    } // if

    if (!soa->isarray)
    {
        assert(soa->dimension == NULL);
        memcpy(retval, dt, sizeof (MOJOSHADER_astDataType));
        if (isconst)
            retval->type = (MOJOSHADER_astDataTypeType)((unsigned)retval->type | MOJOSHADER_AST_DATATYPE_CONST);
        else
            retval->type = (MOJOSHADER_astDataTypeType)((unsigned)retval->type & ~MOJOSHADER_AST_DATATYPE_CONST);
        return retval;
    } // if

    retval->type = MOJOSHADER_AST_DATATYPE_ARRAY;
    retval->array.base = dt;
    if (soa->dimension == NULL)
    {
        retval->array.elements = -1;
        return retval;
    } // if

    // Run the expression to verify it's constant and produces a positive int.
    AstCalcData data;
    data.isflt = 0;
    data.value.i = 0;
    retval->array.elements = 16;  // sane default for failure.
    const int ok = calc_ast_const_expr(ctx, soa->dimension, &data);

    // reset error position.
    ctx->sourcefile = soa->ast.filename;
    ctx->sourceline = soa->ast.line;

    if (!ok)
        fail(ctx, "array dimensions not constant");
    else if (data.isflt)
        fail(ctx, "array dimensions not integer");
    else if (data.value.i < 0)
        fail(ctx, "array dimensions negative");
    else
        retval->array.elements = data.value.i;

    return retval;
} // build_datatype


static void require_numeric_datatype(Context *ctx,
                                     const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    if (datatype->type == MOJOSHADER_AST_DATATYPE_VECTOR)
        datatype = reduce_datatype(ctx, datatype->vector.base);
    else if (datatype->type == MOJOSHADER_AST_DATATYPE_MATRIX)
        datatype = reduce_datatype(ctx, datatype->matrix.base);

    switch (datatype->type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL:
        case MOJOSHADER_AST_DATATYPE_INT:
        case MOJOSHADER_AST_DATATYPE_UINT:
        case MOJOSHADER_AST_DATATYPE_HALF:
        case MOJOSHADER_AST_DATATYPE_FLOAT:
        case MOJOSHADER_AST_DATATYPE_DOUBLE:
            return;
        default: break;
    } // switch

    fail(ctx, "Expected numeric type");  // !!! FIXME: fmt.
    // !!! FIXME: replace AST node with an AST_OP_INT_LITERAL zero, keep going.
} // require_numeric_datatype

static void require_integer_datatype(Context *ctx,
                                     const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    switch (datatype->type)
    {
        case MOJOSHADER_AST_DATATYPE_INT:
        case MOJOSHADER_AST_DATATYPE_UINT:
            return;
        default: break;
    } // switch

    fail(ctx, "Expected integer type");  // !!! FIXME: fmt.
    // !!! FIXME: replace AST node with an AST_OP_INT_LITERAL zero, keep going.
} // require_integer_datatype

static void require_boolean_datatype(Context *ctx,
                                     const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    switch (datatype->type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL:
        case MOJOSHADER_AST_DATATYPE_INT:
        case MOJOSHADER_AST_DATATYPE_UINT:
            return;
        default: break;
    } // switch

    fail(ctx, "Expected boolean type");  // !!! FIXME: fmt.
    // !!! FIXME: replace AST node with an AST_OP_BOOLEAN_LITERAL false, keep going.
} // require_numeric_datatype


static void require_array_datatype(Context *ctx,
                                   const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    if (datatype->type == MOJOSHADER_AST_DATATYPE_ARRAY)
        return;

    fail(ctx, "expected array");
    // !!! FIXME: delete array dereference for further processing.
} // require_array_datatype


static void require_struct_datatype(Context *ctx,
                                    const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    if (datatype->type == MOJOSHADER_AST_DATATYPE_STRUCT)
        return;

    fail(ctx, "expected struct");
    // !!! FIXME: delete struct dereference for further processing.
} // require_struct_datatype


static int require_function_datatype(Context *ctx,
                                     const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    if ((!datatype) || (datatype->type != MOJOSHADER_AST_DATATYPE_FUNCTION))
    {
        fail(ctx, "expected function");
        return 0;
    } // if

    return 1;
} // require_function_datatype


// Extract the individual element type from an array datatype.
static const MOJOSHADER_astDataType *array_element_datatype(Context *ctx,
                                        const MOJOSHADER_astDataType *datatype)
{
    datatype = reduce_datatype(ctx, datatype);
    assert(datatype->type == MOJOSHADER_AST_DATATYPE_ARRAY);
    return datatype->array.base;
} // array_element_datatype


// This tests two datatypes to see if they are compatible, and adds cast
//  operator nodes to the AST if the program was relying on implicit
//  casts between then. Will fail() if the datatypes can't be coerced
//  with a cast at all. (left) can be NULL to say that its datatype is
//  set in stone (an lvalue, for example). No other NULLs are allowed.
// Returns final datatype used once implicit casting is complete.
// The datatypes must be pointers from the string cache.
static const MOJOSHADER_astDataType *add_type_coercion(Context *ctx,
                                     MOJOSHADER_astExpression **left,
                                     const MOJOSHADER_astDataType *_ldatatype,
                                     MOJOSHADER_astExpression **right,
                                     const MOJOSHADER_astDataType *_rdatatype)
{
    // !!! FIXME: this whole function is probably naive at best.
    const MOJOSHADER_astDataType *ldatatype = reduce_datatype(ctx, _ldatatype);
    const MOJOSHADER_astDataType *rdatatype = reduce_datatype(ctx, _rdatatype);

    if (ldatatype == rdatatype)
        return ldatatype;   // they already match, so we're done.

    struct {
        const MOJOSHADER_astDataTypeType type;
        const int bits;
        const int is_unsigned;
        const int floating;
    } typeinf[] = {
        { MOJOSHADER_AST_DATATYPE_BOOL,    1, 1, 0 },
        { MOJOSHADER_AST_DATATYPE_HALF,   16, 0, 1 },
        { MOJOSHADER_AST_DATATYPE_INT,    32, 0, 0 },
        { MOJOSHADER_AST_DATATYPE_UINT,   32, 1, 0 },
        { MOJOSHADER_AST_DATATYPE_FLOAT,  32, 0, 1 },
        { MOJOSHADER_AST_DATATYPE_DOUBLE, 64, 0, 1 },
    };

    int lvector = 0;
    int lmatrix = 0;
    int l = STATICARRAYLEN(typeinf);
    if (ldatatype != NULL)
    {
        MOJOSHADER_astDataTypeType type = ldatatype->type;
        if (type == MOJOSHADER_AST_DATATYPE_VECTOR)
        {
            lvector = 1;
            type = ldatatype->vector.base->type;
        } // if
        else if (type == MOJOSHADER_AST_DATATYPE_MATRIX)
        {
            lmatrix = 1;
            type = ldatatype->matrix.base->type;
        } // if

        for (l = 0; l < STATICARRAYLEN(typeinf); l++)
        {
            if (typeinf[l].type == type)
                break;
        } // for
    } // if

    int rvector = 0;
    int rmatrix = 0;
    int r = STATICARRAYLEN(typeinf);
    if (rdatatype != NULL)
    {
        MOJOSHADER_astDataTypeType type = rdatatype->type;
        if (type == MOJOSHADER_AST_DATATYPE_VECTOR)
        {
            rvector = 1;
            type = rdatatype->vector.base->type;
        } // if
        else if (type == MOJOSHADER_AST_DATATYPE_MATRIX)
        {
            rmatrix = 1;
            type = rdatatype->matrix.base->type;
        } // if

        for (r = 0; r < STATICARRAYLEN(typeinf); r++)
        {
            if (typeinf[r].type == type)
                break;
        } // for
    } // if

    enum { CHOOSE_NEITHER, CHOOSE_LEFT, CHOOSE_RIGHT } choice = CHOOSE_NEITHER;
    if ((l < STATICARRAYLEN(typeinf)) && (r < STATICARRAYLEN(typeinf)))
    {
        if (left == NULL)
            choice = CHOOSE_LEFT;  // we need to force to the lvalue.
        else if (lmatrix && !rmatrix)
            choice = CHOOSE_LEFT;
        else if (!lmatrix && rmatrix)
            choice = CHOOSE_RIGHT;
        else if (lvector && !rvector)
            choice = CHOOSE_LEFT;
        else if (!lvector && rvector)
            choice = CHOOSE_RIGHT;
        else if (typeinf[l].bits > typeinf[r].bits)
            choice = CHOOSE_LEFT;
        else if (typeinf[l].bits < typeinf[r].bits)
            choice = CHOOSE_RIGHT;
        else if (typeinf[l].floating && !typeinf[r].floating)
            choice = CHOOSE_LEFT;
        else if (!typeinf[l].floating && typeinf[r].floating)
            choice = CHOOSE_RIGHT;
        else if (typeinf[l].is_unsigned && !typeinf[r].is_unsigned)
            choice = CHOOSE_LEFT;
        else if (!typeinf[l].is_unsigned && typeinf[r].is_unsigned)
            choice = CHOOSE_RIGHT;
    } // if

    if (choice == CHOOSE_LEFT)
    {
        *right = new_cast_expr(ctx, _ldatatype, *right);
        return _ldatatype;
    } // if
    else if (choice == CHOOSE_RIGHT)
    {
        *left = new_cast_expr(ctx, _rdatatype, *left);
        return _rdatatype;
    } // else if

    assert(choice == CHOOSE_NEITHER);
    fail(ctx, "incompatible data types");
    // Ditch original (*right), force a literal value that matches
    //  ldatatype, so further processing is normalized.
    // !!! FIXME: force (right) to match (left).
    delete_expr(ctx, *right);
    *right = new_cast_expr(ctx, _ldatatype, new_literal_int_expr(ctx, 0));
    return ldatatype;
} // add_type_coercion

static int is_swizzle_str(const char *str, const int veclen)
{
    int i;
    int is_xyzw = 0;
    int is_rgba = 0;

    assert(*str != '\0');  // can this actually happen?

    for (i = 0; i < veclen; i++, str++)
    {
        const char ch = *str;
        if (ch == '\0')
            break;
        else if ((ch == 'x') || (ch == 'y') || (ch == 'z') || (ch == 'w'))
            is_xyzw = 1;
        else if ((ch == 'r') || (ch == 'g') || (ch == 'b') || (ch == 'a'))
            is_rgba = 1;
    } // for

    if (*str != '\0')  // must be end of string here.
        return 0;  // not a swizzle.
    return ((is_rgba + is_xyzw) == 1);  // can only be one or the other.
} // is_swizzle_str

static int datatype_size(const MOJOSHADER_astDataType *dt)
{
    switch (dt->type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL: return 1;
        case MOJOSHADER_AST_DATATYPE_INT: return 4;
        case MOJOSHADER_AST_DATATYPE_UINT: return 4;
        case MOJOSHADER_AST_DATATYPE_FLOAT: return 4;
        case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM: return 4;
        case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM: return 4;
        case MOJOSHADER_AST_DATATYPE_HALF: return 2;
        case MOJOSHADER_AST_DATATYPE_DOUBLE: return 8;
            return 1;
        default:
            assert(0 && "Maybe should have used reduce_datatype()?");
            return 0;
    } // switch
} // datatype_size

static inline int is_scalar_datatype(const MOJOSHADER_astDataType *dt)
{
    switch (dt->type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL:
        case MOJOSHADER_AST_DATATYPE_INT:
        case MOJOSHADER_AST_DATATYPE_UINT:
        case MOJOSHADER_AST_DATATYPE_FLOAT:
        case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM:
        case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM:
        case MOJOSHADER_AST_DATATYPE_HALF:
        case MOJOSHADER_AST_DATATYPE_DOUBLE:
            return 1;
        default:
            return 0;
    } // switch
} // is_scalar_datatype

static inline int is_float_datatype(const MOJOSHADER_astDataType *dt)
{
    switch (dt->type)
    {
        case MOJOSHADER_AST_DATATYPE_FLOAT: return 1;
        case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM: return 1;
        case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM: return 1;
        default: return 0;
    } // switch
} // is_float_datatype

static int datatype_elems(Context *ctx, const MOJOSHADER_astDataType *dt)
{
    dt = reduce_datatype(ctx, dt);
    switch (dt->type)
    {
        case MOJOSHADER_AST_DATATYPE_VECTOR:
            return dt->vector.elements;
        case MOJOSHADER_AST_DATATYPE_MATRIX:
            return dt->matrix.rows * dt->matrix.columns;
        default:
            return 1;
    } // switch
} // datatype_elems

static const MOJOSHADER_astDataType *datatype_base(Context *ctx, const MOJOSHADER_astDataType *dt)
{
    dt = reduce_datatype(ctx, dt);
    if (dt == NULL)
        return dt;

    switch (dt->type)
    {
        case MOJOSHADER_AST_DATATYPE_VECTOR:
            dt = dt->vector.base;
            break;
        case MOJOSHADER_AST_DATATYPE_MATRIX:
            dt = dt->matrix.base;
            break;
        case MOJOSHADER_AST_DATATYPE_BUFFER:
            dt = dt->buffer.base;
            break;
        case MOJOSHADER_AST_DATATYPE_ARRAY:
            dt = dt->array.base;
            break;
        default: break;
    } // switch

    return dt;
} // datatype_base

typedef enum
{
    DT_MATCH_INCOMPATIBLE,         // flatly incompatible
    DT_MATCH_COMPATIBLE_DOWNCAST,  // would have to lose precision
    DT_MATCH_COMPATIBLE_UPCAST,    // would have to gain precision
    DT_MATCH_COMPATIBLE,           // can cast to without serious change.
    DT_MATCH_PERFECT               // identical datatype.
} DatatypeMatch;

static DatatypeMatch compatible_arg_datatype(Context *ctx,
                                   const MOJOSHADER_astDataType *arg,
                                   const MOJOSHADER_astDataType *param)
{
    // The matching rules for HLSL function overloading, as far as I can
    //  tell from experimenting with Microsoft's compiler, seem to be this:
    //
    // - All parameters of a function must match what the caller specified
    //   after possible type promotion via the following rules.
    // - If the number of arguments and the number of parameters don't match,
    //   that overload is immediately rejected.
    // - Each overloaded function is given a score that is the sum of the
    //   "worth" of each parameter vs the caller's arguments
    //   (see DatatypeMatch). The higher the score, the more favorable this
    //   function overload would be.
    // - If there is a tie for highest score between two or more function
    //   overloads, we declare that function call to be ambiguous and fail().
    // - Scalars can be promoted to vectors to make a parameter match.
    // - Scalars can promote to other scalars (short to int, etc).
    // - Datatypes can downcast, but should generate a warning.
    //   (calling void fn(float x); as fn((double)1.0) should warn).
    // - Vectors may NOT be extend (a float2 can't implicity extend to a
    //   float4).
    // - Vectors with the same elements can promote (a half2 can become
    //   a float2). Downcasting between vectors with the same number of
    //   elements is allowed.
    // - A perfect match of all params will be favored over any functions
    //   that only match if type promotion is applied (given a perfect match
    //   of all parameters, we'll stop looking for other matches).

    if (datatypes_match(arg, param))
        return DT_MATCH_PERFECT;  // that was easy.

    arg = reduce_datatype(ctx, arg);
    param = reduce_datatype(ctx, param);

    int do_base_test = 0;

    if (is_scalar_datatype(arg))
        do_base_test = 1; // we let these all go through for now.

    else if (arg->type == param->type)
    {
        if (arg->type == MOJOSHADER_AST_DATATYPE_VECTOR)
            do_base_test = (arg->vector.elements == param->vector.elements);
        else if (arg->type == MOJOSHADER_AST_DATATYPE_MATRIX)
        {
            do_base_test =
                ((arg->matrix.rows == param->matrix.rows) &&
                 (arg->matrix.columns == param->matrix.columns));
        } // if
    } // if

    if (do_base_test)
    {
        arg = datatype_base(ctx, arg);
        param = datatype_base(ctx, param);

        const int argsize = datatype_size(arg);
        const int paramsize = datatype_size(param);
        const int argfloat = is_float_datatype(arg);
        const int paramfloat = is_float_datatype(param);

        if (argfloat && !paramfloat)
            return DT_MATCH_COMPATIBLE_DOWNCAST;  // always loss of precision.
        else if (argfloat && !paramfloat)
        {
            if (argsize < paramsize)
                return DT_MATCH_COMPATIBLE_UPCAST;
            else
                return DT_MATCH_COMPATIBLE_DOWNCAST;  // loss of precision.
        } // else if
        else if (argsize == paramsize)
            return DT_MATCH_COMPATIBLE;
        else if (argsize < paramsize)
            return DT_MATCH_COMPATIBLE_UPCAST;
        else /* if (argsize > paramsize) */
            return DT_MATCH_COMPATIBLE_DOWNCAST;
    } // if

    return DT_MATCH_INCOMPATIBLE;
} // compatible_arg_datatype


static const MOJOSHADER_astDataType *type_check_ast(Context *ctx, void *_ast);

// !!! FIXME: this function sucks.
static const MOJOSHADER_astDataType *match_func_to_call(Context *ctx,
                                    MOJOSHADER_astExpressionCallFunction *ast)
{
    SymbolScope *best = NULL;  // best choice we find.
    int best_score = 0;
    MOJOSHADER_astExpressionIdentifier *ident = ast->identifier;
    const char *sym = ident->identifier;
    const void *value = NULL;
    void *iter = NULL;

    int argcount = 0;
    MOJOSHADER_astArguments *args = ast->args;
    while (args != NULL)
    {
        argcount++;
        type_check_ast(ctx, args->argument);
        args = args->next;
    } // while;

    // we do some tapdancing to handle function overloading here.
    int match = 0;
    while (hash_iter(ctx->variables.hash, sym, &value, &iter))
    {
        SymbolScope *item = (SymbolScope *) value;
        const MOJOSHADER_astDataType *dt = item->datatype;
        dt = reduce_datatype(ctx, dt);
        // there's a locally-scoped symbol with this name? It takes precedence.
        if (dt->type != MOJOSHADER_AST_DATATYPE_FUNCTION)
            return dt;

        const MOJOSHADER_astDataTypeFunction *dtfn = (MOJOSHADER_astDataTypeFunction *) dt;
        const int perfect = argcount * ((int) DT_MATCH_PERFECT);
        int score = 0;

        if (argcount == dtfn->num_params)  // !!! FIXME: default args.
        {
            args = ast->args;
            int i;
            for (i = 0; i < argcount; i++)
            {
                assert(args != NULL);
                dt = args->argument->datatype;
                args = args->next;
                const DatatypeMatch compatible = compatible_arg_datatype(ctx, dt, dtfn->params[i]);
                if (compatible == DT_MATCH_INCOMPATIBLE)
                {
                    args = NULL;
                    score = 0;
                    break;
                } // if

                score += (int) compatible;
            } // for

            if (args != NULL)
                score = 0;  // too many arguments supplied. No match.
        } // else

        if (score == 0)  // incompatible.
            continue;

        else if (score == perfect)  // perfection! stop looking!
        {
            match = 1;  // ignore all other compatible matches.
            best = item;
            break;
        } // if

        else if (score >= best_score)  // compatible, but not perfect, match.
        {
            if (score == best_score)
            {
                match++;
                // !!! FIXME: list each possible function in a fail(),
                // !!! FIXME:  but you can't actually fail() here, since
                // !!! FIXME:  this may cease to be ambiguous if we get
                // !!! FIXME:  a better match on a later overload.
            } // if

            else if (score > best_score)
            {
                match = 1;  // reset the ambiguousness count.
                best = item;
                best_score = score;
            } // if
        } // else if
    } // while

    if (match > 1)
    {
        assert(best != NULL);
        failf(ctx, "Ambiguous function call to '%s'", sym);
    } // if

    if (best == NULL)
    {
        assert(match == 0);
        assert(best_score == 0);
        // !!! FIXME: ident->datatype = ?
        failf(ctx, "No matching function named '%s'", sym);
    } // if
    else
    {
        ident->datatype = reduce_datatype(ctx, best->datatype);
        ident->index = best->index;
    } // else

    return ident->datatype;
} // match_func_to_call


static const MOJOSHADER_astDataType *vectype_from_base(Context *ctx,
                                            const MOJOSHADER_astDataType *base,
                                            const int len)
{
    assert(len > 0);
    assert(len <= 4);

    if (len == 1)  // return "float" and not "float1"
        return base;

    const char *typestr = NULL;
    switch (base->type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL: typestr = "bool"; break;
        case MOJOSHADER_AST_DATATYPE_INT: typestr = "int"; break;
        case MOJOSHADER_AST_DATATYPE_UINT: typestr = "uint"; break;
        case MOJOSHADER_AST_DATATYPE_HALF: typestr = "half"; break;
        case MOJOSHADER_AST_DATATYPE_FLOAT: typestr = "float"; break;
        case MOJOSHADER_AST_DATATYPE_DOUBLE: typestr = "double"; break;
        default: assert(0 && "This shouldn't happen"); break;
    } // switch

    char buf[32];
    snprintf(buf, sizeof (buf), "%s%d", typestr, len);
    const MOJOSHADER_astDataType *datatype = get_usertype(ctx, buf);
    assert(datatype != NULL);
    return datatype;
} // vectype_from_base


// Go through the AST and make sure all datatypes check out okay. For datatypes
//  that are compatible but are relying on an implicit cast, we add explicit
//  casts to the AST here, so further processing doesn't have to worry about
//  type coercion.
// For things that are incompatible, we generate errors and
//  then replace them with reasonable defaults so further processing can
//  continue (but code generation will be skipped due to errors).
// This means further processing can assume the AST is sane and not have to
//  spend effort verifying it again.
// This stage will also set every AST node's datatype field, if it is
//  meaningful to do so. This will allow conversion to IR to know what
//  type/size a given node is.
static const MOJOSHADER_astDataType *type_check_ast(Context *ctx, void *_ast)
{
    MOJOSHADER_astNode *ast = (MOJOSHADER_astNode *) _ast;
    const MOJOSHADER_astDataType *datatype = NULL;
    const MOJOSHADER_astDataType *datatype2 = NULL;
    const MOJOSHADER_astDataType *datatype3 = NULL;

    if ((!ast) || (ctx->out_of_memory))
        return NULL;

    // upkeep so we report correct error locations...
    ctx->sourcefile = ast->ast.filename;
    ctx->sourceline = ast->ast.line;

    switch (ast->ast.type)
    {
        case MOJOSHADER_AST_OP_POSTINCREMENT:
        case MOJOSHADER_AST_OP_POSTDECREMENT:
        case MOJOSHADER_AST_OP_PREINCREMENT:
        case MOJOSHADER_AST_OP_PREDECREMENT:
        case MOJOSHADER_AST_OP_COMPLEMENT:
        case MOJOSHADER_AST_OP_NEGATE:
            // !!! FIXME: must be lvalue.
            // !!! FIXME: bools must type-promote to ...int?
            // !!! FIXME: complement must not be float (...right?)
            datatype = type_check_ast(ctx, ast->unary.operand);
            require_numeric_datatype(ctx, datatype);
            ast->unary.datatype = datatype;
            return datatype;

        case MOJOSHADER_AST_OP_NOT:
            datatype = type_check_ast(ctx, ast->unary.operand);
            require_boolean_datatype(ctx, datatype);
            // !!! FIXME: coerce to bool here.
            ast->unary.datatype = &ctx->dt_bool;
            return datatype;

        case MOJOSHADER_AST_OP_DEREF_ARRAY:
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            require_integer_datatype(ctx, datatype2);
            add_type_coercion(ctx, NULL, &ctx->dt_int, &ast->binary.right, datatype2);

            datatype = reduce_datatype(ctx, datatype);
            if (datatype->type == MOJOSHADER_AST_DATATYPE_VECTOR)
            {
                // !!! FIXME: if constant int, fail if not 0 >= value <= vecsize.
                ast->binary.datatype = datatype->vector.base;
            } // if
            else if (datatype->type == MOJOSHADER_AST_DATATYPE_MATRIX)
            {
                // !!! FIXME: if constant int, fail if not 0 >= value <= rowsize (colsize?).
                ast->binary.datatype = vectype_from_base(ctx, datatype->matrix.base, datatype->matrix.columns);  // !!! FIXME: rows?
            }
            else
            {
                require_array_datatype(ctx, datatype);
                ast->binary.datatype = array_element_datatype(ctx, datatype);
            } // else

            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_DEREF_STRUCT:
        {
            const char *member = ast->derefstruct.member;
            datatype = type_check_ast(ctx, ast->derefstruct.identifier);
            const MOJOSHADER_astDataType *reduced = reduce_datatype(ctx, datatype);

            // Is this a swizzle and not a struct deref?
            if (reduced->type == MOJOSHADER_AST_DATATYPE_VECTOR)
            {
                const int veclen = reduced->vector.elements;
                ast->derefstruct.isswizzle = 1;
                if (!is_swizzle_str(member, veclen))
                {
                    fail(ctx, "invalid swizzle on vector");
                    // force this to be sane for further processing.
                    const char *sane_swiz = stringcache(ctx->strcache, "xyzw");
                    member = ast->derefstruct.member = sane_swiz;
                } // if

                const int swizlen = (int) strlen(member);
                if (swizlen != veclen)
                    datatype = vectype_from_base(ctx, reduced->vector.base, swizlen);

                ast->derefstruct.datatype = datatype;
                return ast->derefstruct.datatype;
            } // if

            // maybe this is an actual struct?
            // !!! FIXME: replace with an int or something if not.
            require_struct_datatype(ctx, reduced);

            // map member to datatype
            assert(ast->derefstruct.datatype == NULL);
            const MOJOSHADER_astDataTypeStructMember *mbrs = reduced->structure.members;
            int i;
            for (i = 0; i < reduced->structure.member_count; i++)
            {
                if (strcmp(mbrs[i].identifier, member) == 0)
                {
                    ast->derefstruct.datatype = mbrs[i].datatype;
                    ast->derefstruct.member_index = i;
                    break;
                } // if
            } // for

            if (ast->derefstruct.datatype == NULL)
            {
                // !!! FIXME: replace with an int or something.
                failf(ctx, "Struct has no member named '%s'", member);
            } // if

            return ast->derefstruct.datatype;
        } // case

        case MOJOSHADER_AST_OP_COMMA:
            // evaluate and throw away left, return right.
            type_check_ast(ctx, ast->binary.left);
            ast->binary.datatype = type_check_ast(ctx, ast->binary.right);
            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_MULTIPLY:
        case MOJOSHADER_AST_OP_DIVIDE:
        case MOJOSHADER_AST_OP_ADD:
        case MOJOSHADER_AST_OP_SUBTRACT:
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            require_numeric_datatype(ctx, datatype);
            require_numeric_datatype(ctx, datatype2);
            ast->binary.datatype = add_type_coercion(ctx, &ast->binary.left,
                                      datatype, &ast->binary.right, datatype2);
            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_LSHIFT:
        case MOJOSHADER_AST_OP_RSHIFT:
        case MOJOSHADER_AST_OP_MODULO:
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            require_integer_datatype(ctx, datatype);
            require_integer_datatype(ctx, datatype2);
            ast->binary.datatype = add_type_coercion(ctx, &ast->binary.left,
                                     datatype,  &ast->binary.right, datatype2);
            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_LESSTHAN:
        case MOJOSHADER_AST_OP_GREATERTHAN:
        case MOJOSHADER_AST_OP_LESSTHANOREQUAL:
        case MOJOSHADER_AST_OP_GREATERTHANOREQUAL:
        case MOJOSHADER_AST_OP_NOTEQUAL:
        case MOJOSHADER_AST_OP_EQUAL:
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            add_type_coercion(ctx, &ast->binary.left, datatype,
                              &ast->binary.right, datatype2);
            ast->binary.datatype = &ctx->dt_bool;
            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_BINARYAND:
        case MOJOSHADER_AST_OP_BINARYXOR:
        case MOJOSHADER_AST_OP_BINARYOR:
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            require_integer_datatype(ctx, datatype);
            require_integer_datatype(ctx, datatype2);
            ast->binary.datatype = add_type_coercion(ctx, &ast->binary.left,
                                      datatype, &ast->binary.right, datatype2);
            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_LOGICALAND:
        case MOJOSHADER_AST_OP_LOGICALOR:
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            require_boolean_datatype(ctx, datatype);
            require_boolean_datatype(ctx, datatype2);
            // !!! FIXME: coerce each to bool here, separately.
            add_type_coercion(ctx, &ast->binary.left, datatype,
                              &ast->binary.right, datatype2);
            ast->binary.datatype = &ctx->dt_bool;

        case MOJOSHADER_AST_OP_ASSIGN:
        case MOJOSHADER_AST_OP_MULASSIGN:
        case MOJOSHADER_AST_OP_DIVASSIGN:
        case MOJOSHADER_AST_OP_MODASSIGN:
        case MOJOSHADER_AST_OP_ADDASSIGN:
        case MOJOSHADER_AST_OP_SUBASSIGN:
        case MOJOSHADER_AST_OP_LSHIFTASSIGN:
        case MOJOSHADER_AST_OP_RSHIFTASSIGN:
        case MOJOSHADER_AST_OP_ANDASSIGN:
        case MOJOSHADER_AST_OP_XORASSIGN:
        case MOJOSHADER_AST_OP_ORASSIGN:
            // !!! FIXME: verify binary.left is an lvalue, or fail()!
            datatype = type_check_ast(ctx, ast->binary.left);
            datatype2 = type_check_ast(ctx, ast->binary.right);
            ast->binary.datatype = add_type_coercion(ctx, NULL, datatype,
                                                &ast->binary.right, datatype2);
            return ast->binary.datatype;

        case MOJOSHADER_AST_OP_CONDITIONAL:
            datatype = type_check_ast(ctx, ast->ternary.left);
            datatype2 = type_check_ast(ctx, ast->ternary.center);
            datatype3 = type_check_ast(ctx, ast->ternary.right);
            require_numeric_datatype(ctx, datatype);
            ast->ternary.datatype = add_type_coercion(ctx, &ast->ternary.center,
                                    datatype2, &ast->ternary.right, datatype3);
            return ast->ternary.datatype;

        case MOJOSHADER_AST_OP_IDENTIFIER:
            datatype = find_variable(ctx, ast->identifier.identifier, &ast->identifier.index);
            if (datatype == NULL)
            {
                fail(ctx, "Unknown identifier");
                // !!! FIXME: replace with a sane default, move on.
                datatype = &ctx->dt_int;
            } // if
            ast->identifier.datatype = datatype;
            return ast->identifier.datatype;

        case MOJOSHADER_AST_OP_INT_LITERAL:
        case MOJOSHADER_AST_OP_FLOAT_LITERAL:
        case MOJOSHADER_AST_OP_STRING_LITERAL:
        case MOJOSHADER_AST_OP_BOOLEAN_LITERAL:
            assert(ast->expression.datatype != NULL);
            return ast->expression.datatype;  // already set up during parsing.

        case MOJOSHADER_AST_ARGUMENTS:
            assert(0 && "Should be done by MOJOSHADER_AST_OP_CALLFUNC/CONSTRUCTOR");
            return NULL;

        case MOJOSHADER_AST_OP_CALLFUNC:
        {
            datatype = match_func_to_call(ctx, &ast->callfunc);
            const MOJOSHADER_astDataType *reduced = reduce_datatype(ctx, datatype);
            // !!! FIXME: replace AST node with an int if this isn't a func.
            if (!require_function_datatype(ctx, reduced))
            {
                ast->callfunc.datatype = &ctx->dt_int;
                return ast->callfunc.datatype;
            } // if

            MOJOSHADER_astArguments *arg = ast->callfunc.args;
            int i;
            for (i = 0; i < reduced->function.num_params; i++)
            {
                if (arg == NULL)  // !!! FIXME: check for default parameters, fill them in.
                {
                    fail(ctx, "Too few arguments");
                    // !!! FIXME: replace AST here.
                    break;
                } // if
                datatype2 = arg->argument->datatype;  // already type-checked.
                add_type_coercion(ctx, NULL, reduced->function.params[i],
                                  &arg->argument, datatype2);
                arg = arg->next;
            } // for

            assert(arg == NULL);  // shouldn't have chosen func if too many args.

            ast->callfunc.datatype = reduced->function.retval;
            return ast->callfunc.datatype;
        } // case

        case MOJOSHADER_AST_OP_CONSTRUCTOR:
        {
            const MOJOSHADER_astDataType *reduced = reduce_datatype(ctx, ast->constructor.datatype);
            const MOJOSHADER_astDataType *base_dt = reduced;
            int num_params = 1;

            assert(reduced != NULL);
            switch (reduced->type)
            {
                case MOJOSHADER_AST_DATATYPE_VECTOR:
                    num_params = reduced->vector.elements;
                    base_dt = reduced->vector.base;
                    break;
                case MOJOSHADER_AST_DATATYPE_MATRIX:
                    num_params = reduced->matrix.rows * reduced->matrix.columns;
                    base_dt = reduced->matrix.base;
                    break;

                case MOJOSHADER_AST_DATATYPE_BOOL:
                case MOJOSHADER_AST_DATATYPE_INT:
                case MOJOSHADER_AST_DATATYPE_UINT:
                case MOJOSHADER_AST_DATATYPE_FLOAT:
                case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM:
                case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM:
                case MOJOSHADER_AST_DATATYPE_HALF:
                case MOJOSHADER_AST_DATATYPE_DOUBLE:
                case MOJOSHADER_AST_DATATYPE_STRING:
                    num_params = 1;
                    break;

                // !!! FIXME: can you construct a MOJOSHADER_AST_DATATYPE_STRUCT?
                // !!! FIXME: can you construct a MOJOSHADER_AST_DATATYPE_ARRAY?
                // !!! FIXME: can you construct a MOJOSHADER_AST_DATATYPE_BUFFER?

                default:
                    fail(ctx, "Invalid type for constructor");
                    delete_arguments(ctx, ast->constructor.args);
                    ast->constructor.args = new_argument(ctx, new_literal_int_expr(ctx, 0));
                    ast->constructor.datatype = &ctx->dt_int;
                    return ast->constructor.datatype;
            } // switch

            assert(num_params > 0);

            MOJOSHADER_astArguments *arg = ast->constructor.args;
            MOJOSHADER_astArguments *prev = NULL;
            int i;
            for (i = 0; i < num_params; i++)
            {
                if (arg == NULL)  // !!! FIXME: check for default parameters.
                {
                    fail(ctx, "Too few arguments");
                    // !!! FIXME: replace AST here.
                    break;
                } // if
                datatype2 = type_check_ast(ctx, arg->argument);

                // "float4(float3(1,2,3),4)" is legal, so we need to see if
                //  we're a vector, and jump that number of parameters instead
                //  of doing type coercion.
                reduced = reduce_datatype(ctx, datatype2);
                if (reduced->type == MOJOSHADER_AST_DATATYPE_VECTOR)
                {
                    // make sure things like float4(half3(1,2,3),1) convert that half3 to float3.
                    const int count = reduced->vector.elements;
                    datatype3 = vectype_from_base(ctx, base_dt, count);
                    add_type_coercion(ctx, NULL, datatype3, &arg->argument, datatype2);
                    i += count - 1;
                } // else
                else
                {
                    add_type_coercion(ctx, NULL, base_dt, &arg->argument, datatype2);
                } // else
                prev = arg;
                arg = arg->next;
            } // for

            if (arg != NULL)
            {
                fail(ctx, "Too many arguments");
                // Process extra arguments then chop them out.
                MOJOSHADER_astArguments *argi;
                for (argi = arg; argi != NULL; argi = argi->next)
                    type_check_ast(ctx, argi->argument);
                if (prev != NULL)
                    prev->next = NULL;
                delete_arguments(ctx, arg);
            } // if

            return ast->constructor.datatype;
        } // case

        case MOJOSHADER_AST_OP_CAST:
            datatype = sanitize_datatype(ctx, ast->cast.datatype);
            datatype2 = type_check_ast(ctx, ast->cast.operand);
            // you still need type coercion, since you could do a wrong cast,
            //  like "int x = (short) mychar;"
            add_type_coercion(ctx, NULL, datatype, &ast->cast.operand, datatype2);
            return datatype;

        case MOJOSHADER_AST_STATEMENT_BREAK:
            if ((ctx->loop_count == 0) && (ctx->switch_count == 0))
                fail(ctx, "Break outside loop or switch");
            // !!! FIXME: warn if unreachable statements follow?
            type_check_ast(ctx, ast->stmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_CONTINUE:
            if (ctx->loop_count == 0)
                fail(ctx, "Continue outside loop");
            // !!! FIXME: warn if unreachable statements follow?
            type_check_ast(ctx, ast->stmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_DISCARD:
            // !!! FIXME: warn if unreachable statements follow?
            type_check_ast(ctx, ast->stmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_EMPTY:
            type_check_ast(ctx, ast->stmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_EXPRESSION:
            // !!! FIXME: warn about expressions without a side-effect here?
            type_check_ast(ctx, ast->exprstmt.expr);  // !!! FIXME: This is named badly...
            type_check_ast(ctx, ast->exprstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_IF:
            push_scope(ctx);  // new scope for "if ((int x = blah()) != 0)"
            type_check_ast(ctx, ast->ifstmt.expr);
            type_check_ast(ctx, ast->ifstmt.statement);
            pop_scope(ctx);
            type_check_ast(ctx, ast->ifstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_TYPEDEF:
            type_check_ast(ctx, ast->typedefstmt.type_info);
            type_check_ast(ctx, ast->typedefstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_SWITCH:
        {
            ctx->switch_count++;
            MOJOSHADER_astSwitchCases *cases = ast->switchstmt.cases;
            // !!! FIXME: expr must be POD (no structs, arrays, etc!).
            datatype = type_check_ast(ctx, ast->switchstmt.expr);
            while (cases)
            {
                // !!! FIXME: case must be POD (no structs, arrays, etc!).
                datatype2 = type_check_ast(ctx, cases->expr);
                add_type_coercion(ctx, NULL, datatype,
                                  &cases->expr, datatype2);
                type_check_ast(ctx, cases->statement);
                cases = cases->next;
            } // while
            ctx->switch_count--;
            type_check_ast(ctx, ast->switchstmt.next);
            return NULL;
        } // case

        case MOJOSHADER_AST_SWITCH_CASE:
            assert(0 && "Should be done by MOJOSHADER_AST_STATEMENT_SWITCH.");
            return NULL;

        case MOJOSHADER_AST_STATEMENT_STRUCT:
            type_check_ast(ctx, ast->structstmt.struct_info);
            type_check_ast(ctx, ast->structstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_VARDECL:
            type_check_ast(ctx, ast->vardeclstmt.declaration);
            type_check_ast(ctx, ast->vardeclstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_BLOCK:
            push_scope(ctx);  // new vars declared here live until '}'.
            type_check_ast(ctx, ast->blockstmt.statements);
            pop_scope(ctx);
            type_check_ast(ctx, ast->blockstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_FOR:
            ctx->loop_count++;
            push_scope(ctx);  // new scope for "for (int x = 0; ...)"
            type_check_ast(ctx, ast->forstmt.var_decl);
            type_check_ast(ctx, ast->forstmt.initializer);
            type_check_ast(ctx, ast->forstmt.looptest);
            type_check_ast(ctx, ast->forstmt.counter);
            type_check_ast(ctx, ast->forstmt.statement);
            pop_scope(ctx);
            ctx->loop_count--;
            type_check_ast(ctx, ast->forstmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_DO:
            ctx->loop_count++;
            // !!! FIXME: should there be a push_scope() here?
            type_check_ast(ctx, ast->dostmt.statement);
            push_scope(ctx);  // new scope for "while ((int x = blah()) != 0)"
            type_check_ast(ctx, ast->dostmt.expr);
            pop_scope(ctx);
            ctx->loop_count--;
            type_check_ast(ctx, ast->dostmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_WHILE:
            ctx->loop_count++;
            push_scope(ctx);  // new scope for "while ((int x = blah()) != 0)"
            type_check_ast(ctx, ast->whilestmt.expr);
            type_check_ast(ctx, ast->whilestmt.statement);
            pop_scope(ctx);
            ctx->loop_count--;
            type_check_ast(ctx, ast->whilestmt.next);
            return NULL;

        case MOJOSHADER_AST_STATEMENT_RETURN:
            // !!! FIXME: type coercion to outer function's return type.
            // !!! FIXME: warn if unreachable statements follow?
            type_check_ast(ctx, ast->returnstmt.expr);
            type_check_ast(ctx, ast->returnstmt.next);
            return NULL;

        case MOJOSHADER_AST_COMPUNIT_FUNCTION:
            assert(!ctx->is_func_scope);

            // We have to tapdance here to make sure the function is in
            //  the global scope, but it's parameters are pushed as variables
            //  in the function's scope.
            datatype = type_check_ast(ctx, ast->funcunit.declaration);
            ast->funcunit.index = push_function(ctx,
                                ast->funcunit.declaration->identifier,
                                datatype, ast->funcunit.definition == NULL);

            // not just a declaration, but a full function definition?
            if (ast->funcunit.definition != NULL)
            {
                assert(ctx->loop_count == 0);
                assert(ctx->switch_count == 0);
                ctx->is_func_scope = 1;
                ctx->var_index = 0;  // reset this every function.
                push_scope(ctx);  // so function params are in function scope.
                // repush the parameters before checking the actual function.
                MOJOSHADER_astFunctionParameters *param;
                for (param = ast->funcunit.declaration->params; param; param = param->next)
                    push_variable(ctx, param->identifier, param->datatype);
                type_check_ast(ctx, ast->funcunit.definition);
                pop_scope(ctx);
                ctx->is_func_scope = 0;
                assert(ctx->loop_count == 0);
                assert(ctx->switch_count == 0);
            } // else

            type_check_ast(ctx, ast->funcunit.next);
            return NULL;

        case MOJOSHADER_AST_COMPUNIT_TYPEDEF:
            type_check_ast(ctx, ast->typedefunit.type_info);
            type_check_ast(ctx, ast->typedefunit.next);
            return NULL;

        case MOJOSHADER_AST_COMPUNIT_STRUCT:
            type_check_ast(ctx, ast->structunit.struct_info);
            type_check_ast(ctx, ast->structunit.next);
            return NULL;

        case MOJOSHADER_AST_COMPUNIT_VARIABLE:
            type_check_ast(ctx, ast->varunit.declaration);
            type_check_ast(ctx, ast->varunit.next);
            return NULL;

        case MOJOSHADER_AST_SCALAR_OR_ARRAY:
            assert(0 && "Should be done by other AST nodes.");
            return NULL;

        case MOJOSHADER_AST_TYPEDEF:
        {
            MOJOSHADER_astScalarOrArray *soa = ast->typdef.details;
            datatype = get_usertype(ctx, soa->identifier);
            if (datatype != NULL)
            {
                fail(ctx, "typedef already defined");
                ast->typdef.datatype = datatype;
                return datatype;
            } // if

            datatype = build_datatype(ctx, ast->typdef.isconst,
                                      ast->typdef.datatype, soa);
            if (datatype == NULL)
                return NULL;  // out of memory?

            push_usertype(ctx, soa->identifier, datatype);
            ast->typdef.datatype = datatype;
            return ast->typdef.datatype;
        } // case

        case MOJOSHADER_AST_FUNCTION_PARAMS:
            assert(0 && "Should be done by MOJOSHADER_AST_FUNCTION_SIGNATURE");

        case MOJOSHADER_AST_FUNCTION_SIGNATURE:
        {
            MOJOSHADER_astFunctionParameters *param;
            const MOJOSHADER_astDataType *dtparams[64];

            int i = 0;
            for (param = ast->funcsig.params; param; param = param->next)
            {
                assert(i <= STATICARRAYLEN(dtparams));  // laziness.
                sanitize_datatype(ctx, param->datatype);
                if (param->initializer != NULL)
                {
                    datatype2 = type_check_ast(ctx, param->initializer);
                    add_type_coercion(ctx, NULL, param->datatype,
                                      &param->initializer, datatype2);
                } // if
                dtparams[i] = param->datatype;
                i++;
            } // for

            ast->funcsig.datatype = build_function_datatype(ctx,
                                                        ast->funcsig.datatype,
                                                        i, dtparams, 0);
            return ast->funcsig.datatype;
        } // case

        case MOJOSHADER_AST_STRUCT_DECLARATION:
        {
            // !!! FIXME: We don't handle struct predeclaration at all right now
            // !!! FIXME:  (neither does the grammar)...not only does that mean
            // !!! FIXME:  you need to know the struct definition up front, but
            // !!! FIXME:  you can't do "struct XXX *next;" for a self-referencing
            // !!! FIXME:  linked list struct thing. This probably isn't a big
            // !!! FIXME:  deal, as there aren't (CURRENTLY!) pointers in HLSL,
            // !!! FIXME:  but you never know.

            const MOJOSHADER_astStructMembers *mbrs;

            // !!! FIXME: count this during parsing?
            int count = 0;
            mbrs = ast->structdecl.members;
            while (mbrs != NULL)
            {
                count++;
                mbrs = mbrs->next;
            } // while

            // !!! FIXME: this is hacky.
            MOJOSHADER_astDataTypeStructMember *dtmbrs;
            void *ptr = Malloc(ctx, sizeof (*dtmbrs) * count);
            if (ptr == NULL)
                return NULL;
            if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr)))
            {
                Free(ctx, ptr);
                return NULL;
            } // if
            dtmbrs = (MOJOSHADER_astDataTypeStructMember *) ptr;

            ptr = Malloc(ctx, sizeof (MOJOSHADER_astDataType));
            if (ptr == NULL)
                return NULL;
            if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr)))
            {
                Free(ctx, ptr);
                return NULL;
            } // if
            MOJOSHADER_astDataType *dt = (MOJOSHADER_astDataType *) ptr;

            mbrs = ast->structdecl.members;
            int i;
            for (i = 0; i < count; i++)
            {
                // !!! FIXME: current grammar forbids const keyword on struct members!
                dtmbrs[i].datatype = build_datatype(ctx, 0, mbrs->datatype, mbrs->details);
                dtmbrs[i].identifier = mbrs->details->identifier;  // cached!
                mbrs = mbrs->next;
            } // for

            dt->structure.type = MOJOSHADER_AST_DATATYPE_STRUCT;
            dt->structure.members = dtmbrs;
            dt->structure.member_count = count;
            ast->structdecl.datatype = dt;

            // !!! FIXME: this shouldn't push for anonymous structs: "struct { int x; } myvar;"
            // !!! FIXME:  but right now, the grammar is wrong and requires a name for the struct.
            push_usertype(ctx, ast->structdecl.name, ast->structdecl.datatype);
            return ast->structdecl.datatype;
        } // case

        case MOJOSHADER_AST_STRUCT_MEMBER:
            assert(0 && "Should be done by MOJOSHADER_AST_STRUCT_DECLARATION.");
            return NULL;

        case MOJOSHADER_AST_VARIABLE_DECLARATION:
        {
            MOJOSHADER_astVariableDeclaration *decl = &ast->vardecl;

            // this is true now, but we'll fill in ->datatype no matter what.
            assert((decl->datatype && !decl->anonymous_datatype) ||
                   (!decl->datatype && decl->anonymous_datatype));

            // An anonymous struct? That AST node does the heavy lifting.
            if (decl->anonymous_datatype != NULL)
                datatype = type_check_ast(ctx, decl->anonymous_datatype);
            else
            {
                datatype = build_datatype(ctx, (decl->attributes & MOJOSHADER_AST_VARATTR_CONST) != 0,
                                          decl->datatype, decl->details);
            } // else

            while (decl != NULL)
            {
                decl->datatype = datatype;
                push_variable(ctx, decl->details->identifier, datatype);
                if (decl->initializer != NULL)
                {
                    datatype2 = type_check_ast(ctx, decl->initializer);
                    add_type_coercion(ctx, NULL, datatype, &decl->initializer, datatype2);
                } // if

                type_check_ast(ctx, decl->annotations);
                type_check_ast(ctx, decl->lowlevel);
                decl = decl->next;
            } // while

            return datatype;
        } // case

        case MOJOSHADER_AST_ANNOTATION:
        {
            MOJOSHADER_astAnnotations *anno = &ast->annotations;
            while (anno)
            {
                type_check_ast(ctx, anno->initializer);
                anno = anno->next;
            } // while
            return NULL;
        } // case

        case MOJOSHADER_AST_PACK_OFFSET:
        case MOJOSHADER_AST_VARIABLE_LOWLEVEL:
            return NULL;  // no-op (for now, at least).

        default:
            assert(0 && "unexpected type");
    } // switch

    return NULL;
} // type_check_ast


static inline void semantic_analysis(Context *ctx)
{
    type_check_ast(ctx, ctx->ast);
} // semantic_analysis

// !!! FIXME: isn't this a cut-and-paste of somewhere else?
static inline int64 strtoi64(const char *str, unsigned int len)
{
    int64 retval = 0;
    int64 mult = 1;
    int i = 0;

    while ((len) && (*str == ' '))
    {
        str++;
        len--;
    } // while

    if ((len) && (*str == '-'))
    {
        mult = -1;
        str++;
        len--;
    } // if

    while (i < len)
    {
        const char ch = str[i];
        if ((ch < '0') || (ch > '9'))
            break;
        i++;
    } // while

    while (--i >= 0)
    {
        const char ch = str[i];
        retval += ((int64) (ch - '0')) * mult;
        mult *= 10;
    } // while

    return retval;
} // strtoi64

// !!! FIXME: isn't this a cut-and-paste of somewhere else?
static inline double strtodouble(const char *_str, unsigned int len)
{
    // !!! FIXME: laziness prevails.
    char *str = (char *) alloca(len+1);
    memcpy(str, _str, len);
    str[len] = '\0';
    return strtod(str, NULL);
} // strtodouble

#if 0
// This does not check correctness (POSITIONT993842 passes, etc).
static int is_semantic(const Context *ctx, const char *token,
                       const unsigned int tokenlen)
{
    static const char *names[] = {
        "BINORMAL", "BLENDINDICES", "BLENDWEIGHT",
        "COLOR", "NORMAL", "POSITION", "POSITIONT", "PSIZE", "TANGENT",
        "TEXCOORD", "FOG", "TESSFACTOR", "TEXCOORD", "VFACE", "VPOS",
        "DEPTH", NULL
    };

    // !!! FIXME: DX10 has SV_* ("System Value Semantics").
    const char **i;
    for (i = names; *i; i++)
    {
        const char *name = *i;
        const size_t namelen = strlen(name);
        if (tokenlen < namelen)
            continue;
        else if (memcmp(token, name, namelen) != 0)
            continue;

        for (name += namelen; *name; name++)
        {
            if ((*name < '0') || (*name > '9'))
                break;
        } // for

        if (*name == '\0')
            return 1;
    } // for

    return 0;
} // is_semantic
#endif

static int convert_to_lemon_token(Context *ctx, const char *token,
                                  unsigned int tokenlen, const Token tokenval)
{
    switch (tokenval)
    {
        case ((Token) ','): return TOKEN_HLSL_COMMA;
        case ((Token) '='): return TOKEN_HLSL_ASSIGN;
        case ((Token) TOKEN_ADDASSIGN): return TOKEN_HLSL_ADDASSIGN;
        case ((Token) TOKEN_SUBASSIGN): return TOKEN_HLSL_SUBASSIGN;
        case ((Token) TOKEN_MULTASSIGN): return TOKEN_HLSL_MULASSIGN;
        case ((Token) TOKEN_DIVASSIGN): return TOKEN_HLSL_DIVASSIGN;
        case ((Token) TOKEN_MODASSIGN): return TOKEN_HLSL_MODASSIGN;
        case ((Token) TOKEN_LSHIFTASSIGN): return TOKEN_HLSL_LSHIFTASSIGN;
        case ((Token) TOKEN_RSHIFTASSIGN): return TOKEN_HLSL_RSHIFTASSIGN;
        case ((Token) TOKEN_ANDASSIGN): return TOKEN_HLSL_ANDASSIGN;
        case ((Token) TOKEN_ORASSIGN): return TOKEN_HLSL_ORASSIGN;
        case ((Token) TOKEN_XORASSIGN): return TOKEN_HLSL_XORASSIGN;
        case ((Token) '?'): return TOKEN_HLSL_QUESTION;
        case ((Token) TOKEN_OROR): return TOKEN_HLSL_OROR;
        case ((Token) TOKEN_ANDAND): return TOKEN_HLSL_ANDAND;
        case ((Token) '|'): return TOKEN_HLSL_OR;
        case ((Token) '^'): return TOKEN_HLSL_XOR;
        case ((Token) '&'): return TOKEN_HLSL_AND;
        case ((Token) TOKEN_EQL): return TOKEN_HLSL_EQL;
        case ((Token) TOKEN_NEQ): return TOKEN_HLSL_NEQ;
        case ((Token) '<'): return TOKEN_HLSL_LT;
        case ((Token) TOKEN_LEQ): return TOKEN_HLSL_LEQ;
        case ((Token) '>'): return TOKEN_HLSL_GT;
        case ((Token) TOKEN_GEQ): return TOKEN_HLSL_GEQ;
        case ((Token) TOKEN_LSHIFT): return TOKEN_HLSL_LSHIFT;
        case ((Token) TOKEN_RSHIFT): return TOKEN_HLSL_RSHIFT;
        case ((Token) '+'): return TOKEN_HLSL_PLUS;
        case ((Token) '-'): return TOKEN_HLSL_MINUS;
        case ((Token) '*'): return TOKEN_HLSL_STAR;
        case ((Token) '/'): return TOKEN_HLSL_SLASH;
        case ((Token) '%'): return TOKEN_HLSL_PERCENT;
        case ((Token) '!'): return TOKEN_HLSL_EXCLAMATION;
        case ((Token) '~'): return TOKEN_HLSL_COMPLEMENT;
        case ((Token) TOKEN_DECREMENT): return TOKEN_HLSL_MINUSMINUS;
        case ((Token) TOKEN_INCREMENT): return TOKEN_HLSL_PLUSPLUS;
        case ((Token) '.'): return TOKEN_HLSL_DOT;
        case ((Token) '['): return TOKEN_HLSL_LBRACKET;
        case ((Token) ']'): return TOKEN_HLSL_RBRACKET;
        case ((Token) '('): return TOKEN_HLSL_LPAREN;
        case ((Token) ')'): return TOKEN_HLSL_RPAREN;
        case ((Token) TOKEN_INT_LITERAL): return TOKEN_HLSL_INT_CONSTANT;
        case ((Token) TOKEN_FLOAT_LITERAL): return TOKEN_HLSL_FLOAT_CONSTANT;
        case ((Token) TOKEN_STRING_LITERAL): return TOKEN_HLSL_STRING_LITERAL;
        case ((Token) ':'): return TOKEN_HLSL_COLON;
        case ((Token) ';'): return TOKEN_HLSL_SEMICOLON;
        case ((Token) '{'): return TOKEN_HLSL_LBRACE;
        case ((Token) '}'): return TOKEN_HLSL_RBRACE;
        //case ((Token) TOKEN_PP_PRAGMA): return TOKEN_HLSL_PRAGMA;
        //case ((Token) '\n'): return TOKEN_HLSL_NEWLINE;

        case ((Token) TOKEN_IDENTIFIER):
            #define tokencmp(t) ((tokenlen == strlen(t)) && (memcmp(token, t, tokenlen) == 0))
            //case ((Token) ''): return TOKEN_HLSL_TYPECAST
            //if (tokencmp("")) return TOKEN_HLSL_TYPE_NAME
            //if (tokencmp("...")) return TOKEN_HLSL_ELIPSIS
            if (tokencmp("else")) return TOKEN_HLSL_ELSE;
            if (tokencmp("inline")) return TOKEN_HLSL_INLINE;
            if (tokencmp("void")) return TOKEN_HLSL_VOID;
            if (tokencmp("in")) return TOKEN_HLSL_IN;
            if (tokencmp("inout")) return TOKEN_HLSL_INOUT;
            if (tokencmp("out")) return TOKEN_HLSL_OUT;
            if (tokencmp("uniform")) return TOKEN_HLSL_UNIFORM;
            if (tokencmp("linear")) return TOKEN_HLSL_LINEAR;
            if (tokencmp("centroid")) return TOKEN_HLSL_CENTROID;
            if (tokencmp("nointerpolation")) return TOKEN_HLSL_NOINTERPOLATION;
            if (tokencmp("noperspective")) return TOKEN_HLSL_NOPERSPECTIVE;
            if (tokencmp("sample")) return TOKEN_HLSL_SAMPLE;
            if (tokencmp("struct")) return TOKEN_HLSL_STRUCT;
            if (tokencmp("typedef")) return TOKEN_HLSL_TYPEDEF;
            if (tokencmp("const")) return TOKEN_HLSL_CONST;
            if (tokencmp("packoffset")) return TOKEN_HLSL_PACKOFFSET;
            if (tokencmp("register")) return TOKEN_HLSL_REGISTER;
            if (tokencmp("extern")) return TOKEN_HLSL_EXTERN;
            if (tokencmp("shared")) return TOKEN_HLSL_SHARED;
            if (tokencmp("static")) return TOKEN_HLSL_STATIC;
            if (tokencmp("volatile")) return TOKEN_HLSL_VOLATILE;
            if (tokencmp("row_major")) return TOKEN_HLSL_ROWMAJOR;
            if (tokencmp("column_major")) return TOKEN_HLSL_COLUMNMAJOR;
            if (tokencmp("bool")) return TOKEN_HLSL_BOOL;
            if (tokencmp("int")) return TOKEN_HLSL_INT;
            if (tokencmp("uint")) return TOKEN_HLSL_UINT;
            if (tokencmp("half")) return TOKEN_HLSL_HALF;
            if (tokencmp("float")) return TOKEN_HLSL_FLOAT;
            if (tokencmp("double")) return TOKEN_HLSL_DOUBLE;
            if (tokencmp("string")) return TOKEN_HLSL_STRING;
            if (tokencmp("snorm")) return TOKEN_HLSL_SNORM;
            if (tokencmp("unorm")) return TOKEN_HLSL_UNORM;
            if (tokencmp("buffer")) return TOKEN_HLSL_BUFFER;
            if (tokencmp("vector")) return TOKEN_HLSL_VECTOR;
            if (tokencmp("matrix")) return TOKEN_HLSL_MATRIX;
            if (tokencmp("break")) return TOKEN_HLSL_BREAK;
            if (tokencmp("continue")) return TOKEN_HLSL_CONTINUE;
            if (tokencmp("discard")) return TOKEN_HLSL_DISCARD;
            if (tokencmp("return")) return TOKEN_HLSL_RETURN;
            if (tokencmp("while")) return TOKEN_HLSL_WHILE;
            if (tokencmp("for")) return TOKEN_HLSL_FOR;
            if (tokencmp("unroll")) return TOKEN_HLSL_UNROLL;
            if (tokencmp("loop")) return TOKEN_HLSL_LOOP;
            if (tokencmp("do")) return TOKEN_HLSL_DO;
            if (tokencmp("if")) return TOKEN_HLSL_IF;
            if (tokencmp("branch")) return TOKEN_HLSL_BRANCH;
            if (tokencmp("flatten")) return TOKEN_HLSL_FLATTEN;
            if (tokencmp("switch")) return TOKEN_HLSL_SWITCH;
            if (tokencmp("forcecase")) return TOKEN_HLSL_FORCECASE;
            if (tokencmp("call")) return TOKEN_HLSL_CALL;
            if (tokencmp("case")) return TOKEN_HLSL_CASE;
            if (tokencmp("default")) return TOKEN_HLSL_DEFAULT;
            if (tokencmp("sampler")) return TOKEN_HLSL_SAMPLER;
            if (tokencmp("sampler1D")) return TOKEN_HLSL_SAMPLER1D;
            if (tokencmp("sampler2D")) return TOKEN_HLSL_SAMPLER2D;
            if (tokencmp("sampler3D")) return TOKEN_HLSL_SAMPLER3D;
            if (tokencmp("samplerCUBE")) return TOKEN_HLSL_SAMPLERCUBE;
            if (tokencmp("sampler_state")) return TOKEN_HLSL_SAMPLER_STATE;
            if (tokencmp("SamplerState")) return TOKEN_HLSL_SAMPLERSTATE;
            if (tokencmp("true")) return TOKEN_HLSL_TRUE;
            if (tokencmp("false")) return TOKEN_HLSL_FALSE;
            if (tokencmp("SamplerComparisonState")) return TOKEN_HLSL_SAMPLERCOMPARISONSTATE;
            if (tokencmp("isolate")) return TOKEN_HLSL_ISOLATE;
            if (tokencmp("maxInstructionCount")) return TOKEN_HLSL_MAXINSTRUCTIONCOUNT;
            if (tokencmp("noExpressionOptimizations")) return TOKEN_HLSL_NOEXPRESSIONOPTIMIZATIONS;
            if (tokencmp("unused")) return TOKEN_HLSL_UNUSED;
            if (tokencmp("xps")) return TOKEN_HLSL_XPS;
            #undef tokencmp

            // get a canonical copy of the string now, as we'll need it.
            token = stringcache_len(ctx->strcache, token, tokenlen);
            if (get_usertype(ctx, token) != NULL)
                return TOKEN_HLSL_USERTYPE;
            return TOKEN_HLSL_IDENTIFIER;

        case TOKEN_EOI: return 0;
        default: assert(0 && "unexpected token from lexer\n"); return 0;
    } // switch

    return 0;
} // convert_to_lemon_token


static void delete_ir(Context *ctx, void *_ir);  // !!! FIXME: move this code around.

static void destroy_context(Context *ctx)
{
    if (ctx != NULL)
    {
        MOJOSHADER_free f = ((ctx->free != NULL) ? ctx->free : MOJOSHADER_internal_free);
        void *d = ctx->malloc_data;
        size_t i = 0;

        // !!! FIXME: this is kinda hacky.
        const size_t count = buffer_size(ctx->garbage) / sizeof (void *);
        if (count > 0)
        {
            void **garbage = (void **) buffer_flatten(ctx->garbage);
            if (garbage != NULL)
            {
                for (i = 0; i < count; i++)
                    f(garbage[i], d);
                f(garbage, d);
            } // if
        } // if
        buffer_destroy(ctx->garbage);

        delete_compilation_unit(ctx, (MOJOSHADER_astCompilationUnit*)ctx->ast);
        destroy_symbolmap(ctx, &ctx->usertypes);
        destroy_symbolmap(ctx, &ctx->variables);
        stringcache_destroy(ctx->strcache);
        errorlist_destroy(ctx->errors);
        errorlist_destroy(ctx->warnings);

        if (ctx->ir != NULL)
        {
            for (i = 0; i <= ctx->user_func_index; i++)
                delete_ir(ctx, ctx->ir[i]);
            f(ctx->ir, d);
        } // if

        // !!! FIXME: more to clean up here, now.

        f(ctx, d);
    } // if
} // destroy_context

static Context *build_context(MOJOSHADER_malloc m, MOJOSHADER_free f, void *d)
{
    if (!m) m = MOJOSHADER_internal_malloc;
    if (!f) f = MOJOSHADER_internal_free;

    Context *ctx = (Context *) m(sizeof (Context), d);
    if (ctx == NULL)
        return NULL;

    memset(ctx, '\0', sizeof (Context));
    ctx->malloc = m;
    ctx->free = f;
    ctx->malloc_data = d;
    //ctx->parse_phase = MOJOSHADER_PARSEPHASE_NOTSTARTED;
    create_symbolmap(ctx, &ctx->usertypes); // !!! FIXME: check for failure.
    create_symbolmap(ctx, &ctx->variables); // !!! FIXME: check for failure.
    ctx->strcache = stringcache_create(MallocBridge, FreeBridge, ctx);  // !!! FIXME: check for failure.
    ctx->errors = errorlist_create(MallocBridge, FreeBridge, ctx);  // !!! FIXME: check for failure.
    ctx->warnings = errorlist_create(MallocBridge, FreeBridge, ctx);  // !!! FIXME: check for failure.

    // !!! FIXME: this feels hacky.
    ctx->garbage = buffer_create(256*sizeof(void*),MallocBridge,FreeBridge,ctx);  // !!! FIXME: check for failure.

    ctx->dt_none.type = MOJOSHADER_AST_DATATYPE_NONE;
    ctx->dt_bool.type = MOJOSHADER_AST_DATATYPE_BOOL;
    ctx->dt_int.type = MOJOSHADER_AST_DATATYPE_INT;
    ctx->dt_uint.type = MOJOSHADER_AST_DATATYPE_UINT;
    ctx->dt_float.type = MOJOSHADER_AST_DATATYPE_FLOAT;
    ctx->dt_float_snorm.type = MOJOSHADER_AST_DATATYPE_FLOAT_SNORM;
    ctx->dt_float_unorm.type = MOJOSHADER_AST_DATATYPE_FLOAT_UNORM;
    ctx->dt_half.type = MOJOSHADER_AST_DATATYPE_HALF;
    ctx->dt_double.type = MOJOSHADER_AST_DATATYPE_DOUBLE;
    ctx->dt_string.type = MOJOSHADER_AST_DATATYPE_STRING;
    ctx->dt_sampler1d.type = MOJOSHADER_AST_DATATYPE_SAMPLER_1D;
    ctx->dt_sampler2d.type = MOJOSHADER_AST_DATATYPE_SAMPLER_2D;
    ctx->dt_sampler3d.type = MOJOSHADER_AST_DATATYPE_SAMPLER_3D;
    ctx->dt_samplercube.type = MOJOSHADER_AST_DATATYPE_SAMPLER_CUBE;
    ctx->dt_samplerstate.type = MOJOSHADER_AST_DATATYPE_SAMPLER_STATE;
    ctx->dt_samplercompstate.type = MOJOSHADER_AST_DATATYPE_SAMPLER_COMPARISON_STATE;

    #define INIT_DT_BUFFER(t) \
        ctx->dt_buf_##t.type = MOJOSHADER_AST_DATATYPE_BUFFER; \
        ctx->dt_buf_##t.buffer.base = &ctx->dt_##t;
    INIT_DT_BUFFER(bool);
    INIT_DT_BUFFER(int);
    INIT_DT_BUFFER(uint);
    INIT_DT_BUFFER(half);
    INIT_DT_BUFFER(float);
    INIT_DT_BUFFER(double);
    INIT_DT_BUFFER(float_snorm);
    INIT_DT_BUFFER(float_unorm);
    #undef INIT_DT_BUFFER

    return ctx;
} // build_context


// This macro salsa is kinda nasty, but it's the smallest, least error-prone
//  way I can find to do this well in C.  :/

#define ADD_INTRINSIC(fn, ret, params) do { \
    push_function(ctx, fn, \
        build_function_datatype(ctx, ret, STATICARRAYLEN(params), params, 1), \
        0); \
} while (0)

#define ADD_INTRINSIC_VECTOR(typestr, code) do { \
    const MOJOSHADER_astDataType *dt; \
    dt = get_usertype(ctx, typestr "1"); code; \
    dt = get_usertype(ctx, typestr "2"); code; \
    dt = get_usertype(ctx, typestr "3"); code; \
    dt = get_usertype(ctx, typestr "4"); code; \
} while (0)

#define ADD_INTRINSIC_VECTOR_FLOAT(code) { \
    ADD_INTRINSIC_VECTOR("float", code); \
    ADD_INTRINSIC_VECTOR("half", code); \
    ADD_INTRINSIC_VECTOR("double", code); \
}
#define ADD_INTRINSIC_VECTOR_INT(code) { \
    ADD_INTRINSIC_VECTOR("int", code); \
    ADD_INTRINSIC_VECTOR("uint", code); \
}
#define ADD_INTRINSIC_VECTOR_BOOL(code) { \
    ADD_INTRINSIC_VECTOR("bool", code); \
}

#define ADD_INTRINSIC_MATRIX(typestr, code) do { \
    const MOJOSHADER_astDataType *dt; \
    dt = get_usertype(ctx, typestr "1x1"); code; \
    dt = get_usertype(ctx, typestr "1x2"); code; \
    dt = get_usertype(ctx, typestr "1x3"); code; \
    dt = get_usertype(ctx, typestr "1x4"); code; \
    dt = get_usertype(ctx, typestr "2x1"); code; \
    dt = get_usertype(ctx, typestr "2x2"); code; \
    dt = get_usertype(ctx, typestr "2x3"); code; \
    dt = get_usertype(ctx, typestr "2x4"); code; \
    dt = get_usertype(ctx, typestr "3x1"); code; \
    dt = get_usertype(ctx, typestr "3x2"); code; \
    dt = get_usertype(ctx, typestr "3x3"); code; \
    dt = get_usertype(ctx, typestr "3x4"); code; \
    dt = get_usertype(ctx, typestr "4x1"); code; \
    dt = get_usertype(ctx, typestr "4x2"); code; \
    dt = get_usertype(ctx, typestr "4x3"); code; \
    dt = get_usertype(ctx, typestr "4x4"); code; \
} while (0)

#define ADD_INTRINSIC_MATRIX_FLOAT(code) { \
    ADD_INTRINSIC_MATRIX("float", code); \
    ADD_INTRINSIC_MATRIX("half", code); \
    ADD_INTRINSIC_MATRIX("double", code); \
}
#define ADD_INTRINSIC_MATRIX_INT(code) { \
    ADD_INTRINSIC_MATRIX("int", code); \
    ADD_INTRINSIC_MATRIX("uint", code); \
}
#define ADD_INTRINSIC_MATRIX_BOOL(code) { \
    ADD_INTRINSIC_MATRIX("bool", code); \
}

#define ADD_INTRINSIC_ANY(scalar, typestr, code) do { \
    { const MOJOSHADER_astDataType *dt = scalar; code; } \
    ADD_INTRINSIC_VECTOR(typestr, code); \
    ADD_INTRINSIC_MATRIX(typestr, code); \
} while (0)

#define ADD_INTRINSIC_ANY_FLOAT(code) do { \
    ADD_INTRINSIC_ANY(&ctx->dt_double, "double", code); \
    ADD_INTRINSIC_ANY(&ctx->dt_half, "half", code); \
    ADD_INTRINSIC_ANY(&ctx->dt_float, "float", code); \
} while (0)
#define ADD_INTRINSIC_ANY_INT(code) do { \
    ADD_INTRINSIC_ANY(&ctx->dt_uint, "uint", code); \
    ADD_INTRINSIC_ANY(&ctx->dt_int, "int", code); \
} while (0)

#define ADD_INTRINSIC_ANY_BOOL(code) ADD_INTRINSIC_ANY(&ctx->dt_bool, "bool", code)

static void add_intrinsic1(Context *ctx, const char *fn,
                           const MOJOSHADER_astDataType *ret,
                           const MOJOSHADER_astDataType *dt1)
{
    const MOJOSHADER_astDataType *params[] = { dt1 };
    ADD_INTRINSIC(fn, ret, params);
} // add_intrinsic1

static void add_intrinsic2(Context *ctx, const char *fn,
                           const MOJOSHADER_astDataType *ret,
                           const MOJOSHADER_astDataType *dt1,
                           const MOJOSHADER_astDataType *dt2)
{
    const MOJOSHADER_astDataType *params[] = { dt1, dt2 };
    ADD_INTRINSIC(fn, ret, params);
} // add_intrinsic2

static void add_intrinsic3(Context *ctx, const char *fn,
                           const MOJOSHADER_astDataType *ret,
                           const MOJOSHADER_astDataType *dt1,
                           const MOJOSHADER_astDataType *dt2,
                           const MOJOSHADER_astDataType *dt3)
{
    const MOJOSHADER_astDataType *params[] = { dt1, dt2, dt3 };
    ADD_INTRINSIC(fn, ret, params);
} // add_intrinsic3

static void add_intrinsic4(Context *ctx, const char *fn,
                           const MOJOSHADER_astDataType *ret,
                           const MOJOSHADER_astDataType *dt1,
                           const MOJOSHADER_astDataType *dt2,
                           const MOJOSHADER_astDataType *dt3,
                           const MOJOSHADER_astDataType *dt4)
{
    const MOJOSHADER_astDataType *params[] = { dt1, dt2, dt3, dt4 };
    ADD_INTRINSIC(fn, ret, params);
} // add_intrinsic4

// PLEASE NOTE that add_intrinsic*() is called AFTER the various
//  ADD_INTRINSIC_* macros, even though these look like functions that
//  should be called first. They might be called multiple times by the macro.
//  The variable "dt" is defined by the macro for use by your code.
static void add_intrinsic_SAME1_ANYf(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_FLOAT(add_intrinsic1(ctx, fn, dt, dt));
} // add_intrinsic_SAME1_ANYf

static void add_intrinsic_SAME1_ANYfi(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_INT(add_intrinsic1(ctx, fn, dt, dt));
    add_intrinsic_SAME1_ANYf(ctx, fn);
} // add_intrinsic_SAME1_ANYfi

static void add_intrinsic_BOOL_ANYf(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_FLOAT(add_intrinsic1(ctx, fn, &ctx->dt_bool, dt));
} // add_intrinsic_BOOL_ANYf

static void add_intrinsic_BOOL_ANYfib(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_BOOL(add_intrinsic1(ctx, fn, &ctx->dt_bool, dt));
    ADD_INTRINSIC_ANY_INT(add_intrinsic1(ctx, fn, &ctx->dt_bool, dt));
    add_intrinsic_BOOL_ANYf(ctx, fn);
} // add_intrinsic_BOOL_ANYfib

static void add_intrinsic_SAME1_ANYf_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_FLOAT(add_intrinsic2(ctx, fn, dt, dt, dt));
} // add_intrinsic_SAME1_ANYf_SAME1

static void add_intrinsic_SAME1_ANYfi_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_INT(add_intrinsic2(ctx, fn, dt, dt, dt));
    add_intrinsic_SAME1_ANYf_SAME1(ctx, fn);
} // add_intrinsic_SAME1_ANYfi_SAME1

static void add_intrinsic_SAME1_ANYf_SAME1_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_FLOAT(add_intrinsic3(ctx, fn, dt, dt, dt, dt));
} // add_intrinsic_SAME1_ANYf_SAME1_SAME1

static void add_intrinsic_SAME1_ANYfi_SAME1_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_INT(add_intrinsic3(ctx, fn, dt, dt, dt, dt));
    add_intrinsic_SAME1_ANYf_SAME1_SAME1(ctx, fn);
} // add_intrinsic_SAME1_ANYfi_SAME1_SAME1

static void add_intrinsic_SAME1_Mfib(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_MATRIX_BOOL(add_intrinsic1(ctx, fn, dt, dt));
    ADD_INTRINSIC_MATRIX_INT(add_intrinsic1(ctx, fn, dt, dt));
    ADD_INTRINSIC_MATRIX_FLOAT(add_intrinsic1(ctx, fn, dt, dt));
} // add_intrinsic_SAME1_Mfib

static void add_intrinsic_SAME1_Vf(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic1(ctx, fn, dt, dt));
} // add_intrinsic_SAME1_Vf

static void add_intrinsic_SAME1_Vf_SAME1_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic3(ctx, fn, dt, dt, dt, dt));
} // add_intrinsic_SAME1_Vf_SAME1_SAME1

static void add_intrinsic_SAME1_Vf_SAME1_f(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic3(ctx, fn, dt, dt, dt, dt->user.details->vector.base));
} // add_intrinsic_SAME1_Vf_SAME1_f

static void add_intrinsic_VOID_ANYf(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_FLOAT(add_intrinsic1(ctx, fn, NULL, dt));
} // add_intrinsic_VOID_ANYf

static void add_intrinsic_VOID_ANYf_SAME1_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_ANY_FLOAT(add_intrinsic3(ctx, fn, NULL, dt, dt, dt));
} // add_intrinsic_VOID_ANYf_SAME1_SAME1

static void add_intrinsic_f_SQUAREMATRIXf(Context *ctx, const char *fn)
{
    add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float1x1"));
    add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float2x2"));
    add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float3x3"));
    add_intrinsic1(ctx, fn, &ctx->dt_float, get_usertype(ctx, "float4x4"));
} // add_intrinsic_f_SQUAREMATRIXf

static void add_intrinsic_f_Vf(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic1(ctx, fn, dt->user.details->vector.base, dt));
} // add_intrinsic_f_Vf

static void add_intrinsic_fi_Vfi_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dt->user.details->vector.base, dt, dt));
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt->user.details->vector.base, dt, dt));
} // add_intrinsic_fi_Vfi_SAME1

static void add_intrinsic_f_Vf_SAME1(Context *ctx, const char *fn)
{
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt->user.details->vector.base, dt, dt));
} // add_intrinsic_f_Vf_SAME1

static void add_intrinsic_3f_3f_3f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float3");
    add_intrinsic2(ctx, fn, dt, dt, dt);
} // add_intrinsic_3f_3f_3f

static void add_intrinsic_4f_f_f_f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f = &ctx->dt_float;
    add_intrinsic3(ctx, fn, f4, f, f, f);
} // add_intrinsic_4f_f_f_f

static void add_intrinsic_4f_s1_4f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float4");
    add_intrinsic2(ctx, fn, dt, &ctx->dt_sampler1d, dt);
} // add_intrinsic_4f_s1_4f

static void add_intrinsic_4f_s1_f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float4");
    add_intrinsic2(ctx, fn, dt, &ctx->dt_sampler1d, &ctx->dt_float);
} // add_intrinsic_4f_s1_f

static void add_intrinsic_4f_s1_f_f_f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *dt = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f = &ctx->dt_float;
    add_intrinsic4(ctx, fn, dt, &ctx->dt_sampler1d, f, f, f);
} // add_intrinsic_4f_s1_f_f_f

static void add_intrinsic_4f_s2_2f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f2 = get_usertype(ctx, "float2");
    add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler2d, f2);
} // add_intrinsic_4f_s2_2f

static void add_intrinsic_4f_s2_2f_2f_2f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f2 = get_usertype(ctx, "float2");
    add_intrinsic4(ctx, fn, f4, &ctx->dt_sampler2d, f2, f2, f2);
} // add_intrinsic_4f_s2_2f_2f_2f

static void add_intrinsic_4f_s2_4f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler2d, f4);
} // add_intrinsic_4f_s2_4f

static void add_intrinsic_4f_s3_3f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3");
    add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler3d, f3);
} // add_intrinsic_4f_s3_3f

static void add_intrinsic_4f_s3_3f_3f_3f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3");
    add_intrinsic4(ctx, fn, f4, &ctx->dt_sampler3d, f3, f3, f3);
} // add_intrinsic_4f_s3_3f_3f_3f

static void add_intrinsic_4f_s3_4f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    add_intrinsic2(ctx, fn, f4, &ctx->dt_sampler3d, f4);
} // add_intrinsic_4f_s3_4f

static void add_intrinsic_4f_sc_3f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3");
    add_intrinsic2(ctx, fn, f4, &ctx->dt_samplercube, f3);
} // add_intrinsic_4f_sc_3f

static void add_intrinsic_4f_sc_3f_3f_3f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3");
    add_intrinsic4(ctx, fn, f4, &ctx->dt_samplercube, f3, f3, f3);
} // add_intrinsic_4f_sc_3f_3f_3f

static void add_intrinsic_4f_sc_4f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    add_intrinsic2(ctx, fn, f4, &ctx->dt_samplercube, f4);
} // add_intrinsic_4f_sc_4f

static void add_intrinsic_4i_4f(Context *ctx, const char *fn)
{
    const MOJOSHADER_astDataType *i4 = get_usertype(ctx, "int4");
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    add_intrinsic1(ctx, fn, i4, f4);
} // add_intrinsic_4i_4f

static void add_intrinsic_mul(Context *ctx, const char *fn)
{
    // mul() is nasty, since there's a bunch of overloads that aren't just
    //  related to vector size.
    // !!! FIXME: needs half, double, uint...
    const MOJOSHADER_astDataType *dtf = &ctx->dt_float;
    const MOJOSHADER_astDataType *dti = &ctx->dt_int;
    const MOJOSHADER_astDataType *f1 = get_usertype(ctx, "float1");
    const MOJOSHADER_astDataType *f2 = get_usertype(ctx, "float2");
    const MOJOSHADER_astDataType *f3 = get_usertype(ctx, "float3");
    const MOJOSHADER_astDataType *f4 = get_usertype(ctx, "float4");
    const MOJOSHADER_astDataType *i1 = get_usertype(ctx, "int1");
    const MOJOSHADER_astDataType *i2 = get_usertype(ctx, "int2");
    const MOJOSHADER_astDataType *i3 = get_usertype(ctx, "int3");
    const MOJOSHADER_astDataType *i4 = get_usertype(ctx, "int4");
    const MOJOSHADER_astDataType *f1x1 = get_usertype(ctx, "float1x1");
    const MOJOSHADER_astDataType *f1x2 = get_usertype(ctx, "float1x2");
    const MOJOSHADER_astDataType *f1x3 = get_usertype(ctx, "float1x3");
    const MOJOSHADER_astDataType *f1x4 = get_usertype(ctx, "float1x4");
    const MOJOSHADER_astDataType *f2x1 = get_usertype(ctx, "float2x1");
    const MOJOSHADER_astDataType *f2x2 = get_usertype(ctx, "float2x2");
    const MOJOSHADER_astDataType *f2x3 = get_usertype(ctx, "float2x3");
    const MOJOSHADER_astDataType *f2x4 = get_usertype(ctx, "float2x4");
    const MOJOSHADER_astDataType *f3x1 = get_usertype(ctx, "float3x1");
    const MOJOSHADER_astDataType *f3x2 = get_usertype(ctx, "float3x2");
    const MOJOSHADER_astDataType *f3x3 = get_usertype(ctx, "float3x3");
    const MOJOSHADER_astDataType *f3x4 = get_usertype(ctx, "float3x4");
    const MOJOSHADER_astDataType *f4x1 = get_usertype(ctx, "float4x1");
    const MOJOSHADER_astDataType *f4x2 = get_usertype(ctx, "float4x2");
    const MOJOSHADER_astDataType *f4x3 = get_usertype(ctx, "float4x3");
    const MOJOSHADER_astDataType *f4x4 = get_usertype(ctx, "float4x4");
    const MOJOSHADER_astDataType *i1x1 = get_usertype(ctx, "int1x1");
    const MOJOSHADER_astDataType *i1x2 = get_usertype(ctx, "int1x2");
    const MOJOSHADER_astDataType *i1x3 = get_usertype(ctx, "int1x3");
    const MOJOSHADER_astDataType *i1x4 = get_usertype(ctx, "int1x4");
    const MOJOSHADER_astDataType *i2x1 = get_usertype(ctx, "int2x1");
    const MOJOSHADER_astDataType *i2x2 = get_usertype(ctx, "int2x2");
    const MOJOSHADER_astDataType *i2x3 = get_usertype(ctx, "int2x3");
    const MOJOSHADER_astDataType *i2x4 = get_usertype(ctx, "int2x4");
    const MOJOSHADER_astDataType *i3x1 = get_usertype(ctx, "int3x1");
    const MOJOSHADER_astDataType *i3x2 = get_usertype(ctx, "int3x2");
    const MOJOSHADER_astDataType *i3x3 = get_usertype(ctx, "int3x3");
    const MOJOSHADER_astDataType *i3x4 = get_usertype(ctx, "int3x4");
    const MOJOSHADER_astDataType *i4x1 = get_usertype(ctx, "int4x1");
    const MOJOSHADER_astDataType *i4x2 = get_usertype(ctx, "int4x2");
    const MOJOSHADER_astDataType *i4x3 = get_usertype(ctx, "int4x3");
    const MOJOSHADER_astDataType *i4x4 = get_usertype(ctx, "int4x4");

    // scalar * scalar
    add_intrinsic2(ctx, fn, dti, dti, dti);
    add_intrinsic2(ctx, fn, dtf, dtf, dtf);

    // scalar * vector
    ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dt, dti, dt));
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt, dtf, dt));

    // scalar * matrix
    ADD_INTRINSIC_MATRIX_INT(add_intrinsic2(ctx, fn, dt, dti, dt));
    ADD_INTRINSIC_MATRIX_FLOAT(add_intrinsic2(ctx, fn, dt, dtf, dt));

    // vector * scalar
    ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dt, dt, dti));
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dt, dt, dtf));

    // vector * vector
    ADD_INTRINSIC_VECTOR_INT(add_intrinsic2(ctx, fn, dti, dt, dt));
    ADD_INTRINSIC_VECTOR_FLOAT(add_intrinsic2(ctx, fn, dtf, dt, dt));

    // vector * matrix
    add_intrinsic2(ctx, fn, i1, i1, i1x1);
    add_intrinsic2(ctx, fn, i2, i1, i1x2);
    add_intrinsic2(ctx, fn, i3, i1, i1x3);
    add_intrinsic2(ctx, fn, i4, i1, i1x4);
    add_intrinsic2(ctx, fn, i1, i2, i2x1);
    add_intrinsic2(ctx, fn, i2, i2, i2x2);
    add_intrinsic2(ctx, fn, i3, i2, i2x3);
    add_intrinsic2(ctx, fn, i4, i2, i2x4);
    add_intrinsic2(ctx, fn, i1, i3, i3x1);
    add_intrinsic2(ctx, fn, i2, i3, i3x2);
    add_intrinsic2(ctx, fn, i3, i3, i3x3);
    add_intrinsic2(ctx, fn, i4, i3, i3x4);
    add_intrinsic2(ctx, fn, i1, i4, i4x1);
    add_intrinsic2(ctx, fn, i2, i4, i4x2);
    add_intrinsic2(ctx, fn, i3, i4, i4x3);
    add_intrinsic2(ctx, fn, i4, i4, i4x4);
    add_intrinsic2(ctx, fn, f1, f1, f1x1);
    add_intrinsic2(ctx, fn, f2, f1, f1x2);
    add_intrinsic2(ctx, fn, f3, f1, f1x3);
    add_intrinsic2(ctx, fn, f4, f1, f1x4);
    add_intrinsic2(ctx, fn, f1, f2, f2x1);
    add_intrinsic2(ctx, fn, f2, f2, f2x2);
    add_intrinsic2(ctx, fn, f3, f2, f2x3);
    add_intrinsic2(ctx, fn, f4, f2, f2x4);
    add_intrinsic2(ctx, fn, f1, f3, f3x1);
    add_intrinsic2(ctx, fn, f2, f3, f3x2);
    add_intrinsic2(ctx, fn, f3, f3, f3x3);
    add_intrinsic2(ctx, fn, f4, f3, f3x4);
    add_intrinsic2(ctx, fn, f1, f4, f4x1);
    add_intrinsic2(ctx, fn, f2, f4, f4x2);
    add_intrinsic2(ctx, fn, f3, f4, f4x3);
    add_intrinsic2(ctx, fn, f4, f4, f4x4);

    // matrix * scalar
    ADD_INTRINSIC_MATRIX_INT(add_intrinsic2(ctx, fn, dt, dt, dti));
    ADD_INTRINSIC_MATRIX_FLOAT(add_intrinsic2(ctx, fn, dt, dt, dtf));

    // matrix * vector
    add_intrinsic2(ctx, fn, i1, i1x1, i1);
    add_intrinsic2(ctx, fn, i1, i1x2, i2);
    add_intrinsic2(ctx, fn, i1, i1x3, i3);
    add_intrinsic2(ctx, fn, i1, i1x4, i4);
    add_intrinsic2(ctx, fn, i2, i2x1, i1);
    add_intrinsic2(ctx, fn, i2, i2x2, i2);
    add_intrinsic2(ctx, fn, i2, i2x3, i3);
    add_intrinsic2(ctx, fn, i2, i2x4, i4);
    add_intrinsic2(ctx, fn, i3, i3x1, i1);
    add_intrinsic2(ctx, fn, i3, i3x2, i2);
    add_intrinsic2(ctx, fn, i3, i3x3, i3);
    add_intrinsic2(ctx, fn, i3, i3x4, i4);
    add_intrinsic2(ctx, fn, i4, i4x1, i1);
    add_intrinsic2(ctx, fn, i4, i4x2, i2);
    add_intrinsic2(ctx, fn, i4, i4x3, i3);
    add_intrinsic2(ctx, fn, i4, i4x4, i4);
    add_intrinsic2(ctx, fn, f1, f1x1, f1);
    add_intrinsic2(ctx, fn, f1, f1x2, f2);
    add_intrinsic2(ctx, fn, f1, f1x3, f3);
    add_intrinsic2(ctx, fn, f1, f1x4, f4);
    add_intrinsic2(ctx, fn, f2, f2x1, f1);
    add_intrinsic2(ctx, fn, f2, f2x2, f2);
    add_intrinsic2(ctx, fn, f2, f2x3, f3);
    add_intrinsic2(ctx, fn, f2, f2x4, f4);
    add_intrinsic2(ctx, fn, f3, f3x1, f1);
    add_intrinsic2(ctx, fn, f3, f3x2, f2);
    add_intrinsic2(ctx, fn, f3, f3x3, f3);
    add_intrinsic2(ctx, fn, f3, f3x4, f4);
    add_intrinsic2(ctx, fn, f4, f4x1, f1);
    add_intrinsic2(ctx, fn, f4, f4x2, f2);
    add_intrinsic2(ctx, fn, f4, f4x3, f3);
    add_intrinsic2(ctx, fn, f4, f4x4, f4);

    // matrix * matrix
    add_intrinsic2(ctx, fn, i1x1, i1x1, i1x1);
    add_intrinsic2(ctx, fn, i1x2, i1x1, i1x2);
    add_intrinsic2(ctx, fn, i1x3, i1x1, i1x3);
    add_intrinsic2(ctx, fn, i1x4, i1x1, i1x4);
    add_intrinsic2(ctx, fn, i1x1, i1x2, i2x1);
    add_intrinsic2(ctx, fn, i1x2, i1x2, i2x2);
    add_intrinsic2(ctx, fn, i1x3, i1x2, i2x3);
    add_intrinsic2(ctx, fn, i1x4, i1x2, i2x4);
    add_intrinsic2(ctx, fn, i1x1, i1x3, i3x1);
    add_intrinsic2(ctx, fn, i1x2, i1x3, i3x2);
    add_intrinsic2(ctx, fn, i1x3, i1x3, i3x3);
    add_intrinsic2(ctx, fn, i1x4, i1x3, i3x4);
    add_intrinsic2(ctx, fn, i1x1, i1x4, i4x1);
    add_intrinsic2(ctx, fn, i1x2, i1x4, i4x2);
    add_intrinsic2(ctx, fn, i1x3, i1x4, i4x3);
    add_intrinsic2(ctx, fn, i1x4, i1x4, i4x4);
    add_intrinsic2(ctx, fn, i2x1, i2x1, i1x1);
    add_intrinsic2(ctx, fn, i2x2, i2x1, i1x2);
    add_intrinsic2(ctx, fn, i2x3, i2x1, i1x3);
    add_intrinsic2(ctx, fn, i2x4, i2x1, i1x4);
    add_intrinsic2(ctx, fn, i2x1, i2x2, i2x1);
    add_intrinsic2(ctx, fn, i2x2, i2x2, i2x2);
    add_intrinsic2(ctx, fn, i2x3, i2x2, i2x3);
    add_intrinsic2(ctx, fn, i2x4, i2x2, i2x4);
    add_intrinsic2(ctx, fn, i2x1, i2x3, i3x1);
    add_intrinsic2(ctx, fn, i2x2, i2x3, i3x2);
    add_intrinsic2(ctx, fn, i2x3, i2x3, i3x3);
    add_intrinsic2(ctx, fn, i2x4, i2x3, i3x4);
    add_intrinsic2(ctx, fn, i2x1, i2x4, i4x1);
    add_intrinsic2(ctx, fn, i2x2, i2x4, i4x2);
    add_intrinsic2(ctx, fn, i2x3, i2x4, i4x3);
    add_intrinsic2(ctx, fn, i2x4, i2x4, i4x4);
    add_intrinsic2(ctx, fn, i3x1, i3x1, i1x1);
    add_intrinsic2(ctx, fn, i3x2, i3x1, i1x2);
    add_intrinsic2(ctx, fn, i3x3, i3x1, i1x3);
    add_intrinsic2(ctx, fn, i3x4, i3x1, i1x4);
    add_intrinsic2(ctx, fn, i3x1, i3x2, i2x1);
    add_intrinsic2(ctx, fn, i3x2, i3x2, i2x2);
    add_intrinsic2(ctx, fn, i3x3, i3x2, i2x3);
    add_intrinsic2(ctx, fn, i3x4, i3x2, i2x4);
    add_intrinsic2(ctx, fn, i3x1, i3x3, i3x1);
    add_intrinsic2(ctx, fn, i3x2, i3x3, i3x2);
    add_intrinsic2(ctx, fn, i3x3, i3x3, i3x3);
    add_intrinsic2(ctx, fn, i3x4, i3x3, i3x4);
    add_intrinsic2(ctx, fn, i3x1, i3x4, i4x1);
    add_intrinsic2(ctx, fn, i3x2, i3x4, i4x2);
    add_intrinsic2(ctx, fn, i3x3, i3x4, i4x3);
    add_intrinsic2(ctx, fn, i3x4, i3x4, i4x4);
    add_intrinsic2(ctx, fn, i4x1, i4x1, i1x1);
    add_intrinsic2(ctx, fn, i4x2, i4x1, i1x2);
    add_intrinsic2(ctx, fn, i4x3, i4x1, i1x3);
    add_intrinsic2(ctx, fn, i4x4, i4x1, i1x4);
    add_intrinsic2(ctx, fn, i4x1, i4x2, i2x1);
    add_intrinsic2(ctx, fn, i4x2, i4x2, i2x2);
    add_intrinsic2(ctx, fn, i4x3, i4x2, i2x3);
    add_intrinsic2(ctx, fn, i4x4, i4x2, i2x4);
    add_intrinsic2(ctx, fn, i4x1, i4x3, i3x1);
    add_intrinsic2(ctx, fn, i4x2, i4x3, i3x2);
    add_intrinsic2(ctx, fn, i4x3, i4x3, i3x3);
    add_intrinsic2(ctx, fn, i4x4, i4x3, i3x4);
    add_intrinsic2(ctx, fn, i4x1, i4x4, i4x1);
    add_intrinsic2(ctx, fn, i4x2, i4x4, i4x2);
    add_intrinsic2(ctx, fn, i4x3, i4x4, i4x3);
    add_intrinsic2(ctx, fn, i4x4, i4x4, i4x4);
    add_intrinsic2(ctx, fn, f1x1, f1x1, f1x1);
    add_intrinsic2(ctx, fn, f1x2, f1x1, f1x2);
    add_intrinsic2(ctx, fn, f1x3, f1x1, f1x3);
    add_intrinsic2(ctx, fn, f1x4, f1x1, f1x4);
    add_intrinsic2(ctx, fn, f1x1, f1x2, f2x1);
    add_intrinsic2(ctx, fn, f1x2, f1x2, f2x2);
    add_intrinsic2(ctx, fn, f1x3, f1x2, f2x3);
    add_intrinsic2(ctx, fn, f1x4, f1x2, f2x4);
    add_intrinsic2(ctx, fn, f1x1, f1x3, f3x1);
    add_intrinsic2(ctx, fn, f1x2, f1x3, f3x2);
    add_intrinsic2(ctx, fn, f1x3, f1x3, f3x3);
    add_intrinsic2(ctx, fn, f1x4, f1x3, f3x4);
    add_intrinsic2(ctx, fn, f1x1, f1x4, f4x1);
    add_intrinsic2(ctx, fn, f1x2, f1x4, f4x2);
    add_intrinsic2(ctx, fn, f1x3, f1x4, f4x3);
    add_intrinsic2(ctx, fn, f1x4, f1x4, f4x4);
    add_intrinsic2(ctx, fn, f2x1, f2x1, f1x1);
    add_intrinsic2(ctx, fn, f2x2, f2x1, f1x2);
    add_intrinsic2(ctx, fn, f2x3, f2x1, f1x3);
    add_intrinsic2(ctx, fn, f2x4, f2x1, f1x4);
    add_intrinsic2(ctx, fn, f2x1, f2x2, f2x1);
    add_intrinsic2(ctx, fn, f2x2, f2x2, f2x2);
    add_intrinsic2(ctx, fn, f2x3, f2x2, f2x3);
    add_intrinsic2(ctx, fn, f2x4, f2x2, f2x4);
    add_intrinsic2(ctx, fn, f2x1, f2x3, f3x1);
    add_intrinsic2(ctx, fn, f2x2, f2x3, f3x2);
    add_intrinsic2(ctx, fn, f2x3, f2x3, f3x3);
    add_intrinsic2(ctx, fn, f2x4, f2x3, f3x4);
    add_intrinsic2(ctx, fn, f2x1, f2x4, f4x1);
    add_intrinsic2(ctx, fn, f2x2, f2x4, f4x2);
    add_intrinsic2(ctx, fn, f2x3, f2x4, f4x3);
    add_intrinsic2(ctx, fn, f2x4, f2x4, f4x4);
    add_intrinsic2(ctx, fn, f3x1, f3x1, f1x1);
    add_intrinsic2(ctx, fn, f3x2, f3x1, f1x2);
    add_intrinsic2(ctx, fn, f3x3, f3x1, f1x3);
    add_intrinsic2(ctx, fn, f3x4, f3x1, f1x4);
    add_intrinsic2(ctx, fn, f3x1, f3x2, f2x1);
    add_intrinsic2(ctx, fn, f3x2, f3x2, f2x2);
    add_intrinsic2(ctx, fn, f3x3, f3x2, f2x3);
    add_intrinsic2(ctx, fn, f3x4, f3x2, f2x4);
    add_intrinsic2(ctx, fn, f3x1, f3x3, f3x1);
    add_intrinsic2(ctx, fn, f3x2, f3x3, f3x2);
    add_intrinsic2(ctx, fn, f3x3, f3x3, f3x3);
    add_intrinsic2(ctx, fn, f3x4, f3x3, f3x4);
    add_intrinsic2(ctx, fn, f3x1, f3x4, f4x1);
    add_intrinsic2(ctx, fn, f3x2, f3x4, f4x2);
    add_intrinsic2(ctx, fn, f3x3, f3x4, f4x3);
    add_intrinsic2(ctx, fn, f3x4, f3x4, f4x4);
    add_intrinsic2(ctx, fn, f4x1, f4x1, f1x1);
    add_intrinsic2(ctx, fn, f4x2, f4x1, f1x2);
    add_intrinsic2(ctx, fn, f4x3, f4x1, f1x3);
    add_intrinsic2(ctx, fn, f4x4, f4x1, f1x4);
    add_intrinsic2(ctx, fn, f4x1, f4x2, f2x1);
    add_intrinsic2(ctx, fn, f4x2, f4x2, f2x2);
    add_intrinsic2(ctx, fn, f4x3, f4x2, f2x3);
    add_intrinsic2(ctx, fn, f4x4, f4x2, f2x4);
    add_intrinsic2(ctx, fn, f4x1, f4x3, f3x1);
    add_intrinsic2(ctx, fn, f4x2, f4x3, f3x2);
    add_intrinsic2(ctx, fn, f4x3, f4x3, f3x3);
    add_intrinsic2(ctx, fn, f4x4, f4x3, f3x4);
    add_intrinsic2(ctx, fn, f4x1, f4x4, f4x1);
    add_intrinsic2(ctx, fn, f4x2, f4x4, f4x2);
    add_intrinsic2(ctx, fn, f4x3, f4x4, f4x3);
    add_intrinsic2(ctx, fn, f4x4, f4x4, f4x4);
} // add_intrinsic_mul

static void init_builtins(Context *ctx)
{
    // add in standard typedefs...
    const struct
    {
        const char *str;
        const MOJOSHADER_astDataType *datatype;
    } types[] = {
        { "bool", &ctx->dt_bool },
        { "int", &ctx->dt_int },
        { "uint", &ctx->dt_uint },
        { "half", &ctx->dt_half },
        { "float", &ctx->dt_float },
        { "double", &ctx->dt_double },
    };

    int i, j, k;
    for (i = 0; i < STATICARRAYLEN(types); i++)
    {
        char buf[32];
        int len;
        const MOJOSHADER_astDataType *dt;

        for (j = 1; j <= 4; j++)
        {
            // "float2"
            dt = new_datatype_vector(ctx, types[i].datatype, j);
            len = snprintf(buf, sizeof (buf), "%s%d", types[i].str, j);
            push_usertype(ctx, stringcache_len(ctx->strcache, buf, len), dt);
            for (k = 1; k <= 4; k++)
            {
                // "float2x2"
                dt = new_datatype_matrix(ctx, types[i].datatype, j, k);
                len = snprintf(buf, sizeof (buf), "%s%dx%d", types[i].str,j,k);
                push_usertype(ctx, stringcache_len(ctx->strcache,buf,len), dt);
            } // for
        } // for
    } // for

    // !!! FIXME: block these out by pixel/vertex/etc shader.
    // !!! FIXME: calculate actual shader model (or maybe just let bytecode verifier throw up?).
    const int shader_model = 3;
    if (shader_model >= 1)
    {
        add_intrinsic_SAME1_ANYfi(ctx, stringcache(ctx->strcache, "abs"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "acos"));
        add_intrinsic_BOOL_ANYfib(ctx, stringcache(ctx->strcache, "all"));
        add_intrinsic_BOOL_ANYfib(ctx, stringcache(ctx->strcache, "any"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "asin"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "atan"));
        add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "atan2"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "ceil"));
        add_intrinsic_SAME1_ANYfi_SAME1_SAME1(ctx, stringcache(ctx->strcache, "clamp"));
        add_intrinsic_VOID_ANYf(ctx, stringcache(ctx->strcache, "clip"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "cos"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "cosh"));
        add_intrinsic_3f_3f_3f(ctx, stringcache(ctx->strcache, "cross"));
        add_intrinsic_4i_4f(ctx, stringcache(ctx->strcache, "D3DCOLORtoUBYTE4"));
        add_intrinsic_f_Vf_SAME1(ctx, stringcache(ctx->strcache, "distance"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "degrees"));
        add_intrinsic_f_SQUAREMATRIXf(ctx, stringcache(ctx->strcache, "determinant"));
        add_intrinsic_fi_Vfi_SAME1(ctx, stringcache(ctx->strcache, "dot"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "exp"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "exp2"));
        add_intrinsic_SAME1_Vf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "faceforward"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "floor"));
        add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "fmod"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "frac"));
        add_intrinsic_BOOL_ANYf(ctx, stringcache(ctx->strcache, "isfinite"));
        add_intrinsic_BOOL_ANYf(ctx, stringcache(ctx->strcache, "isinf"));
        add_intrinsic_BOOL_ANYf(ctx, stringcache(ctx->strcache, "isnan"));
        add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "ldexp"));
        add_intrinsic_f_Vf(ctx, stringcache(ctx->strcache, "length"));
        add_intrinsic_SAME1_ANYf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "lerp"));
        add_intrinsic_4f_f_f_f(ctx, stringcache(ctx->strcache, "lit"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "log"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "log10"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "log2"));
        add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "max"));
        add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "min"));
        add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "modf"));  // !!! FIXME: out var?
        add_intrinsic_mul(ctx, stringcache(ctx->strcache, "mul"));
        add_intrinsic_f_Vf(ctx, stringcache(ctx->strcache, "noise"));
        add_intrinsic_SAME1_Vf(ctx, stringcache(ctx->strcache, "normalize"));
        add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "pow"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "radians"));
        add_intrinsic_SAME1_ANYfi_SAME1(ctx, stringcache(ctx->strcache, "reflect"));
        add_intrinsic_SAME1_Vf_SAME1_f(ctx, stringcache(ctx->strcache, "refract"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "round"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "rsqrt"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "saturate"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sign"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sin"));
        add_intrinsic_VOID_ANYf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "sincos"));  // !!! FIXME: out var?
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sinh"));
        add_intrinsic_SAME1_ANYf_SAME1_SAME1(ctx, stringcache(ctx->strcache, "smoothstep"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "sqrt"));
        add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "step"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "tan"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "tanh"));
        add_intrinsic_4f_s1_f(ctx, stringcache(ctx->strcache, "tex1D"));
        add_intrinsic_4f_s2_2f(ctx, stringcache(ctx->strcache, "tex2D"));
        add_intrinsic_4f_s3_3f(ctx, stringcache(ctx->strcache, "tex3D"));
        add_intrinsic_4f_sc_3f(ctx, stringcache(ctx->strcache, "texCUBE"));
        add_intrinsic_SAME1_Mfib(ctx, stringcache(ctx->strcache, "transpose"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "trunc"));
    } // if

    if (shader_model >= 2)
    {
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "ddx"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "ddy"));
        add_intrinsic_SAME1_ANYf_SAME1(ctx, stringcache(ctx->strcache, "frexp"));
        add_intrinsic_SAME1_ANYf(ctx, stringcache(ctx->strcache, "fwidth"));
        add_intrinsic_4f_s1_f_f_f(ctx, stringcache(ctx->strcache, "tex1D"));
        add_intrinsic_4f_s1_4f(ctx, stringcache(ctx->strcache, "tex1Dbias"));
        add_intrinsic_4f_s1_f_f_f(ctx, stringcache(ctx->strcache, "tex1Dgrad"));
        add_intrinsic_4f_s1_4f(ctx, stringcache(ctx->strcache, "tex1Dproj"));
        add_intrinsic_4f_s2_2f_2f_2f(ctx, stringcache(ctx->strcache, "tex2D"));
        add_intrinsic_4f_s2_4f(ctx, stringcache(ctx->strcache, "tex2Dbias"));
        add_intrinsic_4f_s2_2f_2f_2f(ctx, stringcache(ctx->strcache, "tex2Dgrad"));
        add_intrinsic_4f_s2_4f(ctx, stringcache(ctx->strcache, "tex2Dproj"));
        add_intrinsic_4f_s3_3f_3f_3f(ctx, stringcache(ctx->strcache, "tex3D"));
        add_intrinsic_4f_s3_4f(ctx, stringcache(ctx->strcache, "tex3Dbias"));
        add_intrinsic_4f_s3_3f_3f_3f(ctx, stringcache(ctx->strcache, "tex3Dgrad"));
        add_intrinsic_4f_s3_4f(ctx, stringcache(ctx->strcache, "tex3Dproj"));
        add_intrinsic_4f_sc_3f_3f_3f(ctx, stringcache(ctx->strcache, "texCUBE"));
        add_intrinsic_4f_sc_4f(ctx, stringcache(ctx->strcache, "texCUBEbias"));
        add_intrinsic_4f_sc_3f_3f_3f(ctx, stringcache(ctx->strcache, "texCUBEgrad"));
        add_intrinsic_4f_sc_4f(ctx, stringcache(ctx->strcache, "texCUBEproj"));
    } // if

    if (shader_model >= 3)
    {
        add_intrinsic_4f_s1_4f(ctx, stringcache(ctx->strcache, "tex1Dlod"));
        add_intrinsic_4f_s2_4f(ctx, stringcache(ctx->strcache, "tex2Dlod"));
        add_intrinsic_4f_s3_4f(ctx, stringcache(ctx->strcache, "tex3Dlod"));
        add_intrinsic_4f_sc_4f(ctx, stringcache(ctx->strcache, "texCUBElod"));
    } // if
} // init_builtins


// parse the source code into an AST.
static void parse_source(Context *ctx, const char *filename,
                         const char *source, unsigned int sourcelen,
                         const MOJOSHADER_preprocessorDefine *defines,
                         unsigned int define_count,
                         MOJOSHADER_includeOpen include_open,
                         MOJOSHADER_includeClose include_close)
{
    TokenData data;
    unsigned int tokenlen;
    Token tokenval;
    const char *token;
    int lemon_token;
    const char *fname;
    Preprocessor *pp;
    void *parser;

    if (!include_open) include_open = MOJOSHADER_internal_include_open;
    if (!include_close) include_close = MOJOSHADER_internal_include_close;

    pp = preprocessor_start(filename, source, sourcelen, include_open,
                            include_close, defines, define_count, 0,
                            MallocBridge, FreeBridge, ctx);
    if (pp == NULL)
    {
        assert(ctx->out_of_memory);  // shouldn't fail for any other reason.
        return;
    } // if

    parser = ParseHLSLAlloc(ctx->malloc, ctx->malloc_data);
    if (parser == NULL)
    {
        assert(ctx->out_of_memory);  // shouldn't fail for any other reason.
        preprocessor_end(pp);
        return;
    } // if

    // !!! FIXME: check if (parser == NULL)...

    init_builtins(ctx);

    SymbolScope *start_scope = ctx->usertypes.scope;

    #if DEBUG_COMPILER_PARSER
    ParseHLSLTrace(stdout, "COMPILER: ");
    #endif

    // Run the preprocessor/lexer/parser...
    int is_pragma = 0;   // !!! FIXME: remove this later when we can parse #pragma.
    int skipping = 0; // !!! FIXME: remove this later when we can parse #pragma.
    do {
        token = preprocessor_nexttoken(pp, &tokenlen, &tokenval);

        if (ctx->out_of_memory)
            break;

        fname = preprocessor_sourcepos(pp, &ctx->sourceline);
        ctx->sourcefile = fname ? stringcache(ctx->strcache, fname) : 0;

        if ((tokenval == TOKEN_HASH) || (tokenval == TOKEN_HASHHASH))
            tokenval = TOKEN_BAD_CHARS;

        if (tokenval == TOKEN_BAD_CHARS)
        {
            fail(ctx, "Bad characters in source file");
            continue;
        } // else if

        else if (tokenval == TOKEN_PREPROCESSING_ERROR)
        {
            fail(ctx, token);  // this happens to be null-terminated.
            continue;
        } // else if

        else if (tokenval == TOKEN_PP_PRAGMA)
        {
            assert(!is_pragma);
            is_pragma = 1;
            skipping = 1;
            continue;
        }

        else if (tokenval == ((Token) '\n'))
        {
            assert(is_pragma);
            is_pragma = 0;
            skipping = 0;
            continue;
        }

        else if (skipping)
        {
            continue;
        }

        // !!! FIXME: this is a mess, decide who should be doing this stuff, and only do it once.
        lemon_token = convert_to_lemon_token(ctx, token, tokenlen, tokenval);
        switch (lemon_token)
        {
            case TOKEN_HLSL_INT_CONSTANT:
                data.i64 = strtoi64(token, tokenlen);
                break;

            case TOKEN_HLSL_FLOAT_CONSTANT:
                data.dbl = strtodouble(token, tokenlen);
                break;

            case TOKEN_HLSL_USERTYPE:
                data.string = stringcache_len(ctx->strcache, token, tokenlen);
                data.datatype = get_usertype(ctx, data.string);  // !!! FIXME: do we need this? It's kind of useless during parsing.
                assert(data.datatype != NULL);
                break;

            case TOKEN_HLSL_STRING_LITERAL:
            case TOKEN_HLSL_IDENTIFIER:
                data.string = stringcache_len(ctx->strcache, token, tokenlen);
                break;

            default:
                data.i64 = 0;
                break;
        } // switch

        ParseHLSL(parser, lemon_token, data, ctx);

        // this probably isn't perfect, but it's good enough for surviving
        //  the parse. We'll sort out correctness once we have a tree.
        if (lemon_token == TOKEN_HLSL_LBRACE)
            push_scope(ctx);
        else if (lemon_token == TOKEN_HLSL_RBRACE)
            pop_scope(ctx);
    } while (tokenval != TOKEN_EOI);

    // Clean out extra usertypes; they are dummies until semantic analysis.
    while (ctx->usertypes.scope != start_scope)
        pop_symbol(ctx, &ctx->usertypes);

    ParseHLSLFree(parser, ctx->free, ctx->malloc_data);
    preprocessor_end(pp);
} // parse_source


/* Intermediate representation... */

static inline int generate_ir_label(Context *ctx)
{
    return ctx->ir_label_count++;
} // generate_ir_label

static inline int generate_ir_temp(Context *ctx)
{
    return ctx->ir_temp_count++;
} // generate_ir_temp

static const LoopLabels *push_ir_loop(Context *ctx, const int isswitch)
{
    // !!! FIXME: cache these allocations?
    LoopLabels *retval = (LoopLabels *)Malloc(ctx, sizeof (LoopLabels));
    if (retval)
    {
        retval->start = (isswitch) ? -1 : generate_ir_label(ctx);
        retval->end = generate_ir_label(ctx);
        retval->prev = ctx->ir_loop;
        ctx->ir_loop = retval;
    } // if

    return retval;
} // push_ir_loop

static void pop_ir_loop(Context *ctx)
{
    assert(ctx->ir_loop != NULL);
    LoopLabels *labels = ctx->ir_loop;
    ctx->ir_loop = ctx->ir_loop->prev;
    Free(ctx, labels);
} // pop_ir_loop


#define NEW_IR_NODE(retval, cls, typ) \
    cls *retval = (cls *) Malloc(ctx, sizeof (cls)); \
    do { \
        if (retval == NULL) { return NULL; } \
        retval->ir.type = typ; \
        retval->ir.filename = ctx->sourcefile; \
        retval->ir.line = ctx->sourceline; \
    } while (0)

#define NEW_IR_EXPR(retval, cls, typ, dt, numelems) \
    cls *retval = (cls *) Malloc(ctx, sizeof (cls)); \
    do { \
        if (retval == NULL) { return NULL; } \
        retval->info.ir.type = typ; \
        retval->info.ir.filename = ctx->sourcefile; \
        retval->info.ir.line = ctx->sourceline; \
        retval->info.type = dt; \
        retval->info.elements = numelems; \
    } while (0)

// syntactic sugar.
static inline MOJOSHADER_irNode *build_ir(Context *ctx, void *_ast);
static inline MOJOSHADER_irExpression *build_ir_expr(Context *ctx, void *_ast)
{
    MOJOSHADER_irNode *retval = build_ir(ctx, _ast);
    assert(!retval || (retval->ir.type > MOJOSHADER_IR_START_RANGE_EXPR));
    assert(!retval || (retval->ir.type < MOJOSHADER_IR_END_RANGE_EXPR));
    return (MOJOSHADER_irExpression *) retval;
} // build_ir_expr

static inline MOJOSHADER_irStatement *build_ir_stmt(Context *ctx, void *_ast)
{
    MOJOSHADER_irNode *retval = build_ir(ctx, _ast);
    assert(!retval || (retval->ir.type > MOJOSHADER_IR_START_RANGE_STMT));
    assert(!retval || (retval->ir.type < MOJOSHADER_IR_END_RANGE_STMT));
    return (MOJOSHADER_irStatement *) retval;
} // build_ir_stmt


static MOJOSHADER_irExpression *new_ir_binop(Context *ctx,
                                       const MOJOSHADER_irBinOpType op,
                                       MOJOSHADER_irExpression *left,
                                       MOJOSHADER_irExpression *right)
{
    if ((!left) || (!right)) return NULL;
    NEW_IR_EXPR(retval, MOJOSHADER_irBinOp, MOJOSHADER_IR_BINOP, left->info.type, left->info.elements);
    assert(left->info.type == right->info.type);
    assert(left->info.elements == right->info.elements);
    retval->op = op;
    retval->left = left;
    retval->right = right;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_binop

static MOJOSHADER_irExpression *new_ir_eseq(Context *ctx,
                                      MOJOSHADER_irStatement *stmt,
                                      MOJOSHADER_irExpression *expr)
{
    if (!expr) return NULL;
    NEW_IR_EXPR(retval, MOJOSHADER_irESeq, MOJOSHADER_IR_ESEQ, expr->info.type, expr->info.elements);
    retval->stmt = stmt;
    retval->expr = expr;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_eseq

static MOJOSHADER_irExpression *new_ir_temp(Context *ctx, const int index,
                                            const MOJOSHADER_astDataTypeType type,
                                            const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irTemp, MOJOSHADER_IR_TEMP, type, elements);
    retval->index = index;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_temp



#define NEW_IR_BINOP(op,l,r) new_ir_binop(ctx, MOJOSHADER_IR_BINOP_##op, l, r)
#define EASY_IR_BINOP(op) \
    NEW_IR_BINOP(op, build_ir_expr(ctx, ast->binary.left), \
                 build_ir_expr(ctx, ast->binary.right))

// You have to fill in ->value yourself!
static MOJOSHADER_irExpression *new_ir_constant(Context *ctx,
                                                const MOJOSHADER_astDataTypeType type,
                                                const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, type, elements);
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_constant

static MOJOSHADER_irExpression *new_ir_constint(Context *ctx, const int val)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, MOJOSHADER_AST_DATATYPE_INT, 1);
    retval->value.ival[0] = val;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_constint

static MOJOSHADER_irExpression *new_ir_constfloat(Context *ctx, const float val)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, MOJOSHADER_AST_DATATYPE_FLOAT, 1);
    retval->value.fval[0] = val;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_constfloat

static MOJOSHADER_irExpression *new_ir_constbool(Context *ctx, const int val)
{
    // !!! FIXME: cache true and false in (ctx), ignore in delete_ir().
    NEW_IR_EXPR(retval, MOJOSHADER_irConstant, MOJOSHADER_IR_CONSTANT, MOJOSHADER_AST_DATATYPE_BOOL, 1);
    retval->value.ival[0] = val;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_constbool

static MOJOSHADER_irExpression *new_ir_convert(Context *ctx, MOJOSHADER_irExpression *expr,
                                               const MOJOSHADER_astDataTypeType type,
                                               const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irConvert, MOJOSHADER_IR_CONVERT, type, elements);
    retval->expr = expr;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_convert

static MOJOSHADER_irExpression *new_ir_construct(Context *ctx, MOJOSHADER_irExprList *args,
                                               const MOJOSHADER_astDataTypeType type,
                                               const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irConstruct, MOJOSHADER_IR_CONSTRUCT, type, elements);
    retval->args = args;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_construct

static MOJOSHADER_irExpression *new_ir_call(Context *ctx, const int index,
                                            MOJOSHADER_irExprList *args,
                                            const MOJOSHADER_astDataTypeType type,
                                            const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irCall, MOJOSHADER_IR_CALL, type, elements);
    retval->args = args;
    retval->index = index;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_call

static MOJOSHADER_irExpression *new_ir_swizzle(Context *ctx,
                                               MOJOSHADER_irExpression *expr,
                                               const char *channels,
                                               const MOJOSHADER_astDataTypeType type,
                                               const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irSwizzle, MOJOSHADER_IR_SWIZZLE, type, elements);
    retval->expr = expr;
    memcpy(retval->channels, channels, sizeof (retval->channels));
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_swizzle

static MOJOSHADER_irExpression *new_ir_memory(Context *ctx, const int index,
                                              const MOJOSHADER_astDataTypeType type,
                                              const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irMemory, MOJOSHADER_IR_MEMORY, type, elements);
    retval->index = index;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_memory

static MOJOSHADER_irExpression *new_ir_array(Context *ctx,
                                             MOJOSHADER_irExpression *array,
                                             MOJOSHADER_irExpression *element,
                                             const MOJOSHADER_astDataTypeType type,
                                             const int elements)
{
    NEW_IR_EXPR(retval, MOJOSHADER_irArray, MOJOSHADER_IR_ARRAY, type, elements);
    retval->array = array;
    retval->element = element;
    return (MOJOSHADER_irExpression *) retval;
} // new_ir_array

static MOJOSHADER_irStatement *new_ir_seq(Context *ctx,
                                     MOJOSHADER_irStatement *first,
                                     MOJOSHADER_irStatement *next)
{
    assert((first != NULL) || (next != NULL));
    if (first == NULL)  // don't generate a SEQ if unnecessary.
        return next;
    else if (next == NULL)
        return first;

    NEW_IR_NODE(retval, MOJOSHADER_irSeq, MOJOSHADER_IR_SEQ);
    retval->first = first;
    retval->next = next;
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_seq

static MOJOSHADER_irStatement *new_ir_jump(Context *ctx, const int label)
{
    NEW_IR_NODE(retval, MOJOSHADER_irJump, MOJOSHADER_IR_JUMP);
    retval->label = label;
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_jump

static MOJOSHADER_irStatement *new_ir_cjump(Context *ctx,
                                       const MOJOSHADER_irConditionType cond,
                                       MOJOSHADER_irExpression *left,
                                       MOJOSHADER_irExpression *right,
                                       const int iftrue, const int iffalse)
{
    NEW_IR_NODE(retval, MOJOSHADER_irCJump, MOJOSHADER_IR_CJUMP);
    retval->cond = cond;
    retval->left = left;
    retval->right = right;
    retval->iftrue = iftrue;
    retval->iffalse = iffalse;
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_cjump

static MOJOSHADER_irStatement *new_ir_label(Context *ctx, const int index)
{
    NEW_IR_NODE(retval, MOJOSHADER_irLabel, MOJOSHADER_IR_LABEL);
    retval->index = index;
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_label

static MOJOSHADER_irStatement *new_ir_move(Context *ctx,
                                      MOJOSHADER_irExpression *dst,
                                      MOJOSHADER_irExpression *src,
                                      const int writemask)
{
    NEW_IR_NODE(retval, MOJOSHADER_irMove, MOJOSHADER_IR_MOVE);
    assert(dst && src && (dst->info.type == src->info.type));
    assert(dst && src && (dst->info.elements == src->info.elements));
    retval->dst = dst;
    retval->src = src;
    retval->writemask = writemask;
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_move

static MOJOSHADER_irStatement *new_ir_expr_stmt(Context *ctx, MOJOSHADER_irExpression *expr)
{
    NEW_IR_NODE(retval, MOJOSHADER_irExprStmt, MOJOSHADER_IR_EXPR_STMT);
    retval->expr = expr;
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_expr_stmt

static MOJOSHADER_irStatement *new_ir_discard(Context *ctx)
{
    NEW_IR_NODE(retval, MOJOSHADER_irDiscard, MOJOSHADER_IR_DISCARD);
    return (MOJOSHADER_irStatement *) retval;
} // new_ir_discard

static MOJOSHADER_irExprList *new_ir_exprlist(Context *ctx, MOJOSHADER_irExpression *expr)
{
    NEW_IR_NODE(retval, MOJOSHADER_irExprList, MOJOSHADER_IR_EXPRLIST);
    retval->expr = expr;
    retval->next = NULL;
    return (MOJOSHADER_irExprList *) retval;
} // new_ir_exprlist


// This handles most comparison operators (less-than, equals, etc...)
static MOJOSHADER_irExpression *build_ir_compare(Context *ctx,
                                    const MOJOSHADER_irConditionType operation,
                                    MOJOSHADER_irExpression *left,
                                    MOJOSHADER_irExpression *right,
                                    MOJOSHADER_irExpression *tval,
                                    MOJOSHADER_irExpression *fval)
{
    /* The gist...
            cjump x < y, t, f  // '<' is whatever operation
        t:
            move tmp, tval
            jump join
        f:
            move tmp, fval
        join:
    */

    const int t = generate_ir_label(ctx);
    const int f = generate_ir_label(ctx);
    const int join = generate_ir_label(ctx);
    const int tmp = generate_ir_temp(ctx);

    assert(tval && fval && (tval->info.type == fval->info.type));
    assert(tval && fval && (tval->info.elements == fval->info.elements));

    const MOJOSHADER_astDataTypeType dt = tval->info.type;
    const int elements = tval->info.elements;

    return new_ir_eseq(ctx,
                new_ir_seq(ctx, new_ir_cjump(ctx, operation, left, right, t, f),
                new_ir_seq(ctx, new_ir_label(ctx, t),
                new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, dt, elements), tval, -1),
                new_ir_seq(ctx, new_ir_jump(ctx, join),
                new_ir_seq(ctx, new_ir_label(ctx, f),
                new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, dt, elements), fval, -1),
                                new_ir_label(ctx, join))))))),
                    new_ir_temp(ctx, tmp, dt, elements));
} // build_ir_compare

#define EASY_IR_COMPARE(op) \
    build_ir_compare(ctx, MOJOSHADER_IR_COND_##op, \
                   build_ir_expr(ctx, ast->binary.left), \
                   build_ir_expr(ctx, ast->binary.right), \
                   new_ir_constbool(ctx, 1), \
                   new_ir_constbool(ctx, 0))


// This handles && and || operators.
static MOJOSHADER_irExpression *build_ir_logical_and_or(Context *ctx,
                                    const MOJOSHADER_astExpressionBinary *ast,
                                    const int left_testval)
{
    /* The gist...
            cjump left == left_testval, maybe, f
        maybe:
            cjump right == true, t, f
        t:
            move tmp, 1
            jump join
        f:
            move tmp, 0
        join:
    */

    assert(ast->left->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);
    assert(ast->right->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);

    const int t = generate_ir_label(ctx);
    const int f = generate_ir_label(ctx);
    const int maybe = generate_ir_label(ctx);
    const int join = generate_ir_label(ctx);
    const int tmp = generate_ir_temp(ctx);

    return new_ir_eseq(ctx,
                new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->left), new_ir_constbool(ctx, left_testval), maybe, f),
                new_ir_seq(ctx, new_ir_label(ctx, maybe),
                new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->right), new_ir_constbool(ctx, 1), t, f),
                new_ir_seq(ctx, new_ir_label(ctx, t),
                new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, MOJOSHADER_AST_DATATYPE_BOOL, 1), new_ir_constbool(ctx, 1), -1),
                new_ir_seq(ctx, new_ir_jump(ctx, join),
                new_ir_seq(ctx, new_ir_label(ctx, f),
                new_ir_seq(ctx, new_ir_move(ctx, new_ir_temp(ctx, tmp, MOJOSHADER_AST_DATATYPE_BOOL, 1), new_ir_constbool(ctx, 0), -1),
                                new_ir_label(ctx, join))))))))),
                    new_ir_temp(ctx, tmp, MOJOSHADER_AST_DATATYPE_BOOL, 1));
} // build_ir_logical_and_or

static inline MOJOSHADER_irExpression *build_ir_logical_and(Context *ctx,
                                    const MOJOSHADER_astExpressionBinary *ast)
{
    // this needs to not evaluate (right) if (left) is false!
    return build_ir_logical_and_or(ctx, ast, 1);
} // build_ir_logical_and

static inline MOJOSHADER_irExpression *build_ir_logical_or(Context *ctx,
                                    const MOJOSHADER_astExpressionBinary *ast)
{
    // this needs to not evaluate (right) if (left) is true!
    return build_ir_logical_and_or(ctx, ast, 0);
} // build_ir_logical_or

static inline MOJOSHADER_irStatement *build_ir_no_op(Context *ctx)
{
    return new_ir_label(ctx, generate_ir_label(ctx));
} // build_ir_no_op

static MOJOSHADER_irStatement *build_ir_ifstmt(Context *ctx,
                                          const MOJOSHADER_astIfStatement *ast)
{
    assert(ast->expr->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);

    // !!! FIXME: ast->attributes?

    // IF statement without an ELSE.
    if (ast->else_statement == NULL)
    {
        /* The gist...
                cjump expr, t, join
            t:
                statement
            join:
        */

        const int t = generate_ir_label(ctx);
        const int join = generate_ir_label(ctx);

        return new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), t, join),
               new_ir_seq(ctx, new_ir_label(ctx, t),
               new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement),
               new_ir_seq(ctx, new_ir_label(ctx, join),
                               build_ir_stmt(ctx, ast->next)))));
    } // if

    // IF statement _with_ an ELSE.
    /* The gist...
            cjump expr, t, f
        t:
            statement
            jump join
        f:
            elsestatement
        join:
    */

    const int t = generate_ir_label(ctx);
    const int f = generate_ir_label(ctx);
    const int join = generate_ir_label(ctx);

    return new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), t, f),
           new_ir_seq(ctx, new_ir_label(ctx, t),
           new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement),
           new_ir_seq(ctx, new_ir_jump(ctx, join),
           new_ir_seq(ctx, new_ir_label(ctx, f),
           new_ir_seq(ctx, build_ir_stmt(ctx, ast->else_statement),
           new_ir_seq(ctx, new_ir_label(ctx, join),
                           build_ir_stmt(ctx, ast->next))))))));
} // build_ir_ifstmt


static MOJOSHADER_irStatement *build_ir_forstmt(Context *ctx,
                                       const MOJOSHADER_astForStatement *ast)
{
    // !!! FIXME: ast->unroll

    assert(ast->looptest->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);

    /* The gist...
            initializer  // (or var_decl->initializer!)
        test:
            cjump looptest == true, loop, join
        loop:
            statement
        increment:  // needs to be here; this is where "continue" jumps!
            counter
            jump test
        join:
    */

    const int test = generate_ir_label(ctx);
    const int loop = generate_ir_label(ctx);

    const LoopLabels *labels = push_ir_loop(ctx, 0);
    if (labels == NULL)
        return NULL;  // out of memory...

    const int increment = labels->start;
    const int join = labels->end;

    assert( (ast->var_decl && !ast->initializer) ||
            (!ast->var_decl && ast->initializer) );

    MOJOSHADER_irStatement *init = NULL;
    if (ast->var_decl != NULL)
    {
//sdfsdf
        // !!! FIXME: map the initializer to the variable? Need fix to var_decl parsing.
//        new_ir_move(ctx, FIXME MAP TO REGISTER ast->var_decl->index, build_ir_expr(ctx, ast->fsdf));
//        FIXME
//        init = build_ir_vardecl(ctx, ast->var_decl);
    } // if
    else
    {
//        init = build_ir_expr(ctx, ast->initializer);
    } // else

    MOJOSHADER_irStatement *retval =
        new_ir_seq(ctx, init,
        new_ir_seq(ctx, new_ir_label(ctx, test),
        new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->looptest), new_ir_constbool(ctx, 1), loop, join),
        new_ir_seq(ctx, new_ir_label(ctx, loop),
        new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement),
        new_ir_seq(ctx, new_ir_label(ctx, increment),
        new_ir_seq(ctx, new_ir_expr_stmt(ctx, build_ir_expr(ctx, ast->counter)),
        new_ir_seq(ctx, new_ir_jump(ctx, test),
                        new_ir_label(ctx, join)))))))));

    pop_ir_loop(ctx);

    return new_ir_seq(ctx, retval, build_ir_stmt(ctx, ast->next));
} // build_ir_forstmt

static MOJOSHADER_irStatement *build_ir_whilestmt(Context *ctx,
                                          const MOJOSHADER_astWhileStatement *ast)
{
    // !!! FIXME: ast->unroll

    assert(ast->expr->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);

    /* The gist...
        loop:
            cjump expr == true, t, join
        t:
            statement
            jump loop
        join:
    */

    const LoopLabels *labels = push_ir_loop(ctx, 0);
    if (labels == NULL)
        return NULL;  // out of memory...

    const int loop = labels->start;
    const int t = generate_ir_label(ctx);
    const int join = labels->end;

    MOJOSHADER_irStatement *retval =
        new_ir_seq(ctx, new_ir_label(ctx, loop),
        new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), t, join),
        new_ir_seq(ctx, new_ir_label(ctx, t),
        new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement),
        new_ir_seq(ctx, new_ir_jump(ctx, loop),
                        new_ir_label(ctx, join))))));

    pop_ir_loop(ctx);

    return new_ir_seq(ctx, retval, build_ir_stmt(ctx, ast->next));
} // build_ir_whilestmt

static MOJOSHADER_irStatement *build_ir_dostmt(Context *ctx,
                                          const MOJOSHADER_astDoStatement *ast)
{
    // !!! FIXME: ast->unroll

    assert(ast->expr->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);

    /* The gist...
        loop:
            statement
            cjump expr == true, loop, join
        join:
    */

    const LoopLabels *labels = push_ir_loop(ctx, 0);
    if (labels == NULL)
        return NULL;  // out of memory...

    const int loop = labels->start;
    const int join = labels->end;

    MOJOSHADER_irStatement *retval =
        new_ir_seq(ctx, new_ir_label(ctx, loop),
        new_ir_seq(ctx, build_ir_stmt(ctx, ast->statement),
        new_ir_seq(ctx, new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, ast->expr), new_ir_constbool(ctx, 1), loop, join),
                        new_ir_label(ctx, join))));

    pop_ir_loop(ctx);

    return new_ir_seq(ctx, retval, build_ir_stmt(ctx, ast->next));
} // build_ir_dostmt

static MOJOSHADER_irStatement *build_ir_switch(Context *ctx, const MOJOSHADER_astSwitchStatement *ast)
{
    // Dithering down to a list of if-statements in all cases
    //  isn't ideal, but we can't do jumptables in D3D bytecode.

    // !!! FIXME: attributes?

    /* The gist...
            move tmp, expr
            cjump tmp == case1expr, case1, testcase2
        testcase2:  // etc
            cjump tmp == case2expr, case2, join
        case1:
            case1stmt  // might have a break in it somewhere.
        case2:
            case2stmt
        join:
    */

    const LoopLabels *labels = push_ir_loop(ctx, 1);
    if (labels == NULL)
        return NULL;  // out of memory...

    const int join = labels->end;
    const int elems = datatype_elems(ctx, ast->expr->datatype);
    const MOJOSHADER_astDataTypeType dt = datatype_base(ctx, ast->expr->datatype)->type;

    const MOJOSHADER_astSwitchCases *cases = ast->cases;
    const int tmp = generate_ir_temp(ctx);
    MOJOSHADER_irStatement *startseqs = new_ir_move(ctx, new_ir_temp(ctx, tmp, dt, elems), build_ir_expr(ctx, ast->expr), -1);
    MOJOSHADER_irStatement *testseqs = startseqs;
    MOJOSHADER_irStatement *startcaseseqs = NULL;
    MOJOSHADER_irStatement *caseseqs = NULL;
    while (cases)
    {
        const int t = generate_ir_label(ctx);
        const int f = (cases->next == NULL) ? join : generate_ir_label(ctx);
        MOJOSHADER_irStatement *cjump = new_ir_cjump(ctx, MOJOSHADER_IR_COND_EQL, build_ir_expr(ctx, cases->expr), new_ir_temp(ctx, tmp, dt, elems), t, f);

        if (cases->next == NULL)  // last one, do the join label.
        {
            testseqs = new_ir_seq(ctx, testseqs, cjump);
            caseseqs = new_ir_seq(ctx, caseseqs, new_ir_seq(ctx, new_ir_label(ctx, t), build_ir_stmt(ctx, cases->statement)));
            caseseqs = new_ir_seq(ctx, caseseqs, new_ir_label(ctx, f));
        } // if
        else
        {
            testseqs = new_ir_seq(ctx, testseqs, new_ir_seq(ctx, cjump, new_ir_label(ctx, f)));
            caseseqs = new_ir_seq(ctx, caseseqs, new_ir_seq(ctx, new_ir_label(ctx, t), build_ir_stmt(ctx, cases->statement)));
        } // else

        if (startcaseseqs == NULL)
            startcaseseqs = caseseqs;

        cases = cases->next;
    } // while

    pop_ir_loop(ctx);

    return new_ir_seq(ctx, startseqs, new_ir_seq(ctx, startcaseseqs, build_ir_stmt(ctx, ast->next)));
} // build_ir_switch

static MOJOSHADER_irExpression *build_ir_increxpr(Context *ctx, const MOJOSHADER_astDataType *_dt,
                                                  const int val)
{
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, _dt);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    MOJOSHADER_irConstant *retval = (MOJOSHADER_irConstant *) new_ir_constant(ctx, type, elems);
    int i;

    switch (type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL:
        case MOJOSHADER_AST_DATATYPE_INT:
        case MOJOSHADER_AST_DATATYPE_UINT:
            for (i = 0; i < elems; i++)
                retval->value.ival[i] = (int) val;
            break;

        case MOJOSHADER_AST_DATATYPE_FLOAT:
        case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM:
        case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM:
        case MOJOSHADER_AST_DATATYPE_HALF:
        case MOJOSHADER_AST_DATATYPE_DOUBLE:
            for (i = 0; i < elems; i++)
                retval->value.fval[i] = (float) val;
            break;

        default:
            assert(0 && "Semantic analysis should have caught this!");
    } // switch

    return (MOJOSHADER_irExpression *) retval;
} // build_ir_increxpr

static MOJOSHADER_irExpression *build_ir_preincdec(Context *ctx, MOJOSHADER_astExpressionUnary *ast, const MOJOSHADER_irBinOpType binop)
{
    /* The gist...
        move expr, expr + 1
        return expr
    */
    // !!! FIXME: can you writemask an increment operator?
    MOJOSHADER_irExpression *constant = build_ir_increxpr(ctx, ast->datatype, 1);
    return new_ir_eseq(ctx,
                new_ir_move(ctx,
                    build_ir_expr(ctx, ast->operand),
                    new_ir_binop(ctx, binop, build_ir_expr(ctx, ast->operand), constant), -1),
                build_ir_expr(ctx, ast->operand));
} // build_ir_preincdec

static MOJOSHADER_irExpression *build_ir_postincdec(Context *ctx, MOJOSHADER_astExpressionUnary *ast, const MOJOSHADER_irBinOpType binop)
{
    /* The gist...
        move tmp, expr
        move expr, expr + 1
        return tmp
    */

    // !!! FIXME: can you writemask an increment operator?
    MOJOSHADER_irExpression *constant = build_ir_increxpr(ctx, ast->datatype, 1);
    const int tmp = generate_ir_temp(ctx);
    return new_ir_eseq(ctx,
                new_ir_seq(ctx,
                    new_ir_move(ctx, new_ir_temp(ctx, tmp, constant->info.type, constant->info.elements), build_ir_expr(ctx, ast->operand), -1),
                    new_ir_move(ctx, build_ir_expr(ctx, ast->operand),
                        new_ir_binop(ctx, binop, build_ir_expr(ctx, ast->operand), constant), -1)),
                new_ir_temp(ctx, tmp, constant->info.type, constant->info.elements));
} // build_ir_postincdec

static MOJOSHADER_irExpression *build_ir_convert(Context *ctx, const MOJOSHADER_astExpressionCast *ast)
{
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    return new_ir_convert(ctx, build_ir_expr(ctx, ast->operand), type, elems);
} // build_ir_convert

static MOJOSHADER_irExprList *build_ir_exprlist(Context *ctx, MOJOSHADER_astArguments *args)
{
    MOJOSHADER_irExprList *retval = NULL;
    MOJOSHADER_irExprList *prev = NULL;

    while (args != NULL)
    {
        assert((retval && prev) || ((!retval) && (!prev)));

        MOJOSHADER_irExprList *item = new_ir_exprlist(ctx, build_ir_expr(ctx, args->argument));
        if (prev == NULL)
            prev = retval = item;
        else
            prev->next = item;

        args = args->next;
    } // while

    return retval;
} // build_ir_exprlist

static MOJOSHADER_irExpression *build_ir_constructor(Context *ctx, const MOJOSHADER_astExpressionConstructor *ast)
{
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    assert(elems <= 16);  // just in case (matrix4x4 constructor is largest).
    return new_ir_construct(ctx, build_ir_exprlist(ctx, ast->args), type, elems);
} // build_ir_constructor

static MOJOSHADER_irExpression *build_ir_call(Context *ctx, const MOJOSHADER_astExpressionCallFunction *ast)
{
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    return new_ir_call(ctx, ast->identifier->index, build_ir_exprlist(ctx, ast->args), type, elems);
} // build_ir_call

static char swiz_to_channel(const char swiz)
{
    if ((swiz == 'r') || (swiz == 'x')) return 0;
    if ((swiz == 'g') || (swiz == 'y')) return 1;
    if ((swiz == 'b') || (swiz == 'z')) return 2;
    if ((swiz == 'a') || (swiz == 'w')) return 3;
    assert(0 && "Should have been caught by semantic analysis.");
    return 0;
} // swiz_to_channel

static MOJOSHADER_irExpression *build_ir_swizzle(Context *ctx, const MOJOSHADER_astExpressionDerefStruct *ast)
{
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    char chans[4] = { 0, 0, 0, 0 };
    const char *swizstr = ast->member;
    int i;

    for (i = 0; swizstr[i]; i++)
        chans[i] = swiz_to_channel(swizstr[i]);

    return new_ir_swizzle(ctx, build_ir_expr(ctx, ast->identifier), chans, type, elems);
} // build_ir_swizzle

static MOJOSHADER_irExpression *build_ir_identifier(Context *ctx, const MOJOSHADER_astExpressionIdentifier *ast)
{
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    return new_ir_memory(ctx, ast->index, type, elems);
} // build_ir_identifier

static MOJOSHADER_irExpression *build_ir_derefstruct(Context *ctx, const MOJOSHADER_astExpressionDerefStruct *ast)
{
    // There are only three possible IR nodes that contain a struct:
    //  an irTemp, an irMemory, or an irESeq that results in a temp or memory.
    //  As such, we figure out which it is, and offset appropriately for the
    //  member.
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);
    MOJOSHADER_irExpression *expr = build_ir_expr(ctx, ast->identifier);
    MOJOSHADER_irExpression *finalexpr = expr;

    if (expr == NULL)
        return NULL;

    assert(!ast->isswizzle);

    while (finalexpr->ir.type == MOJOSHADER_IR_ESEQ)
        finalexpr = finalexpr->eseq.expr;

    if (finalexpr->ir.type == MOJOSHADER_IR_TEMP)
        finalexpr->temp.index += ast->member_index;
    else if (finalexpr->ir.type == MOJOSHADER_IR_MEMORY)
        finalexpr->memory.index += ast->member_index;
    else
        assert(0 && "Unexpected condition");

    // Replace the struct type with the type of the member.
    expr->info.type = type;
    expr->info.elements = elems;

    return expr;
} // build_ir_derefstruct

static MOJOSHADER_irExpression *build_ir_derefarray(Context *ctx, const MOJOSHADER_astExpressionBinary *ast)
{
    // In most compilers, arrays dither down to offsets into memory, but
    //  they're somewhat special in D3D, since they might have to deal with
    //  vectors, etc...so we keep them as first-class citizens of the IR,
    //  and let the optimizer/codegen sort it out.
    // !!! FIXME: this might be the wrong move. Maybe remove this IR node type?
    const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->datatype);
    const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
    const int elems = datatype_elems(ctx, dt);

    // !!! FIXME: Array dereference of a vector can become a simple swizzle operation, if we have a constant index.
    // !!! FIXME: Matrix dereference of a vector can become a simple reference to a temp/memory, if we have a constant index.
    return new_ir_array(ctx, build_ir_expr(ctx, ast->left), build_ir_expr(ctx, ast->right), type, elems);
} // build_ir_derefarray

static MOJOSHADER_irExpression *build_ir_assign_binop(Context *ctx,
                                                const MOJOSHADER_irBinOpType op,
                                                const MOJOSHADER_astExpressionBinary *ast)
{
    MOJOSHADER_irExpression *lvalue = build_ir_expr(ctx, ast->left);
    MOJOSHADER_irExpression *rvalue = build_ir_expr(ctx, ast->right);
    const MOJOSHADER_astDataTypeType type = lvalue->info.type;
    const int elems = lvalue->info.elements;
    const int tmp = generate_ir_temp(ctx);

    // Semantic analysis should have inserted casts if necessary.
    assert(type == rvalue->info.type);
    assert(elems == rvalue->info.elements);

    // The destination must eventually be lvalue, which means memory or temp.
    MOJOSHADER_irExpression *dst = lvalue;
    while (dst->ir.type == MOJOSHADER_IR_ESEQ)
        dst = dst->eseq.expr;

    if (dst->ir.type == MOJOSHADER_IR_TEMP)
        dst = new_ir_temp(ctx, dst->temp.index, dst->info.type, dst->info.elements);
    else if (dst->ir.type == MOJOSHADER_IR_MEMORY)
        dst = new_ir_memory(ctx, dst->memory.index, dst->info.type, dst->info.elements);
    else
        assert(0 && "Unexpected condition");

    // !!! FIXME: write masking!
    return new_ir_eseq(ctx,
                new_ir_seq(ctx,
                    new_ir_move(ctx, new_ir_temp(ctx, tmp, type, elems), new_ir_binop(ctx, op, lvalue, rvalue), -1),
                    new_ir_move(ctx, dst, new_ir_temp(ctx, tmp, type, elems), -1)),
                new_ir_temp(ctx, tmp, type, elems));
} // build_ir_assign_binop

static MOJOSHADER_irExpression *build_ir_assign(Context *ctx,
                                                const MOJOSHADER_astExpressionBinary *ast)
{
    MOJOSHADER_irExpression *lvalue = build_ir_expr(ctx, ast->left);
    MOJOSHADER_irExpression *rvalue = build_ir_expr(ctx, ast->right);
    const MOJOSHADER_astDataTypeType type = lvalue->info.type;
    const int elems = lvalue->info.elements;
    const int tmp = generate_ir_temp(ctx);

    // Semantic analysis should have inserted casts if necessary.
    assert(type == rvalue->info.type);
    assert(elems == rvalue->info.elements);

    // !!! FIXME: write masking!
    // !!! FIXME: whole array/struct assignments need to become a sequence of moves.
    return new_ir_eseq(ctx,
                new_ir_seq(ctx,
                    new_ir_move(ctx, new_ir_temp(ctx, tmp, type, elems), rvalue, -1),
                    new_ir_move(ctx, lvalue, new_ir_temp(ctx, tmp, type, elems), -1)),
                new_ir_temp(ctx, tmp, type, elems));
} // build_ir_assign


// The AST must be perfect and normalized and sane here. If there are any
//  strange corner cases, you should strive to handle them in semantic
//  analysis, so conversion to IR can proceed with a minimum of drama.
static void *build_ir_internal(Context *ctx, void *_ast);
static inline MOJOSHADER_irNode *build_ir(Context *ctx, void *_ast)
{
    return (MOJOSHADER_irNode *) build_ir_internal(ctx, _ast);
} // build_ir

static void *build_ir_internal(Context *ctx, void *_ast)
{
    if ((_ast == NULL) || (ctx->out_of_memory))
        return NULL;

    MOJOSHADER_astNode *ast = (MOJOSHADER_astNode *) _ast;

    // upkeep so we report correct error locations...
    ctx->sourcefile = ast->ast.filename;
    ctx->sourceline = ast->ast.line;

    switch (ast->ast.type)
    {
        case MOJOSHADER_AST_OP_PREINCREMENT:  // !!! FIXME: sequence points?
            return build_ir_preincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_ADD);

        case MOJOSHADER_AST_OP_POSTINCREMENT: // !!! FIXME: sequence points?
            return build_ir_postincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_ADD);

        case MOJOSHADER_AST_OP_PREDECREMENT:  // !!! FIXME: sequence points?
            return build_ir_preincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_SUBTRACT);

        case MOJOSHADER_AST_OP_POSTDECREMENT: // !!! FIXME: sequence points?
            return build_ir_postincdec(ctx, &ast->unary, MOJOSHADER_IR_BINOP_SUBTRACT);

        case MOJOSHADER_AST_OP_COMPLEMENT:
            return NEW_IR_BINOP(XOR, build_ir_expr(ctx, ast->unary.operand),
                                new_ir_constint(ctx, 0xFFFFFFFF));

        case MOJOSHADER_AST_OP_NEGATE:  // !!! FIXME: -0.0f != +0.0f
            return NEW_IR_BINOP(SUBTRACT, build_ir_increxpr(ctx, ast->unary.datatype, -1),
                                build_ir_expr(ctx, ast->unary.operand));

        case MOJOSHADER_AST_OP_NOT:  // operand must be bool here!
            assert(ast->unary.operand->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);
            return NEW_IR_BINOP(XOR, build_ir_expr(ctx, ast->unary.operand),
                                new_ir_constint(ctx, 1));

        case MOJOSHADER_AST_OP_DEREF_ARRAY:
            return build_ir_derefarray(ctx, &ast->binary);

        case MOJOSHADER_AST_OP_DEREF_STRUCT:
            if (ast->derefstruct.isswizzle)
                return build_ir_swizzle(ctx, &ast->derefstruct);
            return build_ir_derefstruct(ctx, &ast->derefstruct);

        case MOJOSHADER_AST_OP_COMMA:
            // evaluate and throw away left, return right.
            return new_ir_eseq(ctx, new_ir_expr_stmt(ctx, build_ir_expr(ctx, ast->binary.left)),
                               build_ir_expr(ctx, ast->binary.right));

        case MOJOSHADER_AST_OP_LESSTHAN: return EASY_IR_COMPARE(LT);
        case MOJOSHADER_AST_OP_GREATERTHAN: return EASY_IR_COMPARE(GT);
        case MOJOSHADER_AST_OP_LESSTHANOREQUAL: return EASY_IR_COMPARE(LEQ);
        case MOJOSHADER_AST_OP_GREATERTHANOREQUAL: return EASY_IR_COMPARE(GEQ);
        case MOJOSHADER_AST_OP_NOTEQUAL: return EASY_IR_COMPARE(NEQ);
        case MOJOSHADER_AST_OP_EQUAL: return EASY_IR_COMPARE(EQL);

        case MOJOSHADER_AST_OP_MULTIPLY: return EASY_IR_BINOP(MULTIPLY);
        case MOJOSHADER_AST_OP_DIVIDE: return EASY_IR_BINOP(DIVIDE);
        case MOJOSHADER_AST_OP_MODULO: return EASY_IR_BINOP(MODULO);
        case MOJOSHADER_AST_OP_ADD: return EASY_IR_BINOP(ADD);
        case MOJOSHADER_AST_OP_SUBTRACT: return EASY_IR_BINOP(SUBTRACT);
        case MOJOSHADER_AST_OP_LSHIFT: return EASY_IR_BINOP(LSHIFT);
        case MOJOSHADER_AST_OP_RSHIFT: return EASY_IR_BINOP(RSHIFT);
        case MOJOSHADER_AST_OP_BINARYAND: return EASY_IR_BINOP(AND);
        case MOJOSHADER_AST_OP_BINARYXOR: return EASY_IR_BINOP(XOR);
        case MOJOSHADER_AST_OP_BINARYOR: return EASY_IR_BINOP(OR);

        case MOJOSHADER_AST_OP_LOGICALAND:
            return build_ir_logical_and(ctx, &ast->binary);

        case MOJOSHADER_AST_OP_LOGICALOR:
            return build_ir_logical_or(ctx, &ast->binary);

        case MOJOSHADER_AST_OP_ASSIGN:
            return build_ir_assign(ctx, &ast->binary);

        case MOJOSHADER_AST_OP_MULASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_MULTIPLY, &ast->binary);
        case MOJOSHADER_AST_OP_DIVASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_DIVIDE, &ast->binary);
        case MOJOSHADER_AST_OP_MODASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_MODULO, &ast->binary);
        case MOJOSHADER_AST_OP_ADDASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_ADD, &ast->binary);
        case MOJOSHADER_AST_OP_SUBASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_SUBTRACT, &ast->binary);
        case MOJOSHADER_AST_OP_LSHIFTASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_LSHIFT, &ast->binary);
        case MOJOSHADER_AST_OP_RSHIFTASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_RSHIFT, &ast->binary);
        case MOJOSHADER_AST_OP_ANDASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_AND, &ast->binary);
        case MOJOSHADER_AST_OP_XORASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_XOR, &ast->binary);
        case MOJOSHADER_AST_OP_ORASSIGN: return build_ir_assign_binop(ctx, MOJOSHADER_IR_BINOP_OR, &ast->binary);

        case MOJOSHADER_AST_OP_CONDITIONAL:
            assert(ast->binary.left->datatype->type == MOJOSHADER_AST_DATATYPE_BOOL);
            return build_ir_compare(ctx, MOJOSHADER_IR_COND_EQL,
                                  build_ir_expr(ctx, ast->ternary.left),
                                  new_ir_constbool(ctx, 1),
                                  build_ir_expr(ctx, ast->ternary.center),
                                  build_ir_expr(ctx, ast->ternary.right));

        case MOJOSHADER_AST_OP_IDENTIFIER:
            return build_ir_identifier(ctx, &ast->identifier);

        case MOJOSHADER_AST_OP_INT_LITERAL:
            return new_ir_constint(ctx, ast->intliteral.value);

        case MOJOSHADER_AST_OP_FLOAT_LITERAL:
            return new_ir_constfloat(ctx, ast->floatliteral.value);

        case MOJOSHADER_AST_OP_BOOLEAN_LITERAL:
            return new_ir_constbool(ctx, ast->boolliteral.value);

        case MOJOSHADER_AST_OP_CALLFUNC:
            return build_ir_call(ctx, &ast->callfunc);

        case MOJOSHADER_AST_OP_CONSTRUCTOR:
            return build_ir_constructor(ctx, &ast->constructor);

        case MOJOSHADER_AST_OP_CAST:
            return build_ir_convert(ctx, &ast->cast);

        case MOJOSHADER_AST_STATEMENT_BREAK:
        {
            const LoopLabels *labels = ctx->ir_loop;
            assert(labels != NULL);  // semantic analysis should catch this.
            return new_ir_jump(ctx, labels->end);
        } // case

        case MOJOSHADER_AST_STATEMENT_CONTINUE:
        {
            const LoopLabels *labels = ctx->ir_loop;
            assert(labels != NULL);  // semantic analysis should catch this.
            return new_ir_jump(ctx, labels->start);
        } // case

        case MOJOSHADER_AST_STATEMENT_DISCARD:
            return new_ir_seq(ctx, new_ir_discard(ctx), build_ir_stmt(ctx, ast->discardstmt.next));

        case MOJOSHADER_AST_STATEMENT_EMPTY:
            return build_ir(ctx, ast->stmt.next);  // skip it, do next thing.

        case MOJOSHADER_AST_STATEMENT_EXPRESSION:
            return new_ir_seq(ctx, new_ir_expr_stmt(ctx, build_ir_expr(ctx, ast->exprstmt.expr)), build_ir_stmt(ctx, ast->exprstmt.next));

        case MOJOSHADER_AST_STATEMENT_IF:
            return build_ir_ifstmt(ctx, &ast->ifstmt);

        case MOJOSHADER_AST_STATEMENT_TYPEDEF:  // ignore this, move on.
            return build_ir(ctx, ast->typedefstmt.next);

        case MOJOSHADER_AST_STATEMENT_SWITCH:
            return build_ir_switch(ctx, &ast->switchstmt);

        case MOJOSHADER_AST_STATEMENT_STRUCT:  // ignore this, move on.
            return build_ir(ctx, ast->structstmt.next);

        case MOJOSHADER_AST_STATEMENT_VARDECL: // ignore this, move on.
            return build_ir(ctx, ast->vardeclstmt.next);

        case MOJOSHADER_AST_STATEMENT_BLOCK:
            return new_ir_seq(ctx, build_ir_stmt(ctx, ast->blockstmt.statements), build_ir_stmt(ctx, ast->blockstmt.next));

        case MOJOSHADER_AST_STATEMENT_FOR:
            return build_ir_forstmt(ctx, &ast->forstmt);

        case MOJOSHADER_AST_STATEMENT_DO:
            return build_ir_dostmt(ctx, &ast->dostmt);

        case MOJOSHADER_AST_STATEMENT_WHILE:
            return build_ir_whilestmt(ctx, &ast->whilestmt);

        case MOJOSHADER_AST_STATEMENT_RETURN:
        {
            const int label = ctx->ir_end;
            assert(label >= 0);  // parser should have caught this!
            MOJOSHADER_irStatement *retval = NULL;
            if (ast->returnstmt.expr != NULL)
            {
                // !!! FIXME: whole array/struct returns need to move more into the temp.
                const MOJOSHADER_astDataType *dt = reduce_datatype(ctx, ast->returnstmt.expr->datatype);
                const MOJOSHADER_astDataTypeType type = datatype_base(ctx, dt)->type;
                const int elems = datatype_elems(ctx, dt);
                assert(ctx->ir_ret >= 0);
                retval = new_ir_move(ctx, new_ir_temp(ctx, ctx->ir_ret, type, elems), build_ir_expr(ctx, ast->returnstmt.expr), -1);
            } // if
            return new_ir_seq(ctx, retval, new_ir_jump(ctx, label));
        } // case

        case MOJOSHADER_AST_COMPUNIT_TYPEDEF:
        case MOJOSHADER_AST_COMPUNIT_STRUCT:
        case MOJOSHADER_AST_COMPUNIT_VARIABLE:
        case MOJOSHADER_AST_COMPUNIT_FUNCTION:
        case MOJOSHADER_AST_ARGUMENTS:
        case MOJOSHADER_AST_OP_STRING_LITERAL:
        case MOJOSHADER_AST_SWITCH_CASE:
        case MOJOSHADER_AST_SCALAR_OR_ARRAY:
        case MOJOSHADER_AST_TYPEDEF:
        case MOJOSHADER_AST_FUNCTION_PARAMS:
        case MOJOSHADER_AST_FUNCTION_SIGNATURE:
        case MOJOSHADER_AST_STRUCT_DECLARATION:
        case MOJOSHADER_AST_STRUCT_MEMBER:
        case MOJOSHADER_AST_VARIABLE_DECLARATION:
        case MOJOSHADER_AST_ANNOTATION:
        case MOJOSHADER_AST_PACK_OFFSET:
        case MOJOSHADER_AST_VARIABLE_LOWLEVEL:
            assert(0 && "Shouldn't hit this in build_ir.");
            return NULL;

        default:
            assert(0 && "unexpected type");
            return NULL;
    } // switch
} // build_ir

static void print_ir(FILE *io, unsigned int depth, void *_ir)
{
    MOJOSHADER_irNode *ir = (MOJOSHADER_irNode *) _ir;
    if (ir == NULL)
        return;

    const char *fname = strrchr(ir->ir.filename, '/');
    if (fname != NULL)
        fname++;
    else
    {
        fname = strrchr(ir->ir.filename, '\\');
        if (fname != NULL)
            fname++;
        else
            fname = ir->ir.filename;
    } // else

    int i;
    for (i = 0; i < depth; i++)
        fprintf(io, "  ");
    depth++;

    fprintf(io, "[ %s:%d ", fname, ir->ir.line);

    switch (ir->ir.type)
    {
        case MOJOSHADER_IR_LABEL:
            fprintf(io, "LABEL %d ]\n", ir->stmt.label.index);
            break;

        case MOJOSHADER_IR_CONSTANT:
            fprintf(io, "CONSTANT ");
            switch (ir->expr.constant.info.type)
            {
                case MOJOSHADER_AST_DATATYPE_BOOL:
                case MOJOSHADER_AST_DATATYPE_INT:
                case MOJOSHADER_AST_DATATYPE_UINT:
                    for (i = 0; i < ir->expr.constant.info.elements-1; i++)
                        fprintf(io, "%d, ", ir->expr.constant.value.ival[i]);
                    if (ir->expr.constant.info.elements > 0)
                        fprintf(io, "%d", ir->expr.constant.value.ival[i]);
                    break;

                case MOJOSHADER_AST_DATATYPE_FLOAT:
                case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM:
                case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM:
                case MOJOSHADER_AST_DATATYPE_HALF:
                case MOJOSHADER_AST_DATATYPE_DOUBLE:
                    for (i = 0; i < ir->expr.constant.info.elements-1; i++)
                        fprintf(io, "%ff, ", ir->expr.constant.value.fval[i]);
                    if (ir->expr.constant.info.elements > 0)
                        fprintf(io, "%ff", ir->expr.constant.value.fval[i]);
                    break;

                default: assert(0 && "shouldn't happen");
            } // switch
            fprintf(io, " ]\n");
            break;

        case MOJOSHADER_IR_TEMP:
            fprintf(io, "TEMP %d ]\n", ir->expr.temp.index);
            break;

        case MOJOSHADER_IR_DISCARD:
            fprintf(io, "DISCARD ]\n");
            break;

        case MOJOSHADER_IR_SWIZZLE:
            fprintf(io, "SWIZZLE");
            for (i = 0; i < ir->expr.swizzle.info.elements-1; i++)
                fprintf(io, " %d", (int) ir->expr.swizzle.channels[i]);
            fprintf(io, " ]\n");
            print_ir(io, depth, ir->expr.swizzle.expr);
            break;

        case MOJOSHADER_IR_CONSTRUCT:
            fprintf(io, "CONSTRUCT ]\n");
            print_ir(io, depth, ir->expr.construct.args);
            break;

        case MOJOSHADER_IR_CONVERT:
            fprintf(io, "CONVERT ]\n");
            print_ir(io, depth, ir->expr.convert.expr);
            break;

        case MOJOSHADER_IR_BINOP:
            fprintf(io, "BINOP ");
            switch (ir->expr.binop.op)
            {
                #define PRINT_IR_BINOP(x) \
                    case MOJOSHADER_IR_BINOP_##x: fprintf(io, #x); break;
                PRINT_IR_BINOP(ADD)
                PRINT_IR_BINOP(SUBTRACT)
                PRINT_IR_BINOP(MULTIPLY)
                PRINT_IR_BINOP(DIVIDE)
                PRINT_IR_BINOP(MODULO)
                PRINT_IR_BINOP(AND)
                PRINT_IR_BINOP(OR)
                PRINT_IR_BINOP(XOR)
                PRINT_IR_BINOP(LSHIFT)
                PRINT_IR_BINOP(RSHIFT)
                PRINT_IR_BINOP(UNKNOWN)
                #undef PRINT_IR_BINOP
                default: assert(0 && "unexpected case"); break;
            } // switch
            fprintf(io, " ]\n");
            print_ir(io, depth, ir->expr.binop.left);
            print_ir(io, depth, ir->expr.binop.right);
            break;

        case MOJOSHADER_IR_MEMORY:
            fprintf(io, "MEMORY %d ]\n", ir->expr.memory.index);
            break;

        case MOJOSHADER_IR_CALL:
            fprintf(io, "CALL %d ]\n", ir->expr.call.index);
            print_ir(io, depth, ir->expr.call.args);
            break;

        case MOJOSHADER_IR_ESEQ:
            fprintf(io, "ESEQ ]\n");
            print_ir(io, depth, ir->expr.eseq.stmt);
            break;

        case MOJOSHADER_IR_ARRAY:
            fprintf(io, "ARRAY ]\n");
            print_ir(io, depth, ir->expr.array.array);
            print_ir(io, depth, ir->expr.array.element);
            break;

        case MOJOSHADER_IR_MOVE:
            fprintf(io, "MOVE ]\n");
            print_ir(io, depth, ir->stmt.move.dst);
            print_ir(io, depth, ir->stmt.move.src);
            break;

        case MOJOSHADER_IR_EXPR_STMT:
            fprintf(io, "EXPRSTMT ]\n");
            print_ir(io, depth, ir->stmt.expr.expr);
            break;

        case MOJOSHADER_IR_JUMP:
            fprintf(io, "JUMP %d ]\n", ir->stmt.jump.label);
            break;

        case MOJOSHADER_IR_CJUMP:
            fprintf(io, "CJUMP ");
            switch (ir->stmt.cjump.cond)
            {
                #define PRINT_IR_COND(x) \
                    case MOJOSHADER_IR_COND_##x: fprintf(io, #x); break;
                PRINT_IR_COND(EQL)
                PRINT_IR_COND(NEQ)
                PRINT_IR_COND(LT)
                PRINT_IR_COND(GT)
                PRINT_IR_COND(LEQ)
                PRINT_IR_COND(GEQ)
                PRINT_IR_COND(UNKNOWN)
                #undef PRINT_IR_COND
                default: assert(0 && "unexpected case"); break;
            } // switch
            fprintf(io, " %d %d ]\n", ir->stmt.cjump.iftrue, ir->stmt.cjump.iffalse);
            print_ir(io, depth, ir->stmt.cjump.left);
            print_ir(io, depth, ir->stmt.cjump.right);
            break;

        case MOJOSHADER_IR_SEQ:
            fprintf(io, "SEQ ]\n");
            print_ir(io, depth, ir->stmt.seq.first);
            print_ir(io, depth, ir->stmt.seq.next);  // !!! FIXME: don't recurse?
            break;

        case MOJOSHADER_IR_EXPRLIST:
            fprintf(io, "EXPRLIST ]\n");
            print_ir(io, depth, ir->misc.exprlist.expr);
            print_ir(io, depth, ir->misc.exprlist.next);  // !!! FIXME: don't recurse?
            break;

        default: assert(0 && "unexpected IR node"); break;
    } // switch
} // print_ir

static void print_whole_ir(Context *ctx, FILE *io)
{
    if (ctx->ir != NULL)
    {
        int i;
        for (i = 0; i <= ctx->user_func_index; i++)
        {
            printf("[FUNCTION %d ]\n", i);
            print_ir(io, 1, ctx->ir[i]);
        } // for
    } // if
} // print_whole_ir

static void delete_ir(Context *ctx, void *_ir)
{
    MOJOSHADER_irNode *ir = (MOJOSHADER_irNode *) _ir;
    if (ir == NULL)
        return;

    switch (ir->ir.type)
    {
        case MOJOSHADER_IR_JUMP:
        case MOJOSHADER_IR_LABEL:
        case MOJOSHADER_IR_CONSTANT:
        case MOJOSHADER_IR_TEMP:
        case MOJOSHADER_IR_DISCARD:
        case MOJOSHADER_IR_MEMORY:
            break;  // nothing extra to free here.

        case MOJOSHADER_IR_BINOP:
            delete_ir(ctx, ir->expr.binop.left);
            delete_ir(ctx, ir->expr.binop.right);
            break;

        case MOJOSHADER_IR_CALL:
            delete_ir(ctx, ir->expr.call.args);
            break;

        case MOJOSHADER_IR_ESEQ:
            delete_ir(ctx, ir->expr.eseq.stmt);
            delete_ir(ctx, ir->expr.eseq.expr);
            break;

        case MOJOSHADER_IR_ARRAY:
            delete_ir(ctx, ir->expr.array.array);
            delete_ir(ctx, ir->expr.array.element);
            break;

        case MOJOSHADER_IR_MOVE:
            delete_ir(ctx, ir->stmt.move.dst);
            delete_ir(ctx, ir->stmt.move.src);
            break;

        case MOJOSHADER_IR_EXPR_STMT:
            delete_ir(ctx, ir->stmt.expr.expr);
            break;

        case MOJOSHADER_IR_CJUMP:
            delete_ir(ctx, ir->stmt.cjump.left);
            delete_ir(ctx, ir->stmt.cjump.right);
            break;

        case MOJOSHADER_IR_SEQ:
            delete_ir(ctx, ir->stmt.seq.first);
            delete_ir(ctx, ir->stmt.seq.next);  // !!! FIXME: don't recurse?
            break;

        case MOJOSHADER_IR_EXPRLIST:
            delete_ir(ctx, ir->misc.exprlist.expr);
            delete_ir(ctx, ir->misc.exprlist.next);  // !!! FIXME: don't recurse?
            break;

        case MOJOSHADER_IR_SWIZZLE:
            delete_ir(ctx, ir->expr.swizzle.expr);
            break;

        case MOJOSHADER_IR_CONSTRUCT:
            delete_ir(ctx, ir->expr.construct.args);
            break;

        case MOJOSHADER_IR_CONVERT:
            delete_ir(ctx, ir->expr.convert.expr);
            break;

        default: assert(0 && "unexpected IR node"); break;
    } // switch

    Free(ctx, ir);
} // delete_ir

static void intermediate_representation(Context *ctx)
{
    const MOJOSHADER_astCompilationUnit *ast = NULL;
    const MOJOSHADER_astCompilationUnitFunction *astfn = NULL;
    const size_t arraylen = (ctx->user_func_index+1) * sizeof (MOJOSHADER_irStatement *);

    ctx->ir = (MOJOSHADER_irStatement **)Malloc(ctx, arraylen);
    if (ctx->ir == NULL)
        return;
    memset(ctx->ir, '\0', arraylen);

    ctx->ir_end = -1;
    ctx->ir_ret = -1;

    for (ast = &ctx->ast->compunit; ast != NULL; ast = ast->next)
    {
        assert(ast->ast.type > MOJOSHADER_AST_COMPUNIT_START_RANGE);
        assert(ast->ast.type < MOJOSHADER_AST_COMPUNIT_END_RANGE);
        if (ast->ast.type != MOJOSHADER_AST_COMPUNIT_FUNCTION)
            continue;  // only care about functions right now.

        astfn = (MOJOSHADER_astCompilationUnitFunction *) ast;
        if (astfn->definition == NULL)  // just a predeclare; skip.
            continue;

        assert(ctx->ir_loop == NULL);  // parser should have caught this!
        assert(ctx->ir_end < 0);  // parser should have caught this!
        assert(ctx->ir_ret < 0);  // parser should have caught this!
        const int start = generate_ir_label(ctx);  // !!! FIXME: store somewhere.
        const int end = generate_ir_label(ctx);
        ctx->ir_end = end;

        if (astfn->declaration->datatype != NULL)
            ctx->ir_ret = generate_ir_temp(ctx);

        MOJOSHADER_irStatement *funcseq = new_ir_seq(ctx, new_ir_label(ctx, start), build_ir_stmt(ctx, astfn->definition));
        funcseq = new_ir_seq(ctx, funcseq, new_ir_label(ctx, end));
        assert(ctx->ir_loop == NULL);  // parser should have caught this!
        ctx->ir_end = -1;
        ctx->ir_ret = -1;

        assert(astfn->index <= ctx->user_func_index);
        assert(ctx->ir[astfn->index] == NULL);
        ctx->ir[astfn->index] = funcseq;
    } // for

    print_whole_ir(ctx, stdout);

    // done with the AST, nuke it.
    // !!! FIXME: we're going to need CTAB data from this at some point.
    delete_compilation_unit(ctx, (MOJOSHADER_astCompilationUnit *) ctx->ast);
    ctx->ast = NULL;
} // intermediate_representation



static MOJOSHADER_astData MOJOSHADER_out_of_mem_ast_data = {
    1, &MOJOSHADER_out_of_mem_error, 0, 0, 0, 0, 0, 0
};


// !!! FIXME: cut and paste from assembler.
static const MOJOSHADER_astData *build_failed_ast(Context *ctx)
{
    assert(isfail(ctx));

    if (ctx->out_of_memory)
        return &MOJOSHADER_out_of_mem_ast_data;
        
    MOJOSHADER_astData *retval = NULL;
    retval = (MOJOSHADER_astData *) Malloc(ctx, sizeof (MOJOSHADER_astData));
    if (retval == NULL)
        return &MOJOSHADER_out_of_mem_ast_data;

    memset(retval, '\0', sizeof (MOJOSHADER_astData));
    retval->source_profile = ctx->source_profile;
    retval->malloc = (ctx->malloc == MOJOSHADER_internal_malloc) ? NULL : ctx->malloc;
    retval->free = (ctx->free == MOJOSHADER_internal_free) ? NULL : ctx->free;
    retval->malloc_data = ctx->malloc_data;
    retval->error_count = errorlist_count(ctx->errors);
    retval->errors = errorlist_flatten(ctx->errors);

    if (ctx->out_of_memory)
    {
        Free(ctx, retval);
        return &MOJOSHADER_out_of_mem_ast_data;
    } // if

    return retval;
} // build_failed_ast


static const MOJOSHADER_astData *build_astdata(Context *ctx)
{
    MOJOSHADER_astData *retval = NULL;

    if (ctx->out_of_memory)
        return &MOJOSHADER_out_of_mem_ast_data;

    retval = (MOJOSHADER_astData *) Malloc(ctx, sizeof (MOJOSHADER_astData));
    if (retval == NULL)
        return &MOJOSHADER_out_of_mem_ast_data;

    memset(retval, '\0', sizeof (MOJOSHADER_astData));
    retval->malloc = (ctx->malloc == MOJOSHADER_internal_malloc) ? NULL : ctx->malloc;
    retval->free = (ctx->free == MOJOSHADER_internal_free) ? NULL : ctx->free;
    retval->malloc_data = ctx->malloc_data;

    if (!isfail(ctx))
    {
        retval->source_profile = ctx->source_profile;
        retval->ast = ctx->ast;
    } // if

    retval->error_count = errorlist_count(ctx->errors);
    retval->errors = errorlist_flatten(ctx->errors);
    if (ctx->out_of_memory)
    {
        Free(ctx, retval);
        return &MOJOSHADER_out_of_mem_ast_data;
    } // if

    retval->opaque = ctx;

    return retval;
} // build_astdata


static void choose_src_profile(Context *ctx, const char *srcprofile)
{
    ctx->source_profile = srcprofile;

    #define TEST_PROFILE(x) if (strcmp(srcprofile, x) == 0) { return; }

    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_VS_1_1);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_VS_2_0);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_VS_3_0);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_1);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_2);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_3);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_4);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_2_0);
    TEST_PROFILE(MOJOSHADER_SRC_PROFILE_HLSL_PS_3_0);

    #undef TEST_PROFILE

    fail(ctx, "Unknown profile");
} // choose_src_profile


static MOJOSHADER_compileData MOJOSHADER_out_of_mem_compile_data = {
    1, &MOJOSHADER_out_of_mem_error, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};


// !!! FIXME: cut and paste from assembler.
static const MOJOSHADER_compileData *build_failed_compile(Context *ctx)
{
    assert(isfail(ctx));

    MOJOSHADER_compileData *retval = NULL;
    retval = (MOJOSHADER_compileData *) Malloc(ctx, sizeof (MOJOSHADER_compileData));
    if (retval == NULL)
        return &MOJOSHADER_out_of_mem_compile_data;

    memset(retval, '\0', sizeof (MOJOSHADER_compileData));
    retval->malloc = (ctx->malloc == MOJOSHADER_internal_malloc) ? NULL : ctx->malloc;
    retval->free = (ctx->free == MOJOSHADER_internal_free) ? NULL : ctx->free;
    retval->malloc_data = ctx->malloc_data;
    retval->source_profile = ctx->source_profile;
    retval->error_count = errorlist_count(ctx->errors);
    retval->errors = errorlist_flatten(ctx->errors);
    retval->warning_count = errorlist_count(ctx->warnings);
    retval->warnings = errorlist_flatten(ctx->warnings);

    if (ctx->out_of_memory)  // in case something failed up there.
    {
        MOJOSHADER_freeCompileData(retval);
        return &MOJOSHADER_out_of_mem_compile_data;
    } // if

    return retval;
} // build_failed_compile


static const MOJOSHADER_compileData *build_compiledata(Context *ctx)
{
    assert(!isfail(ctx));

    MOJOSHADER_compileData *retval = NULL;

    retval = (MOJOSHADER_compileData *) Malloc(ctx, sizeof (MOJOSHADER_compileData));
    if (retval == NULL)
        return &MOJOSHADER_out_of_mem_compile_data;

    memset(retval, '\0', sizeof (MOJOSHADER_compileData));
    retval->malloc = (ctx->malloc == MOJOSHADER_internal_malloc) ? NULL : ctx->malloc;
    retval->free = (ctx->free == MOJOSHADER_internal_free) ? NULL : ctx->free;
    retval->malloc_data = ctx->malloc_data;
    retval->source_profile = ctx->source_profile;

    if (!isfail(ctx))
    {
        // !!! FIXME: build output and output_len here.
    } // if

    if (!isfail(ctx))
    {
        // !!! FIXME: build symbols and symbol_count here.
    } // if

    retval->error_count = errorlist_count(ctx->errors);
    retval->errors = errorlist_flatten(ctx->errors);
    retval->warning_count = errorlist_count(ctx->warnings);
    retval->warnings = errorlist_flatten(ctx->warnings);

    if (ctx->out_of_memory)  // in case something failed up there.
    {
        MOJOSHADER_freeCompileData(retval);
        return &MOJOSHADER_out_of_mem_compile_data;
    } // if

    return retval;
} // build_compiledata


// API entry point...

// !!! FIXME: move this (and a lot of other things) to mojoshader_ast.c.
const MOJOSHADER_astData *MOJOSHADER_parseAst(const char *srcprofile,
                                    const char *filename, const char *source,
                                    unsigned int sourcelen,
                                    const MOJOSHADER_preprocessorDefine *defs,
                                    unsigned int define_count,
                                    MOJOSHADER_includeOpen include_open,
                                    MOJOSHADER_includeClose include_close,
                                    MOJOSHADER_malloc m, MOJOSHADER_free f,
                                    void *d)
{
    const MOJOSHADER_astData *retval = NULL;
    Context *ctx = NULL;

    if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) )
        return &MOJOSHADER_out_of_mem_ast_data;  // supply both or neither.

    ctx = build_context(m, f, d);
    if (ctx == NULL)
        return &MOJOSHADER_out_of_mem_ast_data;

    choose_src_profile(ctx, srcprofile);

    if (!isfail(ctx))
    {
        parse_source(ctx, filename, source, sourcelen, defs, define_count,
                     include_open, include_close);
    } // if

    if (!isfail(ctx))
        retval = build_astdata(ctx);  // ctx isn't destroyed yet!
    else
    {
        retval = (MOJOSHADER_astData *) build_failed_ast(ctx);
        destroy_context(ctx);
    } // else

    return retval;
} // MOJOSHADER_parseAst


void MOJOSHADER_freeAstData(const MOJOSHADER_astData *_data)
{
    MOJOSHADER_astData *data = (MOJOSHADER_astData *) _data;
    if ((data == NULL) || (data == &MOJOSHADER_out_of_mem_ast_data))
        return;  // no-op.

    // !!! FIXME: this needs to live for deleting the stringcache and the ast.
    Context *ctx = (Context *) data->opaque;
    MOJOSHADER_free f = (data->free == NULL) ? MOJOSHADER_internal_free : data->free;
    void *d = data->malloc_data;
    int i;

    // we don't f(data->source_profile), because that's internal static data.

    for (i = 0; i < data->error_count; i++)
    {
        f((void *) data->errors[i].error, d);
        f((void *) data->errors[i].filename, d);
    } // for
    f((void *) data->errors, d);

    // don't delete data->ast (it'll delete with the context).
    f(data, d);

    destroy_context(ctx);  // finally safe to destroy this.
} // MOJOSHADER_freeAstData


const MOJOSHADER_compileData *MOJOSHADER_compile(const char *srcprofile,
                                    const char *filename, const char *source,
                                    unsigned int sourcelen,
                                    const MOJOSHADER_preprocessorDefine *defs,
                                    unsigned int define_count,
                                    MOJOSHADER_includeOpen include_open,
                                    MOJOSHADER_includeClose include_close,
                                    MOJOSHADER_malloc m, MOJOSHADER_free f,
                                    void *d)
{
    // !!! FIXME: cut and paste from MOJOSHADER_parseAst().
    MOJOSHADER_compileData *retval = NULL;
    Context *ctx = NULL;

    if ( ((m == NULL) && (f != NULL)) || ((m != NULL) && (f == NULL)) )
        return &MOJOSHADER_out_of_mem_compile_data;  // supply both or neither.

    ctx = build_context(m, f, d);
    if (ctx == NULL)
        return &MOJOSHADER_out_of_mem_compile_data;

    choose_src_profile(ctx, srcprofile);

    if (!isfail(ctx))
    {
        parse_source(ctx, filename, source, sourcelen, defs, define_count,
                     include_open, include_close);
    } // if

    if (!isfail(ctx))
        semantic_analysis(ctx);

    if (!isfail(ctx))
        intermediate_representation(ctx);

    if (isfail(ctx))
        retval = (MOJOSHADER_compileData *) build_failed_compile(ctx);
    else
        retval = (MOJOSHADER_compileData *) build_compiledata(ctx);

    destroy_context(ctx);
    return retval;
} // MOJOSHADER_compile


void MOJOSHADER_freeCompileData(const MOJOSHADER_compileData *_data)
{
    MOJOSHADER_compileData *data = (MOJOSHADER_compileData *) _data;
    if ((data == NULL) || (data == &MOJOSHADER_out_of_mem_compile_data))
        return;  // no-op.

    MOJOSHADER_free f = (data->free == NULL) ? MOJOSHADER_internal_free : data->free;
    void *d = data->malloc_data;
    int i;

    // we don't f(data->source_profile), because that's internal static data.

    for (i = 0; i < data->error_count; i++)
    {
        f((void *) data->errors[i].error, d);
        f((void *) data->errors[i].filename, d);
    } // for
    f((void *) data->errors, d);

    for (i = 0; i < data->warning_count; i++)
    {
        f((void *) data->warnings[i].error, d);
        f((void *) data->warnings[i].filename, d);
    } // for
    f((void *) data->warnings, d);

    for (i = 0; i < data->symbol_count; i++)
    {
        f((void *) data->symbols[i].name, d);
        // !!! FIXME: this is missing stuff (including freeing substructs).
    } // for
    f((void *) data->symbols, d);

    f((void *) data->output, d);
    f(data, d);
} // MOJOSHADER_freeCompileData

// end of mojoshader_compiler.c ...

