A Árvore de Sintaxe Abstrata (AST, na sigla em inglês), também chamada de quoted expression, é uma forma de representar código como dados.
Cada nó da AST é uma tupla de três elementos.
# AST representation of:
# 2 + 3
{:+, [], [2, 3]}
O primeiro elemento, um átomo, é a operação. O segundo elemento, uma lista de palavras-chave, traz os metadados. O terceiro elemento é uma lista de argumentos, que contém outros nós. Valores literais como números inteiros, átomos e strings são representados na AST por eles mesmos, em vez de tuplas de três elementos.
Converter código Elixir em ASTs e ASTs de volta em código faz parte da biblioteca padrão. Você pode encontrar funções para trabalhar com ASTs nos módulos Code (por exemplo, para converter uma string com código em uma AST) e Macro (por exemplo, para percorrer a AST ou convertê-la em uma string).
Repare que todas as funções da biblioteca padrão usam o nome "quoted" para se referir à AST (abreviação de quoted expression).
A forma especial para transformar código em uma AST se chama quote. Ela aceita um bloco de código e retorna sua AST.
quote do
2 + 3 - 1
end
# => {:-, [], [
# {:+, [], [2, 3]},
# 1
# ]}
A capacidade de representar código como uma AST está no coração da metaprogramação em Elixir. As macros, que são uma forma de escrever código Elixir que produz código Elixir, funcionam retornando ASTs como saída.
Outro caso de uso das ASTs é a análise estática de código, como a ferramenta do próprio Exercism, o Analyzer, que você talvez já conheça como o pequeno bot que deixa comentários nas suas soluções.
Você faz parte de uma força-tarefa que luta contra a espionagem corporativa. Você tem um informante secreto na Shady Company X, que você suspeita estar roubando segredos de seus concorrentes.
Sua informante, a Agente Ex, é desenvolvedora de Elixir. Ela está codificando mensagens secretas no código dela.
Para decodificar as mensagens secretas dela:
n caracteres do nome dela, em que n é a aridade da função.Implemente a função TopSecret.to_ast/1. Ela deve receber uma string com código Elixir e retornar sua AST.
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
Implemente a função TopSecret.decode_secret_message_part/2. Ela deve receber um nó da AST e um acumulador para a mensagem secreta (uma lista). Ela deve retornar uma tupla com o nó da AST inalterado como primeiro elemento e o acumulador como segundo elemento.
Se a operação do nó da AST for a definição de uma função (def ou defp), adicione o nome da função (transformado em string) ao início do acumulador. Se a operação for outra coisa, retorne o acumulador inalterado.
ast_node = TopSecret.to_ast("defp cat(a, b, c), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["cat", "day"]}
ast_node = TopSecret.to_ast("10 + 3")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["day"]}
Essa função não precisa fazer nenhuma chamada recursiva para verificar a AST inteira, apenas o nó recebido. Vamos percorrer a AST inteira com ferramentas nativas no último passo.
Estenda a função TopSecret.decode_secret_message_part/2. Se a operação no nó da AST for a definição de uma função, não retorne o nome inteiro da função. Em vez disso, verifique a aridade da função. Depois, retorne apenas os primeiros n caracteres do nome, em que n é a aridade.
ast_node = TopSecret.to_ast("defp cat(a, b), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["ca", "day"]}
ast_node = TopSecret.to_ast("defp cat(), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["", "day"]}
Estenda a função TopSecret.decode_secret_message_part/2. Certifique-se de que o nome e a aridade da função sejam detectados corretamente para definições de função que usam guards.
ast_node = TopSecret.to_ast("defp cat(a, b) when is_nil(a), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["ca", "day"]}
Implemente a função TopSecret.decode_secret_message/1. Ela deve receber uma string com código Elixir e retornar a mensagem secreta como uma string decodificada a partir de todas as definições de função encontradas no código. Reaproveite as funções definidas nos passos anteriores.
code = """
defmodule MyCalendar do
def busy?(date, time) do
Date.day_of_week(date) != 7 and
time.hour in 10..16
end
def yesterday?(date) do
Date.diff(Date.utc_today, date)
end
end
"""
TopSecret.decode_secret_message(code)
# => "buy"
Crie sua conta no Exercism para aprender e dominar Elixir com 58 conceitos168 exercícios e mentoria humana de verdade, tudo de graça.