/// <summary>
        /// Parses all shader field declarations, aggregated with their metadata directives.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="ignoreRegions"></param>
        /// <param name="removeSchedule"></param>
        private void ParseFields(string source, List <IndexRange> ignoreRegions, List <IndexRange> removeSchedule)
        {
            // Read the source line by line and parse it along the way
            List <string> fieldMetadata = new List <string>();

            using (StringReader reader = new StringReader(source))
            {
                int lineIndex = 0;
                while (true)
                {
                    string line = reader.ReadLine();
                    if (line == null)
                    {
                        break;
                    }

                    // Trim the current line, so ignored ranges are removed
                    IndexRange lineRange        = new IndexRange(lineIndex, line.Length);
                    IndexRange trimmedLineRange = lineRange;
                    foreach (IndexRange ignoreRange in ignoreRegions)
                    {
                        trimmedLineRange.Trim(ignoreRange);
                        if (trimmedLineRange.Length == 0)
                        {
                            break;
                        }
                    }
                    foreach (IndexRange ignoreRange in removeSchedule)
                    {
                        trimmedLineRange.Trim(ignoreRange);
                        if (trimmedLineRange.Length == 0)
                        {
                            break;
                        }
                    }
                    string trimmedLine =
                        (trimmedLineRange.Length == 0) ?
                        string.Empty :
                        source.Substring(trimmedLineRange.Index, trimmedLineRange.Length);

                    // Keep track of where we are in the source, and skip over lines that
                    // fall within source regions that are flagged to be ignored.
                    lineIndex += line.Length;
                    lineIndex += Environment.NewLine.Length;

                    // Cleanup remaining line to make it easier to parse
                    trimmedLine = trimmedLine.Trim().TrimEnd(';');
                    if (string.IsNullOrEmpty(trimmedLine))
                    {
                        continue;
                    }

                    // Scan for metadata directives and store them until we hit the next variable declaration
                    Match metadataMatch = RegexMetadataDirective.Match(trimmedLine);
                    if (metadataMatch != null && metadataMatch.Length > 0)
                    {
                        string metadataDirective = metadataMatch.Groups[1].Value;
                        fieldMetadata.Add(metadataDirective);
                        continue;
                    }

                    // Scan for field declarations and aggregate them with previously collected metadata directives
                    ShaderFieldInfo field = this.ParseFieldDeclaration(trimmedLine, fieldMetadata);
                    if (field != null)
                    {
                        this.fields.Add(field);
                        fieldMetadata.Clear();
                        continue;
                    }

                    // Clear metadata directives when reading non-empty lines that don't match any of the above
                    fieldMetadata.Clear();
                }
            }
        }
        /// <summary>
        /// Identify continuous blocks of variable metadata directives, mapped to the range of source code
        /// they're located in.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="ignoreRegions"></param>
        private Dictionary <IndexRange, List <IndexRange> > IdentifyMetadataBlocks(string source, List <IndexRange> ignoreRegions)
        {
            Dictionary <IndexRange, List <IndexRange> > metadataBlocks = new Dictionary <IndexRange, List <IndexRange> >();

            using (StringReader reader = new StringReader(source))
            {
                List <IndexRange> currentBlock      = new List <IndexRange>();
                IndexRange        currentBlockRange = new IndexRange(0, 0);
                int lineIndex = 0;
                while (true)
                {
                    string line = reader.ReadLine();
                    if (line == null)
                    {
                        break;
                    }

                    // Trim the current line, so ignored ranges are removed
                    IndexRange lineRange        = new IndexRange(lineIndex, line.Length);
                    IndexRange trimmedLineRange = lineRange;
                    foreach (IndexRange ignoreRange in ignoreRegions)
                    {
                        trimmedLineRange.Trim(ignoreRange);
                        if (trimmedLineRange.Length == 0)
                        {
                            break;
                        }
                    }
                    string trimmedLine =
                        (trimmedLineRange.Length == 0) ?
                        string.Empty :
                        source.Substring(trimmedLineRange.Index, trimmedLineRange.Length);

                    // Process the current line
                    bool isLineEmpty = string.IsNullOrWhiteSpace(trimmedLine);
                    if (!isLineEmpty)
                    {
                        Match match = RegexMetadataDirective.Match(trimmedLine);
                        if (match != null && match.Length > 0)
                        {
                            // Extend the current metadata block to include the detected directive
                            IndexRange metadataRange = new IndexRange(trimmedLineRange.Index + match.Index, match.Length);
                            metadataRange = this.ExpandToLine(source, metadataRange);
                            currentBlock.Add(metadataRange);
                            currentBlockRange.Length = (metadataRange.Index + metadataRange.Length) - currentBlockRange.Index;
                        }
                        else
                        {
                            // Close the current metadata block
                            if (currentBlock.Count > 0)
                            {
                                metadataBlocks.Add(currentBlockRange, new List <IndexRange>(currentBlock));
                            }

                            // Start a new metadata block
                            currentBlock.Clear();
                            currentBlockRange.Index  = lineRange.Index + lineRange.Length + Environment.NewLine.Length;
                            currentBlockRange.Length = 0;
                        }
                    }
                    else
                    {
                        // If we have a current block, incorporate comment and empty lines into it until it ends
                        if (currentBlock.Count > 0)
                        {
                            currentBlockRange.Length = (lineRange.Index + lineRange.Length) - currentBlockRange.Index;
                        }
                        // Otherwise, move the start of the current block forward until we actually find a metadata line
                        else
                        {
                            currentBlockRange.Index  = lineRange.Index + lineRange.Length + Environment.NewLine.Length;
                            currentBlockRange.Length = 0;
                        }
                    }

                    lineIndex += line.Length;
                    lineIndex += Environment.NewLine.Length;
                }
            }

            return(metadataBlocks);
        }