Para entender facilmente meu problema, abaixo está uma versão simplificada de alguns exemplos de entrada.
['foo', 1, 'a', 'foo', 2, 'foo', 1, 'b', 'foo', -1, 'foo', -1, "bar", 1, "c", "bar", 2, 'baz', 1, 'd', 'baz', -1, "bar", 3, "e", "bar", 4, 'qux', 1, 'stu', 1, 'f', 'stu', -1, 'qux', -1, 'bar', -1]
(Usei "stu" porque fiquei sem nomes de espaço reservado.)
As strings são nomes de funções (mais ou menos, detalhes depois). Os números depois dos nomes de funções especificam a posição do argumento na função que se segue. Uma posição de -1 fecha a função.
Por exemplo, ['foo',1,'a','foo',2,'b','foo',-1]
deve ser equivalente a foo('a', 'b')
.
Isso também deve funcionar quando aninhado:
['foo', 1, 'a', 'foo', 2, 'foo', 1, 'b', 'foo', -1, 'foo', -1]
deve ser equivalente a foo('a', foo('b'))
e
['bar', 1, 'c', 'bar', 2, 'baz', 1, 'd', 'baz', -1, 'bar', 3, 'e', 'bar', 4, 'qux', 1, 'stu', 1, 'f', 'stu',-1, 'qux', -1, 'bar', -1]
deve ser equivalente a bar('c', baz('d'), e, qux(stu('f')))
.
Minha função desejada deve retornar uma lista. Por exemplo,
['foo', 1, 'a', 'foo', 2, 'foo', 1, 'b', 'foo', -1, 'foo', -1, 'bar', 1, 'c', 'bar', -1]
deve resultar em
[['foo', 'a', ['foo', 'b']], ['bar', 'c']]
Agora que o problema está mais claro, meu problema real é um pouco diferente. Todos os elementos da lista são inteiros. Os nomes das funções não são strings, são sequências de três inteiros. Portanto, ['foo',1,'a','foo',2,'b','foo',-1]
é na verdade [1, 1, 1, 1, 104, 1, 1, 1, 2, 105, 1, 1, 1, -1]
.
Os nomes das funções ( [1, 1, 1]
no exemplo acima) agem como chaves de dicionário. O dicionário (chamado constructs
) se parece com isto:
constructs = {
1: {
1: {
1: lambda *chars : print(''.join(chr(char) for char in chars))
}
}
}
Então, finalmente, o exemplo deve resultar em algo como
[[lambda *chars : print(''.join(chr(char) for char in chars)), 104, 105]]
Todas as especificações sobre aninhamento e coisas assim ainda devem ser aplicadas. Não tenho ideia de como implementar isso de forma confiável e elegante, por favor ajude!
Desde já, obrigado.
Editar: esqueci de dizer que isso 0
é sempre ignorado e pulado, e uma 0
chamada de função a seguir escapará da chamada de função e fará com que ela seja tratada como um argumento. Toda essa funcionalidade é implementada até certo ponto na minha tentativa até agora , mas não funciona quando a mesma função é aninhada dentro de si mesma. Também é ineficiente e deselegante, com muitos problemas potenciais, então recorri ao Stack Overflow para obter ajuda para escrever uma melhor. Sinta-se à vontade para usá-la como ponto de partida!
Edição 2: aqui está o código da minha tentativa até agora:
constructs = {
1: {
1: {
1: print,
}
}
}
def parse(code: list) -> list:
if len(code) <= 1:
return code
result = []
in_function = 0
for i, token in enumerate(code):
if in_function > 0:
in_function -= 1
continue
if token == 0:
continue
if result and result[-1][0][3] != -1:
if token in constructs and code[i + 1] in constructs[token] and code[i + 2] in constructs[token][code[i + 1]]:
if i < len(code) - 4 and code[i + 4] == 0:
result[-1][-1].append(token)
else:
if code[i + 3] == result[-1][0][3] + 1:
result[-1].append([])
result[-1][0] = code[i:i + 4]
in_function = 3
else:
result[-1][-1].append(token)
else:
if token in constructs and code[i + 1] in constructs[token] and code[i + 2] in constructs[token][code[i + 1]]:
if code[i + 3] == 1:
result.append([code[i:i + 4], []])
in_function = 3
else:
raise SyntaxError(f'function {code[i:i + 3]} has no previous separator {code[i + 3] - 1}')
else:
raise SyntaxError(f'function {code[i:i + 3]} was not recognized')
for i, function in enumerate(result):
result[i][0] = constructs[result[i][0][0]][result[i][0][1]][result[i][0][2]]
for j, argument in enumerate(result[i][1:]):
result[i][j + 1] = parse(argument)
return result
Funciona para, parse([1, 1, 1, 1, 'Hello world', 1, 1, 1, 2, 'etc', 1, 1, 1, -1])
mas não para parse([1, 1, 1, 1, 1, 1, 1, 1, 'Hello world', 1, 1, 1, -1, 1, 1, 1, 2, 'etc', 1, 1, 1, -1])
.
Uma lista válida seguindo esta regra gramatical pode ser analisada recursivamente com uma função que, dado um índice válido, tenta analisar os tokens começando pelo índice como chaves para uma função seguida pela posição do argumento (com -1 encerrando a chamada da função) ou então assume como padrão um valor escalar.
Além do objeto analisado, a função também deve retornar o índice para o próximo token para avançar o índice de acordo com o número de tokens consumidos no nível atual.
Como pode haver uma ou mais dessas expressões na lista de entrada, a função deve ser chamada iterativamente, retornando valores anexados a uma lista de saída até que o índice atinja o final da entrada:
para que:
saídas:
Demonstração: https://ideone.com/4ct2KA
O código acima assume que a entrada é válida e ignora as chaves para uma função após o primeiro argumento, pois elas são redundantes. Posições de argumento também são ignoradas, pois são sempre incrementadas de 1, embora possam ser usadas para validação de entrada, conforme necessário.
Obrigado, blhsing!
Então consegui fazer minha tentativa funcionar, embora ela seja duas vezes mais longa que a resposta do blhsing.
Ele também implementa o recurso de que um
0
seguido por outro0
não é pulado. O único problema com ele é que sequências escapadas que por acaso formam uma chamada de função aninhada são tratadas como tal, em vez de serem puladas.Ou seja,
parse([1, 1, 1, 1, 1, 1, 2, 1, 0, 97, 1, 1, 2, -1, 0, 1, 1, 1, -1])
retorna([[(1, 1, 1), [(1, 1, 2), 97]]], [])
em vez de([[1, 1, 1], 1, 1, 2, 1, 97, 1, 1, 2, -1], [])
.(Adicionei a pilha ao retorno para que outra função possa dizer se alguma função não foi fechada corretamente.)
Editar: demonstração