Merge pull request #1155 from steffen-kiess/json-string
[mono.git] / mcs / class / System.ServiceModel.Web / System.Runtime.Serialization.Json / JavaScriptReader.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Globalization;
5 using System.IO;
6 using System.Linq;
7 using System.Text;
8
9 namespace System.Runtime.Serialization.Json
10 {
11         internal class JavaScriptReader
12         {
13                 TextReader r;
14                 int line = 1, column = 0;
15 //              bool raise_on_number_error; // FIXME: use it
16
17                 public JavaScriptReader (TextReader reader, bool raiseOnNumberError)
18                 {
19                         if (reader == null)
20                                 throw new ArgumentNullException ("reader");
21                         this.r = reader;
22 //                      raise_on_number_error = raiseOnNumberError;
23                 }
24
25                 public object Read ()
26                 {
27                         object v = ReadCore ();
28                         SkipSpaces ();
29                         if (ReadChar () >= 0)
30                                 throw JsonError (String.Format ("extra characters in JSON input"));
31                         return v;
32                 }
33
34                 object ReadCore ()
35                 {
36                         SkipSpaces ();
37                         int c = PeekChar ();
38                         if (c < 0)
39                                 throw JsonError ("Incomplete JSON input");
40                         switch (c) {
41                         case '[':
42                                 ReadChar ();
43                                 var list = new List<object> ();
44                                 SkipSpaces ();
45                                 if (PeekChar () == ']') {
46                                         ReadChar ();
47                                         return list;
48                                 }
49                                 while (true) {
50                                         list.Add (ReadCore ());
51                                         SkipSpaces ();
52                                         c = PeekChar ();
53                                         if (c != ',')
54                                                 break;
55                                         ReadChar ();
56                                         continue;
57                                 }
58                                 if (ReadChar () != ']')
59                                         throw JsonError ("JSON array must end with ']'");
60                                 return list.ToArray ();
61                         case '{':
62                                 ReadChar ();
63                                 var obj = new Dictionary<string,object> ();
64                                 SkipSpaces ();
65                                 if (PeekChar () == '}') {
66                                         ReadChar ();
67                                         return obj;
68                                 }
69                                 while (true) {
70                                         SkipSpaces ();
71                                         if (PeekChar () == '}') {
72                                                 ReadChar ();
73                                                 break;
74                                         }
75                                         string name = ReadStringLiteral ();
76                                         SkipSpaces ();
77                                         Expect (':');
78                                         SkipSpaces ();
79                                         obj [name] = ReadCore (); // it does not reject duplicate names.
80                                         SkipSpaces ();
81                                         c = ReadChar ();
82                                         if (c == ',')
83                                                 continue;
84                                         if (c == '}')
85                                                 break;
86                                 }
87 #if MONOTOUCH
88                                 int idx = 0;
89                                 KeyValuePair<string, object> [] ret = new KeyValuePair<string, object>[obj.Count];
90                                 foreach (KeyValuePair <string, object> kvp in obj)
91                                         ret [idx++] = kvp;
92
93                                 return ret;
94 #else
95                                 return obj.ToArray ();
96 #endif
97                         case 't':
98                                 Expect ("true");
99                                 return true;
100                         case 'f':
101                                 Expect ("false");
102                                 return false;
103                         case 'n':
104                                 Expect ("null");
105                                 // FIXME: what should we return?
106                                 return (string) null;
107                         case '"':
108                                 return ReadStringLiteral ();
109                         default:
110                                 if ('0' <= c && c <= '9' || c == '-')
111                                         return ReadNumericLiteral ();
112                                 else
113                                         throw JsonError (String.Format ("Unexpected character '{0}'", (char) c));
114                         }
115                 }
116
117                 int peek;
118                 bool has_peek;
119                 bool prev_lf;
120
121                 int PeekChar ()
122                 {
123                         if (!has_peek) {
124                                 peek = r.Read ();
125                                 has_peek = true;
126                         }
127                         return peek;
128                 }
129
130                 int ReadChar ()
131                 {
132                         int v = has_peek ? peek : r.Read ();
133
134                         has_peek = false;
135
136                         if (prev_lf) {
137                                 line++;
138                                 column = 0;
139                                 prev_lf = false;
140                         }
141
142                         if (v == '\n')
143                                 prev_lf = true;
144                         column++;
145
146                         return v;
147                 }
148
149                 void SkipSpaces ()
150                 {
151                         while (true) {
152                                 switch (PeekChar ()) {
153                                 case ' ': case '\t': case '\r': case '\n':
154                                         ReadChar ();
155                                         continue;
156                                 default:
157                                         return;
158                                 }
159                         }
160                 }
161
162                 // It could return either int, long or decimal, depending on the parsed value.
163                 object ReadNumericLiteral ()
164                 {
165                         var sb = new StringBuilder ();
166                         
167                         if (PeekChar () == '-') {
168                                 sb.Append ((char) ReadChar ());
169                         }
170
171                         int c;
172                         int x = 0;
173                         bool zeroStart = PeekChar () == '0';
174                         for (; ; x++) {
175                                 c = PeekChar ();
176                                 if (c < '0' || '9' < c)
177                                         break;
178                                 sb.Append ((char) ReadChar ());
179                                 if (zeroStart && x == 1)
180                                         throw JsonError ("leading zeros are not allowed");
181                         }
182                         if (x == 0) // Reached e.g. for "- "
183                                 throw JsonError ("Invalid JSON numeric literal; no digit found");
184
185                         // fraction
186                         bool hasFrac = false;
187                         int fdigits = 0;
188                         if (PeekChar () == '.') {
189                                 hasFrac = true;
190                                 sb.Append ((char) ReadChar ());
191                                 if (PeekChar () < 0)
192                                         throw JsonError ("Invalid JSON numeric literal; extra dot");
193                                 while (true) {
194                                         c = PeekChar ();
195                                         if (c < '0' || '9' < c)
196                                                 break;
197                                         sb.Append ((char) ReadChar ());
198                                         fdigits++;
199                                 }
200                                 if (fdigits == 0)
201                                         throw JsonError ("Invalid JSON numeric literal; extra dot");
202                         }
203
204                         c = PeekChar ();
205                         if (c != 'e' && c != 'E') {
206                                 if (!hasFrac) {
207                                         int valueInt;
208                                         if (int.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueInt))
209                                                 return valueInt;
210                                         
211                                         long valueLong;
212                                         if (long.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueLong))
213                                                 return valueLong;
214                                         
215                                         ulong valueUlong;
216                                         if (ulong.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueUlong))
217                                                 return valueUlong;
218                                 }
219                                 decimal valueDecimal;
220                                 if (decimal.TryParse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture, out valueDecimal) && valueDecimal != 0)
221                                         return valueDecimal;
222                         } else {
223                                 // exponent
224                                 sb.Append ((char) ReadChar ());
225                                 if (PeekChar () < 0)
226                                         throw new ArgumentException ("Invalid JSON numeric literal; incomplete exponent");
227                         
228                                 c = PeekChar ();
229                                 if (c == '-') {
230                                         sb.Append ((char) ReadChar ());
231                                 }
232                                 else if (c == '+')
233                                         sb.Append ((char) ReadChar ());
234
235                                 if (PeekChar () < 0)
236                                         throw JsonError ("Invalid JSON numeric literal; incomplete exponent");
237                                 while (true) {
238                                         c = PeekChar ();
239                                         if (c < '0' || '9' < c)
240                                                 break;
241                                         sb.Append ((char) ReadChar ());
242                                 }
243                         }
244
245                         return double.Parse (sb.ToString (), NumberStyles.Float, CultureInfo.InvariantCulture);
246                 }
247
248                 StringBuilder vb = new StringBuilder ();
249
250                 string ReadStringLiteral ()
251                 {
252                         if (PeekChar () != '"')
253                                 throw JsonError ("Invalid JSON string literal format");
254
255                         ReadChar ();
256                         vb.Length = 0;
257                         while (true) {
258                                 int c = ReadChar ();
259                                 if (c < 0)
260                                         throw JsonError ("JSON string is not closed");
261                                 if (c == '"')
262                                         return vb.ToString ();
263                                 else if (c != '\\') {
264                                         vb.Append ((char) c);
265                                         continue;
266                                 }
267
268                                 // escaped expression
269                                 c = ReadChar ();
270                                 if (c < 0)
271                                         throw JsonError ("Invalid JSON string literal; incomplete escape sequence");
272                                 switch (c) {
273                                 case '"':
274                                 case '\\':
275                                 case '/':
276                                         vb.Append ((char) c);
277                                         break;
278                                 case 'b':
279                                         vb.Append ('\x8');
280                                         break;
281                                 case 'f':
282                                         vb.Append ('\f');
283                                         break;
284                                 case 'n':
285                                         vb.Append ('\n');
286                                         break;
287                                 case 'r':
288                                         vb.Append ('\r');
289                                         break;
290                                 case 't':
291                                         vb.Append ('\t');
292                                         break;
293                                 case 'u':
294                                         ushort cp = 0;
295                                         for (int i = 0; i < 4; i++) {
296                                                 cp <<= 4;
297                                                 if ((c = ReadChar ()) < 0)
298                                                         throw JsonError ("Incomplete unicode character escape literal");
299                                                 if ('0' <= c && c <= '9')
300                                                         cp += (ushort) (c - '0');
301                                                 if ('A' <= c && c <= 'F')
302                                                         cp += (ushort) (c - 'A' + 10);
303                                                 if ('a' <= c && c <= 'f')
304                                                         cp += (ushort) (c - 'a' + 10);
305                                         }
306                                         vb.Append ((char) cp);
307                                         break;
308                                 default:
309                                         throw JsonError ("Invalid JSON string literal; unexpected escape character");
310                                 }
311                         }
312                 }
313
314                 void Expect (char expected)
315                 {
316                         int c;
317                         if ((c = ReadChar ()) != expected)
318                                 throw JsonError (String.Format ("Expected '{0}', got '{1}'", expected, (char) c));
319                 }
320
321                 void Expect (string expected)
322                 {
323                         for (int i = 0; i < expected.Length; i++)
324                                 if (ReadChar () != expected [i])
325                                         throw JsonError (String.Format ("Expected '{0}', differed at {1}", expected, i));
326                 }
327
328                 Exception JsonError (string msg)
329                 {
330                         return new ArgumentException (String.Format ("{0}. At line {1}, column {2}", msg, line, column));
331                 }
332         }
333 }