mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
4.9 KiB
212 lines
4.9 KiB
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. |
|
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. |
|
|
|
namespace LightJson.Serialization |
|
{ |
|
using System.IO; |
|
using ErrorType = JsonParseException.ErrorType; |
|
|
|
/// <summary> |
|
/// Represents a text scanner that reads one character at a time. |
|
/// </summary> |
|
internal sealed class TextScanner |
|
{ |
|
private TextReader reader; |
|
private TextPosition position; |
|
|
|
/// <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) |
|
{ |
|
this.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 { |
|
return this.position; |
|
} |
|
} |
|
|
|
/// <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)this.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 = this.reader.Peek(); |
|
|
|
if (next == -1 && throwAtEndOfFile) { |
|
throw new JsonParseException( |
|
ErrorType.IncompleteMessage, |
|
this.position); |
|
} else { |
|
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 = this.reader.Read(); |
|
|
|
if (next == -1) { |
|
throw new JsonParseException( |
|
ErrorType.IncompleteMessage, |
|
this.position); |
|
} else { |
|
if (next == '\n') { |
|
this.position.Line += 1; |
|
this.position.Column = 0; |
|
} else { |
|
this.position.Column += 1; |
|
} |
|
|
|
return (char)next; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Advances the scanner to next non-whitespace character. |
|
/// </summary> |
|
public void SkipWhitespace() |
|
{ |
|
while (true) { |
|
char next = this.Peek(); |
|
if (char.IsWhiteSpace(next)) { |
|
this.Read(); |
|
continue; |
|
} else if (next == '/') { |
|
this.SkipComment(); |
|
continue; |
|
} else { |
|
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 = this.position; |
|
if (this.Read() != next) { |
|
throw new JsonParseException( |
|
string.Format("Parser expected '{0}'", next), |
|
ErrorType.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) { |
|
this.Assert(next[i]); |
|
} |
|
} |
|
|
|
private void SkipComment() |
|
{ |
|
// First character is the first slash |
|
this.Read(); |
|
switch (this.Peek()) { |
|
case '/': |
|
this.SkipLineComment(); |
|
return; |
|
|
|
case '*': |
|
this.SkipBlockComment(); |
|
return; |
|
|
|
default: |
|
throw new JsonParseException( |
|
string.Format("Parser expected '{0}'", this.Peek()), |
|
ErrorType.InvalidOrUnexpectedCharacter, |
|
this.position); |
|
} |
|
} |
|
|
|
private void SkipLineComment() |
|
{ |
|
// First character is the second '/' of the opening '//' |
|
this.Read(); |
|
|
|
while (true) { |
|
switch (this.reader.Peek()) { |
|
case '\n': |
|
// Reached the end of the line |
|
this.Read(); |
|
return; |
|
|
|
case -1: |
|
// Reached the end of the file |
|
return; |
|
|
|
default: |
|
this.Read(); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
private void SkipBlockComment() |
|
{ |
|
// First character is the '*' of the opening '/*' |
|
this.Read(); |
|
|
|
bool foundStar = false; |
|
while (true) { |
|
switch (this.reader.Peek()) { |
|
case '*': |
|
this.Read(); |
|
foundStar = true; |
|
continue; |
|
|
|
case '/': |
|
this.Read(); |
|
if (foundStar) { |
|
return; |
|
} else { |
|
foundStar = false; |
|
continue; |
|
} |
|
|
|
case -1: |
|
// Reached the end of the file |
|
return; |
|
|
|
default: |
|
this.Read(); |
|
foundStar = false; |
|
continue; |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|