/// <summary>
        /// Checks whether macros in the given source file are matched
        /// </summary>
        /// <param name="SourceFile"></param>
        /// <param name="IdentifierToIndex">Map of macro identifier to bit index. The complement of an index is used to indicate the end of the pair.</param>
        /// <param name="LogLock">Object used to marshal access to the global log instance</param>
        void CheckSourceFile(FileReference SourceFile, Dictionary <string, int> IdentifierToIndex, object LogLock)
        {
            // Read the text
            string Text = FileReference.ReadAllText(SourceFile);

            // Scan through the file token by token. Each bit in the Flags array indicates an index into the MacroPairs array that is currently active.
            int Flags = 0;

            for (int Idx = 0; Idx < Text.Length;)
            {
                int StartIdx = Idx++;
                if ((Text[StartIdx] >= 'a' && Text[StartIdx] <= 'z') || (Text[StartIdx] >= 'A' && Text[StartIdx] <= 'Z') || Text[StartIdx] == '_')
                {
                    // Identifier
                    while (Idx < Text.Length && ((Text[Idx] >= 'a' && Text[Idx] <= 'z') || (Text[Idx] >= 'A' && Text[Idx] <= 'Z') || (Text[Idx] >= '0' && Text[Idx] <= '9') || Text[Idx] == '_'))
                    {
                        Idx++;
                    }

                    // Extract the identifier
                    string Identifier = Text.Substring(StartIdx, Idx - StartIdx);

                    // Find the matching flag
                    int Index;
                    if (IdentifierToIndex.TryGetValue(Identifier, out Index))
                    {
                        if (Index >= 0)
                        {
                            // Set the flag (should not already be set)
                            int Flag = 1 << Index;
                            if ((Flags & Flag) != 0)
                            {
                                Tools.DotNETCommon.Log.TraceWarning(SourceFile, GetLineNumber(Text, StartIdx), "{0} macro appears a second time without matching {1} macro", Identifier, MacroPairs[Index, 1]);
                            }
                            Flags |= Flag;
                        }
                        else
                        {
                            // Clear the flag (should already be set)
                            int Flag = 1 << ~Index;
                            if ((Flags & Flag) == 0)
                            {
                                Tools.DotNETCommon.Log.TraceWarning(SourceFile, GetLineNumber(Text, StartIdx), "{0} macro appears without matching {1} macro", Identifier, MacroPairs[~Index, 0]);
                            }
                            Flags &= ~Flag;
                        }
                    }
                }
                else if (Text[StartIdx] == '/' && Idx < Text.Length)
                {
                    if (Text[Idx] == '/')
                    {
                        // Single-line comment
                        while (Idx < Text.Length && Text[Idx] != '\n')
                        {
                            Idx++;
                        }
                    }
                    else if (Text[Idx] == '*')
                    {
                        // Multi-line comment
                        Idx++;
                        for (; Idx < Text.Length; Idx++)
                        {
                            if (Idx + 2 < Text.Length && Text[Idx] == '*' && Text[Idx + 1] == '/')
                            {
                                Idx += 2;
                                break;
                            }
                        }
                    }
                }
                else if (Text[StartIdx] == '"' || Text[StartIdx] == '\'')
                {
                    // String
                    for (; Idx < Text.Length; Idx++)
                    {
                        if (Text[Idx] == Text[StartIdx])
                        {
                            Idx++;
                            break;
                        }
                        if (Text[Idx] == '\\')
                        {
                            Idx++;
                        }
                    }
                }
                else if (Text[StartIdx] == '#')
                {
                    // Preprocessor directive (eg. #define)
                    for (; Idx < Text.Length && Text[Idx] != '\n'; Idx++)
                    {
                        if (Text[Idx] == '\\')
                        {
                            Idx++;
                        }
                    }
                }
            }

            // Check if there's anything left over
            if (Flags != 0)
            {
                for (int Idx = 0; Idx < MacroPairs.GetLength(0); Idx++)
                {
                    if ((Flags & (1 << Idx)) != 0)
                    {
                        Tools.DotNETCommon.Log.TraceWarning(SourceFile, "{0} macro does not have matching {1} macro", MacroPairs[Idx, 0], MacroPairs[Idx, 1]);
                    }
                }
            }
        }