private static void CompileShaderFunction(ShaderFunction func, ZScriptTokenizer t)
        {
            ZScriptToken token;

            t.SkipWhitespace();
            token = t.ExpectToken(ZScriptTokenType.Identifier);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected function name, got {0}", token?.ToString() ?? "<EOF>");
            }

            func.Name = token.Value;

            t.SkipWhitespace();
            token = t.ExpectToken(ZScriptTokenType.OpenParen);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected function argument list, got {0}", token?.ToString() ?? "<EOF>");
            }

            func.Arguments = ReadEverythingUntil(t, ZScriptTokenType.CloseParen, false, false);

            token = t.ExpectToken(ZScriptTokenType.CloseParen);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected end of function arguments, got {0}", token?.ToString() ?? "<EOF>");
            }

            t.SkipWhitespace();
            token = t.ExpectToken(ZScriptTokenType.OpenCurly);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected function code block, got {0}", token?.ToString() ?? "<EOF>");
            }

            func.CodeLine = t.PositionToLine(token.Position);
            func.Code     = ReadEverythingUntil(t, ZScriptTokenType.CloseCurly, false, false);

            token = t.ExpectToken(ZScriptTokenType.CloseCurly);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected end of function code block, got {0}", token?.ToString() ?? "<EOF>");
            }
        }
        private static List <ShaderField> CompileShaderDataBlock(ZScriptTokenizer t)
        {
            List <ShaderField> fields = new List <ShaderField>();

            t.SkipWhitespace();
            ZScriptToken token = t.ExpectToken(ZScriptTokenType.OpenCurly);

            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected data block, got {0}", token?.ToString() ?? "<EOF>");
            }

            while (true)
            {
                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected data field or end of block, got {0}", token?.ToString() ?? "<EOF>");
                }

                if (token.Type == ZScriptTokenType.CloseCurly)
                {
                    break;
                }

                ShaderField field = new ShaderField();
                field.Line     = t.PositionToLine(token.Position);
                field.TypeName = token.Value;

                CompileShaderField(field, t);

                fields.Add(field);
            }

            return(fields);
        }
        private static void CompileUniforms(ShaderGroup output, ZScriptTokenizer t)
        {
            // so a type of a variable is normally identifier+array dimensions
            // array dimensions may also exist on the variable itself (oh god this shitty C syntax)
            t.SkipWhitespace();
            ZScriptToken token;

            token = t.ExpectToken(ZScriptTokenType.OpenCurly);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected uniforms block, got {0}", token?.ToString() ?? "<EOF>");
            }

            while (true)
            {
                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected uniform or end of block, got {0}", token?.ToString() ?? "<EOF>");
                }

                if (token.Type == ZScriptTokenType.CloseCurly)
                {
                    break; // done reading uniforms
                }
                // first goes the name, then array dimensions, then the variable name, then array dimensions, then initializer
                ShaderField field = new ShaderField();
                field.Line     = t.PositionToLine(token.Position);
                field.TypeName = token.Value;

                CompileShaderField(field, t);

                // done reading field, add it
                output.Uniforms.Add(field);
            }
        }
        private static void CompileShader(ShaderGroup output, ZScriptTokenizer t)
        {
            t.SkipWhitespace();

            ZScriptToken token = t.ExpectToken(ZScriptTokenType.Identifier);

            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected shader identifier, got {0}", token?.ToString() ?? "<EOF>");
            }

            t.SkipWhitespace();

            Shader s = new Shader(output);

            s.Name = token.Value;
            output.Shaders.Add(s);

            token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.OpenCurly);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected parent identifier or shader block, got {0}", token?.ToString() ?? "<EOF>");
            }

            // has parent shader id
            if (token.Type == ZScriptTokenType.Identifier)
            {
                if (token.Value != "extends")
                {
                    throw new ShaderCompileException("Expected 'extends', got {0}", token.ToString());
                }

                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.Identifier);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected parent identifier, got {0}", token?.ToString() ?? "<EOF>");
                }

                s.ParentName = token.Value;

                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.OpenCurly);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected shader block, got {0}", token?.ToString() ?? "<EOF>");
                }
            }


            s.CodeLine = t.PositionToLine(token.Position);

            while (true)
            {
                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected shader sub-block or end of block, got {0}", token?.ToString() ?? "<EOF>");
                }

                if (token.Type == ZScriptTokenType.CloseCurly)
                {
                    break;
                }

                switch (token.Value)
                {
                case "in":
                    s.In = CompileShaderDataBlock(t);
                    break;

                case "out":
                    s.Out = CompileShaderDataBlock(t);
                    break;

                case "v2f":
                    s.V2F = CompileShaderDataBlock(t);
                    break;

                case "functions":
                    CompileShaderFunctions(s, t);
                    break;

                case "vertex":
                    s.SourceVertex = CompileShaderSource(t);
                    if (s.SourceVertex != null && s.SourceVertex.Count > 0)
                    {
                        s.SourceVertexLine = t.PositionToLine(s.SourceVertex[0].Position);
                    }
                    break;

                case "fragment":
                    s.SourceFragment = CompileShaderSource(t);
                    if (s.SourceFragment != null && s.SourceFragment.Count > 0)
                    {
                        s.SourceFragmentLine = t.PositionToLine(s.SourceFragment[0].Position);
                    }
                    break;

                default:
                    throw new ShaderCompileException("Expected shader sub-block, got {0}", token.ToString());
                }
            }
        }
        private static void CompileShaderFunctions(Shader output, ZScriptTokenizer t)
        {
            t.SkipWhitespace();
            ZScriptToken token;

            token = t.ExpectToken(ZScriptTokenType.OpenCurly);
            if (!(token?.IsValid ?? true))
            {
                throw new ShaderCompileException("Expected functions block, got {0}", token?.ToString() ?? "<EOF>");
            }

            while (true)
            {
                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected function or end of block, got {0}", token?.ToString() ?? "<EOF>");
                }

                if (token.Type == ZScriptTokenType.CloseCurly)
                {
                    break; // done reading functions
                }
                bool isoverride = false;
                if (token.Value == "override")
                {
                    isoverride = true;

                    t.SkipWhitespace();
                    token = t.ExpectToken(ZScriptTokenType.Identifier);
                    if (!(token?.IsValid ?? true))
                    {
                        throw new ShaderCompileException("Expected function return type, got {0}", token?.ToString() ?? "<EOF>");
                    }
                }

                // <return value> <name> (<arguments>) { <code> }
                ShaderFunction func = new ShaderFunction();
                func.Line        = t.PositionToLine(token.Position);
                func.ReturnValue = token.Value;
                func.Override    = isoverride;

                CompileShaderFunction(func, t);

                // check if function with such name already exists in the shader
                // delete it.
                // overloading is not supported for now
                if (!isoverride)
                {
                    ShaderFunction existingFunc = output.Group.GetFunction(func.Name);
                    if (existingFunc != null)
                    {
                        throw new ShaderCompileException("Function {0} is double-defined without 'override' keyword! (previous declaration at line {1})", func.Name, existingFunc.Line);
                    }
                }
                for (int i = 0; i < output.Functions.Count; i++)
                {
                    if (output.Functions[i].Name == func.Name)
                    {
                        if (!isoverride)
                        {
                            throw new ShaderCompileException("Function {0} is double-defined without 'override' keyword! (previous declaration at line {1})", func.Name, output.Functions[i].Line);
                        }
                        output.Functions.RemoveAt(i);
                        i--;
                        continue;
                    }
                }

                output.Functions.Add(func);
            }
        }