Search code examples
pythonregexparsinglua

Search for all function calls in a Lua file using Python


I want to search for all function calls in a Lua file using python. for example, I have this Lua code:

function displayText (text)
    print(text)
end

sendGreetings = function(sender, reciever) 
    print("Hi " ..reciever.. " greetings from " ..sender.. "!")
end

displayText("Hello, World!")

sendGreetings("Roger", "Michael")

I want my python code to search for function calls in that code and returns a dictionary with the function name and parameters, so the output should be like this:

# {function_name: [param1, param2]}
{"displayText": ["Hello, World!"], "sendGreetings": ["Roger", "Michael"]}

I tried to implement it by using regex but I had all sorts of problems, and inaccurate results. Also, I don't believe there is a Lua parser for Python.


Solution

  • You can use luaparser (pip install luaparser) with recursion to traverse the ast:

    import luaparser
    from luaparser import ast
    class FunCalls:
       def __init__(self):
          self.f_defs, self.f_calls = [], []
       def lua_eval(self, tree):
          #attempts to produce a value for a function parameter. If value is a string or an integer, returns the corresponding Python object. If not, returns a string with the lua code
          if isinstance(tree, (luaparser.astnodes.Number, luaparser.astnodes.String)):
             return tree.n if hasattr(tree, 'n') else tree.s
          return ast.to_lua_source(tree)
       def walk(self, tree):
          to_walk = None
          if isinstance(tree, luaparser.astnodes.Function):
             self.f_defs.append((tree.name.id, [i.id for i in tree.args]))
             to_walk = tree.body
          elif isinstance(tree, luaparser.astnodes.Call):
             self.f_calls.append((tree.func.id, [self.lua_eval(i) for i in tree.args]))
          elif isinstance(tree, luaparser.astnodes.Assign):
             if isinstance(tree.values[0], luaparser.astnodes.AnonymousFunction):
                self.f_defs.append((tree.targets[0].id, [i.id for i in tree.values[0].args]))
          if to_walk is not None:
             for i in ([to_walk] if not isinstance(to_walk, list) else to_walk):
                 self.walk(i)
          else:
             for a, b in getattr(tree, '__dict__', {}).items():
                if isinstance(b, list) or 'luaparser.astnodes' in str(b):
                   for i in ([b] if not isinstance(b, list) else b):
                       self.walk(i)
    

    Putting it all together:

    s = """
    function displayText (text)
       print(text)
    end
    
    sendGreetings = function(sender, reciever) 
       print("Hi " ..reciever.. " greetings from " ..sender.. "!")
    end
    
    displayText("Hello, World!")
    
    sendGreetings("Roger", "Michael")
    """
    tree = ast.parse(s)
    f = FunCalls()
    f.walk(tree)
    print(dict(f.f_defs)) #the function definitions with signatures
    calls = {a:b for a, b in f.f_calls if any(j == a for j, _ in f.f_defs)}    
    print(calls)
    

    Output:

    {'displayText': ['text'], 'sendGreetings': ['sender', 'reciever']}
    {'displayText': ['Hello, World!'], 'sendGreetings': ['Roger', 'Michael']}