blob: 6a8652c874bc7fa46d9f754ee580a0b4f57524e4 [file] [log] [blame]
csharptestafe844b2011-06-10 16:03:22 -05001using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.IO;
5using System.Text;
6
7namespace Google.ProtocolBuffers.Serialization
8{
9 /// <summary>
10 /// JSon Tokenizer used by JsonFormatReader
11 /// </summary>
12 class JsonTextCursor
13 {
14 public enum JsType { String, Number, Object, Array, True, False, Null }
15
16 private readonly char[] _buffer;
17 private int _bufferPos;
18 private readonly TextReader _input;
19 private int _lineNo, _linePos;
20
21 public JsonTextCursor(char[] input)
22 {
23 _input = null;
24 _buffer = input;
25 _bufferPos = 0;
26 _next = Peek();
27 _lineNo = 1;
28 }
29
30 public JsonTextCursor(TextReader input)
31 {
32 _input = input;
33 _next = Peek();
34 _lineNo = 1;
35 }
36
37 private int Peek()
38 {
39 if (_input != null)
40 return _input.Peek();
41 else if (_bufferPos < _buffer.Length)
42 return _buffer[_bufferPos];
43 else
44 return -1;
45 }
46
47 private int Read()
48 {
49 if (_input != null)
50 return _input.Read();
51 else if (_bufferPos < _buffer.Length)
52 return _buffer[_bufferPos++];
53 else
54 return -1;
55 }
56
57 int _next;
58 public Char NextChar { get { SkipWhitespace(); return (char)_next; } }
59
60 #region Assert(...)
61 [System.Diagnostics.DebuggerNonUserCode]
62 private string CharDisplay(int ch)
63 {
64 return ch == -1 ? "EOF" :
65 (ch > 32 && ch < 127) ? String.Format("'{0}'", (char)ch) :
66 String.Format("'\\u{0:x4}'", ch);
67 }
68 [System.Diagnostics.DebuggerNonUserCode]
69 private void Assert(bool cond, char expected)
70 {
71 if (!cond)
72 {
73 throw new FormatException(
74 String.Format(CultureInfo.InvariantCulture,
75 "({0}:{1}) error: Unexpected token {2}, expected: {3}.",
76 _lineNo, _linePos,
77 CharDisplay(_next),
78 CharDisplay(expected)
79 ));
80 }
81 }
82 [System.Diagnostics.DebuggerNonUserCode]
83 public void Assert(bool cond, string message)
84 {
85 if (!cond)
86 {
87 throw new FormatException(
88 String.Format(CultureInfo.InvariantCulture,
89 "({0},{1}) error: {2}", _lineNo, _linePos, message));
90 }
91 }
92 [System.Diagnostics.DebuggerNonUserCode]
93 public void Assert(bool cond, string format, params object[] args)
94 {
95 if (!cond)
96 {
97 if (args != null && args.Length > 0)
98 format = String.Format(format, args);
99 throw new FormatException(
100 String.Format(CultureInfo.InvariantCulture,
101 "({0},{1}) error: {2}", _lineNo, _linePos, format));
102 }
103 }
104 #endregion
105
106 private char ReadChar()
107 {
108 int ch = Read();
109 Assert(ch != -1, "Unexpected end of file.");
110 if (ch == '\n')
111 {
112 _lineNo++;
113 _linePos = 0;
114 }
115 else if (ch != '\r')
116 {
117 _linePos++;
118 }
119 _next = Peek();
120 return (char)ch;
121 }
122
123 public void Consume(char ch) { Assert(TryConsume(ch), ch); }
124 public bool TryConsume(char ch)
125 {
126 SkipWhitespace();
127 if (_next == ch)
128 {
129 ReadChar();
130 return true;
131 }
132 return false;
133 }
134
135 public void Consume(string sequence)
136 {
137 SkipWhitespace();
138
139 foreach (char ch in sequence)
140 Assert(ch == ReadChar(), "Expected token '{0}'.", sequence);
141 }
142
143 public void SkipWhitespace()
144 {
145 int chnext = _next;
146 while (chnext != -1)
147 {
148 if (!Char.IsWhiteSpace((char)chnext))
149 break;
150 ReadChar();
151 chnext = _next;
152 }
153 }
154
155 public string ReadString()
156 {
157 SkipWhitespace();
158 Consume('"');
csharptestddb74eb2011-06-10 16:14:51 -0500159 List<Char> sb = new List<char>(100);
csharptestafe844b2011-06-10 16:03:22 -0500160 while (_next != '"')
161 {
162 if (_next == '\\')
163 {
164 Consume('\\');//skip the escape
165 char ch = ReadChar();
166 switch (ch)
167 {
csharptestddb74eb2011-06-10 16:14:51 -0500168 case 'b': sb.Add('\b'); break;
169 case 'f': sb.Add('\f'); break;
170 case 'n': sb.Add('\n'); break;
171 case 'r': sb.Add('\r'); break;
172 case 't': sb.Add('\t'); break;
csharptestafe844b2011-06-10 16:03:22 -0500173 case 'u':
174 {
175 string hex = new string(new char[] { ReadChar(), ReadChar(), ReadChar(), ReadChar() });
176 int result;
177 Assert(int.TryParse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out result),
178 "Expected a 4-character hex specifier.");
csharptestddb74eb2011-06-10 16:14:51 -0500179 sb.Add((char)result);
csharptestafe844b2011-06-10 16:03:22 -0500180 break;
181 }
182 default:
csharptestddb74eb2011-06-10 16:14:51 -0500183 sb.Add(ch); break;
csharptestafe844b2011-06-10 16:03:22 -0500184 }
185 }
186 else
187 {
188 Assert(_next != '\n' && _next != '\r' && _next != '\f' && _next != -1, '"');
csharptestddb74eb2011-06-10 16:14:51 -0500189 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500190 }
191 }
192 Consume('"');
csharptestddb74eb2011-06-10 16:14:51 -0500193 return new String(sb.ToArray());
csharptestafe844b2011-06-10 16:03:22 -0500194 }
195
196 public string ReadNumber()
197 {
198 SkipWhitespace();
csharptestddb74eb2011-06-10 16:14:51 -0500199 List<Char> sb = new List<char>(24);
csharptestafe844b2011-06-10 16:03:22 -0500200 if (_next == '-')
csharptestddb74eb2011-06-10 16:14:51 -0500201 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500202 Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
203 while ((_next >= '0' && _next <= '9') || _next == '.')
csharptestddb74eb2011-06-10 16:14:51 -0500204 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500205 if (_next == 'e' || _next == 'E')
206 {
csharptestddb74eb2011-06-10 16:14:51 -0500207 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500208 if (_next == '-' || _next == '+')
csharptestddb74eb2011-06-10 16:14:51 -0500209 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500210 Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
211 while (_next >= '0' && _next <= '9')
csharptestddb74eb2011-06-10 16:14:51 -0500212 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500213 }
csharptestddb74eb2011-06-10 16:14:51 -0500214 return new String(sb.ToArray());
csharptestafe844b2011-06-10 16:03:22 -0500215 }
216
217 public JsType ReadVariant(out object value)
218 {
219 SkipWhitespace();
220 switch (_next)
221 {
222 case 'n': Consume("null"); value = null; return JsType.Null;
223 case 't': Consume("true"); value = true; return JsType.True;
224 case 'f': Consume("false"); value = false; return JsType.False;
225 case '"': value = ReadString(); return JsType.String;
226 case '{':
227 {
228 Consume('{');
229 while (NextChar != '}')
230 {
231 ReadString();
232 Consume(':');
233 object tmp;
234 ReadVariant(out tmp);
235 if (!TryConsume(','))
236 break;
237 }
238 Consume('}');
239 value = null;
240 return JsType.Object;
241 }
242 case '[':
243 {
244 Consume('[');
245 List<object> values = new List<object>();
246 while (NextChar != ']')
247 {
248 object tmp;
249 ReadVariant(out tmp);
250 values.Add(tmp);
251 if (!TryConsume(','))
252 break;
253 }
254 Consume(']');
255 value = values.ToArray();
256 return JsType.Array;
257 }
258 default:
259 if ((_next >= '0' && _next <= '9') || _next == '-')
260 {
261 value = ReadNumber();
262 return JsType.Number;
263 }
264 Assert(false, "Expected a value.");
265 throw new FormatException();
266 }
267 }
268 }
269}