|
| 1 | +# Instructions |
| 2 | + |
| 3 | +You're part of a task force fighting against corporate espionage. You have a secret informer at Shady Company X, which you suspect of stealing secrets from its competitors. |
| 4 | + |
| 5 | +Your informer, Agent Ex, is an Elixir developer. She is encoding secret messages in her code. |
| 6 | + |
| 7 | +To decode her secret messages: |
| 8 | + |
| 9 | +- Take all functions (public and private) in the order they're defined in. |
| 10 | +- For each function, take the first `n` characters from its name, where `n` is the function's arity. |
| 11 | + |
| 12 | +## 1. Turn code into data |
| 13 | + |
| 14 | +Implement the `TopSecret.to_ast/1` function. It should take a string with Elixir code and return its AST. |
| 15 | + |
| 16 | +```elixir |
| 17 | +TopSecret.to_ast("div(4, 3)") |
| 18 | +# => {:div, [line: 1], [4, 3]} |
| 19 | +``` |
| 20 | + |
| 21 | +## 2. Parse a single AST node |
| 22 | + |
| 23 | +Implement the `TopSecret.decode_secret_message_part/2` function. It should take an AST node and an accumulator for the secret message (a list). It should return a tuple with the AST node unchanged as the first element, and the accumulator as the second element. |
| 24 | + |
| 25 | +If the operation of the AST node is defining a function (`def` or `defp`), prepend the function name (changed to a string) to the accumulator. If the operation is something else, return the accumulator unchanged. |
| 26 | + |
| 27 | +```elixir |
| 28 | +ast_node = TopSecret.to_ast("defp cat(a, b, c), do: nil") |
| 29 | +TopSecret.decode_secret_message_part(ast_node, ["day"]) |
| 30 | +# => {ast_node, ["cat", "day"]} |
| 31 | + |
| 32 | +ast_node = TopSecret.to_ast("10 + 3") |
| 33 | +TopSecret.decode_secret_message_part(ast_node, ["day"]) |
| 34 | +# => {ast_node, ["day"]} |
| 35 | +``` |
| 36 | + |
| 37 | +This function doesn't need to do any recursive calls to check the whole AST, only the given node. We will traverse the whole AST with built-in tools in the last step. |
| 38 | + |
| 39 | +## 3. Decode the secret message part from function definition |
| 40 | + |
| 41 | +Extend the `TopSecret.decode_secret_message_part/2` function. If the operation in the AST node is defining a function, don't return the whole function name. Instead, check the function's arity. Then, return only first `n` character from the name, where `n` is the arity. |
| 42 | + |
| 43 | +```elixir |
| 44 | +ast_node = TopSecret.to_ast("defp cat(a, b), do: nil") |
| 45 | +TopSecret.decode_secret_message_part(ast_node, ["day"]) |
| 46 | +# => {ast_node, ["ca", "day"]} |
| 47 | + |
| 48 | +ast_node = TopSecret.to_ast("defp cat(), do: nil") |
| 49 | +TopSecret.decode_secret_message_part(ast_node, ["day"]) |
| 50 | +# => {ast_node, ["", "day"]} |
| 51 | +``` |
| 52 | + |
| 53 | +## 4. Fix the decoding for functions with guards |
| 54 | + |
| 55 | +Extend the `TopSecret.decode_secret_message_part/2` function. Make sure the function's name and arity is correctly detected for function definitions that use guards. |
| 56 | + |
| 57 | +```elixir |
| 58 | +ast_node = TopSecret.to_ast("defp cat(a, b) when is_nil(a), do: nil") |
| 59 | +TopSecret.decode_secret_message_part(ast_node, ["day"]) |
| 60 | +# => {ast_node, ["ca", "day"]} |
| 61 | +``` |
| 62 | + |
| 63 | +## 5. Decode the full secret message |
| 64 | + |
| 65 | +Implement the `TopSecret.decode_secret_message/1` function. It should take a string with Elixir code and return the secret message as a string decoded from all function definitions found in the code. Make sure to reuse functions defined in previous steps. |
| 66 | + |
| 67 | +```elixir |
| 68 | +code = """ |
| 69 | +defmodule MyCalendar do |
| 70 | + def busy?(date, time) do |
| 71 | + Date.day_of_week(date) != 7 and |
| 72 | + time.hour in 10..16 |
| 73 | + end |
| 74 | +
|
| 75 | + def yesterday?(date) do |
| 76 | + Date.diff(Date.utc_today, date) |
| 77 | + end |
| 78 | +end |
| 79 | +""" |
| 80 | + |
| 81 | +TopSecret.decode_secret_message(code) |
| 82 | +# => "buy" |
| 83 | +``` |
0 commit comments