Вот решение, которое использует 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