Search code examples
pythonsoliditygrammarpyparsing

Infinite recursion in pyparsing grammar for method signatures


Below is my pyparsing grammar for parsing the method signature of a Solidity function, and an example signature to parse:

from pyparsing import Word, alphas, alphanums, oneOf, Group, Forward, ZeroOrMore, Optional, delimitedList

solidity_type = Forward()
primitive = oneOf("address uint uint8 uint16 uint32 uint64 uint128 uint256 int int8 int16 int32 int64 int128 int256 bool bytes bytes1 bytes4 bytes32")
array = solidity_type + "[]"
tuple_type = "(" + delimitedList(solidity_type) + ")"
solidity_type <<= (primitive | array | tuple_type)
function_name = Word(alphas, alphanums + "_")
arguments = Optional(delimitedList(solidity_type))
function_signature = function_name + "(" + arguments + ")"

sig = "diamondCut((address,uint8,bytes4[])[],address,bytes)"
parsed_sig = function_signature.parseString(sig)
print(parsed_sig.dump())

The error I get is {RecursionError}maximum recursion depth exceeded. Narrowing it down, I realize that this statement produces the same error:

array.parseString("(bytes4)[]")

I tried surrounding some of the expressions with Group, and re-ordering the options in the solidity_type definition. I also tried defining array as its own Forward.

I don't understand how to change my code so that the example signature parses without error.


Solution

  • Your grammar is left recursive.

    • solidity_type goes to array in the alternation
    • array goes directly to solidity_type

    During the recursion, no tokens are consumed. That is why you get the recursion error.

    Fortunately, pyparsing can handle that, but you have to enable that feature:

    from pyparsing import ParserElement
    
    ParserElement.enable_left_recursion()
    # rest of your grammar here
    

    You should also rewrite the solidity_type by swapping array and primitive. array should come first, because it is more specific.