/* $Id: vpparse.c,v 1.14.2.1 2003/03/29 16:05:13 brianp Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  5.0.1
 *
 * Copyright (C) 1999-2003  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.
 */

/*
 * -------- Regarding NV_vertex_program --------
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * o Redistribution of the source code must contain a copyright notice
 *   and this list of conditions;
 * 
 * o Redistribution in binary and source code form must contain the
 *   following Notice in the software and any documentation and/or other
 *   materials provided with the distribution; and
 * 
 * o The name of Nvidia may not be used to promote or endorse software
 *   derived from the software.
 * 
 * NOTICE: Nvidia hereby grants to each recipient a non-exclusive worldwide
 * royalty free patent license under patent claims that are licensable by
 * Nvidia and which are necessarily required and for which no commercially
 * viable non infringing alternative exists to make, use, sell, offer to sell,
 * import and otherwise transfer the vertex extension for the Mesa 3D Graphics
 * Library as distributed in source code and object code form.  No hardware or
 * hardware implementation (including a semiconductor implementation and chips)
 * are licensed hereunder. If a recipient makes a patent claim or institutes
 * patent litigation against Nvidia or Nvidia's customers for use or sale of
 * Nvidia products, then this license grant as to such recipient shall
 * immediately terminate and recipient immediately agrees to cease use and
 * distribution of the Mesa Program and derivatives thereof. 
 * 
 * THE MESA 3D GRAPHICS LIBRARY IS PROVIDED ON AN "AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
 * WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-NFRINGEMENT
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * NVIDIA SHALL NOT HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
 * LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE MESA 3D GRAPHICS
 * LIBRARY OR EVIDENCE OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDR, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * If you do not comply with this agreement, then Nvidia may cancel the license
 * and rights granted herein.
 * ---------------------------------------------
 */

/**
 * \file vpparse.c
 * \brief Vertex program parser.
 * \author Brian Paul
 */


#include "glheader.h"
#include "context.h"
#include "hash.h"
#include "imports.h"
#include "macros.h"
#include "mtypes.h"
#include "vpparse.h"



/************************ Symbol Table ******************************/

/* A simple symbol table implementation for ARB_vertex_program
 * (not used yet)
 */

#if 000
struct symbol
{
   GLubyte *name;
   GLint value;
   struct symbol *next;
};

static struct symbol *SymbolTable = NULL;

static GLboolean
IsSymbol(const GLubyte *symbol)
{
   struct symbol *s;
   for (s = SymbolTable; s; s = s->next) {
      if (strcmp((char *) symbol, (char *)s->name) == 0)
         return GL_TRUE;
   }
   return GL_FALSE;
}

static GLint
GetSymbolValue(const GLubyte *symbol)
{
   struct symbol *s;
   for (s = SymbolTable; s; s = s->next) {
      if (strcmp((char *) symbol, (char *)s->name) == 0)
         return s->value;
   }
   return 0;
}

static void
AddSymbol(const GLubyte *symbol, GLint value)
{
   struct symbol *s = MALLOC_STRUCT(symbol);
   if (s) {
      s->name = (GLubyte *) strdup((char *) symbol);
      s->value = value;
      s->next = SymbolTable;
      SymbolTable = s;
   }
}

static void
ResetSymbolTable(void)
{
   struct symbol *s, *next;
   for (s = SymbolTable; s; s = next) {
      next = s->next;
      FREE(s->name);
      FREE(s);
      s = next;
   }   
   SymbolTable = NULL;
}
#endif

/***************************** Parsing ******************************/


static GLboolean IsLetter(GLubyte b)
{
   return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z');
}


static GLboolean IsDigit(GLubyte b)
{
   return b >= '0' && b <= '9';
}


static GLboolean IsWhitespace(GLubyte b)
{
   return b == ' ' || b == '\t' || b == '\n' || b == '\r';
}


/**
 * Starting at 'str' find the next token.  A token can be an integer,
 * an identifier or punctuation symbol.
 * \return <= 0 we found an error, else, return number of characters parsed.
 */
static GLint
GetToken(const GLubyte *str, GLubyte *token)
{
   GLint i = 0, j = 0;

   token[0] = 0;

   /* skip whitespace and comments */
   while (str[i] && (IsWhitespace(str[i]) || str[i] == '#')) {
      if (str[i] == '#') {
         /* skip comment */
         while (str[i] && (str[i] != '\n' && str[i] != '\r')) {
            i++;
         }
      }
      else {
         /* skip whitespace */
         i++;
      }
   }

   if (str[i] == 0)
      return -i;

   /* try matching an integer */
   while (str[i] && IsDigit(str[i])) {
      token[j++] = str[i++];
   }
   if (j > 0 || !str[i]) {
      token[j] = 0;
      return i;
   }

   /* try matching an identifier */
   if (IsLetter(str[i])) {
      while (str[i] && (IsLetter(str[i]) || IsDigit(str[i]))) {
         token[j++] = str[i++];
      }
      token[j] = 0;
      return i;
   }

   /* punctuation */
   if (str[i]) {
      token[0] = str[i++];
      token[1] = 0;
      return i;
   }

   /* end of input */
   token[0] = 0;
   return i;
}


/**
 * Get next token from input stream and increment stream pointer past token.
 */
static GLboolean
Parse_Token(const GLubyte **s, GLubyte *token)
{
   GLint i;
   i = GetToken(*s, token);
   if (i <= 0) {
      *s += (-i);
      return GL_FALSE;
   }
   *s += i;
   return GL_TRUE;
}


/**
 * Get next token from input stream but don't increment stream pointer.
 */
static GLboolean
Peek_Token(const GLubyte **s, GLubyte *token)
{
   GLint i, len;
   i = GetToken(*s, token);
   if (i <= 0) {
      *s += (-i);
      return GL_FALSE;
   }
   len = _mesa_strlen((char *) token);
   *s += (i - len);
   return GL_TRUE;
}


/**
 * String equality test
 */
static GLboolean
StrEq(const GLubyte *a, const GLubyte *b)
{
   GLint i;
   for (i = 0; a[i] && b[i] && a[i] == b[i]; i++)
      ;
   if (a[i] == 0 && b[i] == 0)
      return GL_TRUE;
   else
      return GL_FALSE;
}


/**********************************************************************/

static const char *InputRegisters[] = {
   "OPOS", "WGHT", "NRML", "COL0", "COL1", "FOGC", "6", "7",
   "TEX0", "TEX1", "TEX2", "TEX3", "TEX4", "TEX5", "TEX6", "TEX7", NULL
};

static const char *OutputRegisters[] = {
   "HPOS", "COL0", "COL1", "BFC0", "BFC1", "FOGC", "PSIZ",
   "TEX0", "TEX1", "TEX2", "TEX3", "TEX4", "TEX5", "TEX6", "TEX7", NULL
};

static const char *Opcodes[] = {
   "MOV", "LIT", "RCP", "RSQ", "EXP", "LOG", "MUL", "ADD", "DP3", "DP4",
   "DST", "MIN", "MAX", "SLT", "SGE", "MAD", "ARL", "DPH", "RCC", "SUB",
   "ABS", "END", NULL
};


#ifdef DEBUG

#define PARSE_ERROR						\
do {								\
   _mesa_printf("vpparse.c error at %d: parse error\n", __LINE__);	\
   return GL_FALSE;						\
} while(0)

#define PARSE_ERROR1(msg)					\
do {								\
   _mesa_printf("vpparse.c error at %d: %s\n", __LINE__, msg);	\
   return GL_FALSE;						\
} while(0)

#define PARSE_ERROR2(msg1, msg2)					\
do {									\
   _mesa_printf("vpparse.c error at %d: %s %s\n", __LINE__, msg1, msg2);	\
   return GL_FALSE;							\
} while(0)

#else

#define PARSE_ERROR                return GL_FALSE
#define PARSE_ERROR1(msg1)         return GL_FALSE
#define PARSE_ERROR2(msg1, msg2)   return GL_FALSE

#endif


static GLuint
IsProgRegister(GLuint r)
{
   return (GLuint) (r >= VP_PROG_REG_START && r <= VP_PROG_REG_END);
}

static GLuint
IsInputRegister(GLuint r)
{
   return (GLuint) (r >= VP_INPUT_REG_START && r <= VP_INPUT_REG_END);
}

static GLuint
IsOutputRegister(GLuint r)
{
   return (GLuint) (r >= VP_OUTPUT_REG_START && r <= VP_OUTPUT_REG_END);
}



/**********************************************************************/

/* XXX
 * These shouldn't be globals as that makes the parser non-reentrant.
 * We should really define a "ParserContext" class which contains these
 * and the <s> pointer into the program text.
 */
static GLboolean IsStateProgram = GL_FALSE;
static GLboolean IsPositionInvariant = GL_FALSE;
static GLboolean IsVersion1_1 = GL_FALSE;

/**
 * Try to match 'pattern' as the next token after any whitespace/comments.
 */
static GLboolean
Parse_String(const GLubyte **s, const char *pattern)
{
   GLint i;

   /* skip whitespace and comments */
   while (IsWhitespace(**s) || **s == '#') {
      if (**s == '#') {
         while (**s && (**s != '\n' && **s != '\r')) {
            *s += 1;
         }
      }
      else {
         /* skip whitespace */
         *s += 1;
      }
   }

   /* Try to match the pattern */
   for (i = 0; pattern[i]; i++) {
      if (**s != pattern[i])
         PARSE_ERROR2("failed to match", pattern); /* failure */
      *s += 1;
   }

   return GL_TRUE; /* success */
}


/**
 * Parse a temporary register: Rnn
 */
static GLboolean
Parse_TempReg(const GLubyte **s, GLint *tempRegNum)
{
   GLubyte token[100];

   /* Should be 'R##' */
   if (!Parse_Token(s, token))
      PARSE_ERROR;
   if (token[0] != 'R')
      PARSE_ERROR1("Expected R##");

   if (IsDigit(token[1])) {
      GLint reg = _mesa_atoi((char *) (token + 1));
      if (reg >= VP_NUM_TEMP_REGS)
         PARSE_ERROR1("Bad temporary register name");
      *tempRegNum = VP_TEMP_REG_START + reg;
   }
   else {
      PARSE_ERROR1("Bad temporary register name");
   }

   return GL_TRUE;
}


/**
 * Parse address register "A0.x"
 */
static GLboolean
Parse_AddrReg(const GLubyte **s)
{
   /* match 'A0' */
   if (!Parse_String(s, "A0"))
      PARSE_ERROR;

   /* match '.' */
   if (!Parse_String(s, "."))
      PARSE_ERROR;

   /* match 'x' */
   if (!Parse_String(s, "x"))
      PARSE_ERROR;

   return GL_TRUE;
}


/**
 * Parse absolute program parameter register "c[##]"
 */
static GLboolean
Parse_AbsParamReg(const GLubyte **s, GLint *regNum)
{
   GLubyte token[100];

   if (!Parse_String(s, "c"))
      PARSE_ERROR;

   if (!Parse_String(s, "["))
      PARSE_ERROR;

   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (IsDigit(token[0])) {
      /* a numbered program parameter register */
      GLint reg = _mesa_atoi((char *) token);
      if (reg >= VP_NUM_PROG_REGS)
         PARSE_ERROR1("Bad constant program number");
      *regNum = VP_PROG_REG_START + reg;
   }
   else {
      PARSE_ERROR;
   }

   if (!Parse_String(s, "]"))
      PARSE_ERROR;

   return GL_TRUE;
}


static GLboolean
Parse_ParamReg(const GLubyte **s, struct vp_src_register *srcReg)
{
   GLubyte token[100];

   if (!Parse_String(s, "c"))
      PARSE_ERROR;

   if (!Parse_String(s, "["))
      PARSE_ERROR;

   if (!Peek_Token(s, token))
      PARSE_ERROR;

   if (IsDigit(token[0])) {
      /* a numbered program parameter register */
      GLint reg;
      (void) Parse_Token(s, token);
      reg = _mesa_atoi((char *) token);
      if (reg >= VP_NUM_PROG_REGS)
         PARSE_ERROR1("Bad constant program number");
      srcReg->Register = VP_PROG_REG_START + reg;
   }
   else if (StrEq(token, (GLubyte *) "A0")) {
      /* address register "A0.x" */
      if (!Parse_AddrReg(s))
         PARSE_ERROR;

      srcReg->RelAddr = GL_TRUE;
      srcReg->Register = 0;

      /* Look for +/-N offset */
      if (!Peek_Token(s, token))
         PARSE_ERROR;

      if (token[0] == '-' || token[0] == '+') {
         const GLubyte sign = token[0];
         (void) Parse_Token(s, token); /* consume +/- */

         /* an integer should be next */
         if (!Parse_Token(s, token))
            PARSE_ERROR;

         if (IsDigit(token[0])) {
            const GLint k = _mesa_atoi((char *) token);
            if (sign == '-') {
               if (k > 64)
                  PARSE_ERROR1("Bad address offset");
               srcReg->Register = -k;
            }
            else {
               if (k > 63)
                  PARSE_ERROR1("Bad address offset");
               srcReg->Register = k;
            }
         }
         else {
            PARSE_ERROR;
         }
      }
      else {
         /* probably got a ']', catch it below */
      }
   }
   else {
      PARSE_ERROR;
   }

   /* Match closing ']' */
   if (!Parse_String(s, "]"))
      PARSE_ERROR;

   return GL_TRUE;
}


/**
 * Parse v[#] or v[<name>]
 */
static GLboolean
Parse_AttribReg(const GLubyte **s, GLint *tempRegNum)
{
   GLubyte token[100];
   GLint j;

   /* Match 'v' */
   if (!Parse_String(s, "v"))
      PARSE_ERROR;

   /* Match '[' */
   if (!Parse_String(s, "["))
      PARSE_ERROR;

   /* match number or named register */
   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (IsStateProgram && token[0] != '0')
      PARSE_ERROR1("Only v[0] accessible in vertex state programs");

   if (IsDigit(token[0])) {
      GLint reg = _mesa_atoi((char *) token);
      if (reg >= VP_NUM_INPUT_REGS)
         PARSE_ERROR1("Bad vertex attribute register name");
      *tempRegNum = VP_INPUT_REG_START + reg;
   }
   else {
      for (j = 0; InputRegisters[j]; j++) {
         if (StrEq(token, (const GLubyte *) InputRegisters[j])) {
            *tempRegNum = VP_INPUT_REG_START + j;
            break;
         }
      }
      if (!InputRegisters[j]) {
         /* unknown input register label */
         PARSE_ERROR2("Bad register name", token);
      }
   }

   /* Match '[' */
   if (!Parse_String(s, "]"))
      PARSE_ERROR;

   return GL_TRUE;
}


static GLboolean
Parse_OutputReg(const GLubyte **s, GLint *outputRegNum)
{
   GLubyte token[100];
   GLint start, j;

   /* Match 'o' */
   if (!Parse_String(s, "o"))
      PARSE_ERROR;

   /* Match '[' */
   if (!Parse_String(s, "["))
      PARSE_ERROR;

   /* Get output reg name */
   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (IsPositionInvariant)
      start = 1; /* skip HPOS register name */
   else
      start = 0;

   /* try to match an output register name */
   for (j = start; OutputRegisters[j]; j++) {
      if (StrEq(token, (const GLubyte *) OutputRegisters[j])) {
         *outputRegNum = VP_OUTPUT_REG_START + j;
         break;
      }
   }
   if (!OutputRegisters[j])
      PARSE_ERROR1("Unrecognized output register name");

   /* Match ']' */
   if (!Parse_String(s, "]"))
      PARSE_ERROR1("Expected ]");

   return GL_TRUE;
}


static GLboolean
Parse_MaskedDstReg(const GLubyte **s, struct vp_dst_register *dstReg)
{
   GLubyte token[100];

   /* Dst reg can be R<n> or o[n] */
   if (!Peek_Token(s, token))
      PARSE_ERROR;

   if (token[0] == 'R') {
      /* a temporary register */
      if (!Parse_TempReg(s, &dstReg->Register))
         PARSE_ERROR;
   }
   else if (!IsStateProgram && token[0] == 'o') {
      /* an output register */
      if (!Parse_OutputReg(s, &dstReg->Register))
         PARSE_ERROR;
   }
   else if (IsStateProgram && token[0] == 'c') {
      /* absolute program parameter register */
      if (!Parse_AbsParamReg(s, &dstReg->Register))
         PARSE_ERROR;
   }
   else {
      PARSE_ERROR1("Bad destination register name");
   }

   /* Parse optional write mask */
   if (!Peek_Token(s, token))
      PARSE_ERROR;

   if (token[0] == '.') {
      /* got a mask */
      GLint k = 0;

      if (!Parse_String(s, "."))
         PARSE_ERROR;

      if (!Parse_Token(s, token))
         PARSE_ERROR;

      dstReg->WriteMask[0] = GL_FALSE;
      dstReg->WriteMask[1] = GL_FALSE;
      dstReg->WriteMask[2] = GL_FALSE;
      dstReg->WriteMask[3] = GL_FALSE;

      if (token[k] == 'x') {
         dstReg->WriteMask[0] = GL_TRUE;
         k++;
      }
      if (token[k] == 'y') {
         dstReg->WriteMask[1] = GL_TRUE;
         k++;
      }
      if (token[k] == 'z') {
         dstReg->WriteMask[2] = GL_TRUE;
         k++;
      }
      if (token[k] == 'w') {
         dstReg->WriteMask[3] = GL_TRUE;
         k++;
      }
      if (k == 0) {
         PARSE_ERROR1("Bad writemask character");
      }
      return GL_TRUE;
   }
   else {
      dstReg->WriteMask[0] = GL_TRUE;
      dstReg->WriteMask[1] = GL_TRUE;
      dstReg->WriteMask[2] = GL_TRUE;
      dstReg->WriteMask[3] = GL_TRUE;
      return GL_TRUE;
   }
}


static GLboolean
Parse_SwizzleSrcReg(const GLubyte **s, struct vp_src_register *srcReg)
{
   GLubyte token[100];

   srcReg->RelAddr = GL_FALSE;

   /* check for '-' */
   if (!Peek_Token(s, token))
      PARSE_ERROR;
   if (token[0] == '-') {
      (void) Parse_String(s, "-");
      srcReg->Negate = GL_TRUE;
      if (!Peek_Token(s, token))
         PARSE_ERROR;
   }
   else {
      srcReg->Negate = GL_FALSE;
   }

   /* Src reg can be R<n>, c[n], c[n +/- offset], or a named vertex attrib */
   if (token[0] == 'R') {
      if (!Parse_TempReg(s, &srcReg->Register))
         PARSE_ERROR;
   }
   else if (token[0] == 'c') {
      if (!Parse_ParamReg(s, srcReg))
         PARSE_ERROR;
   }
   else if (token[0] == 'v') {
      if (!Parse_AttribReg(s, &srcReg->Register))
         PARSE_ERROR;
   }
   else {
      PARSE_ERROR2("Bad source register name", token);
   }

   /* init swizzle fields */
   srcReg->Swizzle[0] = 0;
   srcReg->Swizzle[1] = 1;
   srcReg->Swizzle[2] = 2;
   srcReg->Swizzle[3] = 3;

   /* Look for optional swizzle suffix */
   if (!Peek_Token(s, token))
      PARSE_ERROR;
   if (token[0] == '.') {
      (void) Parse_String(s, ".");  /* consume . */

      if (!Parse_Token(s, token))
         PARSE_ERROR;

      if (token[1] == 0) {
         /* single letter swizzle */
         if (token[0] == 'x')
            ASSIGN_4V(srcReg->Swizzle, 0, 0, 0, 0);
         else if (token[0] == 'y')
            ASSIGN_4V(srcReg->Swizzle, 1, 1, 1, 1);
         else if (token[0] == 'z')
            ASSIGN_4V(srcReg->Swizzle, 2, 2, 2, 2);
         else if (token[0] == 'w')
            ASSIGN_4V(srcReg->Swizzle, 3, 3, 3, 3);
         else
            PARSE_ERROR1("Expected x, y, z, or w");
      }
      else {
         /* 2, 3 or 4-component swizzle */
         GLint k;
         for (k = 0; token[k] && k < 5; k++) {
            if (token[k] == 'x')
               srcReg->Swizzle[k] = 0;
            else if (token[k] == 'y')
               srcReg->Swizzle[k] = 1;
            else if (token[k] == 'z')
               srcReg->Swizzle[k] = 2;
            else if (token[k] == 'w')
               srcReg->Swizzle[k] = 3;
            else
               PARSE_ERROR;
         }
         if (k >= 5)
            PARSE_ERROR;
      }
   }

   return GL_TRUE;
}


static GLboolean
Parse_ScalarSrcReg(const GLubyte **s, struct vp_src_register *srcReg)
{
   GLubyte token[100];

   srcReg->RelAddr = GL_FALSE;

   /* check for '-' */
   if (!Peek_Token(s, token))
      PARSE_ERROR;
   if (token[0] == '-') {
      srcReg->Negate = GL_TRUE;
      (void) Parse_String(s, "-"); /* consume '-' */
      if (!Peek_Token(s, token))
         PARSE_ERROR;
   }
   else {
      srcReg->Negate = GL_FALSE;
   }

   /* Src reg can be R<n>, c[n], c[n +/- offset], or a named vertex attrib */
   if (token[0] == 'R') {
      if (!Parse_TempReg(s, &srcReg->Register))
         PARSE_ERROR;
   }
   else if (token[0] == 'c') {
      if (!Parse_ParamReg(s, srcReg))
         PARSE_ERROR;
   }
   else if (token[0] == 'v') {
      if (!Parse_AttribReg(s, &srcReg->Register))
         PARSE_ERROR;
   }
   else {
      PARSE_ERROR2("Bad source register name", token);
   }

   /* Look for .[xyzw] suffix */
   if (!Parse_String(s, "."))
      PARSE_ERROR;

   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (token[0] == 'x' && token[1] == 0) {
      srcReg->Swizzle[0] = 0;
   }
   else if (token[0] == 'y' && token[1] == 0) {
      srcReg->Swizzle[0] = 1;
   }
   else if (token[0] == 'z' && token[1] == 0) {
      srcReg->Swizzle[0] = 2;
   }
   else if (token[0] == 'w' && token[1] == 0) {
      srcReg->Swizzle[0] = 3;
   }
   else {
      PARSE_ERROR1("Bad scalar source suffix");
   }
   srcReg->Swizzle[1] = srcReg->Swizzle[2] = srcReg->Swizzle[3] = 0;

   return GL_TRUE;
}


static GLint
Parse_UnaryOpInstruction(const GLubyte **s, struct vp_instruction *inst)
{
   GLubyte token[100];

   /* opcode */
   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (StrEq(token, (GLubyte *) "MOV")) {
      inst->Opcode = MOV;
   }
   else if (StrEq(token, (GLubyte *) "LIT")) {
      inst->Opcode = LIT;
   }
   else if (StrEq(token, (GLubyte *) "ABS") && IsVersion1_1) {
      inst->Opcode = ABS;
   }
   else {
      PARSE_ERROR;
   }

   /* dest reg */
   if (!Parse_MaskedDstReg(s, &inst->DstReg))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* src arg */
   if (!Parse_SwizzleSrcReg(s, &inst->SrcReg[0]))
      PARSE_ERROR;

   /* semicolon */
   if (!Parse_String(s, ";"))
      PARSE_ERROR;

   return GL_TRUE;
}


static GLboolean
Parse_BiOpInstruction(const GLubyte **s, struct vp_instruction *inst)
{
   GLubyte token[100];

   /* opcode */
   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (StrEq(token, (GLubyte *) "MUL")) {
      inst->Opcode = MUL;
   }
   else if (StrEq(token, (GLubyte *) "ADD")) {
      inst->Opcode = ADD;
   }
   else if (StrEq(token, (GLubyte *) "DP3")) {
      inst->Opcode = DP3;
   }
   else if (StrEq(token, (GLubyte *) "DP4")) {
      inst->Opcode = DP4;
   }
   else if (StrEq(token, (GLubyte *) "DST")) {
      inst->Opcode = DST;
   }
   else if (StrEq(token, (GLubyte *) "MIN")) {
      inst->Opcode = ADD;
   }
   else if (StrEq(token, (GLubyte *) "MAX")) {
      inst->Opcode = ADD;
   }
   else if (StrEq(token, (GLubyte *) "SLT")) {
      inst->Opcode = SLT;
   }
   else if (StrEq(token, (GLubyte *) "SGE")) {
      inst->Opcode = SGE;
   }
   else if (StrEq(token, (GLubyte *) "DPH") && IsVersion1_1) {
      inst->Opcode = DPH;
   }
   else if (StrEq(token, (GLubyte *) "SUB") && IsVersion1_1) {
      inst->Opcode = SUB;
   }
   else {
      PARSE_ERROR;
   }

   /* dest reg */
   if (!Parse_MaskedDstReg(s, &inst->DstReg))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* first src arg */
   if (!Parse_SwizzleSrcReg(s, &inst->SrcReg[0]))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* second src arg */
   if (!Parse_SwizzleSrcReg(s, &inst->SrcReg[1]))
      PARSE_ERROR;

   /* semicolon */
   if (!Parse_String(s, ";"))
      PARSE_ERROR;

   /* make sure we don't reference more than one program parameter register */
   if (IsProgRegister(inst->SrcReg[0].Register) &&
       IsProgRegister(inst->SrcReg[1].Register) &&
       inst->SrcReg[0].Register != inst->SrcReg[1].Register)
      PARSE_ERROR1("Can't reference two program parameter registers");

   /* make sure we don't reference more than one vertex attribute register */
   if (IsInputRegister(inst->SrcReg[0].Register) &&
       IsInputRegister(inst->SrcReg[1].Register) &&
       inst->SrcReg[0].Register != inst->SrcReg[1].Register)
      PARSE_ERROR1("Can't reference two vertex attribute registers");

   return GL_TRUE;
}


static GLboolean
Parse_TriOpInstruction(const GLubyte **s, struct vp_instruction *inst)
{
   GLubyte token[100];

   /* opcode */
   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (StrEq(token, (GLubyte *) "MAD")) {
      inst->Opcode = MAD;
   }
   else {
      PARSE_ERROR;
   }

   /* dest reg */
   if (!Parse_MaskedDstReg(s, &inst->DstReg))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* first src arg */
   if (!Parse_SwizzleSrcReg(s, &inst->SrcReg[0]))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* second src arg */
   if (!Parse_SwizzleSrcReg(s, &inst->SrcReg[1]))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* third src arg */
   if (!Parse_SwizzleSrcReg(s, &inst->SrcReg[2]))
      PARSE_ERROR;

   /* semicolon */
   if (!Parse_String(s, ";"))
      PARSE_ERROR;

   /* make sure we don't reference more than one program parameter register */
   if ((IsProgRegister(inst->SrcReg[0].Register) &&
        IsProgRegister(inst->SrcReg[1].Register) &&
        inst->SrcReg[0].Register != inst->SrcReg[1].Register) ||
       (IsProgRegister(inst->SrcReg[0].Register) &&
        IsProgRegister(inst->SrcReg[2].Register) &&
        inst->SrcReg[0].Register != inst->SrcReg[2].Register) ||
       (IsProgRegister(inst->SrcReg[1].Register) &&
        IsProgRegister(inst->SrcReg[2].Register) &&
        inst->SrcReg[1].Register != inst->SrcReg[2].Register))
      PARSE_ERROR1("Can only reference one program register");

   /* make sure we don't reference more than one vertex attribute register */
   if ((IsInputRegister(inst->SrcReg[0].Register) &&
        IsInputRegister(inst->SrcReg[1].Register) &&
        inst->SrcReg[0].Register != inst->SrcReg[1].Register) ||
       (IsInputRegister(inst->SrcReg[0].Register) &&
        IsInputRegister(inst->SrcReg[2].Register) &&
        inst->SrcReg[0].Register != inst->SrcReg[2].Register) ||
       (IsInputRegister(inst->SrcReg[1].Register) &&
        IsInputRegister(inst->SrcReg[2].Register) &&
        inst->SrcReg[1].Register != inst->SrcReg[2].Register))
      PARSE_ERROR1("Can only reference one input register");

   return GL_TRUE;
}


static GLboolean
Parse_ScalarInstruction(const GLubyte **s, struct vp_instruction *inst)
{
   GLubyte token[100];

   /* opcode */
   if (!Parse_Token(s, token))
      PARSE_ERROR;

   if (StrEq(token, (GLubyte *) "RCP")) {
      inst->Opcode = RCP;
   }
   else if (StrEq(token, (GLubyte *) "RSQ")) {
      inst->Opcode = RSQ;
   }
   else if (StrEq(token, (GLubyte *) "EXP")) {
      inst->Opcode = EXP;
   }
   else if (StrEq(token, (GLubyte *) "LOG")) {
      inst->Opcode = LOG;
   }
   else if (StrEq(token, (GLubyte *) "RCC") && IsVersion1_1) {
      inst->Opcode = RCC;
   }
   else {
      PARSE_ERROR;
   }

   /* dest reg */
   if (!Parse_MaskedDstReg(s, &inst->DstReg))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* first src arg */
   if (!Parse_ScalarSrcReg(s, &inst->SrcReg[0]))
      PARSE_ERROR;

   /* semicolon */
   if (!Parse_String(s, ";"))
      PARSE_ERROR;

   return GL_TRUE;
}


static GLboolean
Parse_AddressInstruction(const GLubyte **s, struct vp_instruction *inst)
{
   inst->Opcode = ARL;

   /* opcode */
   if (!Parse_String(s, "ARL"))
      PARSE_ERROR;

   /* dest A0 reg */
   if (!Parse_AddrReg(s))
      PARSE_ERROR;

   /* comma */
   if (!Parse_String(s, ","))
      PARSE_ERROR;

   /* parse src reg */
   if (!Parse_ScalarSrcReg(s, &inst->SrcReg[0]))
      PARSE_ERROR;

   /* semicolon */
   if (!Parse_String(s, ";"))
      PARSE_ERROR;

   return GL_TRUE;
}


static GLboolean
Parse_EndInstruction(const GLubyte **s, struct vp_instruction *inst)
{
   GLubyte token[100];

   /* opcode */
   if (!Parse_String(s, "END"))
      PARSE_ERROR;

   inst->Opcode = END;

   /* this should fail! */
   if (Parse_Token(s, token))
      PARSE_ERROR2("Unexpected token after END:", token);
   else
      return GL_TRUE;
}


static GLboolean
Parse_OptionSequence(const GLubyte **s, struct vp_instruction program[])
{
   while (1) {
      GLubyte token[100];
      if (!Peek_Token(s, token)) {
         PARSE_ERROR1("Unexpected end of input");
         return GL_FALSE; /* end of input */
      }

      if (!StrEq(token, (GLubyte *) "OPTION"))
         return GL_TRUE;  /* probably an instruction */

      Parse_Token(s, token);

      if (!Parse_String(s, "NV_position_invariant"))
         return GL_FALSE;
      if (!Parse_String(s, ";"))
         return GL_FALSE;
      IsPositionInvariant = GL_TRUE;
   }
}


static GLboolean
Parse_InstructionSequence(const GLubyte **s, struct vp_instruction program[])
{
   GLubyte token[100];
   GLint count = 0;

   while (1) {
      struct vp_instruction *inst = program + count;

      /* Initialize the instruction */
      inst->SrcReg[0].Register = -1;
      inst->SrcReg[1].Register = -1;
      inst->SrcReg[2].Register = -1;
      inst->DstReg.Register = -1;

      if (!Peek_Token(s, token))
         PARSE_ERROR;

      if (StrEq(token, (GLubyte *) "MOV") ||
          StrEq(token, (GLubyte *) "LIT") ||
          StrEq(token, (GLubyte *) "ABS")) {
         if (!Parse_UnaryOpInstruction(s, inst))
            PARSE_ERROR;
      }
      else if (StrEq(token, (GLubyte *) "MUL") ||
          StrEq(token, (GLubyte *) "ADD") ||
          StrEq(token, (GLubyte *) "DP3") ||
          StrEq(token, (GLubyte *) "DP4") ||
          StrEq(token, (GLubyte *) "DST") ||
          StrEq(token, (GLubyte *) "MIN") ||
          StrEq(token, (GLubyte *) "MAX") ||
          StrEq(token, (GLubyte *) "SLT") ||
          StrEq(token, (GLubyte *) "SGE") ||
          StrEq(token, (GLubyte *) "DPH") ||
          StrEq(token, (GLubyte *) "SUB")) {
         if (!Parse_BiOpInstruction(s, inst))
            PARSE_ERROR;
      }
      else if (StrEq(token, (GLubyte *) "MAD")) {
         if (!Parse_TriOpInstruction(s, inst))
            PARSE_ERROR;
      }
      else if (StrEq(token, (GLubyte *) "RCP") ||
               StrEq(token, (GLubyte *) "RSQ") ||
               StrEq(token, (GLubyte *) "EXP") ||
               StrEq(token, (GLubyte *) "LOG") ||
               StrEq(token, (GLubyte *) "RCC")) {
         if (!Parse_ScalarInstruction(s, inst))
            PARSE_ERROR;
      }
      else if (StrEq(token, (GLubyte *) "ARL")) {
         if (!Parse_AddressInstruction(s, inst))
            PARSE_ERROR;
      }
      else if (StrEq(token, (GLubyte *) "END")) {
         if (!Parse_EndInstruction(s, inst))
            PARSE_ERROR;
         else
            return GL_TRUE;  /* all done */
      }
      else {
         /* bad instruction name */
         PARSE_ERROR2("Unexpected token: ", token);
      }

      count++;
      if (count >= VP_MAX_INSTRUCTIONS)
         PARSE_ERROR1("Program too long");
   }

   PARSE_ERROR;
}


static GLboolean
Parse_Program(const GLubyte **s, struct vp_instruction instBuffer[])
{
   if (IsVersion1_1) {
      if (!Parse_OptionSequence(s, instBuffer)) {
         return GL_FALSE;
      }
   }
   return Parse_InstructionSequence(s, instBuffer);
}


/**
 * Parse/compile the 'str' returning the compiled 'program'.
 * ctx->VertexProgram.ErrorPos will be -1 if successful.  Otherwise, ErrorPos
 * indicates the position of the error in 'str'.
 */
void
_mesa_parse_program(GLcontext *ctx, GLenum dstTarget,
                    const GLubyte *str, struct vp_program *program)
{
   const GLubyte *s;
   struct vp_instruction instBuffer[VP_MAX_INSTRUCTIONS];
   GLubyte *newString;
   struct vp_instruction *newInst;
   GLenum target;

   ctx->VertexProgram.ErrorPos = -1;
   IsPositionInvariant = GL_FALSE;
   IsVersion1_1 = GL_FALSE;

   /* check the program header */
   if (_mesa_strncmp((const char *) str, "!!VP1.0", 7) == 0) {
      target = GL_VERTEX_PROGRAM_NV;
      s = str + 7;
      IsStateProgram = GL_FALSE;
   }
   else if (_mesa_strncmp((const char *) str, "!!VP1.1", 7) == 0) {
      target = GL_VERTEX_PROGRAM_NV;
      s = str + 7;
      IsStateProgram = GL_FALSE;
      IsVersion1_1 = GL_TRUE;
   }
   else if (_mesa_strncmp((const char *) str, "!!VSP1.0", 8) == 0) {
      target = GL_VERTEX_STATE_PROGRAM_NV;
      s = str + 8;
      IsStateProgram = GL_TRUE;
   }
   else {
      /* invalid header */
      ctx->VertexProgram.ErrorPos = 0;
      _mesa_error(ctx, GL_INVALID_OPERATION, "glLoadProgramNV(bad header)");
      return;
   }

   /* make sure target and header match */
   if (target != dstTarget) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glLoadProgramNV(target mismatch)");
      return;
   }

   if (Parse_Program(&s, instBuffer)) {
      GLuint numInst;
      GLuint strLen;
      GLuint inputsRead = 0;
      GLuint outputsWritten = 0;
      GLuint progRegsWritten = 0;

      /* Find length of the program and compute bitmasks to indicate which
       * vertex input registers are read, which vertex result registers are
       * written to, and which program registers are written to.
       * We could actually do this while we parse the program.
       */
      for (numInst = 0; instBuffer[numInst].Opcode != END; numInst++) {
         const GLint srcReg0 = instBuffer[numInst].SrcReg[0].Register;
         const GLint srcReg1 = instBuffer[numInst].SrcReg[1].Register;
         const GLint srcReg2 = instBuffer[numInst].SrcReg[2].Register;
         const GLint dstReg = instBuffer[numInst].DstReg.Register;

         if (IsOutputRegister(dstReg))
            outputsWritten |= (1 << (dstReg - VP_OUTPUT_REG_START));
         else if (IsProgRegister(dstReg))
            progRegsWritten |= (1 << (dstReg - VP_PROG_REG_START));
         if (IsInputRegister(srcReg0)
             && !instBuffer[numInst].SrcReg[0].RelAddr)
            inputsRead |= (1 << (srcReg0 - VP_INPUT_REG_START));
         if (IsInputRegister(srcReg1)
             && !instBuffer[numInst].SrcReg[1].RelAddr)
            inputsRead |= (1 << (srcReg1 - VP_INPUT_REG_START));
         if (IsInputRegister(srcReg2)
             && !instBuffer[numInst].SrcReg[2].RelAddr)
            inputsRead |= (1 << (srcReg2 - VP_INPUT_REG_START));
      }
      numInst++;

      if (IsStateProgram) {
         if (progRegsWritten == 0) {
            _mesa_error(ctx, GL_INVALID_OPERATION,
                        "glLoadProgramNV(c[#] not written)");
            return;
         }
      }
      else {
         if (!IsPositionInvariant && !(outputsWritten & 1)) {
            /* bit 1 = HPOS register */
            _mesa_error(ctx, GL_INVALID_OPERATION,
                        "glLoadProgramNV(HPOS not written)");
            return;
         }
      }

      program->InputsRead = inputsRead;
      program->OutputsWritten = outputsWritten;
      program->IsPositionInvariant = IsPositionInvariant;

      /* make copy of the input program string */
      strLen = _mesa_strlen((const char *) str);
      newString = (GLubyte *) MALLOC(strLen + 1);
      if (!newString) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glLoadProgramNV");
         return;
      }
      MEMCPY(newString, str, strLen);
      newString[strLen] = 0; /* terminate */

      /* copy the compiled instructions */
      assert(numInst <= VP_MAX_INSTRUCTIONS);
      newInst = (struct vp_instruction *) MALLOC(numInst * sizeof(struct vp_instruction));
      if (!newInst) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glLoadProgramNV");
         return;  /* out of memory */
      }
      MEMCPY(newInst, instBuffer, numInst * sizeof(struct vp_instruction));

      /* install the program */
      program->Target = target;
      if (program->String) {
         FREE(program->String);
      }
      program->String = newString;
      if (program->Instructions) {
         FREE(program->Instructions);
      }
      program->Instructions = newInst;

#if 0
      _mesa_printf("--- glLoadProgramNV result ---\n");
      _mesa_print_program(program);
      _mesa_printf("------------------------------\n");
#endif
   }
   else {
      /* Error! */
#ifdef DEBUG
      /* print a message showing the program line containing the error */
      ctx->VertexProgram.ErrorPos = s - str;
      {
         const GLubyte *p = str, *line = str;
         int lineNum = 1, statementNum = 1, column = 0;
         char errorLine[1000];
         int i;
         while (*p && p < s) {  /* s is the error position */
            if (*p == '\n') {
               line = p + 1;
               lineNum++;
               column = 0;
            }
            else if (*p == ';') {
               statementNum++;
            }
            else
               column++;
            p++;
         }
         if (p) {
            /* Copy the line with the error into errorLine so we can null-
             * terminate it.
             */
            for (i = 0; line[i] != '\n' && line[i]; i++)
               errorLine[i] = (char) line[i];
            errorLine[i] = 0;
         }
         /*
         _mesa_debug("Error pos = %d  (%c) col %d\n",
                 ctx->VertexProgram.ErrorPos, *s, column);
         */
         _mesa_debug(ctx, "Vertex program error on line %2d: %s\n", lineNum, errorLine);
         _mesa_debug(ctx, "  (statement %2d) near column %2d: ", statementNum, column+1);
         for (i = 0; i < column; i++)
            _mesa_debug(ctx, " ");
         _mesa_debug(ctx, "^\n");
      }
#endif
      _mesa_error(ctx, GL_INVALID_OPERATION, "glLoadProgramNV");
   }
}


static void
PrintSrcReg(const struct vp_src_register *src)
{
   static const char comps[5] = "xyzw";
   if (src->Negate)
      _mesa_printf("-");
   if (src->RelAddr) {
      if (src->Register > 0)
         _mesa_printf("c[A0.x + %d]", src->Register);
      else if (src->Register < 0)
         _mesa_printf("c[A0.x - %d]", -src->Register);
      else
         _mesa_printf("c[A0.x]");
   }
   else if (src->Register >= VP_OUTPUT_REG_START
       && src->Register <= VP_OUTPUT_REG_END) {
      _mesa_printf("o[%s]", OutputRegisters[src->Register - VP_OUTPUT_REG_START]);
   }
   else if (src->Register >= VP_INPUT_REG_START
            && src->Register <= VP_INPUT_REG_END) {
      _mesa_printf("v[%s]", InputRegisters[src->Register - VP_INPUT_REG_START]);
   }
   else if (src->Register >= VP_PROG_REG_START
            && src->Register <= VP_PROG_REG_END) {
      _mesa_printf("c[%d]", src->Register - VP_PROG_REG_START);
   }
   else {
      _mesa_printf("R%d", src->Register - VP_TEMP_REG_START);
   }

   if (src->Swizzle[0] == src->Swizzle[1] &&
       src->Swizzle[0] == src->Swizzle[2] &&
       src->Swizzle[0] == src->Swizzle[3]) {
      _mesa_printf(".%c", comps[src->Swizzle[0]]);
   }
   else if (src->Swizzle[0] != 0 ||
            src->Swizzle[1] != 1 ||
            src->Swizzle[2] != 2 ||
            src->Swizzle[3] != 3) {
      _mesa_printf(".%c%c%c%c",
             comps[src->Swizzle[0]],
             comps[src->Swizzle[1]],
             comps[src->Swizzle[2]],
             comps[src->Swizzle[3]]);
   }
}


static void
PrintDstReg(const struct vp_dst_register *dst)
{
   GLint w = dst->WriteMask[0] + dst->WriteMask[1]
           + dst->WriteMask[2] + dst->WriteMask[3];

   if (dst->Register >= VP_OUTPUT_REG_START
       && dst->Register <= VP_OUTPUT_REG_END) {
      _mesa_printf("o[%s]", OutputRegisters[dst->Register - VP_OUTPUT_REG_START]);
   }
   else if (dst->Register >= VP_INPUT_REG_START
            && dst->Register <= VP_INPUT_REG_END) {
      _mesa_printf("v[%s]", InputRegisters[dst->Register - VP_INPUT_REG_START]);
   }
   else if (dst->Register >= VP_PROG_REG_START
            && dst->Register <= VP_PROG_REG_END) {
      _mesa_printf("c[%d]", dst->Register - VP_PROG_REG_START);
   }
   else {
      _mesa_printf("R%d", dst->Register - VP_TEMP_REG_START);
   }

   if (w != 0 && w != 4) {
      _mesa_printf(".");
      if (dst->WriteMask[0])
         _mesa_printf("x");
      if (dst->WriteMask[1])
         _mesa_printf("y");
      if (dst->WriteMask[2])
         _mesa_printf("z");
      if (dst->WriteMask[3])
         _mesa_printf("w");
   }
}


/**
 * Print (unparse) the given vertex program.  Just for debugging.
 */
void
_mesa_print_program(const struct vp_program *program)
{
   const struct vp_instruction *inst;

   for (inst = program->Instructions; ; inst++) {
      switch (inst->Opcode) {
      case MOV:
      case LIT:
      case RCP:
      case RSQ:
      case EXP:
      case LOG:
      case RCC:
      case ABS:
         _mesa_printf("%s ", Opcodes[(int) inst->Opcode]);
         PrintDstReg(&inst->DstReg);
         _mesa_printf(", ");
         PrintSrcReg(&inst->SrcReg[0]);
         _mesa_printf(";\n");
         break;
      case MUL:
      case ADD:
      case DP3:
      case DP4:
      case DST:
      case MIN:
      case MAX:
      case SLT:
      case SGE:
      case DPH:
      case SUB:
         _mesa_printf("%s ", Opcodes[(int) inst->Opcode]);
         PrintDstReg(&inst->DstReg);
         _mesa_printf(", ");
         PrintSrcReg(&inst->SrcReg[0]);
         _mesa_printf(", ");
         PrintSrcReg(&inst->SrcReg[1]);
         _mesa_printf(";\n");
         break;
      case MAD:
         _mesa_printf("MAD ");
         PrintDstReg(&inst->DstReg);
         _mesa_printf(", ");
         PrintSrcReg(&inst->SrcReg[0]);
         _mesa_printf(", ");
         PrintSrcReg(&inst->SrcReg[1]);
         _mesa_printf(", ");
         PrintSrcReg(&inst->SrcReg[2]);
         _mesa_printf(";\n");
         break;
      case ARL:
         _mesa_printf("ARL A0.x, ");
         PrintSrcReg(&inst->SrcReg[0]);
         _mesa_printf(";\n");
         break;
      case END:
         _mesa_printf("END\n");
         return;
         break;
      default:
         _mesa_printf("BAD INSTRUCTION\n");
      }
   }
}

