blob: a0b468f6c20270760e598a5876ad1b65e3be4226 [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>
csharptest7fc785c2011-06-10 23:54:53 -050012 abstract class JsonCursor
csharptestafe844b2011-06-10 16:03:22 -050013 {
14 public enum JsType { String, Number, Object, Array, True, False, Null }
15
csharptest7fc785c2011-06-10 23:54:53 -050016 #region Buffering implementations
17 class JsonStreamCursor : JsonCursor
18 {
19 private readonly byte[] _buffer;
20 private int _bufferPos;
21 private readonly Stream _input;
22
23 public JsonStreamCursor(Stream input)
24 {
25 _input = input;
26 _next = _input.ReadByte();
27 }
28 public JsonStreamCursor(byte[] input)
29 {
30 _input = null;
31 _buffer = input;
32 _next = _buffer[_bufferPos];
33 }
34
35 protected override int Peek()
36 {
37 if (_input != null)
38 return _next;
39 else if (_bufferPos < _buffer.Length)
40 return _buffer[_bufferPos];
41 else
42 return -1;
43 }
44
45 protected override int Read()
46 {
47 if (_input != null)
48 {
49 int result = _next;
50 _next = _input.ReadByte();
51 return result;
52 }
53 else if (_bufferPos < _buffer.Length)
54 return _buffer[_bufferPos++];
55 else
56 return -1;
57 }
58 }
59
60 class JsonTextCursor : JsonCursor
61 {
62 private readonly char[] _buffer;
63 private int _bufferPos;
64 private readonly TextReader _input;
65
66 public JsonTextCursor(char[] input)
67 {
68 _input = null;
69 _buffer = input;
70 _bufferPos = 0;
71 _next = Peek();
72 }
73
74 public JsonTextCursor(TextReader input)
75 {
76 _input = input;
77 _next = Peek();
78 }
79
80 protected override int Peek()
81 {
82 if (_input != null)
83 return _input.Peek();
84 else if (_bufferPos < _buffer.Length)
85 return _buffer[_bufferPos];
86 else
87 return -1;
88 }
89
90 protected override int Read()
91 {
92 if (_input != null)
93 return _input.Read();
94 else if (_bufferPos < _buffer.Length)
95 return _buffer[_bufferPos++];
96 else
97 return -1;
98 }
99 }
100 #endregion
101
102 protected int _next;
csharptestafe844b2011-06-10 16:03:22 -0500103 private int _lineNo, _linePos;
104
csharptest7fc785c2011-06-10 23:54:53 -0500105 public static JsonCursor CreateInstance(byte[] input) { return new JsonStreamCursor(input); }
106 public static JsonCursor CreateInstance(Stream input) { return new JsonStreamCursor(input); }
107 public static JsonCursor CreateInstance(string input) { return new JsonTextCursor(input.ToCharArray()); }
108 public static JsonCursor CreateInstance(TextReader input) { return new JsonTextCursor(input); }
109
110 protected JsonCursor()
csharptestafe844b2011-06-10 16:03:22 -0500111 {
csharptestafe844b2011-06-10 16:03:22 -0500112 _lineNo = 1;
csharptest7fc785c2011-06-10 23:54:53 -0500113 _linePos = 0;
csharptestafe844b2011-06-10 16:03:22 -0500114 }
csharptest7fc785c2011-06-10 23:54:53 -0500115
116 /// <summary>Returns the next character without actually 'reading' it</summary>
117 protected abstract int Peek();
118 /// <summary>Reads the next character in the input</summary>
119 protected abstract int Read();
csharptestafe844b2011-06-10 16:03:22 -0500120
csharptestafe844b2011-06-10 16:03:22 -0500121 public Char NextChar { get { SkipWhitespace(); return (char)_next; } }
122
123 #region Assert(...)
124 [System.Diagnostics.DebuggerNonUserCode]
125 private string CharDisplay(int ch)
126 {
127 return ch == -1 ? "EOF" :
csharptest7fc785c2011-06-10 23:54:53 -0500128 (ch > 32 && ch < 127) ? String.Format("'{0}'", (char)ch) :
129 String.Format("'\\u{0:x4}'", ch);
csharptestafe844b2011-06-10 16:03:22 -0500130 }
131 [System.Diagnostics.DebuggerNonUserCode]
132 private void Assert(bool cond, char expected)
133 {
134 if (!cond)
135 {
136 throw new FormatException(
137 String.Format(CultureInfo.InvariantCulture,
138 "({0}:{1}) error: Unexpected token {2}, expected: {3}.",
139 _lineNo, _linePos,
140 CharDisplay(_next),
141 CharDisplay(expected)
142 ));
143 }
144 }
145 [System.Diagnostics.DebuggerNonUserCode]
146 public void Assert(bool cond, string message)
147 {
148 if (!cond)
149 {
150 throw new FormatException(
151 String.Format(CultureInfo.InvariantCulture,
152 "({0},{1}) error: {2}", _lineNo, _linePos, message));
153 }
154 }
155 [System.Diagnostics.DebuggerNonUserCode]
156 public void Assert(bool cond, string format, params object[] args)
157 {
158 if (!cond)
159 {
160 if (args != null && args.Length > 0)
161 format = String.Format(format, args);
162 throw new FormatException(
163 String.Format(CultureInfo.InvariantCulture,
164 "({0},{1}) error: {2}", _lineNo, _linePos, format));
165 }
166 }
167 #endregion
168
169 private char ReadChar()
170 {
171 int ch = Read();
172 Assert(ch != -1, "Unexpected end of file.");
173 if (ch == '\n')
174 {
175 _lineNo++;
176 _linePos = 0;
177 }
178 else if (ch != '\r')
179 {
180 _linePos++;
181 }
182 _next = Peek();
183 return (char)ch;
184 }
185
186 public void Consume(char ch) { Assert(TryConsume(ch), ch); }
187 public bool TryConsume(char ch)
188 {
189 SkipWhitespace();
190 if (_next == ch)
191 {
192 ReadChar();
193 return true;
194 }
195 return false;
196 }
197
198 public void Consume(string sequence)
199 {
200 SkipWhitespace();
201
202 foreach (char ch in sequence)
203 Assert(ch == ReadChar(), "Expected token '{0}'.", sequence);
204 }
205
206 public void SkipWhitespace()
207 {
208 int chnext = _next;
209 while (chnext != -1)
210 {
211 if (!Char.IsWhiteSpace((char)chnext))
212 break;
213 ReadChar();
214 chnext = _next;
215 }
216 }
217
218 public string ReadString()
219 {
220 SkipWhitespace();
221 Consume('"');
csharptestddb74eb2011-06-10 16:14:51 -0500222 List<Char> sb = new List<char>(100);
csharptestafe844b2011-06-10 16:03:22 -0500223 while (_next != '"')
224 {
225 if (_next == '\\')
226 {
227 Consume('\\');//skip the escape
228 char ch = ReadChar();
229 switch (ch)
230 {
csharptestddb74eb2011-06-10 16:14:51 -0500231 case 'b': sb.Add('\b'); break;
232 case 'f': sb.Add('\f'); break;
233 case 'n': sb.Add('\n'); break;
234 case 'r': sb.Add('\r'); break;
235 case 't': sb.Add('\t'); break;
csharptestafe844b2011-06-10 16:03:22 -0500236 case 'u':
237 {
238 string hex = new string(new char[] { ReadChar(), ReadChar(), ReadChar(), ReadChar() });
239 int result;
240 Assert(int.TryParse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out result),
241 "Expected a 4-character hex specifier.");
csharptestddb74eb2011-06-10 16:14:51 -0500242 sb.Add((char)result);
csharptestafe844b2011-06-10 16:03:22 -0500243 break;
244 }
245 default:
csharptestddb74eb2011-06-10 16:14:51 -0500246 sb.Add(ch); break;
csharptestafe844b2011-06-10 16:03:22 -0500247 }
248 }
249 else
250 {
251 Assert(_next != '\n' && _next != '\r' && _next != '\f' && _next != -1, '"');
csharptestddb74eb2011-06-10 16:14:51 -0500252 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500253 }
254 }
255 Consume('"');
csharptestddb74eb2011-06-10 16:14:51 -0500256 return new String(sb.ToArray());
csharptestafe844b2011-06-10 16:03:22 -0500257 }
258
259 public string ReadNumber()
260 {
261 SkipWhitespace();
csharptestddb74eb2011-06-10 16:14:51 -0500262 List<Char> sb = new List<char>(24);
csharptestafe844b2011-06-10 16:03:22 -0500263 if (_next == '-')
csharptestddb74eb2011-06-10 16:14:51 -0500264 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500265 Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
266 while ((_next >= '0' && _next <= '9') || _next == '.')
csharptestddb74eb2011-06-10 16:14:51 -0500267 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500268 if (_next == 'e' || _next == 'E')
269 {
csharptestddb74eb2011-06-10 16:14:51 -0500270 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500271 if (_next == '-' || _next == '+')
csharptestddb74eb2011-06-10 16:14:51 -0500272 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500273 Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
274 while (_next >= '0' && _next <= '9')
csharptestddb74eb2011-06-10 16:14:51 -0500275 sb.Add(ReadChar());
csharptestafe844b2011-06-10 16:03:22 -0500276 }
csharptestddb74eb2011-06-10 16:14:51 -0500277 return new String(sb.ToArray());
csharptestafe844b2011-06-10 16:03:22 -0500278 }
279
280 public JsType ReadVariant(out object value)
281 {
282 SkipWhitespace();
283 switch (_next)
284 {
285 case 'n': Consume("null"); value = null; return JsType.Null;
286 case 't': Consume("true"); value = true; return JsType.True;
287 case 'f': Consume("false"); value = false; return JsType.False;
288 case '"': value = ReadString(); return JsType.String;
289 case '{':
290 {
291 Consume('{');
292 while (NextChar != '}')
293 {
294 ReadString();
295 Consume(':');
296 object tmp;
297 ReadVariant(out tmp);
298 if (!TryConsume(','))
299 break;
300 }
301 Consume('}');
302 value = null;
303 return JsType.Object;
304 }
305 case '[':
306 {
307 Consume('[');
308 List<object> values = new List<object>();
309 while (NextChar != ']')
310 {
311 object tmp;
312 ReadVariant(out tmp);
313 values.Add(tmp);
314 if (!TryConsume(','))
315 break;
316 }
317 Consume(']');
318 value = values.ToArray();
319 return JsType.Array;
320 }
321 default:
322 if ((_next >= '0' && _next <= '9') || _next == '-')
323 {
324 value = ReadNumber();
325 return JsType.Number;
326 }
327 Assert(false, "Expected a value.");
328 throw new FormatException();
329 }
330 }
331 }
332}