/// <summary>
        /// parse and add default attribute to entry
        /// <para>"def_bool" / "def_tristate" &lt; expr &gt; ["if" &lt; expr &gt;]</para>
        /// </summary>
        /// <param name="sr">for get location</param>
        /// <param name="entry">attribute will add to this entry</param>
        /// <param name="inStr">source line</param>
        /// <param name="type">"def_bool"/"def_tristate"</param>
        private static void ParseAttrDefaultType(FileReader sr, MenuEntry entry, string inStr,
                                                 MenuAttributeType type)
        {
            // remove "def_"
            if (!Enum.TryParse(type.ToString().Substring(4),
                               true, out MenuAttributeType defValType))
            {
                throw sr.GenExpWithLocation($"Fail to parse \"{type}\" attribute.");
            }

            if (entry.Attributes.Any(attribute =>
                                     attribute.AttributeType == MenuAttributeType.ValueType))
            {
                throw sr.GenExpWithLocation(
                          $"Could not add {type} attribute when \"ValueType\" attribute exist.");
            }

            entry.Attributes.Add(new MenuAttribute
            {
                AttributeType  = MenuAttributeType.ValueType,
                ExpressionType = defValType
            });

            //set entry value type
            entry.ValueType = defValType;

            if (!string.IsNullOrEmpty(inStr))
            {
                ParseAttrWithValAndCond(sr, entry, inStr, MenuAttributeType.Default);
            }
        }
        /// <summary>
        /// Add value type and prompt to entry
        /// <para>"bool"/"tristate"/"string"/"hex"/"int" [&lt;prompt&gt;]</para>
        /// </summary>
        /// <param name="sr">for get location</param>
        /// <param name="entry">attribute will add to this entry</param>
        /// <param name="inStr">prompt</param>
        /// <param name="type">"bool"/"tristate"/"string"/"hex"/"int"</param>
        private static void ParseAttrValueType(FileReader sr, MenuEntry entry, string inStr,
                                               MenuAttributeType type)
        {
            // type definition: "bool"/"tristate"/"string"/"hex"/"int" [<prompt>]
            if (entry.Attributes.Any(attribute =>
                                     attribute.AttributeType == MenuAttributeType.ValueType))
            {
                throw sr.GenExpWithLocation(
                          "Could not add multi \"ValueType\" attribute.");
            }
            // add value type
            entry.Attributes.Add(new MenuAttribute
            {
                AttributeType  = MenuAttributeType.ValueType,
                ExpressionType = type
            });
            // set entry value type
            entry.ValueType = type;

            if (string.IsNullOrEmpty(inStr))
            {
                return;
            }

            // add prompt
            entry.Attributes.Add(new MenuAttribute()
            {
                AttributeType = MenuAttributeType.Prompt,
                SymbolValue   = RemoveQuotationMark(inStr)
            });
        }
        /// <summary>
        /// Split option type and value
        /// <para>format: &lt;type&gt;[=&lt;value&gt;]</para>
        /// </summary>
        /// <param name="src">source line</param>
        /// <param name="optionType">option type</param>
        /// <param name="value">option value, for env option only.</param>
        /// <returns>isSuccess</returns>
        private static bool GetOptionTypeValue(string src,
                                               out MenuAttributeType optionType, out string value)
        {
            var match = FindOptionSymbolValueRegex.Match(src);
            // remove blank, '-', '_'
            var type = ClearAttributeKeyRegex.Replace(match.Groups["key"].Value, "");

            value = match.Groups["val"].Value;
            return(Enum.TryParse(type, true, out optionType));
        }
        /// <summary>
        /// choice's child entry can only be config or if block with configures.
        /// Value type of all config should be bool or tristate.
        /// Assign choice value type from first config if choice value has not assigned.
        /// </summary>
        /// <param name="entry">choice entry</param>
        /// <param name="choiceType">choice value type</param>
        private static void CheckConfigValueTypeInChoice(MenuEntry entry,
                                                         ref MenuAttributeType choiceType)
        {
            foreach (var childEntry in entry.ChildEntries)
            {
                switch (childEntry.EntryType)
                {
                case MenuEntryType.Config:
                    var attr = childEntry.Attributes
                               .FirstOrDefault(attribute =>
                                               attribute.AttributeType == MenuAttributeType.ValueType);
                    if (attr == null)
                    {
                        break;
                    }

                    var valueType = attr.ExpressionType;

                    if (valueType != MenuAttributeType.Bool && valueType != MenuAttributeType.Tristate)
                    {
                        throw new ParseException(
                                  $"Choice contains config which type is \"{valueType}\".",
                                  childEntry.Location);
                    }

                    if (choiceType == MenuAttributeType.Invalid)
                    {
                        choiceType = valueType;
                        break;
                    }

                    if (valueType != choiceType)
                    {
                        throw new ParseException(
                                  $"Choice type is \"{choiceType}\", but it contains config which type is \"{valueType}\".",
                                  childEntry.Location);
                    }
                    break;

                case MenuEntryType.If:
                    CheckConfigValueTypeInChoice(childEntry, ref choiceType);
                    break;

                default:
                    throw new ParseException(
                              $"Choice contains invalid entry which type is \"{childEntry.EntryType}\".",
                              childEntry.Location);
                }
            }
        }
 /// <summary>
 /// parse and add attribute to entry.
 /// <para>prototype is &lt;type&gt; &lt; expr &gt; ["if" &lt; expr &gt;]</para>
 /// </summary>
 /// <param name="inStr">source line</param>
 /// <param name="entry">attribute will add to this entry</param>
 /// <param name="type">attribute type</param>
 /// <param name="sr">for get location</param>
 /// <param name="isRemoveQuotationMark">whether remove quotation mark from value expression</param>
 private static void ParseAttrWithValAndCond(FileReader sr, MenuEntry entry, string inStr,
                                             MenuAttributeType type, bool isRemoveQuotationMark = false)
 {
     // <type> < expression > ["if" < condition >]
     if (!GetAttributeSymbolValueCodition(inStr, out var expression, out var condition))
     {
         throw sr.GenExpWithLocation($"Could not parse \"{type}\" attribute.");
     }
     entry.Attributes.Add(new MenuAttribute
     {
         AttributeType = type,
         SymbolValue   = isRemoveQuotationMark? RemoveQuotationMark(expression) : expression,
         Condition     = condition,
     });
 }