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 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 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>");
                }
            }
        }