using DevDisciples.Parsing; namespace DevDisciples.Json.Parser; public static partial class JsonParser { public static ISyntaxNode 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 ISyntaxNode Expression(ParserContext ctx) { if (ctx.Match(JsonToken.LeftBracket)) return Array(ctx); if (ctx.Match(JsonToken.LeftBrace)) return Object(ctx); if (ctx.Match(JsonToken.Minus) || ctx.Match(JsonToken.Number)) return Number(ctx); if (ctx.Match(JsonToken.String)) return String(ctx); if (ctx.Match(JsonToken.Null)) return Null(ctx); if (ctx.Match(JsonToken.True) || ctx.Match(JsonToken.False)) return Bool(ctx); throw Report.Error(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'"); } private static ISyntaxNode Array(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 JsonArray(previous, elements?.ToArray() ?? System.Array.Empty()); } private static ISyntaxNode Object(ParserContext ctx) { var previous = ctx.Previous(); Dictionary? 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.Lexeme] = Expression(ctx); } while (ctx.Match(JsonToken.Comma)); } ctx.Consume(JsonToken.RightBrace, "Expected '}'"); var propertiesArray = properties?.Select(kv => new JsonObject.Property(kv.Key, kv.Value)).ToArray(); return new JsonObject(previous, propertiesArray ?? System.Array.Empty()); } private static ISyntaxNode Number(ParserContext ctx) { if (ctx.Previous().Type != JsonToken.Minus) return new JsonNumber(ctx.Previous()); var minus = ctx.Previous(); var number = ctx.Consume(JsonToken.Number, "Expected a number after '-'."); return new JsonNumber( new Lexer.Token(minus.File, JsonToken.Number, $"-{number.Lexeme}", minus.Line, minus.Column) ); } private static ISyntaxNode String(ParserContext ctx) => new JsonString(ctx.Previous()); private static ISyntaxNode Null(ParserContext ctx) => new JsonNull(ctx.Previous()); private static ISyntaxNode Bool(ParserContext ctx) => new JsonBool(ctx.Previous()); }