Добавить разбор функции в простой разбор с нечисловыми аргументами

1

Я пытаюсь добавить функции к выражениям, с возможностью принимать нечисловые аргументы. После добавления функции parsing к простой грамматике арифметики pyparsing и https://pyparsing.wikispaces.com/file/view/fourFn.py, я мог бы управлять функциями с числовыми вводами. Однако не удалось обновить их для нечисловых входов. Вот мой тестовый код, пытаясь передать идентификатор также вместе с expr для ввода функции:

# fourFn.py
#
# Demonstration of the pyparsing module, implementing a simple 4-function expression parser,
# with support for scientific notation, and symbols for e and pi.
# Extended to add exponentiation and simple built-in functions.
# Extended test cases, simplified pushFirst method.
#
# Copyright 2003-2006 by Paul McGuire
#
from pyparsing import Literal,CaselessLiteral,Word,Combine,Group,Optional,\
    ZeroOrMore,Forward,nums,alphas,delimitedList
import math
import operator
import pprint 

exprStack = []

def pushFirst( strg, loc, toks ):
    exprStack.append( toks[0] )
def pushUMinus( strg, loc, toks ):
    if toks and toks[0]=='-': 
        exprStack.append( 'unary -' )
        #~ exprStack.append( '-1' )
        #~ exprStack.append( '*' )

bnf = None
def BNF():
    """
    expop   :: '^'
    multop  :: '*' | '/'
    addop   :: '+' | '-'
    integer :: ['+' | '-'] '0'..'9'+
    atom    :: PI | E | real | fn '(' expr ')' | '(' expr ')'
    factor  :: atom [ expop factor ]*
    term    :: factor [ multop factor ]*
    expr    :: term [ addop term ]*
    """
    global bnf
    if not bnf:
        point = Literal( "." )
        #e     = CaselessLiteral( "E" )
        fnumber = Combine( Word( "+-"+nums, nums ) + 
                           Optional( point + Optional( Word( nums ) ) ) 
                            )
        ident = Word(alphas, alphas+nums)

        plus  = Literal( "+" )
        minus = Literal( "-" )
        mult  = Literal( "*" )
        div   = Literal( "/" )
        lpar  = Literal( "(" ).suppress()
        rpar  = Literal( ")" ).suppress()
        addop  = plus | minus
        multop = mult | div
        expop = Literal( "^" )
        #pi    = CaselessLiteral( "PI" )
        expr = Forward()
        #function_calla = Group(ident + lpar + ident + rpar)
        function_call = Group(ident + lpar + Group(Optional(delimitedList(ident|expr))) + rpar)
        atom = (Optional("-") + ( fnumber | ident + lpar + expr + rpar |function_call).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus) 
        # by defining exponentiation as "atom [ ^ factor ]..." instead of "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-righ
        # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
        factor = Forward()
        factor << atom + ZeroOrMore( ( expop + factor ).setParseAction( pushFirst ) )

        term = factor + ZeroOrMore( ( multop + factor ).setParseAction( pushFirst ) )
        expr << term + ZeroOrMore( ( addop + term ).setParseAction( pushFirst ) ) 
        bnf = expr
    return bnf
def asw(x):
    print "X is ",x
    return 1
# map operator symbols to corresponding arithmetic operations
epsilon = 1e-12
opn = { "+" : operator.add,
        "-" : operator.sub,
        "*" : operator.mul,
        "/" : operator.truediv,
        "^" : operator.pow }
fn  = { "sin" : math.sin,
        "cos" : math.cos,
        "tan" : math.tan,
        "abs" : abs,
        "trunc" : lambda a: int(a),
        "round" : round,
        "asw" : asw,
        "sgn" : lambda a: abs(a)>epsilon and cmp(a,0) or 0}
def evaluateStack( s ):
    print "s=",s
    op = s.pop()
    print "op=",op,type(op)
    if op == 'unary -':
        return -evaluateStack( s )
    #if type(op)!=str:
    #    return evaluateStack( s )

    if op in "+-*/^":
        op2 = evaluateStack( s )
        op1 = evaluateStack( s )
        return opn[op]( op1, op2 )
    elif op == "PI":
        return math.pi # 3.1415926535
    elif op == "E":
        return math.e  # 2.718281828
    elif op in fn:
        return fn[op]( evaluateStack( s ) )
    elif op[0].isalpha():
        return 0
    else:
        return float( op )
if __name__ == "__main__":      
    exprStack = []  
    res= BNF().parseString( "asw(aa)").asList()
    print "res=",res
    val = evaluateStack( exprStack[:] )
    print val

Результат для числового ввода:

C:\temp>python test.py
res= ['asw', '11']
s= ['11', 'asw']
op= asw <type 'str'>
s= ['11']
op= 11 <type 'str'>
X is  11.0
1

Если нечисловой ввод приведет к:

C:\temp>python test.py
res= [['asw', ['aa']]]
s= [(['asw', (['aa'], {})], {})]
op= ['asw', ['aa']] <class 'pyparsing.ParseResults'>
Traceback (most recent call last):
  File "test.py", line 117, in <module>
    val = evaluateStack( exprStack[:] )
  File "test.py", line 99, in evaluateStack
    if op in "+-*/^":
TypeError: 'in <string>' requires string as left operand, not ParseResults

Где я иду не так? Очень новичок в пипаринге и все еще пытается понять это.

  • 0
    Пока вы распечатываете результат parseString , это не то, что вы на самом деле интерпретируете; вы интерпретируете глобальную переменную exprStack которую вы exprStack вещи. Поэтому, если вы хотите узнать, чем отличаются эти два случая, вам нужно распечатать exprStack .
  • 0
    Кроме того, почему в вашем atom ident + lpar + expr + rpar function_call ?
Показать ещё 2 комментария
Теги:
pyparsing

1 ответ

0
Лучший ответ

Я почти уверен, что ваш виновник заключается в добавлении function_call в atom в конце и в создании function_call a Group.

function_call = (ident + lpar + Group(Optional(delimitedList(expr))) + rpar)
atom = (Optional("-") + ( fnumber | function_call | ident).setParseAction( pushFirst ) | ( lpar + expr.suppress() + rpar )).setParseAction(pushUMinus) 

Помните, что '|' генерирует выражения MatchFirst, которые будут соответствовать первому данному выражению, которое соответствует. Если сначала определить atom для совпадения с ident, то вы никогда не будете соответствовать function_call, так как ваша функция начинается с идентификатора. Сначала вы должны проверить, является ли идентификатор, который вы обрабатываете, началом вызова функции перед тем, как определить, что он на самом деле является одиноким идентификатором.

Кроме того, function_call не должна быть Группой. Способ работы этого парсера состоит в том, чтобы проанализировать и вставить все аргументы в стек, а затем, наконец, вставить имя функции в стек. Но это работает только в том случае, если function_call не является Группой - в противном случае вы выталкиваете всю анализируемую функцию ParseResults в стек.

Наконец, обратите внимание, что я уменьшил аргументы function_call чтобы просто быть expr s, а не ident | expr ident | expr. Одинокий ident является expr, поэтому чередование не требуется - и на самом деле это снова запутывает вещи, по той же причине, что перечисление ident перед function_call в atom является проблемой.

  • 0
    Спасибо - это решило, и объяснение отличное.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню