2

IronPython (2.7.3) は、論理 AND および OR 演算の短絡評価を実行するために、TryUnaryOperation を ExpressionType.IsFalse および ExpressionType.IsTrue でチェックしないようです。

DynamicObject から継承するクラスを使用する例を次に示します。C# では完全に機能しますが、IronPython 式で使用すると間違った結果が生成されます。その動作は予期されたものですか、それともバグですか? IronPython を C# と同じように動作させるにはどうすればよいですか?

クラス:

public class Dyn : DynamicObject
{
    private readonly string text;

    public Dyn(string text)
    {
        this.text = text;
    }

    public override string ToString()
    {
        return this.text;
    }

    public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
    {
        result = new Dyn(this + " " + binder.Operation + " " + arg);
        return true;
    }

    public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result)
    {
        switch (binder.Operation)
        {
            case ExpressionType.IsFalse:
            case ExpressionType.IsTrue:
                result = false;
                return true;
        }

        return base.TryUnaryOperation(binder, out result);
    }
}

使用法:

dynamic a = new Dyn("a");
dynamic b = new Dyn("b");
dynamic c = new Dyn("c");

var correct = a && b || c;

var engine = Python.CreateEngine();
var scope = engine.CreateScope();
scope.SetVariable("a", a);
scope.SetVariable("b", b);
scope.SetVariable("c", c);
var incorrect = engine.Execute("a and b or c", scope);

Console.WriteLine("Correct: " + correct);
Console.WriteLine("Incorrect: " + incorrect);

版画:

Correct: a And b Or c
Incorrect: b
4

2 に答える 2

1

演算子のオーバーロードを使用して構文ツリーを取得することは、最善の方法ではないと思います。おそらく、構文ツリーをトラバースして、そこから必要な情報を抽出する方がよいでしょう。残念ながら、C# ラムダ式の AST は IronPython AST と互換性がありません。そこで、IronPython AST を Linq AST に変換する変換手順をセットアップしました。

    static void Main(string[] args)
    {
        var a = true;
        var b = true;
        var c = true;
        Expression<Func<bool>> csAst = () => a && b || c;
        var csexpr = csAst.Body;
        Console.WriteLine(csexpr.ToString());

        ScriptEngine engine = Python.CreateEngine();
        ScriptScope scope = engine.CreateScope();
        scope.SetVariable("a", a);
        scope.SetVariable("b", b);
        scope.SetVariable("c", c);
        string code = "a and b or c";
        var pyexpr = GetLinqExpressionFromPyExpression(code, scope);
        Console.WriteLine(pyexpr.ToString());
    }

出力は次のとおりです。

((value(Parse.Program+<>c__DisplayClass0).a AndAlso value(Parse.Program+<>c__DisplayClass0).b) OrElse value(Parse.Program+<>c__DisplayClass0).c)
((a AndAlso b) OrElse c)

そして、ここに(不完全な)変換手順があります:

    static System.Linq.Expressions.Expression GetLinqExpressionFromPyExpression(string pyExpression, ScriptScope scope)
    {
        ScriptEngine engine = scope.Engine;
        ScriptSource source =
            engine.CreateScriptSourceFromString(pyExpression, SourceCodeKind.Expression);
        SourceUnit sourceUnit = HostingHelpers.GetSourceUnit(source);
        LanguageContext context = HostingHelpers.GetLanguageContext(engine);
        Parser parser = Parser.CreateParser(
            new CompilerContext(sourceUnit, context.GetCompilerOptions(), ThrowingErrorSink.Default),
            (PythonOptions)context.Options);
        PythonAst ast = parser.ParseFile(true);
        SuiteStatement suite = (SuiteStatement)ast.Body;
        ExpressionStatement statement = (ExpressionStatement)suite.Statements[0];
        IronPython.Compiler.Ast.Expression expression = statement.Expression;

        return Convert(expression, scope);
    }

    static readonly Dictionary<PythonOperator, ExpressionType> linqOpFromPyOp = new Dictionary<PythonOperator, ExpressionType>{
        { PythonOperator.Not, System.Linq.Expressions.ExpressionType.Not },
        { PythonOperator.Pos, System.Linq.Expressions.ExpressionType.UnaryPlus },
        { PythonOperator.Invert, System.Linq.Expressions.ExpressionType.OnesComplement },
        { PythonOperator.Negate, System.Linq.Expressions.ExpressionType.NegateChecked },
        { PythonOperator.Add, System.Linq.Expressions.ExpressionType.AddChecked },
        { PythonOperator.Subtract, System.Linq.Expressions.ExpressionType.SubtractChecked },
        { PythonOperator.Multiply, System.Linq.Expressions.ExpressionType.MultiplyChecked },
        { PythonOperator.Divide, System.Linq.Expressions.ExpressionType.Divide },
        { PythonOperator.TrueDivide, System.Linq.Expressions.ExpressionType.Divide },
        { PythonOperator.Mod, System.Linq.Expressions.ExpressionType.Modulo },
        { PythonOperator.BitwiseAnd, System.Linq.Expressions.ExpressionType.And },
        { PythonOperator.BitwiseOr, System.Linq.Expressions.ExpressionType.Or },
        { PythonOperator.ExclusiveOr, System.Linq.Expressions.ExpressionType.ExclusiveOr },
        { PythonOperator.LeftShift, System.Linq.Expressions.ExpressionType.LeftShift },
        { PythonOperator.RightShift, System.Linq.Expressions.ExpressionType.RightShift },
        { PythonOperator.Power, System.Linq.Expressions.ExpressionType.Power },
        //{ PythonOperator.FloorDivide, System.Linq.Expressions.ExpressionType.Divide }, // TODO
        { PythonOperator.LessThan, System.Linq.Expressions.ExpressionType.LessThan },
        { PythonOperator.LessThanOrEqual, System.Linq.Expressions.ExpressionType.LessThanOrEqual },
        { PythonOperator.GreaterThan, System.Linq.Expressions.ExpressionType.GreaterThan },
        { PythonOperator.GreaterThanOrEqual, System.Linq.Expressions.ExpressionType.GreaterThanOrEqual },
        { PythonOperator.Equal, System.Linq.Expressions.ExpressionType.Equal },
        { PythonOperator.NotEqual, System.Linq.Expressions.ExpressionType.NotEqual },
        //{ PythonOperator.In, System.Linq.Expressions.ExpressionType. }, // TODO
        //{ PythonOperator.NotIn, System.Linq.Expressions.ExpressionType. }, // TODO
        //{ PythonOperator.IsNot, System.Linq.Expressions.ExpressionType.TypeIs }, // TODO
        { PythonOperator.Is, System.Linq.Expressions.ExpressionType.TypeIs },
    };

    static System.Linq.Expressions.Expression Convert(IronPython.Compiler.Ast.Expression node, ScriptScope scope)
    {
        switch (node.NodeName)
        {
            case "AndExpression":
                {
                    var _node = (IronPython.Compiler.Ast.AndExpression)node;
                    return System.Linq.Expressions.BinaryExpression.AndAlso(
                        Convert(_node.Left, scope), 
                        Convert(_node.Right, scope));
                }
            case "BinaryExpression":
                {
                    var _node = (IronPython.Compiler.Ast.BinaryExpression)node;
                    // TODO: do conversion if left and right have different types
                    return System.Linq.Expressions.BinaryExpression.MakeBinary(
                        linqOpFromPyOp[_node.Operator],
                        Convert(_node.Left, scope),
                        Convert(_node.Right, scope));
                }
            case "OrExpression":
                {
                    var _node = (IronPython.Compiler.Ast.OrExpression)node;
                    return System.Linq.Expressions.BinaryExpression.OrElse(
                        Convert(_node.Left, scope),
                        Convert(_node.Right, scope));
                }
            case "NameExpression":
                {
                    var _node = (IronPython.Compiler.Ast.NameExpression)node;
                    return System.Linq.Expressions.Expression.Parameter(
                        scope.GetVariable(_node.Name).GetType(), 
                        _node.Name);
                }
            // TODO: Add further Python Expression types
            default:
                throw new ArgumentTypeException("unhandled NodeType '" + node.NodeName + "'");
        }
    }

    internal class ThrowingErrorSink : ErrorSink
    {
        public static new readonly ThrowingErrorSink/*!*/ Default = new ThrowingErrorSink();

        private ThrowingErrorSink() { }

        public override void Add(SourceUnit sourceUnit, string message, SourceSpan span, int errorCode, Severity severity)
        {
            if (severity == Severity.Warning)
            {
                PythonOps.SyntaxWarning(message, sourceUnit, span, errorCode);
            }
            else
            {
                throw PythonOps.SyntaxError(message, sourceUnit, span, errorCode);
            }
        }
    }
于 2013-01-11T01:48:20.500 に答える