Skip to content

Commit

Permalink
🪲 Escape internal var used in repeat (#5256)
Browse files Browse the repository at this point in the history
Fixes #5255 
The internal variable `i` used in repeat statements is now escaped with the `__` prefix, so that it does not show in the output window.

**How to test**
- Run Hedy locally and run the following snippets for the specified levels. Ensure that the `i` variable does not appear in the blue variables list in the upper right corner of the output window:

Level 7:
```
options is 1, 2, 3, 4, 5
repeat 5 times print options at random
```
Level 8:
```
options is 1, 2, 3, 4, 5
repeat 5 times
  print options at random
```
  • Loading branch information
boryanagoncharenko authored Mar 15, 2024
1 parent 6588f1f commit 5e5ee55
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 102 deletions.
38 changes: 19 additions & 19 deletions hedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,9 @@ def get_suggestions_for_language(lang, level):


def escape_var(var):
var_name = var.name if type(var) is LookupEntry else var
var_name = var
if isinstance(var, LookupEntry):
var_name = var.name
return "_" + var_name if var_name in reserved_words else var_name


Expand Down Expand Up @@ -2311,15 +2313,23 @@ def add_sleep_to_command(commands, indent=True, is_debug=False, location="after"
@hedy_transpiler(level=7)
@source_map_transformer(source_map)
class ConvertToPython_7(ConvertToPython_6):
def repeat(self, meta, args):
var_name = self.get_fresh_var('__i__')
def make_repeat(self, meta, args, multiline):
var_name = self.get_fresh_var('__i')
times = self.process_variable(args[0], meta.line)
command = args[1]
# in level 7, repeats can only have 1 line as their arguments
command = add_sleep_to_command(command, False, self.is_debug, location="after")

# In level 7, repeat can only have 1 line in its body
if not multiline:
body = self.indent(args[1])
# In level 8 and up, repeat can have multiple lines in its body
else:
body = "\n".join([self.indent(x) for x in args[1:]])

body = add_sleep_to_command(body, indent=True, is_debug=self.is_debug, location="after")
type_check = self.code_to_ensure_variable_type(times, 'int', Command.repeat, 'number')
return f"""{type_check}for {var_name} in range(int({str(times)})):{self.add_debug_breakpoint()}
{ConvertToPython.indent(command)}"""
return f"{type_check}for {var_name} in range(int({times})):{self.add_debug_breakpoint()}\n{body}"

def repeat(self, meta, args):
return self.make_repeat(meta, args, multiline=False)


@v_args(meta=True)
Expand All @@ -2333,17 +2343,7 @@ def command(self, meta, args):
return "".join(args)

def repeat(self, meta, args):
# todo fh, may 2022, could be merged with 7 if we make
# indent a boolean parameter?

var_name = self.get_fresh_var('i')
times = self.process_variable(args[0], meta.line)

all_lines = [ConvertToPython.indent(x) for x in args[1:]]
body = "\n".join(all_lines)
body = add_sleep_to_command(body, indent=True, is_debug=self.is_debug, location="after")
type_check = self.code_to_ensure_variable_type(times, 'int', Command.repeat, 'number')
return f"""{type_check}for {var_name} in range(int({times})):{self.add_debug_breakpoint()}\n{body}"""
return self.make_repeat(meta, args, multiline=True)

def ifs(self, meta, args):
all_lines = [ConvertToPython.indent(x) for x in args[1:]]
Expand Down
36 changes: 18 additions & 18 deletions tests/test_level/test_level_07.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_repeat_turtle(self):
code = "repeat 3 times forward 100"

expected = HedyTester.dedent(
"for __i__ in range(int('3')):",
"for __i in range(int('3')):",
(HedyTester.forward_transpiled(100, self.level), ' '))

self.single_level_tester(code=code, expected=expected, extra_check_function=self.is_turtle())
Expand All @@ -38,7 +38,7 @@ def test_repeat_print(self):
code = "repeat 5 times print 'me wants a cookie!'"

expected = textwrap.dedent("""\
for __i__ in range(int('5')):
for __i in range(int('5')):
print(f'me wants a cookie!')
time.sleep(0.1)""")

Expand All @@ -59,7 +59,7 @@ def test_repeat_print_variable(self):
expected = HedyTester.dedent(
"n = '5'",
self.variable_type_check_transpiled('n', 'int'),
"for __i__ in range(int(n)):",
"for __i in range(int(n)):",
("print(f'me wants a cookie!')", ' '),
("time.sleep(0.1)", ' ')
)
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_repeat_with_missing_print_gives_lonely_text_exc(self):

expected = textwrap.dedent("""\
pass
for __i__ in range(int('3')):
for __i in range(int('3')):
pass
time.sleep(0.1)""")

Expand Down Expand Up @@ -250,7 +250,7 @@ def test_repeat_ask(self):
expected = HedyTester.dedent(
"n = input(f'How many times?')",
self.variable_type_check_transpiled('n', 'int'),
"for __i__ in range(int(n)):",
"for __i in range(int(n)):",
("print(f'n')", ' '),
("time.sleep(0.1)", ' ')
)
Expand All @@ -263,7 +263,7 @@ def test_repeat_with_all_numerals(self, number):
code = textwrap.dedent(f"repeat {number} times print 'me wants a cookie!'")

expected = textwrap.dedent(f"""\
for __i__ in range(int('{int(number)}')):
for __i in range(int('{int(number)}')):
print(f'me wants a cookie!')
time.sleep(0.1)""")

Expand All @@ -281,7 +281,7 @@ def test_repeat_over_9_times(self):
repeat 10 times print 'me wants a cookie!'""")

expected = textwrap.dedent("""\
for __i__ in range(int('10')):
for __i in range(int('10')):
print(f'me wants a cookie!')
time.sleep(0.1)""")

Expand Down Expand Up @@ -311,7 +311,7 @@ def test_repeat_with_variable_name_collision(self):

expected = textwrap.dedent("""\
i = 'hallo!'
for __i__ in range(int('5')):
for __i in range(int('5')):
print(f'me wants a cookie!')
time.sleep(0.1)
print(f'{i}')""")
Expand All @@ -338,7 +338,7 @@ def test_repeat_if(self):
expected = textwrap.dedent("""\
naam = 'Hedy'
if convert_numerals('Latin', naam) == convert_numerals('Latin', 'Hedy'):
for __i__ in range(int('3')):
for __i in range(int('3')):
print(f'Hallo Hedy!')
time.sleep(0.1)""")

Expand All @@ -360,7 +360,7 @@ def test_if_pressed_repeat(self):
break
if event.type == pygame.KEYDOWN:
if event.unicode == 'x':
for __i__ in range(int('5')):
for __i in range(int('5')):
print(f'doe het 5 keer!')
time.sleep(0.1)
break
Expand Down Expand Up @@ -441,7 +441,7 @@ def test_repeat_if_pressed_multiple(self):
repeat 3 times if z is pressed forward 15 else forward -15""")

expected = HedyTester.dedent(f"""\
for __i__ in range(int('3')):
for __i in range(int('3')):
pygame_end = False
while not pygame_end:
pygame.display.update()
Expand Down Expand Up @@ -471,7 +471,7 @@ def test_repeat_if_pressed_multiple(self):
break
# End of PyGame Event Handler
time.sleep(0.1)
for __i__ in range(int('3')):
for __i in range(int('3')):
pygame_end = False
while not pygame_end:
pygame.display.update()
Expand Down Expand Up @@ -501,7 +501,7 @@ def test_repeat_if_pressed_multiple(self):
break
# End of PyGame Event Handler
time.sleep(0.1)
for __i__ in range(int('3')):
for __i in range(int('3')):
pygame_end = False
while not pygame_end:
pygame.display.update()
Expand Down Expand Up @@ -545,13 +545,13 @@ def test_repeat_if_multiple(self):

expected = HedyTester.dedent("""\
aan = 'ja'
for __i__ in range(int('3')):
for __i in range(int('3')):
if convert_numerals('Latin', aan) == convert_numerals('Latin', 'ja'):
print(f'Hedy is leuk!')
else:
x__x__x__x = '5'
time.sleep(0.1)
for __i__ in range(int('3')):
for __i in range(int('3')):
if convert_numerals('Latin', aan) == convert_numerals('Latin', 'ja'):
print(f'Hedy is leuk!')
time.sleep(0.1)""")
Expand All @@ -577,7 +577,7 @@ def test_source_map(self):

expected_code = textwrap.dedent("""\
print(f'The prince kept calling for help')
for __i__ in range(int('5')):
for __i in range(int('5')):
print(f'Help!')
time.sleep(0.1)
print(f'Why is nobody helping me?')""")
Expand All @@ -600,7 +600,7 @@ def test_play_repeat(self):
repeat 3 times play C4""")

expected = textwrap.dedent(f"""\
for __i__ in range(int('3')):
for __i in range(int('3')):
if 'C4' not in notes_mapping.keys() and 'C4' not in notes_mapping.values():
raise Exception({self.value_exception_transpiled()})
play(notes_mapping.get(str('C4'), str('C4')))
Expand All @@ -623,7 +623,7 @@ def test_play_repeat_random(self):

expected = textwrap.dedent(f"""\
notes = ['C4', 'E4', 'D4', 'F4', 'G4']
for __i__ in range(int('3')):
for __i in range(int('3')):
chosen_note = str(random.choice(notes)).upper()
if chosen_note not in notes_mapping.keys() and chosen_note not in notes_mapping.values():
raise Exception({self.value_exception_transpiled()})
Expand Down
32 changes: 16 additions & 16 deletions tests/test_level/test_level_08.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ def test_repeat_turtle(self):
forward 100""")

expected = HedyTester.dedent(
"for i in range(int('3')):",
"for __i in range(int('3')):",
(HedyTester.forward_transpiled(100, self.level), ' '))

self.multi_level_tester(
Expand All @@ -636,7 +636,7 @@ def test_repeat_print(self):
print 'koekoek'""")

expected = textwrap.dedent("""\
for i in range(int('5')):
for __i in range(int('5')):
print(f'koekoek')
time.sleep(0.1)""")

Expand All @@ -651,7 +651,7 @@ def test_repeat_print_variable(self):
expected = HedyTester.dedent(
"n = '5'",
self.variable_type_check_transpiled('n', 'int'),
"for i in range(int(n)):",
"for __i in range(int(n)):",
("print(f'me wants a cookie!')", ' '),
("time.sleep(0.1)", ' ')
)
Expand All @@ -671,7 +671,7 @@ def test_repeat_arabic(self):
print 'koekoek'""")

expected = textwrap.dedent("""\
for i in range(int('5')):
for __i in range(int('5')):
print(f'koekoek')
time.sleep(0.1)""")

Expand All @@ -686,7 +686,7 @@ def test_repeat_with_arabic_variable_print(self):
expected = HedyTester.dedent(
"n = '٥'",
self.variable_type_check_transpiled('n', 'int'),
"for i in range(int(n)):",
"for __i in range(int(n)):",
("print(f'me wants a cookie!')", ' '),
("time.sleep(0.1)", ' ')
)
Expand All @@ -709,7 +709,7 @@ def test_repeat_with_non_latin_variable_print(self):
expected = HedyTester.dedent(
"állatok = '5'",
self.variable_type_check_transpiled('állatok', 'int'),
"for i in range(int(állatok)):",
"for __i in range(int(állatok)):",
("print(f'me wants a cookie!')", ' '),
("time.sleep(0.1)", ' ')
)
Expand Down Expand Up @@ -743,7 +743,7 @@ def test_repeat_print_assign_addition(self):

expected = textwrap.dedent(f"""\
count = '1'
for i in range(int('12')):
for __i in range(int('12')):
print(f'{{count}} times 12 is {{{self.int_cast_transpiled('count', False)} * int(12)}}')
count = {self.int_cast_transpiled('count', False)} + int(1)
time.sleep(0.1)""")
Expand All @@ -757,7 +757,7 @@ def test_repeat_with_comment(self):
print 'koekoek'""")

expected = textwrap.dedent("""\
for i in range(int('5')):
for __i in range(int('5')):
print(f'koekoek')
print(f'koekoek')
time.sleep(0.1)""")
Expand Down Expand Up @@ -805,7 +805,7 @@ def test_repeat_ask(self):
expected = HedyTester.dedent(
"n = input(f'How many times?')",
self.variable_type_check_transpiled('n', 'int'),
'for i in range(int(n)):',
'for __i in range(int(n)):',
("print(f'n')", ' '),
('time.sleep(0.1)', ' ')
)
Expand All @@ -820,7 +820,7 @@ def test_repeat_with_all_numerals(self, number):
print 'me wants a cookie!'""")

expected = textwrap.dedent(f"""\
for i in range(int('{int(number)}')):
for __i in range(int('{int(number)}')):
print(f'me wants a cookie!')
time.sleep(0.1)""")

Expand All @@ -839,7 +839,7 @@ def test_repeat_over_9_times(self):
print 'me wants a cookie!'""")

expected = textwrap.dedent("""\
for i in range(int('10')):
for __i in range(int('10')):
print(f'me wants a cookie!')
time.sleep(0.1)""")

Expand Down Expand Up @@ -872,7 +872,7 @@ def test_repeat_with_variable_name_collision(self):

expected = textwrap.dedent("""\
i = 'hallo!'
for _i in range(int('5')):
for __i in range(int('5')):
print(f'me wants a cookie!')
time.sleep(0.1)
print(f'{i}')""")
Expand Down Expand Up @@ -1222,7 +1222,7 @@ def test_source_map(self):
"people = input(f'How many people will be joining us today?')",
"print(f'Great!')",
self.variable_type_check_transpiled('people', 'int'),
"for i in range(int(people)):",
"for __i in range(int(people)):",
("food = input(f'What would you like to order?')", ' '),
("print(f'{food}')", ' '),
("time.sleep(0.1)", ' '),
Expand Down Expand Up @@ -1258,7 +1258,7 @@ def test_play_repeat_random(self):
play note""")

expected = textwrap.dedent(f"""\
for i in range(int('10')):
for __i in range(int('10')):
notes = ['C4', 'E4', 'D4', 'F4', 'G4']
try:
random.choice(notes)
Expand Down Expand Up @@ -1291,7 +1291,7 @@ def test_play_integers(self):

expected = textwrap.dedent(f"""\
notes = ['1', '2', '3']
for i in range(int('10')):
for __i in range(int('10')):
chosen_note = str(random.choice(notes)).upper()
if chosen_note not in notes_mapping.keys() and chosen_note not in notes_mapping.values():
raise Exception({self.value_exception_transpiled()})
Expand All @@ -1317,7 +1317,7 @@ def test_play_repeat_with_calc(self):

expected = textwrap.dedent(f"""\
note = '34'
for i in range(int('3')):
for __i in range(int('3')):
chosen_note = str(note).upper()
if chosen_note not in notes_mapping.keys() and chosen_note not in notes_mapping.values():
raise Exception({self.value_exception_transpiled()})
Expand Down
Loading

0 comments on commit 5e5ee55

Please sign in to comment.