top-secret
This commit is contained in:
		
							parent
							
								
									b4e1570ba6
								
							
						
					
					
						commit
						3ab71444f5
					
				
							
								
								
									
										19
									
								
								elixir/top-secret/.exercism/config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								elixir/top-secret/.exercism/config.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | { | ||||||
|  |   "authors": [ | ||||||
|  |     "jiegillet", | ||||||
|  |     "angelikatyborska" | ||||||
|  |   ], | ||||||
|  |   "files": { | ||||||
|  |     "solution": [ | ||||||
|  |       "lib/top_secret.ex" | ||||||
|  |     ], | ||||||
|  |     "test": [ | ||||||
|  |       "test/top_secret_test.exs" | ||||||
|  |     ], | ||||||
|  |     "exemplar": [ | ||||||
|  |       ".meta/exemplar.ex" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "language_versions": ">=1.10", | ||||||
|  |   "blurb": "Learn about the Abstract Syntax Tree (AST) by helping decode secret messages from Agent Ex." | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								elixir/top-secret/.exercism/metadata.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								elixir/top-secret/.exercism/metadata.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | {"track":"elixir","exercise":"top-secret","id":"6ab6565f409840a8977c99288b6a10b4","url":"https://exercism.org/tracks/elixir/exercises/top-secret","handle":"negrienko","is_requester":true,"auto_approve":false} | ||||||
							
								
								
									
										4
									
								
								elixir/top-secret/.formatter.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								elixir/top-secret/.formatter.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | # Used by "mix format" | ||||||
|  | [ | ||||||
|  |   inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||||||
|  | ] | ||||||
							
								
								
									
										24
									
								
								elixir/top-secret/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								elixir/top-secret/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -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 | ||||||
|  | 
 | ||||||
							
								
								
									
										75
									
								
								elixir/top-secret/HELP.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								elixir/top-secret/HELP.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | # Help | ||||||
|  | 
 | ||||||
|  | ## Running the tests | ||||||
|  | 
 | ||||||
|  | From the terminal, change to the base directory of the exercise then execute the tests with: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | $ mix test | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` | ||||||
|  | 
 | ||||||
|  | Documentation: | ||||||
|  | 
 | ||||||
|  | * [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) | ||||||
|  | * [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) | ||||||
|  | 
 | ||||||
|  | ## Pending tests | ||||||
|  | 
 | ||||||
|  | In test suites of practice exercises, all but the first test have been tagged to be skipped. | ||||||
|  | 
 | ||||||
|  | Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. | ||||||
|  | 
 | ||||||
|  | For example: | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | # @tag :pending | ||||||
|  | test "shouting" do | ||||||
|  |   assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" | ||||||
|  | end | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | $ mix test --include pending | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | # ExUnit.configure(exclude: :pending, trace: true) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Useful `mix test` options | ||||||
|  | 
 | ||||||
|  | * `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM` | ||||||
|  | * `--failed` - runs only tests that failed the last time they ran | ||||||
|  | * `--max-failures` - the suite stops evaluating tests when this number of test failures | ||||||
|  | is reached | ||||||
|  | * `--seed 0` - disables randomization so the tests in a single file will always be ran | ||||||
|  | in the same order they were defined in | ||||||
|  | 
 | ||||||
|  | ## Submitting your solution | ||||||
|  | 
 | ||||||
|  | You can submit your solution using the `exercism submit lib/top_secret.ex` command. | ||||||
|  | This command will upload your solution to the Exercism website and print the solution page's URL. | ||||||
|  | 
 | ||||||
|  | It's possible to submit an incomplete solution which allows you to: | ||||||
|  | 
 | ||||||
|  | - See how others have completed the exercise | ||||||
|  | - Request help from a mentor | ||||||
|  | 
 | ||||||
|  | ## Need to get help? | ||||||
|  | 
 | ||||||
|  | If you'd like help solving the exercise, check the following pages: | ||||||
|  | 
 | ||||||
|  | - The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) | ||||||
|  | - The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir) | ||||||
|  | - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) | ||||||
|  | - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) | ||||||
|  | 
 | ||||||
|  | Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. | ||||||
|  | 
 | ||||||
|  | If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. | ||||||
							
								
								
									
										51
									
								
								elixir/top-secret/HINTS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								elixir/top-secret/HINTS.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | # Hints | ||||||
|  | 
 | ||||||
|  | ## General | ||||||
|  | 
 | ||||||
|  | - Read about quoting in the [official Getting Started guide][getting-started-quote]. | ||||||
|  | - Read the [introduction to Elixir AST by Lucas San Román][ast-intro-lucas]. | ||||||
|  | - Read [the official documentation for `quote`][doc-quote]. | ||||||
|  | - Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets. | ||||||
|  | 
 | ||||||
|  | ## 1. Turn code into data | ||||||
|  | 
 | ||||||
|  | - There is a [built-in function][doc-code-string-to-quoted] that turns a string with code into an AST. | ||||||
|  | 
 | ||||||
|  | ## 2. Parse a single AST node | ||||||
|  | 
 | ||||||
|  | - Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets. | ||||||
|  | - The operations that define a function are `:def` and `:defp`. | ||||||
|  | - The operation is the first element in a three-element AST node tuple. | ||||||
|  | - You can ignore the second element in the tuple in this exercise completely. | ||||||
|  | - The third element in the tuple is the argument list of the operation that defines the function. | ||||||
|  | - The first element on that list is a tuple with the function's name and arguments, and the second element is the function's body. | ||||||
|  | 
 | ||||||
|  | ## 3. Decode the secret message part from function definition | ||||||
|  | 
 | ||||||
|  | - Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets. | ||||||
|  | - The AST node that contains the function's name also contains the function's argument list as the third element. | ||||||
|  | - The arity of a function is the length of its argument list. | ||||||
|  | - There is a [built-in function in the `String` module][string-slice] that can get the first `n` characters from a string. | ||||||
|  | - A function without arguments written without parentheses will not have a list as argument but an atom. | ||||||
|  | 
 | ||||||
|  | ## 4. Fix the decoding for functions with guards | ||||||
|  | 
 | ||||||
|  | - Inspect the output of [`quote`][doc-quote] to familiarize yourself with how ASTs look like for specific code snippets. | ||||||
|  | - When a function has a guard, the third element in the tuple for the `:def/:defp` operation is a bit different. | ||||||
|  | - That third element is a list with two elements, the first one is the tuple for the `:when` operation, and the second one is the function's body. | ||||||
|  | - The `:when` operation's arguments are a two-element list, where the first argument is the function's name, and the second is the guard expression. | ||||||
|  | 
 | ||||||
|  | ## 5. Decode the full secret message | ||||||
|  | 
 | ||||||
|  | - Use the function `to_ast/1` that you implemented in the first task to create the AST. | ||||||
|  | - There is a [built-in function][macro-prewalk] that can visit each node in an AST with an accumulator. | ||||||
|  | - Use the function `decode_secret_message_part/2` that you implemented in previous tasks to prewalk the AST. | ||||||
|  | - To reverse the accumulator at the end and turn it into a string, refresh your knowledge of the [`Enum` module][enum]. | ||||||
|  | 
 | ||||||
|  | [getting-started-quote]: https://hexdocs.pm/elixir/quote-and-unquote.html | ||||||
|  | [doc-quote]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#quote/2 | ||||||
|  | [ast-intro-lucas]: https://dorgan.ar/posts/2021/04/the_elixir_ast/ | ||||||
|  | [doc-code-string-to-quoted]: https://hexdocs.pm/elixir/Code.html#string_to_quoted/2 | ||||||
|  | [string-slice]: https://hexdocs.pm/elixir/String.html#slice/2 | ||||||
|  | [macro-prewalk]: https://hexdocs.pm/elixir/Macro.html#prewalk/3 | ||||||
|  | [enum]: https://hexdocs.pm/elixir/Enum.html | ||||||
							
								
								
									
										137
									
								
								elixir/top-secret/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								elixir/top-secret/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,137 @@ | |||||||
|  | # Top Secret | ||||||
|  | 
 | ||||||
|  | Welcome to Top Secret on Exercism's Elixir Track. | ||||||
|  | If you need help running the tests or submitting your code, check out `HELP.md`. | ||||||
|  | If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) | ||||||
|  | 
 | ||||||
|  | ## Introduction | ||||||
|  | 
 | ||||||
|  | ## AST | ||||||
|  | 
 | ||||||
|  | The Abstract Syntax Tree (AST), also called a _quoted expression_, is a way to represent code as data. | ||||||
|  | 
 | ||||||
|  | Each node in the AST is a three-element tuple. | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | # AST representation of: | ||||||
|  | # 2 + 3 | ||||||
|  | {:+, [], [2, 3]} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The first element, an atom, is the operation. The second element, a keyword list, is the metadata. The third element is a list of arguments, which contains other nodes. Literal values such as integers, atoms, and strings are represented in the AST as themselves instead of three-element tuples. | ||||||
|  | 
 | ||||||
|  | ### Turning code into ASTs | ||||||
|  | 
 | ||||||
|  | Changing Elixir code to ASTs and ASTs back to code is part of the standard library. You can find functions for working with ASTs in the modules `Code` (e.g. to change a string with code to an AST) and `Macro` (e.g. to traverse the AST or change it to a string). | ||||||
|  | 
 | ||||||
|  | Note that all of the functions in the standard library use the name "quoted" to mean the AST (short for _quoted expression_). | ||||||
|  | 
 | ||||||
|  | The special form for turning code into an AST is called `quote`. It accepts a code block and returns its AST. | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | quote do | ||||||
|  |   2 + 3 - 1 | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | # => {:-, [], [ | ||||||
|  | #      {:+, [], [2, 3]}, | ||||||
|  | #      1 | ||||||
|  | #    ]} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Use cases | ||||||
|  | 
 | ||||||
|  | The ability to represent code as an AST is at the heart of metaprogramming in Elixir. _Macros_, which is a way to write Elixir code that produces Elixir code, work by returning ASTs as output. | ||||||
|  | 
 | ||||||
|  | Another use case for ASTs is static code analysis, like Exercism's own tool, the Analyzer, which you might already know as the little bot that leaves comments on your solutions. | ||||||
|  | 
 | ||||||
|  | ## 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 her secret messages: | ||||||
|  | 
 | ||||||
|  | - 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 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. | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | 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"]} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | ## 3. Decode the secret message part from function definition | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | 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. Fix the decoding for functions with guards | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | 
 | ||||||
|  | ```elixir | ||||||
|  | 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. 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" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Source | ||||||
|  | 
 | ||||||
|  | ### Created by | ||||||
|  | 
 | ||||||
|  | - @jiegillet | ||||||
|  | - @angelikatyborska | ||||||
							
								
								
									
										35
									
								
								elixir/top-secret/lib/top_secret.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								elixir/top-secret/lib/top_secret.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | defmodule TopSecret do | ||||||
|  |   def to_ast(string), do: Code.string_to_quoted!(string) | ||||||
|  | 
 | ||||||
|  |   def decode_secret_message_part({is_function?, _, arguments} = ast, acc) | ||||||
|  |       when is_function? in [:defp, :def] do | ||||||
|  |     {ast, [get_letters(arguments) | acc]} | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def decode_secret_message_part(ast, acc), do: {ast, acc} | ||||||
|  | 
 | ||||||
|  |   defp get_letters([{:when, _, arguments} | _]), do: get_letters(arguments) | ||||||
|  | 
 | ||||||
|  |   defp get_letters([{name, _, arguments} | _]) when is_list(arguments), | ||||||
|  |     do: letters_from_name_and_arguments(name, arguments) | ||||||
|  | 
 | ||||||
|  |   defp get_letters([{name, _, arguments} | _]) when is_atom(arguments), | ||||||
|  |     do: letters_from_name_and_arguments(name, []) | ||||||
|  | 
 | ||||||
|  |   defp letters_from_name_and_arguments(name, arguments) do | ||||||
|  |     name | ||||||
|  |     |> Atom.to_string() | ||||||
|  |     |> String.slice(0, length(arguments)) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def decode_secret_message(string) do | ||||||
|  |     {_ast, result} = | ||||||
|  |       string | ||||||
|  |       |> to_ast() | ||||||
|  |       |> Macro.prewalk([], &decode_secret_message_part/2) | ||||||
|  | 
 | ||||||
|  |     result | ||||||
|  |     |> Enum.reverse() | ||||||
|  |     |> Enum.join() | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										28
									
								
								elixir/top-secret/mix.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								elixir/top-secret/mix.exs
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								elixir/top-secret/test/test_helper.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								elixir/top-secret/test/test_helper.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | ExUnit.start() | ||||||
|  | ExUnit.configure(exclude: :pending, trace: true, seed: 0) | ||||||
							
								
								
									
										301
									
								
								elixir/top-secret/test/top_secret_test.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								elixir/top-secret/test/top_secret_test.exs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,301 @@ | |||||||
|  | defmodule TopSecretTest do | ||||||
|  |   use ExUnit.Case | ||||||
|  | 
 | ||||||
|  |   describe "to_ast/1" do | ||||||
|  |     @tag task_id: 1 | ||||||
|  |     test "handles an empty string" do | ||||||
|  |       string = "" | ||||||
|  |       ast = {:__block__, [], []} | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.to_ast(string) == ast | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 1 | ||||||
|  |     test "handles a small code snippet" do | ||||||
|  |       string = """ | ||||||
|  |       x = 7 | ||||||
|  |       y = x - 2 | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       ast = | ||||||
|  |         {:__block__, [], | ||||||
|  |          [ | ||||||
|  |            {:=, [line: 1], [{:x, [line: 1], nil}, 7]}, | ||||||
|  |            {:=, [line: 2], [{:y, [line: 2], nil}, {:-, [line: 2], [{:x, [line: 2], nil}, 2]}]} | ||||||
|  |          ]} | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.to_ast(string) == ast | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 1 | ||||||
|  |     test "handles a bigger code snippet" do | ||||||
|  |       string = """ | ||||||
|  |       defmodule List do | ||||||
|  |         @spec delete([], any) :: [] | ||||||
|  |         @spec delete([...], any) :: list | ||||||
|  |         def delete(list, element) | ||||||
|  |       end | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       ast = { | ||||||
|  |         :defmodule, | ||||||
|  |         [line: 1], | ||||||
|  |         [ | ||||||
|  |           {:__aliases__, [line: 1], [:List]}, | ||||||
|  |           [ | ||||||
|  |             do: { | ||||||
|  |               :__block__, | ||||||
|  |               [], | ||||||
|  |               [ | ||||||
|  |                 {:@, [line: 2], | ||||||
|  |                  [ | ||||||
|  |                    {:spec, [line: 2], | ||||||
|  |                     [{:"::", [line: 2], [{:delete, [line: 2], [[], {:any, [line: 2], nil}]}, []]}]} | ||||||
|  |                  ]}, | ||||||
|  |                 {:@, [line: 3], | ||||||
|  |                  [ | ||||||
|  |                    {:spec, [line: 3], | ||||||
|  |                     [ | ||||||
|  |                       {:"::", [line: 3], | ||||||
|  |                        [ | ||||||
|  |                          {:delete, [line: 3], [[{:..., [line: 3], nil}], {:any, [line: 3], nil}]}, | ||||||
|  |                          {:list, [line: 3], nil} | ||||||
|  |                        ]} | ||||||
|  |                     ]} | ||||||
|  |                  ]}, | ||||||
|  |                 {:def, [line: 4], | ||||||
|  |                  [{:delete, [line: 4], [{:list, [line: 4], nil}, {:element, [line: 4], nil}]}]} | ||||||
|  |               ] | ||||||
|  |             } | ||||||
|  |           ] | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.to_ast(string) == ast | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe "decode_secret_message_part/2" do | ||||||
|  |     @tag task_id: 2 | ||||||
|  |     test "returns the AST and accumulator unchanged (function call)" do | ||||||
|  |       string = "2 + 3" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["le", "mo"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == acc | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 2 | ||||||
|  |     test "returns the AST and accumulator unchanged (literal values)" do | ||||||
|  |       acc = ["abc"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(12, acc) | ||||||
|  |       assert actual_ast == 12 | ||||||
|  |       assert actual_acc == acc | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(true, acc) | ||||||
|  |       assert actual_ast == true | ||||||
|  |       assert actual_acc == acc | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(:ok, acc) | ||||||
|  |       assert actual_ast == :ok | ||||||
|  |       assert actual_acc == acc | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part("meh", acc) | ||||||
|  |       assert actual_ast == "meh" | ||||||
|  |       assert actual_acc == acc | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 2 | ||||||
|  |     test "appends a public function name to the accumulator" do | ||||||
|  |       string = "def fit(a, b, c), do: :scale" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["at"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["fit", "at"] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 2 | ||||||
|  |     test "appends a private function name to the accumulator" do | ||||||
|  |       string = "defp op(a, b), do: 2" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["e", "ced"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["op", "e", "ced"] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 2 | ||||||
|  |     test "ignores not top-level function definition" do | ||||||
|  |       string = """ | ||||||
|  |       defmodule Math do | ||||||
|  |         def sin(x), do: do_sin(x) | ||||||
|  |         defp do_sin(x), do: nil | ||||||
|  |       end | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = [] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == acc | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 3 | ||||||
|  |     test "function arity affects message part length" do | ||||||
|  |       string = "def adjust(a, b), do: :scale" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["re"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["ad", "re"] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 3 | ||||||
|  |     test "function arity 0 results in empty string" do | ||||||
|  |       string = "def adjust(), do: :scale" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["re"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["", "re"] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 3 | ||||||
|  |     test "function arity 0 and no parentheses results in empty string" do | ||||||
|  |       string = "def adjust, do: :scale" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["re"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["", "re"] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 4 | ||||||
|  |     test "works for public functions with a guard" do | ||||||
|  |       string = "def sign(a) when a >= 0, do: :+" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["e"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["s", "e"] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 4 | ||||||
|  |     test "works for private functions with a guard" do | ||||||
|  |       string = "defp do_sign(a) when a < 0, do: :-" | ||||||
|  |       ast = TopSecret.to_ast(string) | ||||||
|  |       acc = ["e"] | ||||||
|  | 
 | ||||||
|  |       {actual_ast, actual_acc} = TopSecret.decode_secret_message_part(ast, acc) | ||||||
|  |       assert actual_ast == ast | ||||||
|  |       assert actual_acc == ["d", "e"] | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   describe "decode_secret_message/1" do | ||||||
|  |     @tag task_id: 5 | ||||||
|  |     test "decodes a secret message from a single function definition" do | ||||||
|  |       code = """ | ||||||
|  |       defmodule Notebook do | ||||||
|  |         def note(notebook, text) do | ||||||
|  |           add_to_notebook(notebook, text, append: true) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       secret_message = "no" | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.decode_secret_message(code) == secret_message | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 5 | ||||||
|  |     test "decodes a secret message from a two function definitions" do | ||||||
|  |       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 | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       secret_message = "buy" | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.decode_secret_message(code) == secret_message | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 5 | ||||||
|  |     test "decodes a secret message from many function definitions" do | ||||||
|  |       code = """ | ||||||
|  |         defmodule TotallyNotTopSecret do | ||||||
|  |           def force(mass, acceleration), do: mass * acceleration | ||||||
|  |           def uniform(from, to), do: rand.uniform(to - from) + from | ||||||
|  |           def data(%{metadata: metadata}, _opts), do: model(metadata) | ||||||
|  |           defp model(metadata, _opts), do: metadata |> less_data |> Enum.reverse() |> Enum.take(3) | ||||||
|  |           defp less_data(data, _opts), do: Enum.reject(data, &is_nil/1) | ||||||
|  |         end | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       secret_message = "foundamole" | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.decode_secret_message(code) == secret_message | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 5 | ||||||
|  |     test "decodes a secret message without a module definition" do | ||||||
|  |       code = """ | ||||||
|  |       def force(mass, acceleration), do: mass * acceleration | ||||||
|  |       def uniform(from, to), do: rand.uniform(to - from) + from | ||||||
|  |       def data(%{metadata: metadata}, _opts), do: model(metadata) | ||||||
|  |       defp model(metadata, _opts), do: metadata |> less_data |> Enum.reverse() |> Enum.take(3) | ||||||
|  |       defp less_data(data, _opts), do: Enum.reject(data, &is_nil/1) | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       secret_message = "foundamole" | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.decode_secret_message(code) == secret_message | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     @tag task_id: 5 | ||||||
|  |     test "decodes another secret message from multiple modules" do | ||||||
|  |       code = """ | ||||||
|  |       defmodule IOHelpers do | ||||||
|  |         def inspect(x, opts), do: IO.inspect(x, opts) | ||||||
|  |         def vi_or_vim(_env, _preference), do: :vim | ||||||
|  |         def signal(pid, string), do: send(pid, {:signal, string}) | ||||||
|  |         def black(text, label), do: IO.ANSI.black <> label <> text <> IO.ANSI.reset() | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       defmodule TimeHelpers do | ||||||
|  |         defp est_to_cet(time), do: Time.add(time, 6 * 60 * 60) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       defmodule ASTHelpers do | ||||||
|  |         def submodule?(m, _f, _args), do: String.contains?(m, ".") | ||||||
|  |         def module({m, _f, _args}), do: m | ||||||
|  |         def arity(_m, _f, args), do: length(args) | ||||||
|  |         defp nested?(x, y) when is_list(y), do: x in y | ||||||
|  |       end | ||||||
|  |       """ | ||||||
|  | 
 | ||||||
|  |       secret_message = "invisiblesubmarine" | ||||||
|  | 
 | ||||||
|  |       assert TopSecret.decode_secret_message(code) == secret_message | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user