【问题标题】:Text query parsing in SpracheSprache 中的文本查询解析
【发布时间】:2019-01-16 10:30:24
【问题描述】:

我正在尝试编写一些代码来匹配基于模式的字符串:

模式:“狗和(猫或山羊)”

测试字符串:“doggoat”结果:真

测试字符串:“dogfrog”结果:假

我正在尝试使用 Sprache 编写解析器,其中大部分逻辑由 Corey 的 excellent answer 提供以解决类似的问题。我快到了,但是运行代码时出现异常:

'没有为System.Func2[System.String,System.Boolean]'和''System.Func`2[System.String,System.Boolean]'类型定义二元运算符AndAlso。'

我知道这意味着我需要将表达式树节点处的 lambda 与逻辑运算符结合起来,我尝试使用 ExpressionVisitor 根据另一个问题 here 的答案。但是,程序在执行 ExpressionVisitor 之前崩溃 - 似乎首先执行 Parse 命令,但我不太明白为什么(可能是因为 Sprache.Parse.Select 语句不强制执行 lambda?) ,或者如何强制它先被执行。
示例代码如下(为了简洁起见,我删除了所有运算符,但 'and' 除外,从 Corey's template 重新引入它们是微不足道的。必须从 NuGet 添加 Sprache 才能编译代码。

class Program
{
    static void Main(string[] args)
    {
        var patternString = "dog and cat";

        var strTest = "dog cat";
        var strTest2 = "dog frog";

        var conditionTest = ConditionParser.ParseCondition(patternString);

        var fnTest = conditionTest.Compile();
        bool res1 = fnTest(strTest); //true
        bool res2 = fnTest(strTest2); //false
    }
}

public static class ConditionParser
{
    static ParameterExpression Param = Expression.Parameter(typeof(string), "_");

    public static Expression<Func<string, bool>> ParseCondition(string text)
    {
        return Lambda.Parse(text);
    }

    private static Parser<Expression<Func<string, bool>>> Lambda
    {
        get
        {
            var reduced = AndTerm.End().Select(delegate (Expression body)
            {
                var replacer = new ParameterReplacer(Param);
                return Expression.Lambda<Func<string, bool>>((BinaryExpression)replacer.Visit(body), Param);
            });

            return reduced;
        }
    }

    static Parser<Expression> AndTerm =>
        Parse.ChainOperator(OpAnd, StringMatch, Expression.MakeBinary);
    // Other operators (or, not etc.) can be chained here, between AndTerm and StringMatch

    static Parser<ExpressionType> OpAnd = MakeOperator("and", ExpressionType.AndAlso);

    private static Parser<Expression> StringMatch =>
        Parse.Letter.AtLeastOnce()
        .Text().Token()
        .Select(value => StringContains(value));

    static Expression StringContains(string subString)
    {
        MethodInfo contains = typeof(string).GetMethod("Contains");

        var call = Expression.Call(
            Expression.Constant(subString),
            contains,
            Param
        );

        var ret = Expression.Lambda<Func<string, bool>>(call, Param);
        return ret;
    }

    // Helper: define an operator parser
    static Parser<ExpressionType> MakeOperator(string token, ExpressionType type)
        => Parse.IgnoreCase(token).Token().Return(type);
}

internal class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return base.VisitParameter(_parameter);
    }

    internal ParameterReplacer(ParameterExpression parameter)
    {
        _parameter = parameter;
    }
}

【问题讨论】:

    标签: c# expression-trees linq-expressions dynamic-linq sprache


    【解决方案1】:

    您的代码存在几个问题,但导致异常的主要问题是返回 lambda 表达式的 StringContains 方法。而Expression.AndAlso(以及大多数Expression 方法)基于简单的非lambda 表达式(或lambda 表达式主体)。解析代码的整个想法是识别和组合简单的表达式,并从结果表达式中生成单个 lambda 表达式。

    要解决原来的问题,StringContains 方法应该直接返回 MethodCall 表达式而不是 lambda 表达式。

    同一StringContains 方法中的第二个问题是将参数反转为string.Contains。它基本上是token.Contains(parameter),而根据预期结果它应该做相反的事情。

    整个方法(使用另一个方便的Expression.Call重载)可以简化为

    static Expression StringContains(string subString) =>
        Expression.Call(Param, "Contains", Type.EmptyTypes, Expression.Constant(subString));
    

    现在一切都应该按预期工作了。

    但是,由于 ConditionParser 类使用单个 ParameterExpression 实例,然后用于构建 lambda 表达式,因此不需要 ParameterReplacer,因此 Lambda 方法(属性)可以减少到

    private static Parser<Expression<Func<string, bool>>> Lambda =>
        AndTerm.End().Select(body => Expression.Lambda<Func<string, bool>>(body, Param));
    

    【讨论】:

      猜你喜欢
      • 2022-12-19
      • 1970-01-01
      • 1970-01-01
      • 2016-08-08
      • 1970-01-01
      • 2018-08-19
      • 1970-01-01
      • 1970-01-01
      • 2021-06-02
      相关资源
      最近更新 更多