/*
 * Mesa 3-D graphics library
 * Version:  6.5
 *
 * Copyright (C) 2005-2006  Brian Paul   All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * \file slang_typeinfo.c
 * slang type info
 * \author Michal Krol
 */

#include "main/imports.h"
#include "shader/prog_instruction.h"
#include "slang_typeinfo.h"
#include "slang_compile.h"
#include "slang_log.h"
#include "slang_mem.h"


/**
 * Checks if a field selector is a general swizzle (an r-value swizzle
 * with replicated components or an l-value swizzle mask) for a
 * vector.  Returns GL_TRUE if this is the case, <swz> is filled with
 * swizzle information.  Returns GL_FALSE otherwise.
 */
GLboolean
_slang_is_swizzle(const char *field, GLuint rows, slang_swizzle * swz)
{
   GLuint i;
   GLboolean xyzw = GL_FALSE, rgba = GL_FALSE, stpq = GL_FALSE;

   /* init to undefined.
    * We rely on undefined/nil values to distinguish between
    * regular swizzles and writemasks.
    * For example, the swizzle ".xNNN" is the writemask ".x".
    * That's different than the swizzle ".xxxx".
    */
   for (i = 0; i < 4; i++)
      swz->swizzle[i] = SWIZZLE_NIL;

   /* the swizzle can be at most 4-component long */
   swz->num_components = slang_string_length(field);
   if (swz->num_components > 4)
      return GL_FALSE;

   for (i = 0; i < swz->num_components; i++) {
      /* mark which swizzle group is used */
      switch (field[i]) {
      case 'x':
      case 'y':
      case 'z':
      case 'w':
         xyzw = GL_TRUE;
         break;
      case 'r':
      case 'g':
      case 'b':
      case 'a':
         rgba = GL_TRUE;
         break;
      case 's':
      case 't':
      case 'p':
      case 'q':
         stpq = GL_TRUE;
         break;
      default:
         return GL_FALSE;
      }

      /* collect swizzle component */
      switch (field[i]) {
      case 'x':
      case 'r':
      case 's':
         swz->swizzle[i] = 0;
         break;
      case 'y':
      case 'g':
      case 't':
         swz->swizzle[i] = 1;
         break;
      case 'z':
      case 'b':
      case 'p':
         swz->swizzle[i] = 2;
         break;
      case 'w':
      case 'a':
      case 'q':
         swz->swizzle[i] = 3;
         break;
      }

      /* check if the component is valid for given vector's row count */
      if (rows <= swz->swizzle[i])
         return GL_FALSE;
   }

   /* only one swizzle group can be used */
   if ((xyzw && rgba) || (xyzw && stpq) || (rgba && stpq))
      return GL_FALSE;

   return GL_TRUE;
}



/**
 * Checks if a general swizzle is an l-value swizzle - these swizzles
 * do not have duplicated fields.  Returns GL_TRUE if this is a
 * swizzle mask.  Returns GL_FALSE otherwise
 */
static GLboolean
_slang_is_swizzle_mask(const slang_swizzle * swz, GLuint rows)
{
   GLuint i, c = 0;

   /* the swizzle may not be longer than the vector dim */
   if (swz->num_components > rows)
      return GL_FALSE;

   /* the swizzle components cannot be duplicated */
   for (i = 0; i < swz->num_components; i++) {
      if ((c & (1 << swz->swizzle[i])) != 0)
         return GL_FALSE;
      c |= 1 << swz->swizzle[i];
   }

   return GL_TRUE;
}


/**
 * Combines (multiplies) two swizzles to form single swizzle.
 * Example: "vec.wzyx.yx" --> "vec.zw".
 */
static void
_slang_multiply_swizzles(slang_swizzle * dst, const slang_swizzle * left,
                         const slang_swizzle * right)
{
   GLuint i;

   dst->num_components = right->num_components;
   for (i = 0; i < right->num_components; i++)
      dst->swizzle[i] = left->swizzle[right->swizzle[i]];
}


typedef struct
{
   const char *name;
   slang_type_specifier_type type;
} type_specifier_type_name;

static const type_specifier_type_name type_specifier_type_names[] = {
   {"void", SLANG_SPEC_VOID},
   {"bool", SLANG_SPEC_BOOL},
   {"bvec2", SLANG_SPEC_BVEC2},
   {"bvec3", SLANG_SPEC_BVEC3},
   {"bvec4", SLANG_SPEC_BVEC4},
   {"int", SLANG_SPEC_INT},
   {"ivec2", SLANG_SPEC_IVEC2},
   {"ivec3", SLANG_SPEC_IVEC3},
   {"ivec4", SLANG_SPEC_IVEC4},
   {"float", SLANG_SPEC_FLOAT},
   {"vec2", SLANG_SPEC_VEC2},
   {"vec3", SLANG_SPEC_VEC3},
   {"vec4", SLANG_SPEC_VEC4},
   {"mat2", SLANG_SPEC_MAT2},
   {"mat3", SLANG_SPEC_MAT3},
   {"mat4", SLANG_SPEC_MAT4},
   {"mat2x3", SLANG_SPEC_MAT23},
   {"mat3x2", SLANG_SPEC_MAT32},
   {"mat2x4", SLANG_SPEC_MAT24},
   {"mat4x2", SLANG_SPEC_MAT42},
   {"mat3x4", SLANG_SPEC_MAT34},
   {"mat4x3", SLANG_SPEC_MAT43},
   {"sampler1D", SLANG_SPEC_SAMPLER_1D},
   {"sampler2D", SLANG_SPEC_SAMPLER_2D},
   {"sampler3D", SLANG_SPEC_SAMPLER_3D},
   {"samplerCube", SLANG_SPEC_SAMPLER_CUBE},
   {"sampler1DShadow", SLANG_SPEC_SAMPLER_1D_SHADOW},
   {"sampler2DShadow", SLANG_SPEC_SAMPLER_2D_SHADOW},
   {"sampler2DRect", SLANG_SPEC_SAMPLER_RECT},
   {"sampler2DRectShadow", SLANG_SPEC_SAMPLER_RECT_SHADOW},
   {"sampler1DArray", SLANG_SPEC_SAMPLER_1D_ARRAY},
   {"sampler2DArray", SLANG_SPEC_SAMPLER_2D_ARRAY},
   {"sampler1DArrayShadow", SLANG_SPEC_SAMPLER_1D_ARRAY_SHADOW},
   {"sampler2DArrayShadow", SLANG_SPEC_SAMPLER_2D_ARRAY_SHADOW},
   {NULL, SLANG_SPEC_VOID}
};

slang_type_specifier_type
slang_type_specifier_type_from_string(const char *name)
{
   const type_specifier_type_name *p = type_specifier_type_names;
   while (p->name != NULL) {
      if (slang_string_compare(p->name, name) == 0)
         break;
      p++;
   }
   return p->type;
}

const char *
slang_type_specifier_type_to_string(slang_type_specifier_type type)
{
   const type_specifier_type_name *p = type_specifier_type_names;
   while (p->name != NULL) {
      if (p->type == type)
         break;
      p++;
   }
   return p->name;
}

/* slang_fully_specified_type */

int
slang_fully_specified_type_construct(slang_fully_specified_type * type)
{
   type->qualifier = SLANG_QUAL_NONE;
   slang_type_specifier_ctr(&type->specifier);
   return 1;
}

void
slang_fully_specified_type_destruct(slang_fully_specified_type * type)
{
   slang_type_specifier_dtr(&type->specifier);
}

int
slang_fully_specified_type_copy(slang_fully_specified_type * x,
                                const slang_fully_specified_type * y)
{
   slang_fully_specified_type z;

   if (!slang_fully_specified_type_construct(&z))
      return 0;
   z.qualifier = y->qualifier;
   z.precision = y->precision;
   z.variant = y->variant;
   z.centroid = y->centroid;
   z.layout = y->layout;
   z.array_len = y->array_len;
   if (!slang_type_specifier_copy(&z.specifier, &y->specifier)) {
      slang_fully_specified_type_destruct(&z);
      return 0;
   }
   slang_fully_specified_type_destruct(x);
   *x = z;
   return 1;
}


/**
 * Test if two fully specified types are compatible.  This is a bit
 * looser than testing for equality.  We don't check the precision,
 * variant, centroid, etc. information.
 * XXX this may need some tweaking.
 */
GLboolean
slang_fully_specified_types_compatible(const slang_fully_specified_type * x,
                                       const slang_fully_specified_type * y)
{
   if (!slang_type_specifier_equal(&x->specifier, &y->specifier))
      return GL_FALSE;

   if (x->qualifier == SLANG_QUAL_FIXEDINPUT &&
       y->qualifier == SLANG_QUAL_VARYING)
      ; /* ok */
   else if (x->qualifier != y->qualifier)
      return GL_FALSE;

   /* Note: don't compare precision, variant, centroid */

   /* XXX array length? */

   return GL_TRUE;
}


GLvoid
slang_type_specifier_ctr(slang_type_specifier * self)
{
   self->type = SLANG_SPEC_VOID;
   self->_struct = NULL;
   self->_array = NULL;
}

GLvoid
slang_type_specifier_dtr(slang_type_specifier * self)
{
   if (self->_struct != NULL) {
      slang_struct_destruct(self->_struct);
      _slang_free(self->_struct);
   }
   if (self->_array != NULL) {
      slang_type_specifier_dtr(self->_array);
      _slang_free(self->_array);
   }
}

slang_type_specifier *
slang_type_specifier_new(slang_type_specifier_type type,
                         struct slang_struct_ *_struct,
                         struct slang_type_specifier_ *_array)
{
   slang_type_specifier *spec =
      (slang_type_specifier *) _slang_alloc(sizeof(slang_type_specifier));
   if (spec) {
      spec->type = type;
      spec->_struct = _struct;
      spec->_array = _array;
   }
   return spec;
}

GLboolean
slang_type_specifier_copy(slang_type_specifier * x,
                          const slang_type_specifier * y)
{
   slang_type_specifier z;

   slang_type_specifier_ctr(&z);
   z.type = y->type;
   if (z.type == SLANG_SPEC_STRUCT) {
      z._struct = (slang_struct *) _slang_alloc(sizeof(slang_struct));
      if (z._struct == NULL) {
         slang_type_specifier_dtr(&z);
         return GL_FALSE;
      }
      if (!slang_struct_construct(z._struct)) {
         _slang_free(z._struct);
         slang_type_specifier_dtr(&z);
         return GL_FALSE;
      }
      if (!slang_struct_copy(z._struct, y->_struct)) {
         slang_type_specifier_dtr(&z);
         return GL_FALSE;
      }
   }
   else if (z.type == SLANG_SPEC_ARRAY) {
      z._array = (slang_type_specifier *)
         _slang_alloc(sizeof(slang_type_specifier));
      if (z._array == NULL) {
         slang_type_specifier_dtr(&z);
         return GL_FALSE;
      }
      slang_type_specifier_ctr(z._array);
      if (!slang_type_specifier_copy(z._array, y->_array)) {
         slang_type_specifier_dtr(&z);
         return GL_FALSE;
      }
   }
   slang_type_specifier_dtr(x);
   *x = z;
   return GL_TRUE;
}


/**
 * Test if two types are equal.
 */
GLboolean
slang_type_specifier_equal(const slang_type_specifier * x,
                           const slang_type_specifier * y)
{
   if (x->type != y->type)
      return GL_FALSE;
   if (x->type == SLANG_SPEC_STRUCT)
      return slang_struct_equal(x->_struct, y->_struct);
   if (x->type == SLANG_SPEC_ARRAY)
      return slang_type_specifier_equal(x->_array, y->_array);
   return GL_TRUE;
}


/**
 * As above, but allow float/int casting.
 */
GLboolean
slang_type_specifier_compatible(const slang_type_specifier * x,
                                const slang_type_specifier * y)
{
   /* special case: float == int */
   if (x->type == SLANG_SPEC_INT && y->type == SLANG_SPEC_FLOAT) {
      return GL_TRUE;
   }
   /* XXX may need to add bool/int compatibility, etc */

   if (x->type != y->type)
      return GL_FALSE;
   if (x->type == SLANG_SPEC_STRUCT)
      return slang_struct_equal(x->_struct, y->_struct);
   if (x->type == SLANG_SPEC_ARRAY)
      return slang_type_specifier_compatible(x->_array, y->_array);
   return GL_TRUE;
}


GLboolean
slang_typeinfo_construct(slang_typeinfo * ti)
{
   memset(ti, 0, sizeof(*ti));
   slang_type_specifier_ctr(&ti->spec);
   ti->array_len = 0;
   return GL_TRUE;
}

GLvoid
slang_typeinfo_destruct(slang_typeinfo * ti)
{
   slang_type_specifier_dtr(&ti->spec);
}



/**
 * Determine the return type of a function.
 * \param a_name  the function name
 * \param param  function parameters (overloading)
 * \param num_params  number of parameters to function
 * \param space  namespace to search
 * \param spec  returns the type
 * \param funFound  returns pointer to the function, or NULL if not found.
 * \return GL_TRUE for success, GL_FALSE if failure (bad function name)
 */
static GLboolean
_slang_typeof_function(slang_atom a_name,
                       slang_operation * params, GLuint num_params,
                       const slang_name_space * space,
                       slang_type_specifier * spec,
                       slang_function **funFound,
                       slang_atom_pool *atoms, slang_info_log *log)
{
   GLboolean error;

   *funFound = _slang_function_locate(space->funcs, a_name, params,
                                      num_params, space, atoms, log, &error);
   if (error)
      return GL_FALSE;

   if (!*funFound)
      return GL_TRUE;  /* yes, not false */

   return slang_type_specifier_copy(spec, &(*funFound)->header.type.specifier);
}


/**
 * Determine the type of a math function.
 * \param name  name of the operator, one of +,-,*,/ or unary -
 * \param params  array of function parameters
 * \param num_params  number of parameters
 * \param space  namespace to use
 * \param spec  returns the function's type
 * \param atoms  atom pool
 * \return GL_TRUE for success, GL_FALSE if failure
 */
static GLboolean
typeof_math_call(const char *name, slang_operation *call,
                 const slang_name_space * space,
                 slang_type_specifier * spec,
                 slang_atom_pool * atoms,
                 slang_info_log *log)
{
   if (call->fun) {
      /* we've previously resolved this function call */
      slang_type_specifier_copy(spec, &call->fun->header.type.specifier);
      return GL_TRUE;
   }
   else {
      slang_atom atom;
      slang_function *fun;

      /* number of params: */
      assert(call->num_children == 1 || call->num_children == 2);

      atom = slang_atom_pool_atom(atoms, name);
      if (!_slang_typeof_function(atom, call->children, call->num_children,
                                  space, spec, &fun, atoms, log))
         return GL_FALSE;

      if (fun) {
         /* Save pointer to save time in future */
         call->fun = fun;
         return GL_TRUE;
      }
      return GL_FALSE;
   }
}


/**
 * Determine the return type of an operation.
 * \param op  the operation node
 * \param space  the namespace to use
 * \param ti  the returned type
 * \param atoms  atom pool
 * \return GL_TRUE for success, GL_FALSE if failure
 */
GLboolean
_slang_typeof_operation(slang_operation * op,
                         const slang_name_space * space,
                         slang_typeinfo * ti,
                         slang_atom_pool * atoms,
                         slang_info_log *log)
{
   ti->can_be_referenced = GL_FALSE;
   ti->is_swizzled = GL_FALSE;

   switch (op->type) {
   case SLANG_OPER_BLOCK_NO_NEW_SCOPE:
   case SLANG_OPER_BLOCK_NEW_SCOPE:
   case SLANG_OPER_ASM:
   case SLANG_OPER_BREAK:
   case SLANG_OPER_CONTINUE:
   case SLANG_OPER_DISCARD:
   case SLANG_OPER_RETURN:
   case SLANG_OPER_IF:
   case SLANG_OPER_WHILE:
   case SLANG_OPER_DO:
   case SLANG_OPER_FOR:
   case SLANG_OPER_VOID:
      ti->spec.type = SLANG_SPEC_VOID;
      break;
   case SLANG_OPER_EXPRESSION:
   case SLANG_OPER_ASSIGN:
   case SLANG_OPER_ADDASSIGN:
   case SLANG_OPER_SUBASSIGN:
   case SLANG_OPER_MULASSIGN:
   case SLANG_OPER_DIVASSIGN:
   case SLANG_OPER_PREINCREMENT:
   case SLANG_OPER_PREDECREMENT:
      if (!_slang_typeof_operation(op->children, space, ti, atoms, log))
         return GL_FALSE;
      break;
   case SLANG_OPER_LITERAL_BOOL:
      if (op->literal_size == 1)
         ti->spec.type = SLANG_SPEC_BOOL;
      else if (op->literal_size == 2)
         ti->spec.type = SLANG_SPEC_BVEC2;
      else if (op->literal_size == 3)
         ti->spec.type = SLANG_SPEC_BVEC3;
      else if (op->literal_size == 4)
         ti->spec.type = SLANG_SPEC_BVEC4;
      else {
         _mesa_problem(NULL,
               "Unexpected bool literal_size %d in _slang_typeof_operation()",
               op->literal_size);
         ti->spec.type = SLANG_SPEC_BOOL;
      }
      break;
   case SLANG_OPER_LOGICALOR:
   case SLANG_OPER_LOGICALXOR:
   case SLANG_OPER_LOGICALAND:
   case SLANG_OPER_EQUAL:
   case SLANG_OPER_NOTEQUAL:
   case SLANG_OPER_LESS:
   case SLANG_OPER_GREATER:
   case SLANG_OPER_LESSEQUAL:
   case SLANG_OPER_GREATEREQUAL:
   case SLANG_OPER_NOT:
      ti->spec.type = SLANG_SPEC_BOOL;
      break;
   case SLANG_OPER_LITERAL_INT:
      if (op->literal_size == 1)
         ti->spec.type = SLANG_SPEC_INT;
      else if (op->literal_size == 2)
         ti->spec.type = SLANG_SPEC_IVEC2;
      else if (op->literal_size == 3)
         ti->spec.type = SLANG_SPEC_IVEC3;
      else if (op->literal_size == 4)
         ti->spec.type = SLANG_SPEC_IVEC4;
      else {
         _mesa_problem(NULL,
               "Unexpected int literal_size %d in _slang_typeof_operation()",
               op->literal_size);
         ti->spec.type = SLANG_SPEC_INT;
      }
      break;
   case SLANG_OPER_LITERAL_FLOAT:
      if (op->literal_size == 1)
         ti->spec.type = SLANG_SPEC_FLOAT;
      else if (op->literal_size == 2)
         ti->spec.type = SLANG_SPEC_VEC2;
      else if (op->literal_size == 3)
         ti->spec.type = SLANG_SPEC_VEC3;
      else if (op->literal_size == 4)
         ti->spec.type = SLANG_SPEC_VEC4;
      else {
         _mesa_problem(NULL,
               "Unexpected float literal_size %d in _slang_typeof_operation()",
               op->literal_size);
         ti->spec.type = SLANG_SPEC_FLOAT;
      }
      break;
   case SLANG_OPER_IDENTIFIER:
   case SLANG_OPER_VARIABLE_DECL:
      {
         slang_variable *var;
         var = _slang_variable_locate(op->locals, op->a_id, GL_TRUE);
         if (!var) {
            slang_info_log_error(log, "undefined variable '%s'",
                                 (char *) op->a_id);
            return GL_FALSE;
         }
         if (!slang_type_specifier_copy(&ti->spec, &var->type.specifier)) {
            slang_info_log_memory(log);
            return GL_FALSE;
         }
         ti->can_be_referenced = GL_TRUE;
         if (var->type.specifier.type == SLANG_SPEC_ARRAY &&
             var->type.array_len >= 1) {
            /* the datatype is an array, ex: float[3] x; */
            ti->array_len = var->type.array_len;
         }
         else {
            /* the variable is an array, ex: float x[3]; */
            ti->array_len = var->array_len;
         }
      }
      break;
   case SLANG_OPER_SEQUENCE:
      /* TODO: check [0] and [1] if they match */
      if (!_slang_typeof_operation(&op->children[1], space, ti, atoms, log)) {
         return GL_FALSE;
      }
      ti->can_be_referenced = GL_FALSE;
      ti->is_swizzled = GL_FALSE;
      break;
      /*case SLANG_OPER_MODASSIGN: */
      /*case SLANG_OPER_LSHASSIGN: */
      /*case SLANG_OPER_RSHASSIGN: */
      /*case SLANG_OPER_ORASSIGN: */
      /*case SLANG_OPER_XORASSIGN: */
      /*case SLANG_OPER_ANDASSIGN: */
   case SLANG_OPER_SELECT:
      /* TODO: check [1] and [2] if they match */
      if (!_slang_typeof_operation(&op->children[1], space, ti, atoms, log)) {
         return GL_FALSE;
      }
      ti->can_be_referenced = GL_FALSE;
      ti->is_swizzled = GL_FALSE;
      break;
      /*case SLANG_OPER_BITOR: */
      /*case SLANG_OPER_BITXOR: */
      /*case SLANG_OPER_BITAND: */
      /*case SLANG_OPER_LSHIFT: */
      /*case SLANG_OPER_RSHIFT: */
   case SLANG_OPER_ADD:
      assert(op->num_children == 2);
      if (!typeof_math_call("+", op, space, &ti->spec, atoms, log))
         return GL_FALSE;
      break;
   case SLANG_OPER_SUBTRACT:
      assert(op->num_children == 2);
      if (!typeof_math_call("-", op, space, &ti->spec, atoms, log))
         return GL_FALSE;
      break;
   case SLANG_OPER_MULTIPLY:
      assert(op->num_children == 2);
      if (!typeof_math_call("*", op, space, &ti->spec, atoms, log))
         return GL_FALSE;
      break;
   case SLANG_OPER_DIVIDE:
      assert(op->num_children == 2);
      if (!typeof_math_call("/", op, space, &ti->spec, atoms, log))
         return GL_FALSE;
      break;
   /*case SLANG_OPER_MODULUS: */
   case SLANG_OPER_PLUS:
      if (!_slang_typeof_operation(op->children, space, ti, atoms, log))
         return GL_FALSE;
      ti->can_be_referenced = GL_FALSE;
      ti->is_swizzled = GL_FALSE;
      break;
   case SLANG_OPER_MINUS:
      assert(op->num_children == 1);
      if (!typeof_math_call("-", op, space, &ti->spec, atoms, log))
         return GL_FALSE;
      break;
      /*case SLANG_OPER_COMPLEMENT: */
   case SLANG_OPER_SUBSCRIPT:
      {
         slang_typeinfo _ti;

         if (!slang_typeinfo_construct(&_ti))
            return GL_FALSE;
         if (!_slang_typeof_operation(op->children, space, &_ti, atoms, log)) {
            slang_typeinfo_destruct(&_ti);
            return GL_FALSE;
         }
         ti->can_be_referenced = _ti.can_be_referenced;
         if (_ti.spec.type == SLANG_SPEC_ARRAY) {
            if (!slang_type_specifier_copy(&ti->spec, _ti.spec._array)) {
               slang_typeinfo_destruct(&_ti);
               return GL_FALSE;
            }
         }
         else {
            if (!_slang_type_is_vector(_ti.spec.type)
                && !_slang_type_is_matrix(_ti.spec.type)) {
               slang_typeinfo_destruct(&_ti);
               slang_info_log_error(log, "cannot index a non-array type");
               return GL_FALSE;
            }
            ti->spec.type = _slang_type_base(_ti.spec.type);
         }
         slang_typeinfo_destruct(&_ti);
      }
      break;
   case SLANG_OPER_CALL:
      if (op->array_constructor) {
         /* build array typeinfo */
         ti->spec.type = SLANG_SPEC_ARRAY;
         ti->spec._array = (slang_type_specifier *)
            _slang_alloc(sizeof(slang_type_specifier));
         slang_type_specifier_ctr(ti->spec._array);

         ti->spec._array->type =
            slang_type_specifier_type_from_string((char *) op->a_id);
         ti->array_len = op->num_children;
      }
      else if (op->fun) {
         /* we've resolved this call before */
         slang_type_specifier_copy(&ti->spec, &op->fun->header.type.specifier);
      }
      else {
         slang_function *fun;
         if (!_slang_typeof_function(op->a_id, op->children, op->num_children,
                                     space, &ti->spec, &fun, atoms, log))
            return GL_FALSE;
         if (fun) {
            /* save result for future use */
            op->fun = fun;
         }
         else {
            slang_struct *s =
               slang_struct_scope_find(space->structs, op->a_id, GL_TRUE);
            if (s) {
               /* struct initializer */
               ti->spec.type = SLANG_SPEC_STRUCT;
               ti->spec._struct =
                  (slang_struct *) _slang_alloc(sizeof(slang_struct));
               if (ti->spec._struct == NULL)
                  return GL_FALSE;
               if (!slang_struct_construct(ti->spec._struct)) {
                  _slang_free(ti->spec._struct);
                  ti->spec._struct = NULL;
                  return GL_FALSE;
               }
               if (!slang_struct_copy(ti->spec._struct, s))
                  return GL_FALSE;
            }
            else {
               /* float, int, vec4, mat3, etc. constructor? */
               const char *name;
               slang_type_specifier_type type;

               name = slang_atom_pool_id(atoms, op->a_id);
               type = slang_type_specifier_type_from_string(name);
               if (type == SLANG_SPEC_VOID) {
                  slang_info_log_error(log, "undefined function '%s'", name);
                  return GL_FALSE;
               }
               ti->spec.type = type;
            }
         }
      }
      break;
   case SLANG_OPER_METHOD:
      /* at this time, GLSL 1.20 only has one method: array.length()
       * which returns an integer.
       */
      ti->spec.type = SLANG_SPEC_INT;
      break;
   case SLANG_OPER_FIELD:
      {
         slang_typeinfo _ti;

         if (!slang_typeinfo_construct(&_ti))
            return GL_FALSE;
         if (!_slang_typeof_operation(op->children, space, &_ti, atoms, log)) {
            slang_typeinfo_destruct(&_ti);
            return GL_FALSE;
         }
         if (_ti.spec.type == SLANG_SPEC_STRUCT) {
            slang_variable *field;

            field = _slang_variable_locate(_ti.spec._struct->fields, op->a_id,
                                           GL_FALSE);
            if (field == NULL) {
               slang_typeinfo_destruct(&_ti);
               return GL_FALSE;
            }
            if (!slang_type_specifier_copy(&ti->spec, &field->type.specifier)) {
               slang_typeinfo_destruct(&_ti);
               return GL_FALSE;
            }
            ti->can_be_referenced = _ti.can_be_referenced;
            ti->array_len = field->array_len;
         }
         else {
            GLuint rows;
            const char *swizzle;
            slang_type_specifier_type base;

            /* determine the swizzle of the field expression */
            if (!_slang_type_is_vector(_ti.spec.type)) {
               slang_typeinfo_destruct(&_ti);
               slang_info_log_error(log, "Can't swizzle scalar expression");
               return GL_FALSE;
            }
            rows = _slang_type_dim(_ti.spec.type);
            swizzle = slang_atom_pool_id(atoms, op->a_id);
            if (!_slang_is_swizzle(swizzle, rows, &ti->swz)) {
               slang_typeinfo_destruct(&_ti);
               slang_info_log_error(log, "bad swizzle '%s'", swizzle);
               return GL_FALSE;
            }
            ti->is_swizzled = GL_TRUE;
            ti->can_be_referenced = _ti.can_be_referenced
               && _slang_is_swizzle_mask(&ti->swz, rows);
            if (_ti.is_swizzled) {
               slang_swizzle swz;

               /* swizzle the swizzle */
               _slang_multiply_swizzles(&swz, &_ti.swz, &ti->swz);
               ti->swz = swz;
            }
            base = _slang_type_base(_ti.spec.type);
            switch (ti->swz.num_components) {
            case 1:
               ti->spec.type = base;
               break;
            case 2:
               switch (base) {
               case SLANG_SPEC_FLOAT:
                  ti->spec.type = SLANG_SPEC_VEC2;
                  break;
               case SLANG_SPEC_INT:
                  ti->spec.type = SLANG_SPEC_IVEC2;
                  break;
               case SLANG_SPEC_BOOL:
                  ti->spec.type = SLANG_SPEC_BVEC2;
                  break;
               default:
                  break;
               }
               break;
            case 3:
               switch (base) {
               case SLANG_SPEC_FLOAT:
                  ti->spec.type = SLANG_SPEC_VEC3;
                  break;
               case SLANG_SPEC_INT:
                  ti->spec.type = SLANG_SPEC_IVEC3;
                  break;
               case SLANG_SPEC_BOOL:
                  ti->spec.type = SLANG_SPEC_BVEC3;
                  break;
               default:
                  break;
               }
               break;
            case 4:
               switch (base) {
               case SLANG_SPEC_FLOAT:
                  ti->spec.type = SLANG_SPEC_VEC4;
                  break;
               case SLANG_SPEC_INT:
                  ti->spec.type = SLANG_SPEC_IVEC4;
                  break;
               case SLANG_SPEC_BOOL:
                  ti->spec.type = SLANG_SPEC_BVEC4;
                  break;
               default:
                  break;
               }
               break;
            default:
               break;
            }
         }
         slang_typeinfo_destruct(&_ti);
      }
      break;
   case SLANG_OPER_POSTINCREMENT:
   case SLANG_OPER_POSTDECREMENT:
      if (!_slang_typeof_operation(op->children, space, ti, atoms, log))
         return GL_FALSE;
      ti->can_be_referenced = GL_FALSE;
      ti->is_swizzled = GL_FALSE;
      break;
   default:
      return GL_FALSE;
   }

   return GL_TRUE;
}


/**
 * Determine if a type is a matrix.
 * \return GL_TRUE if is a matrix, GL_FALSE otherwise.
 */
GLboolean
_slang_type_is_matrix(slang_type_specifier_type ty)
{
   switch (ty) {
   case SLANG_SPEC_MAT2:
   case SLANG_SPEC_MAT3:
   case SLANG_SPEC_MAT4:
   case SLANG_SPEC_MAT23:
   case SLANG_SPEC_MAT32:
   case SLANG_SPEC_MAT24:
   case SLANG_SPEC_MAT42:
   case SLANG_SPEC_MAT34:
   case SLANG_SPEC_MAT43:
      return GL_TRUE;
   default:
      return GL_FALSE;
   }
}


/**
 * Determine if a type is a vector.
 * \return GL_TRUE if is a vector, GL_FALSE otherwise.
 */
GLboolean
_slang_type_is_vector(slang_type_specifier_type ty)
{
   switch (ty) {
   case SLANG_SPEC_VEC2:
   case SLANG_SPEC_VEC3:
   case SLANG_SPEC_VEC4:
   case SLANG_SPEC_IVEC2:
   case SLANG_SPEC_IVEC3:
   case SLANG_SPEC_IVEC4:
   case SLANG_SPEC_BVEC2:
   case SLANG_SPEC_BVEC3:
   case SLANG_SPEC_BVEC4:
      return GL_TRUE;
   default:
      return GL_FALSE;
   }
}


/**
 * Determine if a type is a float, float vector or float matrix.
 * \return GL_TRUE if so, GL_FALSE otherwise
 */
GLboolean
_slang_type_is_float_vec_mat(slang_type_specifier_type ty)
{
   switch (ty) {
   case SLANG_SPEC_FLOAT:
   case SLANG_SPEC_VEC2:
   case SLANG_SPEC_VEC3:
   case SLANG_SPEC_VEC4:
   case SLANG_SPEC_MAT2:
   case SLANG_SPEC_MAT3:
   case SLANG_SPEC_MAT4:
   case SLANG_SPEC_MAT23:
   case SLANG_SPEC_MAT32:
   case SLANG_SPEC_MAT24:
   case SLANG_SPEC_MAT42:
   case SLANG_SPEC_MAT34:
   case SLANG_SPEC_MAT43:
      return GL_TRUE;
   default:
      return GL_FALSE;
   }
}


/**
 * Given a vector type, return the type of the vector's elements.
 * For a matrix, return the type of the columns.
 */
slang_type_specifier_type
_slang_type_base(slang_type_specifier_type ty)
{
   switch (ty) {
   case SLANG_SPEC_FLOAT:
   case SLANG_SPEC_VEC2:
   case SLANG_SPEC_VEC3:
   case SLANG_SPEC_VEC4:
      return SLANG_SPEC_FLOAT;
   case SLANG_SPEC_INT:
   case SLANG_SPEC_IVEC2:
   case SLANG_SPEC_IVEC3:
   case SLANG_SPEC_IVEC4:
      return SLANG_SPEC_INT;
   case SLANG_SPEC_BOOL:
   case SLANG_SPEC_BVEC2:
   case SLANG_SPEC_BVEC3:
   case SLANG_SPEC_BVEC4:
      return SLANG_SPEC_BOOL;
   case SLANG_SPEC_MAT2:
      return SLANG_SPEC_VEC2;
   case SLANG_SPEC_MAT3:
      return SLANG_SPEC_VEC3;
   case SLANG_SPEC_MAT4:
      return SLANG_SPEC_VEC4;
   case SLANG_SPEC_MAT23:
      return SLANG_SPEC_VEC3;
   case SLANG_SPEC_MAT32:
      return SLANG_SPEC_VEC2;
   case SLANG_SPEC_MAT24:
      return SLANG_SPEC_VEC4;
   case SLANG_SPEC_MAT42:
      return SLANG_SPEC_VEC2;
   case SLANG_SPEC_MAT34:
      return SLANG_SPEC_VEC4;
   case SLANG_SPEC_MAT43:
      return SLANG_SPEC_VEC3;
   default:
      return SLANG_SPEC_VOID;
   }
}


/**
 * Return the dimensionality of a vector, or for a matrix, return number
 * of columns.
 */
GLuint
_slang_type_dim(slang_type_specifier_type ty)
{
   switch (ty) {
   case SLANG_SPEC_FLOAT:
   case SLANG_SPEC_INT:
   case SLANG_SPEC_BOOL:
      return 1;
   case SLANG_SPEC_VEC2:
   case SLANG_SPEC_IVEC2:
   case SLANG_SPEC_BVEC2:
   case SLANG_SPEC_MAT2:
      return 2;
   case SLANG_SPEC_VEC3:
   case SLANG_SPEC_IVEC3:
   case SLANG_SPEC_BVEC3:
   case SLANG_SPEC_MAT3:
      return 3;
   case SLANG_SPEC_VEC4:
   case SLANG_SPEC_IVEC4:
   case SLANG_SPEC_BVEC4:
   case SLANG_SPEC_MAT4:
      return 4;

   case SLANG_SPEC_MAT23:
      return 2;
   case SLANG_SPEC_MAT32:
      return 3;
   case SLANG_SPEC_MAT24:
      return 2;
   case SLANG_SPEC_MAT42:
      return 4;
   case SLANG_SPEC_MAT34:
      return 3;
   case SLANG_SPEC_MAT43:
      return 4;

   default:
      return 0;
   }
}


/**
 * Return the GL_* type that corresponds to a SLANG_SPEC_* type.
 */
GLenum
_slang_gltype_from_specifier(const slang_type_specifier *type)
{
   switch (type->type) {
   case SLANG_SPEC_BOOL:
      return GL_BOOL;
   case SLANG_SPEC_BVEC2:
      return GL_BOOL_VEC2;
   case SLANG_SPEC_BVEC3:
      return GL_BOOL_VEC3;
   case SLANG_SPEC_BVEC4:
      return GL_BOOL_VEC4;
   case SLANG_SPEC_INT:
      return GL_INT;
   case SLANG_SPEC_IVEC2:
      return GL_INT_VEC2;
   case SLANG_SPEC_IVEC3:
      return GL_INT_VEC3;
   case SLANG_SPEC_IVEC4:
      return GL_INT_VEC4;
   case SLANG_SPEC_FLOAT:
      return GL_FLOAT;
   case SLANG_SPEC_VEC2:
      return GL_FLOAT_VEC2;
   case SLANG_SPEC_VEC3:
      return GL_FLOAT_VEC3;
   case SLANG_SPEC_VEC4:
      return GL_FLOAT_VEC4;
   case SLANG_SPEC_MAT2:
      return GL_FLOAT_MAT2;
   case SLANG_SPEC_MAT3:
      return GL_FLOAT_MAT3;
   case SLANG_SPEC_MAT4:
      return GL_FLOAT_MAT4;
   case SLANG_SPEC_MAT23:
      return GL_FLOAT_MAT2x3;
   case SLANG_SPEC_MAT32:
      return GL_FLOAT_MAT3x2;
   case SLANG_SPEC_MAT24:
      return GL_FLOAT_MAT2x4;
   case SLANG_SPEC_MAT42:
      return GL_FLOAT_MAT4x2;
   case SLANG_SPEC_MAT34:
      return GL_FLOAT_MAT3x4;
   case SLANG_SPEC_MAT43:
      return GL_FLOAT_MAT4x3;
   case SLANG_SPEC_SAMPLER_1D:
      return GL_SAMPLER_1D;
   case SLANG_SPEC_SAMPLER_2D:
      return GL_SAMPLER_2D;
   case SLANG_SPEC_SAMPLER_3D:
      return GL_SAMPLER_3D;
   case SLANG_SPEC_SAMPLER_CUBE:
      return GL_SAMPLER_CUBE;
   case SLANG_SPEC_SAMPLER_1D_SHADOW:
      return GL_SAMPLER_1D_SHADOW;
   case SLANG_SPEC_SAMPLER_2D_SHADOW:
      return GL_SAMPLER_2D_SHADOW;
   case SLANG_SPEC_SAMPLER_RECT:
      return GL_SAMPLER_2D_RECT_ARB;
   case SLANG_SPEC_SAMPLER_RECT_SHADOW:
      return GL_SAMPLER_2D_RECT_SHADOW_ARB;
   case SLANG_SPEC_SAMPLER_1D_ARRAY:
      return GL_SAMPLER_1D_ARRAY_EXT;
   case SLANG_SPEC_SAMPLER_2D_ARRAY:
      return GL_SAMPLER_2D_ARRAY_EXT;
   case SLANG_SPEC_SAMPLER_1D_ARRAY_SHADOW:
      return GL_SAMPLER_1D_ARRAY_SHADOW_EXT;
   case SLANG_SPEC_SAMPLER_2D_ARRAY_SHADOW:
      return GL_SAMPLER_2D_ARRAY_SHADOW_EXT;
   case SLANG_SPEC_ARRAY:
      return _slang_gltype_from_specifier(type->_array);
   case SLANG_SPEC_STRUCT:
      /* fall-through */
   default:
      return GL_NONE;
   }
}

