当你要把一个表达式字符串转换成一个lambda expression,本篇也许能帮到你。
把一个字符串解析成lambda要做的工作其实就2个,第一步把字符串通过语法解析转换一颗语法树,第二步就是遍历这棵树把它转换成.lambda expression。做一个C#语法解析器是件麻烦事,好在有一些现成的解析器可以使用,这里选用Irony来完成这个工作。
Irony的包里含有一个Sample的C# grammar,这个grammar除了不支持linq外已经非常完整了,接下来只要稍微修改下就能用来解析C#的表达式。
sample里默认的C# grammar对表达式支持不完善的地方有:
1.不能把强类型转换解析成恰当的节点。
2.不能很好区分成员访问操作,a.b, a.b(), a.b[] 这种成员访问操作的解析结果没有有针对性的区分,加大了语法树的转换难度。
3.对匿名对象支持不够,也就是new {}, new[] {}这种操作支持不够。
4.不支持linq。
只要修改掉以上缺陷,我们就能得到一个相对完美的支持linq的C# 表达式解析器了,由于我没有剔除掉namespace,class,struct等定义,所以修改后的grammar虽然是针对expression用的但是文件还是比较大的,你可以到这里下载CSharpExpressionGrammar.cs,因为本篇不是讲如何用Irony的,所以想了解具体怎么改的话请自行用winmerge之类的工具比较下修改过的grammar和Irony提供的grammar之间的差异。
有一点提一下的是,改过的grammar有一行
this.Root = expression;
这表示这个grammar只支持表达式解析,无法解析namespace,class之类的结构定义语法。
现在我们有了个可以用的grammar,接下来的工作就是使用它进行解析转换了。
首先看下这个解析类
public class ExpressionParser
{ public Expression Parse(string expression)
{
var parser = new Parser(new CSharpExpressionGrammar());
var tree = parser.Parse(expression);
if (tree.Status == ParseTreeStatus.Error)
throw new Exception("Parse Error");
_parameters.Clear();
return ProcessExpression(tree.Root);
}
}
Parse方法很简单,输入一个表达式字符串,然后返回一个Expression对象。具体实现就是先用Irony根据修改后的grammar创建解析器,解析器根据表达式字符串解析成Irony自己的结构ParseTree,请先忽略_parameters.Clear(),重点是:
return ProcessExpression(tree.Root);
语法树解析本质上就是个递归操作,ProcessExpression和很多Visitor.Visit做的工作很接近,只不过没取同样的名字罢了。
private Expression ProcessExpression(ParseTreeNode expNode) { switch (expNode.GetName()) { default: throw new Exception(expNode.GetName()); case "anonymous_function_body": case "parenthesized_expression": return ProcessExpression(expNode.FirstChild); case "lambda_expression": return ProcessLambdaExpression(expNode); case "typecast_expression": return ProcessConvertExpression(expNode); case "primary_expression": return ProcessUnaryExpression(expNode); case "bin_op_expression": return ProcessBinaryExpression(expNode); case "conditional_expression": return ProcessConditionalExpression(expNode); case "member_access": return ProcessMemberAccessExpression(expNode); case "object_creation_expression": return ProcessNewExpression(expNode); case "anonymous_type_creation_expression": return ProcessNewAnonymousExpression(expNode); case "literal": return ProcessConstantExpression(expNode); case "query_expression": return ProcessLINQ(expNode); } return Expression.Empty(); }
ProcessExpression解析树的每个节点,根据节点名字再进行进一步的解析工作,顺便说一下通过Irony自带的GrammarExplorer这个工具可以帮你看到它解析后的完整语法树,我这些手工解析代码就是根据这样的观察得来的。
为了很方便的解析ParseTree,我为ParseTreeNode加了一些扩展方法,把编码复杂度降低了不少,当然如果花点时间给ParseTree专门做一套支持xpath的查询方法,那代码写起来就更舒服了。
internal static class ParseTreeNodeExtensions { public static ParseTreeNode FirstOrDefault(this ParseTreeNode node, Func<ParseTreeNode, bool> predicate) { if (predicate(node)) return node; foreach (var n in node.ChildNodes) { var found = n.FirstOrDefault(predicate); if (found != null) return found; } return null; } public static ParseTreeNode GetChild(this ParseTreeNode node, string childName) { return node.ChildNodes.Find(p => p.GetName() == childName); } public static ParseTreeNode GetDescendant(this ParseTreeNode node, string childName) { return node.FirstOrDefault(p => p.GetName() == childName); } public static string GetName(this ParseTreeNode node) { return node.Term.Name; } public static string GetValue(this ParseTreeNode node) { return node.Token.Text; } public static object GetObject(this ParseTreeNode node) { return node.Token.Value; } public static bool HasChild(this ParseTreeNode node, string childName) { return node.GetChild(childName) != null; } public static Type GetClrType(this ParseTreeNode node) { if (node.HasChild("type_ref")) { var isNullable = node.GetDescendant("qmark_opt").FindTokenAndGetText() == "?"; var typeName = node.FindTokenAndGetText(); var type = ExpressionParser.GetType(typeName); if (isNullable) return typeof(Nullable<>).MakeGenericType(type); return type; } return null; } }
到了这里大致上我们解析表达式的代码逻辑整体框架已经完成了,接下来就是针对特定的操作生成表达式了。完整的ExpressionParser.cs代码也可以到http://code.google.com/p/tinynetevent/downloads/list去下载ExpressionParser.zip得到。要提前说明下的是ExpressionToCodeLib这个第三方库是把表达式转换成易读的字符串,最新版可在http://code.google.com/p/expressiontocode/得到;LINQ的解析完全是手工硬代码结构上没有做任何优化,而且测试也不完全。
可以这么用:
int i = 10; int j = 3; var lambda = parser.Parse<Func<int, int, int>>("(i, j) => i + j"); var method = lambda.Compile(); var result = method(i, j);
也可以这么用:
int i = 10; int j = 3; var lambda = parser.With(()=>i).With(()=>j).Parse<Func<int>>("() => i + j"); var method = lambda.Compile(); var result = method();
LINQ:
parser.Parse<Func<IQueryable<dynamic>, IQueryable<dynamic>, dynamic>>("(A, B) => from a in A from b in B select new { a, b }")
接下来几篇,会专门针对每个操作讲解下如何生成对应的表达式及一些生成表达式时候的技巧和要点。