using Jtr.Parsing.Json.Syntax; namespace Jtr.Parsing.Json; public static partial class JsonParser { public static IJsonSyntax Parse(string file, string source) { var tokens = JsonLexer.Default.Lex(file, source).ToArray(); var context = new Context(tokens); var nodes = Expression(context); return nodes; } private static IJsonSyntax Expression(ParserContext ctx) { if (ctx.Match(JsonToken.LeftBracket)) return ArrayExpression(ctx); if (ctx.Match(JsonToken.LeftBrace)) return ObjectExpression(ctx); if (ctx.Match(JsonToken.Minus) || ctx.Match(JsonToken.Number)) return NumberExpression(ctx); if (ctx.Match(JsonToken.String)) return StringExpression(ctx); if (ctx.Match(JsonToken.Null)) return NullExpression(ctx); if (ctx.Match(JsonToken.True) || ctx.Match(JsonToken.False)) return BoolExpression(ctx); throw Report.SyntaxException(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'"); } private static IJsonSyntax ArrayExpression(ParserContext ctx) { var previous = ctx.Previous(); List? elements = null; if (!ctx.Check(JsonToken.RightBracket)) { do { elements ??= new(); elements.Add(Expression(ctx)); } while (ctx.Match(JsonToken.Comma)); } ctx.Consume(JsonToken.RightBracket, "Expected ']'"); return new JsonArraySyntax(previous, elements?.ToArray() ?? Array.Empty()); } private static IJsonSyntax ObjectExpression(ParserContext ctx) { var previous = ctx.Previous(); Dictionary.Token, IJsonSyntax>? properties = null; if (!ctx.Check(JsonToken.RightBrace)) { do { var key = ctx.Consume(JsonToken.String, "Expected property name"); ctx.Consume(JsonToken.Colon, "Expected ':' after property name"); properties ??= new(); properties[key] = Expression(ctx); } while (ctx.Match(JsonToken.Comma)); } ctx.Consume(JsonToken.RightBrace, "Expected '}'"); var propertiesArray = properties?.Select(kv => new JsonPropertySyntax(kv.Key, kv.Value)).ToArray(); return new JsonObjectSyntax(previous, propertiesArray ?? Array.Empty()); } private static IJsonSyntax NumberExpression(ParserContext ctx) { if (ctx.Previous().Type != JsonToken.Minus) return new JsonNumberSyntax(ctx.Previous()); var minus = ctx.Previous(); var number = ctx.Consume(JsonToken.Number, "Expected a number after '-'."); return new JsonNumberSyntax( new Lexer.Token(minus.File, JsonToken.Number, $"-{number.Lexeme}", minus.Line, minus.Column) ); } private static IJsonSyntax StringExpression(ParserContext ctx) => new JsonStringSyntax(ctx.Previous()); private static IJsonSyntax NullExpression(ParserContext ctx) => new JsonNullSyntax(ctx.Previous()); private static IJsonSyntax BoolExpression(ParserContext ctx) => new JsonBoolSyntax(ctx.Previous()); }