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}