mojoshader_compiler.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 15 Mar 2011 01:45:05 -0700
changeset 1011 0758e03a79d9
parent 1010 bbfed6061a2e
child 1012 04a23c4b0c70
permissions -rw-r--r--
Implemented the Intermediate Representation. This might need some tuning still, and will certainly need some passes to manipulate the IR tree even without optimizations. And, of course, this still needs to actually generate bytecode at some point.

/**
 * 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_irNode *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);
                userdt = NULL;
            } // 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 void 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);

    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

    // 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))
    {
        // there's already something called this.
        if (datatypes_match(dt, ((SymbolScope *) value)->datatype))
        {
            if (!just_declare)
                failf(ctx, "Function '%s' already defined.", sym);
            return;
        } // if
    } // while

    // push_symbol() doesn't check dupes, because we just did.
    push_symbol(ctx, &ctx->variables, sym, dt, idx, 0);
} // 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;
    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)
{
    assert( ((paramcount == 0) && (params == NULL)) ||
            ((paramcount > 0) && (params != 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_AST_DATATYPE_CONST;
        else
            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);
    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;
            MOJOSHADER_astArguments *prev = NULL;
            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);
                prev = arg;
                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);
            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;

        // !!! 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)
            {
                size_t i;
                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);

        delete_ir(ctx, ctx->ir);

        // !!! 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 = 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)
{
    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)
{
    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->info.type == src->info.type);
    assert(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->info.type == fval->info.type);
    assert(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;
            item = prev;
        } // else

        args = args->next;
    } // for

    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.
    MOJOSHADER_irExpression *expr = build_ir_expr(ctx, ast->identifier);
    MOJOSHADER_irExpression *finalexpr = expr;

    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");

    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->expr.binop.op)
            {
                #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 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;

    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;

printf("[FUNCTION %s ]\n", astfn->declaration->identifier); print_ir(stdout, 1, funcseq);
    } // for

    // done with the AST, nuke it.
    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);
        f((void *) data->symbols[i].default_value, d);
    } // for
    f((void *) data->symbols, d);

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

// end of mojoshader_compiler.c ...