A árvore de sintaxe abstrata (AST), também chamada expressão citada, é uma forma de representar código como dados.
Cada nó da AST é um tuplo 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: os metadados. O terceiro elemento é uma lista de argumentos, que contém outros nós. Os valores literais, como inteiros, átomos e strings, são representados na AST como eles próprios, em vez de tuplos de três elementos.
Converter código Elixir em ASTs e ASTs de volta em código faz parte da biblioteca padrão. Podes encontrar funções para trabalhar com ASTs nos módulos Code (por exemplo, para converter uma string com código numa AST) e Macro (por exemplo, para percorrer a AST ou convertê-la numa string).
Repara que todas as funções da biblioteca padrão usam o nome "quoted" para designar a AST (abreviatura de expressão citada).
A forma especial para converter código numa AST chama-se quote. Aceita um bloco de código e devolve a sua AST.
quote do
2 + 3 - 1
end
# => {:-, [], [
# {:+, [], [2, 3]},
# 1
# ]}
A capacidade de representar código como uma AST está no centro da metaprogramação em Elixir. As macros, uma forma de escrever código Elixir que produz código Elixir, funcionam devolvendo ASTs como resultado.
Outro caso de uso das ASTs é a análise estática de código, como a ferramenta do próprio Exercism, o Analyzer, que talvez já conheças como o pequeno bot que deixa comentários nas tuas soluções.
Fazes parte de uma força-tarefa que combate a espionagem empresarial. Tens um informador secreto na Shady Company X, que suspeitas estar a roubar segredos aos concorrentes.
A tua informadora, a Agente Ex, é programadora de Elixir. Está a codificar mensagens secretas no código dela.
Para descodificares as mensagens secretas dela:
n carateres do nome, em que n é a aridade da função.Implementa a função TopSecret.to_ast/1. Deve receber uma string com código Elixir e devolver a respetiva AST.
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
Implementa a função TopSecret.decode_secret_message_part/2. Deve receber um nó da AST e um acumulador para a mensagem secreta (uma lista). Deve devolver um tuplo 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), acrescenta ao início do acumulador o nome da função (convertido numa string). Se a operação for outra coisa, devolve 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"]}
Esta função não precisa de fazer chamadas recursivas para verificar toda a AST, apenas o nó dado. Vamos percorrer toda a AST com ferramentas incorporadas no último passo.
Amplia 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 devolvas o nome completo da função. Em vez disso, verifica a aridade da função. Depois, devolve apenas os primeiros n carateres 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"]}
Amplia a função TopSecret.decode_secret_message_part/2. Certifica-te de que o nome e a aridade da função são detetados corretamente nas definições de funções que usam guardas.
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"]}
Implementa a função TopSecret.decode_secret_message/1. Deve receber uma string com código Elixir e devolver a mensagem secreta, como string, descodificada a partir de todas as definições de funções encontradas no código. Certifica-te de que reutilizas 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"
Inscreve-te no Exercism para aprenderes e dominares Elixir com 58 conceitos168 exercícios, e mentoria humana real, tudo grátis.