public static bool TryRead(ref FilterReader reader, string filterExpr, IList <PropertyInfo> propertyInfos, out IList <Token> tokens, out string error)
        {
            var position = 0;
            var retVal   = true;

            tokens = new List <Token>();
            error  = null;

            try
            {
                while (true)
                {
                    if (!reader.Read())
                    {
                        break;
                    }

                    var val = FilterReader.Utf8Encoding.GetString(reader.ValueSpan);
                    var i   = filterExpr.IndexOf(val, position, StringComparison.Ordinal) + 1;
                    position += val.Length;
                    switch (reader.TokenType)
                    {
                    case FilterTokenType.PropertyName:
                    {
                        var propertyName = val.StartsWith((char)FilterConstants.At) ? val.Substring(1) : val;
                        if (!propertyInfos.Any(x => x.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)))
                        {
                            throw new FilterException($"不存在的属性: {propertyName}。");
                        }

                        tokens.Add(new Token(FilterTokenType.PropertyName, propertyName, i));
                        break;
                    }

                    case FilterTokenType.StartGrouping:
                    case FilterTokenType.EndGrouping:
                    case FilterTokenType.UnaryLogicalOperator:
                    case FilterTokenType.BinaryLogicalOperator:
                    case FilterTokenType.RelationalOperator:
                    case FilterTokenType.Number:
                    case FilterTokenType.True:
                    case FilterTokenType.False:
                    case FilterTokenType.Null:
                        tokens.Add(new Token(reader.TokenType, val, i));
                        break;

                    case FilterTokenType.String:
                    {
                        var s = val.Substring(1, val.Length - 2);
                        tokens.Add(new Token(reader.TokenType, s, i));
                        break;
                    }

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                }
            }
            catch (FormatException e)
            {
                retVal = false;
                error  = e.Message;
            }

            return(retVal);
        }