Showing posts with label linq. Show all posts
Showing posts with label linq. Show all posts

Tuesday, December 11, 2007

Creating fractal images with C# 3.0 features and Parallel Extensions

In this post I'm going to show a small example of using the Parallel Extensions Library to improve a program that renders a escape time fractal image.

A couple of months ago I wrote about a small program that creates an image with the escape time algorithm using C# 3.0 features . For this post I'm going to change the implementation to use the Parallel Extensions Library.

Along the the library documentation and MSDN articles, the blog entries from the Parallel Extensions Team blog provided a nice introduction and guidance . The original post was inspired by Luke Hoban's blog entry A Ray Tracer in C#3.0 .

The escape time algorithm is very simple and also considered a embarrassingly parallelproblem. For previous posts I did a similar experiment of using Fortress parallel for loops to render the Mandelbrot Set fractal.

The code for the original program was the following:


void CreateFractal(double minX, double minY,double maxX, double maxY,int imageWidth, int imageHeight)
{
Func<double, double> xF = MathUtils.InterpFunc(0, minX, imageWidth, maxX);
Func<double, double> yF = MathUtils.InterpFunc(0, minY, imageHeight, maxY);

foreach (var p in from yi in Enumerable.Range(0, imageHeight)
from xi in Enumerable.Range(0, imageWidth)
select new
{
x = xi,
y = yi,
xD = xF(xi),
yD = yF(yi)
})
{

Complex p0 = new Complex(p.xD, p.yD);
Func<Complex, Complex> function = functionConstructor(p0);

int i = ApplyFunction(function, p0)
.TakeWhile(
(x, j) => j < maxIteration && x.NormSquared() < 4.0)
.Count();

HandlePixel(p.x, p.y, i);
}

}



We can change this code in several ways to take advantage of the library. Here I'm going to present two of them.

Write one big LINQ expression

The code for the generation of pixel data is coded in a single LINQ expression.

Func<double, double> xF = MathUtils.InterpFunc(0, minX, imageWidth, maxX);
Func<double, double> yF = MathUtils.InterpFunc(0, minY, imageHeight, maxY);

foreach (var p in from yi in Enumerable.Range(0, imageHeight).AsParallel()
from xi in Enumerable.Range(0, imageWidth)
let mappedX = xF(xi)
let mappedY = yF(yi)
let p0 = new Complex(xF(xi), yF(yi))
let function = functionConstructor(p0)
select new
{
x = xi,
y = yi,
xD = mappedX,
yD = mappedY,
i = ApplyFunction(function, p0)
.TakeWhile(
(x, j) => j < maxIteration && x.NormSquared() < 4.0)
.Count()
})
{
HandlePixel(p.x, p.y, p.i);
}


Elements from the body of the foreach loop were moved to the LINQ expression by using the let keyword.

The AsParallel extension method called does the magic of distributing the work for calculating the lines of the image. A nice discussion on were to put this call can be found in Parallelizing a query with multiple “from” clauses and Chunk partitioning vs range partitioning in PLINQ.

Although this problem is much more simpler than Ray tracing, this alternative tries to follow the same approach as: Taking LINQ to Objects to Extremes: A fully LINQified RayTracer.



Add a Parallel.For loop

The other alternative is to replace one of the from elements with a Parallel.For loop.


Func<double, double> xF = MathUtils.InterpFunc(0, minX, imageWidth, maxX);
Func<double, double> yF = MathUtils.InterpFunc(0, minY, imageHeight, maxY);

Parallel.For(0, imageHeight , delegate(int yi)
{
foreach (var p in from xi in Enumerable.Range(0, imageWidth)
let mappedX = xF(xi)
let mappedY = yF(yi)
let p0 = new Complex(xF(xi), yF(yi))
let function = functionConstructor(p0)
select new
{
x = xi,
y = yi,
xD = mappedX,
yD = mappedY,
i = ApplyFunction(function, p0)
.TakeWhile(
(x, j) => j < maxIteration && x.NormSquared() < 4.0)
.Count()
})
{
lock (this)
{
HandlePixel(p.x, p.y, p.i);
}
}
});



A lock needed to be added because HandlePixel calls Bitmap.SetPixel which needs to be protected.

Results

I tested this code by generating a 2000 by 2000 image of a small section of the Mandelbrot fractal located between (-1.1752491998171,0.223337905807042) and (-1.17342021033379,0.225166895290352) with a escape time of 512. A reduced image of this location looks like this:

Calculated Fractal Image

By using the library the program took about 40% less of the time of original version on my dual core machine. The performance for the two approaches presented above was very similar.

The nicest thing about this experiment is that the code required just a couple of modifications in order to take advantage of the other CPU.

Another approach for rendering this fractal using Parallel extensions is from Jon Skeet's Coding Blog : LINQ to Silliness: Generating a Mandelbrot with parallel potential and A cautionary parallel tale: ordering isn't simple.

Code for this post can be found here.

Tuesday, August 28, 2007

Exploring L-Systems with F# and C#

In this post I'm going to show a little program for displaying graphical representations of L-Systems using turtle graphics implemented in F#, C# and WPF.

First of all this program could be easily implemented using only F#, but for me is interesting to see the interaction between native F# type/structures and C#. Because of this, the code that performs the L-system rewrite is written in F# and the code that takes the result is written in C# and uses WPF.

The first thing we need is an implementation of the turtle. Since we want to use this library with several graphics toolkits, we define our own point type for the generated data.


#light

namespace Langexplr.Lsystems

open System
open Microsoft.FSharp.Math.Vector

type Point =
{x : int; y : int }

module Funcs = begin

let my_create_vector(i,j) =
let result = (create 2 0.0)
result.[0] <- i
result.[1] <- j
result
end

type TurtleGraphics =
class
val mutable direction : vector
val mutable position : Point

new(iX,iY) = { position = {x = iX; y = iY};
direction = Funcs.my_create_vector(1.0,0.0)}

member t.Position
with get() = t.position and
set(v) = t.position <- {x = v.x; y = v.y }

member t.Direction
with get() = t.direction and
set(v) = t.direction <- v

member t.Advance(distance : int) =
let aX = int_of_float (t.direction.[0] * float_of_int distance)
let aY = int_of_float (t.direction.[1] * float_of_int distance)
t.position <- { x = aX+t.position.x;
y = aY+t.position.y }
t.position

member t.Rotate(angle) =
let nI = (t.direction.[0] * Math.Cos(angle)) - (t.direction.[1] * Math.Sin(angle))
let nJ = (t.direction.[0] * Math.Sin(angle)) + (t.direction.[1] * Math.Cos(angle))
t.direction <- Funcs.my_create_vector(nI,nJ)


end


Now we need to represent the elements required for the L-Systems. The following elements are required:

Start point or axiomthe initial sequence of elements
Rules L-system substitution rules
AngleThe angle used when rotating the turtle
Number of iterationsThe number of times the rules will be applied to the axiom
Size of the initial segmentThe size in pixels of the line that is drawn when the turtle moves forward


Also the elements inside the rule and the axiom must be translated to turtle graphics commands. The following commands are supported:

|Draws a line forward, the size of the line inversely proportional to the iteration number
+Turn left by the specified angle
-Right left by the specified angle
[Saves the position and direction of the turtle in a stack
]Restores the position and direction of the turtle from the stack
LetterIf activated, draws a line forward


The following code shows the implementation of this:


#light

namespace Langexplr.Lsystems

open Langexplr.Lsystems

open System

type LsystemElement =
| Var of String
| Constant of String
| PipeCommand of int

type Rule =
| Rule of LsystemElement * LsystemElement list

module LsystemFuncs = begin
let rec gettingLsystemElements (str:string) i result =
if str.Length > i then
if System.Char.IsLetterOrDigit(str.[i]) then
gettingLsystemElements str (i+1) ((Var(str.[i].ToString()))::result)
else
gettingLsystemElements str (i+1) ((Constant(str.[i].ToString()))::result)
else
List.rev result
let getLsystemElements str =
gettingLsystemElements str 0 []
end


type TurtleGraphicsLsystemProcessor =
class
val start : LsystemElement list
val angle : double
val rules : Rule list
val seg_size : int
val mutable saved_positions : Point list
val mutable saved_directions : vector list
val mutable drawVariables : bool

new (a_start,a_angle,t_rules,s_size,drawVars) = {
start = a_start;
angle = (Math.PI/180.0)* a_angle;
rules = t_rules;
seg_size = s_size;
saved_positions = [];
saved_directions = [];
drawVariables = drawVars}

member lp.generate_for n current =
match n with
| 0 -> current
| o -> lp.generate_for (n - 1) (lp.apply_rules current n)

member lp.apply_rules elements iteration =
match elements with
| ((Var v)::rest) -> List.append (lp.apply_rule_for v) (lp.apply_rules rest iteration)
| ((Constant "|")::rest) -> (PipeCommand iteration)::(lp.apply_rules rest iteration)
| (e::rest) -> e::(lp.apply_rules rest iteration)
| [] -> elements

member lp.apply_rule_for v =
match (List.tryfind (fun r -> match r with
| Rule(Var vvar,_) when vvar = v -> true
| _ -> false)
lp.rules) with
| Some (Rule(_, result)) -> result
| None -> [Var v]


member lp.generate_iteration n (tg:TurtleGraphics)=
let final = lp.generate_for n lp.start
in
List.rev(lp.generate_points n final tg [tg.Position] [])

member lp.generate_points iterations elements (tg:TurtleGraphics) current lines =
match elements with
| ((Var _)::rest) ->
if (lp.drawVariables) then
tg.Advance(lp.seg_size)
lp.generate_points iterations rest tg (tg.Position::current) lines
else
lp.generate_points iterations rest tg current lines
| ((Constant "+")::rest) ->
tg.Rotate(lp.angle)
lp.generate_points iterations rest tg current lines
| ((Constant "-")::rest) ->
tg.Rotate(-1.0*lp.angle)
lp.generate_points iterations rest tg current lines
| ((Constant "[")::rest) ->
lp.saved_directions <- tg.Direction::lp.saved_directions
lp.saved_positions <- tg.Position::lp.saved_positions
lp.generate_points iterations rest tg current lines
| ((Constant "]")::rest) ->
match (lp.saved_directions,lp.saved_positions) with
| (cdir::rest_dir,cpos::rest_pos) ->
lp.saved_directions <- rest_dir
lp.saved_positions <- rest_pos
tg.Position <- cpos
tg.Direction <- cdir
lp.generate_points iterations rest tg [tg.Position] (current::lines)
| _ -> lp.generate_points iterations rest tg current lines
| ((Constant "|")::rest) ->
tg.Advance(lp.seg_size / (iterations) )
lp.generate_points iterations rest tg (tg.Position::current) lines
| ((PipeCommand iteration)::rest) ->
tg.Advance(lp.seg_size / (iterations - iteration) )
lp.generate_points iterations rest tg (tg.Position::current) lines
| (_::rest) -> lp.generate_points iterations rest tg current lines
| [] -> current::lines



end



The generate_iteration method is the one that generates the line information. Its result is a list of lists of elements of type Point.

The C# code that takes this F# list of lists of Points and converts it to a group of Polyline instances is the following:


private void b_Click(object sender, RoutedEventArgs e)
{
string origin = this.originTB.Text;
int originX = 50;
int originY = 50;
string[] oparts = origin.Split(',');
if (oparts.Length == 2)
{
originX = int.Parse(oparts[0]);
originY = int.Parse(oparts[1]);
}

TurtleGraphics tg = new TurtleGraphics(originX, originY);
int iterations = int.Parse(this.iterationsTB.Text);

this.canvas1.Children.Clear();

List<Rule> rules = GetRules();

TurtleGraphicsLsystemProcessor tgls =
new TurtleGraphicsLsystemProcessor(
LsystemFuncs.getLsystemElements(this.axiomTextBox.Text),
int.Parse(this.angleTB.Text),
Microsoft.FSharp.Collections.ListModule.of_IEnumerable<List<Rule>, Rule>(rules),
int.Parse(this.segmentSizeTB.Text),
drawVariablesCB.IsChecked == true);

var pointCollections =
from pc in (tgls.generate_iteration(int.Parse(this.iterationsTB.Text))).Invoke(tg)
select (new PointCollection(
from p in pc
select new System.Windows.Point(p.x, p.y)));

foreach (PointCollection pcol in pointCollections)
{
Polyline pLine = new Polyline();

pLine.Points = pcol;
pLine.Stroke = this.colorButton.Background;
this.canvas1.Children.Add(pLine);
}
}



What is interesting to see is that the list generated in F# is easily manipulated using LINQ. The expression that sets the value of pointCollections takes the native list of lists of points and converts it to a list of PointCollection objects in one expression .

Another interesting thing about the interaction between F# and C# is that the definition of the generate_iteration says that it could be applied with one or two arguments (because of Currying) this is used in C# by invoking the result of calling the method with one argument: (tgls.generate_iteration(int.Parse(this.iterationsTB.Text))).Invoke(tg).

Executing the program with the following L-system (from Wikipedia):




Also with using the "|" command:




Code for this experiment can be found here.

Sunday, June 24, 2007

Creating fractal images using C# 3.0 features

In this post I'm going to try to implement the basic Escape time algorithm for creating fractal images. My main goal is to use as many C# 3.0 features as possible .

This post is inspired by the very nice blog post A Ray Tracer in C#3.0.


The Escape time algorithm is very simple, but depending on its input it can generate very complex images.

Although not mandatory, the first thing to create is a class from complex numbers. The complex number class contains the following members:


public class Complex
{
public Complex(double real, double img)
public Complex Multiply(Complex c)
public Complex Add(Complex c)
public double Norm()
public double NormSquared()
public static Complex operator *(Complex c1,Complex c2)
public static Complex operator +(Complex c1, Complex c2)
public double Real
public double Img
}


Now we need something to convert from the image coordinate system to the real coordinate system. To do this the following function was created:


public static Func<double, double> InterpFunc(double x1,double y1,double x2,double y2)
{
double m = (y2 - y1) / (x2 - x1);
double b = y1 - (m * x1);
return (x => m * x + b);
}


The InterpFunc method creates a function that maps values from one coordinate system to the other.

The code that creates the image is the following:


void CreateFractal(double minX, double minY,double maxX, double maxY,int imageWidth, int imageHeight)
{
Func<double, double> xF = MathUtils.InterpFunc(0, minX, imageWidth, maxX);
Func<double, double> yF = MathUtils.InterpFunc(0, minY, imageHeight, maxY);

foreach (var p in from yi in Enumerable.Range(0, imageHeight)
from xi in Enumerable.Range(0, imageWidth)
select new
{
x = xi,
y = yi,
xD = xF(xi),
yD = yF(yi)
})
{

Complex p0 = new Complex(p.xD, p.yD);
Func<Complex, Complex> function = functionConstructor(p0);

int i = ApplyFunction(function, p0)
.TakeWhile(
(x, j) => j < maxIteration && x.NormSquared() < 4.0)
.Count();

HandlePixel(p.x, p.y, i);
}

}


Basically select expression generates the coordinates for all the points in the image . The functionConstructor function is used to create the function that will be applied to generate the fractal. One example of this functions is the one used for the Mandelbrot fractal f(x) = x^2 + p0 . The escape time is calculated by counting the number of elements in the generated sequence of function applications before the value escaped.

The ApplyFunction method is interesting since is the one that creates the sequence of recursive function applications required for this algorithm. The method looks like this:


IEnumerable<Complex> ApplyFunction(Func<Complex, Complex> function,Complex initial)
{
Complex last = initial;
while (true)
{
last = function(last);
yield return last;
}
}



By running this algorithm using the Mandelbrot formula:


Func<Complex,Func<Complex,Complex>> fc =
p0 => (c => c.Multiply(c).Add(p0));

EscapeTimeFractal p = new EscapeTimeFractal(300, 300, fc);
p.MinX = -0.03;
p.MinY = 0.68;
p.MaxX = 0.03;
p.MaxY = 0.62;

p.Run();


The generated image is the following:




By running it using the Szegedi Butterfly 1 formula:


Func<Complex,Func<Complex,Complex>> fc =
p0 =>
(c => (new Complex(
((c.Img * c.Img) - Math.Sqrt(Math.Abs(c.Real))),
((c.Real * c.Real) - Math.Sqrt(Math.Abs(c.Img)))))
+ p0);

EscapeTimeFractal p = new EscapeTimeFractal(300, 300,fc);
p.MinX = -2;
p.MinY = 2;
p.MaxX =2;
p.MaxY = -2;
p.MaxIteration = 127;
p.OutputFileName = @"c:\temp\output.bmp";

p.Run();


The generated image is the following:



The code for this experiment can be found here.

Saturday, May 19, 2007

Using F# active patterns with LINQ expression trees

F# active patterns provide a nice way to handle complex object models.

In this post, I'm going to write a couple of active patterns to access parts of LINQ expression trees. As a little experiment I'm going to change the implementation of the Linq To Google Desktop expression tree handling from C# to F#.


The following definitions of active patterns provide access to sections of expression trees.


let (|BinaryExpression|_|) (x:Expression) =
if (x :? BinaryExpression)
then let be = (x :?> BinaryExpression)
in Some (be.Left,be.Right)
else None

let (|AndAlso|_|) (x:Expression) =
if (x.NodeType = ExpressionType.AndAlso)
then match x with
| BinaryExpression(l,r) -> Some (l,r)
| _ -> None
else None

let (|Equal|_|) (x:Expression) =
if (x.NodeType = ExpressionType.Equal)
then match x with
| BinaryExpression(l,r) -> Some (l,r)
| _ -> None
else None

let (|IsExpression|_|) (x:Expression) =
if (x :? TypeBinaryExpression)
then let be = (x :?> TypeBinaryExpression)
in Some (be.Expression,be.TypeOperand)
else None

let (|MethodCall|_|) (x:Expression) =
if (x.NodeType = ExpressionType.Call &&
(x :? MethodCallExpression))
then
let mc = x :?> MethodCallExpression
in Some (mc.Object,
mc.Method,
IEnumerable.to_list(mc.Arguments))
else None

let (|Cast|_|) (x:Expression) =
if (x.NodeType = ExpressionType.Convert
&& (x :? UnaryExpression))
then let ue = (x :?> UnaryExpression)
in Some (ue.Type,ue.Operand)
else None

let (|Lambda|_|) (x:Expression) =
if ( (x :? UnaryExpression) &&
((x :?> UnaryExpression).Operand :? LambdaExpression))
then let ue = (x :?> UnaryExpression)
in Some (ue.Operand :?> LambdaExpression).Body
else None

let (|MemberAccess|_|) (x:Expression) =
if (x.NodeType = ExpressionType.MemberAccess
&& (x :? MemberExpression))
then let ue = (x :?> MemberExpression)
in Some (ue.Expression,ue.Member)
else None

let (|MethodName|) (m:MethodInfo) = m.Name
let (|TypeName|) (t:Type) = t.Name

let (|PropertyWithName|_|) (m:MemberInfo) =
if (m :? PropertyInfo)
then Some (m.Name)
else None

let (|ExpressionType|) (x:Expression) = x.Type


As you can see, most of the active patterns are Partial Recognizers (see here), for example (|Equal|_|) . This is because of all the dynamic type tests that we need to do.


The C# code for expression tree handling presented on the Linq to Google Desktop post, is located in the GDesktop.CollectQueryInfo method. By using these new active pattern definitions the code could be rewritten in F# as follows:



let rec CollectQueryInfoFS (e:Expression, qi:GDQueryInfo) =
match e with
| Lambda(body) -> CollectQueryInfoFS (body,qi)
| AndAlso(l,r) -> CollectQueryInfoFS(l,qi);
CollectQueryInfoFS(r,qi)
| MethodCall(_,MethodName("Contains"),[argument]) ->
qi.AddTerm(GetStringFromArgument(argument))
| Equal(MemberAccess(ExpressionType(TypeName("GDFileResult")),
PropertyWithName("FileType")),
value) ->
qi.FileType <- GetStringFromArgument(value)
| Equal(MemberAccess(ExpressionType(TypeName("GDEmailResult")),
PropertyWithName(pName)),
value) ->
CollectEmailProperty(pName,GetStringFromArgument(value),qi)
| IsExpression(_,TypeName("GDFileResult")) ->
qi.ElementType <- new Nullable<GDElementType>( GDElementType.File )
| IsExpression(_,TypeName("GDEmailResult")) ->
qi.ElementType <- new Nullable<GDElementType>( GDElementType.Email )
| _ -> raise (new NotSupportedException(e.ToString()))


By using the active pattern definitions, the code is much smaller and easer to read and modify.

Right now only the expression tree handling part was translated to F#. The point where we call F# looks like this:


private GDQueryInfo ProcessWhereMethodCall(MethodCallExpression mcExpression)
{
Expression theObjectArgument = mcExpression.Arguments[0];
Expression whereExpressionTree = mcExpression.Arguments[1];

GDesktop provider =
(GDesktop)((ConstantExpression)theObjectArgument).Value;

GDQueryInfo qi = new GDQueryInfo();
//CollectQueryInfo(whereExpressionTree, qi);
Langexplr.FsharpTests.Langexplr.FsharpTests.CollectQueryInfoFS(
whereExpressionTree, qi);
return qi;

}


For future posts I'm going to try to rewrite the entire implementation.

Saturday, May 12, 2007

Adding support for projections to Linq to Google Desktop

I this post I'm going to show how support for projections was added to the Linq To Google Desktop experiment.

In order to support queries like:


var e10 = from t in gd
where t is GDFileResult &&
t.Contains("sql")
select ((GDFileResult)t).Location;

foreach (string s in e10) {
Console.WriteLine("The file name is: " + s);
}




We need to add support for handling the Select method. The query described above is processed by the compiler as:


gd.Where( ... ).Select( ... );


Given that the Where method processing returns an instance of GDQuery, then the processing of the Select method call is done in the GDQuery.CreateQuery method. The implementation of this method is the following:


public IQueryable<T> CreateQuery<T>(System.Linq.Expressions.Expression expression)
{
if (IsSelectMethodCall(expression))
{
MethodCallExpression methodCallExpr = (MethodCallExpression)expression;

UnaryExpression unaryQuoteExpr =
(UnaryExpression)methodCallExpr.Arguments[1];

LambdaExpression lambdaExpr =
(LambdaExpression)unaryQuoteExpr.Operand;

GDQuery query =
(GDQuery)((ConstantExpression)methodCallExpr.Arguments[0]).Value;

return query.AsEnumerable<GDResult>().Select(
(Func<GDResult,T>)lambdaExpr.Compile()).AsQueryable<T>();
}
else
{
throw new NotSupportedException("Not supported method call");
}

}


What this method does is to delegate the execution to the public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector); method , which is the extension method fro IEnumerable. The only thing we also need to do is to compile the lambda expression that represents the filter.

This is done this way because elements in the projection section cannot be used to make the Google Desktop query more specific. This is not the case of Linq To SQL where the projection section affects the way the query is processed by the DBMS.

Now we can write queries like:


var e11 = from t in gd
where t is GDFileResult &&
t.Contains("sql")
select new { FileName = ((GDFileResult)t).Location,
Info = new FileInfo(((GDFileResult)t).Location)};

foreach (var fResult in e11)
{
Console.WriteLine(fResult.Info.Name);
}

Friday, May 11, 2007

LINQ to Google Desktop

Given the number of recent blog posts and articles talking about creating LINQ providers, I decided to go ahead and try create a provider for Google Desktop.

The work in this post is based on the following posts/articles:



Also samples were created using the March 2007 CTP of Orcas.

The first thing to do is try to figure out how a Linq to Google Desktop query look like.

As discussed in a previous post, the result of a Google Desktop query could yield different kinds of results. For example a query for the "linq" term could return references to files, emails, calendar items, etc.

In order to deal with this, the first thing to do was to create a class hierarchy for the kinds of elements that could result from a Google Desktop query. This class hierarchy will have GDResult as the base class (which will be almost the same as an Indexable in the Google Desktop schemas).

Also we need to create a wrapper around the Google Desktop COM API, to capture the results and instantiate the appropriate class given the selected result.

For simplicity of this experiment only GDFileResult and GDEmailResult will be implemented and supported by the provider.


public class GDResult
{
public string Schema {...}
public string Content {... }

public bool Contains(string term)
{
...
}
}

public class GDFileResult : GDResult
{
public string FileType { ... }
public string Location { ... }
}

public class GDEmailResult : GDResult
{
public string From { ... }
public string To { ... }
public string Cc { ... }
public string Subject { ... }
}


Given this we could image how the Linq queries will look like. For example if we want to get all the PDF files that contain the "statement" and "expression" terms we could write:



GDDesktop gd = new GDProvider();
var t = from t in gd
where t.Contains("statement") &&
t.Contains("expression")&&
t is GDFileResult &&
((GDFileResult)t).FileType == "pdf"
select t;




This Linq query will be translated to a Google Desktop query:

" statement expression filetype:pdf "

And the t is GDFileResult must tell the Google Desktop API that only file references must be retrieved.

This first thing to do is to create the GDesktop this class represents our connection with Google Desktop this object is similar to a database connection object in Linq to Sql . GDesktop must implement the System.Linq.IQueryable<T> interface. This interface inherits from IEnumerable<T>. Since at this time there's no official documentation on how to implement this interface correctly, only the required elements were added.

This implementation of the Google Desktop provider looks like this:


public class GDesktop :IQueryable<GDResult>
{

public IQueryable<T> CreateQuery<T>(Expression expression)
{
GDQuery q = new GDQuery();
if (expression.NodeType == ExpressionType.Call)
{
MethodCallExpression mcExpression =
(MethodCallExpression)expression;
switch (mcExpression.Method.Name)
{
case "Where":
GDQueryInfo qi = ProcessWhereMethodCall(mcExpression);
q.QueryInfo = qi;
break;

default:
throw new NotImplementedException(
"Could not handle method: "+
mcExpression.Method.Name);

}
}
else
{
throw new NotSupportedException(expression.ToString());
}
return (IQueryable<T>)q;

}


public Expression Expression
{
get {
return Expression.Constant(this);
}
}


#region Expression tree processing
....
#endregion


#region Not implemented methods

public TResult Execute<TResult>(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}


public IEnumerator<GDResult> GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}

IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}

public IQueryable CreateQuery(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}

public Type ElementType
{
get { throw new Exception("The method or operation is not implemented."); }
}

public object Execute(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}

#endregion



The CreateQuery method is very important and is called with the expression tree representing the contents of the first section the query. For example for the following query:


var e = from t in gd
where t.Contains("linq")
select t;


Is interpreted by the compiler as:


var e = gd.Where(t => t.Contains("linq"));


When the compiler determines that gd is an IQueryable it generates calls to the Expression property and the CreateQuery method.

The CreateQuery method of the GDesktopclass is going to return a instance of a GDQuery object which represents the specific query that is being created. Also a GDQueryInfo object is used store collected query terms and modifiers from the expressions trees.

The only supported operation that can be applied to GDesktop is Where. This is very important because no other operation, like Select, Take or Skip could be applied because there's no way (that I know of) to get all the elements of the Google Desktop index!.

The ProcessWhereMethodCall walks the where expression tree to collect the required query terms and the query modifiers . The only supported elements in the where section are:


  1. Calls to the Contains method meaning that the document contains a term or phrase

  2. 'is' expressions to filter the type of element that must be returned

  3. Equality expression of "query time" properties for elements such as 'Filetype' or 'From' or 'To'



Processing for each of this elements is implemented this way:


#region Expression tree processing

private GDQueryInfo ProcessWhereMethodCall(MethodCallExpression mcExpression)
{
Expression theObjectArgument = mcExpression.Arguments[0];
Expression whereExpressionTree = mcExpression.Arguments[1];

GDesktop provider =
(GDesktop)((ConstantExpression)theObjectArgument).Value;

GDQueryInfo qi = new GDQueryInfo();
CollectQueryInfo(whereExpressionTree, qi);
return qi;

}

private void CollectQueryInfo(Expression whereExpressionTree, GDQueryInfo qi)
{
if (whereExpressionTree is UnaryExpression &&
((UnaryExpression)whereExpressionTree).Operand
is LambdaExpression)
{
UnaryExpression ue = (UnaryExpression)whereExpressionTree;
CollectQueryInfo(((LambdaExpression)ue.Operand).Body, qi);
}

if (whereExpressionTree.NodeType == ExpressionType.AndAlso)
{
BinaryExpression be = (BinaryExpression)whereExpressionTree;
CollectQueryInfo(be.Left,qi);
CollectQueryInfo(be.Right,qi);
}

if (whereExpressionTree.NodeType == ExpressionType.Call)
{
ProcessMethodCall(whereExpressionTree, qi);
}

if (whereExpressionTree.NodeType == ExpressionType.Equal)
{
ProcessEqual(whereExpressionTree, qi);
}

if (whereExpressionTree.NodeType == ExpressionType.TypeIs)
{
ProcessTypeIs(whereExpressionTree, qi);
}

}

private void ProcessTypeIs(Expression whereExpressionTree, GDQueryInfo qi)
{
if (whereExpressionTree is TypeBinaryExpression)
{
TypeBinaryExpression be = (TypeBinaryExpression)whereExpressionTree;

if (be.Expression is ParameterExpression &&
((ParameterExpression)be.Expression).Type == typeof(GDResult) &&
be.TypeOperand.IsSubclassOf(typeof(GDResult)))
{
switch (be.TypeOperand.Name)
{
case "GDFileResult":
qi.ElementType = GDElementType.File;
break;
case "GDEmailResult":
qi.ElementType = GDElementType.Email;
break;
case "GDResult":
qi.ElementType = null;
break;
default:
throw new NotSupportedException("Element type not supported");
}
}
}
else
{
throw new NotSupportedException("TypeIs expression not supported");
}
}

private void ProcessEqual(Expression whereExpressionTree, GDQueryInfo qi)
{

if (whereExpressionTree is BinaryExpression)
{
BinaryExpression be = (BinaryExpression)whereExpressionTree;
Expression leftExpression = be.Left;
if (IsAPropertyAccessToSpecificResultItem(leftExpression, typeof(GDFileResult)))
{
string argumentValue = (string)GetObjectFromArgument(be.Right);

switch (((MemberExpression)leftExpression).Member.Name)
{
case "FileType":
qi.FileType = argumentValue;
break;
}

}
else
{
if (IsAPropertyAccessToSpecificResultItem(
leftExpression,
typeof(GDEmailResult)))
{

string argumentValue = (string)GetObjectFromArgument(be.Right);

switch (((MemberExpression)leftExpression).Member.Name)
{
case "From":
qi.From = argumentValue;
break;
case "To":
qi.To = argumentValue;
break;
case "Cc":
qi.Cc = argumentValue;
break;
case "Subject":
qi.Subject = argumentValue;
break;
}
}
else
{
throw new NotSupportedException("Property not supported");
}
}
}
else
{
throw new NotSupportedException("Member access not supported");
}
}


private void ProcessMethodCall(Expression whereExpressionTree, GDQueryInfo qi)
{
MethodCallExpression mCall =
(MethodCallExpression)whereExpressionTree;
if (mCall.Method.Name == "Contains")
{
object o = GetObjectFromArgument(mCall.Arguments[0]);
qi.AddTerm((string)o);
}
else
{
throw new NotSupportedException("Method call not supported "+mCall.ToString());
}

}


private object GetObjectFromArgument(Expression e)
{
return LambdaExpression.Lambda(e).Compile().DynamicInvoke();

}

private static bool IsAPropertyAccessToSpecificResultItem(Expression leftExpression, Type elementType)
{
return leftExpression.NodeType == ExpressionType.MemberAccess &&
((MemberExpression)leftExpression).Expression.NodeType
== ExpressionType.Convert &&
((UnaryExpression)((MemberExpression)leftExpression).Expression).Type
== elementType &&
((UnaryExpression)((MemberExpression)leftExpression).Expression).Operand.Type
== typeof(GDResult) &&
((MemberExpression)leftExpression).Member is PropertyInfo;
}

#endregion



Certainly this code is not pretty. In the future I'll try to improve it.

Since we want to allow the user to pass variables, function calls, property references, literal, etc. to the query arguments. For example:


var e2 = from t in gdProvider
where t.Contains(args[0]) &&
t.Contains(GetString(1))&&
t is GDFileResult &&
((GDFileResult)t).FileType == "pdf"
select t;



Because of this we need to get the value from the expression tree representing the query argument. This is done in the GetObjectFromArgument method.


private object GetObjectFromArgument(Expression e)
{
return LambdaExpression.Lambda(e).Compile().DynamicInvoke();

}


Note that the expression tree representing the query argument is being compiled as the body of a lambda expression with no parameters. Then we call the generated delegate to get the value. This is a very good example that shows that you have complete control over the complete query, even the arguments.

Now that we have processed the where section of the query and that we have collected the necessary elements to build the query string, we need a place to put the call to Google Desktop. By looking above in the definition of the GDesktop.CreateQuery method is important to note that GDQuery also have to implement IQueryable.For now our implementation is very basic. The place to put the call to Google Desktop will be the GetEnumerator method of the GDQuery class.


public class GDQuery : IQueryable
{

public IEnumerator GetEnumerator()
{
GDesktopWrapper gd = new GDesktopWrapper();
string qs = qInfo.CreateQueryString();
return gd.Query(qs, qInfo.ElementType).GetEnumerator();
}

public Expression Expression
{
get {
return Expression.Constant(this);
}
}

private GDQueryInfo qInfo;
public GDQueryInfo QueryInfo
{
get {
return this.qInfo;
}
set
{
this.qInfo = value;
}
}



#region Not implemented methods
...
#endregion
}


For features like projections we need to put more work on this implementation.

Code for this experiment can be found here.

In future posts I'll continue working with this implementation in order to add new features. For example projections,joins, and other Linq features.