Repository: hedyorg/hedy

Issue:
[BUG] Incomplete inputs are not handled
**Describe the bug**
In level 18, incomplete inputs are not detected and are instead treated as assignments (it also yields an error but not the correct one). 
 Also, on a related bug, a program that contains input without text inside yields a parsing error:

**Paste the Hedy code & level**
Level 18
```
x is input
```
```
x is input()
```

**Add a screenshot (optional)**
![image](https://user-images.githubusercontent.com/45865185/149646996-dde30ba7-a59f-48cd-b42e-e26e9a1abe59.png)

![image](https://user-images.githubusercontent.com/45865185/149647061-b485214e-3633-41f3-85ff-f2b801dd0c5d.png)

**Expected behavior**
It should raise a `IncompleteCommandException`

**What machine and browser you were using (optional)**
If the issue concerns things in the website, let us know:
- What computer you are using (Windows, Mac, Linux?)
- What browser you were using (Chrome, Edge, Safari)

[BUG] input does not work with =
**Describe the bug**
Input fails to parse when used with `=` (does work with `is`)

**Paste the Hedy code & level**
```
naam = input('Hedy')
print('ik heet', naam)
```

**Add a screenshot (optional)**
![image](https://user-images.githubusercontent.com/1003685/149528353-dc617699-abaf-4b9c-a4fc-17805aefeca0.png)

**Expected behavior**
A clear and concise description of what you expected to happen.

**What machine and browser you were using (optional)**
If the issue concerns things in the website, let us know:
- What computer you are using (Windows, Mac, Linux?)
- What browser you were using (Chrome, Edge, Safari)


Discussion:

Resolving patch:
diff --git a/hedy.py b/hedy.py
--- a/hedy.py
+++ b/hedy.py
@@ -359,25 +359,31 @@ def input(self, tree):
         var_name = tree.children[0].children[0]
         self.add_to_lookup(var_name, tree)
 
+    def input_is(self, tree):
+        self.input(tree)
+    
+    def input_equals(self, tree):
+        self.input(tree)
+
     def assign(self, tree):
         var_name = tree.children[0].children[0]
         self.add_to_lookup(var_name, tree.children[1])
     
     def assign_is(self, tree):
-        return self.assign(tree)
+        self.assign(tree)
     
     def assign_equals(self, tree):
-        return self.assign(tree)
+        self.assign(tree)
 
     def assign_list(self, tree):
         var_name = tree.children[0].children[0]
         self.add_to_lookup(var_name, tree)
     
     def assign_list_is(self, tree):
-        return self.assign_list(tree)
+        self.assign_list(tree)
 
     def assign_list_equals(self, tree):
-        return self.assign_list(tree)
+        self.assign_list(tree)
 
     # list access is added to the lookup table not because it must be escaped
     # for example we print(dieren[1]) not print('dieren[1]')
@@ -825,6 +831,10 @@ def translate_keyword(self, keyword):
             return 'ask'
         if keyword == 'in_list_check':
             return 'in'
+        if keyword in ['input_is', 'input_equals', 'input_is_empty_brackets', 'input_equals_empty_brackets']:
+            return 'input'
+        if keyword == 'print_empty_brackets':
+            return 'print'
         return keyword
 
     def __default__(self, args, children, meta):
@@ -983,7 +993,11 @@ def ask_equals(self, args, meta):
     def print(self, args, meta):
         return args != [], ('print', meta.line)
     def input(self, args, meta):
-        return args != [], ('input', meta.line)
+        return len(args) > 1, ('input', meta.line)
+    def input_is(self, args, meta):
+        return self.input(args, meta)
+    def input_equals(self, args, meta):
+        return self.input(args, meta)
     def length(self, args, meta):
         return args != [], ('len', meta.line)
     def error_print_nq(self, args, meta):
@@ -1710,10 +1724,24 @@ def elifs(self, args):
 
 @hedy_transpiler(level=18)
 class ConvertToPython_18(ConvertToPython_17):
-
     def input(self, args):
         return self.ask(args)
 
+    def input_is(self, args):
+        return self.input(args)
+
+    def input_equals(self, args):
+        return self.input(args)
+    
+    def input_is_empty_brackets(self, args):
+        return self.input(args)
+    
+    def input_equals_empty_brackets(self, args):
+        return self.input(args)
+    
+    def print_empty_brackets(self, args):
+        return self.print(args)
+
 def merge_grammars(grammar_text_1, grammar_text_2, level):
     # this function takes two grammar files and merges them into one
     # rules that are redefined in the second file are overridden
diff --git a/hedy_translation.py b/hedy_translation.py
--- a/hedy_translation.py
+++ b/hedy_translation.py
@@ -403,16 +403,31 @@ def elifs(self, args):
 @hedy_translator(level=18)
 class ConvertToLang18(ConvertToLang17):
 
-    def input(self, args):
+    def input_is(self, args):
         var = args[0]
         remaining_args = args[1:]
         return var + " " + self.keywords["is"] + " " + self.keywords["input"] + "(" + ''.join(remaining_args) + ")"
 
+    def input_equals(self, args):
+        var = args[0]
+        remaining_args = args[1:]
+        return var + " = " + self.keywords["input"] + "(" + ''.join(remaining_args) + ")"
+    
+    def input_is_empty_brackets(self, args):
+        var = args[0]        
+        return var + " " + self.keywords["is"] + " " + self.keywords["input"] + "()"
+
+    def input_equals_empty_brackets(self, args):
+        var = args[0]
+        return var + " = " + self.keywords["input"] + "()"
+
     def for_loop(self, args):
         return self.keywords["for"] + " " + args[0] + " " + self.keywords["in"] + " " + \
                f'{self.keywords["range"]}({args[1]},{args[2]})' + ":" + indent(args[3:])
 
-
     def print(self, args):
         argument_string = ''.join(args)
-        return f'{self.keywords["print"]}({argument_string})'
\ No newline at end of file
+        return f'{self.keywords["print"]}({argument_string})'
+
+    def print_empty_brackets(self, args):
+        return f'{self.keywords["print"]}()'
\ No newline at end of file


Test patch:
diff --git a/tests/test_level_18.py b/tests/test_level_18.py
--- a/tests/test_level_18.py
+++ b/tests/test_level_18.py
@@ -1,6 +1,7 @@
 import hedy
 import textwrap
 from test_level_01 import HedyTester
+from parameterized import parameterized
 
 class TestsLevel18(HedyTester):
     level = 18
@@ -33,10 +34,11 @@ def test_print_var_brackets(self):
           expected=expected,
           extra_check_function=self.is_not_turtle()
         )
-
-    def test_input(self):
-        code = textwrap.dedent("""\
-        leeftijd is input('Hoe oud ben jij?')
+        
+    @parameterized.expand(['=', 'is'])
+    def test_input(self, assigment):
+        code = textwrap.dedent(f"""\
+        leeftijd {assigment} input('Hoe oud ben jij?')
         print(leeftijd)""")
         expected = textwrap.dedent("""\
         leeftijd = input(f'Hoe oud ben jij?')
@@ -204,6 +206,30 @@ def test_input_with_list(self):
           expected=expected,
           extra_check_function=self.is_not_turtle()
       )
+    
+    def test_input_without_text_inside(self):
+      code = "x = input()"
+      expected = textwrap.dedent("""\
+      x = input(f'')
+      try:
+        x = int(x)
+      except ValueError:
+        try:
+          x = float(x)
+        except ValueError:
+          pass""")
+      self.multi_level_tester(
+        code=code,
+        expected=expected,
+        extra_check_function=self.is_not_turtle()
+      )
+    
+    def test_print_without_text_inside(self):
+      self.multi_level_tester(
+        code="print()",
+        expected="print(f'')",
+        extra_check_function=self.is_not_turtle()
+      )
 
     # negative tests
 
@@ -229,4 +255,10 @@ def test_var_undefined_error_message(self):
 
       # deze extra check functie kan nu niet mee omdat die altijd op result werkt
       # evt toch splitsen in 2 (pos en neg?)
-      # self.assertEqual('name', context.exception.arguments['name'])
\ No newline at end of file
+      # self.assertEqual('name', context.exception.arguments['name'])
+    
+    def test_input_without_argument(self):
+      self.multi_level_tester(
+        code="name is input",
+        exception=hedy.exceptions.IncompleteCommandException
+      )   
\ No newline at end of file
diff --git a/tests/test_translation_level_18.py b/tests/test_translation_level_18.py
--- a/tests/test_translation_level_18.py
+++ b/tests/test_translation_level_18.py
@@ -1,3 +1,4 @@
+from cmath import exp
 import hedy
 from test_level_01 import HedyTester
 import hedy_translation
@@ -35,3 +36,26 @@ def test_range_with_brackets(self):
             print('hallo!')""")
 
         self.assertEqual(expected, result)
+
+    def test_input_empty_brackets(self):
+        code = textwrap.dedent("""\
+        nombre es entrada()
+        imprimir(nombre)""")
+
+        result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level)
+
+        expected = textwrap.dedent("""\
+        nombre is input()
+        print(nombre)""")
+
+        self.assertEqual(expected, result)
+
+    def test_print_empty_brackets(self):
+        code = textwrap.dedent("imprimir()")
+
+        result = hedy_translation.translate_keywords(code, from_lang="es", to_lang="en", level=self.level)
+
+        expected = textwrap.dedent("print()")
+
+        self.assertEqual(expected, result)
+