New tests.
[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 (r.Read () >= 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                                         string name = ReadStringLiteral ();
72                                         SkipSpaces ();
73                                         Expect (':');
74                                         SkipSpaces ();
75                                         obj [name] = ReadCore (); // it does not reject duplicate names.
76                                         SkipSpaces ();
77                                         c = ReadChar ();
78                                         if (c == ',')
79                                                 continue;
80                                         if (c == '}')
81                                                 break;
82                                 }
83                                 return obj.ToArray ();
84                         case 't':
85                                 Expect ("true");
86                                 return true;
87                         case 'f':
88                                 Expect ("false");
89                                 return false;
90                         case 'n':
91                                 Expect ("null");
92                                 // FIXME: what should we return?
93                                 return (string) null;
94                         case '"':
95                                 return ReadStringLiteral ();
96                         default:
97                                 if ('0' <= c && c <= '9' || c == '-')
98                                         return ReadNumericLiteral ();
99                                 else
100                                         throw JsonError (String.Format ("Unexpected character '{0}'", (char) c));
101                         }
102                 }
103
104                 int peek;
105                 bool has_peek;
106                 bool prev_lf;
107
108                 int PeekChar ()
109                 {
110                         if (!has_peek) {
111                                 peek = r.Read ();
112                                 has_peek = true;
113                         }
114                         return peek;
115                 }
116
117                 int ReadChar ()
118                 {
119                         int v = has_peek ? peek : r.Read ();
120
121                         has_peek = false;
122
123                         if (prev_lf) {
124                                 line++;
125                                 column = 0;
126                                 prev_lf = false;
127                         }
128
129                         if (v == '\n')
130                                 prev_lf = true;
131                         column++;
132
133                         return v;
134                 }
135
136                 void SkipSpaces ()
137                 {
138                         while (true) {
139                                 switch (PeekChar ()) {
140                                 case ' ': case '\t': case '\r': case '\n':
141                                         ReadChar ();
142                                         continue;
143                                 default:
144                                         return;
145                                 }
146                         }
147                 }
148
149                 // It could return either int, long or decimal, depending on the parsed value.
150                 object ReadNumericLiteral ()
151                 {
152                         bool negative = false;
153                         if (PeekChar () == '-') {
154                                 negative = true;
155                                 ReadChar ();
156                                 if (PeekChar () < 0)
157                                         throw JsonError ("Invalid JSON numeric literal; extra negation");
158                         }
159
160                         int c;
161                         decimal val = 0;
162                         int x = 0;
163                         bool zeroStart = PeekChar () == '0';
164                         for (; ; x++) {
165                                 c = PeekChar ();
166                                 if (c < '0' || '9' < c)
167                                         break;
168                                 val = val * 10 + (c - '0');
169                                 ReadChar ();
170                                 if (zeroStart && x == 1 && c == '0')
171                                         throw JsonError ("leading multiple zeros are not allowed");
172                         }
173
174                         // fraction
175
176                         bool hasFrac = false;
177                         decimal frac = 0;
178                         int fdigits = 0;
179                         if (PeekChar () == '.') {
180                                 hasFrac = true;
181                                 ReadChar ();
182                                 if (PeekChar () < 0)
183                                         throw JsonError ("Invalid JSON numeric literal; extra dot");
184                                 decimal d = 10;
185                                 while (true) {
186                                         c = PeekChar ();
187                                         if (c < '0' || '9' < c)
188                                                 break;
189                                         ReadChar ();
190                                         frac += (c - '0') / d;
191                                         d *= 10;
192                                         fdigits++;
193                                 }
194                                 if (fdigits == 0)
195                                         throw JsonError ("Invalid JSON numeric literal; extra dot");
196                         }
197                         frac = Decimal.Round (frac, fdigits);
198
199                         c = PeekChar ();
200                         if (c != 'e' && c != 'E') {
201                                 if (!hasFrac) {
202                                         if (negative && int.MinValue <= -val ||
203                                             !negative && val <= int.MaxValue)
204                                                 return (int) (negative ? -val : val);
205                                         if (negative && long.MinValue <= -val ||
206                                             !negative && val <= long.MaxValue)
207                                                 return (long) (negative ? -val : val);
208                                 }
209                                 var v = val + frac;
210                                 return negative ? -v : v;
211                         }
212
213                         // exponent
214
215                         ReadChar ();
216
217                         int exp = 0;
218                         if (PeekChar () < 0)
219                                 throw new ArgumentException ("Invalid JSON numeric literal; incomplete exponent");
220                         bool negexp = false;
221                         c = PeekChar ();
222                         if (c == '-') {
223                                 ReadChar ();
224                                 negexp = true;
225                         }
226                         else if (c == '+')
227                                 ReadChar ();
228
229                         if (PeekChar () < 0)
230                                 throw JsonError ("Invalid JSON numeric literal; incomplete exponent");
231                         while (true) {
232                                 c = PeekChar ();
233                                 if (c < '0' || '9' < c)
234                                         break;
235                                 exp = exp * 10 + (c - '0');
236                                 ReadChar ();
237                         }
238                         // it is messy to handle exponent, so I just use Decimal.Parse() with assured JSON format.
239                         int [] bits = Decimal.GetBits (val + frac);
240                         return new Decimal (bits [0], bits [1], bits [2], negative, (byte) exp);
241                 }
242
243                 StringBuilder vb = new StringBuilder ();
244
245                 string ReadStringLiteral ()
246                 {
247                         if (PeekChar () != '"')
248                                 throw JsonError ("Invalid JSON string literal format");
249
250                         ReadChar ();
251                         vb.Length = 0;
252                         while (true) {
253                                 int c = ReadChar ();
254                                 if (c < 0)
255                                         throw JsonError ("JSON string is not closed");
256                                 if (c == '"')
257                                         return vb.ToString ();
258                                 else if (c != '\\') {
259                                         vb.Append ((char) c);
260                                         continue;
261                                 }
262
263                                 // escaped expression
264                                 c = ReadChar ();
265                                 if (c < 0)
266                                         throw JsonError ("Invalid JSON string literal; incomplete escape sequence");
267                                 switch (c) {
268                                 case '"':
269                                 case '\\':
270                                 case '/':
271                                         vb.Append ((char) c);
272                                         break;
273                                 case 'b':
274                                         vb.Append ('\x8');
275                                         break;
276                                 case 'f':
277                                         vb.Append ('\f');
278                                         break;
279                                 case 'n':
280                                         vb.Append ('\n');
281                                         break;
282                                 case 'r':
283                                         vb.Append ('\r');
284                                         break;
285                                 case 't':
286                                         vb.Append ('\t');
287                                         break;
288                                 case 'u':
289                                         ushort cp = 0;
290                                         for (int i = 0; i < 4; i++) {
291                                                 cp <<= 4;
292                                                 if ((c = ReadChar ()) < 0)
293                                                         throw JsonError ("Incomplete unicode character escape literal");
294                                                 if ('0' <= c && c <= '9')
295                                                         cp += (ushort) (c - '0');
296                                                 if ('A' <= c && c <= 'F')
297                                                         cp += (ushort) (c - 'A' + 10);
298                                                 if ('a' <= c && c <= 'f')
299                                                         cp += (ushort) (c - 'a' + 10);
300                                         }
301                                         vb.Append ((char) cp);
302                                         break;
303                                 default:
304                                         throw JsonError ("Invalid JSON string literal; unexpected escape character");
305                                 }
306                         }
307                 }
308
309                 void Expect (char expected)
310                 {
311                         int c;
312                         if ((c = ReadChar ()) != expected)
313                                 throw JsonError (String.Format ("Expected '{0}', got '{1}'", expected, (char) c));
314                 }
315
316                 void Expect (string expected)
317                 {
318                         int c;
319                         for (int i = 0; i < expected.Length; i++)
320                                 if ((c = ReadChar ()) != expected [i])
321                                         throw JsonError (String.Format ("Expected '{0}', differed at {1}", expected, i));
322                 }
323
324                 Exception JsonError (string msg)
325                 {
326                         return new ArgumentException (String.Format ("{0}. At line {1}, column {2}", msg, line, column));
327                 }
328         }
329 }