A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.Linq;
5using System.Text;
6using System.Text.RegularExpressions;
7
8namespace UnityEngine.InputSystem.Utilities
9{
10 /// <summary>
11 /// A JSON parser that instead of turning a string in JSON format into a
12 /// C# object graph, allows navigating the source text directly.
13 /// </summary>
14 /// <remarks>
15 /// This helper is most useful for avoiding a great many string and general object allocations
16 /// that would happen when turning a JSON object into a C# object graph.
17 /// </remarks>
18 internal struct JsonParser
19 {
20 public JsonParser(string json)
21 : this()
22 {
23 if (json == null)
24 throw new ArgumentNullException(nameof(json));
25
26 m_Text = json;
27 m_Length = json.Length;
28 }
29
30 public void Reset()
31 {
32 m_Position = 0;
33 m_MatchAnyElementInArray = false;
34 m_DryRun = false;
35 }
36
37 public override string ToString()
38 {
39 if (m_Text != null)
40 return $"{m_Position}: {m_Text.Substring(m_Position)}";
41 return base.ToString();
42 }
43
44 /// <summary>
45 /// Navigate to the given property.
46 /// </summary>
47 /// <param name="path"></param>
48 /// <remarks>
49 /// This navigates from the current property.
50 /// </remarks>
51 public bool NavigateToProperty(string path)
52 {
53 if (string.IsNullOrEmpty(path))
54 throw new ArgumentNullException(nameof(path));
55
56 var pathLength = path.Length;
57 var pathPosition = 0;
58
59 m_DryRun = true;
60 if (!ParseToken('{'))
61 return false;
62
63 while (m_Position < m_Length && pathPosition < pathLength)
64 {
65 // Find start of property name.
66 SkipWhitespace();
67 if (m_Position == m_Length)
68 return false;
69 if (m_Text[m_Position] != '"')
70 return false;
71 ++m_Position;
72
73 // Try to match single path component.
74 var pathStartPosition = pathPosition;
75 while (pathPosition < pathLength)
76 {
77 var ch = path[pathPosition];
78 if (ch == '/' || ch == '[')
79 break;
80
81 if (m_Text[m_Position] != ch)
82 break;
83
84 ++m_Position;
85 ++pathPosition;
86 }
87
88 // See if we have a match.
89 if (m_Position < m_Length && m_Text[m_Position] == '"' && (pathPosition >= pathLength || path[pathPosition] == '/' || path[pathPosition] == '['))
90 {
91 // Have matched a property name. Navigate to value.
92 ++m_Position;
93 if (!SkipToValue())
94 return false;
95
96 // Check if we have matched everything in the path.
97 if (pathPosition >= pathLength)
98 return true;
99 if (path[pathPosition] == '/')
100 {
101 ++pathPosition;
102 if (!ParseToken('{'))
103 return false;
104 }
105 else if (path[pathPosition] == '[')
106 {
107 ++pathPosition;
108 if (pathPosition == pathLength)
109 throw new ArgumentException("Malformed JSON property path: " + path, nameof(path));
110 if (path[pathPosition] == ']')
111 {
112 m_MatchAnyElementInArray = true;
113 ++pathPosition;
114 if (pathPosition == pathLength)
115 return true;
116 }
117 else
118 throw new NotImplementedException("Navigating to specific array element");
119 }
120 }
121 else
122 {
123 // This property isn't it. Skip the property and its value and reset
124 // to where we started in this iteration in the property path.
125
126 pathPosition = pathStartPosition;
127 while (m_Position < m_Length && m_Text[m_Position] != '"')
128 ++m_Position;
129 if (m_Position == m_Length || m_Text[m_Position] != '"')
130 return false;
131 ++m_Position;
132 if (!SkipToValue() || !ParseValue())
133 return false;
134 SkipWhitespace();
135 if (m_Position == m_Length || m_Text[m_Position] == '}' || m_Text[m_Position] != ',')
136 return false;
137 ++m_Position;
138 }
139 }
140
141 return false;
142 }
143
144 /// <summary>
145 /// Return true if the current property has a value matching <paramref name="expectedValue"/>.
146 /// </summary>
147 /// <param name="expectedValue"></param>
148 /// <returns></returns>
149 public bool CurrentPropertyHasValueEqualTo(JsonValue expectedValue)
150 {
151 // Grab property value.
152 var savedPosition = m_Position;
153 m_DryRun = false;
154 if (!ParseValue(out var propertyValue))
155 {
156 m_Position = savedPosition;
157 return false;
158 }
159 m_Position = savedPosition;
160
161 // Match given value.
162 var isMatch = false;
163 if (propertyValue.type == JsonValueType.Array && m_MatchAnyElementInArray)
164 {
165 var array = propertyValue.arrayValue;
166 for (var i = 0; !isMatch && i < array.Count; ++i)
167 isMatch = array[i] == expectedValue;
168 }
169 else
170 {
171 isMatch = propertyValue == expectedValue;
172 }
173
174 return isMatch;
175 }
176
177 public bool ParseToken(char token)
178 {
179 SkipWhitespace();
180 if (m_Position == m_Length)
181 return false;
182
183 if (m_Text[m_Position] != token)
184 return false;
185
186 ++m_Position;
187 SkipWhitespace();
188
189 return m_Position < m_Length;
190 }
191
192 public bool ParseValue()
193 {
194 return ParseValue(out var result);
195 }
196
197 public bool ParseValue(out JsonValue result)
198 {
199 result = default;
200
201 SkipWhitespace();
202 if (m_Position == m_Length)
203 return false;
204
205 var ch = m_Text[m_Position];
206 switch (ch)
207 {
208 case '"':
209 if (ParseStringValue(out result))
210 return true;
211 break;
212 case '[':
213 if (ParseArrayValue(out result))
214 return true;
215 break;
216 case '{':
217 if (ParseObjectValue(out result))
218 return true;
219 break;
220 case 't':
221 case 'f':
222 if (ParseBooleanValue(out result))
223 return true;
224 break;
225 case 'n':
226 if (ParseNullValue(out result))
227 return true;
228 break;
229 default:
230 if (ParseNumber(out result))
231 return true;
232 break;
233 }
234
235 return false;
236 }
237
238 public bool ParseStringValue(out JsonValue result)
239 {
240 result = default;
241
242 SkipWhitespace();
243 if (m_Position == m_Length || m_Text[m_Position] != '"')
244 return false;
245 ++m_Position;
246
247 var startIndex = m_Position;
248 var hasEscapes = false;
249
250 while (m_Position < m_Length)
251 {
252 var ch = m_Text[m_Position];
253 if (ch == '\\')
254 {
255 ++m_Position;
256 if (m_Position == m_Length)
257 break;
258 hasEscapes = true;
259 }
260 else if (ch == '"')
261 {
262 ++m_Position;
263
264 result = new JsonString
265 {
266 text = new Substring(m_Text, startIndex, m_Position - startIndex - 1),
267 hasEscapes = hasEscapes
268 };
269 return true;
270 }
271 ++m_Position;
272 }
273
274 return false;
275 }
276
277 public bool ParseArrayValue(out JsonValue result)
278 {
279 result = default;
280
281 SkipWhitespace();
282 if (m_Position == m_Length || m_Text[m_Position] != '[')
283 return false;
284 ++m_Position;
285
286 if (m_Position == m_Length)
287 return false;
288 if (m_Text[m_Position] == ']')
289 {
290 // Empty array.
291 result = new JsonValue { type = JsonValueType.Array };
292 ++m_Position;
293 return true;
294 }
295
296 List<JsonValue> values = null;
297 if (!m_DryRun)
298 values = new List<JsonValue>();
299
300 while (m_Position < m_Length)
301 {
302 if (!ParseValue(out var value))
303 return false;
304 if (!m_DryRun)
305 values.Add(value);
306 SkipWhitespace();
307 if (m_Position == m_Length)
308 return false;
309 var ch = m_Text[m_Position];
310 if (ch == ']')
311 {
312 ++m_Position;
313 if (!m_DryRun)
314 result = values;
315 return true;
316 }
317 if (ch == ',')
318 ++m_Position;
319 }
320
321 return false;
322 }
323
324 public bool ParseObjectValue(out JsonValue result)
325 {
326 result = default;
327
328 if (!ParseToken('{'))
329 return false;
330 if (m_Position < m_Length && m_Text[m_Position] == '}')
331 {
332 result = new JsonValue { type = JsonValueType.Object };
333 ++m_Position;
334 return true;
335 }
336
337 while (m_Position < m_Length)
338 {
339 if (!ParseStringValue(out var propertyName))
340 return false;
341
342 if (!SkipToValue())
343 return false;
344
345 if (!ParseValue(out var propertyValue))
346 return false;
347
348 if (!m_DryRun)
349 throw new NotImplementedException();
350
351 SkipWhitespace();
352 if (m_Position < m_Length && m_Text[m_Position] == '}')
353 {
354 if (!m_DryRun)
355 throw new NotImplementedException();
356 ++m_Position;
357 return true;
358 }
359 }
360
361 return false;
362 }
363
364 public bool ParseNumber(out JsonValue result)
365 {
366 result = default;
367
368 SkipWhitespace();
369 if (m_Position == m_Length)
370 return false;
371
372 var negative = false;
373 var haveFractionalPart = false;
374 var integralPart = 0L;
375 var fractionalPart = 0.0;
376 var fractionalDivisor = 10.0;
377 var exponent = 0;
378
379 // Parse sign.
380 if (m_Text[m_Position] == '-')
381 {
382 negative = true;
383 ++m_Position;
384 }
385
386 if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
387 return false;
388
389 // Parse integral part.
390 while (m_Position < m_Length)
391 {
392 var ch = m_Text[m_Position];
393 if (ch == '.')
394 break;
395 if (ch < '0' || ch > '9')
396 break;
397 integralPart = integralPart * 10 + ch - '0';
398 ++m_Position;
399 }
400
401 // Parse fractional part.
402 if (m_Position < m_Length && m_Text[m_Position] == '.')
403 {
404 haveFractionalPart = true;
405 ++m_Position;
406 if (m_Position == m_Length || !char.IsDigit(m_Text[m_Position]))
407 return false;
408 while (m_Position < m_Length)
409 {
410 var ch = m_Text[m_Position];
411 if (ch < '0' || ch > '9')
412 break;
413 fractionalPart = (ch - '0') / fractionalDivisor + fractionalPart;
414 fractionalDivisor *= 10;
415 ++m_Position;
416 }
417 }
418
419 if (m_Position < m_Length && (m_Text[m_Position] == 'e' || m_Text[m_Position] == 'E'))
420 {
421 ++m_Position;
422 var isNegative = false;
423 if (m_Position < m_Length && m_Text[m_Position] == '-')
424 {
425 isNegative = true;
426 ++m_Position;
427 }
428 else if (m_Position < m_Length && m_Text[m_Position] == '+')
429 {
430 ++m_Position;
431 }
432
433 var multiplier = 1;
434 while (m_Position < m_Length && char.IsDigit(m_Text[m_Position]))
435 {
436 var digit = m_Text[m_Position] - '0';
437 exponent *= multiplier;
438 exponent += digit;
439 multiplier *= 10;
440 ++m_Position;
441 }
442
443 if (isNegative)
444 exponent *= -1;
445 }
446
447 if (!m_DryRun)
448 {
449 if (!haveFractionalPart && exponent == 0)
450 {
451 if (negative)
452 result = -integralPart;
453 else
454 result = integralPart;
455 }
456 else
457 {
458 float value;
459 if (negative)
460 value = (float)-(integralPart + fractionalPart);
461 else
462 value = (float)(integralPart + fractionalPart);
463 if (exponent != 0)
464 value *= Mathf.Pow(10, exponent);
465 result = value;
466 }
467 }
468
469 return true;
470 }
471
472 public bool ParseBooleanValue(out JsonValue result)
473 {
474 SkipWhitespace();
475 if (SkipString("true"))
476 {
477 result = true;
478 return true;
479 }
480
481 if (SkipString("false"))
482 {
483 result = false;
484 return true;
485 }
486
487 result = default;
488 return false;
489 }
490
491 public bool ParseNullValue(out JsonValue result)
492 {
493 result = default;
494 return SkipString("null");
495 }
496
497 public bool SkipToValue()
498 {
499 SkipWhitespace();
500 if (m_Position == m_Length || m_Text[m_Position] != ':')
501 return false;
502 ++m_Position;
503 SkipWhitespace();
504 return true;
505 }
506
507 private bool SkipString(string text)
508 {
509 SkipWhitespace();
510 var length = text.Length;
511 if (m_Position + length >= m_Length)
512 return false;
513 for (var i = 0; i < length; ++i)
514 {
515 if (m_Text[m_Position + i] != text[i])
516 return false;
517 }
518
519 m_Position += length;
520 return true;
521 }
522
523 private void SkipWhitespace()
524 {
525 while (m_Position < m_Length && char.IsWhiteSpace(m_Text[m_Position]))
526 ++m_Position;
527 }
528
529 public bool isAtEnd => m_Position >= m_Length;
530
531 private readonly string m_Text;
532 private readonly int m_Length;
533 private int m_Position;
534 private bool m_MatchAnyElementInArray;
535 private bool m_DryRun;
536
537 public enum JsonValueType
538 {
539 None,
540 Bool,
541 Real,
542 Integer,
543 String,
544 Array,
545 Object,
546 Any,
547 }
548
549 public struct JsonString : IEquatable<JsonString>
550 {
551 public Substring text;
552 public bool hasEscapes;
553
554 public override string ToString()
555 {
556 if (!hasEscapes)
557 return text.ToString();
558
559 var builder = new StringBuilder();
560 var length = text.length;
561 for (var i = 0; i < length; ++i)
562 {
563 var ch = text[i];
564 if (ch == '\\')
565 {
566 ++i;
567 if (i == length)
568 break;
569 ch = text[i];
570 }
571 builder.Append(ch);
572 }
573 return builder.ToString();
574 }
575
576 public bool Equals(JsonString other)
577 {
578 if (hasEscapes == other.hasEscapes)
579 return Substring.Compare(text, other.text, StringComparison.InvariantCultureIgnoreCase) == 0;
580
581 var thisLength = text.length;
582 var otherLength = other.text.length;
583
584 int thisIndex = 0, otherIndex = 0;
585 for (; thisIndex < thisLength && otherIndex < otherLength; ++thisIndex, ++otherIndex)
586 {
587 var thisChar = text[thisIndex];
588 var otherChar = other.text[otherIndex];
589
590 if (thisChar == '\\')
591 {
592 ++thisIndex;
593 if (thisIndex == thisLength)
594 return false;
595 thisChar = text[thisIndex];
596 }
597
598 if (otherChar == '\\')
599 {
600 ++otherIndex;
601 if (otherIndex == otherLength)
602 return false;
603 otherChar = other.text[otherIndex];
604 }
605
606 if (char.ToUpperInvariant(thisChar) != char.ToUpperInvariant(otherChar))
607 return false;
608 }
609
610 return thisIndex == thisLength && otherIndex == otherLength;
611 }
612
613 public override bool Equals(object obj)
614 {
615 return obj is JsonString other && Equals(other);
616 }
617
618 public override int GetHashCode()
619 {
620 unchecked
621 {
622 return (text.GetHashCode() * 397) ^ hasEscapes.GetHashCode();
623 }
624 }
625
626 public static bool operator==(JsonString left, JsonString right)
627 {
628 return left.Equals(right);
629 }
630
631 public static bool operator!=(JsonString left, JsonString right)
632 {
633 return !left.Equals(right);
634 }
635
636 public static implicit operator JsonString(string str)
637 {
638 return new JsonString { text = str };
639 }
640 }
641
642 public struct JsonValue : IEquatable<JsonValue>
643 {
644 public JsonValueType type;
645 public bool boolValue;
646 public double realValue;
647 public long integerValue;
648 public JsonString stringValue;
649 public List<JsonValue> arrayValue; // Allocates.
650 public Dictionary<string, JsonValue> objectValue; // Allocates.
651 public object anyValue;
652
653 public bool ToBoolean()
654 {
655 switch (type)
656 {
657 case JsonValueType.Bool: return boolValue;
658 case JsonValueType.Integer: return integerValue != 0;
659 case JsonValueType.Real: return NumberHelpers.Approximately(0, realValue);
660 case JsonValueType.String: return Convert.ToBoolean(ToString());
661 }
662 return default;
663 }
664
665 public long ToInteger()
666 {
667 switch (type)
668 {
669 case JsonValueType.Bool: return boolValue ? 1 : 0;
670 case JsonValueType.Integer: return integerValue;
671 case JsonValueType.Real: return (long)realValue;
672 case JsonValueType.String: return Convert.ToInt64(ToString());
673 }
674 return default;
675 }
676
677 public double ToDouble()
678 {
679 switch (type)
680 {
681 case JsonValueType.Bool: return boolValue ? 1 : 0;
682 case JsonValueType.Integer: return integerValue;
683 case JsonValueType.Real: return realValue;
684 case JsonValueType.String: return Convert.ToSingle(ToString());
685 }
686 return default;
687 }
688
689 public override string ToString()
690 {
691 switch (type)
692 {
693 case JsonValueType.None: return "null";
694 case JsonValueType.Bool: return boolValue.ToString();
695 case JsonValueType.Integer: return integerValue.ToString(CultureInfo.InvariantCulture);
696 case JsonValueType.Real: return realValue.ToString(CultureInfo.InvariantCulture);
697 case JsonValueType.String: return stringValue.ToString();
698 case JsonValueType.Array:
699 if (arrayValue == null)
700 return "[]";
701 return $"[{string.Join(",", arrayValue.Select(x => x.ToString()))}]";
702 case JsonValueType.Object:
703 if (objectValue == null)
704 return "{}";
705 var elements = objectValue.Select(pair => $"\"{pair.Key}\" : \"{pair.Value}\"");
706 return $"{{{string.Join(",", elements)}}}";
707 case JsonValueType.Any: return anyValue.ToString();
708 }
709 return base.ToString();
710 }
711
712 public static implicit operator JsonValue(bool val)
713 {
714 return new JsonValue
715 {
716 type = JsonValueType.Bool,
717 boolValue = val
718 };
719 }
720
721 public static implicit operator JsonValue(long val)
722 {
723 return new JsonValue
724 {
725 type = JsonValueType.Integer,
726 integerValue = val
727 };
728 }
729
730 public static implicit operator JsonValue(double val)
731 {
732 return new JsonValue
733 {
734 type = JsonValueType.Real,
735 realValue = val
736 };
737 }
738
739 public static implicit operator JsonValue(string str)
740 {
741 return new JsonValue
742 {
743 type = JsonValueType.String,
744 stringValue = new JsonString { text = str }
745 };
746 }
747
748 public static implicit operator JsonValue(JsonString str)
749 {
750 return new JsonValue
751 {
752 type = JsonValueType.String,
753 stringValue = str
754 };
755 }
756
757 public static implicit operator JsonValue(List<JsonValue> array)
758 {
759 return new JsonValue
760 {
761 type = JsonValueType.Array,
762 arrayValue = array
763 };
764 }
765
766 public static implicit operator JsonValue(Dictionary<string, JsonValue> obj)
767 {
768 return new JsonValue
769 {
770 type = JsonValueType.Object,
771 objectValue = obj
772 };
773 }
774
775 public static implicit operator JsonValue(Enum val)
776 {
777 return new JsonValue
778 {
779 type = JsonValueType.Any,
780 anyValue = val
781 };
782 }
783
784 public bool Equals(JsonValue other)
785 {
786 // Default comparisons.
787 if (type == other.type)
788 {
789 switch (type)
790 {
791 case JsonValueType.None: return true;
792 case JsonValueType.Bool: return boolValue == other.boolValue;
793 case JsonValueType.Integer: return integerValue == other.integerValue;
794 case JsonValueType.Real: return NumberHelpers.Approximately(realValue, other.realValue);
795 case JsonValueType.String: return stringValue == other.stringValue;
796 case JsonValueType.Object: throw new NotImplementedException();
797 case JsonValueType.Array: throw new NotImplementedException();
798 case JsonValueType.Any: return anyValue.Equals(other.anyValue);
799 }
800 return false;
801 }
802
803 // anyValue-based comparisons.
804 if (anyValue != null)
805 return Equals(anyValue, other);
806 if (other.anyValue != null)
807 return Equals(other.anyValue, this);
808
809 return false;
810 }
811
812 private static bool Equals(object obj, JsonValue value)
813 {
814 if (obj == null)
815 return false;
816
817 if (obj is Regex regex)
818 return regex.IsMatch(value.ToString());
819 if (obj is string str)
820 {
821 switch (value.type)
822 {
823 case JsonValueType.String: return value.stringValue == str;
824 case JsonValueType.Integer: return long.TryParse(str, out var si) && si == value.integerValue;
825 case JsonValueType.Real:
826 return double.TryParse(str, out var sf) && NumberHelpers.Approximately(sf, value.realValue);
827 case JsonValueType.Bool:
828 if (value.boolValue)
829 return str == "True" || str == "true" || str == "1";
830 return str == "False" || str == "false" || str == "0";
831 }
832 }
833 if (obj is float f)
834 {
835 if (value.type == JsonValueType.Real)
836 return NumberHelpers.Approximately(f, value.realValue);
837 if (value.type == JsonValueType.String)
838 return float.TryParse(value.ToString(), out var otherF) && Mathf.Approximately(f, otherF);
839 }
840 if (obj is double d)
841 {
842 if (value.type == JsonValueType.Real)
843 return NumberHelpers.Approximately(d, value.realValue);
844 if (value.type == JsonValueType.String)
845 return double.TryParse(value.ToString(), out var otherD) &&
846 NumberHelpers.Approximately(d, otherD);
847 }
848 if (obj is int i)
849 {
850 if (value.type == JsonValueType.Integer)
851 return i == value.integerValue;
852 if (value.type == JsonValueType.String)
853 return int.TryParse(value.ToString(), out var otherI) && i == otherI;
854 }
855 if (obj is long l)
856 {
857 if (value.type == JsonValueType.Integer)
858 return l == value.integerValue;
859 if (value.type == JsonValueType.String)
860 return long.TryParse(value.ToString(), out var otherL) && l == otherL;
861 }
862 if (obj is bool b)
863 {
864 if (value.type == JsonValueType.Bool)
865 return b == value.boolValue;
866 if (value.type == JsonValueType.String)
867 {
868 if (b)
869 return value.stringValue == "true" || value.stringValue == "True" ||
870 value.stringValue == "1";
871 return value.stringValue == "false" || value.stringValue == "False" ||
872 value.stringValue == "0";
873 }
874 }
875 // NOTE: The enum-based comparisons allocate both on the Convert.ToInt64() and Enum.GetName() path. I've found
876 // no way to do either comparison in a way that does not allocate.
877 if (obj is Enum)
878 {
879 if (value.type == JsonValueType.Integer)
880 return Convert.ToInt64(obj) == value.integerValue;
881 if (value.type == JsonValueType.String)
882 return value.stringValue == Enum.GetName(obj.GetType(), obj);
883 }
884
885 return false;
886 }
887
888 public override bool Equals(object obj)
889 {
890 return obj is JsonValue other && Equals(other);
891 }
892
893 public override int GetHashCode()
894 {
895 unchecked
896 {
897 var hashCode = (int)type;
898 hashCode = (hashCode * 397) ^ boolValue.GetHashCode();
899 hashCode = (hashCode * 397) ^ realValue.GetHashCode();
900 hashCode = (hashCode * 397) ^ integerValue.GetHashCode();
901 hashCode = (hashCode * 397) ^ stringValue.GetHashCode();
902 hashCode = (hashCode * 397) ^ (arrayValue != null ? arrayValue.GetHashCode() : 0);
903 hashCode = (hashCode * 397) ^ (objectValue != null ? objectValue.GetHashCode() : 0);
904 hashCode = (hashCode * 397) ^ (anyValue != null ? anyValue.GetHashCode() : 0);
905 return hashCode;
906 }
907 }
908
909 public static bool operator==(JsonValue left, JsonValue right)
910 {
911 return left.Equals(right);
912 }
913
914 public static bool operator!=(JsonValue left, JsonValue right)
915 {
916 return !left.Equals(right);
917 }
918 }
919 }
920}