mna
mna

Reputation: 263

Parameters of parsy parser

Consider the following code, which parses and evaluates strings like 567 +223 in Python.

import parsy as pr                                                                        
from parsy import generate                                                                
                                                                                          
def lex(p):                                                                               
    return p << pr.regex('\s*')                                                           
                                                                                          
numberP = lex(pr.regex('[0-9]+').map(int))                                                
                                                                                          
@generate                                                                                 
def sumP():                                                                               
    a = yield numberP                                                                     
    yield lex(pr.string('+'))                                                             
    b = yield numberP                                                                     
    return a+b                                                                            
                                                                                          
exp = sumP.parse('567 + 323')                                                             
print(exp)

The @generate is a total mystery for me. Does anyone have more information on how that trick works? It does allow us to write in a similar style to Haskell's monadic do notation. Is code reflection needed to make your own @generate, or is there a clever way to interpret that code literally.

Now here comes my main problem, I want to generalize sumP to opP that also takes an operator symbol and a combinator function:

import parsy as pr                                                                        
from parsy import generate                                                                
                                                                                          
def lex(p):                                                                               
    return p << pr.regex('\s*')                                                           
                                                                                          
numberP = lex(pr.regex('[0-9]+').map(int))                                                
                                                                                          
@generate                                                                                 
def opP(symbol, f):                                                                       
    a = yield numberP                                                                     
    yield lex(pr.string(symbol))                                                          
    b = yield numberP                                                                     
    return f(a,b)                                                                            
                                                                                          
exp = opP('+', lambda x,y:x+y).parse('567 + 323')                                         
print(exp)

This gives an error. It seems that the generated opP already has two arguments, which I do not know how to deal with.

Upvotes: 1

Views: 151

Answers (1)

sepp2k
sepp2k

Reputation: 370102

The way that decorators work in Python is that they're functions that are called with the decorated method as an argument and then their return value is assigned to the method name. In other words this:

@foo
def bar():
    bla

Is equivalent to this:

def bar():
    bla
bar = foo(bar)

Here foo can do anything it wants with bar. It may wrap it in something, it may introspect its code, it may call it.

What @generate does is to wrap the given function in a parser object. The parser object, when parsing, will call the function without arguments, which is why you get an error about missing arguments when you apply @generate to a function that takes arguments.

To create parameterized rules, you can apply @generate to an inner 0-argument function and return that:

def opP(symbol, f):
    @generate
    def op():
        a = yield numberP                                                                  
        yield lex(pr.string(symbol))
        b = yield numberP
        return f(a,b)

    return op

Upvotes: 1

Related Questions