Avalie a string combinando parênteses [duplicado]
Qual seria a melhor maneira de traduzir programaticamente uma string como
"((abc&(def|ghi))|jkl)&mno"
a ser executado como:
if ((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno'):
return True
Sinto que deve haver uma maneira simples de conseguir isso, mas não consigo entender.
Respostas
Este é um pequeno problema interessante, com várias camadas para uma solução.
Em primeiro lugar, dado este exemplo, você precisa de um analisador de notação infixo básico. Em pyparsing, existe um método auxiliar embutido infixNotation. Vários exemplos de pyparsing mostram como analisar uma expressão booleana usando infixNotation. Aqui está um analisador que analisará sua expressão de amostra:
import pyparsing as pp
term = pp.Word(pp.alphas)
AND = pp.Literal("&")
OR = pp.Literal("|")
expr = pp.infixNotation(term,
[
(AND, 2, pp.opAssoc.LEFT,),
(OR, 2, pp.opAssoc.LEFT,),
])
print(expr.parseString(sample).asList())
Para sua amostra, isso imprimirá:
[[[['abc', '&', ['def', '|', 'ghi']], '|', 'jkl'], '&', 'mno']]
Você pode ver que capturamos não apenas a expressão, mas também o agrupamento por parênteses.
Podemos começar a fazer a conversão para a saída desejada adicionando ações de análise. Esses são retornos de chamada em tempo de análise que o pyparsing chamará, para substituir os tokens analisados por um valor diferente (que não precisa ser uma string, pode ser um nó AST para avaliação - mas, neste caso, retornaremos uma string modificada).
AND.addParseAction(lambda: " and ")
OR.addParseAction(lambda: " or ")
term.addParseAction(lambda t: "func('{}')".format(t[0]))
expr.addParseAction(lambda t: "({})".format(''.join(t[0])))
As ações de análise podem ser métodos com várias assinaturas:
function()
function(tokens)
function(location, tokens)
function(input_string, location, tokens)
Para AND e OR, só precisamos substituir os operadores analisados por suas palavras-chave "e" e "ou" correspondentes. Para os termos da variável analisada, queremos mudar "xxx" para "func (xxx)", então escrevemos uma ação de análise que pega os tokens analisados e retorna a string modificada.
A ação de análise de expré interessante porque tudo o que ela faz é pegar o conteúdo analisado, juntá-lo usando ''.join()e, em seguida, envolvê-lo em ()s. Como expré na verdade uma expressão recursiva, veremos que ela faz o agrupamento apropriado entre () em cada nível da lista aninhada analisada.
Depois de adicionar essas ações de análise, podemos tentar chamar parseString()novamente, agora fornecendo:
["(((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno'))"]
Chegando perto!
Para fazer a formatação na ifinstrução desejada , podemos usar outra ação de análise. Mas não podemos anexar essa ação de análise diretamente a expr, uma vez que vimos que expr(e sua ação de análise associada) será analisada em todos os níveis de aninhamento. Em vez disso, podemos criar uma versão "externa" de expr, que é simplesmente uma expressão de contêiner de uma expr:
outer_expr = pp.Group(expr)
A ação de análise é semelhante ao que vimos expr, onde retornamos uma nova string usando os tokens de entrada:
def format_expression(tokens):
return "if {}:\n return True".format(''.join(tokens[0]))
outer_expr.addParseAction(format_expression)
Agora usamos outer_exprpara analisar a string de entrada:
print(outer_expr.parseString(sample)[0])
Obtendo:
if (((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno')):
return True
(Pode haver um conjunto extra de () neste valor, eles podem ser removidos na ação de análise, outer_exprse desejado.)
Versão finalizada do analisador (descomente as instruções de impressão intermediárias para ver a progressão da funcionalidade do analisador):
sample = "((abc&(def|ghi))|jkl)&mno"
import pyparsing as pp
term = pp.Word(pp.alphas)
AND = pp.Literal("&")
OR = pp.Literal("|")
expr = pp.infixNotation(term,
[
(AND, 2, pp.opAssoc.LEFT,),
(OR, 2, pp.opAssoc.LEFT,),
])
# print(expr.parseString(sample).asList())
AND.addParseAction(lambda: " and ")
OR.addParseAction(lambda: " or ")
term.addParseAction(lambda t: "func('{}')".format(t[0]))
expr.addParseAction(lambda t: "({})".format(''.join(t[0])))
# print(expr.parseString(sample).asList())
def format_expression(tokens):
return "if {}:\n return True".format(''.join(tokens[0]))
outer_expr = pp.Group(expr).addParseAction(format_expression)
print(outer_expr.parseString(sample)[0])
Bem, você pode analisá-lo com alguns regexes simples substituindo correspondências conforme necessário, se sua string não for mais complicada do que o que você mostra (por exemplo, é composta apenas por esses símbolos mais letras / números). Depois disso, você pode apenas usar eval()para executá-lo como código python.
Por exemplo:
import re
def func(x):
# just an example...
return True
s = "((abc&(def|ghi))|jkl)&mno"
s = re.sub(r'(\w+)', r"func('\1')", s)
s = s.replace('&', ' and ')
s = s.replace('|', ' or ')
print(s)
print(eval(s))
Resultado:
((func('abc') and (func('def') or func('ghi'))) or func('jkl')) and func('mno')
True