IronPython DSL casting constants to C# types

2019-08-31 16:49发布

问题:

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.

回答1:

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.