using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; namespace Google.ProtocolBuffers.Serialization { /// /// JSon Tokenizer used by JsonFormatReader /// 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; } /// Returns the next character without actually 'reading' it protected abstract int Peek(); /// Reads the next character in the input 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 sb = new List(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 sb = new List(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 values = new List(); 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(); } } } }