Custom data sources

MPDataSource

Data sources consist of two classes. The first one is a data source. This class should be derived from MPDataSource and should override the method:

/// <summary>
/// Returns -1 if the operator doesn't match. Else it returns the length of the matched string.
/// </summary>
/// <param name="expression">The expression</param>
/// <param name="previousToken">The previous expression</param>
/// <returns></returns>
public abstract int Match(string expression, object previousToken, out MPDataGenerator data);

The data source is only necessary while compiling. If a match is found at the start of expression, then length of the match is returned, and data contains an MPDataGenerator object that will be invoked at each evaluation.
The argument previousToken can be used to find out if a unary operator is expected or not.

The second part of a data source is the data generator.

MPDataGenerator

This class will run every time the evaluation of an expression is performed. A data generator is derived from the abstract class MPDataGenerator and should override the method:

/// <summary>
/// Generates an object at evaluation time
/// </summary>
/// <returns></returns>
public abstract object Generate();

The method Generate() is invoked to request an object that should be pushed on the output stack.

For example, the generic data source for variables is defined as follows:

/// <summary>
/// An event handler that is processed when a variable is requested by its name
/// </summary>
/// <param name="sender"></param>
/// <param name="data"></param>
/// <returns></returns>
public delegate void RequestVariableValueEventHandler(object sender, VariableValueRequestData data);

/// <summary>
/// A class containing data
/// </summary>
public class VariableValueRequestData
{
    private string name;

    /// <summary>
    /// Gets the name of the variable of which the value is requested
    /// </summary>
    public string Name { get { return name; } }

    /// <summary>
    /// Gets or sets the value of the variable
    /// </summary>
    public object Value { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="name"></param>
    public VariableValueRequestData(string name)
    {
        this.name = name;
        Value = null;
    }
}

/// <summary>
/// Can generate variables
/// </summary>
public class MPVariable : MPDataSource
{
    /// <summary>
    /// An event that is called when a variable requests its value
    /// </summary>
    public event RequestVariableValueEventHandler ValueRequested;

    /// <summary>
    /// Will request data at evaluation time
    /// </summary>
    class MPVariableValue : MPDataGenerator
    {
        private string name;
        private MPVariable parent;

        /// <summary>
        /// Constructor
        /// </summary>
        public MPVariableValue(string name, MPVariable parent)
            : base()
        {
            this.parent = parent;
            this.name = name;
        }

        /// <summary>
        /// Calls the event to request a value
        /// </summary>
        /// <returns></returns>
        public override object Generate()
        {
            VariableValueRequestData data = new VariableValueRequestData(name);
            if (parent.ValueRequested != null)
                parent.ValueRequested(this, data);

            // Extract the result
            return data.Value;
        }
    }

    /// <summary>
    /// Find a variable
    /// </summary>
    /// <param name="expression"></param>
    /// <param name="previousToken"></param>
    /// <param name="generator"></param>
    /// <returns></returns>
    public override int Match(string expression, object previousToken, out MPDataGenerator data)
    {
        // Check for a variable
        Match m = Regex.Match(expression, @"^[a-zA-Z_]\w*");
        if (m.Success)
        {
            data = new MPVariableValue(m.Value, this);
            return m.Length;
        }

        // Not found
        data = null;
        return -1;
    }
}

When a variable is matched (a series of characters starting with a letter or an underscore) then a new data generator is created with that identifier. This data generator is returned and stored by the parser.
When the expression is evaluated, the data generator will invoke the event ValueRequested of its parent object using its own identifier.

Example

A typical application using the provided class MPVariable looks like this:

using System;
using MultiParse;
using MultiParse.Default;

namespace Tutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression e = new Expression();

            // Add a data source for variables
            MPVariable varSource = new MPVariable();
            varSource.ValueRequested += new RequestVariableValueEventHandler(varSource_ValueRequested);
            e.DataSources.Add(varSource);

            // Evaluate the expression
            object result = e.Evaluate("b==(int)floor(_varA)"); // Returns True System.Boolean
            Console.WriteLine(result + " " + result.GetType().ToString());
            Console.ReadKey();
        }

        /// <summary>
        /// Called when a variable needs its value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        static void varSource_ValueRequested(object sender, VariableValueRequestData data)
        {
            if (data.Name == "_varA")
                data.Value = 3.6;
            else if (data.Name == "b")
                data.Value = 3;
            else
                throw new ParseException("Invalid variable " + data.Name);
        }
    }
}

The event throws an exception if the variable is neither "_varA" or "b", else it returns a double or int respectively. The expression parser therefore evaluates "3==(int)floor(3.6)" which is true.

That's all there is to know about MultiParse. Have fun!

Last edited Dec 29, 2013 at 4:19 PM by SBoulang, version 1