/// <summary>Construct a token string and permanently apply config values.</summary>
        public TokenString Build()
        {
            TokenString tokenString = new TokenString(this.RawValue, this.ConditionTokens, TokenStringBuilder.TokenPattern);
            InvariantDictionary <string> configValues = new InvariantDictionary <string>(this.Config.ToDictionary(p => p.Key, p => p.Value.Value.FirstOrDefault()));

            tokenString.ApplyPermanently(configValues);
            return(tokenString);
        }
        /// <summary>Construct an instance.</summary>
        /// <param name="lexTokens">The lexical tokens parsed from the raw string.</param>
        /// <param name="context">The available token context.</param>
        /// <param name="path">The path to the value from the root content file.</param>
        public TokenString(ILexToken[] lexTokens, IContext context, LogPathBuilder path)
        {
            // get lexical tokens
            this.Parts =
                (
                    from token in (lexTokens ?? new ILexToken[0])
                    let input = token is LexTokenToken lexToken && lexToken.HasInputArgs()
                        ? new TokenString(lexToken.InputArgs.Parts, context, path.With(lexToken.Name))
                                : null
                                select new TokenStringPart(token, input)
                )
                .ToArray();

            // set raw value
            this.Raw = string.Join("", this.Parts.Select(p => p.LexToken)).Trim();
            if (string.IsNullOrWhiteSpace(this.Raw))
            {
                this.Value = this.Raw;
                return;
            }

            // extract tokens
            bool isMutable = false;
            bool hasTokens = false;

            foreach (LexTokenToken lexToken in this.GetTokenPlaceholders(this.LexTokens, recursive: true))
            {
                hasTokens = true;
                IToken token = context.GetToken(lexToken.Name, enforceContext: false);
                if (token != null)
                {
                    this.TokensUsed.Add(token.Name);
                    isMutable = isMutable || token.IsMutable;
                }
                else
                {
                    string requiredModId = lexToken.GetProviderModId();
                    if (!string.IsNullOrWhiteSpace(requiredModId) && !context.IsModInstalled(requiredModId))
                    {
                        this.State.AddUnavailableModTokens(requiredModId);
                    }
                    else
                    {
                        this.State.AddInvalidTokens(lexToken.Name);
                    }

                    isMutable = true; // can't optimize away the token value if it's invalid
                }
            }

            // set metadata
            this.IsMutable         = isMutable;
            this.HasAnyTokens      = hasTokens;
            this.IsSingleTokenOnly = TokenString.GetIsSingleTokenOnly(this.Parts);
            this.Path = path.ToString();

            // set initial context
            if (this.State.IsReady)
            {
                this.UpdateContext(context, forceUpdate: true);
            }
        }
 /*********
 ** Public methods
 *********/
 /// <summary>Construct an instance.</summary>
 /// <param name="lexToken">The underlying lex token.</param>
 /// <param name="input">The parsed version of <see cref="Input"/>.</param>
 public TokenStringPart(ILexToken lexToken, TokenString input)
 {
     this.LexToken  = lexToken;
     this.Input     = input;
     this.InputArgs = new InputArguments(input);
 }