// NOTE: we capture entire lines as "tokens"; this class's job is to classify individual line entries (and tag comment/whitespace trivia), not to parse raw tokens
        public IniToken GetNextToken()
        {
            var leadingTrivia = new List <IniTrivia>();

            // NOTE: this loop will continue until we parse a line or read eof of file
            while (true)
            {
                var nextLine = this.ReadIniTextLine();

                if (nextLine is null)
                {
                    return(new IniToken(IniTokenKind.EndOfFile, new List <char>() /* empty lexeme, rather than null */, IniLineTerminatorOption.None, leadingTrivia, null));
                }

                var lexeme         = nextLine.Text !;
                var lineTerminator = nextLine.LineTerminator;

                // check for empty lines
                if (lexeme.Count == 0)
                {
                    // empty line; treat this as trivia
                    var whitespaceTrivia = new IniTrivia(IniTriviaKind.Whitespace, lexeme, lineTerminator);
                    leadingTrivia.Add(whitespaceTrivia);

                    // capture another line of text
                    continue;
                }
                else
                {
                    // check for comment lines
                    if (lexeme.First() == ';')
                    {
                        // comment; capture this as trivia
                        var commentTrivia = new IniTrivia(IniTriviaKind.Comment, lexeme, lineTerminator);
                        leadingTrivia.Add(commentTrivia);

                        // capture another line of text
                        continue;
                    }

                    // check for whitespace lines
                    var lineIsWhitespace = lexeme.All(ch =>
                    {
                        switch (ch)
                        {
                        case ' ':
                        case '\t':
                            return(true);

                        default:
                            return(false);
                        }
                    });
                    if (lineIsWhitespace == true)
                    {
                        var whitespaceTrivia = new IniTrivia(IniTriviaKind.Whitespace, lexeme, lineTerminator);
                        leadingTrivia.Add(whitespaceTrivia);

                        // capture another line of text
                        continue;
                    }

                    // check for an EOF marker
                    if (lexeme.Count == 1 && lexeme.First() == (char)0x1A /* EOF marker */)
                    {
                        // EOF marker; this will end our text capture
                        return(new IniToken(IniTokenKind.EndOfFile, lexeme, lineTerminator, leadingTrivia, null));
                    }

                    // now categorize our lexeme (or mark it as invalid)
                    char?firstNonWhitespaceCharacter = null;
                    foreach (var ch in lexeme)
                    {
                        switch (ch)
                        {
                        case ' ':
                        case '\t':
                            continue;

                        default:
                            firstNonWhitespaceCharacter = ch;
                            break;
                        }

                        if (firstNonWhitespaceCharacter is not null)
                        {
                            break;
                        }
                    }

                    // check for sections
                    if (firstNonWhitespaceCharacter == '[')
                    {
                        // make sure that the line contains an ending (right) bracket
                        var indexOfRightBracket = lexeme.IndexOf(']');
                        if (indexOfRightBracket > 0)
                        {
                            // found right bracket; make sure that the line does not contain any non-whitespace characters after the bracket
                            for (var index = indexOfRightBracket + 1; index < lexeme.Count; index += 1)
                            {
                                switch (lexeme[index])
                                {
                                case ' ':
                                case '\t':
                                    // whitespace
                                    break;

                                default:
                                    // if we encounter any non-whitespace characters, this an invalid token
                                    return(new IniToken(IniTokenKind.Invalid, lexeme, lineTerminator, leadingTrivia, null));
                                }
                            }

                            // this section is valid; return it now
                            return(new IniToken(IniTokenKind.Section, lexeme, lineTerminator, leadingTrivia, null));
                        }
                        else
                        {
                            // this line does not have a closing bracket for the section
                            return(new IniToken(IniTokenKind.Invalid, lexeme, lineTerminator, leadingTrivia, null));
                        }
                    }

                    // NOTE: at this point, the line should be a property (i.e. key/value pair); anything else is invalid

                    // check for an equals sign; we will treat anything to the left as the key and anything to the right as the value
                    var indexOfEqualsCharacter = lexeme.IndexOf('=');
                    if (indexOfEqualsCharacter >= 0)
                    {
                        // key/value pair
                        // NOTE: our parser is responsible for parsing out the key and value (and dealing with leading/trailing whitespace, double quotes around values, etc.)
                        return(new IniToken(IniTokenKind.Property, lexeme, lineTerminator, leadingTrivia, null));
                    }

                    // anything else is an invalid token
                    return(new IniToken(IniTokenKind.Invalid, lexeme, lineTerminator, leadingTrivia, null));
                }
            }
        }
 private static void AppendTriviaToStringBuilder(IniTrivia trivia, ref StringBuilder builder)
 {
     builder.Append(trivia.Lexeme.ToArray());
     IniFile.AppendLineTerminatorToStringBuilder(trivia.LineTerminator ?? IniLineTerminatorOption.None, ref builder);
 }