This project is read-only.

A brief Introduction

The main goal of QInjection is to validate and execute runtime stubs of lambda expressions (coded as strings).

The current release allows the implementation of a method like the following one:

public IEnumerable<Int32> RuntimeWhere(String expression)
{
	ProviderForInjector provider4injector = new ProviderForInjector();
	provider4injector.ProvideData();
	
	QInjector injector = new QInjector(expression);
	
	injector = injector.Support(Node.SimpleLambdaExpression).As(Branch.Input)
	    .Support(Node.BinaryExpression).As(Branch.Body)
	    .Support(Node.IdentifierName).As(Branch.LeftMember)
	    .Support(Node.LiteralExpression).As(Branch.RightMember)
	    .Support(Node.Parameter).As(Branch.Parameter);
	
	injector.Validate();
	
	return injector.WhereOn<Int32>(provider4injector);
}


So, as you can see, you have to:
  • Define and implement the Data Bridge Class, derived from the interface IQProvider<T> (ProviderForInjector in the example above);
  • Define the Validation Syntax;
  • Validate the lambda expression using the Validation Syntax defined at previous point;
  • Script the lambda expresion using the Data Bridge Class;

This page discuss about how to improve current Validation Syntax of QInjection.

An analysis about Current Validation Syntax

You can have many type of lambda expressions and their syntax can change from case to case.
You can have the simple one expressione like:

x => x == 1


up to something more complex like:

x => x.Check(x ? x : 1, x + 1) && !x && (x + 1 == 2)


and the complexity can grow if you consider also anonymous methods.

Internally the validation engine of QInjcetion builds the AST (Abstract Syntax Tree) of the lambda expression and it walks throught it by validating each branch. An AST is composed by Node and Branches. So declaring the syntax of a lambda expression means to define the "structure" of the Tree in term of Nodes, Branches and Links between them. Moreover, for each node you can define restrictions in term of names, operators and so on ... accepted in input.

The first issue solved with QInjection was to develop a pattern to define the validation profile of a lambda expression.
So we defined following methods:
  • Support(Node node)
  • As(Branch branch)
  • WithArity(int arity)
  • WithOperator(Operator _operator)
  • WithName(string name)
  • WithNameRegex(string regex)
  • RefuseReference(Reference reference)

Using these methods you can define a Validation Syntax like the following one:

injector = injector.Support(Node.BinaryExpression).As(Branch.Body);

injector = injector.Support(Node.InvocationExpression).As(Branch.LeftMember);
injector = injector.Support(Node.PrefixUnaryExpression).As(Branch.RightMember);
injector = injector.Support(Node.ParenthesizedExpression).As(Branch.RightMember);

injector = injector.Support(Node.BinaryExpression).As(Branch.LeftMember);

injector = injector.Support(Node.LiteralExpression).As(Branch.RightMember);
injector = injector.Support(Node.IdentifierName).As(Branch.LeftMember);

injector = injector.Support(Node.IdentifierName).As(Branch.Expression);

injector = injector.Support(Node.Argument).As(Branch.Argument);
injector = injector.Support(Node.BinaryExpression).As(Branch.Expression);
injector = injector.Support(Node.ConditionalExpression).As(Branch.Expression);

injector = injector.Support(Node.IdentifierName).As(Branch.Condition);
injector = injector.Support(Node.IdentifierName).As(Branch.WhenTrue);
injector = injector.Support(Node.LiteralExpression).As(Branch.WhenFalse);


This is for example the code needed to validate with success a semi-complex lambda expressions like:

x => x.Check(x ? x : 1, x + 1) && !x && (x + 1 == 2)


A Validation Syntax so is composed by many Support(...).As(...) definitions.

Some oberservations can be done:
  • The set of rules you have to code using Support and As can be long in term od code lines and difficult to understand on reverse engineering analysis.
  • A lot of declarations should be reduced; see for example these ones about Node.IdentifierName in the previous example, you have three declarations (but you can have more).
  • Usually if you want to support a specificied Node Type (e.g. Node.IdentifierName) it's better to use a coding pattern with fewer line codes.
  • Long and complex list of Support and As can generate misunderstanding.

LPP vs HPP

Next issue is about defining a more hermetic and manageable set of methods for declaring Validation Syntax.

The methods listed above will be named with: "Low Profile Pattern" or in brief LPP.
The LPP make you able to work at a very low level of granularity by deciding for each node how it will be linked and how it have to be made.
So with LPP you have to decide exactly how your AST should be done in order to accept a lambda expression as input.
This means to have an idea about which Nodes to manage and whick Links between them you want to accept.

An other patter that you can use is this one based on "shortcuts" declarations.
Suppose you want to accept a BinaryExpression in any situations you can have it; using the LPP you have to use instructions like following ones:

injector = injector.Support(Node.BinaryExpression).As(Branch.Body);
injector = injector.Support(Node.BinaryExpression).As(Branch.Condition);
injector = injector.Support(Node.BinaryExpression).As(Branch.Expression);
injector = injector.Support(Node.BinaryExpression).As(Branch.LeftMember);
injector = injector.Support(Node.BinaryExpression).As(Branch.RightMember);
injector = injector.Support(Node.BinaryExpression).As(Branch.WhenTrue);
injector = injector.Support(Node.BinaryExpression).As(Branch.WhenFalse);


A solution in order to reduce code and to be more clear is to build a new layer called the "High Profile Pattern" or in brief HPP.
So we will have:
  • A "Low Profile Pattern" that his flexible and accurate.
  • A "High Profile Pattern" based on the "Low Profile Pattern" that is more clear and hermetic.

The ideas behind the HPP are:
  • If you want to "support" the node Node.BinaryExpression you don't have to care about how it is linked to other nodes, an internal engine will configure for you all the possible link cases.
  • If you want to restrict a node you have to do it at the same manner of the LPP and you have also to use a more powerful set of tools.

So if you want to accept a BinaryExpression in any situations you can have it then using the HPP you have to code only one simple instruction:

injector = injector.Accept(Element.BinaryExpression).AsBranch();


This "High profile pattern" will be composed by methods like the following ones:
  • Accept
  • AsBranch
  • WithName
  • WithNameRegex
  • WithArity
  • WithOperator
  • WithOperators
  • WithOperatorSet
  • WithReference
  • WithReferences

The Accept method doesn't work on Node and Branches but on Element. Using methods Accept and and AsBranch you will automatically add all needed Nodes and it will build all the Links (between branches) needed to support them.

So the previous code became merely:

injector.Begin();

injector = injector.Accept(Element.BinaryExpression).AsBranch();
injector = injector.Accept(Element.UnaryExpression).AsBranch();
injector = injector.Accept(Element.ConditionalExpression).AsBranch();
injector = injector.Accept(Element.IdentifierName).AsBranch();
injector = injector.Accept(Element.LiteralExpression).AsBranch();
injector = injector.Accept(Element.InvocationExpression).AsBranch();
injector = injector.Accept(Element.ParenthesizedExpression).AsBranch();

injector.End();


Note the use of the Begin and End statements< with HPP they are mandatory.

The next step: The Instructor

Now, the next question is: do we can do better than this? The answer from our point of view is: it depends on what goal do you want to achieve!
Let us to explain.
Do you want a way to support more powerful lambda expression? Do you want to configure the syntax of a lambda expression in a different way?
It depends on programmers needs and currently QInjection from our point of view is only an idea, a concept ... maybe only a strange and small research's work!
When it will have a more large audience we can better decide (if this will happen!!!).

Maybe a more useful tools now is to have a way to define your own configuration, to save it ... and then load at runtime!
This is what we'll call a "Configuration Profile Pattern" or in brief CPP.

The basic idea behind CPP is this one of the Instructor (and Janitor).
An Instructor is a Validation Syntax Provider that load custom configurations build up on HPP and LPP.

We will talk about Instructors in the future.

Last edited Jan 5, 2014 at 5:08 PM by SamNium, version 3