Search code examples
ironpythondslfluent-interface

IronPython DSL casting constants to C# types


I am implementing a DSL in IronPython.

Suppose I have a value hierachy implemented in C# to be used in iron python:

 public abstract Value
 {

 }

 public abstract DoubleValue : Value
 {
   // Constructors...      

   public double magnitude;

   // Arithmetic operators overloaded...
 }

 public abstract FractionValue : Value
 {
   // Constructors....

   public int numerator;
   public int denominator;

   // Arithmetic operators overloaded...
 }

Due to the operator overloading in C#, i can do this in Python:

 # a, b are of type Value
 def Sum(a,b):
    return a + b

And everything works fine, the function returns an object of type = Value.

But if i want to use a PythonConstant:

 # a is of type Value
 def Sum5(a):
     return a + 5

you get an error type, because the constant 5 is not of Value type.

One solution would be to overload the + operator to work ints like:

public DoubleValue operator+(DoubleValue, int)

but then you get a huge amount of possible combinations and end up with hundreds of overloads in the Value framework. Anyway you still get this problem:

def ReturnFive():
    return 5

In this case the returned value is not of Value type, you should do something like:

def ReturnFive():
    return DoubleValue(5.0)

But it is a pretty ugly syntax for my DSL.

What would you recommend?

Thank you very much.


Solution

  • This is the major issues with DSELs: they don't always play well with native types. Generally you need to wrap the native types; one option is to introduce a function with a very short name (such as _) that wraps the passed-in value and triggers the operator overloads.

    IronPython only has three number types of interest -- System.Int32 (int), System.Double (float), and System.Numerics.BigInteger (long) -- so there aren't too many cases to take care of.

    On the C# side you would have something like:

    class Value {
        static Value WrapLiteral(object literal) {
            if(literal is System.Double) {
                return DoubleValue((System.Double)literal);
            } // etc ...
        }
    }
    

    When you create the scope for your user scripts, add that function with the short name:

    scope.SetVariable("_", Value.WrapLiteral);
    

    Then from the user side, the users only have to do:

    def ReturnFive():
        return _(5)
    

    It's still a bit ugly, but not too bad.