aboutsummaryrefslogblamecommitdiff
path: root/src/ProtocolBuffers/Serialization/JsonTextCursor.cs
blob: a0b468f6c20270760e598a5876ad1b65e3be4226 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                                                
                              


                                                                                

                                            
          


                                             
 


































                                                      

          
                                          
          




































                                                      
          








                                                                                                                  
 
                               
          

                          
          




                                                                                        
 






                                                                               

                                                                           



























































































                                                                                           
                                                 







                                                    




                                                       





                                                                                                                                    
                                                      


                                       
                                               




                                                                                                 
                                        


                          
                                             




                                   
                                                
                              
                                    

                                                                              
                                    

                                              
                                    
                                                  
                                        

                                                                                  
                                        
              
                                             






















































                                                                                
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;

namespace Google.ProtocolBuffers.Serialization
{
    /// <summary>
    /// JSon Tokenizer used by JsonFormatReader
    /// </summary>
    abstract class JsonCursor
    {
        public enum JsType { String, Number, Object, Array, True, False, Null }

        #region Buffering implementations
        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;
            }
        }

        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(...)
        [System.Diagnostics.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);
        }
        [System.Diagnostics.DebuggerNonUserCode]
        private void Assert(bool cond, char expected)
        {
            if (!cond)
            {
                throw new FormatException(
                    String.Format(CultureInfo.InvariantCulture,
                                  "({0}:{1}) error: Unexpected token {2}, expected: {3}.",
                                  _lineNo, _linePos, 
                                  CharDisplay(_next),
                                  CharDisplay(expected)
                        ));
            }
        }
        [System.Diagnostics.DebuggerNonUserCode]
        public void Assert(bool cond, string message)
        {
            if (!cond)
            {
                throw new FormatException(
                    String.Format(CultureInfo.InvariantCulture,
                                  "({0},{1}) error: {2}", _lineNo, _linePos, message));
            }
        }
        [System.Diagnostics.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(CultureInfo.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(int.TryParse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.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();
            }
        }
    }
}