blob: a2a5b73d97d9693226980617e381c69f8cf38e75 [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('"');
159 StringBuilder sb = new StringBuilder();
160 while (_next != '"')
161 {
162 if (_next == '\\')
163 {
164 Consume('\\');//skip the escape
165 char ch = ReadChar();
166 switch (ch)
167 {
168 case 'b': sb.Append('\b'); break;
169 case 'f': sb.Append('\f'); break;
170 case 'n': sb.Append('\n'); break;
171 case 'r': sb.Append('\r'); break;
172 case 't': sb.Append('\t'); break;
173 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.");
179 sb.Append((char)result);
180 break;
181 }
182 default:
183 sb.Append(ch); break;
184 }
185 }
186 else
187 {
188 Assert(_next != '\n' && _next != '\r' && _next != '\f' && _next != -1, '"');
189 sb.Append(ReadChar());
190 }
191 }
192 Consume('"');
193 return sb.ToString();
194 }
195
196 public string ReadNumber()
197 {
198 SkipWhitespace();
199
200 StringBuilder sb = new StringBuilder();
201 if (_next == '-')
202 sb.Append(ReadChar());
203 Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
204 while ((_next >= '0' && _next <= '9') || _next == '.')
205 sb.Append(ReadChar());
206 if (_next == 'e' || _next == 'E')
207 {
208 sb.Append(ReadChar());
209 if (_next == '-' || _next == '+')
210 sb.Append(ReadChar());
211 Assert(_next >= '0' && _next <= '9', "Expected a numeric type.");
212 while (_next >= '0' && _next <= '9')
213 sb.Append(ReadChar());
214 }
215 return sb.ToString();
216 }
217
218 public JsType ReadVariant(out object value)
219 {
220 SkipWhitespace();
221 switch (_next)
222 {
223 case 'n': Consume("null"); value = null; return JsType.Null;
224 case 't': Consume("true"); value = true; return JsType.True;
225 case 'f': Consume("false"); value = false; return JsType.False;
226 case '"': value = ReadString(); return JsType.String;
227 case '{':
228 {
229 Consume('{');
230 while (NextChar != '}')
231 {
232 ReadString();
233 Consume(':');
234 object tmp;
235 ReadVariant(out tmp);
236 if (!TryConsume(','))
237 break;
238 }
239 Consume('}');
240 value = null;
241 return JsType.Object;
242 }
243 case '[':
244 {
245 Consume('[');
246 List<object> values = new List<object>();
247 while (NextChar != ']')
248 {
249 object tmp;
250 ReadVariant(out tmp);
251 values.Add(tmp);
252 if (!TryConsume(','))
253 break;
254 }
255 Consume(']');
256 value = values.ToArray();
257 return JsType.Array;
258 }
259 default:
260 if ((_next >= '0' && _next <= '9') || _next == '-')
261 {
262 value = ReadNumber();
263 return JsType.Number;
264 }
265 Assert(false, "Expected a value.");
266 throw new FormatException();
267 }
268 }
269 }
270}