Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions exercises/concept/top-secret/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Hints

## General
73 changes: 73 additions & 0 deletions exercises/concept/top-secret/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Instructions

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.

Your informer, Agent Ex, is an Elixir developer. She is encoding secret messages in her code.

To decode the secret message:

- Take all functions (public and private) in the order they're defined in.
- For each function, take the first `n` characters from its name, where `n` is the function's arity.

## 1. Turn code into data

Implement the `TopSecret.to_ast/1` function. It should take a string with Elixir code and return its AST.

```elixir
TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}
```

## 2. Parse a single AST node

Implement the `TopSecret.decode_secret_message_part/2` function. It should take an AST and an accumulator for the secret message (a list). It should return a tuple with the AST unchanged as the first element, and the accumulator as the second element.

If the top-most operation in the AST is defining a function (`def` or `defp`), prepend the function name (changed to a string) to the accumulator. If the top-most operation is something else, return the accumulator unchanged.

```elixir
ast = TopSecret.to_ast("defp cat(a, b, c), do: nil")
TopSecret.decode_secret_message_part(ast, ["day"])
# => {ast, ["cat", "day"]}

ast = TopSecret.to_ast("10 + 3")
TopSecret.decode_secret_message_part(ast, ["day"])
# => {ast, ["day"]}
```

This function doesn't need to do any recursive calls to check the whole AST, only the top-most operation. We will traverse the whole AST with built-in tools in the last step.

## 3. Decode the secret message part from function definition

Extend the `TopSecret.decode_secret_message_part/2` function. If the top-most operation in the AST 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.

```elixir
ast = TopSecret.to_ast("defp cat(a, b), do: nil")
TopSecret.decode_secret_message_part(ast, ["day"])
# => {ast, ["ca", "day"]}

ast = TopSecret.to_ast("defp cat(), do: nil")
TopSecret.decode_secret_message_part(ast, ["day"])
# => {ast, ["", "day"]}
```

## 4. Decode the full secret message

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.

```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"
```
11 changes: 11 additions & 0 deletions exercises/concept/top-secret/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Introduction

## AST

to write:

AST, also called a quoted expression in Elixir

necessary for metaprogramming

what is arity
4 changes: 4 additions & 0 deletions exercises/concept/top-secret/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
24 changes: 24 additions & 0 deletions exercises/concept/top-secret/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
maps-*.tar

20 changes: 20 additions & 0 deletions exercises/concept/top-secret/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"blurb": "TODO",
"icon": "TODO",
"authors": [
"jiegillet",
"angelikatyborska"
],
"files": {
"solution": [
"lib/top_secret.ex"
],
"test": [
"test/top_secret_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10"
}
27 changes: 27 additions & 0 deletions exercises/concept/top-secret/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Design

## Learning objectives

- How to parse a string to AST
- Understanding ASTs
- Traversing ASTs

## Out of scope

- `quote`/`unquote`
- macros

## Concepts

- `ast`

## Prerequisites

- `enum`
- `strings`
- `tuples`
- `pattern-matching`

## Analyzer

- function reuse: `decode_secret_message` should use `decode_secret_message_part` and `to_ast`.
32 changes: 32 additions & 0 deletions exercises/concept/top-secret/.meta/exemplar.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule TopSecret do
def to_ast(string) do
Code.string_to_quoted!(string)
end

def decode_secret_message_part({keyword, _, children} = ast, acc)
when keyword in [:def, :defp] do
[{function_name, _, arguments} | _] = children

arity = length(arguments)

message_part =
function_name
|> to_string()
|> String.slice(0, arity)

{ast, [message_part | acc]}
end

def decode_secret_message_part(ast, acc) do
{ast, acc}
end

def decode_secret_message(string) do
ast = to_ast(string)
{_, acc} = Macro.prewalk(ast, [], &decode_secret_message_part/2)

acc
|> Enum.reverse()
|> Enum.join("")
end
end
13 changes: 13 additions & 0 deletions exercises/concept/top-secret/lib/top_secret.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule TopSecret do
def to_ast(string) do
# Please implement the to_ast/1 function
end

def decode_secret_message_part(ast, acc) do
# Please implement the decode_secret_message_part/2 function
end

def decode_secret_message(string) do
# Please implement the decode_secret_message/1 function
end
end
28 changes: 28 additions & 0 deletions exercises/concept/top-secret/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule TopSecret.MixProject do
use Mix.Project

def project do
[
app: :top_secret,
version: "0.1.0",
# elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
2 changes: 2 additions & 0 deletions exercises/concept/top-secret/test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
Loading