Sprache 中的文本查询解析
Text query parsing in Sprache
我正在尝试编写一些代码来根据模式匹配字符串:
模式:"dog and (cat or goat)"
测试字符串:"doggoat" 结果:真
测试字符串:"dogfrog" 结果:false
我正在尝试使用 Sprache 编写解析器,Corey 提供的大部分逻辑都解决了类似的问题。我快到了,但是当代码为 运行:
时出现异常
'The binary operator AndAlso is not defined for the types System.Func
2[System.String,System.Boolean]' and ''System.Func`2[System.String,System.Boolean]'.'
我明白这意味着我需要将表达式树节点处的 lambda 与逻辑运算符结合起来,我已尝试使用基于另一个问题 here 的答案的 ExpressionVisitor。但是,程序在执行 ExpressionVisitor 之前就崩溃了——似乎先执行了 Parse 命令,但我不太明白为什么(也许是因为 Sprache.Parse.Select 语句没有强制执行 lambda?) , 或者如何强制先执行它。
示例代码如下(为了简洁起见,我去掉了所有运算符,但 'and' 去掉了,从 重新引入它们很简单。必须从 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;
}
}
您的代码存在多个问题,但导致相关异常的主要问题是 return lambda 表达式的 StringContains
方法。并且 Expression.AndAlso
(以及大多数 Expression
方法)基于简单的非 lambda 表达式(或 lambda 表达式主体)。解析代码的整个思想是识别和组合简单的表达式,并从结果表达式中生成单个 lambda 表达式。
要解决原来的问题,StringContains
方法应该 return 直接 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
class 使用单个 ParameterExpression
实例,然后用于构建 lambda 表达式,因此不需要 ParameterReplacer
, 所以 Lambda
方法 (属性) 可以简化为
private static Parser<Expression<Func<string, bool>>> Lambda =>
AndTerm.End().Select(body => Expression.Lambda<Func<string, bool>>(body, Param));
我正在尝试编写一些代码来根据模式匹配字符串:
模式:"dog and (cat or goat)"
测试字符串:"doggoat" 结果:真
测试字符串:"dogfrog" 结果:false
我正在尝试使用 Sprache 编写解析器,Corey
'The binary operator AndAlso is not defined for the types
System.Func
2[System.String,System.Boolean]' and ''System.Func`2[System.String,System.Boolean]'.'
我明白这意味着我需要将表达式树节点处的 lambda 与逻辑运算符结合起来,我已尝试使用基于另一个问题 here 的答案的 ExpressionVisitor。但是,程序在执行 ExpressionVisitor 之前就崩溃了——似乎先执行了 Parse 命令,但我不太明白为什么(也许是因为 Sprache.Parse.Select 语句没有强制执行 lambda?) , 或者如何强制先执行它。
示例代码如下(为了简洁起见,我去掉了所有运算符,但 'and' 去掉了,从
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;
}
}
您的代码存在多个问题,但导致相关异常的主要问题是 return lambda 表达式的 StringContains
方法。并且 Expression.AndAlso
(以及大多数 Expression
方法)基于简单的非 lambda 表达式(或 lambda 表达式主体)。解析代码的整个思想是识别和组合简单的表达式,并从结果表达式中生成单个 lambda 表达式。
要解决原来的问题,StringContains
方法应该 return 直接 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
class 使用单个 ParameterExpression
实例,然后用于构建 lambda 表达式,因此不需要 ParameterReplacer
, 所以 Lambda
方法 (属性) 可以简化为
private static Parser<Expression<Func<string, bool>>> Lambda =>
AndTerm.End().Select(body => Expression.Lambda<Func<string, bool>>(body, Param));