Friday, August 1, 2008

Post 4: Expression Evaluator - Object Typing

We have seen how the evaluation engine recursively evaluates an expression tree and associates a value with each Expression object. We have also seen how the values associated with the Expression object are examined only by the individual function processors. Let's look at the function processor for the "+" function again

    case "+":  
        { 
            try 
            { 
                return Functor.SetValue(Arguments.Aggregate(0.0d, (result, x) => result += x.Evaluate().ValueAsNumber); 
            } 
            catch 
            { }

            return Functor.SetNil(); 
        }

We notice the generic Aggregate function on Arguments returns a double, and consequently the lambda expression returns a double, using the native += operator to operate on double values. This means that we have to somehow express the value of the Expression x as a double. Further, we need to ensure that the value associated with the Functor is henceforth interpretable as a double.

We also need to handle the case where it cannot be expressed as a number, and we'll deal with that in a moment.

We can also imagine other scalar types - like strings and boolean values - of immense importance when we want to do comparison expressions like:

(> a b)

We therefore create a Variant class (again a throwback to my C/C++ days) where a class can encapsulate a given value and represent it, if possible, in various types. In our case, we are going to support strings, numbers (doubles) and booleans, so our variant class looks something like:

public class VariantValue
{
    public E_VALUETYPE ValueType { get; protected internal set; }

       public object UntypedValue { get; protected internal set; }

    public virtual bool IsNumber
    {
        get
        {
            try
            {
                CastToNumber(this.UntypedValue);
                return true;
            }
            catch
            {
                return false;
            }
        }
    }

    protected static double CastToNumber(object value)
    {
        try
        {
            return System.Convert.ToDouble(value);
        }
        catch
        {
            throw new InvalidCastException("Cannot express (" + value.ToString() + ") as a Number");
        }
    }

    public virtual double ValueAsNumber
    {
        get
        {
            try
            {
                return CastToNumber(this.UntypedValue);
            }
            catch
            {
                return double.NaN;
            }
        }

        set
        {
            this.UntypedValue = value;
            this.ValueType = E_VALUETYPE.Number;
        }
    }

    #region Boolean ...

    #region String ...

    protected internal virtual VariantValue SetValue(bool value) { this.ValueAsBoolean = value; return this; }
    protected internal virtual VariantValue SetValue(double value) { this.ValueAsNumber = value; return this; }
    protected internal virtual VariantValue SetValue(string value) { this.ValueAsString = value; return this; }

    protected internal virtual VariantValue SetNil() ...

    protected internal virtual VariantValue SetValue(VariantValue value) ...

    protected internal virtual VariantValue SetValue(object value) ...
}

We can see how the VariantValue class stores the raw value in the UntypedValue property and tries to represent it as a Number, Boolean or String when required. This is important because a value can have more than one representation.

The Is... accessor properties allow callers to query if the value can be represented in the appropriate type.

The ValueAs... accessor properties return the value as the appropriate type if possible. They will never throw any exceptions. They are helped by the internal CastTo... functions, which do throw exceptions. When we examine the SetValue(object) function, we'll see how the exceptions play a crucial role in helping us decide how best to represent a generic object.

The ValueAs... mutator properties save the raw value in the UntypedValue property. Since the type of the value is known to them, they also store the type information in the ValueType property.

The code below is the SetValue(object) function. It is called when the type-specific version of SetValue cannot be called (for example when the value is obtained from a symbol table - look at the section on integration).

We successively attempt to coerce the value into one of the three known types, using the exceptions thrown from the CastTo... functions to signal that coercion was unsuccessful.

    protected internal virtual VariantValue SetValue(object value)
    {
        if (value == null) { return SetNil(); }

        // [true] can be coerced to a number if we don't do this
        if ((value is long) || (value is double))
        {
            this.ValueAsNumber = CastToNumber(value); return this;
        }

        if (value is bool)
        {
            this.ValueAsBoolean = CastToBoolean(value); return this;
        }

        try
        {
            this.ValueAsNumber = CastToNumber(value);
            return this;
        }
        catch
        { }

        try
        {
            this.ValueAsBoolean = CastToBoolean(value);
            return this;
        }
        catch
        { }

        try
        {
            this.ValueAsString = CastToString(value);
            return this;
        }
        catch
        { }

        this.UntypedValue = value;
        this.ValueType = E_VALUETYPE.Uninitialized;

        return this;
    }

Since every Expression object is associated with a value, we can make life easy and simply derive the Expression class from the VariantValue class. We are effectively saying that an Expression is a VariantValue.

public class Expression : VariantValue 
   ...

We're close to putting the finishing touches on our expression evaluator. The last post in this series will deal with integration with the calling environment, allowing us to use the evaluator within the context of any other application.

No comments: