парсинг вложенных функций в python - PullRequest
0 голосов
/ 31 января 2020
line = "add(multiply(add(2,3),add(4,5)),1)"

def readLine(line):
    countLeftBracket=0
    string = ""
    for char in line:
        if char !=")":
            string += char
        else:
            string +=char
            break

    for i in string:
        if i=="(":
            countLeftBracket+=1

    if countLeftBracket>1:
        cutString(string)
    else:
        return execute(string)

def cutString(string):
    countLeftBracket=0

    for char in string:
        if char!="(":
            string.replace(char,'')
        elif char=="(":
            string.replace(char,'')
            break

    for char in string:
        if char=="(":
            countLeftBracket+=1

    if countLeftBracket>1:
        cutString(string)
    elif countLeftBracket==1:
        return execute(string)

def add(num1,num2):
    return print(num1+num2)

def multiply(num1,num2):
    return print(num1*num2)

readLines(line)

Мне нужно выполнить всю строку строки. Я пытался вырезать каждую функцию внутри скобок одну за другой и заменять их результатом, но я как-то растерялся. Не знаю, как продолжить, мой код получает ошибку:

  File "main.py", line 26, in cutString                                                                                 
    if char!="(":                                                                                                       
RuntimeError: maximum recursion depth exceeded in comparison 

Дайте мне представление о том, куда двигаться, какой метод использовать?

Ответы [ 3 ]

3 голосов
/ 31 января 2020

Вот решение, которое использует pyparsing, и поэтому его будет гораздо проще развернуть:

from pyparsing import *

первая удобная функция (используйте вторую функцию тега и напечатайте дерево разбора, чтобы понять почему)

def tag(name):
    """This version converts ["expr", 4] => 4
       comment in the version below to see the original parse tree
    """
    def tagfn(tokens):
        tklist = tokens.asList()
        if name == 'expr' and len(tklist) == 1:
            # LL1 artifact removal
            return tklist
        return tuple([name] + tklist)
    return tagfn

# def tag(name):
#     return lambda tokens: tuple([name] + tokens.asList())

Наш лексер не должен распознавать левую и правую круглые скобки, целые числа и имена. Вот как вы определяете их с помощью pyparsing:

LPAR = Suppress("(")
RPAR = Suppress(")")
integer = Word(nums).setParseAction(lambda s,l,t: [int(t[0])])
name = Word(alphas)

наш парсер имеет вызовы функций, которые принимают ноль или более выражений в качестве параметров. Вызов функции также является выражением, поэтому, чтобы справиться с цикличностью, мы должны переслать декларацию expr и fncall:

expr = Forward()
fncall = Forward()

expr << (integer | fncall).setParseAction(tag('expr'))
fnparams = delimitedList(expr)

fncall << (name + Group(LPAR + Optional(fnparams, default=[]) + RPAR)).setParseAction(tag('fncall'))

Теперь мы можем проанализировать нашу строку (мы можем добавить пробелы и больше или меньше двух параметров для функций):

line = "add(multiply(add(2,3),add(4,5)),1)"
res = fncall.parseString(line)

чтобы увидеть, что возвращается, вы можете напечатать его, это называется синтаксическим деревом (или, поскольку наша функция тега упростила его, абстрактным синтаксическим деревом):

import pprint
pprint.pprint(list(res))

, который выводит:

[('fncall',
  'add',
  [('fncall',
    'multiply',
    [('fncall', 'add', [2, 3]), ('fncall', 'add', [4, 5])]),
   1])]

с функцией закомментированного тега (это просто дополнительная работа, без дополнительной выгоды):

[('fncall',
  'add',
  [('expr',
    ('fncall',
     'multiply',
     [('expr', ('fncall', 'add', [('expr', 2), ('expr', 3)])),
      ('expr', ('fncall', 'add', [('expr', 4), ('expr', 5)]))])),
   ('expr', 1)])]

Теперь определите функции, которые доступны для нашей программы:

FUNCTIONS = {
    'add': lambda *args: sum(args, 0),
    'multiply': lambda *args: reduce(lambda a, b: a*b, args, 1),
}

# print FUNCTIONS['multiply'](1,2,3,4)   # test that it works ;-)

Теперь наш парсер очень просто написать:

def parse(ast):
    if not ast:  # will not happen in our program, but it's good practice to exit early on no input
        return

    if isinstance(ast, tuple) and ast[0] == 'fncall':
        # ast is here ('fncall', <name-of-function>, [list-of-arguments])
        fn_name = ast[1]          # get the function name
        fn_args = parse(ast[2])   # parse each parameter (see elif below)
        return FUNCTIONS[fn_name](*fn_args)  # find and apply the function to its arguments
    elif isinstance(ast, list):
        # this is called when we hit a parameter list
        return [parse(item) for item in ast]
    elif isinstance(ast, int):
        return ast

Теперь вызовите парсер для результата лексическая фаза:

>>> print parse(res[0])  # the outermost item is an expression
46
2 голосов
/ 31 января 2020

Вы можете использовать функцию генератора для создания очень простого парсера:

import re, operator
line, f = "add(multiply(add(2,3),add(4,5)),1)", {'add':operator.add, 'multiply':operator.mul}
def parse(d):
   n = next(d, None)
   if n is not None and n != ')':
     if n == '(':
       yield iter(parse(d))
     else:
       yield n
     yield from parse(d)

parsed = parse(iter(re.findall('\(|\)|\w+', line)))
def _eval(d):
   _r = []
   n = next(d, None)
   while n is not None:
      if n.isdigit():
        _r.append(int(n))
      else:
        _r.append(f[n](*_eval(next(d))))
      n = next(d, None)
   return _r

print(_eval(parsed)[0])

Вывод:

46
1 голос
/ 31 января 2020

Похоже, что это можно решить с помощью регулярных выражений.

Так что это пример одного сокращения

import re, operator
def apply(match):
   func_name = match.group(1) # what's outside the patentesis
   func_args = [int(x) for x in match.group(2).split(',')]
   func = {"add": operator.add, "multiply": operator.mul}
   return str(func[func_name](*func_args))
def single_step(line):
   return re.sub(r"([a-z]+)\(([^()]+)\)",apply,line)

Например:

line = "add(multiply(add(2,3),add(4,5)),1)"
print(single_step(line))

Будет выводить :

add (multiply (5,9), 1)

Все, что осталось сделать, это l oop, пока выражение не станет числом

while not line.isdigit():
   line = single_step(line)
print (line)

Покажет

46

...