Custom functions


MPBrackets

Brackets are defined by a left side, a right side and delimiters. The following methods need to be implemented:

/// <summary>
/// Match for an opening bracket
/// </summary>
/// <param name="expression"></param>
/// <param name="previousToken"></param>
/// <param name="bracketOpen"></param>
/// <returns></returns>
public abstract int MatchOpen(string expression, object previousToken);

/// <summary>
/// Match for a separator
/// </summary>
/// <param name="expression"></param>
/// <param name="previousToken"></param>
/// <param name="separator"></param>
/// <returns></returns>
public abstract int MatchSeparator(string expression, object previousToken);

/// <summary>
/// Match for a closing bracket
/// </summary>
/// <param name="expression"></param>
/// <param name="previousToken"></param>
/// <param name="bracketClose"></param>
/// <returns></returns>
public abstract int MatchClose(string expression, object previousToken);

/// <summary>
/// Matches
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public abstract bool BracketsMatch(MPBracketInfo left, string right);

MatchOpen() is a method that will return the length of the opening bracket if the start of expression matches. For example, if a round opening bracket is found at the start of expression, then the method will return 1, else it will return -1. MatchSeparator() and MatchClose() will analogously check for a valid separator and closing bracket.
The method MatchSeparator() is called when a set of brackets is believed to be found. The structure MPBracketInfo contains the opening bracket and a list of all delimiters to check whether or not this set of brackets is valid. This is to check for errors with nested brackets (eg. for "1+(2*3+1)*1").

The method Execute() is called at runtime and is by default doing nothing (like for normal round brackets), but it will still create an action on the action queue. If you would want to avoid this behavior you could also override the following method of MPBrackets:

/// <summary>
/// No action should be taken so just return null for the action
/// </summary>
/// <param name="output"></param>
/// <param name="info"></param>
/// <returns></returns>
public override CompiledAction Action(Stack<object> output, MPBracketInfo info)
{
    return null;
}

MPFunction

Functions are relatively easy to implement compared to data types and operators. They are derived from an abstract class MPFunction which in turn inherits from the abstract class MPBrackets. This class provides some default implementations of MatchOpen(), MatchClose(), MatchSeparator(), BracketsMatch() and Action(). The derived class should still override the following method:

/// <summary>
/// Executes the function. The function should pop the arguments from the output stack,
/// and push the result of the operation back.
/// </summary>
/// <param name="output">The output stack</param>
/// <param name="arguments">The number of arguments passed with the function</param>
public abstract void Execute(Stack<object> output, int arguments);

The function name is given with the constructor, and can be part of a regular expression. E.g. specifying [Aa]bs will match both Abs(...) and abs(...).
The parameter arguments contains the number of arguments that should be popped off the stack. If the function returns a value, that value should finally be pushed back on the output stack.
If the method does not pop the right amount of arguments off the stack, the user risks having a "Stack empty" exception or ending up with more than one objects on the stack after evaluation (Cannot resolve determined result - exception).

The default Log() function for example is implemented as follows:

using System;
using System.Collections.Generic;

namespace MultiParse.Default
{
    public class MPLog : MPFunction
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public MPLog()
            : base("[lL]og", false)
        {
        }

        /// <summary>
        /// Execute logarithm
        /// </summary>
        /// <param name="output"></param>
        /// <param name="arguments"></param>
        public override void Execute(Stack<object> output, int arguments)
        {
            // This function needs two arguments
            object val, b;
            switch (arguments)
            {
                case 1:
                    val = PopOrGet(output);
                    Log(output, val);
                    break;
                case 2:
                    b = PopOrGet(output);
                    val = PopOrGet(output);
                    Log(output, val, b);
                    break;
                default:
                    throw new InvalidArgumentCountException(1, 2, "Log()");
            }
        }

        /// <summary>
        /// Logarithm
        /// </summary>
        /// <param name="output"></param>
        /// <param name="arg"></param>
        public void Log(Stack<object> output, object arg)
        {
            double d;
            if (CastImplicit(arg, out d))
                output.Push(Math.Log(d));
            else
                throw new InvalidArgumentTypeException("Log()", arg);
        }

        /// <summary>
        /// Logarithm
        /// </summary>
        /// <param name="output"></param>
        /// <param name="left"></param>
        /// <param name="right"></param>
        public void Log(Stack<object> output, object left, object right)
        {
            double d, b;
            if (CastImplicit(left, out d) && CastImplicit(right, out b))
                output.Push(Math.Log(d, b));
            else
                throw new InvalidArgumentTypeException("Log()", left, right);
        }
    }
}

This function accepts either one or two arguments. It is recognized by either the keyword log and Log followed by an opening bracket.

Example - Complex number

As an example, we will implement the Abs function. We can again implement the function by deriving it from MPAbs().

public class ComplexAbs : MultiParse.Default.MPAbs
{
    /// <summary>
    /// Execute absolute value
    /// </summary>
    /// <param name="output"></param>
    /// <param name="arguments"></param>
    public override void Execute(Stack<object> output, int arguments)
    {
        // Check the argument count
        if (arguments != 1)
            throw new InvalidArgumentCountException(1, "Abs()");

        // Pop the argument
        object top = PopOrGet(output);
        if (top is Complex)
            Abs(output, (Complex)top);
        else
            Abs(output, top);
    }

    /// <summary>
    /// Absolute value
    /// </summary>
    /// <param name="output"></param>
    /// <param name="c"></param>
    public void Abs(Stack<object> output, Complex c)
    {
        output.Push(Math.Sqrt(c.Real * c.Real + c.Imag * c.Imag));
    }
}

Finally we add the function to the expression parser, and voila:

using System;
using MultiParse;
using MultiParse.Default;
using Tutorials.Complex;

namespace Tutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression e = new Expression(MPDefault.DataTypes.Double);
            e.DataTypes.Add(new ComplexDataType());
            e.Operators.Add(new ComplexAddition());
            e.Operators.Add(new ComplexSubtraction());
            e.Operators.Add(new ComplexMultiplication());
            e.Operators.Add(new ComplexDivision());
            e.Operators.Add(new ComplexPower());
            e.Functions.Add(new ComplexAbs());

            object result = e.Evaluate("abs(1+i)"); // Returns 1.1412135623731 (=Sqrt(2))
            Console.WriteLine(result + " " + result.GetType().ToString());
            Console.ReadKey();
        }
    }
}

Example - Putting it all together

We now have a pretty complete parser for evaluating complex numbers. The following code demonstrates how it can all be put together, making it almost trivial!

using System;
using MultiParse;
using MultiParse.Default;
using Tutorials.Complex;

namespace Tutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression e = new Expression(MPDefault.DataTypes.Double | MPDefault.DataTypes.Variable);
            e.Operators.Add(new ComplexAddition());
            e.Operators.Add(new ComplexSubtraction());
            e.Operators.Add(new ComplexMultiplication());
            e.Operators.Add(new ComplexDivision());
            e.Operators.Add(new ComplexPower());
            e.Functions.Add(new ComplexAbs());
            e.DataTypes.Insert(0, new ComplexDataType()); // We want to test for an imaginary number before any variables!
            MPDefault.RegisterOperators(e.Operators, MPDefault.Operators.Assignment);

            // Try to evaluate
            e.Evaluate("c=3+6/8*i");
            object result = e.Evaluate("abs(c)");
            Console.WriteLine(result.ToString() + " of type " + result.GetType());
            Console.ReadKey();
        }
    }
}

This concludes the tutorial. You can always reference the Source Code as a reference for extra information about the helper functions and the inner workings.

Last edited Dec 4, 2014 at 12:10 PM by SBoulang, version 9