/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES Utilities
 * ------------------------------------------------
 *
 * Copyright 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Shader .test file utilities.
 *//*--------------------------------------------------------------------*/

#include "gluShaderLibrary.hpp"

#include "tcuStringTemplate.hpp"
#include "tcuResource.hpp"
#include "tcuTestLog.hpp"

#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deFilePath.hpp"

#include "glwEnums.hpp"

#include <sstream>
#include <map>
#include <cstdlib>

#if 0
#define PARSE_DBG(X) printf X
#else
#define PARSE_DBG(X) DE_NULL_STATEMENT
#endif

namespace glu
{
namespace sl
{

using namespace tcu;

using de::UniquePtr;
using std::map;
using std::ostringstream;
using std::pair;
using std::string;
using std::vector;

// Specification

bool isValid(const ValueBlock &block)
{
    for (size_t storageNdx = 0; storageNdx < 3; ++storageNdx)
    {
        const vector<Value> &values = storageNdx == 0 ? block.inputs : storageNdx == 1 ? block.outputs : block.uniforms;
        const size_t refArrayLen =
            values.empty() ? 0 : (values[0].elements.size() / (size_t)values[0].type.getScalarSize());

        for (size_t valNdx = 0; valNdx < values.size(); ++valNdx)
        {
            const Value &value = values[valNdx];

            if (!value.type.isBasicType())
            {
                print("ERROR: Value '%s' is of unsupported type!\n", value.name.c_str());
                return false;
            }

            if (value.elements.size() != refArrayLen * (size_t)value.type.getScalarSize())
            {
                print("ERROR: Value '%s' has invalid number of scalars!\n", value.name.c_str());
                return false;
            }
        }
    }

    return true;
}

bool isValid(const ShaderCaseSpecification &spec)
{
    const uint32_t vtxFragMask = (1u << SHADERTYPE_VERTEX) | (1u << SHADERTYPE_FRAGMENT);
    const uint32_t tessCtrlEvalMask =
        (1u << SHADERTYPE_TESSELLATION_CONTROL) | (1u << SHADERTYPE_TESSELLATION_EVALUATION);
    const uint32_t supportedStageMask = vtxFragMask | tessCtrlEvalMask | (1u << SHADERTYPE_GEOMETRY);
    const bool isSeparable            = !spec.programs.empty() && spec.programs[0].sources.separable;

    if (spec.programs.empty())
    {
        print("ERROR: No programs specified!\n");
        return false;
    }

    if (isCapabilityRequired(CAPABILITY_FULL_GLSL_ES_100_SUPPORT, spec))
    {
        if (spec.targetVersion != GLSL_VERSION_100_ES)
        {
            print("ERROR: Full GLSL ES 1.00 support requested for other GLSL version!\n");
            return false;
        }

        if (spec.expectResult != EXPECT_PASS && spec.expectResult != EXPECT_VALIDATION_FAIL &&
            spec.expectResult != EXPECT_BUILD_SUCCESSFUL)
        {
            print("ERROR: Full GLSL ES 1.00 support doesn't make sense when expecting compile/link failure!\n");
            return false;
        }
    }

    if (!de::inBounds(spec.caseType, (CaseType)0, CASETYPE_LAST))
    {
        print("ERROR: Invalid case type!\n");
        return false;
    }

    if (!de::inBounds(spec.expectResult, (ExpectResult)0, EXPECT_LAST))
    {
        print("ERROR: Invalid expected result!\n");
        return false;
    }

    if (!isValid(spec.values))
        return false;

    if (!spec.values.inputs.empty() && !spec.values.outputs.empty() &&
        spec.values.inputs[0].elements.size() / spec.values.inputs[0].type.getScalarSize() !=
            spec.values.outputs[0].elements.size() / spec.values.outputs[0].type.getScalarSize())
    {
        print("ERROR: Number of input and output elements don't match!\n");
        return false;
    }

    if (isSeparable)
    {
        uint32_t usedStageMask = 0u;

        if (spec.caseType != CASETYPE_COMPLETE)
        {
            print("ERROR: Separable shaders supported only for complete cases!\n");
            return false;
        }

        for (size_t progNdx = 0; progNdx < spec.programs.size(); ++progNdx)
        {
            for (int shaderStageNdx = 0; shaderStageNdx < SHADERTYPE_LAST; ++shaderStageNdx)
            {
                const uint32_t curStageMask = (1u << shaderStageNdx);

                if (supportedStageMask & curStageMask)
                {
                    const bool hasShader = !spec.programs[progNdx].sources.sources[shaderStageNdx].empty();
                    const bool isEnabled = (spec.programs[progNdx].activeStages & curStageMask) != 0;

                    if (hasShader != isEnabled)
                    {
                        print("ERROR: Inconsistent source/enable for shader stage %s!\n",
                              getShaderTypeName((ShaderType)shaderStageNdx));
                        return false;
                    }

                    if (hasShader && (usedStageMask & curStageMask) != 0)
                    {
                        print("ERROR: Stage %s enabled on multiple programs!\n",
                              getShaderTypeName((ShaderType)shaderStageNdx));
                        return false;
                    }

                    if (isEnabled)
                        usedStageMask |= curStageMask;
                }
                else if (!spec.programs[progNdx].sources.sources[shaderStageNdx].empty())
                {
                    print("ERROR: Source specified for unsupported shader stage %s!\n",
                          getShaderTypeName((ShaderType)shaderStageNdx));
                    return false;
                }
            }
        }

        if ((usedStageMask & vtxFragMask) != vtxFragMask)
        {
            print("ERROR: Vertex and fragment shaders are mandatory!\n");
            return false;
        }

        if ((usedStageMask & tessCtrlEvalMask) != 0 && (usedStageMask & tessCtrlEvalMask) != tessCtrlEvalMask)
        {
            print("ERROR: Both tessellation control and eval shaders must be either enabled or disabled!\n");
            return false;
        }
    }
    else
    {
        const bool hasVertex   = !spec.programs[0].sources.sources[SHADERTYPE_VERTEX].empty();
        const bool hasFragment = !spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT].empty();

        if (spec.programs.size() != 1)
        {
            print("ERROR: Only cases using separable programs can have multiple programs!\n");
            return false;
        }

        if (spec.caseType == CASETYPE_VERTEX_ONLY && (!hasVertex || hasFragment))
        {
            print("ERROR: Vertex-only case must have only vertex shader!\n");
            return false;
        }

        if (spec.caseType == CASETYPE_FRAGMENT_ONLY && (hasVertex || !hasFragment))
        {
            print("ERROR: Fragment-only case must have only fragment shader!\n");
            return false;
        }

        if (spec.caseType == CASETYPE_COMPLETE && (!hasVertex || !hasFragment))
        {
            print("ERROR: Complete case must have at least vertex and fragment shaders\n");
            return false;
        }
    }

    return true;
}

bool isCapabilityRequired(CapabilityFlag capabilityFlag, const ShaderCaseSpecification &spec)
{
    std::vector<RequiredCapability>::const_iterator currRequirement = spec.requiredCaps.begin();
    while (currRequirement != spec.requiredCaps.end())
    {
        if ((currRequirement->type == CAPABILITY_FLAG) && (currRequirement->flagName == capabilityFlag))
            return true;
        ++currRequirement;
    }

    return false;
}

// Parser

static const glu::GLSLVersion DEFAULT_GLSL_VERSION = glu::GLSL_VERSION_100_ES;

DE_INLINE bool isWhitespace(char c)
{
    return (c == ' ') || (c == '\t') || (c == '\r') || (c == '\n');
}

DE_INLINE bool isEOL(char c)
{
    return (c == '\r') || (c == '\n');
}

DE_INLINE bool isNumeric(char c)
{
    return deInRange32(c, '0', '9');
}

DE_INLINE bool isAlpha(char c)
{
    return deInRange32(c, 'a', 'z') || deInRange32(c, 'A', 'Z');
}

DE_INLINE bool isCaseNameChar(char c)
{
    return deInRange32(c, 'a', 'z') || deInRange32(c, 'A', 'Z') || deInRange32(c, '0', '9') || (c == '_') ||
           (c == '-') || (c == '.');
}

class ShaderParser
{
public:
    ShaderParser(const tcu::Archive &archive, const std::string &filename, ShaderCaseFactory *caseFactory);
    ~ShaderParser(void);

    vector<tcu::TestNode *> parse(void);

private:
    enum Token
    {
        TOKEN_INVALID = 0,
        TOKEN_EOF,
        TOKEN_STRING,
        TOKEN_SHADER_SOURCE,

        TOKEN_INT_LITERAL,
        TOKEN_FLOAT_LITERAL,

        // identifiers
        TOKEN_IDENTIFIER,
        TOKEN_TRUE,
        TOKEN_FALSE,
        TOKEN_DESC,
        TOKEN_EXPECT,
        TOKEN_GROUP,
        TOKEN_CASE,
        TOKEN_END,
        TOKEN_OUTPUT_COLOR,
        TOKEN_FORMAT,
        TOKEN_VALUES,
        TOKEN_BOTH,
        TOKEN_VERTEX,
        TOKEN_FRAGMENT,
        TOKEN_UNIFORM,
        TOKEN_INPUT,
        TOKEN_OUTPUT,
        TOKEN_FLOAT,
        TOKEN_FLOAT_VEC2,
        TOKEN_FLOAT_VEC3,
        TOKEN_FLOAT_VEC4,
        TOKEN_FLOAT_MAT2,
        TOKEN_FLOAT_MAT2X3,
        TOKEN_FLOAT_MAT2X4,
        TOKEN_FLOAT_MAT3X2,
        TOKEN_FLOAT_MAT3,
        TOKEN_FLOAT_MAT3X4,
        TOKEN_FLOAT_MAT4X2,
        TOKEN_FLOAT_MAT4X3,
        TOKEN_FLOAT_MAT4,
        TOKEN_INT,
        TOKEN_INT_VEC2,
        TOKEN_INT_VEC3,
        TOKEN_INT_VEC4,
        TOKEN_UINT,
        TOKEN_UINT_VEC2,
        TOKEN_UINT_VEC3,
        TOKEN_UINT_VEC4,
        TOKEN_BOOL,
        TOKEN_BOOL_VEC2,
        TOKEN_BOOL_VEC3,
        TOKEN_BOOL_VEC4,
        TOKEN_VERSION,
        TOKEN_TESSELLATION_CONTROL,
        TOKEN_TESSELLATION_EVALUATION,
        TOKEN_GEOMETRY,
        TOKEN_REQUIRE,
        TOKEN_IN,
        TOKEN_IMPORT,
        TOKEN_PIPELINE_PROGRAM,
        TOKEN_ACTIVE_STAGES,

        // symbols
        TOKEN_ASSIGN,
        TOKEN_PLUS,
        TOKEN_MINUS,
        TOKEN_COMMA,
        TOKEN_VERTICAL_BAR,
        TOKEN_SEMI_COLON,
        TOKEN_LEFT_PAREN,
        TOKEN_RIGHT_PAREN,
        TOKEN_LEFT_BRACKET,
        TOKEN_RIGHT_BRACKET,
        TOKEN_LEFT_BRACE,
        TOKEN_RIGHT_BRACE,
        TOKEN_GREATER,

        TOKEN_LAST
    };

    void parseError(const std::string &errorStr);
    float parseFloatLiteral(const char *str);
    int parseIntLiteral(const char *str);
    string parseStringLiteral(const char *str);
    string parseShaderSource(const char *str);
    void advanceToken(void);
    void advanceToken(Token assumed);
    void assumeToken(Token token);
    DataType mapDataTypeToken(Token token);
    const char *getTokenName(Token token);
    uint32_t getShaderStageLiteralFlag(void);
    uint32_t getGLEnumFromName(const std::string &enumName);

    void parseValueElement(DataType dataType, Value &result);
    void parseValue(ValueBlock &valueBlock);
    void parseValueBlock(ValueBlock &valueBlock);
    uint32_t parseShaderStageList(void);
    void parseRequirement(vector<RequiredCapability> &requiredCaps, vector<RequiredExtension> &requiredExts);
    void parseExpectResult(ExpectResult &expectResult);
    void parseFormat(DataType &format);
    void parseGLSLVersion(glu::GLSLVersion &version);
    void parsePipelineProgram(ProgramSpecification &program);
    void parseShaderCase(vector<tcu::TestNode *> &shaderNodeList);
    void parseShaderGroup(vector<tcu::TestNode *> &shaderNodeList);
    void parseImport(vector<tcu::TestNode *> &shaderNodeList);

    const tcu::Archive &m_archive;
    const string m_filename;
    ShaderCaseFactory *const m_caseFactory;

    UniquePtr<tcu::Resource> m_resource;
    vector<char> m_input;

    const char *m_curPtr;
    Token m_curToken;
    std::string m_curTokenStr;
};

ShaderParser::ShaderParser(const tcu::Archive &archive, const string &filename, ShaderCaseFactory *caseFactroy)
    : m_archive(archive)
    , m_filename(filename)
    , m_caseFactory(caseFactroy)
    , m_resource(archive.getResource(m_filename.c_str()))
    , m_curPtr(DE_NULL)
    , m_curToken(TOKEN_LAST)
{
}

ShaderParser::~ShaderParser(void)
{
}

void ShaderParser::parseError(const std::string &errorStr)
{
    string atStr = string(m_curPtr, 80);
    throw tcu::InternalError((string("Parser error: ") + errorStr + " near '" + atStr + " ...'").c_str(), DE_NULL,
                             __FILE__, __LINE__);
}

float ShaderParser::parseFloatLiteral(const char *str)
{
    return (float)atof(str);
}

int ShaderParser::parseIntLiteral(const char *str)
{
    return atoi(str);
}

string ShaderParser::parseStringLiteral(const char *str)
{
    const char *p = str;
    char endChar  = *p++;
    ostringstream o;

    while (*p != endChar && *p)
    {
        if (*p == '\\')
        {
            switch (p[1])
            {
            case 0:
                DE_ASSERT(false);
                break;
            case 'n':
                o << '\n';
                break;
            case 't':
                o << '\t';
                break;
            default:
                o << p[1];
                break;
            }

            p += 2;
        }
        else
            o << *p++;
    }

    return o.str();
}

static string removeExtraIndentation(const string &source)
{
    // Detect indentation from first line.
    int numIndentChars = 0;
    for (int ndx = 0; ndx < (int)source.length() && isWhitespace(source[ndx]); ndx++)
        numIndentChars += source[ndx] == '\t' ? 4 : 1;

    // Process all lines and remove preceding indentation.
    ostringstream processed;
    {
        bool atLineStart       = true;
        int indentCharsOmitted = 0;

        for (int pos = 0; pos < (int)source.length(); pos++)
        {
            char c = source[pos];

            if (atLineStart && indentCharsOmitted < numIndentChars && (c == ' ' || c == '\t'))
            {
                indentCharsOmitted += c == '\t' ? 4 : 1;
            }
            else if (isEOL(c))
            {
                if (source[pos] == '\r' && source[pos + 1] == '\n')
                {
                    pos += 1;
                    processed << '\n';
                }
                else
                    processed << c;

                atLineStart        = true;
                indentCharsOmitted = 0;
            }
            else
            {
                processed << c;
                atLineStart = false;
            }
        }
    }

    return processed.str();
}

string ShaderParser::parseShaderSource(const char *str)
{
    const char *p = str + 2;
    ostringstream o;

    // Eat first empty line from beginning.
    while (*p == ' ')
        p++;
    if (*p == '\r')
        p++;
    if (*p == '\n')
        p++;

    while ((p[0] != '"') || (p[1] != '"'))
    {
        if (*p == '\\')
        {
            switch (p[1])
            {
            case 0:
                DE_ASSERT(false);
                break;
            case 'n':
                o << '\n';
                break;
            case 't':
                o << '\t';
                break;
            default:
                o << p[1];
                break;
            }

            p += 2;
        }
        else
            o << *p++;
    }

    return removeExtraIndentation(o.str());
}

void ShaderParser::advanceToken(void)
{
    // Skip old token.
    m_curPtr += m_curTokenStr.length();

    // Reset token (for safety).
    m_curToken    = TOKEN_INVALID;
    m_curTokenStr = "";

    // Eat whitespace & comments while they last.
    for (;;)
    {
        while (isWhitespace(*m_curPtr))
            m_curPtr++;

        // Check for EOL comment.
        if (*m_curPtr == '#')
        {
            while (*m_curPtr && !isEOL(*m_curPtr))
                m_curPtr++;
        }
        else
            break;
    }

    if (!*m_curPtr)
    {
        m_curToken    = TOKEN_EOF;
        m_curTokenStr = "<EOF>";
    }
    else if (isAlpha(*m_curPtr))
    {
        struct Named
        {
            const char *str;
            Token token;
        };

        static const Named s_named[] = {
            {"true", TOKEN_TRUE},
            {"false", TOKEN_FALSE},
            {"desc", TOKEN_DESC},
            {"expect", TOKEN_EXPECT},
            {"group", TOKEN_GROUP},
            {"case", TOKEN_CASE},
            {"end", TOKEN_END},
            {"output_color", TOKEN_OUTPUT_COLOR},
            {"format", TOKEN_FORMAT},
            {"values", TOKEN_VALUES},
            {"both", TOKEN_BOTH},
            {"vertex", TOKEN_VERTEX},
            {"fragment", TOKEN_FRAGMENT},
            {"uniform", TOKEN_UNIFORM},
            {"input", TOKEN_INPUT},
            {"output", TOKEN_OUTPUT},
            {"float", TOKEN_FLOAT},
            {"vec2", TOKEN_FLOAT_VEC2},
            {"vec3", TOKEN_FLOAT_VEC3},
            {"vec4", TOKEN_FLOAT_VEC4},
            {"mat2", TOKEN_FLOAT_MAT2},
            {"mat2x3", TOKEN_FLOAT_MAT2X3},
            {"mat2x4", TOKEN_FLOAT_MAT2X4},
            {"mat3x2", TOKEN_FLOAT_MAT3X2},
            {"mat3", TOKEN_FLOAT_MAT3},
            {"mat3x4", TOKEN_FLOAT_MAT3X4},
            {"mat4x2", TOKEN_FLOAT_MAT4X2},
            {"mat4x3", TOKEN_FLOAT_MAT4X3},
            {"mat4", TOKEN_FLOAT_MAT4},
            {"int", TOKEN_INT},
            {"ivec2", TOKEN_INT_VEC2},
            {"ivec3", TOKEN_INT_VEC3},
            {"ivec4", TOKEN_INT_VEC4},
            {"uint", TOKEN_UINT},
            {"uvec2", TOKEN_UINT_VEC2},
            {"uvec3", TOKEN_UINT_VEC3},
            {"uvec4", TOKEN_UINT_VEC4},
            {"bool", TOKEN_BOOL},
            {"bvec2", TOKEN_BOOL_VEC2},
            {"bvec3", TOKEN_BOOL_VEC3},
            {"bvec4", TOKEN_BOOL_VEC4},
            {"version", TOKEN_VERSION},
            {"tessellation_control", TOKEN_TESSELLATION_CONTROL},
            {"tessellation_evaluation", TOKEN_TESSELLATION_EVALUATION},
            {"geometry", TOKEN_GEOMETRY},
            {"require", TOKEN_REQUIRE},
            {"in", TOKEN_IN},
            {"import", TOKEN_IMPORT},
            {"pipeline_program", TOKEN_PIPELINE_PROGRAM},
            {"active_stages", TOKEN_ACTIVE_STAGES},
        };

        const char *end = m_curPtr + 1;
        while (isCaseNameChar(*end))
            end++;
        m_curTokenStr = string(m_curPtr, end - m_curPtr);

        m_curToken = TOKEN_IDENTIFIER;

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_named); ndx++)
        {
            if (m_curTokenStr == s_named[ndx].str)
            {
                m_curToken = s_named[ndx].token;
                break;
            }
        }
    }
    else if (isNumeric(*m_curPtr))
    {
        /* \todo [2010-03-31 petri] Hex? */
        const char *p = m_curPtr;
        while (isNumeric(*p))
            p++;
        if (*p == '.')
        {
            p++;
            while (isNumeric(*p))
                p++;

            if (*p == 'e' || *p == 'E')
            {
                p++;
                if (*p == '+' || *p == '-')
                    p++;
                DE_ASSERT(isNumeric(*p));
                while (isNumeric(*p))
                    p++;
            }

            m_curToken    = TOKEN_FLOAT_LITERAL;
            m_curTokenStr = string(m_curPtr, p - m_curPtr);
        }
        else
        {
            m_curToken    = TOKEN_INT_LITERAL;
            m_curTokenStr = string(m_curPtr, p - m_curPtr);
        }
    }
    else if (*m_curPtr == '"' && m_curPtr[1] == '"')
    {
        const char *p = m_curPtr + 2;

        while ((p[0] != '"') || (p[1] != '"'))
        {
            DE_ASSERT(*p);
            if (*p == '\\')
            {
                DE_ASSERT(p[1] != 0);
                p += 2;
            }
            else
                p++;
        }
        p += 2;

        m_curToken    = TOKEN_SHADER_SOURCE;
        m_curTokenStr = string(m_curPtr, (int)(p - m_curPtr));
    }
    else if (*m_curPtr == '"' || *m_curPtr == '\'')
    {
        char endChar  = *m_curPtr;
        const char *p = m_curPtr + 1;

        while (*p != endChar)
        {
            DE_ASSERT(*p);
            if (*p == '\\')
            {
                DE_ASSERT(p[1] != 0);
                p += 2;
            }
            else
                p++;
        }
        p++;

        m_curToken    = TOKEN_STRING;
        m_curTokenStr = string(m_curPtr, (int)(p - m_curPtr));
    }
    else
    {
        struct SimpleToken
        {
            const char *str;
            Token token;
        };

        static const SimpleToken s_simple[] = {
            {"=", TOKEN_ASSIGN},       {"+", TOKEN_PLUS},          {"-", TOKEN_MINUS},      {",", TOKEN_COMMA},
            {"|", TOKEN_VERTICAL_BAR}, {";", TOKEN_SEMI_COLON},    {"(", TOKEN_LEFT_PAREN}, {")", TOKEN_RIGHT_PAREN},
            {"[", TOKEN_LEFT_BRACKET}, {"]", TOKEN_RIGHT_BRACKET}, {"{", TOKEN_LEFT_BRACE}, {"}", TOKEN_RIGHT_BRACE},
            {">", TOKEN_GREATER},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_simple); ndx++)
        {
            if (strncmp(s_simple[ndx].str, m_curPtr, strlen(s_simple[ndx].str)) == 0)
            {
                m_curToken    = s_simple[ndx].token;
                m_curTokenStr = s_simple[ndx].str;
                return;
            }
        }

        // Otherwise invalid token.
        m_curToken    = TOKEN_INVALID;
        m_curTokenStr = *m_curPtr;
    }
}

void ShaderParser::advanceToken(Token assumed)
{
    assumeToken(assumed);
    advanceToken();
}

void ShaderParser::assumeToken(Token token)
{
    if (m_curToken != token)
        parseError(
            (string("unexpected token '") + m_curTokenStr + "', expecting '" + getTokenName(token) + "'").c_str());
    DE_TEST_ASSERT(m_curToken == token);
}

DataType ShaderParser::mapDataTypeToken(Token token)
{
    switch (token)
    {
    case TOKEN_FLOAT:
        return TYPE_FLOAT;
    case TOKEN_FLOAT_VEC2:
        return TYPE_FLOAT_VEC2;
    case TOKEN_FLOAT_VEC3:
        return TYPE_FLOAT_VEC3;
    case TOKEN_FLOAT_VEC4:
        return TYPE_FLOAT_VEC4;
    case TOKEN_FLOAT_MAT2:
        return TYPE_FLOAT_MAT2;
    case TOKEN_FLOAT_MAT2X3:
        return TYPE_FLOAT_MAT2X3;
    case TOKEN_FLOAT_MAT2X4:
        return TYPE_FLOAT_MAT2X4;
    case TOKEN_FLOAT_MAT3X2:
        return TYPE_FLOAT_MAT3X2;
    case TOKEN_FLOAT_MAT3:
        return TYPE_FLOAT_MAT3;
    case TOKEN_FLOAT_MAT3X4:
        return TYPE_FLOAT_MAT3X4;
    case TOKEN_FLOAT_MAT4X2:
        return TYPE_FLOAT_MAT4X2;
    case TOKEN_FLOAT_MAT4X3:
        return TYPE_FLOAT_MAT4X3;
    case TOKEN_FLOAT_MAT4:
        return TYPE_FLOAT_MAT4;
    case TOKEN_INT:
        return TYPE_INT;
    case TOKEN_INT_VEC2:
        return TYPE_INT_VEC2;
    case TOKEN_INT_VEC3:
        return TYPE_INT_VEC3;
    case TOKEN_INT_VEC4:
        return TYPE_INT_VEC4;
    case TOKEN_UINT:
        return TYPE_UINT;
    case TOKEN_UINT_VEC2:
        return TYPE_UINT_VEC2;
    case TOKEN_UINT_VEC3:
        return TYPE_UINT_VEC3;
    case TOKEN_UINT_VEC4:
        return TYPE_UINT_VEC4;
    case TOKEN_BOOL:
        return TYPE_BOOL;
    case TOKEN_BOOL_VEC2:
        return TYPE_BOOL_VEC2;
    case TOKEN_BOOL_VEC3:
        return TYPE_BOOL_VEC3;
    case TOKEN_BOOL_VEC4:
        return TYPE_BOOL_VEC4;
    default:
        return TYPE_INVALID;
    }
}

const char *ShaderParser::getTokenName(Token token)
{
    switch (token)
    {
    case TOKEN_INVALID:
        return "<invalid>";
    case TOKEN_EOF:
        return "<eof>";
    case TOKEN_STRING:
        return "<string>";
    case TOKEN_SHADER_SOURCE:
        return "source";

    case TOKEN_INT_LITERAL:
        return "<int>";
    case TOKEN_FLOAT_LITERAL:
        return "<float>";

    // identifiers
    case TOKEN_IDENTIFIER:
        return "<identifier>";
    case TOKEN_TRUE:
        return "true";
    case TOKEN_FALSE:
        return "false";
    case TOKEN_DESC:
        return "desc";
    case TOKEN_EXPECT:
        return "expect";
    case TOKEN_GROUP:
        return "group";
    case TOKEN_CASE:
        return "case";
    case TOKEN_END:
        return "end";
    case TOKEN_VALUES:
        return "values";
    case TOKEN_BOTH:
        return "both";
    case TOKEN_VERTEX:
        return "vertex";
    case TOKEN_FRAGMENT:
        return "fragment";
    case TOKEN_TESSELLATION_CONTROL:
        return "tessellation_control";
    case TOKEN_TESSELLATION_EVALUATION:
        return "tessellation_evaluation";
    case TOKEN_GEOMETRY:
        return "geometry";
    case TOKEN_REQUIRE:
        return "require";
    case TOKEN_UNIFORM:
        return "uniform";
    case TOKEN_INPUT:
        return "input";
    case TOKEN_OUTPUT:
        return "output";
    case TOKEN_FLOAT:
        return "float";
    case TOKEN_FLOAT_VEC2:
        return "vec2";
    case TOKEN_FLOAT_VEC3:
        return "vec3";
    case TOKEN_FLOAT_VEC4:
        return "vec4";
    case TOKEN_FLOAT_MAT2:
        return "mat2";
    case TOKEN_FLOAT_MAT2X3:
        return "mat2x3";
    case TOKEN_FLOAT_MAT2X4:
        return "mat2x4";
    case TOKEN_FLOAT_MAT3X2:
        return "mat3x2";
    case TOKEN_FLOAT_MAT3:
        return "mat3";
    case TOKEN_FLOAT_MAT3X4:
        return "mat3x4";
    case TOKEN_FLOAT_MAT4X2:
        return "mat4x2";
    case TOKEN_FLOAT_MAT4X3:
        return "mat4x3";
    case TOKEN_FLOAT_MAT4:
        return "mat4";
    case TOKEN_INT:
        return "int";
    case TOKEN_INT_VEC2:
        return "ivec2";
    case TOKEN_INT_VEC3:
        return "ivec3";
    case TOKEN_INT_VEC4:
        return "ivec4";
    case TOKEN_UINT:
        return "uint";
    case TOKEN_UINT_VEC2:
        return "uvec2";
    case TOKEN_UINT_VEC3:
        return "uvec3";
    case TOKEN_UINT_VEC4:
        return "uvec4";
    case TOKEN_BOOL:
        return "bool";
    case TOKEN_BOOL_VEC2:
        return "bvec2";
    case TOKEN_BOOL_VEC3:
        return "bvec3";
    case TOKEN_BOOL_VEC4:
        return "bvec4";
    case TOKEN_IN:
        return "in";
    case TOKEN_IMPORT:
        return "import";
    case TOKEN_PIPELINE_PROGRAM:
        return "pipeline_program";
    case TOKEN_ACTIVE_STAGES:
        return "active_stages";

    case TOKEN_ASSIGN:
        return "=";
    case TOKEN_PLUS:
        return "+";
    case TOKEN_MINUS:
        return "-";
    case TOKEN_COMMA:
        return ",";
    case TOKEN_VERTICAL_BAR:
        return "|";
    case TOKEN_SEMI_COLON:
        return ";";
    case TOKEN_LEFT_PAREN:
        return "(";
    case TOKEN_RIGHT_PAREN:
        return ")";
    case TOKEN_LEFT_BRACKET:
        return "[";
    case TOKEN_RIGHT_BRACKET:
        return "]";
    case TOKEN_LEFT_BRACE:
        return "{";
    case TOKEN_RIGHT_BRACE:
        return "}";
    case TOKEN_GREATER:
        return ">";

    default:
        return "<unknown>";
    }
}

uint32_t ShaderParser::getShaderStageLiteralFlag(void)
{
    switch (m_curToken)
    {
    case TOKEN_VERTEX:
        return (1 << glu::SHADERTYPE_VERTEX);
    case TOKEN_FRAGMENT:
        return (1 << glu::SHADERTYPE_FRAGMENT);
    case TOKEN_GEOMETRY:
        return (1 << glu::SHADERTYPE_GEOMETRY);
    case TOKEN_TESSELLATION_CONTROL:
        return (1 << glu::SHADERTYPE_TESSELLATION_CONTROL);
    case TOKEN_TESSELLATION_EVALUATION:
        return (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION);

    default:
        parseError(std::string() + "invalid shader stage name, got " + m_curTokenStr);
        return 0;
    }
}

uint32_t ShaderParser::getGLEnumFromName(const std::string &enumName)
{
    static const struct
    {
        const char *name;
        uint32_t value;
    } names[] = {
        {"GL_MAX_VERTEX_IMAGE_UNIFORMS", GL_MAX_VERTEX_IMAGE_UNIFORMS},
        {"GL_MAX_VERTEX_ATOMIC_COUNTERS", GL_MAX_VERTEX_ATOMIC_COUNTERS},
        {"GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS", GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS},
        {"GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS", GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(names); ++ndx)
        if (names[ndx].name == enumName)
            return names[ndx].value;

    parseError(std::string() + "unknown enum name, got " + enumName);
    return 0;
}

void ShaderParser::parseValueElement(DataType expectedDataType, Value &result)
{
    DataType scalarType = getDataTypeScalarType(expectedDataType);
    int scalarSize      = getDataTypeScalarSize(expectedDataType);

    /* \todo [2010-04-19 petri] Support arrays. */
    Value::Element elems[16];

    if (scalarSize > 1)
    {
        DE_ASSERT(mapDataTypeToken(m_curToken) == expectedDataType);
        advanceToken(); // data type (float, vec2, etc.)
        advanceToken(TOKEN_LEFT_PAREN);
    }

    for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
    {
        if (scalarType == TYPE_FLOAT)
        {
            float signMult = 1.0f;
            if (m_curToken == TOKEN_MINUS)
            {
                signMult = -1.0f;
                advanceToken();
            }

            assumeToken(TOKEN_FLOAT_LITERAL);
            elems[scalarNdx].float32 = signMult * parseFloatLiteral(m_curTokenStr.c_str());
            advanceToken(TOKEN_FLOAT_LITERAL);
        }
        else if (scalarType == TYPE_INT || scalarType == TYPE_UINT)
        {
            int signMult = 1;
            if (m_curToken == TOKEN_MINUS)
            {
                signMult = -1;
                advanceToken();
            }

            assumeToken(TOKEN_INT_LITERAL);
            elems[scalarNdx].int32 = signMult * parseIntLiteral(m_curTokenStr.c_str());
            advanceToken(TOKEN_INT_LITERAL);
        }
        else
        {
            DE_ASSERT(scalarType == TYPE_BOOL);
            elems[scalarNdx].bool32 = (m_curToken == TOKEN_TRUE);
            if (m_curToken != TOKEN_TRUE && m_curToken != TOKEN_FALSE)
                parseError(string("unexpected token, expecting bool: " + m_curTokenStr));
            advanceToken(); // true/false
        }

        if (scalarNdx != (scalarSize - 1))
            advanceToken(TOKEN_COMMA);
    }

    if (scalarSize > 1)
        advanceToken(TOKEN_RIGHT_PAREN);

    // Store results.
    for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
        result.elements.push_back(elems[scalarNdx]);
}

void ShaderParser::parseValue(ValueBlock &valueBlock)
{
    PARSE_DBG(("      parseValue()\n"));

    // Parsed results.
    vector<Value> *dstBlock = DE_NULL;
    DataType basicType      = TYPE_LAST;
    std::string valueName;

    // Parse storage.
    if (m_curToken == TOKEN_UNIFORM)
        dstBlock = &valueBlock.uniforms;
    else if (m_curToken == TOKEN_INPUT)
        dstBlock = &valueBlock.inputs;
    else if (m_curToken == TOKEN_OUTPUT)
        dstBlock = &valueBlock.outputs;
    else
        parseError(string("unexpected token encountered when parsing value classifier"));
    advanceToken();

    // Parse data type.
    basicType = mapDataTypeToken(m_curToken);
    if (basicType == TYPE_INVALID)
        parseError(string("unexpected token when parsing value data type: " + m_curTokenStr));
    advanceToken();

    // Parse value name.
    if (m_curToken == TOKEN_IDENTIFIER || m_curToken == TOKEN_STRING)
    {
        if (m_curToken == TOKEN_IDENTIFIER)
            valueName = m_curTokenStr;
        else
            valueName = parseStringLiteral(m_curTokenStr.c_str());
    }
    else
        parseError(string("unexpected token when parsing value name: " + m_curTokenStr));
    advanceToken();

    // Parse assignment operator.
    advanceToken(TOKEN_ASSIGN);

    {
        Value value;
        value.name = valueName;
        value.type = VarType(basicType, PRECISION_LAST);
        dstBlock->push_back(value);
    }

    // Parse actual value.
    if (m_curToken == TOKEN_LEFT_BRACKET) // value list
    {
        advanceToken(TOKEN_LEFT_BRACKET);

        for (;;)
        {
            parseValueElement(basicType, dstBlock->back());

            if (m_curToken == TOKEN_RIGHT_BRACKET)
                break;
            else if (m_curToken == TOKEN_VERTICAL_BAR)
            {
                advanceToken();
                continue;
            }
            else
                parseError(string("unexpected token in value element array: " + m_curTokenStr));
        }

        advanceToken(TOKEN_RIGHT_BRACKET);
    }
    else //  single elements
    {
        parseValueElement(basicType, dstBlock->back());
    }

    advanceToken(TOKEN_SEMI_COLON); // end of declaration
}

void ShaderParser::parseValueBlock(ValueBlock &valueBlock)
{
    PARSE_DBG(("    parseValueBlock()\n"));
    advanceToken(TOKEN_VALUES);
    advanceToken(TOKEN_LEFT_BRACE);

    for (;;)
    {
        if (m_curToken == TOKEN_UNIFORM || m_curToken == TOKEN_INPUT || m_curToken == TOKEN_OUTPUT)
            parseValue(valueBlock);
        else if (m_curToken == TOKEN_RIGHT_BRACE)
            break;
        else
            parseError(string("unexpected token when parsing a value block: " + m_curTokenStr));
    }

    advanceToken(TOKEN_RIGHT_BRACE);
}

uint32_t ShaderParser::parseShaderStageList(void)
{
    uint32_t mask = 0;

    assumeToken(TOKEN_LEFT_BRACE);

    // don't allow 0-sized lists
    advanceToken();
    mask |= getShaderStageLiteralFlag();
    advanceToken();

    for (;;)
    {
        if (m_curToken == TOKEN_RIGHT_BRACE)
            break;
        else if (m_curToken == TOKEN_COMMA)
        {
            uint32_t stageFlag;
            advanceToken();

            stageFlag = getShaderStageLiteralFlag();
            if (stageFlag & mask)
                parseError(string("stage already set in the shader stage set: " + m_curTokenStr));

            mask |= stageFlag;
            advanceToken();
        }
        else
            parseError(string("invalid shader stage set token: " + m_curTokenStr));
    }
    advanceToken(TOKEN_RIGHT_BRACE);

    return mask;
}

void ShaderParser::parseRequirement(vector<RequiredCapability> &requiredCaps, vector<RequiredExtension> &requiredExts)
{
    PARSE_DBG(("    parseRequirement()\n"));

    advanceToken();
    assumeToken(TOKEN_IDENTIFIER);

    if (m_curTokenStr == "extension")
    {
        std::vector<std::string> anyExtensionStringList;
        uint32_t affectedCasesFlags = -1; // by default all stages

        advanceToken();
        assumeToken(TOKEN_LEFT_BRACE);

        advanceToken();
        assumeToken(TOKEN_STRING);

        anyExtensionStringList.push_back(parseStringLiteral(m_curTokenStr.c_str()));
        advanceToken();

        for (;;)
        {
            if (m_curToken == TOKEN_RIGHT_BRACE)
                break;
            else if (m_curToken == TOKEN_VERTICAL_BAR)
            {
                advanceToken();
                assumeToken(TOKEN_STRING);

                anyExtensionStringList.push_back(parseStringLiteral(m_curTokenStr.c_str()));
                advanceToken();
            }
            else
                parseError(string("invalid extension list token: " + m_curTokenStr));
        }
        advanceToken(TOKEN_RIGHT_BRACE);

        if (m_curToken == TOKEN_IN)
        {
            advanceToken();
            affectedCasesFlags = parseShaderStageList();
        }

        requiredExts.push_back(RequiredExtension(anyExtensionStringList, affectedCasesFlags));
    }
    else if (m_curTokenStr == "limit")
    {
        uint32_t limitEnum;
        int limitValue;

        advanceToken();

        assumeToken(TOKEN_STRING);
        limitEnum = getGLEnumFromName(parseStringLiteral(m_curTokenStr.c_str()));
        advanceToken();

        assumeToken(TOKEN_GREATER);
        advanceToken();

        assumeToken(TOKEN_INT_LITERAL);
        limitValue = parseIntLiteral(m_curTokenStr.c_str());
        advanceToken();

        requiredCaps.push_back(RequiredCapability(limitEnum, limitValue));
    }
    else if (m_curTokenStr == "full_glsl_es_100_support")
    {
        advanceToken();

        requiredCaps.push_back(RequiredCapability(CAPABILITY_FULL_GLSL_ES_100_SUPPORT));
    }
    else if (m_curTokenStr == "only_glsl_es_100_support")
    {
        advanceToken();

        requiredCaps.push_back(RequiredCapability(CAPABILITY_ONLY_GLSL_ES_100_SUPPORT));
    }
    else if (m_curTokenStr == "exactly_one_draw_buffer")
    {
        advanceToken();

        requiredCaps.push_back(RequiredCapability(CAPABILITY_EXACTLY_ONE_DRAW_BUFFER));
    }
    else
        parseError(string("invalid requirement value: " + m_curTokenStr));
}

void ShaderParser::parseExpectResult(ExpectResult &expectResult)
{
    assumeToken(TOKEN_IDENTIFIER);

    if (m_curTokenStr == "pass")
        expectResult = EXPECT_PASS;
    else if (m_curTokenStr == "compile_fail")
        expectResult = EXPECT_COMPILE_FAIL;
    else if (m_curTokenStr == "link_fail")
        expectResult = EXPECT_LINK_FAIL;
    else if (m_curTokenStr == "compile_or_link_fail")
        expectResult = EXPECT_COMPILE_LINK_FAIL;
    else if (m_curTokenStr == "validation_fail")
        expectResult = EXPECT_VALIDATION_FAIL;
    else if (m_curTokenStr == "build_successful")
        expectResult = EXPECT_BUILD_SUCCESSFUL;
    else
        parseError(string("invalid expected result value: " + m_curTokenStr));

    advanceToken();
}

void ShaderParser::parseFormat(DataType &format)
{
    format = mapDataTypeToken(m_curToken);
    advanceToken();
}

void ShaderParser::parseGLSLVersion(glu::GLSLVersion &version)
{
    int versionNum      = 0;
    std::string postfix = "";

    assumeToken(TOKEN_INT_LITERAL);
    versionNum = parseIntLiteral(m_curTokenStr.c_str());
    advanceToken();

    if (m_curToken == TOKEN_IDENTIFIER)
    {
        postfix = m_curTokenStr;
        advanceToken();
    }

    DE_STATIC_ASSERT(glu::GLSL_VERSION_LAST == 15);

    if (versionNum == 100 && postfix == "es")
        version = glu::GLSL_VERSION_100_ES;
    else if (versionNum == 300 && postfix == "es")
        version = glu::GLSL_VERSION_300_ES;
    else if (versionNum == 310 && postfix == "es")
        version = glu::GLSL_VERSION_310_ES;
    else if (versionNum == 320 && postfix == "es")
        version = glu::GLSL_VERSION_320_ES;
    else if (versionNum == 130)
        version = glu::GLSL_VERSION_130;
    else if (versionNum == 140)
        version = glu::GLSL_VERSION_140;
    else if (versionNum == 150)
        version = glu::GLSL_VERSION_150;
    else if (versionNum == 330)
        version = glu::GLSL_VERSION_330;
    else if (versionNum == 400)
        version = glu::GLSL_VERSION_400;
    else if (versionNum == 410)
        version = glu::GLSL_VERSION_410;
    else if (versionNum == 420)
        version = glu::GLSL_VERSION_420;
    else if (versionNum == 430)
        version = glu::GLSL_VERSION_430;
    else if (versionNum == 440)
        version = glu::GLSL_VERSION_440;
    else if (versionNum == 450)
        version = glu::GLSL_VERSION_450;
    else if (versionNum == 460)
        version = glu::GLSL_VERSION_460;
    else
        parseError("Unknown GLSL version");
}

void ShaderParser::parsePipelineProgram(ProgramSpecification &program)
{
    advanceToken(TOKEN_PIPELINE_PROGRAM);

    for (;;)
    {
        if (m_curToken == TOKEN_END)
            break;
        else if (m_curToken == TOKEN_ACTIVE_STAGES)
        {
            advanceToken();
            program.activeStages = parseShaderStageList();
        }
        else if (m_curToken == TOKEN_REQUIRE)
        {
            vector<RequiredCapability> unusedCaps;
            size_t size = program.requiredExtensions.size();
            parseRequirement(unusedCaps, program.requiredExtensions);

            if (size == program.requiredExtensions.size())
                parseError("only extension requirements are allowed inside pipeline program");
        }
        else if (m_curToken == TOKEN_VERTEX || m_curToken == TOKEN_FRAGMENT ||
                 m_curToken == TOKEN_TESSELLATION_CONTROL || m_curToken == TOKEN_TESSELLATION_EVALUATION ||
                 m_curToken == TOKEN_GEOMETRY)
        {
            const Token token = m_curToken;
            string source;

            advanceToken();
            assumeToken(TOKEN_SHADER_SOURCE);
            source = parseShaderSource(m_curTokenStr.c_str());
            advanceToken();

            switch (token)
            {
            case TOKEN_VERTEX:
                program.sources.sources[SHADERTYPE_VERTEX].push_back(source);
                break;
            case TOKEN_FRAGMENT:
                program.sources.sources[SHADERTYPE_FRAGMENT].push_back(source);
                break;
            case TOKEN_TESSELLATION_CONTROL:
                program.sources.sources[SHADERTYPE_TESSELLATION_CONTROL].push_back(source);
                break;
            case TOKEN_TESSELLATION_EVALUATION:
                program.sources.sources[SHADERTYPE_TESSELLATION_EVALUATION].push_back(source);
                break;
            case TOKEN_GEOMETRY:
                program.sources.sources[SHADERTYPE_GEOMETRY].push_back(source);
                break;
            default:
                DE_FATAL("Unreachable");
            }
        }
        else
            parseError(string("invalid pipeline program value: " + m_curTokenStr));
    }
    advanceToken(TOKEN_END);

    if (program.activeStages == 0)
        parseError("program pipeline object must have active stages");
}

void ShaderParser::parseShaderCase(vector<tcu::TestNode *> &shaderNodeList)
{
    // Parse 'case'.
    PARSE_DBG(("  parseShaderCase()\n"));
    advanceToken(TOKEN_CASE);

    // Parse case name.
    string caseName = m_curTokenStr;
    advanceToken(); // \note [pyry] All token types are allowed here.

    // \todo [pyry] Optimize by parsing most stuff directly to ShaderCaseSpecification

    // Setup case.
    GLSLVersion version       = DEFAULT_GLSL_VERSION;
    ExpectResult expectResult = EXPECT_PASS;
    OutputType outputType     = OUTPUT_RESULT;
    DataType format           = TYPE_LAST;
    string description;
    string bothSource;
    vector<string> vertexSources;
    vector<string> fragmentSources;
    vector<string> tessellationCtrlSources;
    vector<string> tessellationEvalSources;
    vector<string> geometrySources;
    ValueBlock valueBlock;
    bool valueBlockSeen = false;
    vector<RequiredCapability> requiredCaps;
    vector<RequiredExtension> requiredExts;
    vector<ProgramSpecification> pipelinePrograms;

    for (;;)
    {
        if (m_curToken == TOKEN_END)
            break;
        else if (m_curToken == TOKEN_DESC)
        {
            advanceToken();
            assumeToken(TOKEN_STRING);
            description = parseStringLiteral(m_curTokenStr.c_str());
            advanceToken();
        }
        else if (m_curToken == TOKEN_EXPECT)
        {
            advanceToken();
            parseExpectResult(expectResult);
        }
        else if (m_curToken == TOKEN_OUTPUT_COLOR)
        {
            outputType = OUTPUT_COLOR;
            advanceToken();
            parseFormat(format);
        }
        else if (m_curToken == TOKEN_VALUES)
        {
            if (valueBlockSeen)
                parseError("multiple value blocks");
            parseValueBlock(valueBlock);
            valueBlockSeen = true;
        }
        else if (m_curToken == TOKEN_BOTH || m_curToken == TOKEN_VERTEX || m_curToken == TOKEN_FRAGMENT ||
                 m_curToken == TOKEN_TESSELLATION_CONTROL || m_curToken == TOKEN_TESSELLATION_EVALUATION ||
                 m_curToken == TOKEN_GEOMETRY)
        {
            const Token token = m_curToken;
            string source;

            advanceToken();
            assumeToken(TOKEN_SHADER_SOURCE);
            source = parseShaderSource(m_curTokenStr.c_str());
            advanceToken();

            switch (token)
            {
            case TOKEN_VERTEX:
                vertexSources.push_back(source);
                break;
            case TOKEN_FRAGMENT:
                fragmentSources.push_back(source);
                break;
            case TOKEN_TESSELLATION_CONTROL:
                tessellationCtrlSources.push_back(source);
                break;
            case TOKEN_TESSELLATION_EVALUATION:
                tessellationEvalSources.push_back(source);
                break;
            case TOKEN_GEOMETRY:
                geometrySources.push_back(source);
                break;
            case TOKEN_BOTH:
            {
                if (!bothSource.empty())
                    parseError("multiple 'both' blocks");
                bothSource = source;
                break;
            }

            default:
                DE_FATAL("Unreachable");
            }
        }
        else if (m_curToken == TOKEN_VERSION)
        {
            advanceToken();
            parseGLSLVersion(version);
        }
        else if (m_curToken == TOKEN_REQUIRE)
        {
            parseRequirement(requiredCaps, requiredExts);
        }
        else if (m_curToken == TOKEN_PIPELINE_PROGRAM)
        {
            ProgramSpecification pipelineProgram;
            parsePipelineProgram(pipelineProgram);
            pipelineProgram.sources.separable = true;
            pipelinePrograms.push_back(pipelineProgram);
        }
        else
            parseError(string("unexpected token while parsing shader case: " + m_curTokenStr));
    }

    advanceToken(TOKEN_END); // case end

    if (!bothSource.empty())
    {
        if (!vertexSources.empty() || !fragmentSources.empty() || !tessellationCtrlSources.empty() ||
            !tessellationEvalSources.empty() || !geometrySources.empty() || !pipelinePrograms.empty())
        {
            parseError("'both' cannot be mixed with other shader stages");
        }

        // vertex
        {
            ShaderCaseSpecification spec;
            spec.caseType      = CASETYPE_VERTEX_ONLY;
            spec.expectResult  = expectResult;
            spec.targetVersion = version;
            spec.requiredCaps  = requiredCaps;
            spec.values        = valueBlock;

            spec.programs.resize(1);
            spec.programs[0].sources << VertexSource(bothSource);
            spec.programs[0].requiredExtensions = requiredExts;

            shaderNodeList.push_back(
                m_caseFactory->createCase(caseName + "_vertex", description, ShaderCaseSpecification(spec)));
        }

        // fragment
        {
            ShaderCaseSpecification spec;
            spec.caseType      = CASETYPE_FRAGMENT_ONLY;
            spec.expectResult  = expectResult;
            spec.targetVersion = version;
            spec.requiredCaps  = requiredCaps;
            spec.values        = valueBlock;

            spec.programs.resize(1);
            spec.programs[0].sources << FragmentSource(bothSource);
            spec.programs[0].requiredExtensions = requiredExts;

            shaderNodeList.push_back(
                m_caseFactory->createCase(caseName + "_fragment", description, ShaderCaseSpecification(spec)));
        }
    }
    else if (pipelinePrograms.empty())
    {
        ShaderCaseSpecification spec;
        spec.caseType      = CASETYPE_COMPLETE;
        spec.expectResult  = expectResult;
        spec.outputType    = outputType;
        spec.outputFormat  = format;
        spec.targetVersion = version;
        spec.requiredCaps  = requiredCaps;
        spec.values        = valueBlock;

        spec.programs.resize(1);
        spec.programs[0].sources.sources[SHADERTYPE_VERTEX].swap(vertexSources);
        spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT].swap(fragmentSources);
        spec.programs[0].sources.sources[SHADERTYPE_TESSELLATION_CONTROL].swap(tessellationCtrlSources);
        spec.programs[0].sources.sources[SHADERTYPE_TESSELLATION_EVALUATION].swap(tessellationEvalSources);
        spec.programs[0].sources.sources[SHADERTYPE_GEOMETRY].swap(geometrySources);
        spec.programs[0].requiredExtensions.swap(requiredExts);

        shaderNodeList.push_back(m_caseFactory->createCase(caseName, description, ShaderCaseSpecification(spec)));
    }
    else
    {
        if (!vertexSources.empty() || !fragmentSources.empty() || !tessellationCtrlSources.empty() ||
            !tessellationEvalSources.empty() || !geometrySources.empty())
        {
            parseError("pipeline programs cannot be mixed with complete programs");
        }

        if (!requiredExts.empty())
            parseError("global extension requirements cannot be mixed with pipeline programs");

        // Pipeline case, multiple programs
        {
            ShaderCaseSpecification spec;
            spec.caseType      = CASETYPE_COMPLETE;
            spec.expectResult  = expectResult;
            spec.targetVersion = version;
            spec.requiredCaps  = requiredCaps;
            spec.values        = valueBlock;

            spec.programs.swap(pipelinePrograms);

            shaderNodeList.push_back(m_caseFactory->createCase(caseName, description, ShaderCaseSpecification(spec)));
        }
    }
}

void ShaderParser::parseShaderGroup(vector<tcu::TestNode *> &shaderNodeList)
{
    // Parse 'case'.
    PARSE_DBG(("  parseShaderGroup()\n"));
    advanceToken(TOKEN_GROUP);

    // Parse case name.
    string name = m_curTokenStr;
    advanceToken(); // \note [pyry] We don't want to check token type here (for instance to allow "uniform") group.

    // Parse description.
    assumeToken(TOKEN_STRING);
    string description = parseStringLiteral(m_curTokenStr.c_str());
    advanceToken(TOKEN_STRING);

    std::vector<tcu::TestNode *> children;

    // Parse group children.
    for (;;)
    {
        if (m_curToken == TOKEN_END)
            break;
        else if (m_curToken == TOKEN_GROUP)
            parseShaderGroup(children);
        else if (m_curToken == TOKEN_CASE)
            parseShaderCase(children);
        else if (m_curToken == TOKEN_IMPORT)
            parseImport(children);
        else
            parseError(string("unexpected token while parsing shader group: " + m_curTokenStr));
    }

    advanceToken(TOKEN_END); // group end

    // Create group node.
    tcu::TestCaseGroup *groupNode = m_caseFactory->createGroup(name, description, children);
    shaderNodeList.push_back(groupNode);
}

void ShaderParser::parseImport(vector<tcu::TestNode *> &shaderNodeList)
{
    std::string importFileName;

    advanceToken(TOKEN_IMPORT);

    assumeToken(TOKEN_STRING);
    importFileName = parseStringLiteral(m_curTokenStr.c_str());
    advanceToken(TOKEN_STRING);

    {
        ShaderParser subParser(m_archive,
                               de::FilePath::join(de::FilePath(m_filename).getDirName(), importFileName).getPath(),
                               m_caseFactory);
        const vector<tcu::TestNode *> importedCases = subParser.parse();

        // \todo [2015-08-03 pyry] Not exception safe
        shaderNodeList.insert(shaderNodeList.end(), importedCases.begin(), importedCases.end());
    }
}

vector<tcu::TestNode *> ShaderParser::parse(void)
{
    const int dataLen = m_resource->getSize();

    m_input.resize(dataLen + 1);
    m_resource->setPosition(0);
    m_resource->read((uint8_t *)&m_input[0], dataLen);
    m_input[dataLen] = '\0';

    // Initialize parser.
    m_curPtr      = &m_input[0];
    m_curToken    = TOKEN_INVALID;
    m_curTokenStr = "";
    advanceToken();

    vector<tcu::TestNode *> nodeList;

    // Parse all cases.
    PARSE_DBG(("parse()\n"));
    for (;;)
    {
        if (m_curToken == TOKEN_CASE)
            parseShaderCase(nodeList);
        else if (m_curToken == TOKEN_GROUP)
            parseShaderGroup(nodeList);
        else if (m_curToken == TOKEN_IMPORT)
            parseImport(nodeList);
        else if (m_curToken == TOKEN_EOF)
            break;
        else
            parseError(string("invalid token encountered at main level: '") + m_curTokenStr + "'");
    }

    assumeToken(TOKEN_EOF);
    // printf("  parsed %d test cases.\n", caseList.size());
    return nodeList;
}

std::vector<tcu::TestNode *> parseFile(const tcu::Archive &archive, const std::string &filename,
                                       ShaderCaseFactory *caseFactory)
{
    sl::ShaderParser parser(archive, filename, caseFactory);

    return parser.parse();
}

// Execution utilities

static void dumpValue(tcu::TestLog &log, const Value &val, const char *storageName, int arrayNdx)
{
    const char *const valueName = val.name.c_str();
    const DataType dataType     = val.type.getBasicType();
    int scalarSize              = getDataTypeScalarSize(dataType);
    ostringstream result;

    result << "    " << storageName << " ";

    result << getDataTypeName(dataType) << " " << valueName << ":";

    if (isDataTypeScalar(dataType))
        result << " ";
    if (isDataTypeVector(dataType))
        result << " [ ";
    else if (isDataTypeMatrix(dataType))
        result << "\n";

    if (isDataTypeScalarOrVector(dataType))
    {
        for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
        {
            int elemNdx             = arrayNdx;
            const Value::Element &e = val.elements[elemNdx * scalarSize + scalarNdx];
            result << ((scalarNdx != 0) ? ", " : "");

            if (isDataTypeFloatOrVec(dataType))
                result << e.float32;
            else if (isDataTypeIntOrIVec(dataType))
                result << e.int32;
            else if (isDataTypeUintOrUVec(dataType))
                result << (uint32_t)e.int32;
            else if (isDataTypeBoolOrBVec(dataType))
                result << (e.bool32 ? "true" : "false");
        }
    }
    else if (isDataTypeMatrix(dataType))
    {
        int numRows = getDataTypeMatrixNumRows(dataType);
        int numCols = getDataTypeMatrixNumColumns(dataType);
        for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
        {
            result << "       [ ";
            for (int colNdx = 0; colNdx < numCols; colNdx++)
            {
                int elemNdx = arrayNdx;
                float v     = val.elements[elemNdx * scalarSize + rowNdx * numCols + colNdx].float32;
                result << ((colNdx == 0) ? "" : ", ") << v;
            }
            result << " ]\n";
        }
    }

    if (isDataTypeScalar(dataType))
        result << "\n";
    else if (isDataTypeVector(dataType))
        result << " ]\n";

    log << TestLog::Message << result.str() << TestLog::EndMessage;
}

static void dumpValues(tcu::TestLog &log, const vector<Value> &values, const char *storageName, int arrayNdx)
{
    for (size_t valNdx = 0; valNdx < values.size(); valNdx++)
        dumpValue(log, values[valNdx], storageName, arrayNdx);
}

void dumpValues(tcu::TestLog &log, const ValueBlock &values, int arrayNdx)
{
    dumpValues(log, values.inputs, "input", arrayNdx);
    dumpValues(log, values.outputs, "expected", arrayNdx);
    dumpValues(log, values.uniforms, "uniform", arrayNdx);
}

static void generateExtensionStatements(std::ostringstream &buf, const std::vector<RequiredExtension> &extensions,
                                        glu::ShaderType type)
{
    for (size_t ndx = 0; ndx < extensions.size(); ++ndx)
    {
        DE_ASSERT(extensions[ndx].effectiveStages != 0u && extensions[ndx].alternatives.size() == 1);

        if ((extensions[ndx].effectiveStages & (1u << (uint32_t)type)) != 0)
            buf << "#extension " << extensions[ndx].alternatives[0] << " : require\n";
    }
}

// Injects #extension XXX : require lines after the last preprocessor directive in the shader code. Does not support line continuations
std::string injectExtensionRequirements(const std::string &baseCode, const std::vector<RequiredExtension> &extensions,
                                        glu::ShaderType shaderType)
{
    std::istringstream baseCodeBuf(baseCode);
    std::ostringstream resultBuf;
    std::string line;
    bool firstNonPreprocessorLine = true;
    std::ostringstream extStr;

    generateExtensionStatements(extStr, extensions, shaderType);

    // skip if no requirements
    if (extStr.str().empty())
        return baseCode;

    while (std::getline(baseCodeBuf, line))
    {
        // begins with '#'?
        const std::string::size_type firstNonWhitespace = line.find_first_not_of("\t ");
        const bool isPreprocessorDirective =
            (firstNonWhitespace != std::string::npos && line.at(firstNonWhitespace) == '#');

        // Inject #extensions
        if (!isPreprocessorDirective && firstNonPreprocessorLine)
        {
            firstNonPreprocessorLine = false;
            resultBuf << extStr.str();
        }

        resultBuf << line << "\n";
    }

    return resultBuf.str();
}

void genCompareFunctions(ostringstream &stream, const ValueBlock &valueBlock, bool useFloatTypes)
{
    bool cmpTypeFound[TYPE_LAST];
    for (int i = 0; i < TYPE_LAST; i++)
        cmpTypeFound[i] = false;

    for (size_t valueNdx = 0; valueNdx < valueBlock.outputs.size(); valueNdx++)
    {
        const Value &val                              = valueBlock.outputs[valueNdx];
        cmpTypeFound[(size_t)val.type.getBasicType()] = true;
    }

    if (useFloatTypes)
    {
        if (cmpTypeFound[TYPE_BOOL])
            stream << "bool isOk (float a, bool b) { return ((a > 0.5) == b); }\n";
        if (cmpTypeFound[TYPE_BOOL_VEC2])
            stream << "bool isOk (vec2 a, bvec2 b) { return (greaterThan(a, vec2(0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_BOOL_VEC3])
            stream << "bool isOk (vec3 a, bvec3 b) { return (greaterThan(a, vec3(0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_BOOL_VEC4])
            stream << "bool isOk (vec4 a, bvec4 b) { return (greaterThan(a, vec4(0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_INT])
            stream << "bool isOk (float a, int b)  { float atemp = a+0.5; return (float(b) <= atemp && atemp <= "
                      "float(b+1)); }\n";
        if (cmpTypeFound[TYPE_INT_VEC2])
            stream << "bool isOk (vec2 a, ivec2 b) { return (ivec2(floor(a + 0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_INT_VEC3])
            stream << "bool isOk (vec3 a, ivec3 b) { return (ivec3(floor(a + 0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_INT_VEC4])
            stream << "bool isOk (vec4 a, ivec4 b) { return (ivec4(floor(a + 0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_UINT])
            stream << "bool isOk (float a, uint b) { float atemp = a+0.5; return (float(b) <= atemp && atemp <= "
                      "float(b+1u)); }\n";
        if (cmpTypeFound[TYPE_UINT_VEC2])
            stream << "bool isOk (vec2 a, uvec2 b) { return (uvec2(floor(a + 0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_UINT_VEC3])
            stream << "bool isOk (vec3 a, uvec3 b) { return (uvec3(floor(a + 0.5)) == b); }\n";
        if (cmpTypeFound[TYPE_UINT_VEC4])
            stream << "bool isOk (vec4 a, uvec4 b) { return (uvec4(floor(a + 0.5)) == b); }\n";
    }
    else
    {
        if (cmpTypeFound[TYPE_BOOL])
            stream << "bool isOk (bool a, bool b)   { return (a == b); }\n";
        if (cmpTypeFound[TYPE_BOOL_VEC2])
            stream << "bool isOk (bvec2 a, bvec2 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_BOOL_VEC3])
            stream << "bool isOk (bvec3 a, bvec3 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_BOOL_VEC4])
            stream << "bool isOk (bvec4 a, bvec4 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_INT])
            stream << "bool isOk (int a, int b)     { return (a == b); }\n";
        if (cmpTypeFound[TYPE_INT_VEC2])
            stream << "bool isOk (ivec2 a, ivec2 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_INT_VEC3])
            stream << "bool isOk (ivec3 a, ivec3 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_INT_VEC4])
            stream << "bool isOk (ivec4 a, ivec4 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_UINT])
            stream << "bool isOk (uint a, uint b)   { return (a == b); }\n";
        if (cmpTypeFound[TYPE_UINT_VEC2])
            stream << "bool isOk (uvec2 a, uvec2 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_UINT_VEC3])
            stream << "bool isOk (uvec3 a, uvec3 b) { return (a == b); }\n";
        if (cmpTypeFound[TYPE_UINT_VEC4])
            stream << "bool isOk (uvec4 a, uvec4 b) { return (a == b); }\n";
    }

    if (cmpTypeFound[TYPE_FLOAT])
        stream << "bool isOk (float a, float b, float eps) { return (abs(a-b) <= (eps*abs(b) + eps)); }\n";
    if (cmpTypeFound[TYPE_FLOAT_VEC2])
        stream
            << "bool isOk (vec2 a, vec2 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_VEC3])
        stream
            << "bool isOk (vec3 a, vec3 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_VEC4])
        stream
            << "bool isOk (vec4 a, vec4 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";

    if (cmpTypeFound[TYPE_FLOAT_MAT2])
        stream << "bool isOk (mat2 a, mat2 b, float eps) { vec2 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return "
                  "all(lessThanEqual(diff, vec2(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT2X3])
        stream << "bool isOk (mat2x3 a, mat2x3 b, float eps) { vec3 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return "
                  "all(lessThanEqual(diff, vec3(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT2X4])
        stream << "bool isOk (mat2x4 a, mat2x4 b, float eps) { vec4 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return "
                  "all(lessThanEqual(diff, vec4(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT3X2])
        stream << "bool isOk (mat3x2 a, mat3x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
                  "abs(a[2]-b[2])); return all(lessThanEqual(diff, vec2(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT3])
        stream << "bool isOk (mat3 a, mat3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
                  "abs(a[2]-b[2])); return all(lessThanEqual(diff, vec3(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT3X4])
        stream << "bool isOk (mat3x4 a, mat3x4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
                  "abs(a[2]-b[2])); return all(lessThanEqual(diff, vec4(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT4X2])
        stream << "bool isOk (mat4x2 a, mat4x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
                  "max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec2(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT4X3])
        stream << "bool isOk (mat4x3 a, mat4x3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
                  "max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec3(eps))); }\n";
    if (cmpTypeFound[TYPE_FLOAT_MAT4])
        stream << "bool isOk (mat4 a, mat4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
                  "max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec4(eps))); }\n";
}

} // namespace sl
} // namespace glu
