Абстрактне синтаксичне дерево (AST), яке також називають quoted expression, дає змогу подати код як дані.
Кожен вузол в AST - це кортеж із трьох елементів.
# AST representation of:
# 2 + 3
{:+, [], [2, 3]}
Перший елемент, атом, - це операція. Другий елемент, список ключових слів, - це метадані. Третій елемент - це список аргументів, який містить інші вузли. Літеральні значення, як-от цілі числа, атоми та рядки тексту (англ. string), подаються в AST самі собою, а не як кортежі з трьох елементів.
Перетворення коду Elixir на AST і AST назад на код входить до стандартної бібліотеки. Функції для роботи з AST можна знайти в модулях Code (наприклад, щоб перетворити рядок тексту з кодом на AST) і Macro (наприклад, щоб обійти AST або перетворити його на рядок тексту).
Варто зауважити, що всі функції зі стандартної бібліотеки використовують назву «quoted» на позначення AST (скорочення від quoted expression).
Спеціальна форма для перетворення коду на AST називається quote. Вона приймає блок коду і повертає його AST.
quote do
2 + 3 - 1
end
# => {:-, [], [
# {:+, [], [2, 3]},
# 1
# ]}
Можливість подати код як AST лежить в основі метапрограмування в Elixir. Макроси, тобто спосіб писати код Elixir, який породжує код Elixir, працюють так, що повертають AST як вихідні дані.
Ще один варіант використання AST - це статичний аналіз коду, як-от власний інструмент Exercism, Analyzer, який, можливо, вже відомий нам як маленький бот, що залишає коментарі до наших рішень.
Ми входимо до складу оперативної групи, яка бореться проти корпоративного шпигунства. У нас є таємний інформатор у Shady Company X, яку ми підозрюємо в крадіжці секретів у конкурентів.
Наш інформатор, агент Екс, працює розробницею Elixir. Вона кодує секретні повідомлення у своєму коді.
Щоб декодувати її секретні повідомлення:
n символів з її назви, де n - арність функції.Реалізуйте функцію TopSecret.to_ast/1. Вона має приймати рядок тексту (англ. string) з кодом Elixir і повертати його AST.
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
Реалізуйте функцію TopSecret.decode_secret_message_part/2. Вона має приймати вузол AST і акумулятор секретного повідомлення (список). Вона має повертати кортеж, першим елементом якого є незмінений вузол AST, а другим - акумулятор.
Якщо операція вузла AST визначає функцію (def або defp), додайте назву функції (перетворену на рядок тексту) на початок акумулятора. Якщо ж операція інша, поверніть акумулятор без змін.
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"]}
Ця функція не потребує рекурсивних викликів, щоб перевірити все AST, лише заданий вузол. В останньому кроці ми обійдемо все AST вбудованими інструментами.
Розширте функцію TopSecret.decode_secret_message_part/2. Якщо операція вузла AST визначає функцію, не повертайте всю назву функції. Натомість перевірте арність функції. Тоді поверніть лише перші n символів з назви, де n - арність.
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"]}
Розширте функцію TopSecret.decode_secret_message_part/2. Переконайтеся, що назву й арність функції правильно визначено для визначень функцій, які використовують захисні вирази.
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"]}
Реалізуйте функцію TopSecret.decode_secret_message/1. Вона має приймати рядок тексту з кодом Elixir і повертати секретне повідомлення як рядок тексту, декодований з усіх визначень функцій, знайдених у коді. Не забудьте повторно використати функції, визначені в попередніх кроках.
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"
Зареєструйтеся на Exercism, щоб вивчати й опановувати Elixir, а також 58 концепцій168 вправ та справжнє наставництво від людей, і все це безкоштовно.