private string GetFunctionSource(List <ZScriptToken> shaderSource)
        {
            List <string> functionNames = new List <string>();

            GetReferencedFunctions(shaderSource, functionNames);

            string preOutput = "";
            string output    = "";

            foreach (string functionName in functionNames)
            {
                ShaderFunction func = GetFunction(functionName);
                if (func == null)
                {
                    func = Group.GetFunction(functionName);
                }
                if (func == null)
                {
                    continue;
                }

                string funcOutput = string.Format("#line {0}\n", func.Line) + func.ReturnValue + " " + func.Name + " (" + GetTokenListSource(func.Arguments) + ")";
                preOutput += funcOutput + ";\n";

                funcOutput += " {\n" + string.Format("#line {0}\n", func.CodeLine) + GetTokenListSource(func.Code) + "}\n";
                output     += funcOutput;
            }

            return(preOutput + "\n" + output);
        }
        private void GetReferencedFunctions(List <ZScriptToken> source, List <string> functions)
        {
            for (int i = 0; i < source.Count; i++)
            {
                ZScriptToken token = source[i];
                if (token.Type != ZScriptTokenType.Identifier)
                {
                    continue;
                }

                if (functions.Contains(token.Value))
                {
                    continue;
                }

                // check token to the left - needs to not be identifier.
                // check token to the right - needs to be open parenthesis
                // ---
                // the idea is that we can differentiate pixel = desaturate(...) from vec4 desaturate(1,1,1,1)
                //
                ZScriptTokenType leftToken  = ZScriptTokenType.Invalid;
                ZScriptTokenType rightToken = ZScriptTokenType.Invalid;

                for (int j = i - 1; j >= 0; j--)
                {
                    ZScriptTokenType tt = source[j].Type;
                    if (!IsWhitespace(tt))
                    {
                        leftToken = tt;
                        break;
                    }
                }

                for (int j = i + 1; j < source.Count; j++)
                {
                    ZScriptTokenType tt = source[j].Type;
                    if (!IsWhitespace(tt))
                    {
                        rightToken = tt;
                        break;
                    }
                }

                if (leftToken != ZScriptTokenType.Identifier && rightToken == ZScriptTokenType.OpenParen)
                {
                    // find function
                    functions.Add(token.Value);
                    // if function was found, recurse and find functions it may depend on.
                    ShaderFunction func = GetFunction(token.Value);
                    if (func == null)
                    {
                        func = Group.GetFunction(token.Value);
                    }
                    if (func != null)
                    {
                        GetReferencedFunctions(func.Code, functions);
                    }
                }
            }
        }
        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 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);
            }
        }