L'arbre syntaxique abstrait (AST), aussi appelé expression quotée, est une façon de représenter du code sous forme de données.
Chaque nœud de l'AST est un tuple à trois éléments.
# AST representation of:
# 2 + 3
{:+, [], [2, 3]}
Le premier élément, un atome, est l'opération. Le deuxième élément, une liste de mots-clés, contient les métadonnées. Le troisième élément est une liste d'arguments, qui contient d'autres nœuds. Les valeurs littérales telles que les entiers, les atomes et les strings sont représentées dans l'AST telles quelles au lieu de tuples à trois éléments.
Transformer du code Elixir en AST et inversement fait partie de la bibliothèque standard. Tu peux trouver des fonctions pour travailler avec les AST dans les modules Code (par exemple pour transformer une string contenant du code en AST) et Macro (par exemple pour parcourir l'AST ou le transformer en string).
Note que toutes les fonctions de la bibliothèque standard utilisent le nom « quoted » pour désigner l'AST (abréviation de expression quotée).
La forme spéciale pour transformer du code en AST s'appelle quote. Elle accepte un bloc de code et renvoie son AST.
quote do
2 + 3 - 1
end
# => {:-, [], [
# {:+, [], [2, 3]},
# 1
# ]}
La capacité de représenter du code sous forme d'AST est au cœur de la métaprogrammation en Elixir. Les macros, qui sont une façon d'écrire du code Elixir qui produit du code Elixir, fonctionnent en renvoyant des AST en sortie.
Un autre cas d'usage des AST est l'analyse statique de code, comme l'outil d'Exercism, l'analyseur, que tu connais peut-être déjà comme le petit bot qui laisse des commentaires sur tes solutions.
Tu fais partie d'un groupe de travail qui lutte contre l'espionnage industriel. Tu as une informatrice secrète chez Shady Company X, que tu soupçonnes de voler les secrets de ses concurrents.
Ton informatrice, l'agente Ex, est développeuse Elixir. Elle encode des messages secrets dans son code.
Pour décoder ses messages secrets :
n premiers caractères de son nom, où n est l'arité de la fonction.Implémente la fonction TopSecret.to_ast/1. Elle doit prendre une string contenant du code Elixir et renvoyer son AST.
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
Implémente la fonction TopSecret.decode_secret_message_part/2. Elle doit prendre un nœud de l'AST et un accumulateur pour le message secret (un tableau). Elle doit renvoyer un tuple avec le nœud de l'AST inchangé comme premier élément, et l'accumulateur comme second élément.
Si l'opération du nœud de l'AST est la définition d'une fonction (def ou defp), ajoute le nom de la fonction (converti en string) au début de l'accumulateur. Si l'opération est autre chose, renvoie l'accumulateur inchangé.
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"]}
Cette fonction n'a pas besoin de faire d'appels récursifs pour vérifier tout l'AST, seulement le nœud donné. On parcourra tout l'AST avec des outils intégrés dans la dernière étape.
Étends la fonction TopSecret.decode_secret_message_part/2. Si l'opération du nœud de l'AST est la définition d'une fonction, ne renvoie pas le nom complet de la fonction. Vérifie plutôt l'arité de la fonction. Ensuite, ne renvoie que les n premiers caractères du nom, où n est l'arité.
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"]}
Étends la fonction TopSecret.decode_secret_message_part/2. Assure-toi que le nom et l'arité de la fonction sont correctement détectés pour les définitions de fonction qui utilisent des gardes.
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"]}
Implémente la fonction TopSecret.decode_secret_message/1. Elle doit prendre une string contenant du code Elixir et renvoyer le message secret sous forme de string, décodé à partir de toutes les définitions de fonction trouvées dans le code. Pense à réutiliser les fonctions définies dans les étapes précédentes.
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"
Inscris-toi sur Exercism pour apprendre et maîtriser Elixir avec 58 concepts168 exercices, et un vrai mentorat humain, le tout gratuitement.