public (GreenJsonValueSyntax, JsonSymbolType) ParseMap() { var mapBuilder = new List <GreenJsonKeyValueSyntax>(); var keyValueSyntaxBuilder = new List <GreenJsonMultiValueSyntax>(); // Maintain a separate set of keys to aid error reporting on duplicate keys. HashSet <string> foundKeys = new HashSet <string>(); for (; ;) { // Save CurrentLength for error reporting before parsing the key. int keyStart = CurrentLength; GreenJsonMultiValueSyntax multiKeyNode = ParseMultiValue(JsonErrorCode.MultiplePropertyKeys); GreenJsonValueSyntax parsedKeyNode = multiKeyNode.ValueNode.ContentNode; // Analyze if this is an actual, unique property key. int parsedKeyNodeStart = keyStart + multiKeyNode.ValueNode.BackgroundBefore.Length; bool gotKey; Maybe <GreenJsonStringLiteralSyntax> validKey = Maybe <GreenJsonStringLiteralSyntax> .Nothing; switch (parsedKeyNode) { case GreenJsonMissingValueSyntax _: gotKey = false; break; case GreenJsonStringLiteralSyntax stringLiteral: gotKey = true; string propertyKey = stringLiteral.Value; // Expect unique keys. if (!foundKeys.Contains(propertyKey)) { validKey = stringLiteral; foundKeys.Add(propertyKey); } else { Report(new JsonErrorInfo( JsonErrorCode.PropertyKeyAlreadyExists, parsedKeyNodeStart, parsedKeyNode.Length, // Take the substring, key may contain escape sequences. new JsonErrorInfoParameter <string>(Json.Substring(parsedKeyNodeStart + 1, parsedKeyNode.Length - 2)))); } break; default: gotKey = true; Report(new JsonErrorInfo( JsonErrorCode.InvalidPropertyKey, parsedKeyNodeStart, parsedKeyNode.Length)); break; } // Reuse keyValueSyntaxBuilder. keyValueSyntaxBuilder.Clear(); keyValueSyntaxBuilder.Add(multiKeyNode); // Keep parsing multi-values until encountering a non ':'. JsonSymbolType symbolType = CurrentToken.SymbolType; bool gotColon = false; while (symbolType == JsonSymbolType.Colon) { if (gotColon) { // Multiple ':' without a ','. Report(new JsonErrorInfo( JsonErrorCode.MultiplePropertyKeySections, CurrentLength - CurrentToken.Length, CurrentToken.Length)); } else { gotColon = true; } // ParseMultiValue() guarantees that the next symbol is never a ValueStartSymbol. keyValueSyntaxBuilder.Add(ParseMultiValue(JsonErrorCode.MultipleValues)); symbolType = CurrentToken.SymbolType; } // One key-value section done. var jsonKeyValueSyntax = new GreenJsonKeyValueSyntax(validKey, keyValueSyntaxBuilder); mapBuilder.Add(jsonKeyValueSyntax); bool isComma = symbolType == JsonSymbolType.Comma; bool isCurlyClose = symbolType == JsonSymbolType.CurlyClose; // '}' directly following a ',' should not report errors. // '..., : }' however misses both a key and a value. if (isComma || (isCurlyClose && (gotKey || gotColon))) { // Report missing property key and/or value. if (!gotKey) { Report(new JsonErrorInfo( JsonErrorCode.MissingPropertyKey, CurrentLength - CurrentToken.Length, CurrentToken.Length)); } // Report missing value error from being reported if all value sections are empty. // Example: { "key1":: 2, "key2": , } // Skip the fist value section, it contains the key node. if (jsonKeyValueSyntax.ValueSectionNodes.Skip(1).All(x => x.ValueNode.ContentNode is GreenJsonMissingValueSyntax)) { Report(new JsonErrorInfo( JsonErrorCode.MissingValue, CurrentLength - CurrentToken.Length, CurrentToken.Length)); } } if (!isComma) { if (isCurlyClose) { return(new GreenJsonMapSyntax(mapBuilder, missingCurlyClose: false), ShiftToNextForegroundToken()); } // ']', EOF; assume missing closing bracket '}'. Report(new JsonErrorInfo( symbolType == JsonSymbolType.Eof ? JsonErrorCode.UnexpectedEofInObject : JsonErrorCode.ControlSymbolInObject, CurrentLength - CurrentToken.Length, CurrentToken.Length)); return(new GreenJsonMapSyntax(mapBuilder, missingCurlyClose: true), symbolType); } } }
public virtual TResult Visit(GreenJsonValueSyntax node, T arg) => node == null ? default : node.Accept(this, arg);
/// <summary> /// Initializes a new instance of <see cref="GreenJsonValueWithBackgroundSyntax"/>. /// </summary> /// <param name="backgroundBefore"> /// The background symbols which directly precede the content value node. /// </param> /// <param name="contentNode"> /// The content node containing the actual value. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="backgroundBefore"/> and/or <paramref name="contentNode"/> are null. /// </exception> public GreenJsonValueWithBackgroundSyntax(GreenJsonBackgroundListSyntax backgroundBefore, GreenJsonValueSyntax contentNode) { BackgroundBefore = backgroundBefore ?? throw new ArgumentNullException(nameof(backgroundBefore)); ContentNode = contentNode ?? throw new ArgumentNullException(nameof(contentNode)); Length = BackgroundBefore.Length + ContentNode.Length; }
public virtual TResult DefaultVisit(GreenJsonValueSyntax node, T arg) => default;