El árbol de sintaxis abstracta (AST), también llamado expresión citada, es una forma de representar código como datos.
Cada nodo del AST es una tupla de tres elementos.
# AST representation of:
# 2 + 3
{:+, [], [2, 3]}
El primer elemento, un átomo, es la operación. El segundo elemento, una lista de palabras clave, son los metadatos. El tercer elemento es una lista de argumentos, que contiene otros nodos. Los valores literales, como los enteros, los átomos y los strings, se representan en el AST tal cual, en lugar de como tuplas de tres elementos.
Convertir código Elixir en AST y los AST de vuelta en código es parte de la biblioteca estándar. Puedes encontrar funciones para trabajar con AST en los módulos Code (por ejemplo, para convertir un string con código en un AST) y Macro (por ejemplo, para recorrer el AST o convertirlo en un string).
Nota que todas las funciones de la biblioteca estándar usan el nombre «quoted» para referirse al AST (abreviatura de expresión citada).
La forma especial para convertir código en un AST se llama quote. Acepta un bloque de código y devuelve su AST.
quote do
2 + 3 - 1
end
# => {:-, [], [
# {:+, [], [2, 3]},
# 1
# ]}
La capacidad de representar código como un AST está en el corazón de la metaprogramación en Elixir. Las macros, que son una forma de escribir código Elixir que produce código Elixir, funcionan devolviendo AST como resultado.
Otro caso de uso de los AST es el análisis estático de código, como el de la propia herramienta de Exercism, el Analyzer, que quizás ya conozcas como el pequeño bot que deja comentarios en tus soluciones.
Eres parte de un grupo de trabajo que lucha contra el espionaje corporativo. Tienes un informante secreto en Shady Company X, de la que sospechas que roba secretos a sus competidores.
Tu informante, la Agente Ex, es una desarrolladora de Elixir. Está codificando mensajes secretos en su código.
Para decodificar sus mensajes secretos:
n caracteres de su nombre, donde n es la aridad de la función.Implementa la función TopSecret.to_ast/1. Debe recibir un string con código de Elixir y devolver su AST.
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
Implementa la función TopSecret.decode_secret_message_part/2. Debe recibir un nodo del AST y un acumulador para el mensaje secreto (un array). Debe devolver una tupla con el nodo del AST sin cambios como primer elemento y el acumulador como segundo elemento.
Si la operación del nodo del AST define una función (def o defp), antepón el nombre de la función (convertido a string) al acumulador. Si la operación es otra cosa, devuelve el acumulador sin cambios.
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 función no necesita hacer llamadas recursivas para revisar todo el AST, solo el nodo dado. Recorreremos todo el AST con herramientas integradas en el último paso.
Extiende la función TopSecret.decode_secret_message_part/2. Si la operación del nodo del AST define una función, no devuelvas el nombre completo de la función. En su lugar, revisa la aridad de la función. Luego, devuelve solo los primeros n caracteres del nombre, donde n es la aridad.
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"]}
Extiende la función TopSecret.decode_secret_message_part/2. Asegúrate de que el nombre y la aridad de la función se detecten correctamente en las definiciones de funciones que usan 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 la función TopSecret.decode_secret_message/1. Debe recibir un string con código de Elixir y devolver el mensaje secreto como un string decodificado a partir de todas las definiciones de funciones que se encuentren en el código. Asegúrate de reutilizar las funciones definidas en los pasos 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"
Regístrate en Exercism para aprender y dominar Elixir con 58 conceptos168 ejercicios y mentoría humana real, todo gratis.