Ugly Linq

time to read 3 min | 485 words

One of the things that always bothered me with Linq was that it is actually not an interesting idea from the compiler perspective. I just had to implement a very simple expression to expression tree converter, which only served to strengthen my opinion. Here is the (ugly, proof of concept, horrible) implementation, using the Boo AST:

private static Block Linqify(Expression expr)
{
	var block = new Block(expr.LexicalInfo);
	ReferenceExpression condition = AddCondition(block);
	Parse(block, condition, expr);
	block.Add(new ReturnStatement(condition));
	return block;
}

private static ReferenceExpression AddCondition(Block block)
{
	var condition = new MethodInvocationExpression(new ReferenceExpression("Condition"));
	var expression = new ReferenceExpression("condition_" + CompilerContext.Current.AllocIndex());
	block.Add(
		new BinaryExpression(
			BinaryOperatorType.Assign,
			expression,
			condition)
		);
	return expression;
}

private static void Parse(Block block, Expression condition, Expression expr)
{
	var be = expr as BinaryExpression;
	if (be != null && (be.Operator == BinaryOperatorType.Or || be.Operator == BinaryOperatorType.And))
	{
		block.Add(new BinaryExpression(
			BinaryOperatorType.Assign,
			new MemberReferenceExpression(condition, "Operator"),
			new StringLiteralExpression(be.Operator.ToString().ToLowerInvariant()))
			);
		ReferenceExpression left = AddCondition(block);
		block.Add(
			new MethodInvocationExpression(
				new MemberReferenceExpression(new MemberReferenceExpression(condition, "Expressions"), "Add"), left));
		ReferenceExpression right = AddCondition(block);
		block.Add(
			new MethodInvocationExpression(
				new MemberReferenceExpression(new MemberReferenceExpression(condition, "Expressions"), "Add"), right));
		Parse(block, left, be.Left);
		Parse(block, right, be.Right);
		return;
	}
	var fragment = new MethodInvocationExpression(new ReferenceExpression("Fragment"));
	if (expr is UnaryExpression)
	{
		fragment.NamedArguments.Add(
			new ExpressionPair(new ReferenceExpression("Modifier"),
				new StringLiteralExpression("not"))
			);
		be = (BinaryExpression)((UnaryExpression)expr).Operand;
	}
	var func1 = (MethodInvocationExpression)be.Left;
	fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Func1"), new StringLiteralExpression(func1.Target.ToString())));
	fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Prop1"), GetStringArgument(func1.Arguments[0])));
	var func2 = be.Right as MethodInvocationExpression;
	if (func2 != null)
	{
		fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Func2"), new StringLiteralExpression(func2.Target.ToString())));
		fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Prop2"), GetStringArgument(func2.Arguments[0])));
	}
	else
	{
		fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Func2"), new StringLiteralExpression("literal")));
		fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Prop2"), GetStringArgument(be.Right)));
	}
	fragment.NamedArguments.Add(new ExpressionPair(new ReferenceExpression("Operator"),
		new StringLiteralExpression(GetOperator(be))));

	block.Add(
		new MethodInvocationExpression(
			new MemberReferenceExpression(new MemberReferenceExpression(condition, "Expressions"), "Add"), fragment));

}

private static Expression GetStringArgument(Expression expr)
{
	if (expr is StringLiteralExpression)
		return expr;
	return new StringLiteralExpression(expr.ToString());
}

private static string GetOperator(BinaryExpression be)
{
	switch (be.Operator)
	{
		case BinaryOperatorType.Equality:
			return "==";
		case BinaryOperatorType.Inequality:
			return "!=";
		case BinaryOperatorType.Member:
			return "in";
		case BinaryOperatorType.LessThan:
			return "<";
		case BinaryOperatorType.LessThanOrEqual:
			return "<=";
		case BinaryOperatorType.GreaterThan:
			return ">";
		case BinaryOperatorType.GreaterThanOrEqual:
			return ">=";
		default:
			throw new NotSupportedException(be.Operator.ToString());
	}
}

This takes a Boo expression and transform that into the code that creates an object model that represents this expression. Linq is simply an extension to this implementation.