Skip to content

Commit

Permalink
Add runtime validation and correction to the transpiled turtle commands
Browse files Browse the repository at this point in the history
hedyorg#1983 (hedyorg#2081)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
boryanagoncharenko and mergify[bot] authored Mar 1, 2022
1 parent 6e0a25e commit d594602
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 116 deletions.
35 changes: 24 additions & 11 deletions hedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1211,21 +1211,16 @@ def comment(self, args):

def forward(self, args):
if len(args) == 0:
return self.make_forward(50)

parameter = int(args[0])
return self.make_forward(parameter)

def make_forward(self, parameter):
return sleep_after(f"t.forward({parameter})", False)
return sleep_after('t.forward(50)', False)
return self.make_forward(int(args[0]))

def turn(self, args):
if len(args) == 0:
return "t.right(90)" # no arguments defaults to a right turn

arg = args[0]
if self.is_variable(arg) or arg.lstrip("-").isnumeric():
return f"t.right({arg})"
return self.make_turn(arg)
elif arg == 'left':
return "t.left(90)"
elif arg == 'right':
Expand All @@ -1235,6 +1230,24 @@ def turn(self, args):
raise exceptions.InvalidArgumentTypeException(command=Command.turn, invalid_type='', invalid_argument=arg,
allowed_types=get_allowed_types(Command.turn, self.level))

def make_turn(self, parameter):
return self.make_turtle_command(parameter, Command.turn, 'right', False)

def make_forward(self, parameter):
return self.make_turtle_command(parameter, Command.forward, 'forward', True)

def make_turtle_command(self, parameter, command, command_text, add_sleep):
variable = self.get_fresh_var('trtl')
transpiled = textwrap.dedent(f"""\
{variable} = {parameter}
try:
{variable} = int({variable})
except ValueError:
raise Exception(f'While running your program the command {style_closest_command(command)} received the value {style_closest_command('{'+variable+'}')} which is not allowed. Try changing the value to a number.')
t.{command_text}(min(600, {variable}) if {variable} > 0 else max(-600, {variable}))""")
if add_sleep:
return sleep_after(transpiled, False)
return transpiled



Expand Down Expand Up @@ -1287,7 +1300,7 @@ def ask(self, args):

def forward(self, args):
if len(args) == 0:
return self.make_forward(50)
return sleep_after('t.forward(50)', False)

if ConvertToPython.is_int(args[0]):
parameter = int(args[0])
Expand All @@ -1303,11 +1316,11 @@ def turn(self, args):

arg = args[0]
if arg.lstrip('-').isnumeric():
return f"t.right({arg})"
return self.make_turn(arg)

hashed_arg = hash_var(arg)
if self.is_variable(hashed_arg):
return f"t.right({hashed_arg})"
return self.make_turn(hashed_arg)

# the TypeValidator should protect against reaching this line:
raise exceptions.InvalidArgumentTypeException(command=Command.turn, invalid_type='', invalid_argument=arg,
Expand Down
38 changes: 35 additions & 3 deletions tests/Tester.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import unittest
import textwrap
import app
import hedy, hedy_translation
import re
Expand Down Expand Up @@ -73,7 +73,7 @@ def as_list_of_tuples(*args):
t = tuple((item[i] for item in args))
res.append(t)
return res

def multi_level_tester(self, code, max_level=hedy.HEDY_MAX_LEVEL, expected=None, exception=None, extra_check_function=None, expected_commands=None, lang='en', translate=True):
# used to test the same code snippet over multiple levels
# Use exception to check for an exception
Expand Down Expand Up @@ -172,4 +172,36 @@ def validate_Python_code(parseresult):
return True # programs with ask cannot be tested with output :(
except Exception as E:
return False
return True
return True

# The turtle commands get transpiled into big pieces of code that probably will change
# The followings methods abstract the specifics of the tranpilation and keep tests succinct
@staticmethod
def forward_transpiled(val):
return HedyTester.turtle_command_transpiled('forward', val)

@staticmethod
def turn_transpiled(val):
return HedyTester.turtle_command_transpiled('right', val)

@staticmethod
def turtle_command_transpiled(command, val):
command_text = 'turn'
suffix = ''
if command == 'forward':
command_text = 'forward'
suffix = '\n time.sleep(0.1)'
return textwrap.dedent(f"""\
trtl = {val}
try:
trtl = int(trtl)
except ValueError:
raise Exception(f'While running your program the command <span class="command-highlighted">{command_text}</span> received the value <span class="command-highlighted">{{trtl}}</span> which is not allowed. Try changing the value to a number.')
t.{command}(min(600, trtl) if trtl > 0 else max(-600, trtl)){suffix}""")

# Used to overcome indentation issues when the above code is inserted
# in test cases which use different indentation style (e.g. 2 or 4 spaces)
@staticmethod
def dedent(*args):
return '\n'.join([textwrap.indent(textwrap.dedent(a[0]), a[1]) if type(a) is tuple else textwrap.dedent(a)
for a in args])
56 changes: 25 additions & 31 deletions tests/test_level_01.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_print_with_quotes(self):
code=code,
expected=expected,
output="'Welcome to OceanView!'")

def test_print_with_slashes(self):
code = "print 'Welcome to \\O/ceanView!'"

Expand All @@ -94,7 +94,7 @@ def test_print_with_slashed_at_end(self):
expected=expected,
output="Welcome to \\"
)

def test_print_with_spaces(self):
code = "print hallo!"
expected = textwrap.dedent("""\
Expand Down Expand Up @@ -146,7 +146,7 @@ def test_ask_with_quotes(self):
answer = input('\\'Welcome to OceanView?\\'')""")

self.single_level_tester(code=code, expected=expected)

def test_ask_nl_code_transpiled_in_nl(self):
code = "vraag Heb je er zin in?"
expected = "answer = input('Heb je er zin in?')"
Expand Down Expand Up @@ -202,9 +202,7 @@ def test_echo_with_quotes(self):
# forward tests
def test_forward(self):
code = "forward 50"
expected = textwrap.dedent("""\
t.forward(50)
time.sleep(0.1)""")
expected = HedyTester.dedent(HedyTester.forward_transpiled(50))
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand All @@ -214,9 +212,7 @@ def test_forward(self):

def test_forward_arabic_numeral(self):
code = "forward ١١١١١١١"
expected = textwrap.dedent("""\
t.forward(1111111)
time.sleep(0.1)""")
expected = HedyTester.forward_transpiled(1111111)
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand All @@ -226,9 +222,7 @@ def test_forward_arabic_numeral(self):

def test_forward_hindi_numeral(self):
code = "forward ५५५"
expected = textwrap.dedent("""\
t.forward(555)
time.sleep(0.1)""")
expected = HedyTester.forward_transpiled(555)
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand All @@ -237,10 +231,10 @@ def test_forward_hindi_numeral(self):
)

def test_forward_without_argument(self):
code = textwrap.dedent("""forward""")
code = 'forward'
expected = textwrap.dedent("""\
t.forward(50)
time.sleep(0.1)""")
t.forward(50)
time.sleep(0.1)""")

self.multi_level_tester(
max_level=self.max_turtle_level,
Expand Down Expand Up @@ -292,10 +286,9 @@ def test_one_turn_left(self):
self.single_level_tester(code=code, expected=expected,
extra_check_function=self.is_turtle())


def test_turn_number(self):
code = "turn 180"
expected = "t.right(180)"
expected = HedyTester.turn_transpiled(180)
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand All @@ -305,7 +298,7 @@ def test_turn_number(self):

def test_turn_negative_number(self):
code = "turn -180"
expected = "t.right(-180)"
expected = HedyTester.turn_transpiled(-180)
self.multi_level_tester(
max_level=10,
code=code,
Expand All @@ -332,28 +325,29 @@ def test_comment(self):

# combined keywords tests
def test_print_ask_echo(self):
code = textwrap.dedent("""\
code = textwrap.dedent("""\
print Hallo
ask Wat is je lievelingskleur
echo je lievelingskleur is""")

expected = textwrap.dedent("""\
expected = textwrap.dedent("""\
print('Hallo')
answer = input('Wat is je lievelingskleur')
print('je lievelingskleur is '+answer)""")

self.single_level_tester(
code=code,
expected=expected,
expected_commands=['print', 'ask', 'echo'])
self.single_level_tester(
code=code,
expected=expected,
expected_commands=['print', 'ask', 'echo'])
def test_forward_turn_combined(self):
code = "forward 50\nturn\nforward 100"
expected = textwrap.dedent("""\
t.forward(50)
time.sleep(0.1)
t.right(90)
t.forward(100)
time.sleep(0.1)""")
code = textwrap.dedent("""\
forward 50
turn
forward 100""")
expected = HedyTester.dedent(
HedyTester.forward_transpiled(50),
't.right(90)',
HedyTester.forward_transpiled(100))
self.multi_level_tester(
max_level=7,
code=code,
Expand Down
46 changes: 22 additions & 24 deletions tests/test_level_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ def test_turn_with_number_var(self):
code = textwrap.dedent("""\
direction is 70
turn direction""")
expected = textwrap.dedent("""\
direction = '70'
t.right(direction)""")
expected = HedyTester.dedent(
"direction = '70'",
HedyTester.turn_transpiled('direction'))
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand All @@ -202,9 +202,9 @@ def test_turn_with_non_ascii_var(self):
code = textwrap.dedent("""\
ángulo is 90
turn ángulo""")
expected = textwrap.dedent("""\
vefd88f42b64136f16e8f305dd375a921 = '90'
t.right(vefd88f42b64136f16e8f305dd375a921)""")
expected = HedyTester.dedent(
"vefd88f42b64136f16e8f305dd375a921 = '90'",
HedyTester.turn_transpiled('vefd88f42b64136f16e8f305dd375a921'))
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand Down Expand Up @@ -235,10 +235,9 @@ def test_forward_with_integer_variable(self):
code = textwrap.dedent("""\
a is 50
forward a""")
expected = textwrap.dedent("""\
a = '50'
t.forward(a)
time.sleep(0.1)""")
expected = HedyTester.dedent(
"a = '50'",
HedyTester.forward_transpiled('a'))
self.multi_level_tester(
max_level=self.max_turtle_level,
code=code,
Expand Down Expand Up @@ -282,28 +281,27 @@ def test_assign_print(self):
print(f'{naam}')""")

self.single_level_tester(code=code, expected=expected)

def test_forward_ask(self):
code = textwrap.dedent("""\
afstand is ask hoe ver dan?
forward afstand""")
afstand is ask hoe ver dan?
forward afstand""")

expected = textwrap.dedent("""\
afstand = input('hoe ver dan'+'?')
t.forward(afstand)
time.sleep(0.1)""")
expected = HedyTester.dedent(
"afstand = input('hoe ver dan'+'?')",
HedyTester.forward_transpiled('afstand'))
self.single_level_tester(code=code, expected=expected, extra_check_function=self.is_turtle())


def test_turn_ask(self):
code = textwrap.dedent("""\
print Turtle race
direction is ask Where to turn?
turn direction""")
print Turtle race
direction is ask Where to turn?
turn direction""")

expected = textwrap.dedent("""\
print(f'Turtle race')
direction = input('Where to turn'+'?')
t.right(direction)""")
expected = HedyTester.dedent("""\
print(f'Turtle race')
direction = input('Where to turn'+'?')""",
HedyTester.turn_transpiled('direction'))

self.single_level_tester(code=code, expected=expected, extra_check_function=self.is_turtle())
def test_assign_print_punctuation(self):
Expand Down
Loading

0 comments on commit d594602

Please sign in to comment.