| File: ApiLifecycle\Json\TextScanner.cs | Web Access |
| Project: src\src\Analyzers\Microsoft.Analyzers.Local\Microsoft.Analyzers.Local.csproj (Microsoft.Analyzers.Local) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Forked from StyleCop.Analyzers repo. using System.Globalization; using System.IO; namespace Microsoft.Extensions.LocalAnalyzers.Json; /// <summary> /// Represents a text scanner that reads one character at a time. /// </summary> internal sealed class TextScanner { private readonly TextReader _reader; /// <summary> /// Initializes a new instance of the <see cref="TextScanner"/> class. /// </summary> /// <param name="reader">The TextReader to read the text.</param> public TextScanner(TextReader reader) { _reader = reader; } /// <summary> /// Gets the position of the scanner within the text. /// </summary> /// <value>The position of the scanner within the text.</value> public TextPosition Position { get; private set; } /// <summary> /// Reads the next character in the stream without changing the current position. /// </summary> /// <returns>The next character in the stream.</returns> public char Peek() => (char)Peek(throwAtEndOfFile: true); /// <summary> /// Reads the next character in the stream without changing the current position. /// </summary> /// <param name="throwAtEndOfFile"><see langword="true"/> to throw an exception if the end of the file is /// reached; otherwise, <see langword="false"/>.</param> /// <returns>The next character in the stream, or -1 if the end of the file is reached with /// <paramref name="throwAtEndOfFile"/> set to <see langword="false"/>.</returns> public int Peek(bool throwAtEndOfFile) { var next = _reader.Peek(); if (next == -1 && throwAtEndOfFile) { throw new JsonParseException(ParsingError.IncompleteMessage, Position); } return next; } /// <summary> /// Reads the next character in the stream, advancing the text position. /// </summary> /// <returns>The next character in the stream.</returns> public char Read() { var next = _reader.Read(); if (next == -1) { throw new JsonParseException(ParsingError.IncompleteMessage, Position); } else { Position = next == '\n' ? new(0, Position.Line + 1) : new(Position.Column + 1, Position.Line); return (char)next; } } /// <summary> /// Advances the scanner to next non-whitespace character. /// </summary> public void SkipWhitespace() { while (true) { char next = Peek(); if (char.IsWhiteSpace(next)) { _ = Read(); continue; } else if (next == '/') { SkipComment(); continue; } break; } } /// <summary> /// Verifies that the given character matches the next character in the stream. /// If the characters do not match, an exception will be thrown. /// </summary> /// <param name="next">The expected character.</param> public void Assert(char next) { var errorPosition = Position; if (Read() != next) { throw new JsonParseException( string.Format(CultureInfo.InvariantCulture, "Parser expected '{0}'", next), ParsingError.InvalidOrUnexpectedCharacter, errorPosition); } } /// <summary> /// Verifies that the given string matches the next characters in the stream. /// If the strings do not match, an exception will be thrown. /// </summary> /// <param name="next">The expected string.</param> public void Assert(string next) { for (var i = 0; i < next.Length; i += 1) { Assert(next[i]); } } private void SkipComment() { // First character is the first slash _ = Read(); switch (Peek()) { case '/': SkipLineComment(); return; case '*': SkipBlockComment(); return; default: throw new JsonParseException( string.Format(CultureInfo.InvariantCulture, "Parser expected '{0}'", Peek()), ParsingError.InvalidOrUnexpectedCharacter, Position); } } private void SkipLineComment() { // First character is the second '/' of the opening '//' _ = Read(); while (true) { switch (_reader.Peek()) { case '\n': // Reached the end of the line _ = Read(); return; case -1: return; default: _ = Read(); break; } } } private void SkipBlockComment() { // First character is the '*' of the opening '/*' _ = Read(); bool foundStar = false; while (true) { switch (_reader.Peek()) { case '*': _ = Read(); foundStar = true; break; case '/': _ = Read(); if (foundStar) { return; } foundStar = false; break; case -1: return; default: _ = Read(); foundStar = false; break; } } } }