JsonTextCursor.cs 13.1 KB
Newer Older
1 2
using System;
using System.Collections.Generic;
3
using System.Diagnostics;
4 5 6 7 8 9 10 11
using System.Globalization;
using System.IO;

namespace Google.ProtocolBuffers.Serialization
{
    /// <summary>
    /// JSon Tokenizer used by JsonFormatReader
    /// </summary>
12
    internal abstract class JsonCursor
13
    {
14 15 16 17 18 19 20 21 22 23
        public enum JsType
        {
            String,
            Number,
            Object,
            Array,
            True,
            False,
            Null
        }
24

25
        #region Buffering implementations
26 27

        private class JsonStreamCursor : JsonCursor
28
        {
29 30 31
            private readonly byte[] _buffer;
            private int _bufferPos;
            private readonly Stream _input;
32

33 34 35 36 37
            public JsonStreamCursor(Stream input)
            {
                _input = input;
                _next = _input.ReadByte();
            }
38

39 40 41 42 43 44 45 46 47 48
            public JsonStreamCursor(byte[] input)
            {
                _input = null;
                _buffer = input;
                _next = _buffer[_bufferPos];
            }

            protected override int Peek()
            {
                if (_input != null)
49
                {
50
                    return _next;
51
                }
52
                else if (_bufferPos < _buffer.Length)
53
                {
54
                    return _buffer[_bufferPos];
55
                }
56
                else
57
                {
58
                    return -1;
59
                }
60 61 62 63 64 65 66 67 68 69 70
            }

            protected override int Read()
            {
                if (_input != null)
                {
                    int result = _next;
                    _next = _input.ReadByte();
                    return result;
                }
                else if (_bufferPos < _buffer.Length)
71
                {
72
                    return _buffer[_bufferPos++];
73
                }
74
                else
75
                {
76
                    return -1;
77
                }
78
            }
79 80
        }

81
        private class JsonTextCursor : JsonCursor
82
        {
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
            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)
104
                {
105
                    return _input.Peek();
106
                }
107
                else if (_bufferPos < _buffer.Length)
108
                {
109
                    return _buffer[_bufferPos];
110
                }
111
                else
112
                {
113
                    return -1;
114
                }
115 116 117 118 119
            }

            protected override int Read()
            {
                if (_input != null)
120
                {
121
                    return _input.Read();
122
                }
123
                else if (_bufferPos < _buffer.Length)
124
                {
125
                    return _buffer[_bufferPos++];
126
                }
127
                else
128
                {
129
                    return -1;
130
                }
131
            }
132
        }
133

134 135 136 137 138
        #endregion

        protected int _next;
        private int _lineNo, _linePos;

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
        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);
        }
158

159
        protected JsonCursor()
160
        {
161 162
            _lineNo = 1;
            _linePos = 0;
163
        }
164

165 166
        /// <summary>Returns the next character without actually 'reading' it</summary>
        protected abstract int Peek();
167

168 169
        /// <summary>Reads the next character in the input</summary>
        protected abstract int Read();
170

171 172 173 174 175 176 177 178
        public Char NextChar
        {
            get
            {
                SkipWhitespace();
                return (char) _next;
            }
        }
179 180

        #region Assert(...)
181 182

        [DebuggerNonUserCode]
183 184
        private string CharDisplay(int ch)
        {
185 186 187 188 189
            return ch == -1
                       ? "EOF"
                       : (ch > 32 && ch < 127)
                             ? String.Format("'{0}'", (char) ch)
                             : String.Format("'\\u{0:x4}'", ch);
190
        }
191 192

        [DebuggerNonUserCode]
193 194 195 196 197
        private void Assert(bool cond, char expected)
        {
            if (!cond)
            {
                throw new FormatException(
198
                    String.Format(FrameworkPortability.InvariantCulture,
199
                                  "({0}:{1}) error: Unexpected token {2}, expected: {3}.",
200
                                  _lineNo, _linePos,
201 202 203 204 205
                                  CharDisplay(_next),
                                  CharDisplay(expected)
                        ));
            }
        }
206 207

        [DebuggerNonUserCode]
208 209 210 211 212
        public void Assert(bool cond, string message)
        {
            if (!cond)
            {
                throw new FormatException(
213
                    String.Format(FrameworkPortability.InvariantCulture,
214 215 216
                                  "({0},{1}) error: {2}", _lineNo, _linePos, message));
            }
        }
217 218

        [DebuggerNonUserCode]
219 220 221 222 223
        public void Assert(bool cond, string format, params object[] args)
        {
            if (!cond)
            {
                if (args != null && args.Length > 0)
224
                {
225
                    format = String.Format(format, args);
226
                }
227
                throw new FormatException(
228
                    String.Format(FrameworkPortability.InvariantCulture,
229 230 231
                                  "({0},{1}) error: {2}", _lineNo, _linePos, format));
            }
        }
232

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
        #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();
249 250 251 252 253 254
            return (char) ch;
        }

        public void Consume(char ch)
        {
            Assert(TryConsume(ch), ch);
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
        }

        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)
273
            {
274
                Assert(ch == ReadChar(), "Expected token '{0}'.", sequence);
275
            }
276 277 278 279 280 281 282
        }

        public void SkipWhitespace()
        {
            int chnext = _next;
            while (chnext != -1)
            {
283 284
                if (!Char.IsWhiteSpace((char) chnext))
                {
285
                    break;
286
                }
287 288 289 290 291 292 293 294 295
                ReadChar();
                chnext = _next;
            }
        }

        public string ReadString()
        {
            SkipWhitespace();
            Consume('"');
296
            List<Char> sb = new List<char>(100);
297 298 299 300
            while (_next != '"')
            {
                if (_next == '\\')
                {
301
                    Consume('\\'); //skip the escape
302 303 304
                    char ch = ReadChar();
                    switch (ch)
                    {
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
                        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;
320 321
                        case 'u':
                            {
322
                                string hex = new string(new char[] {ReadChar(), ReadChar(), ReadChar(), ReadChar()});
323
                                int result;
324
                                Assert(
325
                                    FrameworkPortability.TryParseInt32(hex, NumberStyles.AllowHexSpecifier, FrameworkPortability.InvariantCulture,
326 327 328
                                                 out result),
                                    "Expected a 4-character hex specifier.");
                                sb.Add((char) result);
329 330 331
                                break;
                            }
                        default:
332 333
                            sb.Add(ch);
                            break;
334 335 336 337 338
                    }
                }
                else
                {
                    Assert(_next != '\n' && _next != '\r' && _next != '\f' && _next != -1, '"');
339
                    sb.Add(ReadChar());
340 341 342
                }
            }
            Consume('"');
343
            return new String(sb.ToArray());
344 345 346 347 348
        }

        public string ReadNumber()
        {
            SkipWhitespace();
349
            List<Char> sb = new List<char>(24);
350
            if (_next == '-')
351
            {
352
                sb.Add(ReadChar());
353
            }
354 355
            Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
            while ((_next >= '0' && _next <= '9') || _next == '.')
356
            {
357
                sb.Add(ReadChar());
358
            }
359 360
            if (_next == 'e' || _next == 'E')
            {
361
                sb.Add(ReadChar());
362
                if (_next == '-' || _next == '+')
363
                {
364
                    sb.Add(ReadChar());
365
                }
366 367
                Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
                while (_next >= '0' && _next <= '9')
368
                {
369
                    sb.Add(ReadChar());
370
                }
371
            }
372
            return new String(sb.ToArray());
373 374 375 376 377 378 379
        }

        public JsType ReadVariant(out object value)
        {
            SkipWhitespace();
            switch (_next)
            {
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
                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;
395 396 397 398 399 400 401 402 403 404
                case '{':
                    {
                        Consume('{');
                        while (NextChar != '}')
                        {
                            ReadString();
                            Consume(':');
                            object tmp;
                            ReadVariant(out tmp);
                            if (!TryConsume(','))
405
                            {
406
                                break;
407
                            }
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
                        }
                        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(','))
423
                            {
424
                                break;
425
                            }
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
                        }
                        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();
            }
        }
    }
}