OSDN Git Service

水上爆撃機の改修値を爆撃に反映する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / JsonParser.cs
1 using System;\r
2 using System.Collections;\r
3 using System.Collections.Generic;\r
4 using System.Dynamic;\r
5 using System.Linq;\r
6 using System.Text.RegularExpressions;\r
7 \r
8 namespace KancolleSniffer\r
9 {\r
10     public class JsonParser\r
11     {\r
12         private readonly string _source;\r
13         private int _position;\r
14 \r
15         public static JsonObject Parse(string json)\r
16         {\r
17             return new JsonParser(json).Parse();\r
18         }\r
19 \r
20         private JsonParser(string json)\r
21         {\r
22             _source = json;\r
23         }\r
24 \r
25         private JsonObject Parse()\r
26         {\r
27             var ch = NextChar();\r
28             if ('0' <= ch && ch <= '9' || ch == '-')\r
29                 return ParseNumber();\r
30             switch (ch)\r
31             {\r
32                 case 'n':\r
33                     if (IsMatch("null"))\r
34                         return null;\r
35                     goto invalid;\r
36                 case 't':\r
37                     if (IsMatch("true"))\r
38                         return new JsonObject(true);\r
39                     goto invalid;\r
40                 case 'f':\r
41                     if (IsMatch("false"))\r
42                         return new JsonObject(false);\r
43                     goto invalid;\r
44                 case '"':\r
45                     return ParseString();\r
46                 case '[':\r
47                     return ParseArray();\r
48                 case '{':\r
49                     return ParseObject();\r
50             }\r
51             invalid:\r
52             throw new JsonParserException($"'{ch}'不正な文字です。 at {_position}");\r
53         }\r
54 \r
55         private bool IsMatch(string s)\r
56         {\r
57             foreach (char ch in s)\r
58             {\r
59                 var src = LookAhead();\r
60                 if (ch != src)\r
61                     return false;\r
62                 Consume();\r
63             }\r
64             return true;\r
65         }\r
66 \r
67         private JsonObject ParseNumber()\r
68         {\r
69             return new JsonObject(GetNumber());\r
70         }\r
71 \r
72         private double GetNumber()\r
73         {\r
74             var result = 0d;\r
75             var ch = LookAhead();\r
76             var sign = 1d;\r
77             if (ch == '-')\r
78             {\r
79                 sign = -1;\r
80                 Consume();\r
81                 ch = LookAhead();\r
82             }\r
83             while ('0' <= ch && ch <= '9')\r
84             {\r
85                 result = (result * 10.0) + (ch - '0');\r
86                 Consume();\r
87                 ch = LookAhead();\r
88             }\r
89             if (ch != '.')\r
90                 return sign * result;\r
91             var exp = 0.1;\r
92             Consume();\r
93             ch = LookAhead();\r
94             while ('0' <= ch && ch <= '9')\r
95             {\r
96                 result += (ch - '0') * exp;\r
97                 exp *= 0.1;\r
98                 Consume();\r
99                 ch = LookAhead();\r
100             }\r
101             return sign * result;\r
102         }\r
103 \r
104         private JsonObject ParseString()\r
105         {\r
106             return new JsonObject(GetString());\r
107         }\r
108 \r
109         private string GetString()\r
110         {\r
111             Consume();\r
112             var len = 0;\r
113             while (true)\r
114             {\r
115                 var ch = LookAhead();\r
116                 if (ch == '\\')\r
117                 {\r
118                     Consume();\r
119                     Consume();\r
120                     len += 2;\r
121                     continue;\r
122                 }\r
123                 if (ch == '"')\r
124                 {\r
125                     Consume();\r
126                     break;\r
127                 }\r
128                 len++;\r
129                 Consume();\r
130             }\r
131             return Unescape(_source.Substring(_position - len - 1, len));\r
132         }\r
133 \r
134         private static readonly Regex EscapeRegex =\r
135             new Regex(@"\\[^u]|\\u(?:[0-9A-Fa-f]{4})", RegexOptions.Compiled);\r
136 \r
137         private string Unescape(string s)\r
138         {\r
139             return EscapeRegex.Replace(s, m =>\r
140             {\r
141                 switch (m.Value[1])\r
142                 {\r
143                     case '/':\r
144                         return '/'.ToString();\r
145                     case '"':\r
146                     case '\\':\r
147                     case 'b':\r
148                     case 'f':\r
149                     case 'n':\r
150                     case 'r':\r
151                     case 't':\r
152                     case 'u':\r
153                         return Regex.Unescape(m.Value).ToString();\r
154                     default:\r
155                         throw new JsonParserException("不正なエスケープシーケンスです。 at {");\r
156                 }\r
157             });\r
158         }\r
159 \r
160         private JsonObject ParseArray()\r
161         {\r
162             Consume();\r
163             var ary = new List<JsonObject>();\r
164             while (true)\r
165             {\r
166                 var ch = NextChar();\r
167                 if (ch == ']')\r
168                 {\r
169                     Consume();\r
170                     return new JsonObject(ary);\r
171                 }\r
172                 ary.Add(Parse());\r
173                 ch = NextChar();\r
174                 if (ch != ',' && ch != ']')\r
175                     throw new JsonParserException($"','か']'が必要です。 at {_position}");\r
176                 if (ch == ']')\r
177                 {\r
178                     Consume();\r
179                     return new JsonObject(ary);\r
180                 }\r
181                 Consume();\r
182             }\r
183         }\r
184 \r
185         private JsonObject ParseObject()\r
186         {\r
187             Consume();\r
188             var dict = new Dictionary<string, JsonObject>();\r
189             while (true)\r
190             {\r
191                 var ch = NextChar();\r
192                 if (ch == '}')\r
193                 {\r
194                     Consume();\r
195                     return new JsonObject(dict);\r
196                 }\r
197                 if (ch != '"')\r
198                     throw new JsonParserException($"文字列が必要です。 at {_position}");\r
199                 var key = GetString();\r
200                 ch = NextChar();\r
201                 if (ch != ':')\r
202                     throw new JsonParserException($"':'が必要です。 at {_position}");\r
203                 Consume();\r
204                 var value = Parse();\r
205                 dict.Add(key, value);\r
206                 ch = NextChar();\r
207                 if (ch != ',' && ch != '}')\r
208                     throw new JsonParserException($"','か'}}'が必要です。 at {_position}");\r
209                 if (ch == '}')\r
210                 {\r
211                     Consume();\r
212                     return new JsonObject(dict);\r
213                 }\r
214                 Consume();\r
215             }\r
216         }\r
217 \r
218         private char LookAhead()\r
219         {\r
220             if (_source.Length == _position)\r
221                 return '\0';\r
222             return _source[_position];\r
223         }\r
224 \r
225         private void Consume()\r
226         {\r
227             if (_source.Length == _position)\r
228                 throw new JsonParserException($"入力が途切れています。 at {_position}");\r
229             _position++;\r
230         }\r
231 \r
232         private char NextChar()\r
233         {\r
234             while (true)\r
235             {\r
236                 var ch = LookAhead();\r
237                 if (!(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' '))\r
238                     return ch;\r
239                 Consume();\r
240             }\r
241         }\r
242     }\r
243 \r
244     public class JsonObject : DynamicObject\r
245     {\r
246         private readonly JsonType _type;\r
247         private readonly bool _bool;\r
248         private readonly double _number;\r
249         private readonly string _string;\r
250         private readonly List<JsonObject> _array;\r
251         private readonly Dictionary<string, JsonObject> _dict;\r
252 \r
253         public bool IsArray => _type == JsonType.Array;\r
254         public bool IsObject => _type == JsonType.Object;\r
255         public bool IsDefined(string attr) => IsObject && _dict.ContainsKey(attr);\r
256 \r
257         public JsonObject(bool b)\r
258         {\r
259             _type = JsonType.Bool;\r
260             _bool = b;\r
261         }\r
262 \r
263         public JsonObject(double n)\r
264         {\r
265             _type = JsonType.Number;\r
266             _number = n;\r
267         }\r
268 \r
269         public JsonObject(string s)\r
270         {\r
271             _type = JsonType.String;\r
272             _string = s;\r
273         }\r
274 \r
275         public JsonObject(List<JsonObject> ary)\r
276         {\r
277             _type = JsonType.Array;\r
278             _array = ary;\r
279         }\r
280 \r
281         public JsonObject(Dictionary<string, JsonObject> dict)\r
282         {\r
283             _type = JsonType.Object;\r
284             _dict = dict;\r
285         }\r
286 \r
287         public override bool TryGetMember(GetMemberBinder binder, out object result)\r
288         {\r
289             result = null;\r
290             if (_type != JsonType.Object)\r
291                 return false;\r
292             JsonObject dict;\r
293             if (!_dict.TryGetValue(binder.Name, out dict))\r
294                 return false;\r
295             result = dict?.Value;\r
296             return true;\r
297         }\r
298 \r
299         public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\r
300         {\r
301             result = _type == JsonType.Object && _dict.ContainsKey(binder.Name);\r
302             return true;\r
303         }\r
304 \r
305         public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\r
306         {\r
307             switch (_type)\r
308             {\r
309                 case JsonType.Array:\r
310                     result = _array[(int)indexes[0]]?.Value;\r
311                     return true;\r
312                 case JsonType.Object:\r
313                     result = _dict[(string)indexes[0]]?.Value;\r
314                     return true;\r
315             }\r
316             result = null;\r
317             return false;\r
318         }\r
319 \r
320         public override bool TryConvert(ConvertBinder binder, out object result)\r
321         {\r
322             if (binder.Type == typeof(IEnumerable))\r
323             {\r
324                 switch (_type)\r
325                 {\r
326                     case JsonType.Array:\r
327                         result = _array.Select(x => x.Value);\r
328                         return true;\r
329                     case JsonType.Object:\r
330                         result = _dict.Select(x => new KeyValuePair<string, dynamic>(x.Key, x.Value));\r
331                         return true;\r
332                     default:\r
333                         result = null;\r
334                         return false;\r
335                 }\r
336             }\r
337             if (_type != JsonType.Array)\r
338                 return ConvertPrivateType(binder.Type, out result);\r
339             if (binder.Type == typeof(int[]))\r
340             {\r
341                 result = _array.Select(x => (int)x._number).ToArray();\r
342                 return true;\r
343             }\r
344             if (binder.Type.IsArray)\r
345             {\r
346                 return ConvertArray(binder.Type.GetElementType(), _array, out result);\r
347             }\r
348             result = null;\r
349             return false;\r
350         }\r
351 \r
352         private bool ConvertPrivateType(Type type, out object result)\r
353         {\r
354             if (type == typeof(bool) && _type == JsonType.Bool)\r
355             {\r
356                 result = _bool;\r
357                 return true;\r
358             }\r
359             if (type == typeof(int) && _type == JsonType.Number)\r
360             {\r
361                 result = (int)_number;\r
362                 return true;\r
363             }\r
364             if (type == typeof(double) && _type == JsonType.Number)\r
365             {\r
366                 result = _number;\r
367                 return true;\r
368             }\r
369             if (type == typeof(string) && _type == JsonType.String)\r
370             {\r
371                 result = _string;\r
372                 return true;\r
373             }\r
374             if (type == typeof(object))\r
375             {\r
376                 result = Value;\r
377                 return true;\r
378             }\r
379             result = null;\r
380             return false;\r
381         }\r
382 \r
383         private bool ConvertArray(Type type, List<JsonObject> values, out object result)\r
384         {\r
385             result = null;\r
386             var array = Array.CreateInstance(type, values.Count);\r
387             for (var i = 0; i < array.Length; i++)\r
388             {\r
389                 if (type.IsArray)\r
390                 {\r
391                     object one;\r
392                     if (!values[i].IsArray || !ConvertArray(type.GetElementType(), values[i]._array, out one))\r
393                         return false;\r
394                     array.SetValue((dynamic)one, i);\r
395                 }\r
396                 else\r
397                 {\r
398                     object one;\r
399                     if (!values[i].ConvertPrivateType(type, out one))\r
400                         return false;\r
401                     array.SetValue((dynamic)one, i);\r
402                 }\r
403             }\r
404             result = array;\r
405             return true;\r
406         }\r
407 \r
408         private object Value\r
409         {\r
410             get\r
411             {\r
412                 switch (_type)\r
413                 {\r
414                     case JsonType.Bool:\r
415                         return _bool;\r
416                     case JsonType.Number:\r
417                         return _number;\r
418                     case JsonType.String:\r
419                         return _string;\r
420                 }\r
421                 return this;\r
422             }\r
423         }\r
424 \r
425         private enum JsonType\r
426         {\r
427             Bool,\r
428             Number,\r
429             String,\r
430             Array,\r
431             Object\r
432         }\r
433     }\r
434 \r
435     public class JsonParserException : Exception\r
436     {\r
437         public JsonParserException()\r
438         {\r
439         }\r
440 \r
441         public JsonParserException(string message) : base(message)\r
442         {\r
443         }\r
444     }\r
445 }