Repository: hedyorg/hedy

Issue:
Never suggest the command that is found
This makes kids hate computers :)

![image](https://user-images.githubusercontent.com/1003685/109135419-56f9be80-7757-11eb-963b-ddb87d288227.png)



Discussion:

Resolving patch:
diff --git a/app.py b/app.py
--- a/app.py
+++ b/app.py
@@ -132,10 +132,12 @@ def parse():
             elif E.args[0] == "Parse":
                 error_template = hedy_errors[E.error_code]
                 # Localize the names of characters
-                # Localize the names of characters
                 if 'character_found' in E.arguments:
                     E.arguments['character_found'] = hedy_errors[E.arguments['character_found']]
                 response["Error"] = error_template.format(**E.arguments)
+            elif E.args[0] == "Unquoted Text":
+                error_template = hedy_errors[E.error_code]
+                response["Error"] = error_template.format(**E.arguments)
             else:
                 error_template = hedy_errors[E.error_code]
                 response["Error"] = error_template.format(**E.arguments)
diff --git a/hedy.py b/hedy.py
--- a/hedy.py
+++ b/hedy.py
@@ -43,12 +43,16 @@ def closest_command(invalid_command, known_commands):
     # If not found, search for partial match of know commands
     if min_command == '':
         min_command = closest_command_with_min_distance(invalid_command, known_commands)
-    
+
+    # Check if we are not returning the found command
+    # In that case we have no suggestion
+    # This is to prevent "print is not a command in Hedy level 3, did you mean print?" error message
+
+    if min_command == invalid_command:
+        return None
+
     return min_command
 
-#
-#  closest_command_with_min_distance()
-#
 
 def closest_command_with_min_distance(command, commands):
     #simple string distance, could be more sophisticated MACHINE LEARNING!
@@ -253,12 +257,16 @@ def invalid(self, args):
         return False, args[0][1]
 
 class IsValid(Filter):
-    # all rules are valid except for the invalid production rule
+    # all rules are valid except for the "Invalid" production rule
     # this function is used to generate more informative error messages
     # tree is transformed to a node of [Bool, args, linenumber]
 
     #would be lovely if there was some sort of default rule! Not sure Lark supports that
 
+    # note! If this function errors out with:
+    # Error trying to process rule "program" Tree object not subscriptable
+    # that means you added a production rule but did not add a method for the rule here
+
     def ask(self, args):
         return all_arguments_true(args)
     def print(self, args):
@@ -277,9 +285,12 @@ def addition(self, args):
         return all(args), ''.join([c for c in args])
 
     def invalid_space(self, args):
-        # return space to indicate that line start in a space
+        # return space to indicate that line starts in a space
         return False, " "
 
+    def print_nq(self, args):
+        # return error source to indicate what went wrong
+        return False, "print without quotes"
 
 class IsComplete(Filter):
     # print, ask an echo can miss arguments and then are not complete
@@ -288,10 +299,16 @@ class IsComplete(Filter):
 
     #would be lovely if there was some sort of default rule! Not sure Lark supports that
 
+    # note! If this function errors out with:
+    # Error trying to process rule "program" Tree object not subscriptable
+    # that means you added a production rule but did not add a method for the rule here
+
     def ask(self, args):
         return args != [], 'ask'
     def print(self, args):
         return args != [], 'print'
+    def print_nq(self, args):
+        return args != [], 'print level 2'
     def echo(self, args):
         #echo may miss an argument
         return True, 'echo'
@@ -386,6 +403,8 @@ def text(self, args):
     def print(self, args):
         #opzoeken is nu niet meer nodig
         return "print(" + '+'.join(args) + ')'
+    def print_nq(self, args):
+        return ConvertToPython_2.print(self, args)
 
 def indent(s):
     lines = s.split('\n')
@@ -683,6 +702,8 @@ def transpile(input_string, level):
     try:
         return transpile_inner(input_string, level)
     except Exception as E:
+        # This is the 'fall back' transpilation
+        # that should surely be improved!!
         # we retry HedyExceptions of the type Parse (and Lark Errors) but we raise Invalids
         if E.args[0] == 'Parse':
             #try 1 level lower
@@ -788,9 +809,15 @@ def transpile_inner(input_string, level):
             if fixed_code != input_string: #only if we have made a successful fix
                 result = transpile_inner(fixed_code, level)
             raise HedyException('Invalid Space', level=level, line_number=line, fixed_code = result)
+        elif is_valid[1] == 'print without quotes':
+            # grammar rule is ignostic of line number so we can't easily return that here
+            raise HedyException('Unquoted Text', level=level)
         else:
             invalid_command = is_valid[1]
             closest = closest_command(invalid_command, commands_per_level[level])
+            if closest == None: #we couldn't find a suggestion because the command itself was found
+                # clearly the error message here should be better or it should be a different one!
+                raise HedyException('Parse', level=level, location=["?", "?"], characters_expected="?")
             raise HedyException('Invalid', invalid_command=invalid_command, level=level, guessed_command=closest)
 
     is_complete = IsComplete().transform(program_root)


Test patch:
diff --git a/tests/tests_distance_algo.py b/tests/tests_distance_algo.py
--- a/tests/tests_distance_algo.py
+++ b/tests/tests_distance_algo.py
@@ -3,47 +3,57 @@
 
 class TestsHelperFunctions(unittest.TestCase):
 
-  def test_closest_command(self):
+  def test_empty_command(self):
     invalid_command = ""
     closest = hedy.closest_command(invalid_command, ['ask', 'print', 'echo'])
-    self.assertEqual(closest, 'ask')
+    self.assertEqual('ask', closest)
 
+  def test_self_command_print(self):
     invalid_command = "print"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "print")
+    self.assertEqual(None, closest)
 
+  def test_self_command_ask(self):
     invalid_command = "ask"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "ask")
+    self.assertEqual(None, closest)
 
+  def test_self_command_echo(self):
     invalid_command = "echo"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "echo")
+    self.assertEqual(None, closest)
 
+  def test_print_command_1(self):
     invalid_command = "printechoask"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "print")
+    self.assertEqual("print", closest)
 
+  def test_print_command_2(self):
     invalid_command = "printaskecho"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "print")
+    self.assertEqual("print", closest)
 
+  def test_echo_command_1(self):
     invalid_command = "echoprintask"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "echo")
+    self.assertEqual("echo", closest)
 
+  def test_echo_command_2(self):
     invalid_command = "echoprint ask"
     closest = hedy.closest_command(invalid_command, ['print', 'ask', 'echo'])
-    self.assertEqual(closest, "echo")
+    self.assertEqual("echo", closest)
 
+  def test_echo_command_3(self):
     invalid_command = "echo print ask"
     closest = hedy.closest_command(invalid_command, ['ask', 'print', 'echo'])
-    self.assertEqual(closest, "echo")
+    self.assertEqual("echo", closest)
 
+  def test_ask_command_1(self):
     invalid_command = "hello ask echoprint"
     closest = hedy.closest_command(invalid_command, ['ask', 'print', 'echo'])
-    self.assertEqual(closest, "ask")
+    self.assertEqual("ask", closest)
 
+  def test_echo_command_4(self):
     invalid_command = "hello Ask echo0 print"
     closest = hedy.closest_command(invalid_command, ['ask', 'print', 'echo'])
-    self.assertEqual(closest, "echo")
+    self.assertEqual("echo", closest)
diff --git a/tests/tests_level_03.py b/tests/tests_level_03.py
--- a/tests/tests_level_03.py
+++ b/tests/tests_level_03.py
@@ -42,7 +42,8 @@ def test_transpile_other(self):
   def test_transpile_print_level_2(self):
     with self.assertRaises(Exception) as context:
       result = hedy.transpile("print felienne 123", 3)
-      self.assertEqual(str(context), 'First word is not a command')  # hier moet nog we een andere foutmelding komen!
+
+    self.assertEqual('Unquoted Text', context.exception.args[0])  # hier moet nog we een andere foutmelding komen!
 
   def test_print(self):
 
@@ -202,4 +203,18 @@ def test_transpile_ask_with_print(self):
     kleur = input('wat is je lievelingskleur?')
     print('jouw lievelingskleur is dus'+kleur+'!')""")
 
-    self.assertEqual(expected, result)
\ No newline at end of file
+    self.assertEqual(expected, result)
+
+  def test_transpile_missing_opening_quote(self):
+    code = textwrap.dedent("""
+      print hallo wereld'""")
+
+    with self.assertRaises(Exception) as context:
+      result = hedy.transpile(code, 3)
+
+    self.assertEqual('Unquoted Text', context.exception.args[0])  # hier moet nog we een andere foutmelding komen!
+
+
+
+
+