using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.IO; | |
namespace Google.ProtocolBuffers.Serialization | |
{ | |
/// <summary> | |
/// JSon Tokenizer used by JsonFormatReader | |
/// </summary> | |
internal abstract class JsonCursor | |
{ | |
public enum JsType | |
{ | |
String, | |
Number, | |
Object, | |
Array, | |
True, | |
False, | |
Null | |
} | |
#region Buffering implementations | |
private class JsonStreamCursor : JsonCursor | |
{ | |
private readonly byte[] _buffer; | |
private int _bufferPos; | |
private readonly Stream _input; | |
public JsonStreamCursor(Stream input) | |
{ | |
_input = input; | |
_next = _input.ReadByte(); | |
} | |
public JsonStreamCursor(byte[] input) | |
{ | |
_input = null; | |
_buffer = input; | |
_next = _buffer[_bufferPos]; | |
} | |
protected override int Peek() | |
{ | |
if (_input != null) | |
{ | |
return _next; | |
} | |
else if (_bufferPos < _buffer.Length) | |
{ | |
return _buffer[_bufferPos]; | |
} | |
else | |
{ | |
return -1; | |
} | |
} | |
protected override int Read() | |
{ | |
if (_input != null) | |
{ | |
int result = _next; | |
_next = _input.ReadByte(); | |
return result; | |
} | |
else if (_bufferPos < _buffer.Length) | |
{ | |
return _buffer[_bufferPos++]; | |
} | |
else | |
{ | |
return -1; | |
} | |
} | |
} | |
private class JsonTextCursor : JsonCursor | |
{ | |
private readonly char[] _buffer; | |
private int _bufferPos; | |
private readonly TextReader _input; | |
public JsonTextCursor(char[] input) | |
{ | |
_input = null; | |
_buffer = input; | |
_bufferPos = 0; | |
_next = Peek(); | |
} | |
public JsonTextCursor(TextReader input) | |
{ | |
_input = input; | |
_next = Peek(); | |
} | |
protected override int Peek() | |
{ | |
if (_input != null) | |
{ | |
return _input.Peek(); | |
} | |
else if (_bufferPos < _buffer.Length) | |
{ | |
return _buffer[_bufferPos]; | |
} | |
else | |
{ | |
return -1; | |
} | |
} | |
protected override int Read() | |
{ | |
if (_input != null) | |
{ | |
return _input.Read(); | |
} | |
else if (_bufferPos < _buffer.Length) | |
{ | |
return _buffer[_bufferPos++]; | |
} | |
else | |
{ | |
return -1; | |
} | |
} | |
} | |
#endregion | |
protected int _next; | |
private int _lineNo, _linePos; | |
public static JsonCursor CreateInstance(byte[] input) | |
{ | |
return new JsonStreamCursor(input); | |
} | |
public static JsonCursor CreateInstance(Stream input) | |
{ | |
return new JsonStreamCursor(input); | |
} | |
public static JsonCursor CreateInstance(string input) | |
{ | |
return new JsonTextCursor(input.ToCharArray()); | |
} | |
public static JsonCursor CreateInstance(TextReader input) | |
{ | |
return new JsonTextCursor(input); | |
} | |
protected JsonCursor() | |
{ | |
_lineNo = 1; | |
_linePos = 0; | |
} | |
/// <summary>Returns the next character without actually 'reading' it</summary> | |
protected abstract int Peek(); | |
/// <summary>Reads the next character in the input</summary> | |
protected abstract int Read(); | |
public Char NextChar | |
{ | |
get | |
{ | |
SkipWhitespace(); | |
return (char) _next; | |
} | |
} | |
#region Assert(...) | |
[DebuggerNonUserCode] | |
private string CharDisplay(int ch) | |
{ | |
return ch == -1 | |
? "EOF" | |
: (ch > 32 && ch < 127) | |
? String.Format("'{0}'", (char) ch) | |
: String.Format("'\\u{0:x4}'", ch); | |
} | |
[DebuggerNonUserCode] | |
private void Assert(bool cond, char expected) | |
{ | |
if (!cond) | |
{ | |
throw new FormatException( | |
String.Format(FrameworkPortability.InvariantCulture, | |
"({0}:{1}) error: Unexpected token {2}, expected: {3}.", | |
_lineNo, _linePos, | |
CharDisplay(_next), | |
CharDisplay(expected) | |
)); | |
} | |
} | |
[DebuggerNonUserCode] | |
public void Assert(bool cond, string message) | |
{ | |
if (!cond) | |
{ | |
throw new FormatException( | |
String.Format(FrameworkPortability.InvariantCulture, | |
"({0},{1}) error: {2}", _lineNo, _linePos, message)); | |
} | |
} | |
[DebuggerNonUserCode] | |
public void Assert(bool cond, string format, params object[] args) | |
{ | |
if (!cond) | |
{ | |
if (args != null && args.Length > 0) | |
{ | |
format = String.Format(format, args); | |
} | |
throw new FormatException( | |
String.Format(FrameworkPortability.InvariantCulture, | |
"({0},{1}) error: {2}", _lineNo, _linePos, format)); | |
} | |
} | |
#endregion | |
private char ReadChar() | |
{ | |
int ch = Read(); | |
Assert(ch != -1, "Unexpected end of file."); | |
if (ch == '\n') | |
{ | |
_lineNo++; | |
_linePos = 0; | |
} | |
else if (ch != '\r') | |
{ | |
_linePos++; | |
} | |
_next = Peek(); | |
return (char) ch; | |
} | |
public void Consume(char ch) | |
{ | |
Assert(TryConsume(ch), ch); | |
} | |
public bool TryConsume(char ch) | |
{ | |
SkipWhitespace(); | |
if (_next == ch) | |
{ | |
ReadChar(); | |
return true; | |
} | |
return false; | |
} | |
public void Consume(string sequence) | |
{ | |
SkipWhitespace(); | |
foreach (char ch in sequence) | |
{ | |
Assert(ch == ReadChar(), "Expected token '{0}'.", sequence); | |
} | |
} | |
public void SkipWhitespace() | |
{ | |
int chnext = _next; | |
while (chnext != -1) | |
{ | |
if (!Char.IsWhiteSpace((char) chnext)) | |
{ | |
break; | |
} | |
ReadChar(); | |
chnext = _next; | |
} | |
} | |
public string ReadString() | |
{ | |
SkipWhitespace(); | |
Consume('"'); | |
List<Char> sb = new List<char>(100); | |
while (_next != '"') | |
{ | |
if (_next == '\\') | |
{ | |
Consume('\\'); //skip the escape | |
char ch = ReadChar(); | |
switch (ch) | |
{ | |
case 'b': | |
sb.Add('\b'); | |
break; | |
case 'f': | |
sb.Add('\f'); | |
break; | |
case 'n': | |
sb.Add('\n'); | |
break; | |
case 'r': | |
sb.Add('\r'); | |
break; | |
case 't': | |
sb.Add('\t'); | |
break; | |
case 'u': | |
{ | |
string hex = new string(new char[] {ReadChar(), ReadChar(), ReadChar(), ReadChar()}); | |
int result; | |
Assert( | |
FrameworkPortability.TryParseInt32(hex, NumberStyles.AllowHexSpecifier, FrameworkPortability.InvariantCulture, | |
out result), | |
"Expected a 4-character hex specifier."); | |
sb.Add((char) result); | |
break; | |
} | |
default: | |
sb.Add(ch); | |
break; | |
} | |
} | |
else | |
{ | |
Assert(_next != '\n' && _next != '\r' && _next != '\f' && _next != -1, '"'); | |
sb.Add(ReadChar()); | |
} | |
} | |
Consume('"'); | |
return new String(sb.ToArray()); | |
} | |
public string ReadNumber() | |
{ | |
SkipWhitespace(); | |
List<Char> sb = new List<char>(24); | |
if (_next == '-') | |
{ | |
sb.Add(ReadChar()); | |
} | |
Assert(_next >= '0' && _next <= '9', "Expected a numeric type."); | |
while ((_next >= '0' && _next <= '9') || _next == '.') | |
{ | |
sb.Add(ReadChar()); | |
} | |
if (_next == 'e' || _next == 'E') | |
{ | |
sb.Add(ReadChar()); | |
if (_next == '-' || _next == '+') | |
{ | |
sb.Add(ReadChar()); | |
} | |
Assert(_next >= '0' && _next <= '9', "Expected a numeric type."); | |
while (_next >= '0' && _next <= '9') | |
{ | |
sb.Add(ReadChar()); | |
} | |
} | |
return new String(sb.ToArray()); | |
} | |
public JsType ReadVariant(out object value) | |
{ | |
SkipWhitespace(); | |
switch (_next) | |
{ | |
case 'n': | |
Consume("null"); | |
value = null; | |
return JsType.Null; | |
case 't': | |
Consume("true"); | |
value = true; | |
return JsType.True; | |
case 'f': | |
Consume("false"); | |
value = false; | |
return JsType.False; | |
case '"': | |
value = ReadString(); | |
return JsType.String; | |
case '{': | |
{ | |
Consume('{'); | |
while (NextChar != '}') | |
{ | |
ReadString(); | |
Consume(':'); | |
object tmp; | |
ReadVariant(out tmp); | |
if (!TryConsume(',')) | |
{ | |
break; | |
} | |
} | |
Consume('}'); | |
value = null; | |
return JsType.Object; | |
} | |
case '[': | |
{ | |
Consume('['); | |
List<object> values = new List<object>(); | |
while (NextChar != ']') | |
{ | |
object tmp; | |
ReadVariant(out tmp); | |
values.Add(tmp); | |
if (!TryConsume(',')) | |
{ | |
break; | |
} | |
} | |
Consume(']'); | |
value = values.ToArray(); | |
return JsType.Array; | |
} | |
default: | |
if ((_next >= '0' && _next <= '9') || _next == '-') | |
{ | |
value = ReadNumber(); | |
return JsType.Number; | |
} | |
Assert(false, "Expected a value."); | |
throw new FormatException(); | |
} | |
} | |
} | |
} |