blob: 19c45af753818aaf333c0729a6408c5fa31fc2c0 [file] [log] [blame]
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();
}
}
}
}