Reworked datatype processing in the compiler.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 06 Dec 2010 02:47:31 -0500
changeset 964 e8c09c28162e
parent 963 5a8f7ef13735
child 965 6284deccb61e
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.h
mojoshader_compiler.c
mojoshader_parser_hlsl.lemon
utils/mojoshader-compiler.c
--- a/mojoshader.h	Mon Dec 06 02:42:31 2010 -0500
+++ b/mojoshader.h	Mon Dec 06 02:47:31 2010 -0500
@@ -916,8 +916,118 @@
  *
  * ALSO: This API is still evolving! We make no promises at this time to keep
  *  source or binary compatibility for the AST pieces.
+ *
+ * Important notes:
+ *  - ASTs are the result of parsing the source code: a program that fails to
+ *    compile will often parse successfully. Undeclared variables,
+ *    type incompatibilities, etc, aren't detected at this point.
+ *  - Vector swizzles (the ".xyzw" part of "MyVec4.xyzw") will look like
+ *    structure dereferences. We don't realize these are actually swizzles
+ *    until semantic analysis.
+ *  - MOJOSHADER_astDataType info is not reliable when returned from
+ *    MOJOSHADER_parseAst()! Most of the datatype info will be missing or have
+ *    inaccurate data types. We sort these out during semantic analysis, which
+ *    happens after the AST parsing is complete. A few are filled in, or can
+ *    be deduced fairly trivially by processing several pieces into one.
+ *    It's enough that you can reproduce the original source code, more or
+ *    less, from the AST.
  */
 
+/* High-level datatypes for AST nodes. */
+typedef enum MOJOSHADER_astDataTypeType
+{
+    MOJOSHADER_AST_DATATYPE_NONE,
+    MOJOSHADER_AST_DATATYPE_BOOL,
+    MOJOSHADER_AST_DATATYPE_INT,
+    MOJOSHADER_AST_DATATYPE_UINT,
+    MOJOSHADER_AST_DATATYPE_FLOAT,
+    MOJOSHADER_AST_DATATYPE_FLOAT_SNORM,
+    MOJOSHADER_AST_DATATYPE_FLOAT_UNORM,
+    MOJOSHADER_AST_DATATYPE_HALF,
+    MOJOSHADER_AST_DATATYPE_DOUBLE,
+    MOJOSHADER_AST_DATATYPE_STRING,
+    MOJOSHADER_AST_DATATYPE_SAMPLER_1D,
+    MOJOSHADER_AST_DATATYPE_SAMPLER_2D,
+    MOJOSHADER_AST_DATATYPE_SAMPLER_3D,
+    MOJOSHADER_AST_DATATYPE_SAMPLER_CUBE,
+    MOJOSHADER_AST_DATATYPE_SAMPLER_STATE,
+    MOJOSHADER_AST_DATATYPE_SAMPLER_COMPARISON_STATE,
+    MOJOSHADER_AST_DATATYPE_STRUCT,
+    MOJOSHADER_AST_DATATYPE_ARRAY,
+    MOJOSHADER_AST_DATATYPE_VECTOR,
+    MOJOSHADER_AST_DATATYPE_MATRIX,
+    MOJOSHADER_AST_DATATYPE_BUFFER,
+    MOJOSHADER_AST_DATATYPE_FUNCTION,
+    MOJOSHADER_AST_DATATYPE_USER,
+} MOJOSHADER_astDataTypeType;
+#define MOJOSHADER_AST_DATATYPE_CONST (1 << 31)
+
+typedef union MOJOSHADER_astDataType MOJOSHADER_astDataType;
+
+// This is just part of DataTypeStruct, never appears outside of it.
+typedef struct MOJOSHADER_astDataTypeStructMember
+{
+    const MOJOSHADER_astDataType *datatype;
+    const char *identifier;
+} MOJOSHADER_astDataTypeStructMember;
+
+typedef struct MOJOSHADER_astDataTypeStruct
+{
+    MOJOSHADER_astDataTypeType type;
+    const MOJOSHADER_astDataTypeStructMember *members;
+    int member_count;
+} MOJOSHADER_astDataTypeStruct;
+
+typedef struct MOJOSHADER_astDataTypeArray
+{
+    MOJOSHADER_astDataTypeType type;
+    const MOJOSHADER_astDataType *base;
+    int elements;
+} MOJOSHADER_astDataTypeArray;
+
+typedef MOJOSHADER_astDataTypeArray MOJOSHADER_astDataTypeVector;
+
+typedef struct MOJOSHADER_astDataTypeMatrix
+{
+    MOJOSHADER_astDataTypeType type;
+    const MOJOSHADER_astDataType *base;
+    int rows;
+    int columns;
+} MOJOSHADER_astDataTypeMatrix;
+
+typedef struct MOJOSHADER_astDataTypeBuffer
+{
+    MOJOSHADER_astDataTypeType type;
+    const MOJOSHADER_astDataType *base;
+} MOJOSHADER_astDataTypeBuffer;
+
+typedef struct MOJOSHADER_astDataTypeFunction
+{
+    MOJOSHADER_astDataTypeType type;
+    const MOJOSHADER_astDataType *retval;
+    const MOJOSHADER_astDataType *params;
+    int num_params;
+} MOJOSHADER_astDataTypeFunction;
+
+typedef struct MOJOSHADER_astDataTypeUser
+{
+    MOJOSHADER_astDataTypeType type;
+    const MOJOSHADER_astDataType *details;
+    const char *name;
+} MOJOSHADER_astDataTypeUser;
+
+union MOJOSHADER_astDataType
+{
+    MOJOSHADER_astDataTypeType type;
+    MOJOSHADER_astDataTypeArray array;
+    MOJOSHADER_astDataTypeStruct structure;
+    MOJOSHADER_astDataTypeVector vector;
+    MOJOSHADER_astDataTypeMatrix matrix;
+    MOJOSHADER_astDataTypeBuffer buffer;
+    MOJOSHADER_astDataTypeUser user;
+    MOJOSHADER_astDataTypeFunction function;
+};
+
 /* Structures that make up the parse tree... */
 
 typedef enum MOJOSHADER_astNodeType
@@ -1077,7 +1187,11 @@
     MOJOSHADER_astNodeInfo ast;
 } MOJOSHADER_astGeneric;
 
-typedef MOJOSHADER_astGeneric MOJOSHADER_astExpression;
+typedef struct MOJOSHADER_astExpression
+{
+    MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
+} MOJOSHADER_astExpression;
 
 typedef struct MOJOSHADER_astArguments
 {
@@ -1089,12 +1203,14 @@
 typedef struct MOJOSHADER_astExpressionUnary
 {
     MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *operand;
 } MOJOSHADER_astExpressionUnary;
 
 typedef struct MOJOSHADER_astExpressionBinary
 {
     MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *left;
     MOJOSHADER_astExpression *right;
 } MOJOSHADER_astExpressionBinary;
@@ -1102,6 +1218,7 @@
 typedef struct MOJOSHADER_astExpressionTernary
 {
     MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *left;
     MOJOSHADER_astExpression *center;
     MOJOSHADER_astExpression *right;
@@ -1110,50 +1227,58 @@
 typedef struct MOJOSHADER_astExpressionIdentifier
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_IDENTIFIER */
+    const MOJOSHADER_astDataType *datatype;
     const char *identifier;
 } MOJOSHADER_astExpressionIdentifier;
 
 typedef struct MOJOSHADER_astExpressionIntLiteral
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_INT_LITERAL */
+    const MOJOSHADER_astDataType *datatype;  /* always AST_DATATYPE_INT */
     int value;
 } MOJOSHADER_astExpressionIntLiteral;
 
 typedef struct MOJOSHADER_astExpressionFloatLiteral
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_FLOAT_LITERAL */
+    const MOJOSHADER_astDataType *datatype;  /* always AST_DATATYPE_FLOAT */
     double value;
 } MOJOSHADER_astExpressionFloatLiteral;
 
 typedef struct MOJOSHADER_astExpressionStringLiteral
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_STRING_LITERAL */
+    const MOJOSHADER_astDataType *datatype;  /* always AST_DATATYPE_STRING */
     const char *string;
 } MOJOSHADER_astExpressionStringLiteral;
 
 typedef struct MOJOSHADER_astExpressionBooleanLiteral
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_BOOLEAN_LITERAL */
+    const MOJOSHADER_astDataType *datatype;  /* always AST_DATATYPE_BOOL */
     int value;  /* Always 1 or 0. */
 } MOJOSHADER_astExpressionBooleanLiteral;
 
 typedef struct MOJOSHADER_astExpressionConstructor
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_CONSTRUCTOR */
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astArguments *args;
 } MOJOSHADER_astExpressionConstructor;
 
 typedef struct MOJOSHADER_astExpressionDerefStruct
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_DEREF_STRUCT */
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *identifier;
     const char *member;
+    int isswizzle;  /* Always 1 or 0. Never set by parseAst()! */
 } MOJOSHADER_astExpressionDerefStruct;
 
 typedef struct MOJOSHADER_astExpressionCallFunction
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_CALLFUNC */
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *identifier;
     MOJOSHADER_astArguments *args;
 } MOJOSHADER_astExpressionCallFunction;
@@ -1161,7 +1286,7 @@
 typedef struct MOJOSHADER_astExpressionCast
 {
     MOJOSHADER_astNodeInfo ast;  /* Always MOJOSHADER_AST_OP_CAST */
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *operand;
 } MOJOSHADER_astExpressionCast;
 
@@ -1199,8 +1324,8 @@
 typedef struct MOJOSHADER_astFunctionParameters
 {
     MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astInputModifier input_modifier;
-    const char *datatype;
     const char *identifier;
     const char *semantic;
     MOJOSHADER_astInterpolationModifier interpolation_modifier;
@@ -1211,7 +1336,7 @@
 typedef struct MOJOSHADER_astFunctionSignature
 {
     MOJOSHADER_astNodeInfo ast;
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     const char *identifier;
     MOJOSHADER_astFunctionParameters *params;
     MOJOSHADER_astFunctionStorageClass storage_class;
@@ -1229,7 +1354,7 @@
 typedef struct MOJOSHADER_astAnnotations
 {
     MOJOSHADER_astNodeInfo ast;
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astExpression *initializer;
     struct MOJOSHADER_astAnnotations *next;
 } MOJOSHADER_astAnnotations;
@@ -1251,7 +1376,7 @@
 typedef struct MOJOSHADER_astStructMembers
 {
     MOJOSHADER_astNodeInfo ast;
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     const char *semantic;
     MOJOSHADER_astScalarOrArray *details;
     MOJOSHADER_astInterpolationModifier interpolation_mod;
@@ -1261,6 +1386,7 @@
 typedef struct MOJOSHADER_astStructDeclaration
 {
     MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
     const char *name;
     MOJOSHADER_astStructMembers *members;
 } MOJOSHADER_astStructDeclaration;
@@ -1269,7 +1395,7 @@
 {
     MOJOSHADER_astNodeInfo ast;
     int attributes;
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     MOJOSHADER_astStructDeclaration *anonymous_datatype;
     MOJOSHADER_astScalarOrArray *details;
     const char *semantic;
@@ -1365,8 +1491,8 @@
 typedef struct MOJOSHADER_astTypedef
 {
     MOJOSHADER_astNodeInfo ast;
+    const MOJOSHADER_astDataType *datatype;
     int isconst;  /* boolean: 1 or 0 */
-    const char *datatype;
     MOJOSHADER_astScalarOrArray *details;
 } MOJOSHADER_astTypedef;
 
--- a/mojoshader_compiler.c	Mon Dec 06 02:42:31 2010 -0500
+++ b/mojoshader_compiler.c	Mon Dec 06 02:47:31 2010 -0500
@@ -58,6 +58,7 @@
     int64 i64;
     double dbl;
     const char *string;
+    const MOJOSHADER_astDataType *datatype;
 } TokenData;
 
 
@@ -66,7 +67,7 @@
 typedef struct SymbolScope
 {
     const char *symbol;
-    const char *datatype;
+    const MOJOSHADER_astDataType *datatype;
     struct SymbolScope *next;
 } SymbolScope;
 
@@ -96,30 +97,32 @@
     MOJOSHADER_astNode *ast;  // Abstract Syntax Tree
     const char *source_profile;
 
-    // These are entries allocated in the strcache, so these pointers are
-    //  valid from shortly after we create the cache until we destroy it
-    //  with the rest of the context. This makes it so we can compare common
-    //  strings by pointer without having to hash them every time, so long as
-    //  we're comparing a string pointer we know came from this string cache.
-    // The first batch are simplifed datatype strings ("b" == bool, etc).
-    const char *str_b;  // "b"
-    const char *str_f;  // "f"
-    const char *str_i;  // "i"
-    const char *str_u;  // "u"
-    const char *str_h;  // "h"
-    const char *str_d;  // "d"
-    const char *str_s;  // "s"
-    const char *str_S;  // "S"
-    const char *str_s1;  // "s1"
-    const char *str_s2;  // "s2"
-    const char *str_s3;  // "s3"
-    const char *str_sc;  // "sc"
-    const char *str_ss;  // "ss"
-    const char *str_sS;  // "sS"
-    const char *str_Fs;  // "Fs"
-    const char *str_Fu;  // "Fu"
-    const char *str_ns;  // "ns"
-    const char *str_nu;  // "nu"
+    // Cache intrinsic types for fast lookup and consistent pointer values.
+    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;
 
 
@@ -219,7 +222,7 @@
 
 
 static void push_symbol(Context *ctx, SymbolMap *map,
-                        const char *sym, const char *datatype)
+                        const char *sym, const MOJOSHADER_astDataType *dt)
 {
     // !!! FIXME: decide if this symbol is defined, and if so, if it's in
     // !!! FIXME:  the current scope.
@@ -230,7 +233,7 @@
 
     if (sym != NULL)
     {
-        if (hash_insert(map->hash, sym, datatype) == -1)
+        if (hash_insert(map->hash, sym, dt) == -1)
         {
             Free(ctx, item);
             return;
@@ -238,17 +241,38 @@
     } // if
 
     item->symbol = sym;  // cached strings, don't copy.
-    item->datatype = datatype;
+    item->datatype = dt;
     item->next = map->scope;
     map->scope = item;
 } // push_symbol
 
-static inline void push_usertype(Context *ctx, const char *sym, const char *dt)
+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);
 } // push_usertype
 
-static inline void push_variable(Context *ctx, const char *sym, const char *dt)
+static inline void push_variable(Context *ctx, const char *sym, const MOJOSHADER_astDataType *dt)
 {
     push_symbol(ctx, &ctx->variables, sym, dt);
 } // push_variable
@@ -287,19 +311,19 @@
     pop_symbol_scope(ctx, &ctx->variables);
 } // push_scope
 
-static const char *find_symbol(Context *ctx, SymbolMap *map, const char *sym)
+static const MOJOSHADER_astDataType *find_symbol(Context *ctx, SymbolMap *map, const char *sym)
 {
     const void *value = NULL;
     hash_find(map->hash, sym, &value);
-    return (const char *) value;
+    return (const MOJOSHADER_astDataType *) value;
 } // find_symbol
 
-static inline const char *find_usertype(Context *ctx, const char *sym)
+static inline const MOJOSHADER_astDataType *find_usertype(Context *ctx, const char *sym)
 {
     return find_symbol(ctx, &ctx->usertypes, sym);
 } // find_usertype
 
-static inline const char *find_variable(Context *ctx, const char *sym)
+static inline const MOJOSHADER_astDataType *find_variable(Context *ctx, const char *sym)
 {
     return find_symbol(ctx, &ctx->variables, sym);
 } // find_variable
@@ -312,7 +336,64 @@
 } // destroy_symbolmap
 
 
-// !!! FIXME: move this to to mojoshader_ast.c
+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
@@ -333,6 +414,7 @@
     if (!cls) return; \
 } while (0)
 
+
 static void delete_compilation_unit(Context*, MOJOSHADER_astCompilationUnit*);
 static void delete_statement(Context *ctx, MOJOSHADER_astStatement *stmt);
 
@@ -348,22 +430,22 @@
 } // new_callfunc_expr
 
 static MOJOSHADER_astExpression *new_constructor_expr(Context *ctx,
-                                                const char *datatype,
-                                                MOJOSHADER_astArguments *args)
+                                            const MOJOSHADER_astDataType *dt,
+                                            MOJOSHADER_astArguments *args)
 {
     NEW_AST_NODE(retval, MOJOSHADER_astExpressionConstructor,
                  MOJOSHADER_AST_OP_CONSTRUCTOR);
-    retval->datatype = datatype;
+    retval->datatype = dt;
     retval->args = args;
     return (MOJOSHADER_astExpression *) retval;
 } // new_constructor_expr
 
 static MOJOSHADER_astExpression *new_cast_expr(Context *ctx,
-                                            const char *datatype,
+                                            const MOJOSHADER_astDataType *dt,
                                             MOJOSHADER_astExpression *operand)
 {
     NEW_AST_NODE(retval, MOJOSHADER_astExpressionCast, MOJOSHADER_AST_OP_CAST);
-    retval->datatype = datatype;
+    retval->datatype = dt;
     retval->operand = operand;
     return (MOJOSHADER_astExpression *) retval;
 } // new_cast_expr
@@ -398,6 +480,8 @@
 {
     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;
@@ -412,6 +496,7 @@
                  MOJOSHADER_AST_OP_DEREF_STRUCT);
     retval->identifier = identifier;
     retval->member = member;  // cached; don't copy string.
+    retval->isswizzle = 0;  // may change during semantic analysis.
     return (MOJOSHADER_astExpression *) retval;
 } // new_deref_struct_expr
 
@@ -429,6 +514,7 @@
 {
     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
@@ -438,6 +524,7 @@
 {
     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
@@ -447,6 +534,7 @@
 {
     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
@@ -456,6 +544,7 @@
 {
     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
@@ -523,15 +612,15 @@
 
 static MOJOSHADER_astFunctionParameters *new_function_param(Context *ctx,
                         const MOJOSHADER_astInputModifier inputmod,
-                        const char *datatype, const char *identifier,
-                        const char *semantic,
+                        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->datatype = datatype;
     retval->identifier = identifier;
     retval->semantic = semantic;
     retval->interpolation_modifier = interpmod;
@@ -550,13 +639,13 @@
 } // delete_function_params
 
 static MOJOSHADER_astFunctionSignature *new_function_signature(Context *ctx,
-                                    const char *datatype,
+                                    const MOJOSHADER_astDataType *dt,
                                     const char *identifier,
                                     MOJOSHADER_astFunctionParameters *params)
 {
     NEW_AST_NODE(retval, MOJOSHADER_astFunctionSignature,
                  MOJOSHADER_AST_FUNCTION_SIGNATURE);
-    retval->datatype = datatype;
+    retval->datatype = dt;
     retval->identifier = identifier;
     retval->params = params;
     retval->storage_class = MOJOSHADER_AST_FNSTORECLS_NONE;
@@ -614,13 +703,13 @@
 } // delete_scalar_or_array
 
 static MOJOSHADER_astTypedef *new_typedef(Context *ctx, const int isconst,
-                                          const char *datatype,
+                                          const MOJOSHADER_astDataType *dt,
                                           MOJOSHADER_astScalarOrArray *soa)
 {
-    // we correct this datatype to the final string during semantic analysis.
+    // 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->datatype = datatype;
     retval->details = soa;
     return retval;
 } // new_typedef
@@ -667,11 +756,11 @@
 } // delete_variable_lowlevel
 
 static MOJOSHADER_astAnnotations *new_annotation(Context *ctx,
-                                        const char *datatype,
+                                        const MOJOSHADER_astDataType *dt,
                                         MOJOSHADER_astExpression *initializer)
 {
     NEW_AST_NODE(retval, MOJOSHADER_astAnnotations, MOJOSHADER_AST_ANNOTATION);
-    retval->datatype = datatype;
+    retval->datatype = dt;
     retval->initializer = initializer;
     retval->next = NULL;
     return retval;
@@ -695,7 +784,6 @@
     NEW_AST_NODE(retval, MOJOSHADER_astVariableDeclaration,
                  MOJOSHADER_AST_VARIABLE_DECLARATION);
     retval->attributes = 0;
-    retval->datatype = NULL;
     retval->anonymous_datatype = NULL;
     retval->details = soa;
     retval->semantic = semantic;
@@ -762,7 +850,6 @@
 {
     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;
@@ -785,6 +872,7 @@
 {
     NEW_AST_NODE(retval, MOJOSHADER_astStructDeclaration,
                  MOJOSHADER_AST_STRUCT_DECLARATION);
+    retval->datatype = NULL;
     retval->name = name;
     retval->members = members;
     return retval;
@@ -1226,12 +1314,14 @@
     // don't free (stmt) here, the class-specific functions do it.
 } // delete_statement
 
-static const char *get_usertype(const Context *ctx, const char *token)
+
+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 (const char *) value;
+    return (MOJOSHADER_astDataType *) value;
 } // get_usertype
 
 
@@ -1240,120 +1330,506 @@
 #include "mojoshader_parser_hlsl.h"
 
 
-/*
- * datatype strings...
- *
- * "v" == void
- * "b" == bool
- * "i" == int
- * "u" == uint
- * "h" == half
- * "f" == float
- * "ns" == snorm float
- * "nu" == unorm float
- * "B{*}" == buffer
- * "V{#,*}" == vector
- * "M{#,#,*}" == matrix
- * "d" == double
- * "a{#,*}" == array
- * "S" == string
- * "X{*}" == struct
- * "s1" == sampler1D
- * "s2" == sampler2D
- * "s3" == sampler3D
- * "sc" == samplerCUBE
- * "ss" == sampler_state
- * "sS" == SamplerComparisonState
- * "U{*}" == user type.
- * "F{*,*}" == function
- */
-
-
-static void require_numeric_datatype(Context *ctx, const char *datatype)
+#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)
 {
-    if (datatype == ctx->str_f) return;  // float
-    if (datatype == ctx->str_i) return;  // int
-    if (datatype == ctx->str_b) return;  // bool
-    if (datatype == ctx->str_u) return;  // uint
-    if (datatype == ctx->str_h) return;  // half
-    if (datatype == ctx->str_d) return;  // double
+    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 inline const MOJOSHADER_astDataType *reduce_datatype(const MOJOSHADER_astDataType *dt)
+{
+    const MOJOSHADER_astDataType *retval = dt;
+    while (retval && retval->type == MOJOSHADER_AST_DATATYPE_USER)
+        retval = retval->user.details;
+    return retval;
+} // reduce_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) );
+
+    // 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(datatype);
+    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 char *datatype)
+static void require_integer_datatype(Context *ctx,
+                                     const MOJOSHADER_astDataType *datatype)
 {
-    if (datatype == ctx->str_i) return;  // int
-    if (datatype == ctx->str_u) return;  // uint
+    datatype = reduce_datatype(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 char *datatype)
+static void require_boolean_datatype(Context *ctx,
+                                     const MOJOSHADER_astDataType *datatype)
 {
-    if (datatype == ctx->str_b) return;  // bool
-    if (datatype == ctx->str_i) return;  // int
-    if (datatype == ctx->str_u) return;  // uint
+    datatype = reduce_datatype(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 char *datatype)
+static void require_array_datatype(Context *ctx,
+                                   const MOJOSHADER_astDataType *datatype)
 {
-    if (datatype[0] != 'a')
-        fail(ctx, "expected array");
-        // !!! FIXME: delete array dereference for further processing.
+    datatype = reduce_datatype(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 char *datatype)
+static void require_struct_datatype(Context *ctx,
+                                    const MOJOSHADER_astDataType *datatype)
 {
-    if (datatype[0] != 'X')
-        fail(ctx, "expected struct");
-        // !!! FIXME: delete struct dereference for further processing.
-} // require_array_datatype
-
-
-static const char *require_function_datatype(Context *ctx, const char *datatype)
+    datatype = reduce_datatype(datatype);
+    if (datatype->type == MOJOSHADER_AST_DATATYPE_STRUCT)
+        return;
+
+    fail(ctx, "expected struct");
+    // !!! FIXME: delete struct dereference for further processing.
+} // require_struct_datatype
+
+
+static const MOJOSHADER_astDataType *require_function_datatype(Context *ctx,
+                                        const MOJOSHADER_astDataType *datatype)
 {
-    if (datatype[0] != 'F')
+    datatype = reduce_datatype(datatype);
+    if (datatype->type != MOJOSHADER_AST_DATATYPE_FUNCTION)
     {
         fail(ctx, "expected function");
         // !!! FIXME: delete function call for further processing.
-        return ctx->str_i;
+        return &ctx->dt_int;
     } // if
 
-    assert(datatype[1] == '{');
-    datatype += 2;
-    const char *ptr = strchr(datatype, ',');
-    assert(ptr != NULL);
-    return stringcache_len(ctx->strcache, datatype, (unsigned int) (ptr-datatype));
+    return datatype->function.retval;
 } // require_function_datatype
 
 
 // Extract the individual element type from an array datatype.
-static const char *array_element_datatype(Context *ctx, const char *datatype)
+static const MOJOSHADER_astDataType *array_element_datatype(Context *ctx,
+                                        const MOJOSHADER_astDataType *datatype)
 {
-    const char *ptr;
-    const char *ptr2;
-    unsigned int depth = 1;
-    assert(datatype[0] == 'a');
-    assert(datatype[1] == '{');
-    ptr = strchr(datatype+2, ',');
-    assert(ptr != NULL);
-    ptr++;
-    for (ptr2 = ptr; depth > 0; ptr2++)
-    {
-        const char ch = *ptr2;
-        assert(ch != '\0');
-        if (ch == '{')
-            depth++;
-        else if (ch == '}')
-            depth--;
-    } // for
-
-    return stringcache_len(ctx->strcache, ptr, (unsigned int) (ptr2 - ptr));
+    datatype = reduce_datatype(datatype);
+    assert(datatype->type == MOJOSHADER_AST_DATATYPE_ARRAY);
+    return datatype->array.base;
 } // array_element_datatype
 
 
@@ -1364,42 +1840,52 @@
 //  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 char *add_type_coercion(Context *ctx,
+static const MOJOSHADER_astDataType *add_type_coercion(Context *ctx,
                                      MOJOSHADER_astExpression **left,
-                                     const char *ldatatype,
+                                     const MOJOSHADER_astDataType *_ldatatype,
                                      MOJOSHADER_astExpression **right,
-                                     const char *rdatatype)
+                                     const MOJOSHADER_astDataType *_rdatatype)
 {
     // !!! FIXME: this whole function is probably naive at best.
+    const MOJOSHADER_astDataType *ldatatype = reduce_datatype(_ldatatype);
+    const MOJOSHADER_astDataType *rdatatype = reduce_datatype(_rdatatype);
 
     if (ldatatype == rdatatype)
         return ldatatype;   // they already match, so we're done.
 
     struct {
-        const char *datatype;
+        const MOJOSHADER_astDataTypeType type;
         const int bits;
         const int is_unsigned;
         const int floating;
     } typeinf[] = {
-        { ctx->str_b,  1, 1, 0 },
-        { ctx->str_h, 16, 0, 1 },
-        { ctx->str_i, 32, 0, 0 },
-        { ctx->str_u, 32, 1, 0 },
-        { ctx->str_f, 32, 0, 1 },
-        { ctx->str_d, 64, 0, 1 },
+        { 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 l, r;
-    for (l = 0; l < STATICARRAYLEN(typeinf); l++)
+    int l = STATICARRAYLEN(typeinf);
+    if (ldatatype != NULL)
     {
-        if (typeinf[l].datatype == ldatatype)
-            break;
-    } // for
-    for (r = 0; r < STATICARRAYLEN(typeinf); r++)
+        for (l = 0; l < STATICARRAYLEN(typeinf); l++)
+        {
+            if (typeinf[l].type == ldatatype->type)
+                break;
+        } // for
+    } // if
+
+    int r = STATICARRAYLEN(typeinf);
+    if (rdatatype != NULL)
     {
-        if (typeinf[r].datatype == rdatatype)
-            break;
-    } // for
+        for (r = 0; r < STATICARRAYLEN(typeinf); r++)
+        {
+            if (typeinf[r].type == rdatatype->type)
+                break;
+        } // for
+    } // if
 
     enum { CHOOSE_NEITHER, CHOOSE_LEFT, CHOOSE_RIGHT } choice = CHOOSE_NEITHER;
     if ((l < STATICARRAYLEN(typeinf)) && (r < STATICARRAYLEN(typeinf)))
@@ -1422,13 +1908,13 @@
 
     if (choice == CHOOSE_LEFT)
     {
-        *right = new_cast_expr(ctx, ldatatype, *right);
-        return ldatatype;
+        *right = new_cast_expr(ctx, _ldatatype, *right);
+        return _ldatatype;
     } // if
     else if (choice == CHOOSE_RIGHT)
     {
-        *left = new_cast_expr(ctx, rdatatype, *left);
-        return rdatatype;
+        *left = new_cast_expr(ctx, _rdatatype, *left);
+        return _rdatatype;
     } // else if
 
     assert(choice == CHOOSE_NEITHER);
@@ -1437,10 +1923,34 @@
     //  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));
+    *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)
+{
+    int i;
+    int is_xyzw = 0;
+    int is_rgba = 0;
+
+    assert(*str != '\0');  // can this actually happen?
+
+    for (i = 0; i < 4; 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
+
 
 // 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
@@ -1451,14 +1961,17 @@
 //  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.
-static const char *type_check_ast(Context *ctx, void *_ast)
+// 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 char *datatype;
-    const char *datatype2;
-    const char *datatype3;
-
-    if (!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...
@@ -1473,34 +1986,73 @@
         case MOJOSHADER_AST_OP_PREDECREMENT:
         case MOJOSHADER_AST_OP_COMPLEMENT:
         case MOJOSHADER_AST_OP_NEGATE:
+        case MOJOSHADER_AST_OP_NOT:
             datatype = type_check_ast(ctx, ast->unary.operand);
             require_numeric_datatype(ctx, datatype);
-            return datatype;
-
-        case MOJOSHADER_AST_OP_NOT:
-            datatype = type_check_ast(ctx, ast->unary.operand);
-            require_boolean_datatype(ctx, datatype);
+            ast->unary.datatype = datatype;
             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_array_datatype(ctx, datatype);
-            require_numeric_datatype(ctx, datatype2);
-            add_type_coercion(ctx, NULL, ctx->str_i, &ast->binary.right, datatype2);
-            return array_element_datatype(ctx, datatype);
+            require_integer_datatype(ctx, datatype2);
+            add_type_coercion(ctx, NULL, &ctx->dt_int, &ast->binary.right, datatype2);
+            ast->binary.datatype = array_element_datatype(ctx, datatype);
+            return ast->binary.datatype;
 
         case MOJOSHADER_AST_OP_DEREF_STRUCT:
+        {
+            const char *member = ast->derefstruct.member;
             datatype = type_check_ast(ctx, ast->derefstruct.identifier);
-            require_struct_datatype(ctx, datatype);
-// !!! FIXME: map member to datatype
-datatype = "!!! FIXME";
-            return datatype;
+            const MOJOSHADER_astDataType *reduced = reduce_datatype(datatype);
+
+            // Is this a swizzle and not a struct deref?
+            if (reduced->type == MOJOSHADER_AST_DATATYPE_VECTOR)
+            {
+                ast->derefstruct.isswizzle = 1;
+                if (!is_swizzle_str(member))
+                {
+                    fail(ctx, "invalid swizzle on vector");
+                    // force this to be sane for further processing.
+                    const char *sane_swiz = stringcache(ctx->strcache, "xyzw");
+                    ast->derefstruct.member = sane_swiz;
+                } // if
+                ast->derefstruct.datatype = datatype;  // !!! FIXME: should float4(0,0,0,0).xy become a float2?
+                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 = datatype->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;
+                    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);
-            return type_check_ast(ctx, ast->binary.right);
+            ast->binary.datatype = type_check_ast(ctx, ast->binary.right);
+            return ast->binary.datatype;
 
         case MOJOSHADER_AST_OP_MULTIPLY:
         case MOJOSHADER_AST_OP_DIVIDE:
@@ -1510,8 +2062,9 @@
             datatype2 = type_check_ast(ctx, ast->binary.right);
             require_numeric_datatype(ctx, datatype);
             require_numeric_datatype(ctx, datatype2);
-            return add_type_coercion(ctx, &ast->binary.left, datatype,
-                                     &ast->binary.right, 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:
@@ -1520,8 +2073,9 @@
             datatype2 = type_check_ast(ctx, ast->binary.right);
             require_integer_datatype(ctx, datatype);
             require_integer_datatype(ctx, datatype2);
-            return add_type_coercion(ctx, &ast->binary.left, datatype,
-                                     &ast->binary.right, 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:
@@ -1533,7 +2087,8 @@
             datatype2 = type_check_ast(ctx, ast->binary.right);
             add_type_coercion(ctx, &ast->binary.left, datatype,
                               &ast->binary.right, datatype2);
-            return ctx->str_b;
+            ast->binary.datatype = &ctx->dt_bool;
+            return ast->binary.datatype;
 
         case MOJOSHADER_AST_OP_BINARYAND:
         case MOJOSHADER_AST_OP_BINARYXOR:
@@ -1542,8 +2097,9 @@
             datatype2 = type_check_ast(ctx, ast->binary.right);
             require_integer_datatype(ctx, datatype);
             require_integer_datatype(ctx, datatype2);
-            return add_type_coercion(ctx, &ast->binary.left, datatype,
-                                     &ast->binary.right, 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:
@@ -1553,7 +2109,7 @@
             require_boolean_datatype(ctx, datatype2);
             add_type_coercion(ctx, &ast->binary.left, datatype,
                               &ast->binary.right, datatype2);
-            return ctx->str_b;
+            ast->binary.datatype = &ctx->dt_bool;
 
         case MOJOSHADER_AST_OP_ASSIGN:
         case MOJOSHADER_AST_OP_MULASSIGN:
@@ -1568,16 +2124,18 @@
         case MOJOSHADER_AST_OP_ORASSIGN:
             datatype = type_check_ast(ctx, ast->binary.left);
             datatype2 = type_check_ast(ctx, ast->binary.right);
-            add_type_coercion(ctx, NULL, datatype, &ast->binary.right, datatype2);
-            return datatype;
+            ast->binary.datatype = add_type_coercion(ctx, NULL, datatype,
+                                                &ast->binary.right, datatype2);
+            return ast->binary.datatype;
 
         case MOJOSHADER_AST_OP_CONDITIONAL:
-            type_check_ast(ctx, ast->ternary.left);
-            type_check_ast(ctx, ast->ternary.center);
-            type_check_ast(ctx, ast->ternary.right);
-            require_boolean_datatype(ctx, datatype);
-            return add_type_coercion(ctx, &ast->ternary.center, datatype2,
-                                     &ast->ternary.right, datatype3);
+            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);
@@ -1585,43 +2143,137 @@
             {
                 fail(ctx, "Unknown identifier");
                 // !!! FIXME: replace with a sane default, move on.
-                datatype = ctx->str_i;
+                datatype = &ctx->dt_int;
             } // if
-            return datatype;
+            ast->identifier.datatype = datatype;
+            return ast->identifier.datatype;
 
         case MOJOSHADER_AST_OP_INT_LITERAL:
-            return ctx->str_i;
-
         case MOJOSHADER_AST_OP_FLOAT_LITERAL:
-            return ctx->str_f;
-
         case MOJOSHADER_AST_OP_STRING_LITERAL:
-            return ctx->str_S;
-
         case MOJOSHADER_AST_OP_BOOLEAN_LITERAL:
-            return ctx->str_b;
+            assert(ast->expression.datatype != NULL);
+            return ast->expression.datatype;  // already set up during parsing.
 
         case MOJOSHADER_AST_ARGUMENTS:
-            datatype = type_check_ast(ctx, ast->arguments.argument);
-            if (ast->arguments.next != NULL)
-            {
-                datatype2 = type_check_ast(ctx, ast->arguments.next);
-                datatype = stringcache_fmt(ctx->strcache, "%s%s",
-                                           datatype, datatype2);
-            } // if
-            return datatype;
+            assert(0 && "Should be done by MOJOSHADER_AST_OP_CALLFUNC/CONSTRUCTOR");
+            return NULL;
 
         case MOJOSHADER_AST_OP_CALLFUNC:
+        {
             datatype = type_check_ast(ctx, ast->callfunc.identifier);
-            datatype2 = type_check_ast(ctx, ast->callfunc.args);
-            return require_function_datatype(ctx, datatype);
-// !!! FIXME: test each arg against function datatype.
-            //return retval;  // this is the datatype of the func's return value.
+            const MOJOSHADER_astDataType *reduced = reduce_datatype(datatype);
+            require_function_datatype(ctx, reduced);
+            // !!! FIXME: replace with an int literal if this isn't a function.
+            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.
+                {
+                    fail(ctx, "Too few arguments");
+                    // !!! FIXME: replace AST here.
+                    break;
+                } // if
+                datatype2 = type_check_ast(ctx, arg->argument);
+                add_type_coercion(ctx, NULL, &reduced->function.params[i],
+                                  &arg->argument, datatype2);
+                prev = arg;
+                arg = arg->next;
+            } // for
+
+            if (arg != NULL)
+            {
+                // 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);
+                fail(ctx, "Too many arguments");
+            } // if
+
+            ast->callfunc.datatype = reduced->function.retval;
+            return ast->callfunc.datatype;
+        } // case
 
         case MOJOSHADER_AST_OP_CONSTRUCTOR:
-// !!! FIXME: test each arg against constructor datatype.
-            type_check_ast(ctx, ast->constructor.args);
+        {
+            const MOJOSHADER_astDataType *reduced = reduce_datatype(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);
+                add_type_coercion(ctx, NULL, base_dt, &arg->argument, datatype2);
+                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 = ast->cast.datatype;
@@ -1725,6 +2377,8 @@
             type_check_ast(ctx, ast->returnstmt.next);
 
         case MOJOSHADER_AST_COMPUNIT_FUNCTION:
+            // !!! FIXME: this is totally broken for function overloading.
+//fsdfsdf
             datatype = get_usertype(ctx, ast->funcunit.declaration->identifier);
             if (datatype == NULL)
             {
@@ -1770,9 +2424,7 @@
             return NULL;
 
         case MOJOSHADER_AST_SCALAR_OR_ARRAY:
-            datatype = type_check_ast(ctx, ast->soa.dimension);
-            require_integer_datatype(ctx, datatype);
-assert(0); // !!! FIXME: figure out datatype of identifier.
+            assert(0 && "Should be done by other AST nodes.");
             return NULL;
 
         case MOJOSHADER_AST_TYPEDEF:
@@ -1782,106 +2434,161 @@
             if (datatype != NULL)
             {
                 fail(ctx, "typedef already defined");
+                ast->typdef.datatype = datatype;
                 return datatype;
             } // if
 
-            datatype = ast->typdef.datatype;
-
-            // don't walk into MOJOSHADER_AST_SCALAR_OR_ARRAY here, since it can't resolve the identifier.
-            // !!! FIXME: SCALAR_OR_ARRAY is sort of a mess.
-            // !!! FIXME: this part is cut and paste.
-            assert( (soa->isarray && soa->dimension) ||
-                    (!soa->isarray && !soa->dimension) );
-
-            if (soa->isarray)
-            {
-                if (soa->dimension->ast.type != MOJOSHADER_AST_OP_INT_LITERAL)
-                {
-                    fail(ctx, "Expected integer");
-                    delete_expr(ctx, soa->dimension);  // make sane.
-                    soa->dimension = new_literal_int_expr(ctx, 1);
-                } // if
-
-                const int dim = ((MOJOSHADER_astExpressionIntLiteral *) soa->dimension)->value;
-                datatype = stringcache_fmt(ctx->strcache, "a{%d,%s}",
-                                           dim, datatype);
-            } // if
-
-            ast->typdef.datatype = datatype;  // make sane.
+            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);
-            return datatype;
+            ast->typdef.datatype = datatype;
+            return ast->typdef.datatype;
         } // case
 
         case MOJOSHADER_AST_FUNCTION_PARAMS:
-            push_variable(ctx, ast->params.identifier, ast->params.datatype);
-            type_check_ast(ctx, ast->params.initializer);
-            type_check_ast(ctx, ast->params.next);
-            return NULL;
+            assert(0 && "Should be done by MOJOSHADER_AST_FUNCTION_SIGNATURE");
 
         case MOJOSHADER_AST_FUNCTION_SIGNATURE:
-            type_check_ast(ctx, ast->funcsig.params);
+        {
+            MOJOSHADER_astFunctionParameters *param;
+            int count = 0;
+
+            // !!! FIXME: pre-count this?
+            for (param = ast->funcsig.params; param; param = param->next)
+                count++;
+
+            // !!! FIXME: this is hacky.
+            MOJOSHADER_astDataType *dtparams;
+            void *ptr = Malloc(ctx, sizeof (*dtparams) * count);
+            if (ptr == NULL)
+                return NULL;
+            if (!buffer_append(ctx->garbage, &ptr, sizeof (ptr)))
+            {
+                Free(ctx, ptr);
+                return NULL;
+            } // if
+            dtparams = (MOJOSHADER_astDataType *) 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;
+
+            int i = 0;
+            for (param = ast->funcsig.params; param; param = param->next)
+            {
+                assert(i < count);
+                push_variable(ctx, param->identifier, param->datatype);
+                datatype2 = type_check_ast(ctx, param->initializer);
+                add_type_coercion(ctx, NULL, param->datatype,
+                                  &param->initializer, datatype2);
+                memcpy(&dtparams[i], param->datatype, sizeof (*param->datatype));
+                i++;
+            } // for
+
+            dt->type = MOJOSHADER_AST_DATATYPE_FUNCTION;
+            dt->function.retval = ast->funcsig.datatype;
+            dt->function.params = dtparams;
+            dt->function.num_params = count;
+
+            ast->funcsig.datatype = dt;
             return ast->funcsig.datatype;
+        } // case
 
         case MOJOSHADER_AST_STRUCT_DECLARATION:
-            datatype = type_check_ast(ctx, ast->structdecl.members);
-            datatype = stringcache_fmt(ctx->strcache, "X{%s}", datatype);
-            push_usertype(ctx, ast->structdecl.name, datatype);
-            return stringcache_fmt(ctx->strcache, "U{%s}", ast->structdecl.name);
+        {
+            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;
+
+            push_usertype(ctx, ast->structdecl.name, ast->structdecl.datatype);
+            return ast->structdecl.datatype;
+        } // case
 
         case MOJOSHADER_AST_STRUCT_MEMBER:
-            datatype = type_check_ast(ctx, ast->structmembers.details);
-            datatype2 = type_check_ast(ctx, ast->structmembers.next);
-            if (datatype2)
-            {
-                return stringcache_fmt(ctx->strcache, "%s%s",
-                                       datatype, datatype2);
-            } // if
-            return datatype;
+            assert(0 && "Should be done by MOJOSHADER_AST_STRUCT_DECLARATION.");
+            return NULL;
 
         case MOJOSHADER_AST_VARIABLE_DECLARATION:
             // this is true now, but we'll fill in ->datatype no matter what.
             assert((ast->vardecl.datatype && !ast->vardecl.anonymous_datatype) ||
                    (!ast->vardecl.datatype && ast->vardecl.anonymous_datatype));
 
-            // fix up if necessary.
+            // An anonymous struct? That AST node does the heavy lifting.
             if (ast->vardecl.anonymous_datatype != NULL)
-                ast->vardecl.datatype = type_check_ast(ctx, ast->vardecl.anonymous_datatype);
-            datatype = ast->vardecl.datatype;
-
-            // don't walk into MOJOSHADER_AST_SCALAR_OR_ARRAY here, since it can't resolve the identifier.
-            // !!! FIXME: SCALAR_OR_ARRAY is sort of a mess.
-            // !!! FIXME: this part is cut and paste.
-            assert( (ast->vardecl.details->isarray &&
-                     ast->vardecl.details->dimension) ||
-                     (!ast->vardecl.details->isarray &&
-                      !ast->vardecl.details->dimension) );
-
-            if (ast->vardecl.details->isarray)
+                datatype = type_check_ast(ctx, ast->vardecl.anonymous_datatype);
+            else
             {
-                MOJOSHADER_astScalarOrArray *soa = ast->vardecl.details;
-                if (soa->dimension->ast.type != MOJOSHADER_AST_OP_INT_LITERAL)
-                {
-                    fail(ctx, "Expected integer");
-                    delete_expr(ctx, soa->dimension);  // make sane.
-                    soa->dimension = new_literal_int_expr(ctx, 1);
-                } // if
-
-                const int dim = ((MOJOSHADER_astExpressionIntLiteral *) soa->dimension)->value;
-                datatype = stringcache_fmt(ctx->strcache, "a{%d,%s}",
-                                           dim, datatype);
+                datatype = build_datatype(ctx, (ast->vardecl.attributes & MOJOSHADER_AST_VARATTR_CONST) != 0,
+                                          ast->vardecl.datatype, ast->vardecl.details);
+            } // else
+
+            ast->vardecl.datatype = datatype;
+
+            push_variable(ctx, ast->vardecl.details->identifier, datatype);
+            if (ast->vardecl.initializer != NULL)
+            {
+                datatype2 = type_check_ast(ctx, ast->vardecl.initializer);
+                add_type_coercion(ctx, NULL, datatype, &ast->vardecl.initializer, datatype2);
             } // if
 
-            ast->vardecl.datatype = datatype;  // make sane.
-            push_variable(ctx, ast->vardecl.details->identifier, datatype);
-            datatype2 = type_check_ast(ctx, ast->vardecl.initializer);
-            add_type_coercion(ctx, NULL, datatype, &ast->vardecl.initializer, datatype2);
-
             type_check_ast(ctx, ast->vardecl.annotations);
             type_check_ast(ctx, ast->vardecl.lowlevel);
 
             datatype2 = type_check_ast(ctx, ast->vardecl.next);
-            assert(datatype == datatype2);
-            return datatype;
+            return ast->vardecl.datatype;
 
         case MOJOSHADER_AST_ANNOTATION:
         {
@@ -1906,7 +2613,6 @@
 } // type_check_ast
 
 
-
 static inline void semantic_analysis(Context *ctx)
 {
     type_check_ast(ctx, ctx->ast);
@@ -2154,6 +2860,21 @@
         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);
@@ -2187,25 +2908,37 @@
     ctx->errors = errorlist_create(MallocBridge, FreeBridge, ctx);  // !!! FIXME: check for failure.
     ctx->warnings = errorlist_create(MallocBridge, FreeBridge, ctx);  // !!! FIXME: check for failure.
 
-    // fill in some common strings we'll want to use without further hashing.
-    ctx->str_b = stringcache(ctx->strcache, "b");
-    ctx->str_f = stringcache(ctx->strcache, "f");
-    ctx->str_i = stringcache(ctx->strcache, "i");
-    ctx->str_u = stringcache(ctx->strcache, "u");
-    ctx->str_h = stringcache(ctx->strcache, "h");
-    ctx->str_d = stringcache(ctx->strcache, "d");
-    ctx->str_s = stringcache(ctx->strcache, "s");
-    ctx->str_S = stringcache(ctx->strcache, "S");
-    ctx->str_s1 = stringcache(ctx->strcache, "s1");
-    ctx->str_s2 = stringcache(ctx->strcache, "s2");
-    ctx->str_s3 = stringcache(ctx->strcache, "s3");
-    ctx->str_sc = stringcache(ctx->strcache, "sc");
-    ctx->str_ss = stringcache(ctx->strcache, "ss");
-    ctx->str_sS = stringcache(ctx->strcache, "sS");
-    ctx->str_Fs = stringcache(ctx->strcache, "Fs");
-    ctx->str_Fu = stringcache(ctx->strcache, "Fu");
-    ctx->str_ns = stringcache(ctx->strcache, "ns");
-    ctx->str_nu = stringcache(ctx->strcache, "nu");
+    // !!! FIXME: this feels hacky.
+    ctx->garbage = buffer_create(256*sizeof(void*),MallocBridge,FreeBridge,ctx);  // !!! FIXME: check for failure.
+
+    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
@@ -2213,11 +2946,11 @@
 
 // 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)
+                         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;
@@ -2234,10 +2967,19 @@
     pp = preprocessor_start(filename, source, sourcelen, include_open,
                             include_close, defines, define_count, 0,
                             MallocBridge, FreeBridge, ctx);
-
-    // !!! FIXME: check if (pp == NULL)...
+    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)...
 
@@ -2245,24 +2987,40 @@
     ParseHLSLTrace(stdout, "COMPILER: ");
     #endif
 
+    // !!! FIXME: move this to a subroutine.
     // add in standard typedefs...
-    static char *types[] = { "bool", "int", "uint", "half", "float", "double" };
-    int i;
+    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 j;
+        int len;
+        const MOJOSHADER_astDataType *dt;
+
         for (j = 1; j <= 4; j++)
         {
             // "float2"
-            int len = snprintf(buf, sizeof (buf), "%s%d", types[i], j);
-            push_usertype(ctx, stringcache_len(ctx->strcache, buf, len), "?");
-            int k;
+            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"
-                len = snprintf(buf, sizeof (buf), "%s%dx%d", types[i], j, k);
-                push_usertype(ctx, stringcache_len(ctx->strcache, buf, len), "?");
+                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
@@ -2315,6 +3073,7 @@
             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)
         {
@@ -2327,6 +3086,11 @@
                 break;
 
             case TOKEN_HLSL_USERTYPE:
+                data.string = stringcache_len(ctx->strcache, token, tokenlen);
+                data.datatype = get_usertype(ctx, data.string);
+                assert(data.datatype != NULL);
+                break;
+
             case TOKEN_HLSL_STRING_LITERAL:
             case TOKEN_HLSL_IDENTIFIER:
                 data.string = stringcache_len(ctx->strcache, token, tokenlen);
--- a/mojoshader_parser_hlsl.lemon	Mon Dec 06 02:42:31 2010 -0500
+++ b/mojoshader_parser_hlsl.lemon	Mon Dec 06 02:47:31 2010 -0500
@@ -95,8 +95,8 @@
 %type typedef { MOJOSHADER_astTypedef * }
 %destructor typedef { delete_typedef(ctx, $$); }
 // !!! FIXME: should CONST be here, or in datatype?
-typedef(A) ::= TYPEDEF CONST datatype(B) scalar_or_array(C). { A = new_typedef(ctx, 1, B, C); push_usertype(ctx, C->identifier, "?"); }
-typedef(A) ::= TYPEDEF datatype(B) scalar_or_array(C). { A = new_typedef(ctx, 0, B, C); push_usertype(ctx, C->identifier, "?"); }
+typedef(A) ::= TYPEDEF CONST datatype(B) scalar_or_array(C). { A = new_typedef(ctx, 1, B, C); push_usertype(ctx, C->identifier, A->datatype); }
+typedef(A) ::= TYPEDEF datatype(B) scalar_or_array(C). { A = new_typedef(ctx, 0, B, C); push_usertype(ctx, C->identifier, A->datatype); }
 
 %type function_signature { MOJOSHADER_astFunctionSignature * }
 %destructor function_signature { delete_function_signature(ctx, $$); }
@@ -132,6 +132,7 @@
 function_parameter_list(A) ::= function_parameter_list(B) COMMA function_parameter(C). { C->next = B; A = C; }
 
 // !!! FIXME: this is pretty unreadable.
+// !!! FIXME: CONST?
 %type function_parameter { MOJOSHADER_astFunctionParameters * }
 %destructor function_parameter { delete_function_params(ctx, $$); }
 function_parameter(A) ::= input_modifier(B) datatype(C) IDENTIFIER(D) semantic(E) interpolation_mod(F) initializer(G). { A = new_function_param(ctx, B, C, D.string, E, F, G); }
@@ -224,7 +225,7 @@
 
 // This has to be separate from struct_declaration so that the struct is in the usertypemap when parsing its members.
 %type struct_intro { const char * }
-struct_intro(A) ::= STRUCT IDENTIFIER(B). { A = B.string; push_usertype(ctx, A, "?"); }
+struct_intro(A) ::= STRUCT IDENTIFIER(B). { A = B.string; push_usertype(ctx, A, &ctx->dt_int); }  // datatype is bogus until semantic analysis.
 
 %type struct_member_list { MOJOSHADER_astStructMembers * }
 %destructor struct_member_list { delete_struct_member(ctx, $$); }
@@ -300,48 +301,53 @@
 initializer(A) ::= ASSIGN initializer_block(B). { A = B; }
 initializer(A) ::= ASSIGN expression(B). { A = B; }
 
-%type intrinsic_datatype { const char * }
+%type intrinsic_datatype { const MOJOSHADER_astDataType * }
 intrinsic_datatype(A) ::= datatype_vector(B). { A = B; }
 intrinsic_datatype(A) ::= datatype_matrix(B). { A = B; }
 intrinsic_datatype(A) ::= datatype_scalar(B). { A = B; }
 intrinsic_datatype(A) ::= datatype_sampler(B). { A = B; }
+intrinsic_datatype(A) ::= datatype_buffer(B). { A = B; }
 
-%type datatype { const char * }
+%type datatype { const MOJOSHADER_astDataType * }
 datatype(A) ::= intrinsic_datatype(B). { A = B; }
-datatype(A) ::= USERTYPE(B). { A = B.string; }
+datatype(A) ::= USERTYPE(B). { A = B.datatype; }
 
-%type datatype_sampler { const char * }
-datatype_sampler(A) ::= SAMPLER. { A = ctx->str_s1; }
-datatype_sampler(A) ::= SAMPLER1D. { A = ctx->str_s1; }
-datatype_sampler(A) ::= SAMPLER2D. { A = ctx->str_s2; }
-datatype_sampler(A) ::= SAMPLER3D. { A = ctx->str_s3; }
-datatype_sampler(A) ::= SAMPLERCUBE. { A = ctx->str_sc; }
-datatype_sampler(A) ::= SAMPLER_STATE. { A = ctx->str_ss; }
-datatype_sampler(A) ::= SAMPLERSTATE. { A = ctx->str_ss; }
-datatype_sampler(A) ::= SAMPLERCOMPARISONSTATE. { A = ctx->str_sS; }
+%type datatype_sampler { const MOJOSHADER_astDataType * }
+datatype_sampler(A) ::= SAMPLER. { A = &ctx->dt_sampler2d; }
+datatype_sampler(A) ::= SAMPLER1D. { A = &ctx->dt_sampler1d; }
+datatype_sampler(A) ::= SAMPLER2D. { A = &ctx->dt_sampler2d; }
+datatype_sampler(A) ::= SAMPLER3D. { A = &ctx->dt_sampler3d; }
+datatype_sampler(A) ::= SAMPLERCUBE. { A = &ctx->dt_samplercube; }
+datatype_sampler(A) ::= SAMPLER_STATE. { A = &ctx->dt_samplerstate; }
+datatype_sampler(A) ::= SAMPLERSTATE. { A = &ctx->dt_samplerstate; }
+datatype_sampler(A) ::= SAMPLERCOMPARISONSTATE. { A = &ctx->dt_samplercompstate; }
 
-%type datatype_scalar { const char * }
-datatype_scalar(A) ::= BOOL. { A = ctx->str_b; }
-datatype_scalar(A) ::= INT. { A = ctx->str_i; }
-datatype_scalar(A) ::= UINT. { A = ctx->str_u; }
-datatype_scalar(A) ::= HALF. { A = ctx->str_h; }
-datatype_scalar(A) ::= FLOAT. { A = ctx->str_f; }
-datatype_scalar(A) ::= DOUBLE. { A = ctx->str_d; }
-datatype_scalar(A) ::= STRING. { A = ctx->str_S; } // this is for the effects framework, not HLSL.
-datatype_scalar(A) ::= SNORM FLOAT. { A = ctx->str_ns; }
-datatype_scalar(A) ::= UNORM FLOAT. { A = ctx->str_nu; }
-datatype_scalar(A) ::= BUFFER LT datatype_scalar(B) GT. { A = stringcache_fmt(ctx->strcache, "B{%s}", B); }
+%type datatype_scalar { const MOJOSHADER_astDataType * }
+datatype_scalar(A) ::= BOOL. { A = &ctx->dt_bool; }
+datatype_scalar(A) ::= INT. { A = &ctx->dt_int; }
+datatype_scalar(A) ::= UINT. { A = &ctx->dt_uint; }
+datatype_scalar(A) ::= HALF. { A = &ctx->dt_half; }
+datatype_scalar(A) ::= FLOAT. { A = &ctx->dt_float; }
+datatype_scalar(A) ::= DOUBLE. { A = &ctx->dt_double; }
+datatype_scalar(A) ::= STRING. { A = &ctx->dt_string; } // this is for the effects framework, not HLSL.
+datatype_scalar(A) ::= SNORM FLOAT. { A = &ctx->dt_float_snorm; }
+datatype_scalar(A) ::= UNORM FLOAT. { A = &ctx->dt_float_unorm; }
 
-// !!! FIXME: MSDN suggests that the matrix ones are just typedefs inserted
-// !!! FIXME:  before parsing begins, like:
-// !!! FIXME: typedef matrix <bool,4,3> bool4x3;
-// !!! FIXME:  ...maybe we can rip these out of the grammar and just create
-// !!! FIXME:  them at startup?
-%type datatype_vector { const char * }
-datatype_vector(A) ::= VECTOR LT datatype_scalar(B) COMMA INT_CONSTANT(C) GT. { A = stringcache_fmt(ctx->strcache, "V{%d,%s}", (int) C.i64, B); }
+%type datatype_buffer { const MOJOSHADER_astDataType * }
+datatype_buffer(A) ::= BUFFER LT BOOL GT. { A = &ctx->dt_buf_bool; }
+datatype_buffer(A) ::= BUFFER LT INT GT. { A = &ctx->dt_buf_int; }
+datatype_buffer(A) ::= BUFFER LT UINT GT. { A = &ctx->dt_buf_uint; }
+datatype_buffer(A) ::= BUFFER LT HALF GT. { A = &ctx->dt_buf_half; }
+datatype_buffer(A) ::= BUFFER LT FLOAT GT. { A = &ctx->dt_buf_float; }
+datatype_buffer(A) ::= BUFFER LT DOUBLE GT. { A = &ctx->dt_buf_double; }
+datatype_buffer(A) ::= BUFFER LT SNORM FLOAT GT. { A = &ctx->dt_buf_float_snorm; }
+datatype_buffer(A) ::= BUFFER LT UNORM FLOAT GT. { A = &ctx->dt_buf_float_unorm; }
 
-%type datatype_matrix { const char * }
-datatype_matrix(A) ::= MATRIX LT datatype_scalar(B) COMMA INT_CONSTANT(C) COMMA INT_CONSTANT(D) GT. { A = stringcache_fmt(ctx->strcache, "M{%d,%d,%s}", (int) C.i64, (int) D.i64, B); }
+%type datatype_vector { const MOJOSHADER_astDataType * }
+datatype_vector(A) ::= VECTOR LT datatype_scalar(B) COMMA INT_CONSTANT(C) GT. { A = new_datatype_vector(ctx, B, (int) C.i64); }
+
+%type datatype_matrix { const MOJOSHADER_astDataType * }
+datatype_matrix(A) ::= MATRIX LT datatype_scalar(B) COMMA INT_CONSTANT(C) COMMA INT_CONSTANT(D) GT. { A = new_datatype_matrix(ctx, B, (int) C.i64, (int) D.i64); }
 
 %type statement_block { MOJOSHADER_astStatement * }
 %destructor statement_block { delete_statement(ctx, $$); }
--- a/utils/mojoshader-compiler.c	Mon Dec 06 02:42:31 2010 -0500
+++ b/utils/mojoshader-compiler.c	Mon Dec 06 02:47:31 2010 -0500
@@ -64,6 +64,109 @@
         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)
 {
@@ -221,13 +324,16 @@
             break;
 
         case MOJOSHADER_AST_OP_CONSTRUCTOR:
-            fprintf(io, "%s(", ast->constructor.datatype);
+            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, "(%s) (", ast->cast.datatype);
+            fprintf(io, "(");
+            print_ast_datatype(io, ast->cast.datatype);
+            fprintf(io, ") (");
             print_ast(io, 0, ast->cast.operand);
             fprintf(io, ")");
             break;
@@ -452,15 +558,17 @@
 
         case MOJOSHADER_AST_TYPEDEF:
             DO_INDENT;
-            fprintf(io, "typedef %s%s ",
-                   ast->typdef.isconst ? "const " : "", ast->typdef.datatype);
+            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]);
-            fprintf(io, "%s %s", ast->params.datatype, ast->params.identifier);
+            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]);
@@ -480,9 +588,11 @@
 
         case MOJOSHADER_AST_FUNCTION_SIGNATURE:
             fprintf(io, "%s", fnstorage[(int) ast->funcsig.storage_class]);
-            fprintf(io, "%s %s(",
-                    ast->funcsig.datatype ? ast->funcsig.datatype : "void",
-                    ast->funcsig.identifier);
+            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)
@@ -503,7 +613,8 @@
         case MOJOSHADER_AST_STRUCT_MEMBER:
             DO_INDENT;
             fprintf(io, "%s", interpmod[(int)ast->structmembers.interpolation_mod]);
-            fprintf(io, "%s ", ast->structmembers.datatype);
+            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);
@@ -533,7 +644,7 @@
                 fprintf(io, "columnmajor ");
 
             if (ast->vardecl.datatype)
-                fprintf(io, "%s", ast->vardecl.datatype);
+                print_ast_datatype(io, ast->vardecl.datatype);
             else
                 print_ast(io, 0, ast->vardecl.anonymous_datatype);
             fprintf(io, " ");
@@ -582,7 +693,8 @@
             fprintf(io, "<");
             while (a)
             {
-                fprintf(io, " %s ", a->datatype);
+                fprintf(io, " ");
+                print_ast_datatype(io, a->datatype);
                 if (a->initializer != NULL)
                 {
                     fprintf(io, " = ");