internal void ResolveValue(HoconSubstitution child) { if (child.Type == HoconType.Empty) { Remove(child); } else { if (Type == HoconType.Empty) { Type = child.Type; } else if (Type != child.Type) { throw HoconParserException.Create(child, child.Path, "Invalid substitution, substituted type must match its sibling type. " + $"Sibling type:{Type}, substitution type:{child.Type}"); } } switch (Parent) { case HoconField v: v.ResolveValue(this); break; case HoconArray a: a.ResolveValue(child); break; default: throw new Exception($"Invalid parent type while resolving substitution:{Parent.GetType()}"); } }
private HoconArray ParsePlusEqualAssignArray(IHoconElement owner) { // sanity check if (_tokens.Current.Type != TokenType.PlusEqualAssign) { throw HoconParserException.Create(_tokens.Current, Path, "Failed to parse Hocon field with += operator. " + $"Expected {TokenType.PlusEqualAssign}, found {{_tokens.Current.Type}} instead."); } var currentArray = new HoconArray(owner); // consume += operator token ConsumeWhitelines(); switch (_tokens.Current.Type) { case TokenType.Include: currentArray.Add(ParseInclude(currentArray)); break; case TokenType.StartOfArray: // Array inside of arrays are parsed as values because it can be value concatenated with another array. currentArray.Add(ParseValue(currentArray)); break; case TokenType.StartOfObject: currentArray.Add(ParseObject(currentArray)); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitelines(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } currentArray.Add(ParseValue(currentArray)); break; case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: var pointerPath = HoconPath.Parse(_tokens.Current.Value); HoconSubstitution sub = new HoconSubstitution(currentArray, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.SubstituteRequired); _substitutions.Add(sub); currentArray.Add(sub); _tokens.Next(); break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected {TokenType.EndOfArray} but found {_tokens.Current.Type} instead."); } return(currentArray); }
internal void ResolveValue(HoconSubstitution child) { var index = IndexOf(child); Remove(child); if (child.Type != HoconType.Empty) { if (Type == HoconType.Empty) { Type = child.Type; } else if (!Type.IsMergeable(child.Type)) { throw HoconParserException.Create(child, child.Path, "Invalid substitution, substituted type be must be mergeable with its sibling type. " + $"Sibling type:{Type}, substitution type:{child.Type}"); } var clonedValue = (HoconValue)child.ResolvedValue.Clone(Parent); switch (Type) { case HoconType.Object: Insert(index, clonedValue.GetObject()); break; case HoconType.Array: var hoconArray = new HoconArray(this); hoconArray.AddRange(clonedValue.GetArray()); Insert(index, hoconArray); break; case HoconType.Boolean: case HoconType.Number: case HoconType.String: var elementList = new List <IHoconElement>(); foreach (var element in clonedValue) { elementList.Add(element); } InsertRange(index, elementList); break; } } switch (Parent) { case HoconField v: v.ResolveValue(this); break; case HoconArray a: a.ResolveValue(child); break; default: throw new Exception($"Invalid parent type while resolving substitution:{Parent.GetType()}"); } }
private HoconValue ResolveSubstitution(HoconSubstitution sub) { var subField = sub.ParentField; // first case, this substitution is a direct self-reference if (sub.Path == subField.Path) { var parent = sub.Parent; while (parent is HoconValue) { parent = parent.Parent; } // Fail case if (parent is HoconArray) { throw new HoconException("Self-referencing substitution may not be declared within an array."); } // try to resolve substitution by looking backward in the field assignment stack return(subField.OlderValueThan(sub)); } // second case, the substitution references a field child in the past if (sub.Path.IsChildPathOf(subField.Path)) { var olderValue = subField.OlderValueThan(sub); if (olderValue.Type == HoconType.Object) { var difLength = sub.Path.Count - subField.Path.Count; var deltaPath = sub.Path.SubPath(sub.Path.Count - difLength, difLength); var olderObject = olderValue.GetObject(); if (olderObject.TryGetValue(deltaPath, out var innerValue)) { return(innerValue.Type == HoconType.Object ? innerValue : null); } } } // Detect invalid parent-referencing substitution if (subField.Path.IsChildPathOf(sub.Path)) { throw new HoconException("Substitution may not reference one of its direct parents."); } // Detect invalid cyclic reference loop if (IsValueCyclic(subField, sub)) { throw new HoconException("A cyclic substitution loop is detected in the Hocon file."); } // third case, regular substitution _root.GetObject().TryGetValue(sub.Path, out var field); return(field?.Clone(field.Parent) as HoconValue); }
internal void ResolveValue(HoconSubstitution sub) { if (sub.Type == HoconType.Empty) { Remove(sub); return; } if (sub.Type != HoconType.Array) { throw HoconParserException.Create(sub, sub.Path, $"Substitution value must match the rest of the field type or empty. Parent value type: {Type}, substitution type: {sub.Type}"); } }
internal void ResolveValue(HoconSubstitution sub) { var subValue = (HoconValue)sub.Parent; if (sub.Type == HoconType.Empty) { subValue.Remove(sub); if (subValue.Count == 0) { Remove(subValue); } return; } if (_arrayType != HoconType.Empty && sub.Type != _arrayType) { throw HoconParserException.Create(sub, sub.Path, $"Substitution value must match the rest of the field type or empty. Array value type: {_arrayType}, substitution type: {sub.Type}"); } }
/// <summary> /// Retrieves the next value token from the tokenizer and appends it /// to the supplied element <paramref name="owner" />. /// </summary> /// <param name="owner">The element to append the next token.</param> /// <exception cref="System.Exception">End of file reached while trying to read a value</exception> private HoconValue ParseValue(IHoconElement owner) { // value is lazy initialized because we don't know what kind of value we're parsing HoconValue value = null; var parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: var includeToken = _tokens.Current; var includeValue = ParseInclude(); switch (includeValue.Type) { case HoconType.Empty: value = new HoconEmptyValue(owner); break; case HoconType.Object: value = GetHoconValueFromParentElement(owner, TokenType.StartOfObject); value.ReParent(includeValue); break; case HoconType.Array: value = GetHoconValueFromParentElement(owner, TokenType.StartOfArray); value.ReParent(includeValue); break; default: throw HoconParserException.Create(includeToken, Path, "Include could never contain a literal type."); } break; case TokenType.LiteralValue: // Consume leading whitespaces. if (_tokens.Current.IsNonSignificant()) { _tokens.ToNextSignificant(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (value == null) { value = GetHoconValueFromParentElement(owner, _tokens.Current.Type); } while (_tokens.Current.Type == TokenType.LiteralValue) { value.Add(HoconLiteral.Create(value, _tokens.Current)); _tokens.Next(); } break; case TokenType.StartOfObject: if (value == null) { value = GetHoconValueFromParentElement(owner, _tokens.Current.Type); } ParseObject(ref value); break; case TokenType.StartOfArray: if (value == null) { value = GetHoconValueFromParentElement(owner, _tokens.Current.Type); } // If this array is already initialized, we are going to overwrite it if (value.Type == HoconType.Array && value.Count > 0) { value.Clear(); } value.Add(ParseArray(value)); break; case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: if (value == null) { value = new HoconValue(owner); } var pointerPath = HoconPath.Parse(_tokens.Current.Value); var sub = new HoconSubstitution(value, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.SubstituteRequired); _substitutions.Add(sub); _tokens.Next(); value.Add(sub); break; case TokenType.PlusEqualAssign: if (value == null) { value = new HoconValue(owner); } var subAssign = new HoconSubstitution(value, new HoconPath(Path), _tokens.Current, false); _substitutions.Add(subAssign); value.Add(subAssign); value.Add(ParsePlusEqualAssignArray(value)); parsing = false; break; case TokenType.EndOfObject: case TokenType.EndOfArray: parsing = false; break; case TokenType.Comment: _tokens.ToNextSignificant(); break; case TokenType.EndOfLine: parsing = false; break; case TokenType.EndOfFile: case TokenType.Comma: parsing = false; break; case TokenType.Assign: // Special case to support end of line after assign _tokens.ToNextSignificantLine(); break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon value. Unexpected token: `{_tokens.Current.Type}`"); } } if (value == null) { value = new HoconEmptyValue(owner); } // trim trailing whitespace if result is a literal if (value.Type.IsLiteral()) { if (value[value.Count - 1] is HoconWhitespace) { value.RemoveAt(value.Count - 1); } } return(value); }
private bool IsValueCyclic(HoconField field, HoconSubstitution sub) { var pendingValues = new Stack <HoconValue>(); var visitedFields = new List <HoconField> { field }; var pendingSubs = new Stack <HoconSubstitution>(); pendingSubs.Push(sub); while (pendingSubs.Count > 0) { var currentSub = pendingSubs.Pop(); if (!_root.GetObject().TryGetField(currentSub.Path, out var currentField)) { continue; } if (visitedFields.Contains(currentField)) { return(true); } visitedFields.Add(currentField); pendingValues.Push(currentField.Value); while (pendingValues.Count > 0) { var currentValue = pendingValues.Pop(); foreach (var value in currentValue) { switch (value) { case HoconLiteral _: break; case HoconObject o: foreach (var f in o.Values) { if (visitedFields.Contains(f)) { return(true); } visitedFields.Add(f); pendingValues.Push(f.Value); } break; case HoconArray a: foreach (var item in a.GetArray()) { pendingValues.Push(item); } break; case HoconSubstitution s: pendingSubs.Push(s); break; } } } } return(false); }
/// <summary> /// Retrieves the next value token from the tokenizer and appends it /// to the supplied element <paramref name="owner"/>. /// </summary> /// <param name="owner">The element to append the next token.</param> /// <exception cref="System.Exception">End of file reached while trying to read a value</exception> public void ParseValue(HoconValue owner, string currentPath) { if (_reader.EoF) { throw new HoconParserException("End of file reached while trying to read a value"); } _reader.PullWhitespaceAndComments(); var start = _reader.Index; try { while (_reader.IsValue()) { Token t = _reader.PullValue(); switch (t.Type) { case TokenType.EoF: break; case TokenType.LiteralValue: if (owner.IsObject()) { //needed to allow for override objects owner.Clear(); } var lit = new HoconLiteral { Value = t.Value }; owner.AppendValue(lit); break; case TokenType.ObjectStart: ParseObject(owner, true, currentPath); break; case TokenType.ArrayStart: HoconArray arr = ParseArray(currentPath); owner.AppendValue(arr); break; case TokenType.Substitute: HoconSubstitution sub = ParseSubstitution(t.Value); _substitutions.Add(sub); owner.AppendValue(sub); break; } if (_reader.IsSpaceOrTab()) { ParseTrailingWhitespace(owner); } } IgnoreComma(); } catch (HoconTokenizerException tokenizerException) { throw new HoconParserException(string.Format("{0}\r{1}", tokenizerException.Message, GetDiagnosticsStackTrace()), tokenizerException); } finally { //no value was found, tokenizer is still at the same position if (_reader.Index == start) { throw new HoconParserException(string.Format("Hocon syntax error {0}\r{1}", _reader.GetHelpTextAtIndex(start), GetDiagnosticsStackTrace())); } } }
/// <summary> /// Retrieves the next array token from the tokenizer. /// </summary> /// <returns>An array of elements retrieved from the token.</returns> private HoconArray ParseArray(IHoconElement owner) { var currentArray = new HoconArray(owner); // consume start of array token ConsumeWhitelines(); IHoconElement lastValue = null; var parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: if (lastValue != null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } lastValue = ParseInclude(currentArray); break; case TokenType.StartOfArray: if (lastValue != null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } // Array inside of arrays are parsed as values because it can be value concatenated with another array. lastValue = ParseValue(currentArray); break; case TokenType.StartOfObject: if (lastValue != null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } lastValue = ParseObject(currentArray); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitelines(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (lastValue != null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } lastValue = ParseValue(currentArray); break; case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: if (lastValue != null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } var pointerPath = HoconPath.Parse(_tokens.Current.Value); HoconSubstitution sub = new HoconSubstitution(currentArray, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.SubstituteRequired); _substitutions.Add(sub); lastValue = sub; _tokens.Next(); break; case TokenType.Comment: case TokenType.EndOfLine: if (lastValue == null) { ConsumeWhitelines(); break; } switch (lastValue.Type) { case HoconType.Array: currentArray.Add(lastValue); break; default: currentArray.Add(lastValue); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.Comma: if (lastValue == null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected a valid value, found `{_tokens.Current.Type}` instead."); } switch (lastValue.Type) { case HoconType.Array: currentArray.Add(lastValue); break; default: currentArray.Add(lastValue); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.EndOfArray: if (lastValue != null) { switch (lastValue.Type) { case HoconType.Array: currentArray.Add(lastValue); break; default: currentArray.Add(lastValue); break; } lastValue = null; } parsing = false; break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected {TokenType.EndOfArray} but found {_tokens.Current.Type} instead."); } } // Consume end of array token _tokens.Next(); return(currentArray); }
/// <summary> /// Retrieves the next value token from the tokenizer and appends it /// to the supplied element <paramref name="owner"/>. /// </summary> /// <param name="owner">The element to append the next token.</param> /// <exception cref="System.Exception">End of file reached while trying to read a value</exception> private HoconValue ParseValue(IHoconElement owner) { var value = new HoconValue(owner); var parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: value.Add(ParseInclude(value)); break; case TokenType.LiteralValue: // Consume leading whitespaces. if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } while (_tokens.Current.Type == TokenType.LiteralValue) { value.Add(HoconLiteral.Create(value, _tokens.Current)); _tokens.Next(); } break; case TokenType.StartOfObject: value.Add(ParseObject(value)); break; case TokenType.StartOfArray: value.Add(ParseArray(value)); break; case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: var pointerPath = HoconPath.Parse(_tokens.Current.Value); HoconSubstitution sub = new HoconSubstitution(value, pointerPath, _tokens.Current, _tokens.Current.Type == TokenType.SubstituteRequired); _substitutions.Add(sub); value.Add(sub); _tokens.Next(); break; case TokenType.EndOfObject: case TokenType.EndOfArray: parsing = false; break; // comments automatically stop value parsing. case TokenType.Comment: ConsumeWhitelines(); parsing = false; break; case TokenType.EndOfLine: parsing = false; break; case TokenType.EndOfFile: case TokenType.Comma: parsing = false; break; case TokenType.PlusEqualAssign: HoconSubstitution subAssign = new HoconSubstitution(value, new HoconPath(Path), _tokens.Current, false); _substitutions.Add(subAssign); value.Add(subAssign); value.Add(ParsePlusEqualAssignArray(value)); parsing = false; break; case TokenType.Assign: ConsumeWhitelines(); break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon value. Unexpected token: `{_tokens.Current.Type}`"); } } // trim trailing whitespace if result is a literal if (value.Type == HoconType.Literal) { if (value[value.Count - 1] is HoconWhitespace) { value.RemoveAt(value.Count - 1); } } return(value); }