using System; using System.Collections.Generic; using System.Globalization; using System.Text; namespace Luneraria.Core.Tests { /// /// Minimal JSON reader for the golden files, without package dependencies (the same code /// runs in Unity and in the .NET harness). Objects keep their key order; numbers without /// fraction or exponent become long, all others double. /// public static class MiniJson { public static object Parse(string text) { int at = 0; object value = ReadValue(text, ref at); SkipSpace(text, ref at); if (at != text.Length) throw new FormatException("trailing characters at " + at); return value; } static void SkipSpace(string text, ref int at) { while (at < text.Length && char.IsWhiteSpace(text[at])) at++; } static object ReadValue(string text, ref int at) { SkipSpace(text, ref at); char c = text[at]; if (c == '{') return ReadObject(text, ref at); if (c == '[') return ReadArray(text, ref at); if (c == '"') return ReadString(text, ref at); if (text.Length - at >= 4 && string.CompareOrdinal(text, at, "true", 0, 4) == 0) { at += 4; return true; } if (text.Length - at >= 5 && string.CompareOrdinal(text, at, "false", 0, 5) == 0) { at += 5; return false; } if (text.Length - at >= 4 && string.CompareOrdinal(text, at, "null", 0, 4) == 0) { at += 4; return null; } return ReadNumber(text, ref at); } static OrderedMap ReadObject(string text, ref int at) { var result = new OrderedMap(); at++; SkipSpace(text, ref at); if (text[at] == '}') { at++; return result; } while (true) { SkipSpace(text, ref at); string key = ReadString(text, ref at); SkipSpace(text, ref at); if (text[at] != ':') throw new FormatException("expected ':' at " + at); at++; result[key] = ReadValue(text, ref at); SkipSpace(text, ref at); if (text[at] == ',') { at++; continue; } if (text[at] == '}') { at++; return result; } throw new FormatException("expected ',' or '}' at " + at); } } static List ReadArray(string text, ref int at) { var result = new List(); at++; SkipSpace(text, ref at); if (text[at] == ']') { at++; return result; } while (true) { result.Add(ReadValue(text, ref at)); SkipSpace(text, ref at); if (text[at] == ',') { at++; continue; } if (text[at] == ']') { at++; return result; } throw new FormatException("expected ',' or ']' at " + at); } } static string ReadString(string text, ref int at) { if (text[at] != '"') throw new FormatException("expected string at " + at); at++; var builder = new StringBuilder(); while (true) { char c = text[at++]; if (c == '"') return builder.ToString(); if (c != '\\') { builder.Append(c); continue; } char escape = text[at++]; switch (escape) { case '"': builder.Append('"'); break; case '\\': builder.Append('\\'); break; case '/': builder.Append('/'); break; case 'b': builder.Append('\b'); break; case 'f': builder.Append('\f'); break; case 'n': builder.Append('\n'); break; case 'r': builder.Append('\r'); break; case 't': builder.Append('\t'); break; case 'u': builder.Append((char)int.Parse(text.Substring(at, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture)); at += 4; break; default: throw new FormatException("bad escape at " + at); } } } static object ReadNumber(string text, ref int at) { int start = at; bool fraction = false; while (at < text.Length && "+-0123456789.eE".IndexOf(text[at]) >= 0) { if (text[at] == '.' || text[at] == 'e' || text[at] == 'E') fraction = true; at++; } string token = text.Substring(start, at - start); if (!fraction) { long whole; if (long.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out whole)) return whole; } return double.Parse(token, NumberStyles.Float, CultureInfo.InvariantCulture); } // ---------------------------------------------------------------- access helpers public static OrderedMap Obj(object value) { return (OrderedMap)value; } public static List Arr(object value) { return (List)value; } public static double Num(object value) { return value is long l ? l : (double)value; } public static int Int(object value) { return value is long l ? (int)l : (int)(double)value; } public static string Str(object value) { return value == null ? "" : value.ToString(); } public static object Get(OrderedMap map, string key) { object value; return map.TryGetValue(key, out value) ? value : null; } } }