L'albero sintattico astratto (AST), detto anche espressione quotata, è un modo per rappresentare il codice come dati.
Ogni nodo dell'AST è una tupla di tre elementi.
# AST representation of:
# 2 + 3
{:+, [], [2, 3]}
Il primo elemento, un atomo, è l'operazione. Il secondo elemento, una keyword list, contiene i metadati. Il terzo elemento è una lista di argomenti, che contiene altri nodi. I valori letterali, come i numeri interi, gli atomi e le stringhe, sono rappresentati nell'AST direttamente, invece che come tuple di tre elementi.
Trasformare il codice Elixir in AST e gli AST di nuovo in codice fa parte della libreria standard. Puoi trovare le funzioni per lavorare con gli AST nei moduli Code (ad esempio per trasformare una stringa di codice in un AST) e Macro (ad esempio per attraversare l'AST o trasformarlo in una stringa).
Tieni presente che tutte le funzioni della libreria standard usano il nome «quoted» per indicare l'AST (abbreviazione di quoted expression).
La forma speciale per trasformare il codice in un AST si chiama quote. Accetta un blocco di codice e restituisce il suo AST.
quote do
2 + 3 - 1
end
# => {:-, [], [
# {:+, [], [2, 3]},
# 1
# ]}
La capacità di rappresentare il codice come AST è al cuore della metaprogrammazione in Elixir. Le macro, che sono un modo per scrivere codice Elixir che produce codice Elixir, funzionano restituendo come output degli AST.
Un altro caso d'uso degli AST è l'analisi statica del codice, come l'Analyzer, lo strumento di Exercism che potresti già conoscere come il piccolo bot che lascia commenti sulle soluzioni che invii.
Fai parte di una task force che combatte contro lo spionaggio aziendale. Hai un'informatrice segreta presso Shady Company X, che sospetti rubi segreti ai suoi concorrenti.
La tua informatrice, l'agente Ex, è una sviluppatrice Elixir. Sta codificando messaggi segreti nel suo codice.
Per decodificare i suoi messaggi segreti:
n caratteri del suo nome, dove n è l'arità della funzione.Implementa la funzione TopSecret.to_ast/1. Deve prendere una stringa con codice Elixir e restituirne l'AST.
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
Implementa la funzione TopSecret.decode_secret_message_part/2. Deve prendere un nodo AST e un accumulatore per il messaggio segreto (un array). Deve restituire una tupla con il nodo AST invariato come primo elemento e l'accumulatore come secondo elemento.
Se l'operazione del nodo AST definisce una funzione (def o defp), anteponi all'accumulatore il nome della funzione (convertito in stringa). Se l'operazione è qualcos'altro, restituisci l'accumulatore invariato.
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"]}
Questa funzione non ha bisogno di fare chiamate ricorsive per controllare l'intero AST, ma solo il nodo dato. Attraverseremo l'intero AST con strumenti integrati nell'ultimo passaggio.
Estendi la funzione TopSecret.decode_secret_message_part/2. Se l'operazione nel nodo AST definisce una funzione, non restituire l'intero nome della funzione. Controlla invece l'arità della funzione. Poi restituisci solo i primi n caratteri del nome, dove n è 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"]}
Estendi la funzione TopSecret.decode_secret_message_part/2. Assicurati che il nome e l'arità della funzione siano rilevati correttamente per le definizioni di funzione che usano guardie.
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 funzione TopSecret.decode_secret_message/1. Deve prendere una stringa con codice Elixir e restituire il messaggio segreto come stringa decodificata da tutte le definizioni di funzione trovate nel codice. Assicurati di riutilizzare le funzioni definite nei passaggi precedenti.
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"
Iscriviti a Exercism per imparare e padroneggiare Elixir con 58 concetti168 esercizi e il mentoring di persone reali, tutto gratis.