【问题标题】:Recursive expression parsing in SpracheSprache 中的递归表达式解析
【发布时间】:2018-08-09 09:47:10
【问题描述】:

我正在构建一个 Sprache 解析器来解析类似于 SQL 搜索条件的表达式。例如Property = 123Property > AnotherProperty

到目前为止,这两个示例都有效,但是我正在努力弄清楚我需要做些什么来允许 ANDing/ORing 条件和括号。

到目前为止,基本上我有这个:

private static readonly Parser<string> Operators =
    Parse.String("+").Or(Parse.String("-")).Or(Parse.String("="))
        .Or(Parse.String("<")).Or(Parse.String(">"))
        .Or(Parse.String("<=")).Or(Parse.String(">=")).Or(Parse.String("<>"))
        .Text();

private static readonly Parser<IdentifierExpression> Identifier = 
    from first in Parse.Letter.Once()
    from rest in Parse.LetterOrDigit.Many()
    select new IdentifierExpression(first.Concat(rest).ToArray());

public static readonly Parser<Expression> Integer =
    Parse.Number.Select(n => new IntegerExpression {Value = int.Parse(n)});

public static readonly Parser<SearchCondition> SearchCondition = 
    from left in Identifier.Or(Number)
    from op in Operators.Token()
    from right in Identifier.Or(Number)
    select new SearchCondition { Left = left, Right = right, Operator = op};

这适用于上面的简单情况,但现在我需要一个关于如何实现条件的指针,例如:

  • PropertyX = PropertyY OR PropertyX = PropertyZ
  • PropertyA &gt; PropertyB AND (OtherAnotherProperty = 72 OR OtherAnotherProperty = 150)

谁能告诉我如何为这类事情构建解析器?

【问题讨论】:

  • 什么是IdentifierExpression?自定义 LINQ 表达式来访问您的数据?

标签: c# parsing sprache


【解决方案1】:

到目前为止,您拥有的是一个基本的比较表达式解析器。看起来您想将其包装在处理逻辑表达式(andor 等)并支持子表达式的解析器中。

我最初发布的代码是从我仍在处理的测试不佳的代码中提取的,这些代码无法处理包含多个术语的语句。我对ChainOperator方法的理解显然是不完整的。

Parse.ChainOperator 是让您指定运算符并让它们在表达式中出现 0 到多次的方法。我一直在假设它是如何工作的,结果证明是错误的。

我已经重写了代码并添加了一些代码以使其更易于使用:

// Helpers to make access simpler
public static class Condition
{
    // For testing, will fail all variable references
    public static Expression<Func<object, bool>> Parse(string text)
        => ConditionParser<object>.ParseCondition(text);

    public static Expression<Func<T, bool>> Parse<T>(string text)
        => ConditionParser<T>.ParseCondition(text);

    public static Expression<Func<T, bool>> Parse<T>(string text, T instance)
        => ConditionParser<T>.ParseCondition(text);
}

public static class ConditionParser<T>
{
    static ParameterExpression Parm = Expression.Parameter(typeof(T), "_");

    public static Expression<Func<T, bool>> ParseCondition(string text)
        => Lambda.Parse(text);

    static Parser<Expression<Func<T, bool>>> Lambda =>
        OrTerm.End().Select(body => Expression.Lambda<Func<T, bool>>(body, Parm));

    // lowest priority first
    static Parser<Expression> OrTerm =>
        Parse.ChainOperator(OpOr, AndTerm, Expression.MakeBinary);

    static Parser<ExpressionType> OpOr = MakeOperator("or", ExpressionType.OrElse);

    static Parser<Expression> AndTerm =>
        Parse.ChainOperator(OpAnd, NegateTerm, Expression.MakeBinary);

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

    static Parser<Expression> NegateTerm =>
        NegatedFactor
        .Or(Factor);

    static Parser<Expression> NegatedFactor =>
        from negate in Parse.IgnoreCase("not").Token()
        from expr in Factor
        select Expression.Not(expr);

    static Parser<Expression> Factor =>
        SubExpression
        .Or(BooleanLiteral)
        .Or(BooleanVariable);

    static Parser<Expression> SubExpression =>
        from lparen in Parse.Char('(').Token()
        from expr in OrTerm
        from rparen in Parse.Char(')').Token()
        select expr;

    static Parser<Expression> BooleanValue =>
        BooleanLiteral
        .Or(BooleanVariable);

    static Parser<Expression> BooleanLiteral =>
        Parse.IgnoreCase("true").Or(Parse.IgnoreCase("false"))
        .Text().Token()
        .Select(value => Expression.Constant(bool.Parse(value)));

    static Parser<Expression> BooleanVariable =>
        Parse.Regex(@"[A-Za-z_][A-Za-z_\d]*").Token()
        .Select(name => VariableAccess<bool>(name));

    static Expression VariableAccess<TTarget>(string name)
    {
        MemberInfo mi = typeof(T).GetMember(name, MemberTypes.Field | MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public).FirstOrDefault();
        var targetType = typeof(TTarget);
        var type = 
            (mi is FieldInfo fi) ? fi.FieldType : 
            (mi is PropertyInfo pi) ? pi.PropertyType : 
            throw new ParseException($"Variable '{name}' not found.");

        if (type != targetType)
            throw new ParseException($"Variable '{name}' is type '{type.Name}', expected '{targetType.Name}'");

        return Expression.MakeMemberAccess(Parm, mi);
    }

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

还有一些例子:

static class Program
{
    static void Main()
    {
        // Parser with no input
        var condition1 = Condition.Parse("true and false or true");
        Console.WriteLine(condition1.ToString());
        var fn1 = condition1.Compile();
        Console.WriteLine("\t={0}", fn1(null));

        // Parser with record input
        var record1 = new { a = true, b = false };
        var record2 = new { a = false, b = true };
        var condition2 = Condition.Parse("a and b or not a", record);
        Console.WriteLine(condition2.ToString());
        var fn2 = condition2.Compile();
        Console.WriteLine("\t{0} => {1}", record1.ToString(), fn2(record1));
        Console.WriteLine("\t{0} => {1}", record2.ToString(), fn2(record2));
    }
}

您仍然需要添加自己的解析器来处理比较表达式等。在现有条款之后将它们插入BooleanValue 解析器:

static Parser<Expression> BooleanValue => 
    BooleanLiteral
    .Or(BooleanVariable)
    .Or(SearchCondition);

我正在使用更多 C# 样式的过滤器规范做类似的事情,在解析阶段进行类型检查,并将字符串与数字的解析器分开。

【讨论】:

  • 谢谢@Corey... 除非我遗漏了什么,否则它不会解析像true and false or true 这样的表达式——它只会处理两个表达式(显然两个对于真/假来说很好,但我想处理任意数量的条件(例如Prop1 = A AND Prop2 = B AND Prop3 = C
  • 该死的。我以为我测试过了。将修复并更新答案。
  • 完成。顺便说一下,这是基于 SimpleCalc 中使用的结构。我只是改变了一些东西来处理布尔条件而不是算术。在某个时候,我会将我正在处理的内容发布到 GitHub。
  • 好的,谢谢先生。这是非常好的。我还发现了这个github.com/anpv/SpracheTest,它与你有类似的方法 - 现在我只需要弄清楚它是如何工作的:) 非常感谢
  • 相当肯定递归魔法发生在Parse.ChainOperator 代码中。然而,我还没有阅读足够多的 Sprache 代码来弄清楚它是如何工作的。
猜你喜欢
  • 2022-01-07
  • 1970-01-01
  • 1970-01-01
  • 2012-07-25
  • 1970-01-01
  • 2021-11-09
  • 2014-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多