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); }
/// <summary> /// Generates a configuration defined in the supplied /// HOCON (Human-Optimized Config Object Notation) string. /// </summary> /// <param name="hocon">A string that contains configuration options to use.</param> /// <param name="includeCallback">callback used to resolve includes</param> /// <returns>The configuration defined in the supplied HOCON string.</returns> public static Config ParseString(string hocon, HoconIncludeCallbackAsync includeCallback) { HoconRoot res = HoconParser.Parse(hocon, includeCallback); return(new Config(res)); }