Top secret

Top secret

Exercice d'apprentissage

Introduction

AST

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.

Transforme du code en AST

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
#    ]}

Cas d'usage

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.

Instructions

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 :

  • Prends toutes les fonctions (publiques et privées) dans l'ordre dans lequel elles sont définies.
  • Pour chaque fonction, prends les n premiers caractères de son nom, où n est l'arité de la fonction.

1. Transforme le code en données

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]}

2. Analyse un seul nœud de l'AST

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.

3. Décode la partie du message secret à partir d'une définition de fonction

É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"]}

4. Corrige le décodage pour les fonctions avec des gardes

É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"]}

5. Décode le message secret complet

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"
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Elixir Exercism

Prêt à commencer Top secret ?

Inscris-toi sur Exercism pour apprendre et maîtriser Elixir avec 58 concepts168 exercices, et un vrai mentorat humain, le tout gratuitement.