// parse path value private HoconPath ParseKey() { // sanity check if (_tokens.Current.IsNonSignificant() || _tokens.Current.Type != TokenType.LiteralValue) { throw HoconParserException.Create(_tokens.Current, Path, $"Internal parser error, ParseKey() is called on an invalid token. Should be `{TokenType.LiteralValue}`, found `{_tokens.Current.Type}` instead."); } var keyTokens = new HoconTokenizerResult(); while (_tokens.Current.Type == TokenType.LiteralValue) { keyTokens.Add(_tokens.Current); _tokens.Next(); } // TODO: this is janky, fix this keyTokens.Reverse(); while (keyTokens.Count > 0 && keyTokens[0].LiteralType == TokenLiteralType.Whitespace) { keyTokens.RemoveAt(0); } keyTokens.Reverse(); keyTokens.Add(new Token("", TokenType.EndOfFile, null)); return(HoconPath.FromTokens(keyTokens)); }
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 HoconRoot ParseText(string text, bool resolveSubstitutions, HoconIncludeCallbackAsync includeCallback) { if (string.IsNullOrWhiteSpace(text)) { throw new HoconParserException( $"Parameter {nameof(text)} is null or empty.\n" + "If you want to create an empty Hocon HoconRoot, use \"{}\" instead."); } if (includeCallback != null) { _includeCallback = includeCallback; } try { _tokens = new HoconTokenizer(text).Tokenize(); _root = new HoconValue(null); ParseTokens(); if (resolveSubstitutions) { ResolveSubstitutions(); } } catch (HoconTokenizerException e) { throw HoconParserException.Create(e, null, $"Error while tokenizing Hocon: {e.Message}", e); } catch (HoconException e) { throw HoconParserException.Create(_tokens.Current, Path, e.Message, e); } return(new HoconRoot(_root, _substitutions)); }
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 void ResolveSubstitutions() { foreach (var sub in _substitutions) { // Retrieve value HoconValue res; try { res = ResolveSubstitution(sub); } catch (HoconException e) { throw HoconParserException.Create(sub, sub.Path, $"Invalid substitution declaration. {e.Message}.", e); } if (res != null) { sub.ResolvedValue = res; continue; } // Try to pull value from environment string envValue = null; try { envValue = Environment.GetEnvironmentVariable(sub.Path.Value); } catch (Exception) { // ignored } if (envValue != null) { // undefined value resolved to an environment variable res = new HoconValue(sub.Parent); if (envValue.NeedQuotes()) { res.Add(new HoconQuotedString(sub.Parent, envValue)); } else { res.Add(new HoconUnquotedString(sub.Parent, envValue)); } sub.ResolvedValue = res; continue; } // ${ throws exception if it is not resolved if (sub.Required) { throw HoconParserException.Create(sub, sub.Path, $"Unresolved substitution: {sub.Path}"); } sub.ResolvedValue = new HoconEmptyValue(sub.Parent); } }
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}"); } }
private HoconField ParseField(HoconObject owner) { // sanity check if (_tokens.Current.Type != TokenType.LiteralValue) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon field. Expected start of field {TokenType.LiteralValue}, " + $"found {_tokens.Current.Type} instead."); } var pathDelta = ParseKey(); if (_tokens.Current.IsNonSignificant()) { ConsumeWhitelines(); } // sanity check if (_tokens.Current.Type != TokenType.Assign && _tokens.Current.Type != TokenType.StartOfObject && _tokens.Current.Type != TokenType.PlusEqualAssign) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon field. Expected {TokenType.Assign}, {TokenType.StartOfObject} " + $"or {TokenType.PlusEqualAssign}, found {{_tokens.Current.Type}} instead."); } // sanity check if (pathDelta == null || pathDelta.Count == 0) { throw HoconParserException.Create(_tokens.Current, Path, "Failed to parse Hocon field. ParseField() was called with null or empty path"); } List <HoconField> childInPath = owner.TraversePath(pathDelta); Path.AddRange(pathDelta); HoconField currentField = childInPath[childInPath.Count - 1]; var parsedValue = ParseValue(currentField); foreach (var removedSub in currentField.SetValue(parsedValue)) { _substitutions.Remove(removedSub); } Path.RemoveRange(Path.Count - pathDelta.Count, pathDelta.Count); return(childInPath[0]); }
internal static HoconPath FromTokens(HoconTokenizerResult tokens) { if (tokens == null) { throw new ArgumentNullException(nameof(tokens)); } var result = new List <string>(); var sb = new StringBuilder(); while (tokens.Current.Type == TokenType.LiteralValue) { switch (tokens.Current.LiteralType) { case TokenLiteralType.TripleQuotedLiteralValue: throw HoconParserException.Create(tokens.Current, null, "Triple quoted string could not be used in path expression."); case TokenLiteralType.QuotedLiteralValue: // Normalize quoted keys, remove the quotes if the key doesn't need them. //sb.Append(tokens.Current.Value.NeedQuotes() ? $"\"{tokens.Current.Value}\"" : tokens.Current.Value); sb.Append(tokens.Current.Value); break; default: var split = tokens.Current.Value.Split('.'); for (var i = 0; i < split.Length - 1; ++i) { sb.Append(split[i]); result.Add(sb.ToString()); sb.Clear(); } sb.Append(split[split.Length - 1]); break; } tokens.Next(); } result.Add(sb.ToString()); return(new HoconPath(result)); }
private void ParseField(HoconObject owner) { // sanity check if (_tokens.Current.IsNonSignificant() || _tokens.Current.Type != TokenType.LiteralValue) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon field. Expected start of field {TokenType.LiteralValue}, " + $"found {_tokens.Current.Type} instead."); } var relativePath = ParseKey(); if (_tokens.Current.Type == TokenType.EndOfLine) { _tokens.ToNextSignificantLine(); } // sanity check if (_tokens.Current.Type != TokenType.Assign && _tokens.Current.Type != TokenType.StartOfObject && _tokens.Current.Type != TokenType.PlusEqualAssign) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon field. Expected {TokenType.Assign}, {TokenType.StartOfObject} " + $"or {TokenType.PlusEqualAssign}, found {_tokens.Current.Type} instead."); } // sanity check if (relativePath == null || relativePath.Count == 0) { throw HoconParserException.Create(_tokens.Current, Path, "Failed to parse Hocon field. Null or empty path"); } Path.AddRange(relativePath); var currentField = owner.TraversePath(relativePath); currentField.SetValue(ParseValue(currentField)); Path.RemoveRange(Path.Count - relativePath.Count, relativePath.Count); }
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}"); } }
internal void ResolveValue(IHoconElement child) { if (child.Type == HoconType.Empty) { Remove(child); } else if (Type == HoconType.Empty) { Type = child.Type; } else if (Type != child.Type) { var sub = (HoconSubstitution)child; throw HoconParserException.Create(sub, sub.Path, "Invalid substitution, substituted type must match its sibling type. " + $"Sibling type:{Type}, substitution type:{child.Type}"); } ((HoconField)Parent).ResolveValue(this); }
private void ParseTokens() { if (_tokens.Current.IsNonSignificant() || _tokens.Current.Type == TokenType.EndOfLine) { _tokens.ToNextSignificantLine(); } // Array Hocon files are handled differently than normal Hocon file. // Array Hocon files HAVE to start with an angle bracket and can only contain ONE array if (_tokens.Current.Type == TokenType.StartOfArray) { _root.Add(ParseArray(_root)); // Sanity check if (_tokens.Current.IsNonSignificant() || _tokens.Current.Type == TokenType.EndOfLine) { _tokens.ToNextSignificantLine(); } if (_tokens.Current.Type != TokenType.EndOfFile) { throw HoconParserException.Create(_tokens.Current, null, $"Illegal token type: {_tokens.Current.Type}. Hocon array file can only contain one array.", null); } return; } if (_tokens.Current.Type != TokenType.StartOfObject) { // This is a "headless" Hocon file, we'll normalize the file // by inserting the proper open and close curly brackets _tokens.Insert(new Token("{", TokenType.StartOfObject, null)); _tokens.Insert(_tokens.Count - 1, new Token("}", TokenType.EndOfObject, null)); } ParseObject(ref _root); }
private HoconValue GetHoconValueFromParentElement(IHoconElement parentElement, TokenType type) { if (parentElement is HoconArray arr) { return(new HoconValue(arr)); } if (!(parentElement is HoconField hf)) { throw HoconParserException.Create(_tokens.Current, Path, "Invalid parent element for HoconValue. Valid objects are HoconArray and HoconField."); } var fieldValue = hf.Value; if (fieldValue == null) { return(new HoconValue(hf)); } switch (type) { case TokenType.LiteralValue: return(new HoconValue(hf)); case TokenType.StartOfObject when fieldValue.Type == HoconType.Object: return(fieldValue); case TokenType.StartOfArray when fieldValue.Type == HoconType.Array: return(fieldValue); case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: case TokenType.Include: default: return(new HoconValue(hf)); } }
private HoconValue ParseInclude() { // Sanity check if (_tokens.Current.Type != TokenType.Include) { throw HoconParserException.Create(_tokens.Current, Path, $"Internal parser error, ParseInclude() is called on an invalid token: `{_tokens.Current.Type}`"); } var parenthesisCount = 0; var required = false; var callbackType = HoconCallbackType.File; string fileName = null; var includeToken = _tokens.Current; var expectedTokens = new List <TokenType>(new[] { TokenType.Required, TokenType.Url, TokenType.File, TokenType.Classpath, TokenType.LiteralValue, TokenType.ParenthesisEnd, TokenType.EndOfLine }); var parsing = true; while (parsing) { _tokens.ToNextSignificant(); var currentType = _tokens.Current.Type; if (expectedTokens.All(t => t != currentType)) { throw HoconParserException.Create(_tokens.Current, Path, $"Invalid token in include: `{currentType}`", null); } switch (currentType) { case TokenType.ParenthesisEnd: if (parenthesisCount <= 0) { throw HoconParserException.Create(_tokens.Current, Path, "Unexpected closing parenthesis.", null); } parenthesisCount--; parsing = parenthesisCount > 0; break; case TokenType.Required: _tokens.ToNextSignificant(); // The next token after the "required" keyword have to be an open paren if (_tokens.Current.Type != TokenType.ParenthesisStart) { throw HoconParserException.Create(_tokens.Current, Path, $"Expected {TokenType.ParenthesisStart}, found `{_tokens.Current.Type}` instead."); } parenthesisCount++; required = true; expectedTokens.Remove(TokenType.Required); break; case TokenType.Url: _tokens.ToNextSignificant(); // The next token after the "url" keyword have to be an open paren if (_tokens.Current.Type != TokenType.ParenthesisStart) { throw HoconParserException.Create(_tokens.Current, Path, $"Expected {TokenType.ParenthesisStart}, found `{_tokens.Current.Type}` instead."); } parenthesisCount++; callbackType = HoconCallbackType.Url; expectedTokens.Remove(TokenType.Required); expectedTokens.Remove(TokenType.Url); expectedTokens.Remove(TokenType.File); expectedTokens.Remove(TokenType.Classpath); break; case TokenType.File: _tokens.ToNextSignificant(); // The next token after the "file" keyword have to be an open paren if (_tokens.Current.Type != TokenType.ParenthesisStart) { throw HoconParserException.Create(_tokens.Current, Path, $"Expected {TokenType.ParenthesisStart}, found `{_tokens.Current.Type}` instead."); } parenthesisCount++; callbackType = HoconCallbackType.File; expectedTokens.Remove(TokenType.Required); expectedTokens.Remove(TokenType.Url); expectedTokens.Remove(TokenType.File); expectedTokens.Remove(TokenType.Classpath); break; case TokenType.Classpath: _tokens.ToNextSignificant(); // The next token after the "classpath" keyword have to be an open paren if (_tokens.Current.Type != TokenType.ParenthesisStart) { throw HoconParserException.Create(_tokens.Current, Path, $"Expected {TokenType.ParenthesisStart}, found `{_tokens.Current.Type}` instead."); } parenthesisCount++; callbackType = HoconCallbackType.Resource; expectedTokens.Remove(TokenType.Required); expectedTokens.Remove(TokenType.Url); expectedTokens.Remove(TokenType.File); expectedTokens.Remove(TokenType.Classpath); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { _tokens.ToNextSignificant(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (_tokens.Current.LiteralType != TokenLiteralType.QuotedLiteralValue) { throw HoconParserException.Create(_tokens.Current, Path, $"Invalid literal type for declaring file name. Expected {TokenLiteralType.QuotedLiteralValue}, " + $"found `{_tokens.Current.LiteralType}` instead."); } fileName = _tokens.Current.Value; expectedTokens.Remove(TokenType.LiteralValue); expectedTokens.Remove(TokenType.Required); expectedTokens.Remove(TokenType.Url); expectedTokens.Remove(TokenType.File); expectedTokens.Remove(TokenType.Classpath); parsing = parenthesisCount > 0; break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Unexpected token `{_tokens.Current.Type}`."); } } if (parenthesisCount > 0) { throw HoconParserException.Create(_tokens.Current, Path, $"Expected {TokenType.ParenthesisEnd}, found `{_tokens.Current.Type}`"); } if (fileName == null) { throw HoconParserException.Create(_tokens.Current, Path, "Include does not contain any quoted file name value."); } // Consume the last token _tokens.ToNextSignificant(); var includeHocon = _includeCallback(callbackType, fileName).ConfigureAwait(false).GetAwaiter().GetResult(); if (string.IsNullOrWhiteSpace(includeHocon)) { if (required) { throw HoconParserException.Create(includeToken, Path, "Invalid Hocon include. Include was declared as required but include callback returned a null or empty string."); } return(new HoconEmptyValue(null)); } var includeRoot = new HoconParser().ParseText(includeHocon, false, _includeCallback); /* * if (owner != null && owner.Type != HoconType.Empty && owner.Type != includeRoot.Value.Type) * throw HoconParserException.Create(includeToken, Path, * "Invalid Hocon include. Hocon config substitution type must be the same as the field it's merged into. " + * $"Expected type: `{owner.Type}`, type returned by include callback: `{includeRoot.Value.Type}`"); */ // fixup the substitution, add the current path as a prefix to the substitution path foreach (var substitution in includeRoot.Substitutions) { substitution.Path.InsertRange(0, Path); } _substitutions.AddRange(includeRoot.Substitutions); // re-parent the value returned by the callback to the owner of the include declaration return(includeRoot.Value); }
// The owner in this context can be either an object or an array. private HoconObject ParseObject(IHoconElement owner) { var hoconObject = new HoconObject(owner); if (_tokens.Current.Type != TokenType.StartOfObject && _tokens.Current.Type != TokenType.LiteralValue && _tokens.Current.Type != TokenType.Include) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.StartOfObject}` or `{TokenType.LiteralValue}, " + $"found `{_tokens.Current.Type}` instead."); } var headless = true; if (_tokens.Current.Type == TokenType.StartOfObject) { headless = false; 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 object. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}`, " + $"found `{_tokens.Current.Type}` instead."); } lastValue = ParseInclude(hoconObject); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { ConsumeWhitespace(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (lastValue != null) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}`, " + $"found `{_tokens.Current.Type}` instead."); } lastValue = ParseField(hoconObject); break; // TODO: can an object be declared floating without being assigned to a field? //case TokenType.StartOfObject: case TokenType.Comment: case TokenType.EndOfLine: switch (lastValue) { case null: ConsumeWhitelines(); break; case HoconField _: break; default: ((HoconValue)hoconObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.Comma: switch (lastValue) { case null: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.Assign}` or `{TokenType.StartOfObject}`, " + $"found `{_tokens.Current.Type}` instead."); case HoconField _: break; default: ((HoconValue)hoconObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; ConsumeWhitelines(); break; case TokenType.EndOfObject: case TokenType.EndOfFile: if (headless && _tokens.Current.Type != TokenType.EndOfFile) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected {TokenType.EndOfFile} but found {_tokens.Current.Type} instead."); } if (!headless && _tokens.Current.Type != TokenType.EndOfObject) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected {TokenType.EndOfObject} but found {_tokens.Current.Type} instead."); } switch (lastValue) { case null: break; case HoconField _: break; default: ((HoconValue)hoconObject.Parent).Add(lastValue.GetObject()); break; } lastValue = null; parsing = false; break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Unexpected token `{_tokens.Current.Type}`."); } } if (_tokens.Current.Type == TokenType.EndOfObject) { _tokens.Next(); } return(hoconObject); }
/// <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) { // sanity check if (_tokens.Current.Type != TokenType.StartOfArray) { throw HoconParserException.Create(_tokens.Current, Path, "Failed to parse Hocon array. " + $"Expected {TokenType.StartOfArray}, found {_tokens.Current.Type} instead."); } var currentArray = new HoconArray(owner); // consume start of array token _tokens.ToNextSignificant(); var valueWasParsed = false; var parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: if (valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } currentArray.Add(ParseValue(currentArray)); valueWasParsed = true; break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { _tokens.ToNextSignificant(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } currentArray.Add(ParseValue(currentArray)); valueWasParsed = true; break; case TokenType.StartOfObject: if (valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } currentArray.Add(ParseValue(currentArray)); valueWasParsed = true; break; case TokenType.StartOfArray: if (valueWasParsed) { 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. currentArray.Add(ParseValue(currentArray)); valueWasParsed = true; break; case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: if (valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}, " + $"found `{_tokens.Current.Type}` instead."); } currentArray.Add(ParseValue(currentArray)); valueWasParsed = true; break; case TokenType.EndOfArray: valueWasParsed = false; // If there is a next array on the same like - let's move to it's first element if (_tokens.ForwardMatch(TokenType.StartOfArray)) { _tokens.ToNextSignificant(); _tokens.Next(); } else { // Otherwise, array value is fully loaded and nothing to do here parsing = false; } break; case TokenType.Comment: case TokenType.EndOfLine: valueWasParsed = false; _tokens.ToNextSignificantLine(); break; case TokenType.Comma: if (!valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected a valid value, found `{_tokens.Current.Type}` instead."); } valueWasParsed = false; _tokens.ToNextSignificant(); 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); }
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 _tokens.ToNextSignificant(); switch (_tokens.Current.Type) { case TokenType.Include: var includeToken = _tokens.Current; var includeValue = ParseInclude(); if (includeValue.Type == HoconType.Empty) { break; } if (currentArray.Type != HoconType.Empty && currentArray.Type != includeValue.Type) { throw HoconParserException.Create(includeToken, Path, "Invalid Hocon include. Hocon config substitution type must be the same as the field it's merged into. " + $"Expected type: `{currentArray.Type}`, type returned by include callback: `{includeValue.Type}`"); } currentArray.Add((HoconValue)includeValue.Clone(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(ParseValue(currentArray)); break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { _tokens.ToNextSignificant(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } currentArray.Add(ParseValue(currentArray)); break; case TokenType.SubstituteOptional: case TokenType.SubstituteRequired: currentArray.Add(ParseValue(currentArray)); break; default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon array. Expected {TokenType.EndOfArray} but found {_tokens.Current.Type} instead."); } 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) { // 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); }
/// <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); }
/// <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); }
// The owner in this context can be either an object or an array. private void ParseObject(ref HoconValue owner) { if (owner == null) { throw HoconParserException.Create(_tokens.Current, Path, "ParseObject called with null parent."); } // Sanity check if (_tokens.Current.Type != TokenType.StartOfObject) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.StartOfObject}`, found `{_tokens.Current.Type}` instead."); } // Consume curly bracket _tokens.ToNextSignificant(); var hoconObject = owner.GetObject(); if (hoconObject == null) { hoconObject = new HoconObject(owner); owner.Add(hoconObject); } var valueWasParsed = false; var parsing = true; while (parsing) { switch (_tokens.Current.Type) { case TokenType.Include: if (valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}`, " + $"found `{_tokens.Current.Type}` instead."); } owner.ReParent(ParseInclude()); valueWasParsed = true; break; case TokenType.LiteralValue: if (_tokens.Current.IsNonSignificant()) { _tokens.ToNextSignificant(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } if (valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.Comma}` or `{TokenType.EndOfLine}`, " + $"found `{_tokens.Current.Type}` instead."); } ParseField(hoconObject); valueWasParsed = true; break; case TokenType.Comment: case TokenType.EndOfLine: valueWasParsed = false; _tokens.ToNextSignificantLine(); break; case TokenType.Comma: if (!valueWasParsed) { throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected `{TokenType.Assign}` or `{TokenType.StartOfObject}`, " + $"found `{_tokens.Current.Type}` instead."); } valueWasParsed = false; _tokens.ToNextSignificant(); break; case TokenType.EndOfObject: valueWasParsed = false; parsing = false; break; case TokenType.EndOfFile: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Expected {TokenType.EndOfObject} but found {_tokens.Current.Type} instead."); default: throw HoconParserException.Create(_tokens.Current, Path, $"Failed to parse Hocon object. Unexpected token `{_tokens.Current.Type}`."); } } // Consume the closing curly bracket. _tokens.ToNextSignificant(); }
private void ParseTokens() { if (_tokens.Current.IsNonSignificant()) { ConsumeWhitelines(); } while (_tokens.Current.Type != TokenType.EndOfFile) { switch (_tokens.Current.Type) { case TokenType.Include: var parsedInclude = ParseInclude(null); if (_root.Type != HoconType.Object) { _root.Clear(); _root.Add(parsedInclude.GetObject()); } else { _root.Add(parsedInclude.GetObject()); } break; // Hocon config file may contain one array and one array only case TokenType.StartOfArray: _root.Clear(); _root.Add(ParseArray(null)); ConsumeWhitelines(); if (_tokens.Current.Type != TokenType.EndOfFile) { throw HoconParserException.Create(_tokens.Current, Path, "Hocon config can only contain one array or one object."); } return; case TokenType.StartOfObject: { var parsedObject = ParseObject(null); if (_root.Type != HoconType.Object) { _root.Clear(); _root.Add(parsedObject); } else { _root.Add(parsedObject.GetObject()); } break; } case TokenType.LiteralValue: { if (_tokens.Current.IsNonSignificant()) { ConsumeWhitelines(); } if (_tokens.Current.Type != TokenType.LiteralValue) { break; } var parsedObject = ParseObject(null); if (_root.Type != HoconType.Object) { _root.Clear(); _root.Add(parsedObject); } else { _root.Add(parsedObject.GetObject()); } break; } case TokenType.Comment: case TokenType.EndOfLine: case TokenType.EndOfFile: case TokenType.EndOfObject: case TokenType.EndOfArray: _tokens.Next(); break; default: throw HoconParserException.Create(_tokens.Current, null, $"Illegal token type: {_tokens.Current.Type}", null); } } }