Wednesday 2 November 2011

Arithmetic Converter

So before we get started on some more of the data grid functionality just wanted to upload a converter I've been working on yesterday.

You'll have probably seen a number of attempts at this before so why have I bothered writing my own? Well there are three reasons:
  1. I haven't written many parsers before so thought I would give it a go. It's the best way to learn.
  2. Most of the ones out there are not easily extendable so wanted to see if I could make on that was.
  3. Most good arithmetic expression parsers available on the market cost money.
I'm really writing this because I need to use it for some of the deeper dives into styling within WPF and XAML that I'll be doing later. Although it is obviously possible to write a converter for each and every styling equation you come across (and when you get slightly more complex there are quite a few of them) it is a lot easier to understand if all in one place and a lot easier to write as well.

If you have style designers, they can focus completely on the XAML without any requirement of c# knowledge or having to be bogged down by the unit testing that comes with that as well.

Here you will find the code that incorporates the arithmetic converter codebase, plus the associated unit test and an example of it's usage which looks something like this:


All of the rows in this view are calculated via bindings. No magic here!

So what does the converter allow at the moment?
  • Constants: Currently supported are digits and Pi.
  • Bound Variables: The converter can be used with either a binding or multi-binding. There is nothing stopping these being referenced multiple times.
    • Binding - the variable can be referenced either by x, a or {0}
    • MultiBinding - The first four bindings can be referenced either by x,y,z,t or a,b,c,d. It is also possible to use {n} to reference the nth element. 
  • Unary Operators: Either a negative or positive sign in front of a variable or constant.
  • Binary Operators: Support for addition, subtraction, division and multiplication in standard precedence order.
  • Brackets: Brackets to control evalation order.
  • Trigonometric Functions: Currently can support the application of sine, cosine and tangent functions.
  • Absolute Function: Can be used to evaluate the absolute value of an expression.
This has grown slightly organically over the last few hours but here's the basic components:

ArithmeticConverter.cs
This is focused on the management of type conversion between the bound sources and the target. It uses the parser to evaluate the operator tree and calculate the result from this.

ArithmeticExpressionParser.cs
Firstly will maintain a cache of any operator trees for a given expression. This means that our performance of parsing the expression can take a slight hit as this will all be evaluated up front for us.

So lets take a look at the key part of this class, the parse method. This is a recursively called method which each of the tokens have access to call if they need to parse the expression at a certain precedence level (i.e, when evaluating the right hand side of multiplication, we don't want to be taking into account additions as they'll take control)

   1:  public IArithmeticOperator Parse(string parseExpression, ref int position, int precedence, ref bool needsClosed)
   2:  {
   3:      IArithmeticOperator currentOperator = null;
   4:   
   5:      while (position < parseExpression.Length)
   6:      {
   7:          SkipWhiteSpace(parseExpression, ref position);
   8:   
   9:          var applied = false;
  10:          var highPrecedenceTokens = from t in Tokens where t.Precedence() > precedence select t;
  11:          foreach (var token in highPrecedenceTokens)
  12:          {
  13:              if (token.Apply(this, parseExpression, ref currentOperator, ref position, ref needsClosed))
  14:              {
  15:                  applied = true;
  16:                  break;
  17:              }
  18:          }
  19:   
  20:          // Keep returning until we no longer need to be closed.
  21:          if (needsClosed)
  22:              return currentOperator;
  23:   
  24:          if (applied) 
  25:              continue;
  26:   
  27:          if (precedence == (int)ArithmeticTokenPrecedence.LowestPrecendence)
  28:              throw new ArgumentException(String.Format("Failed to parse expression {0}", parseExpression));
  29:   
  30:          return currentOperator;
  31:      }
  32:   
  33:      return currentOperator;
  34:  }
 
The key things of note here are the precedence settings which are used to control which tokens can take over control from the current one. So for example, if we're currently parsing an addition but then come across a multiplication, it will take precedence.

If it was the other way round, then the current parse loop would be stopped (since addition wouldn't be a token to check) and the multiplication operator would be closed out. We would then go on to evaluate the addition afterwards.

The other main point is the needs closed parameter which is used for bracketing expressions. When a close bracket is found, we'll wriggle our way all the way back to the opening bracket and close out any open operators along the way. This is done by all tokens returning false when the expression needs closed until we get back to the bracket token, which will reset the variable so we can continue on our merry way.

The operators are relatively simple so I won't go into detail on them but what I will talk about is an example token so you can get a feel for how they work (and understand how to write your own if it so desires you). For our example we'll use a BinaryArithmeticToken (which all the basic operations like addition, subtraction, multiplication and division inherit from).

   1:  public abstract class BinaryArithmeticToken : BaseArithmeticToken
   2:  {
   3:      protected abstract char OperatorCharacter { get; }
   4:      protected abstract BinaryArithmeticOperator GetOperator(IArithmeticOperator leftOperator, IArithmeticOperator rightOperator);
   5:   
   6:      public override bool Apply(ArithmeticExpressionParser parser, string expression, ref IArithmeticOperator arithmeticOperator, ref int position)
   7:      {
   8:          if (arithmeticOperator == null)
   9:              return false;
  10:   
  11:          if (expression[position] != OperatorCharacter)
  12:              return false;
  13:   
  14:          position++;
  15:   
  16:          arithmeticOperator = GetOperator(arithmeticOperator, parser.Parse(expression, ref position, Precedence()));
  17:          return true;
  18:      }
  19:  }
 
So what is going on here? Well the operator character just refers to the grammer that we use in the expression like + or *. The key is in the application.

It first checks if arithmetic operator is null and doesn't process it if it is. The reason being that all binary operators (not functions if you're thinking of implementing it) have an expression on the left hand and on the right hand side. If the arithmetic operator is currently null, then no left hand side must exist so we'll pass this down to the unary tokens.

Then we obviously check the character matches up with the value in the expression. If so, we move the position forward passed this character and do our processing. All this means is that we set the arithmetic operator to a new BinaryOperator, where the left hand side is the currently evaluated operator, and the right hand side is left to the parser.

We set a precedence level so that no lowly operators can try to take the power from us.

So all of that is well and good, but lets be honest, most of the time you just want to hook up a new function. Well it's as simple as anything. say I want to write a new function which changes degrees to radians. What do I need to do?

Well given that there is a UnaryFunctionArithmeticToken already out there, you just need to inherit from that and fill in the pieces. Once you save it down, the parser will automatically pick it up and attempt to parse the expression with it! The code ain't too hard either.

   1:  public class DegsToRadsFunctionArithmeticToken : UnaryFunctionArithmeticToken
   2:  {
   3:      protected override IEnumerable<string> FunctionNames
   4:      {
   5:          get
   6:          {
   7:              yield return "radians";
   8:              yield return "rad";                
   9:          }
  10:      }
  11:   
  12:      protected override UnaryFunctionArithmeticOperator GetOperator(IArithmeticOperator arithmeticOperator)
  13:      {
  14:          return new UnaryFunctionArithmeticOperator(arithmeticOperator, x => (Math.PI / 180) * x);
  15:      }
  16:  }
 
All you need to know is how to use it. Well that isn't too much either. You can add the following XAML into the example provided to convert our entered value from degrees into radians

   1:  <Label Content="{Binding Text, ElementName=Text, Converter={ArithmeticConverter:ArithmeticConverter}, ConverterParameter=rad(x), UpdateSourceTrigger=PropertyChanged}"/>

And of course you can combine this in anyway you like with other operators or functions in your expression.

I want to stress one major thing here. I highly recommend NOT putting business logic into your XAML. It may seem very easy to do but should still focus on keeping the view seperate from the model using the MVVM pattern.

We will only be using this for styling purposes and as a visual helper in the later posts. Anyway, now that we have this, we can get on to some of the more cooler styling techniques. I'll also be looking to use the MediaFire site to host all the code written in this blog.

No comments:

Post a Comment