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);
            }
        }
        // this is to implement partial parsing. it counts {}, (), and []. it stops parsing at the specified type, if outside of nesting.
        // also if last block token equals to the type, it will stop at the outer level. (i.e. last })
        private static List <ZScriptToken> ReadEverythingUntil(ZScriptTokenizer t, ZScriptTokenType type, bool eofIsOk, bool skipWhitespace)
        {
            List <ZScriptToken> tokens = new List <ZScriptToken>();

            int levelCurly  = 0;
            int levelSquare = 0;
            int levelParen  = 0;

            while (true)
            {
                if (skipWhitespace)
                {
                    t.SkipWhitespace();
                }

                long cpos = t.Reader.BaseStream.Position;

                ZScriptToken token = t.ReadToken();
                if (token == null)
                {
                    if (!eofIsOk)
                    {
                        throw new ShaderCompileException("Expected {0} or token, got <EOF>", type);
                    }
                    break;
                }

                // if this is the end token, don't check anything -- just return
                if (levelCurly == 0 && levelSquare == 0 && levelParen == 0 && token.Type == type)
                {
                    // rewind and return token list
                    t.Reader.BaseStream.Position = cpos;
                    break;
                }

                switch (token.Type)
                {
                case ZScriptTokenType.OpenCurly:
                    levelCurly++;
                    break;

                case ZScriptTokenType.CloseCurly:
                    levelCurly--;
                    break;

                case ZScriptTokenType.OpenParen:
                    levelParen++;
                    break;

                case ZScriptTokenType.CloseParen:
                    levelParen--;
                    break;

                case ZScriptTokenType.OpenSquare:
                    levelSquare++;
                    break;

                case ZScriptTokenType.CloseSquare:
                    levelSquare--;
                    break;
                }

                tokens.Add(token);
            }

            return(tokens);
        }
        private static void CompileShaderField(ShaderField field, ZScriptTokenizer t)
        {
            ZScriptToken token;

            // read name and array dimensions
            while (true)
            {
                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.OpenSquare, ZScriptTokenType.Identifier);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected array dimensions or field name, got {0}", token?.ToString() ?? "<EOF>");
                }

                // array finished
                if (token.Type == ZScriptTokenType.Identifier)
                {
                    field.Name = token.Value;
                    break;
                }

                // read array
                List <ZScriptToken> arrayDimTokens = ReadEverythingUntil(t, ZScriptTokenType.CloseSquare, false, false);
                if (field.ArrayDimensions == null)
                {
                    field.ArrayDimensions = new List <List <ZScriptToken> >();
                }
                field.ArrayDimensions.Add(arrayDimTokens);

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

            // read additional array dimensions if present, and initializer. or end parsing
            while (true)
            {
                t.SkipWhitespace();
                token = t.ExpectToken(ZScriptTokenType.OpenSquare, ZScriptTokenType.OpAssign, ZScriptTokenType.Semicolon);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected array dimensions, initializer or semicolon, got {0}", token?.ToString() ?? "<EOF>");
                }

                // field is done
                if (token.Type == ZScriptTokenType.Semicolon)
                {
                    break;
                }

                // has initializer
                if (token.Type == ZScriptTokenType.OpAssign)
                {
                    field.Initializer = ReadEverythingUntil(t, ZScriptTokenType.Semicolon, false, false);
                    token             = t.ExpectToken(ZScriptTokenType.Semicolon);
                    if (!(token?.IsValid ?? true))
                    {
                        throw new ShaderCompileException("Expected semicolon, got {0}", token?.ToString() ?? "<EOF>");
                    }
                    break;
                }

                // read array
                List <ZScriptToken> arrayDimTokens = ReadEverythingUntil(t, ZScriptTokenType.CloseSquare, false, false);
                if (field.ArrayDimensions == null)
                {
                    field.ArrayDimensions = new List <List <ZScriptToken> >();
                }
                field.ArrayDimensions.Add(arrayDimTokens);

                token = t.ExpectToken(ZScriptTokenType.CloseSquare);
                if (!(token?.IsValid ?? true))
                {
                    throw new ShaderCompileException("Expected closing square brace, got {0}", token?.ToString() ?? "<EOF>");
                }
            }
        }
        public static ShaderGroup Compile(string src)
        {
            ShaderGroup output = new ShaderGroup();

            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(src)))
                using (BinaryReader br = new BinaryReader(ms))
                {
                    ZScriptTokenizer t = new ZScriptTokenizer(br);

                    // main cycle
                    // in the root scope, we allow three blocks:
                    //  - uniforms{}
                    //  - functions{}
                    //  - shader <name> {}
                    // everything else is a syntax error.
                    while (true)
                    {
                        t.SkipWhitespace();
                        ZScriptToken token = t.ExpectToken(ZScriptTokenType.Identifier);
                        if (token == null)
                        {
                            break;
                        }

                        if (!token.IsValid)
                        {
                            throw new ShaderCompileException("Expected 'uniforms', 'functions', or 'shader'; got {0}", token.ToString());
                        }

                        switch (token.Value)
                        {
                        case "uniforms":
                            CompileUniforms(output, t);
                            break;

                        case "functions":
                            CompileFunctions(output, t);
                            break;

                        case "shader":
                            CompileShader(output, t);
                            break;

                        default:
                            throw new ShaderCompileException("Expected 'uniforms', 'functions', or 'shader'; got {0}", token.ToString());
                        }
                    }

                    // done parsing, postprocess - apply parents
                    foreach (Shader s in output.Shaders)
                    {
                        List <string> parents = new List <string>();
                        parents.Add(s.Name);
                        Shader p = s;
                        while (p.ParentName != null && p.ParentName != "")
                        {
                            string parentName = p.ParentName;
                            if (parents.Contains(parentName))
                            {
                                throw new ShaderCompileException("Recursive parent shader {0} found", parentName);
                            }
                            parents.Add(parentName);
                            p = output.GetShader(parentName);
                            if (p == null)
                            {
                                throw new ShaderCompileException("Parent shader {0} not found", parentName);
                            }

                            if (s.In == null)
                            {
                                s.In = p.In;
                            }
                            if (s.Out == null)
                            {
                                s.Out = p.Out;
                            }
                            if (s.V2F == null)
                            {
                                s.V2F = p.V2F;
                            }
                            if (s.SourceFragment == null)
                            {
                                s.SourceFragment     = p.SourceFragment;
                                s.SourceFragmentLine = p.SourceFragmentLine;
                            }
                            if (s.SourceVertex == null)
                            {
                                s.SourceVertex     = p.SourceVertex;
                                s.SourceVertexLine = p.SourceVertexLine;
                            }

                            // add functions from parent
                            foreach (ShaderFunction func in p.Functions)
                            {
                                if (s.GetFunction(func.Name) == null)
                                {
                                    s.Functions.Add(func);
                                }
                            }
                        }
                    }

                    return(output);
                }
        }