Getting started

Installation

To use the library in your project, first download the files from the download page and include the DLL in your project. For Microsoft Visual C# Express/Studio users, right-click 'References' in the solution explorer and select 'Add Reference ...'. Under the tab Browse, browse to the downloaded MultiParse.DLL file and click ok.

First program

Let's look at a simple console program to demonstrate.

using System;
using MultiParse;

namespace Tutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression e = new Expression();
            e.ParseExpression = "5*3+2*6-cos(3+8/3)*max(2,4)"; // returns 25.8653512581471
            Console.WriteLine(e.Evaluate());
            e.ParseExpression = "\"Hello \"+\"World\""; // returns Hello World
            Console.WriteLine(e.Evaluate());
            e.ParseExpression = @"""\x40"""; // returns @
            Console.WriteLine(e.Evaluate());
            Console.ReadKey();
        }
    }
}

Pretty simple isn't it? The constructor Expression() automatically loads support for all default data types, explicit type casts, operators and Math functions. All datatype literals are processed the same (if the defaults are used) as in C#. This means for example that the literal "str" will be processed as a string. The literal 5ul will be detected as an unsigned long , 1.5f as a float, etc.

The first time the method Evaluate() is called, the expression will be compiled while being calculated. This means that subsequent evaluations will be processed faster because it then uses the compiled expression list. Assigning a new expression to ParseExpression will cause the method to recompile the complete string the next time Evaluate() is called.

To be able to add your own custom types, I believe it will help that you have some knowledge about stack-based expression evaluation or RPN (Reverse Polish Notation). If you do not and you want to customize MultiParse, I recommend doing a quick read through the wiki page http://en.wikipedia.org/wiki/Reverse_Polish_notation. The parser will however do most of the work for you. The parser itself is based on the Shunting Yard Algorithm by Dijkstra, which you can find described at http://en.wikipedia.org/wiki/Shunting-yard_algorithm.

Choosing data types

It is completely up to the user to decide what data types can be read by MultiParse. Take the next example:

using System;
using MultiParse;
using MultiParse.Default;

namespace Tutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression e = new Expression(MPDefault.DataTypes.Double, MPDefault.Operators.All, MPDefault.Functions.None);
            object result = e.Evaluate("3+6/8");
            Console.WriteLine(result.ToString() + " of type " + result.GetType());
            Console.ReadKey();
        }
    }
}

This constructor will create a MultiParse expression object that can only read double literals, can work with all operators, but without any Math functions. As a result, although the evaluation string only contains integer literals (3, 6 and 8), the result will be a double because that is the only data type loaded that can read these literals.

Each MultiParse expression object contains its own list of data types, operators and functions that can be accessed manually. The following example creates an expression object that can only read doubles and perform an explicit cast to double. It does not contain any operators or functions.

Expression e = new Expression(MPDefault.DataTypes.Double);
e.ParseExpression = "5";
object result = e.Evaluate();

The following example is equivalent, but manually adds the data type and explicit type cast to their respective list:

Expression e = new Expression(MPDefault.DataTypes.None);

// Add a literal parser for type double
e.DataTypes.Add(new MPDouble());

// Add an explicit cast operator for type double
e.Operators.Add(new MPDoubleCast());

However, and this is important: when adding anything manually to the lists DataTypes, Operators or Functions the order becomes important. The first item in the list will also be the first one that will be used by the parser. The following code for example will throw an exception:

Expression e = new Expression(MPDefault.DataTypes.None);

// Add literal parsers for type double and int
e.DataTypes.Add(new MPDouble());
e.DataTypes.Add(new MPInt32());
e.ParseExpression = "5";
int result = (int)e.Evaluate();

The parser first tries to identify the 5 using the data type MPDouble() which succeeds. The 5 is consequently parsed as a double, and not as an integer. The result of e.Evaluate() is a double, and cannot be casted to an integer so it throws an exception. Changing the order in the list leads to code that does work:

// Add a literal parser for type double
e.DataTypes.Add(new MPInt32());
e.DataTypes.Add(new MPDouble());
e.ParseExpression = "5";
int result = (int)e.Evaluate();

The integer will now try to read 5 first resulting in an integer. If the parse expression is for example "3.6", the integer data type will fail to read it, and the parser moves to the next data type in the list (a double). The data type MPDouble() succeeds in reading the literal, and the parser will use the parsed double.

Each data type object is derived from the abstract class MPDataType. It is able to match an expression for a literal, and return an object that is pushed on the stack by the expression parser.

Variables
It is possible to use variables in your expression, given that the datatype MPVariable is included as a data type. The following example adds support for variables:

Expression e = new Expression(MPDefault.DataTypes.Double | MPDefault.DataTypes.Variable, MPDefault.Operators.All, MPDefault.Functions.None);
e.Evaluate("c=3+6/8");
object result = e.Evaluate("c*2");

Variables can be assigned to if the operator MPAssignment is included in the operator list. By default, new variables are generated on the fly, with their default value null. In some cases, this is not wanted as it makes debugging more difficult (e.g. it can recognize the variable float in the expression "(float)3.6" if the operator (float) is not added as an operator). This can be turned off using the following code:

Expression e = new Expression(MPDefault.DataTypes.Double | MPDefault.DataTypes.Variable, MPDefault.Operators.All, MPDefault.Functions.None);

// Find variable datatypes
MPDataType[] vars = e.FindDataType(typeof(MPVariable));
foreach (MPDataType var in vars)
    (var as MPVariable).AutoGenerate = false;

// Try to evaluate
e.Evaluate("c=3+6/8");
object result = e.Evaluate("c*2");

The code will throw an exception if the variable does not exist. The variable can be added manually using:

Expression e = new Expression(MPDefault.DataTypes.Double | MPDefault.DataTypes.Variable, MPDefault.Operators.All, MPDefault.Functions.None);

// Find variable datatypes
MPDataType[] vars = e.FindDataType(typeof(MPVariable));
foreach (MPDataType var in vars)
    (var as MPVariable).AutoGenerate = false;

// Add the variable "c" with a value of the double 0.0
(vars[0] as MPVariable).Variables.Add("c", new MPVariableInstance(0.0));

// Try to evaluate
e.Evaluate("c=3+6/8");
object result = e.Evaluate("c*2");

Custom data types are derived from the abstract class MPDataType. A data type is able to match a literal or variable at the start of an expression, and it returns the correct object that will be pushed on the output stack by the expression parser.

Choosing operators

The operators behave much like data types. But, instead of reading literals, they access the output stack directly. For example, the addition operator could execute using the following output stack:

top = (double)3.6
second = (int)6
third = (int)-2

The addition pops off the right operand 3.6 and left operand 6, adds them resulting in 9.6 and pushes the result back on the stack. The stack after executing the operator results in:

top = (double)9.6
second = (int)-2

Operators are derived from the abstract class MPOperator. It is able to match an operator at the start of an expression. It can also execute using the output stack.

Choosing functions and brackets

Brackets are structures that have a left and a right side and can have multiple arguments inside. For example, the regular round brackets are defined by a ( and matches with a ) and cannot have multiple arguments.
Functions can also be defined as such a bracketted structure. For example, the function max(a,b) has a left side max( and a right side ) and can have multiple arguments inside delimited by a comma ,. A function is therefore implemented as a special case of a bracket (MPFunction inherits from MPBrackets).

So any functions derived from MPFunction have the form fun(...) and can have any number of arguments. By default, the functions of System.Math are implemented (except for DivRem) and work for lowercase or capitalized identifiers (eg. cos() and Cos() will work, but not cOs()).

Next, we will see how we can create our own Custom types!

Last edited Dec 4, 2014 at 10:40 AM by SBoulang, version 15