JavaScriptReader.cs 7.4 KB

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