Я пытаюсь написать код для соответствия строк на основе шаблона:
шаблон: "собака и (кошка или коза)"
тестовая строка: "doggoat" результат: true
тестовая строка: "dogfrog" результат: false
Я пытаюсь написать синтаксический анализатор, используя Sprache, с большей частью логики превосходного ответа Кори на подобную проблему. Я почти у цели, но я получаю исключение при запуске кода:
'Бинарный оператор AndAlso также не определен для типов System.Func
2 [System.String, System.Boolean]' и '' System.Func`2 [System.String, System.Boolean] '.'
Я понимаю, что это означает, что мне нужно объединить лямбды в узлах дерева выражений с логическими операторами, которые я пытался использовать ExpressionVisitor, основываясь на ответе на другой вопрос здесь . Тем не менее, программа аварийно завершает работу до запуска ExpressionVisitor - кажется, что сначала выполняется команда Parse, но я не совсем понимаю, почему (возможно, это потому, что инструкция Sprache.Parse.Select не вызывает принудительное выполнение лямбда-выражения?) или как заставить его выполнить первым.
Пример кода приведен ниже (для краткости я удалил все операторы, кроме 'и', повторное введение их из шаблона Corey тривиально. Для компиляции кода необходимо добавить Sprache из NuGet.
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;
}
}