Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PacmanRules #30

Merged
merged 2 commits into from
Nov 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion lib/elixir_analyzer/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ defmodule ElixirAnalyzer.Constants do
two_fer_wrong_specification: "elixir.two-fer.wrong_specification",
two_fer_use_function_level_guard: "elixir.two_fer.use_function_level_guard",
two_fer_use_of_aux_functions: "elixir.two_fer.use_of_aux_functions",
two_fer_use_of_function_header: "elixir.two_fer.use_of_function_header"
two_fer_use_of_function_header: "elixir.two_fer.use_of_function_header",
pacman_rules_use_strictly_boolean_operators:
"elixir.pacman_rules.use_strictly_boolean_operators"
]

for {constant, markdown} <- @constants do
Expand Down
40 changes: 40 additions & 0 deletions lib/elixir_analyzer/exercise_test/pacman_rules.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule ElixirAnalyzer.ExerciseTest.PacmanRules do
@dialyzer generated: true
@moduledoc """
This is an exercise analyzer extension module for the concept exercise Pacman Rules
"""

alias ElixirAnalyzer.Constants

use ElixirAnalyzer.ExerciseTest

feature "requires using strictly boolean operators" do
find :none
on_fail :disapprove
comment Constants.pacman_rules_use_strictly_boolean_operators()

form do
_ignore && _ignore
end

form do
Kernel.&&(_ignore, _ignore)
end

form do
_ignore || _ignore
end

form do
Kernel.||(_ignore, _ignore)
end

form do
!_ignore
end

form do
Kernel.!(_ignore)
end
end
end
104 changes: 104 additions & 0 deletions test/elixir_analyzer/exercise_test/pacman_rules_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule ElixirAnalyzer.ExerciseTest.PacmanRulesTest do
use ElixirAnalyzer.ExerciseTestCase,
exercise_test_module: ElixirAnalyzer.ExerciseTest.PacmanRules

test_exercise_analysis "example solution",
comments: [],
status: :approve do
defmodule Rules do
def eat_ghost?(power_pellet_active, touching_ghost) do
power_pellet_active and touching_ghost
end

def score?(touching_power_pellet, touching_dot) do
touching_power_pellet or touching_dot
end

def lose?(power_pellet_active, touching_ghost) do
not power_pellet_active and touching_ghost
end

def win?(has_eaten_all_dots, power_pellet_active, touching_ghost) do
has_eaten_all_dots and not lose?(power_pellet_active, touching_ghost)
end
end
end

describe "requires strictly boolean operators" do
test_exercise_analysis "detects &&",
comments_include: [Constants.pacman_rules_use_strictly_boolean_operators()] do
[
defmodule Rules do
def eat_ghost?(power_pellet_active, touching_ghost) do
power_pellet_active && touching_ghost
end
end,
defmodule Rules do
def eat_ghost?(power_pellet_active, touching_ghost) do
power_pellet_active and touching_ghost
end

def lose?(power_pellet_active, touching_ghost) do
not power_pellet_active && touching_ghost
end
end,
defmodule Rules do
def eat_ghost?(power_pellet_active, touching_ghost) do
Kernel.&&(power_pellet_active, touching_ghost)
end
end
]
end

test_exercise_analysis "detects ||",
comments_include: [Constants.pacman_rules_use_strictly_boolean_operators()] do
[
defmodule Rules do
def score?(touching_power_pellet, touching_dot) do
touching_power_pellet || touching_dot
end
end,
defmodule Rules do
def score?(touching_power_pellet, touching_dot) do
Kernel.||(touching_power_pellet, touching_dot)
end
end
]
end

test_exercise_analysis "detects !",
comments_include: [Constants.pacman_rules_use_strictly_boolean_operators()] do
[
defmodule Rules do
def lose?(power_pellet_active, touching_ghost) do
!power_pellet_active and touching_ghost
end
end,
defmodule Rules do
def lose?(power_pellet_active, touching_ghost) do
touching_ghost and !power_pellet_active
end
end,
defmodule Rules do
def lose?(power_pellet_active, touching_ghost) do
not power_pellet_active and touching_ghost
end

def win?(has_eaten_all_dots, power_pellet_active, touching_ghost) do
has_eaten_all_dots and !lose?(power_pellet_active, touching_ghost)
end
end,
defmodule Rules do
def eat_ghost?(power_pellet_active, touching_ghost) do
!!power_pellet_active and !!touching_ghost
end
end,
defmodule Rules do
def lose?(power_pellet_active, touching_ghost) do
Kernel.!(power_pellet_active) and touching_ghost
end
end
]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first three test cases fail, while the last two work and I have no idea why. I would think I cannot go wrong with such a simple form:

form do
  !_ignore
end

But apparently I can...

  1) test requires strictly boolean operators detects ! 1 (ElixirAnalyzer.ExerciseTest.PacmanRulesTest)
     test/elixir_analyzer/exercise_test/pacman_rules_test.exs:72
     Assertion with in failed
     code:  assert comment in result.comments
     left:  "elixir.pacman_rules.use_strictly_boolean_operators"
     right: []
     stacktrace:
       (elixir 1.10.4) lib/enum.ex:783: Enum."-each/2-lists^foreach/1-0-"/2
       (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
       test/elixir_analyzer/exercise_test/pacman_rules_test.exs:72: (test)

  * test requires strictly boolean operators detects ! 2 (0.1ms)

  2) test requires strictly boolean operators detects ! 2 (ElixirAnalyzer.ExerciseTest.PacmanRulesTest)
     test/elixir_analyzer/exercise_test/pacman_rules_test.exs:77
     Assertion with in failed
     code:  assert comment in result.comments
     left:  "elixir.pacman_rules.use_strictly_boolean_operators"
     right: []
     stacktrace:
       (elixir 1.10.4) lib/enum.ex:783: Enum."-each/2-lists^foreach/1-0-"/2
       (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
       test/elixir_analyzer/exercise_test/pacman_rules_test.exs:77: (test)

  * test requires strictly boolean operators detects ! 3 (0.1ms)

  3) test requires strictly boolean operators detects ! 3 (ElixirAnalyzer.ExerciseTest.PacmanRulesTest)
     test/elixir_analyzer/exercise_test/pacman_rules_test.exs:82
     Assertion with in failed
     code:  assert comment in result.comments
     left:  "elixir.pacman_rules.use_strictly_boolean_operators"
     right: []
     stacktrace:
       (elixir 1.10.4) lib/enum.ex:783: Enum."-each/2-lists^foreach/1-0-"/2
       (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
       test/elixir_analyzer/exercise_test/pacman_rules_test.exs:82: (test)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting behavior. Off the top of my head I don't know either.

AST of the form:

{:__block__, [], [{:!, [line: 1], [{:_ignore, [line: 1], nil}]}]}

# Which should get reduced by the analyzer to:
{:!, _, [_]}

AST of the module:

{:defmodule, [line: 1],
 [
   {:__aliases__, [line: 1], [:Rules]},
   [
     do: {:def, [line: 2],
      [
        {:lose?, [line: 2],
         [
           {:power_pellet_active, [line: 2], nil},
           {:touching_ghost, [line: 2], nil}
         ]},
        [
          do: {:and, [line: 3],
           [
             {:!, [line: 3], [{:power_pellet_active, [line: 3], nil}]},   # Which then should match here
             {:touching_ghost, [line: 3], nil}
           ]}
        ]
      ]}
   ]
 ]}

I would suspect that somewhere when the form is compiled, it isn't forming the correct pattern. Maybe I didn't account for unary functions with the pattern matching? Maybe it is making the wrong pattern to match against?

Copy link
Contributor

@neenjaw neenjaw Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting an inspect after this line here would let you output the pattern generated by each form when you compile.

The output might be a bit noisy, so it might be worthwhile to comment out other cases and temporarily remove the other analyzer modules that might get compiled to isolate the pattern generated by the form.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you're saying that

{:__block__, [], [{:!, [line: 1], [{:_ignore, [line: 1], nil}]}]}

should get reduced to

{:!, _, [_]}

But in reality, it gets reduced to:

[{:!, _, [_]}]

So it can only ever match if it's used on its own in a single line of code. This is unlike regular function calls, which aren't wrapped in a list and thus behave like expected:

form do
  foo(_ignore)
end

# gets reduced to
{:foo, _, [_]}

I'm not sure at the moment how to work around that...

Copy link
Contributor

@neenjaw neenjaw Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't if that is the pattern that is made by the analyzer. it is a missed case in my design. It should have resulted as I said. So now we know where the bug is. 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay, bug resolved! 👢 🪲

end
end
end