From 89dac5bb05f5fca6bc7732535f75d6b29a5bd40a Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Sun, 13 Mar 2016 14:56:43 +0000 Subject: [PATCH 1/6] ASTMangler supports multiple placeholders --- lib/ast_mangler.ex | 23 ++++++++++++++++++++--- test/ast_mangler_test.exs | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/ast_mangler.ex b/lib/ast_mangler.ex index eed4281..ac5f253 100644 --- a/lib/ast_mangler.ex +++ b/lib/ast_mangler.ex @@ -1,8 +1,25 @@ defmodule ASTMangler do + + def expand([do: remainder], replacements) when is_list(replacements) do + [do: expand(remainder, replacements)] + end + def expand(ast, replacements) when is_list(replacements) do + {node, _acc} = Macro.prewalk(ast, replacements, fn(node, acc) -> pre(node, acc) end) + node + end def expand(ast, replacement) do - Macro.prewalk(ast, fn(node) -> update(node, replacement) end) + expand(ast, [replacement]) end - def update(:__, replacement), do: replacement - def update(node, _), do: node + def pre(:__, [first | remainder]), do: {first, remainder} + def pre(node, acc), do: {node, acc} + + + def count(ast) do + {_node, acc} = Macro.prewalk(ast, 0, fn(node, acc) -> count(node, acc) end) + acc + end + + def count(:__, acc), do: {node, acc+1} + def count(node, acc), do: {node, acc} end diff --git a/test/ast_mangler_test.exs b/test/ast_mangler_test.exs index 9cb66b7..223fdec 100644 --- a/test/ast_mangler_test.exs +++ b/test/ast_mangler_test.exs @@ -28,4 +28,22 @@ defmodule ASTManglerTest do assert [do: {:assert, [line: 5], [{:==, [line: 5], [true, true]}]}] == ASTMangler.expand(ast, true) end + + test "multiple arguments" do + ast = [do: {:assert, [line: 5], [{:==, [line: 5], [:__, :__]}]}] + + assert [do: {:assert, [line: 5], [{:==, [line: 5], [true, false]}]}] == ASTMangler.expand(ast, [true, false]) + end + + test "counts simple blanks" do + ast = quote do: 1 + :__ + + assert ASTMangler.count(ast) == 1 + end + + test "counts multiple blanks" do + ast = [do: {:assert, [line: 5], [{:==, [line: 5], [:__, :__]}]}] + + assert ASTMangler.count(ast) == 2 + end end From 4897527dab4d329a6c399be4c9d25698b8d47cd4 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Thu, 17 Mar 2016 23:13:35 +0000 Subject: [PATCH 2/6] Use a tuple to get indexed access to a arguments --- lib/koans.ex | 10 ++++++++-- test/ast_mangler_test.exs | 5 +++++ test/sample_koan_test.exs | 8 ++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 test/sample_koan_test.exs diff --git a/lib/koans.ex b/lib/koans.ex index c313581..9331728 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -1,7 +1,8 @@ defmodule Koans do defmacro koan(name, body) do compiled_name = String.to_atom(name) - mangled_body = ASTMangler.expand(body, quote do: answer) + expanded_answers = expand(ASTMangler.count(body)) + mangled_body = ASTMangler.expand(body, expanded_answers) quote do @koans unquote(compiled_name) def unquote(compiled_name)() do @@ -13,13 +14,18 @@ defmodule Koans do end end - def unquote(compiled_name)(answer) do + def unquote(compiled_name)(answer) when is_list(answer) do + converted = List.to_tuple(answer) unquote(mangled_body) :ok end end end + def expand(amount) do + Enum.map(0..amount, fn (idx) -> {:elem, [context: Koans, import: Kernel], [{:converted, [], Koans}, idx]} end) + end + defmacro __using__(_opts) do quote do @compile :nowarn_unused_vars diff --git a/test/ast_mangler_test.exs b/test/ast_mangler_test.exs index 223fdec..4478ac4 100644 --- a/test/ast_mangler_test.exs +++ b/test/ast_mangler_test.exs @@ -8,6 +8,11 @@ defmodule ASTManglerTest do assert {:+, [context: ASTManglerTest, import: Kernel], [1, 37]} == mangled end + test "Work with multiple different replacements" do + [koan | _] = SampleKoan.all_koans + assert :ok == apply(SampleKoan, koan, [{:multiple, [3,4]}]) + end + def complex_example do [head | tail] = [1,2,3,4] diff --git a/test/sample_koan_test.exs b/test/sample_koan_test.exs new file mode 100644 index 0000000..d48e340 --- /dev/null +++ b/test/sample_koan_test.exs @@ -0,0 +1,8 @@ +defmodule SampleKoan do + use Koans + + koan "thinking more than once" do + assert 3 == :__ + assert 4 == :__ + end +end From 023bb62e015f4ff28155af0fee25dee13194cb5f Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Fri, 18 Mar 2016 12:46:13 +0000 Subject: [PATCH 3/6] Generate test methods depending on the number of arguments. --- lib/koans.ex | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/koans.ex b/lib/koans.ex index 9331728..32fdb4d 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -1,10 +1,12 @@ defmodule Koans do defmacro koan(name, body) do compiled_name = String.to_atom(name) - expanded_answers = expand(ASTMangler.count(body)) - mangled_body = ASTMangler.expand(body, expanded_answers) + number_of_args = ASTMangler.count(body) quote do @koans unquote(compiled_name) + + generate_test_method(unquote(compiled_name), unquote(number_of_args), unquote(body)) + def unquote(compiled_name)() do try do unquote(body) @@ -13,17 +15,34 @@ defmodule Koans do e in _ -> e end end + end + end - def unquote(compiled_name)(answer) when is_list(answer) do - converted = List.to_tuple(answer) - unquote(mangled_body) + defmacro generate_test_method(_name, 0, _body), do: false + defmacro generate_test_method(name, 1, body) do + single_var = ASTMangler.expand(body, Macro.var(:answer, Koans)) + quote do + def unquote(name)(answer) do + converted = {answer} + unquote(single_var) + :ok + end + end + end + defmacro generate_test_method(name, number_of_args, body) do + answer_placeholders = expand(number_of_args) + multi_var = ASTMangler.expand(body, answer_placeholders) + quote do + def unquote(name)({:multiple, answers}) do + converted = List.to_tuple(answers) + unquote(multi_var) :ok end end end def expand(amount) do - Enum.map(0..amount, fn (idx) -> {:elem, [context: Koans, import: Kernel], [{:converted, [], Koans}, idx]} end) + Enum.map(0..amount, fn (idx) -> quote do: elem(converted, unquote(idx)) end) end defmacro __using__(_opts) do From 994579f4974a07c5014d2c50bda1f4522b0e762a Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Sun, 20 Mar 2016 19:18:26 +0000 Subject: [PATCH 4/6] Rename and clean the ASTMangler to Blanks --- lib/ast_mangler.ex | 25 ------------------- lib/blanks.ex | 21 ++++++++++++++++ lib/koans.ex | 10 ++++---- .../{ast_mangler_test.exs => blanks_test.exs} | 14 +++++------ 4 files changed, 33 insertions(+), 37 deletions(-) delete mode 100644 lib/ast_mangler.ex create mode 100644 lib/blanks.ex rename test/{ast_mangler_test.exs => blanks_test.exs} (75%) diff --git a/lib/ast_mangler.ex b/lib/ast_mangler.ex deleted file mode 100644 index ac5f253..0000000 --- a/lib/ast_mangler.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule ASTMangler do - - def expand([do: remainder], replacements) when is_list(replacements) do - [do: expand(remainder, replacements)] - end - def expand(ast, replacements) when is_list(replacements) do - {node, _acc} = Macro.prewalk(ast, replacements, fn(node, acc) -> pre(node, acc) end) - node - end - def expand(ast, replacement) do - expand(ast, [replacement]) - end - - def pre(:__, [first | remainder]), do: {first, remainder} - def pre(node, acc), do: {node, acc} - - - def count(ast) do - {_node, acc} = Macro.prewalk(ast, 0, fn(node, acc) -> count(node, acc) end) - acc - end - - def count(:__, acc), do: {node, acc+1} - def count(node, acc), do: {node, acc} -end diff --git a/lib/blanks.ex b/lib/blanks.ex new file mode 100644 index 0000000..248803f --- /dev/null +++ b/lib/blanks.ex @@ -0,0 +1,21 @@ +defmodule Blanks do + def replace(ast, replacement) when not is_list(replacement), do: replace(ast, [replacement]) + def replace([do: ast], replacements), do: [do: replace(ast, replacements)] + def replace(ast, replacements) do + ast + |> Macro.prewalk(replacements, &pre/2) + |> elem(0) + end + + def pre(:__, [first | remainder]), do: {first, remainder} + def pre(node, acc), do: {node, acc} + + def count(ast) do + ast + |> Macro.prewalk(0, &count/2) + |> elem(1) + end + + def count(:__, acc), do: {node, acc+1} + def count(node, acc), do: {node, acc} +end diff --git a/lib/koans.ex b/lib/koans.ex index 32fdb4d..f54514e 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -1,7 +1,7 @@ defmodule Koans do defmacro koan(name, body) do compiled_name = String.to_atom(name) - number_of_args = ASTMangler.count(body) + number_of_args = Blanks.count(body) quote do @koans unquote(compiled_name) @@ -20,7 +20,7 @@ defmodule Koans do defmacro generate_test_method(_name, 0, _body), do: false defmacro generate_test_method(name, 1, body) do - single_var = ASTMangler.expand(body, Macro.var(:answer, Koans)) + single_var = Blanks.replace(body, Macro.var(:answer, Koans)) quote do def unquote(name)(answer) do converted = {answer} @@ -30,8 +30,8 @@ defmodule Koans do end end defmacro generate_test_method(name, number_of_args, body) do - answer_placeholders = expand(number_of_args) - multi_var = ASTMangler.expand(body, answer_placeholders) + answer_placeholders = create_vars(number_of_args) + multi_var = Blanks.replace(body, answer_placeholders) quote do def unquote(name)({:multiple, answers}) do converted = List.to_tuple(answers) @@ -41,7 +41,7 @@ defmodule Koans do end end - def expand(amount) do + def create_vars(amount) do Enum.map(0..amount, fn (idx) -> quote do: elem(converted, unquote(idx)) end) end diff --git a/test/ast_mangler_test.exs b/test/blanks_test.exs similarity index 75% rename from test/ast_mangler_test.exs rename to test/blanks_test.exs index 4478ac4..9423e70 100644 --- a/test/ast_mangler_test.exs +++ b/test/blanks_test.exs @@ -1,11 +1,11 @@ -defmodule ASTManglerTest do +defmodule BlanksTest do use ExUnit.Case, async: true test "simple replacement" do ast = quote do: 1 + :__ - mangled = ASTMangler.expand(ast, 37) - assert {:+, [context: ASTManglerTest, import: Kernel], [1, 37]} == mangled + mangled = Blanks.replace(ast, 37) + assert {:+, [context: BlanksTest, import: Kernel], [1, 37]} == mangled end test "Work with multiple different replacements" do @@ -31,24 +31,24 @@ defmodule ASTManglerTest do test "complex example" do ast = [do: {:assert, [line: 5], [{:==, [line: 5], [true, :__]}]}] - assert [do: {:assert, [line: 5], [{:==, [line: 5], [true, true]}]}] == ASTMangler.expand(ast, true) + assert [do: {:assert, [line: 5], [{:==, [line: 5], [true, true]}]}] == Blanks.replace(ast, true) end test "multiple arguments" do ast = [do: {:assert, [line: 5], [{:==, [line: 5], [:__, :__]}]}] - assert [do: {:assert, [line: 5], [{:==, [line: 5], [true, false]}]}] == ASTMangler.expand(ast, [true, false]) + assert [do: {:assert, [line: 5], [{:==, [line: 5], [true, false]}]}] == Blanks.replace(ast, [true, false]) end test "counts simple blanks" do ast = quote do: 1 + :__ - assert ASTMangler.count(ast) == 1 + assert Blanks.count(ast) == 1 end test "counts multiple blanks" do ast = [do: {:assert, [line: 5], [{:==, [line: 5], [:__, :__]}]}] - assert ASTMangler.count(ast) == 2 + assert Blanks.count(ast) == 2 end end From f564927751319ab36faaf87d91349322ee77eee8 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Sun, 20 Mar 2016 19:36:57 +0000 Subject: [PATCH 5/6] Make private functions private --- lib/blanks.ex | 8 ++++---- lib/koans.ex | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/blanks.ex b/lib/blanks.ex index 248803f..7ed8614 100644 --- a/lib/blanks.ex +++ b/lib/blanks.ex @@ -7,8 +7,8 @@ defmodule Blanks do |> elem(0) end - def pre(:__, [first | remainder]), do: {first, remainder} - def pre(node, acc), do: {node, acc} + defp pre(:__, [first | remainder]), do: {first, remainder} + defp pre(node, acc), do: {node, acc} def count(ast) do ast @@ -16,6 +16,6 @@ defmodule Blanks do |> elem(1) end - def count(:__, acc), do: {node, acc+1} - def count(node, acc), do: {node, acc} + defp count(:__, acc), do: {node, acc+1} + defp count(node, acc), do: {node, acc} end diff --git a/lib/koans.ex b/lib/koans.ex index f54514e..e4c26c6 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -41,7 +41,7 @@ defmodule Koans do end end - def create_vars(amount) do + defp create_vars(amount) do Enum.map(0..amount, fn (idx) -> quote do: elem(converted, unquote(idx)) end) end From 6e8845cb5145cdf6997e81f5258c5921ada9d0ff Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Sun, 20 Mar 2016 16:23:44 +0000 Subject: [PATCH 6/6] Assert on result of Task.start_link/1 --- lib/koans/12_task.ex | 3 ++- test/koans_harness_test.exs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/koans/12_task.ex b/lib/koans/12_task.ex index bdfcfb1..26970ee 100644 --- a/lib/koans/12_task.ex +++ b/lib/koans/12_task.ex @@ -8,7 +8,8 @@ defmodule Tasks do end koan "if you don't need a result, use start_link/1" do - {:ok, _pid} = Task.start_link(fn -> 1+1 end) + {result, _pid} = Task.start_link(fn -> 1+1 end) + assert result == :__ end koan "yield returns nothing if the task isn't done yet" do diff --git a/test/koans_harness_test.exs b/test/koans_harness_test.exs index 5db07bb..c86ee4d 100644 --- a/test/koans_harness_test.exs +++ b/test/koans_harness_test.exs @@ -222,7 +222,7 @@ defmodule KoansHarnessTest do test "Tasks" do answers = [ 10, - :todo, + :ok, nil, nil, 9,