diff --git a/lib/ast_mangler.ex b/lib/ast_mangler.ex new file mode 100644 index 0000000..eed4281 --- /dev/null +++ b/lib/ast_mangler.ex @@ -0,0 +1,8 @@ +defmodule ASTMangler do + def expand(ast, replacement) do + Macro.prewalk(ast, fn(node) -> update(node, replacement) end) + end + + def update(:__, replacement), do: replacement + def update(node, _), do: node +end diff --git a/lib/koans.ex b/lib/koans.ex index 0cd67fe..c313581 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -1,6 +1,7 @@ defmodule Koans do defmacro koan(name, body) do compiled_name = String.to_atom(name) + mangled_body = ASTMangler.expand(body, quote do: answer) quote do @koans unquote(compiled_name) def unquote(compiled_name)() do @@ -11,6 +12,11 @@ defmodule Koans do e in _ -> e end end + + def unquote(compiled_name)(answer) do + unquote(mangled_body) + :ok + end end end diff --git a/lib/koans/01_equalities.ex b/lib/koans/01_equalities.ex index f636f25..c9b85bc 100644 --- a/lib/koans/01_equalities.ex +++ b/lib/koans/01_equalities.ex @@ -2,22 +2,22 @@ defmodule Equalities do use Koans koan "We shall contemplate truth by testing reality, via equality" do - assert true == true + assert true == :__ end koan "To understand reality, we must compare our expectations against reality" do - assert 2 == 1 + 1 + assert 2 == 1 + :__ end koan "Some things may appear different, but be the same" do - assert 1 == 2 / 2 + assert 1 == 2 / :__ end koan "Something is not equal to nothing" do - assert !(1 == nil) + assert !(:__ == nil) end koan "When things cannot be equal, they must be different" do - refute :something == 4 + refute :__ == 4 end end diff --git a/lib/koans/02_lists.ex b/lib/koans/02_lists.ex index f8041b3..aa789ca 100644 --- a/lib/koans/02_lists.ex +++ b/lib/koans/02_lists.ex @@ -2,67 +2,70 @@ defmodule Lists do use Koans koan "We can see what is ahead" do - assert List.first([1, 2, 3]) == 1 + assert List.first([1, 2, 3]) == :__ end koan "Checking what's trailing is also simple" do - assert List.last([1, 2, 3]) == 3 + assert List.last([1, 2, 3]) == :__ end koan "Diversity is embraced" do - assert [1, 2] ++ [:a, "b"] == [1, 2, :a, "b"] + assert [1, 2] ++ [:a, "b"] == :__ end koan "Things can evolve" do - assert [1, 2, 3] -- [3] == [1, 2] + assert [1, 2, 3] -- [3] == :__ end koan "Evolution can have different forms" do - assert List.delete([1, 2, 2, 3], 2) == [1, 2, 3] + assert List.delete([1, 2, 2, 3], 2) == :__ end koan "Precision is also valued" do - assert List.delete_at([1, 2, 3], 1) == [1, 3] + assert List.delete_at([1, 2, 3], 1) == :__ end koan "Replication is also possible" do - assert List.duplicate("life", 3) == ["life", "life", "life"] + assert List.duplicate("life", 3) == :__ end koan "Sometimes levelling the playing field is desired" do - assert List.flatten([1, [2, 3], 4, [5]]) == [1, 2, 3, 4, 5] + assert List.flatten([1, [2, 3], 4, [5]]) == :__ end koan "Same rules apply to new members that arrive late" do - assert List.flatten([1, [2, 3]], [4]) == [1, 2, 3, 4] + assert List.flatten([1, [2, 3]], [4]) == :__ end koan "Order can also be specified for new members" do - assert List.insert_at([1, 2, 3], 1, 4) == [1, 4, 2, 3] + assert List.insert_at([1, 2, 3], 1, 4) == :__ end koan "We can replace things at specified positions" do - assert List.replace_at([1, 2, 3], 0, 10) == [10, 2, 3] + assert List.replace_at([1, 2, 3], 0, 10) == :__ end koan "Replacing something which is not" do - assert List.replace_at([1, 2, 3], 10, 0) == [1, 2, 3] + assert List.replace_at([1, 2, 3], 10, 0) == :__ end koan "Order is bound by nature's laws" do - assert List.insert_at([1, 2, 3], 10, 4) == [1, 2, 3, 4] - assert List.insert_at([1, 2, 3], -1, 4) == [1, 2, 3, 4] + assert List.insert_at([1, 2, 3], 10, 4) == :__ + end + + koan "Sometimes its faster to loop around back" do + assert List.insert_at([1, 2, 3], -1, 4) == :__ end koan "We can also transform ourselves completely" do - assert List.to_tuple([1, 2, 3]) == {1, 2, 3} + assert List.to_tuple([1, 2, 3]) == :__ end koan "Wrapping other values is a handy option" do - assert List.wrap("value") == ["value"] + assert List.wrap("value") == :__ end koan "Zipping can be a useful operation" do - assert List.zip([[1, 2], [3, 4], [5, 6]]) == [{1, 3, 5}, {2, 4, 6}] + assert List.zip([[1, 2], [3, 4], [5, 6]]) == :__ end end diff --git a/lib/runner.ex b/lib/runner.ex index 279686a..2d05c27 100644 --- a/lib/runner.ex +++ b/lib/runner.ex @@ -30,7 +30,11 @@ defmodule Runner do koans = module.all_koans passed = Enum.take_while(koans, fn(name) -> - run_koan(module, name) == :passed + case run_koan(module, name) do + :passed -> true + {:failed, error, module, name} -> Display.show_failure(error, module, name) + false + end end) if Enum.count(koans) == Enum.count(passed) do @@ -40,12 +44,10 @@ defmodule Runner do end end - def run_koan(module, name) do - case apply(module, name, []) do + def run_koan(module, name, args \\ []) do + case apply(module, name, args) do :ok -> :passed - error -> - Display.show_failure(error, module, name) - :failed + error -> {:failed, error, module, name} end end end diff --git a/test/ast_mangler_test.exs b/test/ast_mangler_test.exs new file mode 100644 index 0000000..9cb66b7 --- /dev/null +++ b/test/ast_mangler_test.exs @@ -0,0 +1,31 @@ +defmodule ASTManglerTest 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 + end + + def complex_example do + [head | tail] = [1,2,3,4] + + assert head == 1 + assert tail == [2,3,4] + end + + def foo(answer) do + if answer == :yes do + :bar + else + :batz + end + end + + 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) + end +end diff --git a/test/koans_harness_test.exs b/test/koans_harness_test.exs new file mode 100644 index 0000000..192cb17 --- /dev/null +++ b/test/koans_harness_test.exs @@ -0,0 +1,46 @@ +defmodule KoansHarnessTest do + use ExUnit.Case + + test "Equalities" do + answers = [true, + 1, + 2, + 1, + :something] + + test_all(Equalities, answers) + end + + test "Lists" do + answers = [1, + 3, + [1,2,:a,"b"], + [1,2], + [1,2,3], + [1,3], + ["life", "life", "life"], + [1, 2, 3, 4, 5], + [1, 2, 3, 4], + [1, 4, 2, 3], + [10, 2, 3], + [1, 2, 3], + [1, 2, 3, 4], + [1, 2, 3, 4], + {1, 2, 3}, + ["value"], + [{1, 3, 5}, {2, 4, 6}] + ] + + test_all(Lists, answers) + end + + def test_all(module, answers) do + module.all_koans + |> Enum.zip(answers) + |> run_all(module) + end + + def run_all(pairs, module) do + Enum.map(pairs, fn ({koan, answer}) -> Runner.run_koan(module, koan, [answer]) end) + end +end