/*
 * Direct3D shader assembler
 *
 * Copyright 2008 Stefan Dösinger
 * Copyright 2009 Matteo Bruni
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

%{
#include "config.h"
#include "wine/port.h"
#include "wine/debug.h"

#include "d3dcompiler_private.h"

#include <stdio.h>

WINE_DEFAULT_DEBUG_CHANNEL(asmshader);

struct asm_parser asm_ctx;

/* Error reporting function */
void asmparser_message(struct asm_parser *ctx, const char *fmt, ...) {
    va_list args;
    char* newbuffer;
    int rc, newsize;

    if(ctx->messagecapacity == 0) {
        ctx->messages = asm_alloc(MESSAGEBUFFER_INITIAL_SIZE);
        if(ctx->messages == NULL) {
            ERR("Error allocating memory for parser messages\n");
            return;
        }
        ctx->messagecapacity = MESSAGEBUFFER_INITIAL_SIZE;
    }

    while(1) {
        va_start(args, fmt);
        rc = vsnprintf(ctx->messages + ctx->messagesize,
                       ctx->messagecapacity - ctx->messagesize, fmt, args);
        va_end(args);

        if (rc < 0 ||                                           /* C89 */
            rc >= ctx->messagecapacity - ctx->messagesize) {    /* C99 */
            /* Resize the buffer */
            newsize = ctx->messagecapacity * 2;
            newbuffer = asm_realloc(ctx->messages, newsize);
            if(newbuffer == NULL){
                ERR("Error reallocating memory for parser messages\n");
                return;
            }
            ctx->messages = newbuffer;
            ctx->messagecapacity = newsize;
        } else {
            ctx->messagesize += rc;
            return;
        }
    }
}

static void asmshader_error(char const *s) {
    asmparser_message(&asm_ctx, "Line %u: Error \"%s\" from bison\n", asm_ctx.line_no, s);
    set_parse_status(&asm_ctx, PARSE_ERR);
}

static void set_rel_reg(struct shader_reg *reg, struct rel_reg *rel) {
    /* We can have an additional offset without true relative addressing
     * ex. c2[ 4 ] */
    reg->regnum += rel->additional_offset;
    if(!rel->has_rel_reg) {
        reg->rel_reg = NULL;
    } else {
        reg->rel_reg = asm_alloc(sizeof(*reg->rel_reg));
        if(!reg->rel_reg) {
            return;
        }
        reg->rel_reg->type = rel->type;
        reg->rel_reg->u.swizzle = rel->swizzle;
        reg->rel_reg->regnum = rel->rel_regnum;
    }
}

/* Needed lexer functions declarations */
int asmshader_lex(void);


%}

%union {
    struct {
        float           val;
        BOOL            integer;
    } immval;
    BOOL                immbool;
    unsigned int        regnum;
    struct shader_reg   reg;
    DWORD               srcmod;
    DWORD               writemask;
    struct {
        DWORD           writemask;
        DWORD           idx;
        DWORD           last;
    } wm_components;
    DWORD               swizzle;
    struct {
        DWORD           swizzle;
        DWORD           idx;
    } sw_components;
    DWORD               component;
    struct {
        DWORD           mod;
        DWORD           shift;
    } modshift;
    BWRITER_COMPARISON_TYPE comptype;
    struct {
        DWORD           dclusage;
        unsigned int    regnum;
    } declaration;
    BWRITERSAMPLER_TEXTURE_TYPE samplertype;
    struct rel_reg      rel_reg;
    struct src_regs     sregs;
}

/* Common instructions between vertex and pixel shaders */
%token INSTR_ADD
%token INSTR_NOP
%token INSTR_MOV
%token INSTR_SUB
%token INSTR_MAD
%token INSTR_MUL
%token INSTR_RCP
%token INSTR_RSQ
%token INSTR_DP3
%token INSTR_DP4
%token INSTR_MIN
%token INSTR_MAX
%token INSTR_SLT
%token INSTR_SGE
%token INSTR_ABS
%token INSTR_EXP
%token INSTR_LOG
%token INSTR_EXPP
%token INSTR_LOGP
%token INSTR_DST
%token INSTR_LRP
%token INSTR_FRC
%token INSTR_POW
%token INSTR_CRS
%token INSTR_SGN
%token INSTR_NRM
%token INSTR_SINCOS
%token INSTR_M4x4
%token INSTR_M4x3
%token INSTR_M3x4
%token INSTR_M3x3
%token INSTR_M3x2
%token INSTR_DCL
%token INSTR_DEF
%token INSTR_DEFB
%token INSTR_DEFI
%token INSTR_REP
%token INSTR_ENDREP
%token INSTR_IF
%token INSTR_ELSE
%token INSTR_ENDIF
%token INSTR_BREAK
%token INSTR_BREAKP
%token INSTR_CALL
%token INSTR_CALLNZ
%token INSTR_LOOP
%token INSTR_RET
%token INSTR_ENDLOOP
%token INSTR_LABEL
%token INSTR_SETP
%token INSTR_TEXLDL

/* Vertex shader only instructions  */
%token INSTR_LIT
%token INSTR_MOVA

/* Pixel shader only instructions   */
%token INSTR_CND
%token INSTR_CMP
%token INSTR_DP2ADD
%token INSTR_TEXCOORD
%token INSTR_TEXCRD
%token INSTR_TEXKILL
%token INSTR_TEX
%token INSTR_TEXLD
%token INSTR_TEXBEM
%token INSTR_TEXBEML
%token INSTR_TEXREG2AR
%token INSTR_TEXREG2GB
%token INSTR_TEXREG2RGB
%token INSTR_TEXM3x2PAD
%token INSTR_TEXM3x2TEX
%token INSTR_TEXM3x3PAD
%token INSTR_TEXM3x3SPEC
%token INSTR_TEXM3x3VSPEC
%token INSTR_TEXM3x3TEX
%token INSTR_TEXDP3TEX
%token INSTR_TEXM3x2DEPTH
%token INSTR_TEXDP3
%token INSTR_TEXM3x3
%token INSTR_TEXDEPTH
%token INSTR_BEM
%token INSTR_DSX
%token INSTR_DSY
%token INSTR_TEXLDP
%token INSTR_TEXLDB
%token INSTR_TEXLDD
%token INSTR_PHASE

/* Registers */
%token <regnum> REG_TEMP
%token <regnum> REG_OUTPUT
%token <regnum> REG_INPUT
%token <regnum> REG_CONSTFLOAT
%token <regnum> REG_CONSTINT
%token <regnum> REG_CONSTBOOL
%token <regnum> REG_TEXTURE
%token <regnum> REG_SAMPLER
%token <regnum> REG_TEXCRDOUT
%token REG_OPOS
%token REG_OFOG
%token REG_OPTS
%token <regnum> REG_VERTEXCOLOR
%token <regnum> REG_FRAGCOLOR
%token REG_FRAGDEPTH
%token REG_VPOS
%token REG_VFACE
%token REG_ADDRESS
%token REG_LOOP
%token REG_PREDICATE
%token <regnum> REG_LABEL

/* Version tokens */
%token VER_VS10
%token VER_VS11
%token VER_VS20
%token VER_VS2X
%token VER_VS30

%token VER_PS10
%token VER_PS11
%token VER_PS12
%token VER_PS13
%token VER_PS14
%token VER_PS20
%token VER_PS2X
%token VER_PS30

/* Output modifiers */
%token SHIFT_X2
%token SHIFT_X4
%token SHIFT_X8
%token SHIFT_D2
%token SHIFT_D4
%token SHIFT_D8
%token MOD_SAT
%token MOD_PP
%token MOD_CENTROID

/* Compare tokens */
%token COMP_GT
%token COMP_LT
%token COMP_GE
%token COMP_LE
%token COMP_EQ
%token COMP_NE

/* Source register modifiers */
%token SMOD_BIAS
%token SMOD_SCALEBIAS
%token SMOD_DZ
%token SMOD_DW
%token SMOD_ABS
%token SMOD_NOT

/* Sampler types */
%token SAMPTYPE_1D
%token SAMPTYPE_2D
%token SAMPTYPE_CUBE
%token SAMPTYPE_VOLUME

/* Usage declaration tokens */
%token <regnum> USAGE_POSITION
%token <regnum> USAGE_BLENDWEIGHT
%token <regnum> USAGE_BLENDINDICES
%token <regnum> USAGE_NORMAL
%token <regnum> USAGE_PSIZE
%token <regnum> USAGE_TEXCOORD
%token <regnum> USAGE_TANGENT
%token <regnum> USAGE_BINORMAL
%token <regnum> USAGE_TESSFACTOR
%token <regnum> USAGE_POSITIONT
%token <regnum> USAGE_COLOR
%token <regnum> USAGE_FOG
%token <regnum> USAGE_DEPTH
%token <regnum> USAGE_SAMPLE

/* Misc stuff */
%token <component> COMPONENT
%token <immval> IMMVAL
%token <immbool> IMMBOOL

%type <reg> dreg_name
%type <reg> dreg
%type <reg> sreg_name
%type <reg> relreg_name
%type <reg> sreg
%type <srcmod> smod
%type <writemask> writemask
%type <wm_components> wm_components
%type <swizzle> swizzle
%type <sw_components> sw_components
%type <modshift> omods
%type <modshift> omodifier
%type <comptype> comp
%type <declaration> dclusage
%type <reg> dcl_inputreg
%type <samplertype> sampdcl
%type <rel_reg> rel_reg
%type <reg> predicate
%type <immval> immsum
%type <sregs> sregs

%%

shader:               version_marker instructions
                        {
                            asm_ctx.funcs->end(&asm_ctx);
                        }

version_marker:       VER_VS10
                        {
                            TRACE("Vertex shader 1.0\n");
                            create_vs10_parser(&asm_ctx);
                        }
                    | VER_VS11
                        {
                            TRACE("Vertex shader 1.1\n");
                            create_vs11_parser(&asm_ctx);
                        }
                    | VER_VS20
                        {
                            TRACE("Vertex shader 2.0\n");
                            create_vs20_parser(&asm_ctx);
                        }
                    | VER_VS2X
                        {
                            TRACE("Vertex shader 2.x\n");
                            create_vs2x_parser(&asm_ctx);
                        }
                    | VER_VS30
                        {
                            TRACE("Vertex shader 3.0\n");
                            create_vs30_parser(&asm_ctx);
                        }
                    | VER_PS10
                        {
                            TRACE("Pixel  shader 1.0\n");
                            create_ps10_parser(&asm_ctx);
                        }
                    | VER_PS11
                        {
                            TRACE("Pixel  shader 1.1\n");
                            create_ps11_parser(&asm_ctx);
                        }
                    | VER_PS12
                        {
                            TRACE("Pixel  shader 1.2\n");
                            create_ps12_parser(&asm_ctx);
                        }
                    | VER_PS13
                        {
                            TRACE("Pixel  shader 1.3\n");
                            create_ps13_parser(&asm_ctx);
                        }
                    | VER_PS14
                        {
                            TRACE("Pixel  shader 1.4\n");
                            create_ps14_parser(&asm_ctx);
                        }
                    | VER_PS20
                        {
                            TRACE("Pixel  shader 2.0\n");
                            create_ps20_parser(&asm_ctx);
                        }
                    | VER_PS2X
                        {
                            TRACE("Pixel  shader 2.x\n");
                            create_ps2x_parser(&asm_ctx);
                        }
                    | VER_PS30
                        {
                            TRACE("Pixel  shader 3.0\n");
                            create_ps30_parser(&asm_ctx);
                        }

instructions:         /* empty */
                    | instructions complexinstr
                            {
                                /* Nothing to do */
                            }

complexinstr:         instruction
                            {

                            }
                    | predicate instruction
                            {
                                TRACE("predicate\n");
                                asm_ctx.funcs->predicate(&asm_ctx, &$1);
                            }
                    | '+' instruction
                            {
                                TRACE("coissue\n");
                                asm_ctx.funcs->coissue(&asm_ctx);
                            }

instruction:          INSTR_ADD omods dreg ',' sregs
                            {
                                TRACE("ADD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_ADD, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_NOP
                            {
                                TRACE("NOP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_NOP, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_MOV omods dreg ',' sregs
                            {
                                TRACE("MOV\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_MOV, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_SUB omods dreg ',' sregs
                            {
                                TRACE("SUB\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_SUB, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_MAD omods dreg ',' sregs
                            {
                                TRACE("MAD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_MAD, $2.mod, $2.shift, 0, &$3, &$5, 3);
                            }
                    | INSTR_MUL omods dreg ',' sregs
                            {
                                TRACE("MUL\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_MUL, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_RCP omods dreg ',' sregs
                            {
                                TRACE("RCP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_RCP, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_RSQ omods dreg ',' sregs
                            {
                                TRACE("RSQ\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_RSQ, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_DP3 omods dreg ',' sregs
                            {
                                TRACE("DP3\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_DP3, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_DP4 omods dreg ',' sregs
                            {
                                TRACE("DP4\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_DP4, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_MIN omods dreg ',' sregs
                            {
                                TRACE("MIN\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_MIN, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_MAX omods dreg ',' sregs
                            {
                                TRACE("MAX\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_MAX, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_SLT omods dreg ',' sregs
                            {
                                TRACE("SLT\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_SLT, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_SGE omods dreg ',' sregs
                            {
                                TRACE("SGE\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_SGE, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_ABS omods dreg ',' sregs
                            {
                                TRACE("ABS\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_ABS, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_EXP omods dreg ',' sregs
                            {
                                TRACE("EXP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_EXP, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_LOG omods dreg ',' sregs
                            {
                                TRACE("LOG\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_LOG, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_LOGP omods dreg ',' sregs
                            {
                                TRACE("LOGP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_LOGP, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_EXPP omods dreg ',' sregs
                            {
                                TRACE("EXPP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_EXPP, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_DST omods dreg ',' sregs
                            {
                                TRACE("DST\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_DST, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_LRP omods dreg ',' sregs
                            {
                                TRACE("LRP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_LRP, $2.mod, $2.shift, 0, &$3, &$5, 3);
                            }
                    | INSTR_FRC omods dreg ',' sregs
                            {
                                TRACE("FRC\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_FRC, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_POW omods dreg ',' sregs
                            {
                                TRACE("POW\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_POW, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_CRS omods dreg ',' sregs
                            {
                                TRACE("CRS\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_CRS, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_SGN omods dreg ',' sregs
                            {
                                TRACE("SGN\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_SGN, $2.mod, $2.shift, 0, &$3, &$5, 3);
                            }
                    | INSTR_NRM omods dreg ',' sregs
                            {
                                TRACE("NRM\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_NRM, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_SINCOS omods dreg ',' sregs
                            {
                                TRACE("SINCOS\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_SINCOS, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_M4x4 omods dreg ',' sregs
                            {
                                TRACE("M4x4\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_M4x4, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_M4x3 omods dreg ',' sregs
                            {
                                TRACE("M4x3\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_M4x3, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_M3x4 omods dreg ',' sregs
                            {
                                TRACE("M3x4\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_M3x4, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_M3x3 omods dreg ',' sregs
                            {
                                TRACE("M3x3\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_M3x3, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_M3x2 omods dreg ',' sregs
                            {
                                TRACE("M3x2\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_M3x2, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_DCL dclusage REG_OUTPUT
                            {
                                struct shader_reg reg;
                                TRACE("Output reg declaration\n");
                                ZeroMemory(&reg, sizeof(reg));
                                reg.type = BWRITERSPR_OUTPUT;
                                reg.regnum = $3;
                                reg.rel_reg = NULL;
                                reg.srcmod = 0;
                                reg.u.writemask = BWRITERSP_WRITEMASK_ALL;
                                asm_ctx.funcs->dcl_output(&asm_ctx, $2.dclusage, $2.regnum, &reg);
                            }
                    | INSTR_DCL dclusage REG_OUTPUT writemask
                            {
                                struct shader_reg reg;
                                TRACE("Output reg declaration\n");
                                ZeroMemory(&reg, sizeof(reg));
                                reg.type = BWRITERSPR_OUTPUT;
                                reg.regnum = $3;
                                reg.rel_reg = NULL;
                                reg.srcmod = 0;
                                reg.u.writemask = $4;
                                asm_ctx.funcs->dcl_output(&asm_ctx, $2.dclusage, $2.regnum, &reg);
                            }
                    | INSTR_DCL dclusage omods dcl_inputreg
                            {
                                struct shader_reg reg;
                                TRACE("Input reg declaration\n");
                                if($3.shift != 0) {
                                    asmparser_message(&asm_ctx, "Line %u: Shift modifier not allowed here\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                if(asm_ctx.shader->version == BWRITERPS_VERSION(2, 0) ||
                                    asm_ctx.shader->version == BWRITERPS_VERSION(2, 1)) {
                                    asmparser_message(&asm_ctx, "Line %u: Declaration not supported in PS 2\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                ZeroMemory(&reg, sizeof(reg));
                                reg.type = $4.type;
                                reg.regnum = $4.regnum;
                                reg.rel_reg = NULL;
                                reg.srcmod = 0;
                                reg.u.writemask = BWRITERSP_WRITEMASK_ALL;
                                asm_ctx.funcs->dcl_input(&asm_ctx, $2.dclusage, $2.regnum, $3.mod, &reg);
                            }
                    | INSTR_DCL dclusage omods dcl_inputreg writemask
                            {
                                struct shader_reg reg;
                                TRACE("Input reg declaration\n");
                                if($3.shift != 0) {
                                    asmparser_message(&asm_ctx, "Line %u: Shift modifier not allowed here\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                if(asm_ctx.shader->version == BWRITERPS_VERSION(2, 0) ||
                                    asm_ctx.shader->version == BWRITERPS_VERSION(2, 1)) {
                                    asmparser_message(&asm_ctx, "Line %u: Declaration not supported in PS 2\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                ZeroMemory(&reg, sizeof(reg));
                                reg.type = $4.type;
                                reg.regnum = $4.regnum;
                                reg.rel_reg = NULL;
                                reg.srcmod = 0;
                                reg.u.writemask = $5;
                                asm_ctx.funcs->dcl_input(&asm_ctx, $2.dclusage, $2.regnum, $3.mod, &reg);
                            }
                    | INSTR_DCL omods dcl_inputreg
                            {
                                struct shader_reg reg;
                                TRACE("Input reg declaration\n");
                                if($2.shift != 0) {
                                    asmparser_message(&asm_ctx, "Line %u: Shift modifier not allowed here\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                if(asm_ctx.shader->type != ST_PIXEL) {
                                    asmparser_message(&asm_ctx, "Line %u: Declaration needs a semantic\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                ZeroMemory(&reg, sizeof(reg));
                                reg.type = $3.type;
                                reg.regnum = $3.regnum;
                                reg.rel_reg = NULL;
                                reg.srcmod = 0;
                                reg.u.writemask = BWRITERSP_WRITEMASK_ALL;
                                asm_ctx.funcs->dcl_input(&asm_ctx, 0, 0, $2.mod, &reg);
                            }
                    | INSTR_DCL omods dcl_inputreg writemask
                            {
                                struct shader_reg reg;
                                TRACE("Input reg declaration\n");
                                if($2.shift != 0) {
                                    asmparser_message(&asm_ctx, "Line %u: Shift modifier not allowed here\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                if(asm_ctx.shader->type != ST_PIXEL) {
                                    asmparser_message(&asm_ctx, "Line %u: Declaration needs a semantic\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                ZeroMemory(&reg, sizeof(reg));
                                reg.type = $3.type;
                                reg.regnum = $3.regnum;
                                reg.rel_reg = NULL;
                                reg.srcmod = 0;
                                reg.u.writemask = $4;
                                asm_ctx.funcs->dcl_input(&asm_ctx, 0, 0, $2.mod, &reg);
                            }
                    | INSTR_DCL sampdcl omods REG_SAMPLER
                            {
                                TRACE("Sampler declared\n");
                                if($3.shift != 0) {
                                    asmparser_message(&asm_ctx, "Line %u: Shift modifier not allowed here\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                asm_ctx.funcs->dcl_sampler(&asm_ctx, $2, $3.mod, $4, asm_ctx.line_no);
                            }
                    | INSTR_DCL omods REG_SAMPLER
                            {
                                TRACE("Sampler declared\n");
                                if($2.shift != 0) {
                                    asmparser_message(&asm_ctx, "Line %u: Shift modifier not allowed here\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                if(asm_ctx.shader->type != ST_PIXEL) {
                                    asmparser_message(&asm_ctx, "Line %u: Declaration needs a sampler type\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                }
                                asm_ctx.funcs->dcl_sampler(&asm_ctx, BWRITERSTT_UNKNOWN, $2.mod, $3, asm_ctx.line_no);
                            }
                    | INSTR_DCL sampdcl omods dcl_inputreg
                            {
                                TRACE("Error rule: sampler decl of input reg\n");
                                asmparser_message(&asm_ctx, "Line %u: Sampler declarations of input regs is not valid\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_WARN);
                            }
                    | INSTR_DCL sampdcl omods REG_OUTPUT
                            {
                                TRACE("Error rule: sampler decl of output reg\n");
                                asmparser_message(&asm_ctx, "Line %u: Sampler declarations of output regs is not valid\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_WARN);
                            }
                    | INSTR_DEF REG_CONSTFLOAT ',' IMMVAL ',' IMMVAL ',' IMMVAL ',' IMMVAL
                            {
                                asm_ctx.funcs->constF(&asm_ctx, $2, $4.val, $6.val, $8.val, $10.val);
                            }
                    | INSTR_DEFI REG_CONSTINT ',' IMMVAL ',' IMMVAL ',' IMMVAL ',' IMMVAL
                            {
                                asm_ctx.funcs->constI(&asm_ctx, $2, $4.val, $6.val, $8.val, $10.val);
                            }
                    | INSTR_DEFB REG_CONSTBOOL ',' IMMBOOL
                            {
                                asm_ctx.funcs->constB(&asm_ctx, $2, $4);
                            }
                    | INSTR_REP sregs
                            {
                                TRACE("REP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_REP, 0, 0, 0, 0, &$2, 1);
                            }
                    | INSTR_ENDREP
                            {
                                TRACE("ENDREP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_ENDREP, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_IF sregs
                            {
                                TRACE("IF\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_IF, 0, 0, 0, 0, &$2, 1);
                            }
                    | INSTR_IF comp sregs
                            {
                                TRACE("IFC\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_IFC, 0, 0, $2, 0, &$3, 2);
                            }
                    | INSTR_ELSE
                            {
                                TRACE("ELSE\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_ELSE, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_ENDIF
                            {
                                TRACE("ENDIF\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_ENDIF, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_BREAK
                            {
                                TRACE("BREAK\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_BREAK, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_BREAK comp sregs
                            {
                                TRACE("BREAKC\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_BREAKC, 0, 0, $2, 0, &$3, 2);
                            }
                    | INSTR_BREAKP sregs
                            {
                                TRACE("BREAKP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_BREAKP, 0, 0, 0, 0, &$2, 1);
                            }
                    | INSTR_CALL sregs
                            {
                                TRACE("CALL\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_CALL, 0, 0, 0, 0, &$2, 1);
                            }
                    | INSTR_CALLNZ sregs
                            {
                                TRACE("CALLNZ\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_CALLNZ, 0, 0, 0, 0, &$2, 2);
                            }
                    | INSTR_LOOP sregs
                            {
                                TRACE("LOOP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_LOOP, 0, 0, 0, 0, &$2, 2);
                            }
                    | INSTR_RET
                            {
                                TRACE("RET\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_RET, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_ENDLOOP
                            {
                                TRACE("ENDLOOP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_ENDLOOP, 0, 0, 0, 0, 0, 0);
                            }
                    | INSTR_LABEL sregs
                            {
                                TRACE("LABEL\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_LABEL, 0, 0, 0, 0, &$2, 1);
                            }
                    | INSTR_SETP comp dreg ',' sregs
                            {
                                TRACE("SETP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_SETP, 0, 0, $2, &$3, &$5, 2);
                            }
                    | INSTR_TEXLDL omods dreg ',' sregs
                            {
                                TRACE("TEXLDL\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXLDL, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_LIT omods dreg ',' sregs
                            {
                                TRACE("LIT\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_LIT, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_MOVA omods dreg ',' sregs
                            {
                                TRACE("MOVA\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_MOVA, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_CND omods dreg ',' sregs
                            {
                                TRACE("CND\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_CND, $2.mod, $2.shift, 0, &$3, &$5, 3);
                            }
                    | INSTR_CMP omods dreg ',' sregs
                            {
                                TRACE("CMP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_CMP, $2.mod, $2.shift, 0, &$3, &$5, 3);
                            }
                    | INSTR_DP2ADD omods dreg ',' sregs
                            {
                                TRACE("DP2ADD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_DP2ADD, $2.mod, $2.shift, 0, &$3, &$5, 3);
                            }
                    | INSTR_TEXCOORD omods dreg
                            {
                                TRACE("TEXCOORD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXCOORD, $2.mod, $2.shift, 0, &$3, 0, 0);
                            }
                    | INSTR_TEXCRD omods dreg ',' sregs
                            {
                                TRACE("TEXCRD\n");
                                /* texcoord and texcrd share the same opcode */
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXCOORD, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXKILL dreg
                            {
                                TRACE("TEXKILL\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXKILL, 0, 0, 0, &$2, 0, 0);
                            }
                    | INSTR_TEX omods dreg
                            {
                                TRACE("TEX\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEX, $2.mod, $2.shift, 0, &$3, 0, 0);
                            }
                    | INSTR_TEXDEPTH omods dreg
                            {
                                TRACE("TEXDEPTH\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXDEPTH, $2.mod, $2.shift, 0, &$3, 0, 0);
                            }
                    | INSTR_TEXLD omods dreg ',' sregs
                            {
                                TRACE("TEXLD\n");
                                /* There is more than one acceptable syntax for texld:
                                   with 1 sreg (PS 1.4) or
                                   with 2 sregs (PS 2.0+)
                                   Moreover, texld shares the same opcode as the tex instruction,
                                   so there are a total of 3 valid syntaxes
                                   These variations are handled in asmparser.c */
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEX, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_TEXLDP omods dreg ',' sregs
                            {
                                TRACE("TEXLDP\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXLDP, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_TEXLDB omods dreg ',' sregs
                            {
                                TRACE("TEXLDB\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXLDB, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_TEXBEM omods dreg ',' sregs
                            {
                                TRACE("TEXBEM\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXBEM, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXBEML omods dreg ',' sregs
                            {
                                TRACE("TEXBEML\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXBEML, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXREG2AR omods dreg ',' sregs
                            {
                                TRACE("TEXREG2AR\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXREG2AR, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXREG2GB omods dreg ',' sregs
                            {
                                TRACE("TEXREG2GB\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXREG2GB, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXREG2RGB omods dreg ',' sregs
                            {
                                TRACE("TEXREG2RGB\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXREG2RGB, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x2PAD omods dreg ',' sregs
                            {
                                TRACE("TEXM3x2PAD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x2PAD, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x3PAD omods dreg ',' sregs
                            {
                                TRACE("INSTR_TEXM3x3PAD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x3PAD, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x3SPEC omods dreg ',' sregs
                            {
                                TRACE("TEXM3x3SPEC\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x3SPEC, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_TEXM3x3VSPEC omods dreg ',' sregs
                            {
                                TRACE("TEXM3x3VSPEC\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x3VSPEC, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x3TEX omods dreg ',' sregs
                            {
                                TRACE("TEXM3x3TEX\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x3TEX, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXDP3TEX omods dreg ',' sregs
                            {
                                TRACE("TEXDP3TEX\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXDP3TEX, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x2DEPTH omods dreg ',' sregs
                            {
                                TRACE("TEXM3x2DEPTH\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x2DEPTH, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x2TEX omods dreg ',' sregs
                            {
                                TRACE("TEXM3x2TEX\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x2TEX, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXDP3 omods dreg ',' sregs
                            {
                                TRACE("TEXDP3\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXDP3, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXM3x3 omods dreg ',' sregs
                            {
                                TRACE("TEXM3x3\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXM3x3, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_BEM omods dreg ',' sregs
                            {
                                TRACE("BEM\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_BEM, $2.mod, $2.shift, 0, &$3, &$5, 2);
                            }
                    | INSTR_DSX omods dreg ',' sregs
                            {
                                TRACE("DSX\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_DSX, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_DSY omods dreg ',' sregs
                            {
                                TRACE("DSY\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_DSY, $2.mod, $2.shift, 0, &$3, &$5, 1);
                            }
                    | INSTR_TEXLDD omods dreg ',' sregs
                            {
                                TRACE("TEXLDD\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_TEXLDD, $2.mod, $2.shift, 0, &$3, &$5, 4);
                            }
                    | INSTR_PHASE
                            {
                                TRACE("PHASE\n");
                                asm_ctx.funcs->instr(&asm_ctx, BWRITERSIO_PHASE, 0, 0, 0, 0, 0, 0);
                            }


dreg:                 dreg_name rel_reg
                            {
                                $$.regnum = $1.regnum;
                                $$.type = $1.type;
                                $$.u.writemask = BWRITERSP_WRITEMASK_ALL;
                                $$.srcmod = BWRITERSPSM_NONE;
                                set_rel_reg(&$$, &$2);
                            }
                    | dreg_name writemask
                            {
                                $$.regnum = $1.regnum;
                                $$.type = $1.type;
                                $$.u.writemask = $2;
                                $$.srcmod = BWRITERSPSM_NONE;
                                $$.rel_reg = NULL;
                            }

dreg_name:            REG_TEMP
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_TEMP;
                        }
                    | REG_OUTPUT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_OUTPUT;
                        }
                    | REG_INPUT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_INPUT;
                        }
                    | REG_CONSTFLOAT
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register c%u is not a valid destination register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_CONSTINT
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register i%u is not a valid destination register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_CONSTBOOL
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register b%u is not a valid destination register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_TEXTURE
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_TEXTURE;
                        }
                    | REG_TEXCRDOUT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_TEXCRDOUT;
                        }
                    | REG_SAMPLER
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register s%u is not a valid destination register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_OPOS
                        {
                            $$.regnum = BWRITERSRO_POSITION; $$.type = BWRITERSPR_RASTOUT;
                        }
                    | REG_OPTS
                        {
                            $$.regnum = BWRITERSRO_POINT_SIZE; $$.type = BWRITERSPR_RASTOUT;
                        }
                    | REG_OFOG
                        {
                            $$.regnum = BWRITERSRO_FOG; $$.type = BWRITERSPR_RASTOUT;
                        }
                    | REG_VERTEXCOLOR
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_ATTROUT;
                        }
                    | REG_FRAGCOLOR
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_COLOROUT;
                        }
                    | REG_FRAGDEPTH
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_DEPTHOUT;
                        }
                    | REG_PREDICATE
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_PREDICATE;
                        }
                    | REG_VPOS
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register vPos is not a valid destination register\n",
                                              asm_ctx.line_no);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_VFACE
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register vFace is not a valid destination register\n",
                                              asm_ctx.line_no);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_ADDRESS
                        {
                            /* index 0 is hardcoded for the addr register */
                            $$.regnum = 0; $$.type = BWRITERSPR_ADDR;
                        }
                    | REG_LOOP
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register aL is not a valid destination register\n",
                                              asm_ctx.line_no);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }

writemask:            '.' wm_components
                        {
                            if($2.writemask == SWIZZLE_ERR) {
                                asmparser_message(&asm_ctx, "Line %u: Invalid writemask specified\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                                /* Provide a correct writemask to prevent following complaints */
                                $$ = BWRITERSP_WRITEMASK_ALL;
                            }
                            else {
                                $$ = $2.writemask;
                                TRACE("Writemask: %x\n", $$);
                            }
                        }

wm_components:        COMPONENT
                        {
                            $$.writemask = 1 << $1;
                            $$.last = $1;
                            $$.idx = 1;
                        }
                    | wm_components COMPONENT
                        {
                            if($1.writemask == SWIZZLE_ERR || $1.idx == 4)
                                /* Wrong writemask */
                                $$.writemask = SWIZZLE_ERR;
                            else {
                                if($2 <= $1.last)
                                    $$.writemask = SWIZZLE_ERR;
                                else {
                                    $$.writemask = $1.writemask | (1 << $2);
                                    $$.idx = $1.idx + 1;
                                }
                            }
                        }

swizzle:              /* empty */
                        {
                            $$ = BWRITERVS_NOSWIZZLE;
                            TRACE("Default swizzle: %08x\n", $$);
                        }
                    | '.' sw_components
                        {
                            if($2.swizzle == SWIZZLE_ERR) {
                                asmparser_message(&asm_ctx, "Line %u: Invalid swizzle\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                                /* Provide a correct swizzle to prevent following complaints */
                                $$ = BWRITERVS_NOSWIZZLE;
                            }
                            else {
                                DWORD last, i;

                                $$ = $2.swizzle << BWRITERVS_SWIZZLE_SHIFT;
                                /* Fill the swizzle by extending the last component */
                                last = ($2.swizzle >> 2 * ($2.idx - 1)) & 0x03;
                                for(i = $2.idx; i < 4; i++){
                                    $$ |= last << (BWRITERVS_SWIZZLE_SHIFT + 2 * i);
                                }
                                TRACE("Got a swizzle: %08x\n", $$);
                            }
                        }

sw_components:        COMPONENT
                        {
                            $$.swizzle = $1;
                            $$.idx = 1;
                        }
                    | sw_components COMPONENT
                        {
                            if($1.idx == 4) {
                                /* Too many sw_components */
                                $$.swizzle = SWIZZLE_ERR;
                                $$.idx = 4;
                            }
                            else {
                                $$.swizzle = $1.swizzle | ($2 << 2 * $1.idx);
                                $$.idx = $1.idx + 1;
                            }
                        }

omods:                 /* Empty */
                        {
                            $$.mod = 0;
                            $$.shift = 0;
                        }
                    | omods omodifier
                        {
                            $$.mod = $1.mod | $2.mod;
                            if($1.shift && $2.shift) {
                                asmparser_message(&asm_ctx, "Line %u: More than one shift flag\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                                $$.shift = $1.shift;
                            } else {
                                $$.shift = $1.shift | $2.shift;
                            }
                        }

omodifier:            SHIFT_X2
                        {
                            $$.mod = 0;
                            $$.shift = 1;
                        }
                    | SHIFT_X4
                        {
                            $$.mod = 0;
                            $$.shift = 2;
                        }
                    | SHIFT_X8
                        {
                            $$.mod = 0;
                            $$.shift = 3;
                        }
                    | SHIFT_D2
                        {
                            $$.mod = 0;
                            $$.shift = 15;
                        }
                    | SHIFT_D4
                        {
                            $$.mod = 0;
                            $$.shift = 14;
                        }
                    | SHIFT_D8
                        {
                            $$.mod = 0;
                            $$.shift = 13;
                        }
                    | MOD_SAT
                        {
                            $$.mod = BWRITERSPDM_SATURATE;
                            $$.shift = 0;
                        }
                    | MOD_PP
                        {
                            $$.mod = BWRITERSPDM_PARTIALPRECISION;
                            $$.shift = 0;
                        }
                    | MOD_CENTROID
                        {
                            $$.mod = BWRITERSPDM_MSAMPCENTROID;
                            $$.shift = 0;
                        }

sregs:                sreg
                        {
                            $$.reg[0] = $1;
                            $$.count = 1;
                        }
                    | sregs ',' sreg
                        {
                            if($$.count == MAX_SRC_REGS){
                                asmparser_message(&asm_ctx, "Line %u: Too many source registers in this instruction\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                            }
                            else
                                $$.reg[$$.count++] = $3;
                        }

sreg:                   sreg_name rel_reg swizzle
                        {
                            $$.type = $1.type;
                            $$.regnum = $1.regnum;
                            $$.u.swizzle = $3;
                            $$.srcmod = BWRITERSPSM_NONE;
                            set_rel_reg(&$$, &$2);
                        }
                    | sreg_name rel_reg smod swizzle
                        {
                            $$.type = $1.type;
                            $$.regnum = $1.regnum;
                            set_rel_reg(&$$, &$2);
                            $$.srcmod = $3;
                            $$.u.swizzle = $4;
                        }
                    | '-' sreg_name rel_reg swizzle
                        {
                            $$.type = $2.type;
                            $$.regnum = $2.regnum;
                            $$.srcmod = BWRITERSPSM_NEG;
                            set_rel_reg(&$$, &$3);
                            $$.u.swizzle = $4;
                        }
                    | '-' sreg_name rel_reg smod swizzle
                        {
                            $$.type = $2.type;
                            $$.regnum = $2.regnum;
                            set_rel_reg(&$$, &$3);
                            switch($4) {
                                case BWRITERSPSM_BIAS: $$.srcmod = BWRITERSPSM_BIASNEG; break;
                                case BWRITERSPSM_X2:   $$.srcmod = BWRITERSPSM_X2NEG;   break;
                                case BWRITERSPSM_SIGN: $$.srcmod = BWRITERSPSM_SIGNNEG; break;
                                case BWRITERSPSM_ABS:  $$.srcmod = BWRITERSPSM_ABSNEG;  break;
                                case BWRITERSPSM_DZ:
                                    asmparser_message(&asm_ctx, "Line %u: Incompatible source modifiers: NEG and DZ\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                    break;
                                case BWRITERSPSM_DW:
                                    asmparser_message(&asm_ctx, "Line %u: Incompatible source modifiers: NEG and DW\n",
                                                      asm_ctx.line_no);
                                    set_parse_status(&asm_ctx, PARSE_ERR);
                                    break;
                                default:
                                    FIXME("Unhandled combination of NEGATE and %u\n", $4);
                            }
                            $$.u.swizzle = $5;
                        }
                    | IMMVAL '-' sreg_name rel_reg swizzle
                        {
                            if($1.val != 1.0 || (!$1.integer)) {
                                asmparser_message(&asm_ctx, "Line %u: Only \"1 - reg\" is valid for D3DSPSM_COMP, "
                                                  "%g - reg found\n", asm_ctx.line_no, $1.val);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                            }
                            /* Complement - not compatible with other source modifiers */
                            $$.type = $3.type;
                            $$.regnum = $3.regnum;
                            $$.srcmod = BWRITERSPSM_COMP;
                            set_rel_reg(&$$, &$4);
                            $$.u.swizzle = $5;
                        }
                    | IMMVAL '-' sreg_name rel_reg smod swizzle
                        {
                            /* For nicer error reporting */
                            if($1.val != 1.0 || (!$1.integer)) {
                                asmparser_message(&asm_ctx, "Line %u: Only \"1 - reg\" is valid for D3DSPSM_COMP\n",
                                                  asm_ctx.line_no);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                            } else {
                                asmparser_message(&asm_ctx, "Line %u: Incompatible source modifiers: D3DSPSM_COMP and %s\n",
                                                  asm_ctx.line_no,
                                                  debug_print_srcmod($5));
                                set_parse_status(&asm_ctx, PARSE_ERR);
                            }
                        }
                    | SMOD_NOT sreg_name swizzle
                        {
                            $$.type = $2.type;
                            $$.regnum = $2.regnum;
                            $$.rel_reg = NULL;
                            $$.srcmod = BWRITERSPSM_NOT;
                            $$.u.swizzle = $3;
                        }

rel_reg:               /* empty */
                        {
                            $$.has_rel_reg = FALSE;
                            $$.additional_offset = 0;
                        }
                    | '[' immsum ']'
                        {
                            $$.has_rel_reg = FALSE;
                            $$.additional_offset = $2.val;
                        }
                    | '[' relreg_name swizzle ']'
                        {
                            $$.has_rel_reg = TRUE;
                            $$.type = $2.type;
                            $$.additional_offset = 0;
                            $$.rel_regnum = $2.regnum;
                            $$.swizzle = $3;
                        }
                    | '[' immsum '+' relreg_name swizzle ']'
                        {
                            $$.has_rel_reg = TRUE;
                            $$.type = $4.type;
                            $$.additional_offset = $2.val;
                            $$.rel_regnum = $4.regnum;
                            $$.swizzle = $5;
                        }
                    | '[' relreg_name swizzle '+' immsum ']'
                        {
                            $$.has_rel_reg = TRUE;
                            $$.type = $2.type;
                            $$.additional_offset = $5.val;
                            $$.rel_regnum = $2.regnum;
                            $$.swizzle = $3;
                        }
                    | '[' immsum '+' relreg_name swizzle '+' immsum ']'
                        {
                            $$.has_rel_reg = TRUE;
                            $$.type = $4.type;
                            $$.additional_offset = $2.val + $7.val;
                            $$.rel_regnum = $4.regnum;
                            $$.swizzle = $5;
                        }

immsum:               IMMVAL
                        {
                            if(!$1.integer) {
                                asmparser_message(&asm_ctx, "Line %u: Unexpected float %f\n",
                                                  asm_ctx.line_no, $1.val);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                            }
                            $$.val = $1.val;
                        }
                    | immsum '+' IMMVAL
                        {
                            if(!$3.integer) {
                                asmparser_message(&asm_ctx, "Line %u: Unexpected float %f\n",
                                                  asm_ctx.line_no, $3.val);
                                set_parse_status(&asm_ctx, PARSE_ERR);
                            }
                            $$.val = $1.val + $3.val;
                        }

smod:                 SMOD_BIAS
                        {
                            $$ = BWRITERSPSM_BIAS;
                        }
                    | SHIFT_X2
                        {
                            $$ = BWRITERSPSM_X2;
                        }
                    | SMOD_SCALEBIAS
                        {
                            $$ = BWRITERSPSM_SIGN;
                        }
                    | SMOD_DZ
                        {
                            $$ = BWRITERSPSM_DZ;
                        }
                    | SMOD_DW
                        {
                            $$ = BWRITERSPSM_DW;
                        }
                    | SMOD_ABS
                        {
                            $$ = BWRITERSPSM_ABS;
                        }

relreg_name:          REG_ADDRESS
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_ADDR;
                        }
                    | REG_LOOP
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_LOOP;
                        }

sreg_name:            REG_TEMP
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_TEMP;
                        }
                    | REG_OUTPUT
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register o%u is not a valid source register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_INPUT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_INPUT;
                        }
                    | REG_CONSTFLOAT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_CONST;
                        }
                    | REG_CONSTINT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_CONSTINT;
                        }
                    | REG_CONSTBOOL
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_CONSTBOOL;
                        }
                    | REG_TEXTURE
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_TEXTURE;
                        }
                    | REG_TEXCRDOUT
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register oT%u is not a valid source register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_SAMPLER
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_SAMPLER;
                        }
                    | REG_OPOS
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register oPos is not a valid source register\n",
                                              asm_ctx.line_no);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_OFOG
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register oFog is not a valid source register\n",
                                              asm_ctx.line_no);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_VERTEXCOLOR
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register oD%u is not a valid source register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_FRAGCOLOR
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register oC%u is not a valid source register\n",
                                              asm_ctx.line_no, $1);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_FRAGDEPTH
                        {
                            asmparser_message(&asm_ctx, "Line %u: Register oDepth is not a valid source register\n",
                                              asm_ctx.line_no);
                            set_parse_status(&asm_ctx, PARSE_WARN);
                        }
                    | REG_PREDICATE
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_PREDICATE;
                        }
                    | REG_VPOS
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_MISCTYPE;
                        }
                    | REG_VFACE
                        {
                            $$.regnum = 1; $$.type = BWRITERSPR_MISCTYPE;
                        }
                    | REG_ADDRESS
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_ADDR;
                        }
                    | REG_LOOP
                        {
                            $$.regnum = 0; $$.type = BWRITERSPR_LOOP;
                        }
                    | REG_LABEL
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_LABEL;
                        }

comp:                 COMP_GT           { $$ = BWRITER_COMPARISON_GT;       }
                    | COMP_LT           { $$ = BWRITER_COMPARISON_LT;       }
                    | COMP_GE           { $$ = BWRITER_COMPARISON_GE;       }
                    | COMP_LE           { $$ = BWRITER_COMPARISON_LE;       }
                    | COMP_EQ           { $$ = BWRITER_COMPARISON_EQ;       }
                    | COMP_NE           { $$ = BWRITER_COMPARISON_NE;       }

dclusage:             USAGE_POSITION
                        {
                            TRACE("dcl_position%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_POSITION;
                        }
                    | USAGE_BLENDWEIGHT
                        {
                            TRACE("dcl_blendweight%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_BLENDWEIGHT;
                        }
                    | USAGE_BLENDINDICES
                        {
                            TRACE("dcl_blendindices%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_BLENDINDICES;
                        }
                    | USAGE_NORMAL
                        {
                            TRACE("dcl_normal%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_NORMAL;
                        }
                    | USAGE_PSIZE
                        {
                            TRACE("dcl_psize%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_PSIZE;
                        }
                    | USAGE_TEXCOORD
                        {
                            TRACE("dcl_texcoord%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_TEXCOORD;
                        }
                    | USAGE_TANGENT
                        {
                            TRACE("dcl_tangent%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_TANGENT;
                        }
                    | USAGE_BINORMAL
                        {
                            TRACE("dcl_binormal%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_BINORMAL;
                        }
                    | USAGE_TESSFACTOR
                        {
                            TRACE("dcl_tessfactor%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_TESSFACTOR;
                        }
                    | USAGE_POSITIONT
                        {
                            TRACE("dcl_positiont%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_POSITIONT;
                        }
                    | USAGE_COLOR
                        {
                            TRACE("dcl_color%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_COLOR;
                        }
                    | USAGE_FOG
                        {
                            TRACE("dcl_fog%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_FOG;
                        }
                    | USAGE_DEPTH
                        {
                            TRACE("dcl_depth%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_DEPTH;
                        }
                    | USAGE_SAMPLE
                        {
                            TRACE("dcl_sample%u\n", $1);
                            $$.regnum = $1;
                            $$.dclusage = BWRITERDECLUSAGE_SAMPLE;
                        }

dcl_inputreg:         REG_INPUT
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_INPUT;
                        }
                    | REG_TEXTURE
                        {
                            $$.regnum = $1; $$.type = BWRITERSPR_TEXTURE;
                        }

sampdcl:              SAMPTYPE_1D
                        {
                            $$ = BWRITERSTT_1D;
                        }
                    | SAMPTYPE_2D
                        {
                            $$ = BWRITERSTT_2D;
                        }
                    | SAMPTYPE_CUBE
                        {
                            $$ = BWRITERSTT_CUBE;
                        }
                    | SAMPTYPE_VOLUME
                        {
                            $$ = BWRITERSTT_VOLUME;
                        }

predicate:            '(' REG_PREDICATE swizzle ')'
                        {
                            $$.type = BWRITERSPR_PREDICATE;
                            $$.regnum = 0;
                            $$.rel_reg = NULL;
                            $$.srcmod = BWRITERSPSM_NONE;
                            $$.u.swizzle = $3;
                        }
                    | '(' SMOD_NOT REG_PREDICATE swizzle ')'
                        {
                            $$.type = BWRITERSPR_PREDICATE;
                            $$.regnum = 0;
                            $$.rel_reg = NULL;
                            $$.srcmod = BWRITERSPSM_NOT;
                            $$.u.swizzle = $4;
                        }

%%

/* New status is the worst between current status and parameter value */
void set_parse_status(struct asm_parser *ctx, enum parse_status status) {
    if(status == PARSE_ERR) ctx->status = PARSE_ERR;
    else if(status == PARSE_WARN && ctx->status == PARSE_SUCCESS) ctx->status = PARSE_WARN;
}

struct bwriter_shader *parse_asm_shader(char **messages) {
    struct bwriter_shader *ret = NULL;

    asm_ctx.shader = NULL;
    asm_ctx.status = PARSE_SUCCESS;
    asm_ctx.messagesize = asm_ctx.messagecapacity = 0;
    asm_ctx.line_no = 1;

    asmshader_parse();

    if(asm_ctx.status != PARSE_ERR) ret = asm_ctx.shader;
    else if(asm_ctx.shader) SlDeleteShader(asm_ctx.shader);

    if(messages) {
        if(asm_ctx.messagesize) {
            /* Shrink the buffer to the used size */
            *messages = asm_realloc(asm_ctx.messages, asm_ctx.messagesize + 1);
            if(!*messages) {
                ERR("Out of memory, no messages reported\n");
                asm_free(asm_ctx.messages);
            }
        } else {
            *messages = NULL;
        }
    } else {
        if(asm_ctx.messagecapacity) asm_free(asm_ctx.messages);
    }

    return ret;
}
