El Abstract Syntax Tree (AST), también llamada quoted expression, es una forma de representar el 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 números enteros, los átomos y los strings, se representan en el AST como ellos mismos en lugar de como tuplas de tres elementos.
Cambiar código de Elixir a AST y los AST de nuevo a código forma parte de la biblioteca estándar. Puedes encontrar funciones para trabajar con AST en los módulos Code (por ejemplo, para cambiar una string con código a un AST) y Macro (por ejemplo, para recorrer el AST o cambiarlo a una string).
Ten en cuenta que todas las funciones de la biblioteca estándar usan el nombre «quoted» para referirse al AST (abreviatura de quoted expression).
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 de Elixir que produce código de Elixir, funcionan devolviendo AST como resultado.
Otro caso de uso de los AST es el análisis estático de código, como la propia herramienta de Exercism, el Analyzer, que quizá ya conozcas como el pequeño bot que deja comentarios en tus soluciones.
Formas parte de un grupo de trabajo que lucha contra el espionaje empresarial. Tienes un informante secreto en Shady Company X, a la que sospechas de robar secretos a sus competidores.
Tu informante, la agente Ex, es 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 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 (una lista). 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 es definir 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 ninguna llamada recursiva para comprobar todo el AST, solo el nodo dado. Recorreremos todo el AST con herramientas integradas en el último paso.
Amplía la función TopSecret.decode_secret_message_part/2. Si la operación del nodo del AST es definir una función, no devuelvas el nombre completo de la función. En su lugar, comprueba la aridad de la función. Después, 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"]}
Amplía la función TopSecret.decode_secret_message_part/2. Asegúrate de que el nombre y la aridad de la función se detectan 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 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.