utils/mojoshader-compiler.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 06 Dec 2010 02:47:31 -0500
changeset 964 e8c09c28162e
parent 956 7888858a6777
child 992 a951ce8bf2ec
permissions -rw-r--r--
Reworked datatype processing in the compiler. Now we can track this better without screwing around with strings. Also, printing the AST is sane now, since it never handled datatype strings correctly, anyhow.

/**
 * 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "mojoshader.h"

#ifdef _WIN32
#define snprintf _snprintf   // !!! FIXME: not a safe replacement!
#endif

static const char **include_paths = NULL;
static unsigned int include_path_count = 0;

#define MOJOSHADER_DEBUG_MALLOC 0

#if MOJOSHADER_DEBUG_MALLOC
static void *Malloc(int len, void *d)
{
    void *ptr = malloc(len + sizeof (int));
    int *store = (int *) ptr;
    printf("malloc() %d bytes (%p)\n", len, ptr);
    if (ptr == NULL) return NULL;
    *store = len;
    return (void *) (store + 1);
} // Malloc


static void Free(void *_ptr, void *d)
{
    int *ptr = (((int *) _ptr) - 1);
    int len = *ptr;
    printf("free() %d bytes (%p)\n", len, ptr);
    free(ptr);
} // Free
#else
#define Malloc NULL
#define Free NULL
#endif


static void fail(const char *err)
{
    printf("%s.\n", err);
    exit(1);
} // fail

static void print_unroll_attr(FILE *io, const int unroll)
{
    if (unroll == 0)
        fprintf(io, "[loop] ");
    else if (unroll < 0)
        fprintf(io, "[unroll] ");
    else
        fprintf(io, "[unroll(%d)] ", unroll);
} // print_unroll_attr

static void print_ast_datatype(FILE *io, const MOJOSHADER_astDataType *dt)
{
    int i;

    if (dt == NULL)
        return;

    switch (dt->type)
    {
        case MOJOSHADER_AST_DATATYPE_BOOL:
            fprintf(io, "bool");
            return;
        case MOJOSHADER_AST_DATATYPE_INT:
            fprintf(io, "int");
            return;
        case MOJOSHADER_AST_DATATYPE_UINT:
            fprintf(io, "uint");
            return;
        case MOJOSHADER_AST_DATATYPE_FLOAT:
            fprintf(io, "float");
            return;
        case MOJOSHADER_AST_DATATYPE_FLOAT_SNORM:
            fprintf(io, "snorm float");
            return;
        case MOJOSHADER_AST_DATATYPE_FLOAT_UNORM:
            fprintf(io, "unorm float");
            return;
        case MOJOSHADER_AST_DATATYPE_HALF:
            fprintf(io, "half");
            return;
        case MOJOSHADER_AST_DATATYPE_DOUBLE:
            fprintf(io, "double");
            return;
        case MOJOSHADER_AST_DATATYPE_STRING:
            fprintf(io, "string");
            return;
        case MOJOSHADER_AST_DATATYPE_SAMPLER_1D:
            fprintf(io, "sampler1D");
            return;
        case MOJOSHADER_AST_DATATYPE_SAMPLER_2D:
            fprintf(io, "sampler2D");
            return;
        case MOJOSHADER_AST_DATATYPE_SAMPLER_3D:
            fprintf(io, "sampler3D");
            return;
        case MOJOSHADER_AST_DATATYPE_SAMPLER_CUBE:
            fprintf(io, "samplerCUBE");
            return;
        case MOJOSHADER_AST_DATATYPE_SAMPLER_STATE:
            fprintf(io, "sampler_state");
            return;
        case MOJOSHADER_AST_DATATYPE_SAMPLER_COMPARISON_STATE:
            fprintf(io, "SamplerComparisonState");
            return;

        case MOJOSHADER_AST_DATATYPE_STRUCT:
            fprintf(io, "struct { ");
            for (i = 0; i < dt->structure.member_count; i++)
            {
                print_ast_datatype(io, dt->structure.members[i].datatype);
                fprintf(io, " %s; ", dt->structure.members[i].identifier);
            } // for
            fprintf(io, "}");
            return;

        case MOJOSHADER_AST_DATATYPE_ARRAY:
            print_ast_datatype(io, dt->array.base);
            if (dt->array.elements < 0)
                fprintf(io, "[]");
            else
                fprintf(io, "[%d]", dt->array.elements);
            return;

        case MOJOSHADER_AST_DATATYPE_VECTOR:
            fprintf(io, "vector<");
            print_ast_datatype(io, dt->vector.base);
            fprintf(io, ",%d>", dt->vector.elements);
            return;

        case MOJOSHADER_AST_DATATYPE_MATRIX:
            fprintf(io, "matrix<");
            print_ast_datatype(io, dt->matrix.base);
            fprintf(io, ",%d,%d>", dt->matrix.rows, dt->matrix.columns);
            return;

        case MOJOSHADER_AST_DATATYPE_BUFFER:
            fprintf(io, "buffer<");
            print_ast_datatype(io, dt->buffer.base);
            fprintf(io, ">");
            return;

        case MOJOSHADER_AST_DATATYPE_USER:
            fprintf(io, "%s", dt->user.name);
            return;

        //case MOJOSHADER_AST_DATATYPE_NONE:
        //case MOJOSHADER_AST_DATATYPE_FUNCTION:
        default:
            assert(0 && "Unexpected datatype.");
            return;
    } // switch
} // print_ast_datatype

// !!! FIXME: this screws up on order of operations.
static void print_ast(FILE *io, const int substmt, const void *_ast)
{
    const MOJOSHADER_astNode *ast = (const MOJOSHADER_astNode *) _ast;
    const char *nl = substmt ? "" : "\n";
    int typeint = 0;
    static int indent = 0;
    int isblock = 0;
    int i;

    // These _HAVE_ to be in the same order as MOJOSHADER_astNodeType!
    static const char *binary[] =
    {
        ",", "*", "/", "%", "+", "-", "<<", ">>", "<", ">", "<=", ">=", "==",
        "!=", "&", "^", "|", "&&", "||", "=", "*=", "/=", "%=", "+=", "-=",
        "<<=", ">>=", "&=", "^=", "|="
    };

    static const char *pre_unary[] = { "++", "--", "-", "~", "!" };
    static const char *post_unary[] = { "++", "--" };
    static const char *simple_stmt[] = { "", "break", "continue", "discard" };
    static const char *inpmod[] = { "", "in ", "out ", "in out ", "uniform " };
    static const char *fnstorage[] = { "", "inline " };

    static const char *interpmod[] = {
        "", " linear", " centroid", " nointerpolation",
        " noperspective", " sample"
    };

    if (!ast) return;

    typeint = (int) ast->ast.type;

    #define DO_INDENT do { \
        if (!substmt) { for (i = 0; i < indent; i++) fprintf(io, "    "); } \
    } while (0)

    switch (ast->ast.type)
    {
        case MOJOSHADER_AST_OP_PREINCREMENT:
        case MOJOSHADER_AST_OP_PREDECREMENT:
        case MOJOSHADER_AST_OP_NEGATE:
        case MOJOSHADER_AST_OP_COMPLEMENT:
        case MOJOSHADER_AST_OP_NOT:
            fprintf(io, "%s", pre_unary[(typeint-MOJOSHADER_AST_OP_START_RANGE_UNARY)-1]);
            print_ast(io, 0, ast->unary.operand);
            break;

        case MOJOSHADER_AST_OP_POSTINCREMENT:
        case MOJOSHADER_AST_OP_POSTDECREMENT:
            print_ast(io, 0, ast->unary.operand);
            fprintf(io, "%s", post_unary[typeint-MOJOSHADER_AST_OP_POSTINCREMENT]);
            break;

        case MOJOSHADER_AST_OP_MULTIPLY:
        case MOJOSHADER_AST_OP_DIVIDE:
        case MOJOSHADER_AST_OP_MODULO:
        case MOJOSHADER_AST_OP_ADD:
        case MOJOSHADER_AST_OP_SUBTRACT:
        case MOJOSHADER_AST_OP_LSHIFT:
        case MOJOSHADER_AST_OP_RSHIFT:
        case MOJOSHADER_AST_OP_LESSTHAN:
        case MOJOSHADER_AST_OP_GREATERTHAN:
        case MOJOSHADER_AST_OP_LESSTHANOREQUAL:
        case MOJOSHADER_AST_OP_GREATERTHANOREQUAL:
        case MOJOSHADER_AST_OP_EQUAL:
        case MOJOSHADER_AST_OP_NOTEQUAL:
        case MOJOSHADER_AST_OP_BINARYAND:
        case MOJOSHADER_AST_OP_BINARYXOR:
        case MOJOSHADER_AST_OP_BINARYOR:
        case MOJOSHADER_AST_OP_LOGICALAND:
        case MOJOSHADER_AST_OP_LOGICALOR:
        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:
        case MOJOSHADER_AST_OP_COMMA:
            print_ast(io, 0, ast->binary.left);
            if (ast->ast.type != MOJOSHADER_AST_OP_COMMA)
                fprintf(io, " ");  // no space before the comma.
            fprintf(io, "%s ", binary[
                (typeint - MOJOSHADER_AST_OP_START_RANGE_BINARY) - 1]);
            print_ast(io, 0, ast->binary.right);
            break;

        case MOJOSHADER_AST_OP_DEREF_ARRAY:
            print_ast(io, 0, ast->binary.left);
            fprintf(io, "[");
            print_ast(io, 0, ast->binary.right);
            fprintf(io, "]");
            break;

        case MOJOSHADER_AST_OP_DEREF_STRUCT:
            print_ast(io, 0, ast->derefstruct.identifier);
            fprintf(io, ".");
            fprintf(io, "%s", ast->derefstruct.member);
            break;

        case MOJOSHADER_AST_OP_CONDITIONAL:
            print_ast(io, 0, ast->ternary.left);
            fprintf(io, " ? ");
            print_ast(io, 0, ast->ternary.center);
            fprintf(io, " : ");
            print_ast(io, 0, ast->ternary.right);
            break;

        case MOJOSHADER_AST_OP_IDENTIFIER:
            fprintf(io, "%s", ast->identifier.identifier);
            break;

        case MOJOSHADER_AST_OP_INT_LITERAL:
            fprintf(io, "%d", ast->intliteral.value);
            break;

        case MOJOSHADER_AST_OP_FLOAT_LITERAL:
        {
            const float f = ast->floatliteral.value;
            const long long flr = (long long) f;
            if (((float) flr) == f)
                fprintf(io, "%lld.0", flr);
            else
                fprintf(io, "%.16g", f);
            break;
        } // case

        case MOJOSHADER_AST_OP_STRING_LITERAL:
            fprintf(io, "\"%s\"", ast->stringliteral.string);
            break;

        case MOJOSHADER_AST_OP_BOOLEAN_LITERAL:
            fprintf(io, "%s", ast->boolliteral.value ? "true" : "false");
            break;

        case MOJOSHADER_AST_ARGUMENTS:
            print_ast(io, 0, ast->arguments.argument);
            if (ast->arguments.next != NULL)
            {
                fprintf(io, ", ");
                print_ast(io, 0, ast->arguments.next);
            } // if
            break;

        case MOJOSHADER_AST_OP_CALLFUNC:
            print_ast(io, 0, ast->callfunc.identifier);
            fprintf(io, "(");
            print_ast(io, 0, ast->callfunc.args);
            fprintf(io, ")");
            break;

        case MOJOSHADER_AST_OP_CONSTRUCTOR:
            print_ast_datatype(io, ast->constructor.datatype);
            fprintf(io, "(");
            print_ast(io, 0, ast->constructor.args);
            fprintf(io, ")");
            break;

        case MOJOSHADER_AST_OP_CAST:
            fprintf(io, "(");
            print_ast_datatype(io, ast->cast.datatype);
            fprintf(io, ") (");
            print_ast(io, 0, ast->cast.operand);
            fprintf(io, ")");
            break;

        case MOJOSHADER_AST_STATEMENT_EXPRESSION:
            DO_INDENT;
            print_ast(io, 0, ast->exprstmt.expr);  // !!! FIXME: This is named badly...
            fprintf(io, ";%s", nl);
            print_ast(io, 0, ast->exprstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_IF:
            DO_INDENT;
            fprintf(io, "if (");
            print_ast(io, 0, ast->ifstmt.expr);
            fprintf(io, ")\n");
            isblock = ast->ifstmt.statement->ast.type == MOJOSHADER_AST_STATEMENT_BLOCK;
            if (!isblock) indent++;
            print_ast(io, 0, ast->ifstmt.statement);
            if (!isblock) indent--;
            print_ast(io, 0, ast->ifstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_TYPEDEF:
            DO_INDENT;
            print_ast(io, 1, ast->typedefstmt.type_info);
            fprintf(io, "%s", nl);
            print_ast(io, 0, ast->typedefstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_SWITCH:
            DO_INDENT;
            switch ( ast->switchstmt.attributes )
            {
                case MOJOSHADER_AST_SWITCHATTR_NONE: break;
                case MOJOSHADER_AST_SWITCHATTR_FLATTEN: fprintf(io, "[flatten] "); break;
                case MOJOSHADER_AST_SWITCHATTR_BRANCH: fprintf(io, "[branch] "); break;
                case MOJOSHADER_AST_SWITCHATTR_FORCECASE: fprintf(io, "[forcecase] "); break;
                case MOJOSHADER_AST_SWITCHATTR_CALL: fprintf(io, "[call] "); break;
            } // switch

            fprintf(io, "switch (");
            print_ast(io, 0, ast->switchstmt.expr);
            fprintf(io, ")\n");
            DO_INDENT;
            fprintf(io, "{\n");
            indent++;
            print_ast(io, 0, ast->switchstmt.cases);
            indent--;
            fprintf(io, "\n");
            DO_INDENT;
            fprintf(io, "}\n");
            print_ast(io, 0, ast->switchstmt.next);
            break;

        case MOJOSHADER_AST_SWITCH_CASE:
            DO_INDENT;
            fprintf(io, "case ");
            print_ast(io, 0, ast->cases.expr);
            fprintf(io, ":\n");
            isblock = ast->cases.statement->ast.type == MOJOSHADER_AST_STATEMENT_BLOCK;
            if (!isblock) indent++;
            print_ast(io, 0, ast->cases.statement);
            if (!isblock) indent--;
            print_ast(io, 0, ast->cases.next);
            break;

        case MOJOSHADER_AST_STATEMENT_STRUCT:
            DO_INDENT;
            print_ast(io, 0, ast->structstmt.struct_info);
            fprintf(io, ";%s%s", nl, nl);  // always space these out.
            print_ast(io, 0, ast->structstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_VARDECL:
            DO_INDENT;
            print_ast(io, 1, ast->vardeclstmt.declaration);
            fprintf(io, ";%s", nl);
            print_ast(io, 0, ast->vardeclstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_BLOCK:
            DO_INDENT;
            fprintf(io, "{\n");
            indent++;
            print_ast(io, 0, ast->blockstmt.statements);
            indent--;
            DO_INDENT;
            fprintf(io, "}\n");
            print_ast(io, 0, ast->blockstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_FOR:
            DO_INDENT;
            print_unroll_attr(io, ast->forstmt.unroll);
            fprintf(io, "for (");
            print_ast(io, 1, ast->forstmt.var_decl);
            if (ast->forstmt.initializer != NULL)
            {
                fprintf(io, " = ");
                print_ast(io, 1, ast->forstmt.initializer);
            } // if
            fprintf(io, "; ");
            print_ast(io, 1, ast->forstmt.looptest);
            fprintf(io, "; ");
            print_ast(io, 1, ast->forstmt.counter);

            fprintf(io, ")\n");
            isblock = ast->forstmt.statement->ast.type == MOJOSHADER_AST_STATEMENT_BLOCK;
            if (!isblock) indent++;
            print_ast(io, 0, ast->forstmt.statement);
            if (!isblock) indent--;

            print_ast(io, 0, ast->forstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_DO:
            DO_INDENT;
            print_unroll_attr(io, ast->dostmt.unroll);
            fprintf(io, "do\n");

            isblock = ast->dostmt.statement->ast.type == MOJOSHADER_AST_STATEMENT_BLOCK;
            if (!isblock) indent++;
            print_ast(io, 0, ast->dostmt.statement);
            if (!isblock) indent--;

            DO_INDENT;
            fprintf(io, "while (");
            print_ast(io, 0, ast->dostmt.expr);
            fprintf(io, ");\n");

            print_ast(io, 0, ast->dostmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_WHILE:
            DO_INDENT;
            print_unroll_attr(io, ast->whilestmt.unroll);
            fprintf(io, "while (");
            print_ast(io, 0, ast->whilestmt.expr);
            fprintf(io, ")\n");

            isblock = ast->whilestmt.statement->ast.type == MOJOSHADER_AST_STATEMENT_BLOCK;
            if (!isblock) indent++;
            print_ast(io, 0, ast->whilestmt.statement);
            if (!isblock) indent--;

            print_ast(io, 0, ast->whilestmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_RETURN:
            DO_INDENT;
            fprintf(io, "return");
            if (ast->returnstmt.expr)
            {
                fprintf(io, " ");
                print_ast(io, 0, ast->returnstmt.expr);
            } // if
            fprintf(io, ";%s", nl);
            print_ast(io, 0, ast->returnstmt.next);
            break;

        case MOJOSHADER_AST_STATEMENT_EMPTY:
        case MOJOSHADER_AST_STATEMENT_BREAK:
        case MOJOSHADER_AST_STATEMENT_CONTINUE:
        case MOJOSHADER_AST_STATEMENT_DISCARD:
            DO_INDENT;
            fprintf(io, "%s;%s",
                simple_stmt[(typeint-MOJOSHADER_AST_STATEMENT_START_RANGE)-1],
                nl);
            print_ast(io, 0, ast->stmt.next);
            break;

        case MOJOSHADER_AST_COMPUNIT_FUNCTION:
            DO_INDENT;
            print_ast(io, 0, ast->funcunit.declaration);
            if (ast->funcunit.definition == NULL)
                fprintf(io, ";%s", nl);
            else
            {
                fprintf(io, "%s", nl);
                print_ast(io, 0, ast->funcunit.definition);
                fprintf(io, "%s", nl);
            } // else
            print_ast(io, 0, ast->funcunit.next);
            break;

        case MOJOSHADER_AST_COMPUNIT_TYPEDEF:
            DO_INDENT;
            print_ast(io, 0, ast->typedefunit.type_info);
            fprintf(io, "%s", nl);
            print_ast(io, 0, ast->typedefunit.next);
            break;

        case MOJOSHADER_AST_COMPUNIT_STRUCT:
            DO_INDENT;
            print_ast(io, 0, ast->structunit.struct_info);
            fprintf(io, ";%s%s", nl, nl);  // always space these out.
            print_ast(io, 0, ast->structunit.next);
            break;

        case MOJOSHADER_AST_COMPUNIT_VARIABLE:
            DO_INDENT;
            print_ast(io, 1, ast->varunit.declaration);
            fprintf(io, ";%s", nl);
            if (ast->varunit.next &&
                ast->varunit.next->ast.type!=MOJOSHADER_AST_COMPUNIT_VARIABLE)
            {
                fprintf(io, "%s", nl);  // group vars together, and space out other things.
            } // if
            print_ast(io, 0, ast->varunit.next);
            break;

        case MOJOSHADER_AST_SCALAR_OR_ARRAY:
            fprintf(io, "%s", ast->soa.identifier);
            if (ast->soa.isarray)
            {
                fprintf(io, "[");
                print_ast(io, 0, ast->soa.dimension);
                fprintf(io, "]");
            } // if
            break;

        case MOJOSHADER_AST_TYPEDEF:
            DO_INDENT;
            fprintf(io, "typedef %s", ast->typdef.isconst ? "const " : "");
            print_ast_datatype(io, ast->typdef.datatype);
            fprintf(io, " ");
            print_ast(io, 0, ast->typdef.details);
            fprintf(io, ";%s", nl);
            break;

        case MOJOSHADER_AST_FUNCTION_PARAMS:
            fprintf(io, "%s", inpmod[(int) ast->params.input_modifier]);
            print_ast_datatype(io, ast->params.datatype);
            fprintf(io, " %s", ast->params.identifier);
            if (ast->params.semantic)
                fprintf(io, " : %s", ast->params.semantic);
            fprintf(io, "%s", interpmod[(int) ast->params.interpolation_modifier]);

            if (ast->params.initializer)
            {
                fprintf(io, " = ");
                print_ast(io, 0, ast->params.initializer);
            } // if

            if (ast->params.next)
            {
                fprintf(io, ", ");
                print_ast(io, 0, ast->params.next);
            } // if
            break;

        case MOJOSHADER_AST_FUNCTION_SIGNATURE:
            fprintf(io, "%s", fnstorage[(int) ast->funcsig.storage_class]);
            if (ast->funcsig.datatype)
                print_ast_datatype(io, ast->funcsig.datatype);
            else
                fprintf(io, "void");
            fprintf(io, " %s(", ast->funcsig.identifier);
            print_ast(io, 0, ast->funcsig.params);
            fprintf(io, ")");
            if (ast->funcsig.semantic)
                fprintf(io, " : %s", ast->funcsig.semantic);
            break;

        case MOJOSHADER_AST_STRUCT_DECLARATION:
            fprintf(io, "struct %s\n", ast->structdecl.name);
            DO_INDENT;
            fprintf(io, "{\n");
            indent++;
            print_ast(io, 0, ast->structdecl.members);
            indent--;
            DO_INDENT;
            fprintf(io, "}");
            break;

        case MOJOSHADER_AST_STRUCT_MEMBER:
            DO_INDENT;
            fprintf(io, "%s", interpmod[(int)ast->structmembers.interpolation_mod]);
            print_ast_datatype(io, ast->structmembers.datatype);
            fprintf(io, " ");
            print_ast(io, 0, ast->structmembers.details);
            if (ast->structmembers.semantic)
                fprintf(io, " : %s", ast->structmembers.semantic);
            fprintf(io, ";%s", nl);
            print_ast(io, 0, ast->structmembers.next);
            break;

        case MOJOSHADER_AST_VARIABLE_DECLARATION:
            DO_INDENT;
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_EXTERN)
                fprintf(io, "extern ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_NOINTERPOLATION)
                fprintf(io, "nointerpolation ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_SHARED)
                fprintf(io, "shared");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_STATIC)
                fprintf(io, "static ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_UNIFORM)
                fprintf(io, "uniform ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_VOLATILE)
                fprintf(io, "nointerpolation ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_CONST)
                fprintf(io, "const ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_ROWMAJOR)
                fprintf(io, "rowmajor ");
            if (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_COLUMNMAJOR)
                fprintf(io, "columnmajor ");

            if (ast->vardecl.datatype)
                print_ast_datatype(io, ast->vardecl.datatype);
            else
                print_ast(io, 0, ast->vardecl.anonymous_datatype);
            fprintf(io, " ");
            print_ast(io, 0, ast->vardecl.details);
            if (ast->vardecl.semantic)
                fprintf(io, " : %s", ast->vardecl.semantic);
            if (ast->vardecl.annotations)
            {
                fprintf(io, " ");
                print_ast(io, 0, ast->vardecl.annotations);
            } // if
            if (ast->vardecl.initializer != NULL)
            {
                fprintf(io, " = ");
                print_ast(io, 0, ast->vardecl.initializer);
            } // if
            print_ast(io, 0, ast->vardecl.lowlevel);

            if (ast->vardecl.next == NULL)
                fprintf(io, "%s", nl);
            else
            {
                const int attr = ast->vardecl.next->attributes;
                fprintf(io, ", ");
                ast->vardecl.next->attributes = 0;
                print_ast(io, 1, ast->vardecl.next);
                ast->vardecl.next->attributes = attr;
            } // if
            break;

        case MOJOSHADER_AST_PACK_OFFSET:
            fprintf(io, " : packoffset(%s%s%s)", ast->packoffset.ident1,
                    ast->packoffset.ident2 ? "." : "",
                    ast->packoffset.ident2 ? ast->packoffset.ident2 : "");
            break;

        case MOJOSHADER_AST_VARIABLE_LOWLEVEL:
            print_ast(io, 0, ast->varlowlevel.packoffset);
            if (ast->varlowlevel.register_name)
                fprintf(io, " : register(%s)", ast->varlowlevel.register_name);
            break;

        case MOJOSHADER_AST_ANNOTATION:
        {
            const MOJOSHADER_astAnnotations *a = &ast->annotations;
            fprintf(io, "<");
            while (a)
            {
                fprintf(io, " ");
                print_ast_datatype(io, a->datatype);
                if (a->initializer != NULL)
                {
                    fprintf(io, " = ");
                    print_ast(io, 0, a->initializer);
                } // if
                if (a->next)
                    fprintf(io, ",");
                a = a->next;
            } // while
            fprintf(io, " >");
            break;
        } // case

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

    #undef DO_INDENT
} // print_ast


static int open_include(MOJOSHADER_includeType inctype, const char *fname,
                        const char *parent, const char **outdata,
                        unsigned int *outbytes, MOJOSHADER_malloc m,
                        MOJOSHADER_free f, void *d)
{
    int i;
    for (i = 0; i < include_path_count; i++)
    {
        const char *path = include_paths[i];
        const size_t len = strlen(path) + strlen(fname) + 2;
        char *buf = (char *) m(len, d);
        if (buf == NULL)
            return 0;

        snprintf(buf, len, "%s/%s", path, fname);
        FILE *io = fopen(buf, "rb");
        f(buf, d);
        if (io == NULL)
            continue;

        if (fseek(io, 0, SEEK_END) != -1)
        {
            const long fsize = ftell(io);
            if ((fsize == -1) || (fseek(io, 0, SEEK_SET) == -1))
            {
                fclose(io);
                return 0;
            } // if

            char *data = (char *) m(fsize, d);
            if (data == NULL)
            {
                fclose(io);
                return 0;
            } // if

            if (fread(data, fsize, 1, io) != 1)
            {
                f(data, d);
                fclose(io);
                return 0;
            } // if

            fclose(io);
            *outdata = data;
            *outbytes = (unsigned int) fsize;
            return 1;
        } // if
    } // for

    return 0;
} // open_include


static void close_include(const char *data, MOJOSHADER_malloc m,
                          MOJOSHADER_free f, void *d)
{
    f((void *) data, d);
} // close_include


static int preprocess(const char *fname, const char *buf, int len,
                      const char *outfile,
                      const MOJOSHADER_preprocessorDefine *defs,
                      unsigned int defcount, FILE *io)
{
    const MOJOSHADER_preprocessData *pd;
    int retval = 0;

    pd = MOJOSHADER_preprocess(fname, buf, len, defs, defcount, open_include,
                               close_include, Malloc, Free, NULL);

    if (pd->error_count > 0)
    {
        int i;
        for (i = 0; i < pd->error_count; i++)
        {
            fprintf(stderr, "%s:%d: ERROR: %s\n",
                    pd->errors[i].filename ? pd->errors[i].filename : "???",
                    pd->errors[i].error_position,
                    pd->errors[i].error);
        } // for
    } // if
    else
    {
        if (pd->output != NULL)
        {
            const int len = pd->output_len;
            if ((len) && (fwrite(pd->output, len, 1, io) != 1))
                printf(" ... fwrite('%s') failed.\n", outfile);
            else if ((outfile != NULL) && (fclose(io) == EOF))
                printf(" ... fclose('%s') failed.\n", outfile);
            else
                retval = 1;
        } // if
    } // else
    MOJOSHADER_freePreprocessData(pd);

    return retval;
} // preprocess


static int assemble(const char *fname, const char *buf, int len,
                    const char *outfile,
                    const MOJOSHADER_preprocessorDefine *defs,
                    unsigned int defcount, FILE *io)
{
    const MOJOSHADER_parseData *pd;
    int retval = 0;

    pd = MOJOSHADER_assemble(fname, buf, len, NULL, 0, NULL, 0,
                             defs, defcount, open_include, close_include,
                             Malloc, Free, NULL);

    if (pd->error_count > 0)
    {
        int i;
        for (i = 0; i < pd->error_count; i++)
        {
            fprintf(stderr, "%s:%d: ERROR: %s\n",
                    pd->errors[i].filename ? pd->errors[i].filename : "???",
                    pd->errors[i].error_position,
                    pd->errors[i].error);
        } // for
    } // if
    else
    {
        if (pd->output != NULL)
        {
            const int len = pd->output_len;
            if ((len) && (fwrite(pd->output, len, 1, io) != 1))
                printf(" ... fwrite('%s') failed.\n", outfile);
            else if ((outfile != NULL) && (fclose(io) == EOF))
                printf(" ... fclose('%s') failed.\n", outfile);
            else
                retval = 1;
        } // if
    } // else
    MOJOSHADER_freeParseData(pd);

    return retval;
} // assemble

static int ast(const char *fname, const char *buf, int len,
               const char *outfile, const MOJOSHADER_preprocessorDefine *defs,
               unsigned int defcount, FILE *io)
{
    const MOJOSHADER_astData *ad;
    int retval = 0;

    ad = MOJOSHADER_parseAst(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_1,  // !!! FIXME
                        fname, buf, len, defs, defcount,
                        open_include, close_include, Malloc, Free, NULL);
    
    if (ad->error_count > 0)
    {
        int i;
        for (i = 0; i < ad->error_count; i++)
        {
            fprintf(stderr, "%s:%d: ERROR: %s\n",
                    ad->errors[i].filename ? ad->errors[i].filename : "???",
                    ad->errors[i].error_position,
                    ad->errors[i].error);
        } // for
    } // if
    else
    {
        print_ast(io, 0, ad->ast);
        if ((outfile != NULL) && (fclose(io) == EOF))
            printf(" ... fclose('%s') failed.\n", outfile);
        else
            retval = 1;
    } // else
    MOJOSHADER_freeAstData(ad);

    return retval;
} // ast

static int compile(const char *fname, const char *buf, int len,
                    const char *outfile,
                    const MOJOSHADER_preprocessorDefine *defs,
                    unsigned int defcount, FILE *io)
{
    // !!! FIXME: write me.
    //const MOJOSHADER_parseData *pd;
    //int retval = 0;

    MOJOSHADER_compile(MOJOSHADER_SRC_PROFILE_HLSL_PS_1_1,  // !!! FIXME
                        fname, buf, len, defs, defcount,
                             open_include, close_include,
                             Malloc, Free, NULL);
    return 1;
} // compile

typedef enum
{
    ACTION_UNKNOWN,
    ACTION_VERSION,
    ACTION_PREPROCESS,
    ACTION_ASSEMBLE,
    ACTION_AST,
    ACTION_COMPILE,
} Action;


int main(int argc, char **argv)
{
    Action action = ACTION_UNKNOWN;
    int retval = 1;
    const char *infile = NULL;
    const char *outfile = NULL;
    int i;

    MOJOSHADER_preprocessorDefine *defs = NULL;
    unsigned int defcount = 0;

    include_paths = (const char **) malloc(sizeof (char *));
    include_paths[0] = ".";
    include_path_count = 1;

    // !!! FIXME: clean this up.
    for (i = 1; i < argc; i++)
    {
        const char *arg = argv[i];

        if (strcmp(arg, "-P") == 0)
        {
            if ((action != ACTION_UNKNOWN) && (action != ACTION_PREPROCESS))
                fail("Multiple actions specified");
            action = ACTION_PREPROCESS;
        } // if

        else if (strcmp(arg, "-A") == 0)
        {
            if ((action != ACTION_UNKNOWN) && (action != ACTION_ASSEMBLE))
                fail("Multiple actions specified");
            action = ACTION_ASSEMBLE;
        } // else if

        else if (strcmp(arg, "-T") == 0)
        {
            if ((action != ACTION_UNKNOWN) && (action != ACTION_AST))
                fail("Multiple actions specified");
            action = ACTION_AST;
        } // else if

        else if (strcmp(arg, "-C") == 0)
        {
            if ((action != ACTION_UNKNOWN) && (action != ACTION_COMPILE))
                fail("Multiple actions specified");
            action = ACTION_COMPILE;
        } // else if

        else if ((strcmp(arg, "-V") == 0) || (strcmp(arg, "--version") == 0))
        {
            if ((action != ACTION_UNKNOWN) && (action != ACTION_VERSION))
                fail("Multiple actions specified");
            action = ACTION_VERSION;
        } // else if

        else if (strcmp(arg, "-o") == 0)
        {
            if (outfile != NULL)
                fail("multiple output files specified");

            arg = argv[++i];
            if (arg == NULL)
                fail("no filename after '-o'");
            outfile = arg;
        } // if

        else if (strcmp(arg, "-I") == 0)
        {
            arg = argv[++i];
            if (arg == NULL)
                fail("no path after '-I'");

            include_paths = (const char **) realloc(include_paths,
                       (include_path_count+1) * sizeof (char *));
            include_paths[include_path_count] = arg;
            include_path_count++;
        } // if

        else if (strncmp(arg, "-D", 2) == 0)
        {
            arg += 2;
            char *ident = strdup(arg);
            char *ptr = strchr(ident, '=');
            const char *val = "";
            if (ptr)
            {
                *ptr = '\0';
                val = ptr+1;
            } // if

            defs = (MOJOSHADER_preprocessorDefine *) realloc(defs,
                       (defcount+1) * sizeof (MOJOSHADER_preprocessorDefine));
            defs[defcount].identifier = ident;
            defs[defcount].definition = val;
            defcount++;
        } // else if

        else
        {
            if (infile != NULL)
                fail("multiple input files specified");
            infile = arg;
        } // else
    } // for

    if (action == ACTION_UNKNOWN)
        action = ACTION_ASSEMBLE;

    if (action == ACTION_VERSION)
    {
        printf("mojoshader-compiler, changeset %s\n", MOJOSHADER_CHANGESET);
        return 0;
    } // if

    if (infile == NULL)
        fail("no input file specified");

    FILE *io = fopen(infile, "rb");
    if (io == NULL)
        fail("failed to open input file");

    fseek(io, 0, SEEK_END);
    long fsize = ftell(io);
    fseek(io, 0, SEEK_SET);
    if (fsize == -1)
        fsize = 1000000;
    char *buf = (char *) malloc(fsize);
    const int rc = fread(buf, 1, fsize, io);
    fclose(io);
    if (rc == EOF)
        fail("failed to read input file");

    FILE *outio = outfile ? fopen(outfile, "wb") : stdout;
    if (outio == NULL)
        fail("failed to open output file");


    if (action == ACTION_PREPROCESS)
        retval = (!preprocess(infile, buf, rc, outfile, defs, defcount, outio));
    else if (action == ACTION_ASSEMBLE)
        retval = (!assemble(infile, buf, rc, outfile, defs, defcount, outio));
    else if (action == ACTION_AST)
        retval = (!ast(infile, buf, rc, outfile, defs, defcount, outio));
    else if (action == ACTION_COMPILE)
        retval = (!compile(infile, buf, rc, outfile, defs, defcount, outio));

    if ((retval != 0) && (outfile != NULL))
        remove(outfile);

    free(buf);

    for (i = 0; i < defcount; i++)
        free((void *) defs[i].identifier);
    free(defs);

    free(include_paths);

    return retval;
} // main

// end of mojoshader-compiler.c ...