0
votes

Are there any helper methods in ironpython to analyze python code?

Given for example this script

def bar(b):
   return foo(b)

def foo(f):
   return filters.delta(f) #This is a reference added to the scope from C#

x = 1
bar(x)

I need to know how filters.delta is called in the script like bar(x) > foo(b) > filters.delta(f)

Can this be done from IronPython or do I need to parse the script myself? Its not very easy done

Background info

The software this is for is called FreePIE. Its a scriptable input output emulator. It has a set of util functions. Consider this code.

mouse.deltaX = filters.delta(joystick[0].x)

It takes the absolute x coordinate of a joystick or gamepad, gets the delta value using filers.delta and sets the relative x coordinate of the mouse

To get the delta value my software must compare two samples between script execution, the util functions takes care of this.

[NeedIndexer]
public double delta(double x, string indexer)
{
    var lastSample = x;
    if (deltaLastSamples.ContainsKey(indexer))
        lastSample = deltaLastSamples[indexer];

    deltaLastSamples[indexer] = x;

    return x - lastSample;
}

But to solve that it needs to store the last sample. AS you can see it takes a string that can index the collection holding the last sample. I dont want to require the the user to handle this index for simplicity. So what I do is preparsing the script and look for all methods that has the [NeedIndexer] attribute. If I find one I parse the method argument and use that as indexer. so a python script looking like above will look like this after parsing

mouse.deltaX = filters.delta(joystick[0].x, "joystick[0].x")

In this example the code will work because joystick[0].x is unique, but if the user defines a python function and uses the filters.delta from there he will index on the same variable name for all samples.

The current code for parsing the script https://github.com/AndersMalmgren/FreePIE/blob/master/FreePIE.Core/ScriptEngine/Python/PythonScriptParser.cs#L135

I hope I make this more clear what I am trying to achieve

Update I gotten a little further thanks to Jeff's answer

var engine = IronPython.Hosting.Python.CreateEngine();
var script = @"def foo(y):
   return filters.delta(y)

def bar(x):
   return foo(x)

bar(-5)
";

var ast = @"import ast
tree = ast.parse(script)
callback(ast.dump(tree))";


var callback = new Action<object>(o =>
{
    Console.WriteLine(o);
});


engine.SetSearchPaths(new[] { "pylib" });
var scope = engine.CreateScope();
scope.SetVariable("script", script);
scope.SetVariable("callback", callback);

engine.Execute(ast, scope);

I have been able to dump the entire tree to a string using ast.dump but I havent found out how I can recursively iterate over the tree. I need to do this until I find a filters.delta call, then traverse backup again and add a argument to each function in the chain. There isnt much info on the net about the ast module. Anyone here knows how this can be achived? There is a _fields member on the object that is returned by the ast.parse but I cant find out of to go from there

1
Curious, how does this question relate to c# tag?Jsinh
Well, I guess you can use IronPython from any .NET language. But I use C#Anders
Added some background info that might clear thins upAnders

1 Answers

2
votes

First off, I think you should rethink how you're doing this. Any solution is going to be fragile and magical and probably cause more problems down the road. Something like mouse.deltaX = joystick[0].x_filters.delta() is going to be more clear. You can even create x_filters on the fly (it doesn't have to be part of the joystick type) by wrapping the joystick with a DynamicObject wrapper that creates x_filters as needed.

If you do want to modify the script, you probably want to use the ast module to parse the code. You'll still have to walk the ast looking for that function. Whether you deal with the function being bound to a different name, etc. is up to you. The ast module should give you the textual location of the symbol as well, so you can insert the special code after it.

You could also do it at runtime using sys.settrace, which should work under IronPython if you enable the "Tracing" option when you create the engine. This may fire too late for your purposes though.