Click here to Skip to main content
11,584,208 members (38,324 online)
Click here to Skip to main content

An Expression Parser for CodeDom

, 22 Apr 2009 CPOL 95.6K 863 67
Rate this:
Please Sign up or sign in to vote.
Tired of writing long, complicated CodeDom expressions? This parser's for you!

Introduction

If you've ever tried to work at any length with CodeDom, you know that it's verbose and sometimes confusing to work with. For me, the hardest part of working with CodeDom is writing expressions. I find myself trying to limit what happens in my expressions so that I can make the CodeDom easier to write.

After seeing Pascal Ganaye's article (The expression evaluator revisited (Eval function in 100% managed .NET)[^]), I figured that I could use that code and create a CodeDom expression parser. This article presents my results.

Putting It To Use

I've put this parser to work and made an embeddable rules library in .NET 3.0. It stores CodeDom statements in the application configuration. Check it out here: DmRules - A helper library for running rules in .NET 3.0[^].

Features

The parser uses a C#-like syntax and can read all of the following:

  • Local variables
  • Primitives: string, double, int, long, etc.
  • Operators: +, -, *, /, %, >, >=, <, <=, ==, !=, !, &, &&, |, ||, unary -
  • Fields
  • Properties
  • Methods
  • Indexes
  • Casting with (Type)
  • typeof()
  • Enums

Using the Code

Let's say I have to write a statement in CodeDom like this:

xr.HasAttributes && xr.MoveToAttribute("xmi.value")

It's a simple enough thing to want to put in an if condition. Writing the CodeDom would look something like this:

CodeExpression ce = new CodeBinaryOperatorExpression(
   new CodePropertyReferenceExpression(
   new CodeVariableReferenceExpression("xr"), "HasAttributes"),
   CodeBinaryOperatorType.BooleanAnd, new CodeMethodInvokeExpression(
   new CodeMethodReferenceExpression(
   new CodeVariableReferenceExpression("xr"), "MoveToAttribute"), 
   new CodeExpression[] { new CodePrimitiveExpression("xmi.value") }));

With this library, you can write this instead:

Parser p = new Parser();
CodeExpression ce = p.ParseExpression(
   "xr.HasAttributes && xr.MoveToAttribute(\"xmi.value\")");

Now, I also have other requirements. For instance, I need the parser to know what a field is and to recognize when I do a cast. So, I might want to have the following expression:

(double)Convert.ChangeType(xr.Value, typeof(double))

Here's how it would look in CodeDom:

CodeExpression ce1 = new CodeCastExpression(
   new CodeTypeReference(typeof(double)), 
   new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(
   new CodeTypeReferenceExpression(typeof(Convert)), "ChangeType"), 
   new CodeExpression[] { new CodePropertyReferenceExpression(
   new CodeVariableReferenceExpression("xr"), "Value"), 
   new CodeTypeOfExpression(new CodeTypeReference(typeof(double))) }));

And here's how to write it with the parser:

Parser p = new Parser();
CodeExpression ce = p.ParseExpression(
   "(double)Convert.ChangeType(xr.Value, typeof(double))");

The reason that the ParseExpression method is done as an instance method instead of a static method is because I want the Parser object to collect some context. There are two properties exposed on Parser: Fields and RecognizedTypes. By default, anything after a this reference is considered a property. If the name matches one of the entries in the Fields collection, then it is treated as a field instead. Also, the parser will check the RecognizedTypes to see if an identifier should be treated as a variable or as a type. It also helps when using a cast or a typeof.

In the most recent update of this code, I have included the ability to use enums. Each of the enumerations is treated as a field reference of a type, so the parser needs to handle them differently. Just add an enum type to the Enums property of the parser.

Parser p = new Parser();
p.Enums.Add("MyEnum", new CodeTypeReference("MyEnum"));
CodeExpression ce = p.ParseExpression("this.Foo == MyEnum.Bar");

Some Other Examples

All of the following are correctly parsed:

-5                  // This one actually parses as (0 - 5)
!stream.EndOfFile   // Parses as (stream.EndOfFile == false)
!stream.EndOfFile && this.Count > 5
foo[0]
foo.bar()[0] > 2
(int)foo
s == ""

What's Inside

Here's a listing of the files inside:

  • CodeDomExpParser
    • Enums.cs
    • Parser.cs
    • Token.cs
    • Tokenizer.cs
  • CodeDomExpParser.Test - This is a test harness for the parser library.
    • DogFood.cs - This contains some of the more complicated CodeDom statements I had to do in my XMI CodeDom library and also some examples gathered from the comments. Hence the expression, "eating my own dog food."
    • TestParser.cs - Throws a few tests at the parser. Look at this file for examples of what can be parsed.
    • TestTokenizer.cs - Runs some tests against the tokenizer.

Summary

I used a lot of the ideas that Pascal was using in his article for the tokenizer and parser. With the basic stuff in place, I pretty much went my own way. It was a cool project to work on. Hopefully, some of you will find it useful.

History

  • 0.1 : 2006-06-08 : Initial version
    • Handles most of the expressions that I need it to do
    • More thorough testing would definitely be nice
    • One of the to-do items is to allow the input of an existing CodeDom graph for finding types, fields, and the like
  • 0.2 : 2006-06-21
    • Added modulus operator and created a .NET 2.0 version
  • 0.3 : 2006-07-19
    • Fixed a problem where empty strings were not being parsed
    • Added the capability to parse enums
  • 0.4 : 2009-04-20
    • Fixed two bugs mentioned in the comments
    • Converted to Visual Studio 2008 solution
    • Uses Visual Studio Test Tools instead of NUnit
    • DogFood test cases now use a CompareExpression method that automates testing

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dustin Metzgar
Software Developer
United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionMy vote of 5 Pin
Filip D'haene8-Sep-11 6:48
memberFilip D'haene8-Sep-11 6:48 
GeneralBroken Link Pin
ByteGhost14-Jul-10 18:11
memberByteGhost14-Jul-10 18:11 
GeneralDecimal Point Pin
zarpas3-Jun-09 5:13
memberzarpas3-Jun-09 5:13 
GeneralRe: Decimal Point Pin
Dustin Metzgar4-Jun-09 21:28
memberDustin Metzgar4-Jun-09 21:28 
I just tried making a test for this:
            CodeExpression ce1 = new CodeBinaryOperatorExpression(
                new CodePrimitiveExpression(1),
                CodeBinaryOperatorType.Add,
                new CodePrimitiveExpression(1.1D));
            Console.WriteLine("Expected:");
            WriteExpression(ce1);
 
            Parser p = new Parser();
            CodeExpression ce2 = p.ParseExpression("1 + 1.1");
            Console.WriteLine("Parsed:");
            WriteExpression(ce2);
 
            Assert.IsTrue(CompareExpressions(ce1, ce2));
The issue does not repro for me. Is this the original string you tried? If you can't send the original string, are you able to break it down to something that still produces the error?
GeneralRe: Decimal Point Pin
zarpas5-Jun-09 0:47
memberzarpas5-Jun-09 0:47 
GeneralRe: Decimal Point Pin
Dustin Metzgar7-Jun-09 16:29
memberDustin Metzgar7-Jun-09 16:29 
GeneralRe: Decimal Point Pin
zarpas8-Jun-09 23:04
memberzarpas8-Jun-09 23:04 
GeneralRe: Decimal Point Pin
Dustin Metzgar8-Jun-09 23:09
memberDustin Metzgar8-Jun-09 23:09 
GeneralRe: Decimal Point Pin
zarpas10-Jun-09 5:06
memberzarpas10-Jun-09 5:06 
QuestionIs this a bug? Pin
zarpas28-Apr-09 5:16
memberzarpas28-Apr-09 5:16 
AnswerRe: Is this a bug? Pin
Dustin Metzgar28-Apr-09 20:59
memberDustin Metzgar28-Apr-09 20:59 
GeneralRe: Is this a bug? Pin
zarpas28-Apr-09 22:02
memberzarpas28-Apr-09 22:02 
GeneralParser has a big bug Pin
Member 345703616-Apr-09 5:29
memberMember 345703616-Apr-09 5:29 
GeneralRe: Parser has a big bug Pin
Dustin Metzgar19-Apr-09 20:55
memberDustin Metzgar19-Apr-09 20:55 
GeneralTwo things I would like to say ;-) Pin
michael.kruisz14-Oct-08 0:18
membermichael.kruisz14-Oct-08 0:18 
GeneralRe: Two things I would like to say ;-) Pin
Dustin Metzgar14-Oct-08 8:24
memberDustin Metzgar14-Oct-08 8:24 
GeneralRe: Two things I would like to say ;-) Pin
Michael Kruisz14-Oct-08 21:28
memberMichael Kruisz14-Oct-08 21:28 
GeneralRe: Two things I would like to say ;-) Pin
Dustin Metzgar20-Oct-08 7:23
memberDustin Metzgar20-Oct-08 7:23 
GeneralRe: Two things I would like to say ;-) Pin
Michael Kruisz21-Oct-08 21:20
memberMichael Kruisz21-Oct-08 21:20 
GeneralA little fix Pin
Emmanuel Liossis14-Mar-08 6:31
memberEmmanuel Liossis14-Mar-08 6:31 
GeneralRe: A little fix Pin
Dustin Metzgar14-Mar-08 15:32
memberDustin Metzgar14-Mar-08 15:32 
QuestionWhat a nice piece of work Pin
Pascal Ganaye31-Jul-07 5:11
memberPascal Ganaye31-Jul-07 5:11 
Questionencounter an execption Pin
beergetit12-Jun-07 17:04
memberbeergetit12-Jun-07 17:04 
AnswerRe: encounter an execption Pin
Dustin Metzgar14-Oct-08 8:49
memberDustin Metzgar14-Oct-08 8:49 
GeneralDateTime Support Pin
bfizel27-Feb-07 2:03
memberbfizel27-Feb-07 2:03 
GeneralRe: DateTime Support Pin
Dustin Metzgar27-Feb-07 9:44
mvpDustin Metzgar27-Feb-07 9:44 
GeneralTime Saver Pin
Zapper.Net24-Jul-06 5:48
memberZapper.Net24-Jul-06 5:48 
GeneralRe: Time Saver Pin
Dustin Metzgar24-Jul-06 17:24
memberDustin Metzgar24-Jul-06 17:24 
GeneralA minor change ... Pin
Sébastien Lorion26-Jun-06 4:25
memberSébastien Lorion26-Jun-06 4:25 
GeneralMan... Pin
BoneSoft13-Jun-06 10:48
memberBoneSoft13-Jun-06 10:48 
GeneralRe: Man... Pin
Dustin Metzgar13-Jun-06 11:29
memberDustin Metzgar13-Jun-06 11:29 
GeneralRe: Man... Pin
BoneSoft14-Jun-06 16:33
memberBoneSoft14-Jun-06 16:33 
GeneralRe: Man... Pin
Dustin Metzgar14-Jun-06 16:41
memberDustin Metzgar14-Jun-06 16:41 
GeneralMy name in the body ! Pin
Pascal Ganaye9-Jun-06 7:56
memberPascal Ganaye9-Jun-06 7:56 
GeneralGoto is bad Pin
Steve Hansen8-Jun-06 3:54
memberSteve Hansen8-Jun-06 3:54 
QuestionRe: Goto is bad Pin
Dustin Metzgar8-Jun-06 3:59
memberDustin Metzgar8-Jun-06 3:59 
AnswerRe: Goto is bad Pin
Steve Hansen8-Jun-06 4:11
memberSteve Hansen8-Jun-06 4:11 
GeneralRe: Goto is bad Pin
Dustin Metzgar8-Jun-06 4:14
memberDustin Metzgar8-Jun-06 4:14 
GeneralHmm... Pin
Dustin Metzgar8-Jun-06 2:58
memberDustin Metzgar8-Jun-06 2:58 
GeneralRe: Hmm... Pin
Marc Clifton8-Jun-06 5:20
protectorMarc Clifton8-Jun-06 5:20 
GeneralRe: Hmm... Pin
Dustin Metzgar9-Jun-06 1:21
memberDustin Metzgar9-Jun-06 1:21 
GeneralRe: Hmm... Pin
Igor Sukhov8-Jun-06 14:16
memberIgor Sukhov8-Jun-06 14:16 
GeneralRe: Hmm... Pin
Dustin Metzgar8-Jun-06 16:04
memberDustin Metzgar8-Jun-06 16:04 
GeneralRe: Hmm... Pin
bodzebod13-Jun-06 3:55
memberbodzebod13-Jun-06 3:55 
GeneralRe: Hmm... Pin
Dustin Metzgar13-Jun-06 4:22
memberDustin Metzgar13-Jun-06 4:22 
GeneralRe: Hmm... Pin
ENewton25-Jul-06 8:42
memberENewton25-Jul-06 8:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150603.1 | Last Updated 22 Apr 2009
Article Copyright 2006 by Dustin Metzgar
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid