/// <summary>
        /// Get the next token in the stream. Returns an EOF at the end of document.
        /// </summary>
        /// <returns></returns>
        public YamlToken ReadNext()
        {
            var pair = ReadNextWorker();

            _lastPair = pair;
            return(pair);
        }
            internal YamlToken CheckDuplicate(string propName, int currentLine)
            {
                int oldLine;

                if (_previousProperties.TryGetValue(propName, out oldLine))
                {
                    // Key is already present.
                    return(YamlToken.NewError(default(SourceLocation), $"Property '{propName}' is already defined on line {oldLine}."));
                }
                _previousProperties.Add(propName, currentLine);
                return(null);
            }
        public YamlLexer(TextReader source, string filenameHint = null)
        {
            _reader          = source;
            _currentFileName = filenameHint; // For errort reporting

            // We pretend the file is wrapped in a "object:" tag.
            _lastPair      = YamlToken.NewStartObj(default(SourceLocation), null);
            _currentIndent = new Stack <Indent>()
            {
                new Indent
                {
                    _oldIndentLevel = -1,
                    _lineStart      = 0
                }
            };
        }
 private YamlToken Error(LineParser line, string message)
 {
     return(YamlToken.NewError(this.Loc(line), message));
 }
        private YamlToken ReadNextWorker()
        {
            // Warn on:
            //   # comment
            //   no escape (= or |)
            //   unallowed multiline escape
            //   blank lines
            //   different indent size
            //   duplicate keys
            //   empty properties (should have an indent)
            //   no tabs.
            //  don't allow --- documents

            // Start of line.
            // [Indent] [PropertyName] [Colon]
            // [Indent] [PropertyName] [Colon] [space] [equals] [VALUE]
            // [Indent] [PropertyName] [Colon] [space] [MultilineEscape]

            LineParser line;
            int        indentLen;

            while (true)
            {
                line = PeekLine();

                if (line == null)
                {
                    // End of File.
                    // Close any  outstanding objects.
                    if (_currentIndent.Count > 1)
                    {
                        _currentIndent.Pop();
                        return(YamlToken.EndObj);
                    }
                    return(YamlToken.EndOfFile);
                }

                // get starting indent.
                indentLen = line.EatIndent();

                // Comment indent level doesn't matter - may not match object indent.
                if (line.Current == '#')
                {
                    // https://github.com/microsoft/PowerApps-Language-Tooling/issues/115
                    // Allow comment lines. These will get stripped (won't roundtrip).
                    if (!_commentStrippedWarning.HasValue)
                    {
                        _commentStrippedWarning = Loc(line);
                    }

                    MoveNextLine();
                    continue;
                }

                if (line.Current != 0)
                {
                    break;
                }

                // Eat the newline and go to the next line.
                MoveNextLine();
            }

            if (line.Current == '-')
            {
                return(Unsupported(line, "--- markers are not supported. Only a single document per file."));
            }

            if (line.Current == '\t')
            {
                // Helpful warning.
                return(Error(line, "Use spaces, not tabs."));
            }

            var startColumn = line._idx + 1; // 1-based start column number.


            // If last was 'start object', then this indent sets the new level,
            if (_lastPair.Kind == YamlTokenKind.StartObj)
            {
                var lastIndent = _currentIndent.Peek()._oldIndentLevel;

                if (indentLen == lastIndent) // Close immediate parent
                {
                    _currentIndent.Pop();
                    return(YamlToken.EndObj);
                }
                else if (indentLen < lastIndent)
                {
                    // Close current objects one at a time.
                    _currentIndent.Pop();

                    var prevIndent = _currentIndent.Peek()._oldIndentLevel;
                    // Indent must exactly match a previous one up the stack.
                    if (indentLen > prevIndent)
                    {
                        return(Error(line, "Property indent must align exactly with a previous indent level."));
                    }

                    return(YamlToken.EndObj);
                }

                if (indentLen <= lastIndent)
                {
                    // Error. new object should be indented.
                    return(Unsupported(line, "Can't have null properties. Line should be indented to start a new property."));
                }
                _currentIndent.Peek()._newIndentLevel = indentLen;
            }
            else
            {
                // Subsequent properties should be at same indentation level.
                var expectedIndent = _currentIndent.Peek()._newIndentLevel;
                if (indentLen == expectedIndent)
                {
                    // Good. Common case.
                    // Continue processing below to actually parse this property.
                }
                else if (indentLen > expectedIndent)
                {
                    return(Error(line, "Property should be at same indent level"));
                }
                else
                {
                    // Closing an object.
                    // Indent must exactly match a previous one up the stack.
                    if (indentLen > _currentIndent.Peek()._oldIndentLevel)
                    {
                        return(Error(line, "Property indent must align exactly with a previous indent level."));
                    }

                    // Close current objects one at a time.
                    _currentIndent.Pop();
                    return(YamlToken.EndObj);
                }
            }

            // Get identifier
            // toggle isEscaped on every ' appearance
            bool isEscaped = false;
            bool requiresClosingDoubleQuote = line.MaybeEat('"');

            while (line.Current != ':' || isEscaped || requiresClosingDoubleQuote)
            {
                if (line.Current == '\'')
                {
                    isEscaped = !isEscaped;
                }

                if (requiresClosingDoubleQuote && line.Current == '"' && line.Previous != '\\')
                {
                    line._idx++;
                    break;
                }

                if (line.Current == 0) // EOL
                {
                    if (isEscaped)
                    {
                        return(Unsupported(line, "Missing closing \'."));
                    }

                    if (requiresClosingDoubleQuote)
                    {
                        return(Unsupported(line, "Missing closing \"."));
                    }

                    return(Unsupported(line, "Missing ':'. If this is a multiline property, use |"));
                }
                line._idx++;
            }
            line.MaybeEat(':'); // skip colon.

            // Prop name could have spaces, but no colons.
            var propName = line._line.Substring(indentLen, line._idx - indentLen - 1).Trim();

            if (requiresClosingDoubleQuote)
            {
                propName = propName.Replace("\\\"", "\"").Trim('"');
            }

            // If it's a property, must have at least 1 space.
            // If it's start object, then ignore all spaces.

            int iSpaces = 0;

            while (line.MaybeEat(' '))
            {
                iSpaces++; // skip optional spaces
            }

            if (line.Current == 0) // EOL
            {
                var error = _currentIndent.Peek().CheckDuplicate(propName, _currentLine);
                if (error != null)
                {
                    error.Span = Loc(line);
                    return(error);
                }

                // New Object.
                // Next line must begin an indent.
                _currentIndent.Push(new Indent {
                    _lineStart      = _currentLine,
                    _oldIndentLevel = indentLen
                                      // newIndentLevel will be set at next line
                });

                MoveNextLine();
                return(YamlToken.NewStartObj(Loc(startColumn, line), propName));
            }

            if (line.Current == '#')
            {
                return(UnsupportedComment(line));
            }

            if (iSpaces == 0)
            {
                return(Error(line, "Must have at least 1 space after colon."));
            }


            // Escape must be

            string value = null;

            if (line.MaybeEat('='))
            {
                // Single line. Property doesn't include \n at end.
                value = line.RestOfLine;

                if (value.IndexOf('#') >= 0)
                {
                    return(UnsupportedComment(line));
                }

                MoveNextLine();
            }
            else if ((line.Current == '\"') || (line.Current == '\''))
            {
                // These are common YAml sequences, but extremely problematic and could be user error.
                // Disallow them and force the user to explicit.
                // Is "hello" a string or identifer?
                //    Foo: "Hello"
                //
                // Instead, have the user write:
                //    Foo: ="Hello"  // String
                //    Foo: =Hello    // identifier
                //    Foo: |
                //      ="Hello"     // string
                return(Unsupported(line, "Quote is not a supported escape sequence. Use = or |"));
            }
            else if (line.MaybeEat('>'))
            {
                // Unsupported Multiline escape.
                return(Unsupported(line, "> is not a supported multiline escape. Use |"));
            }
            else if (line.MaybeEat('|'))
            {
                // Multiline
                int multilineMode;
                if (line.MaybeEat('-'))
                {
                    multilineMode = 0; // 0 newlines at end
                }
                else if (line.MaybeEat('+'))
                {
                    multilineMode = 2; // 1+ newlines.
                }
                else
                {
                    multilineMode = 1; // exactly 1 newline at end.
                }

                iSpaces = 0;
                while (line.MaybeEat(' '))
                {
                    iSpaces++; // skip optional spaces
                }

                if (line.Current == '#')
                {
                    return(UnsupportedComment(line));
                }
                else if (line.Current != 0) // EOL, catch all error.
                {
                    return(Error(line, "Content for | escape must start on next line."));
                }

                MoveNextLine();
                value = ReadMultiline(multilineMode);

                if (value.Length == 0)
                {
                    return(Unsupported(line, "Can't have empty multiline expressions."));
                }

                if (value[0] != '=')
                {
                    return(Unsupported(line, "Property value must start with an '='"));
                }

                value = value.Substring(1); // move past '='
            }
            else
            {
                // Warn on legal yaml escapes (>) that we don't support in our subset here.
                return(Error(line, "Expected either '=' for a single line expression or '|' to begin a multiline expression"));
            }

            {
                var error = _currentIndent.Peek().CheckDuplicate(propName, _currentLine);
                if (error != null)
                {
                    error.Span = Loc(line);
                    return(error);
                }
            }

            int endIndex = line._line.Length + 1;

            return(YamlToken.NewProperty(LocWorker(startColumn, endIndex), propName, value));
        }