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

🪲 Revise runtime errors to include actual values #5308

Merged
merged 6 commits into from
Apr 1, 2024
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
106 changes: 35 additions & 71 deletions hedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from hedy_content import KEYWORDS
from hedy_sourcemap import SourceMap, source_map_transformer

from prefixes.music import notes_mapping
from prefixes.music import present_in_notes_mapping

HEDY_MAX_LEVEL = 18
HEDY_MAX_LEVEL_SKIPPING_FAULTY = 5
Expand Down Expand Up @@ -185,7 +185,7 @@ def needs_colon(rule):
}


def make_value_error(command, value, tip, lang):
def make_value_error(command, tip, lang, value='{}'):
return make_error_text(exceptions.RuntimeValueException(command=command, value=value, tip=tip), lang)


Expand Down Expand Up @@ -243,6 +243,7 @@ class Command:
define = 'define'
call = 'call'
returns = 'return'
play = 'play'


translatable_commands = {Command.print: ['print'],
Expand Down Expand Up @@ -1193,7 +1194,7 @@ def error_invalid(self, meta, args):
if not sug_exists: # the suggestion is invalid, i.e. identical to the command
invalid_command_en = hedy_translation.translate_keyword_to_en(invalid_command, self.lang)
if invalid_command_en == Command.turn:
arg = args[0][0]
arg = args[1][1]
raise hedy.exceptions.InvalidArgumentException(
command=invalid_command,
allowed_types=get_allowed_types(Command.turn, self.level),
Expand Down Expand Up @@ -1491,17 +1492,6 @@ def check_var_usage_when_quotes_are_required(self, arg, meta):
ConvertToPython.is_random(arg)):
raise exceptions.UnquotedAssignTextException(text=arg, line_number=meta.line)

def code_to_ensure_variable_type(self, arg, expected_type, command, suggested_type):
if not self.is_variable(arg):
return ""
exception = make_value_error(command, f'{{{arg}}}', suggested_type, self.language)
return textwrap.dedent(f"""\
try:
{expected_type}({arg})
except ValueError:
raise Exception(f{exception})
""")

# static methods

@staticmethod
Expand Down Expand Up @@ -1644,47 +1634,37 @@ def turn(self, meta, args):
line_number=meta.line)

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

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

def make_play(self, note, meta):
exception_text = make_value_error('play', note, 'suggestion_note', self.language)
ex = make_value_error(Command.play, 'suggestion_note', self.language)

return textwrap.dedent(f"""\
if '{note}' not in notes_mapping.keys() and '{note}' not in notes_mapping.values():
raise Exception({exception_text})
play(notes_mapping.get(str('{note}'), str('{note}')))
play(note_with_error('{note}', {ex}))
time.sleep(0.5)""")

def make_play_var(self, note, meta):
exception_text = make_value_error('play', note, 'suggestion_note', self.language)
self.check_var_usage([note], meta.line)
chosen_note = note.children[0] if isinstance(note, Tree) else note
ex = make_value_error(Command.play, 'suggestion_note', self.language)

return textwrap.dedent(f"""\
chosen_note = str({chosen_note}).upper()
if chosen_note not in notes_mapping.keys() and chosen_note not in notes_mapping.values():
raise Exception({exception_text})
play(notes_mapping.get(chosen_note, chosen_note))
play(note_with_error({chosen_note}, {ex}))
time.sleep(0.5)""")

def make_color(self, parameter, language):
return self.make_turtle_color_command(parameter, Command.color, 'pencolor', language)

def make_turtle_command(self, parameter, command, command_text, add_sleep, type):
exception = ''
if isinstance(parameter, str):
exception = self.make_index_error_check_if_list([parameter])
def make_turtle_command(self, parameter, command, command_text, add_sleep, target_type):
list_index_exception = self.make_index_error_check_if_list([parameter]) if isinstance(parameter, str) else ''
variable = self.get_fresh_var('__trtl')
exception_text = make_value_error(command, variable, 'suggestion_number', self.language)
transpiled = exception + textwrap.dedent(f"""\
{variable} = {parameter}
try:
{variable} = {type}({variable})
except ValueError:
raise Exception({exception_text})
func = 'int_with_error' if target_type == HedyType.integer else 'number_with_error'
ex = make_value_error(command, 'suggestion_number', self.language)
transpiled = list_index_exception + textwrap.dedent(f"""\
{variable} = {func}({parameter}, {ex})
t.{command_text}(min(600, {variable}) if {variable} > 0 else max(-600, {variable})){self.add_debug_breakpoint()}""")
if add_sleep and not self.is_debug:
return add_sleep_to_command(transpiled, False, self.is_debug, location="after")
Expand All @@ -1698,12 +1678,12 @@ def make_turtle_color_command(self, parameter, command, command_text, language):
# coming from a random list or ask

color_dict = {hedy_translation.translate_keyword_from_en(x, language): x for x in english_colors}
exception_text = make_value_error(command, parameter, 'suggestion_color', self.language)
ex = make_value_error(command, 'suggestion_color', self.language, parameter)
return textwrap.dedent(f"""\
{variable} = f'{parameter}'
color_dict = {color_dict}
if {variable} not in {both_colors}:
raise Exception({exception_text})
raise Exception(f{ex})
else:
if not {variable} in {english_colors}:
{variable} = color_dict[{variable}]
Expand Down Expand Up @@ -1843,9 +1823,8 @@ def play(self, meta, args):

note = args[0]
if isinstance(note, str):
uppercase_note = note.upper()
if uppercase_note in list(notes_mapping.values()) + list(notes_mapping.keys()): # this is a supported note
return self.make_play(uppercase_note, meta) + self.add_debug_breakpoint()
if present_in_notes_mapping(note): # this is a supported note
return self.make_play(note.upper(), meta) + self.add_debug_breakpoint()

# no note? it must be a variable!
self.add_variable_access_location(note, meta.line)
Expand All @@ -1868,20 +1847,16 @@ def assign(self, meta, args):
return variable_name + " = '" + value + "'" + self.add_debug_breakpoint()

def sleep(self, meta, args):

if not args:
return f"time.sleep(1){self.add_debug_breakpoint()}"
else:
value = f'"{args[0]}"' if self.is_int(args[0]) else args[0]
if not self.is_int(args[0]):
self.add_variable_access_location(value, meta.line)
exceptions = self.make_index_error_check_if_list(args)
try_prefix = "try:\n" + textwrap.indent(exceptions, " ")
exception_text = make_value_error(Command.sleep, value, 'suggestion_number', self.language)
code = try_prefix + textwrap.dedent(f"""\
time.sleep(int({value})){self.add_debug_breakpoint()}
except ValueError:
raise Exception({exception_text})""")
index_exception = self.make_index_error_check_if_list(args)
ex = make_value_error(Command.sleep, 'suggestion_number', self.language)
code = index_exception + \
textwrap.dedent(f"time.sleep(int_with_error({value}, {ex})){self.add_debug_breakpoint()}")
return code


Expand Down Expand Up @@ -2138,13 +2113,9 @@ def sleep(self, meta, args):
if not self.is_int(args[0]):
self.add_variable_access_location(value, meta.line)

exceptions = self.make_index_error_check_if_list(args)
try_prefix = "try:\n" + textwrap.indent(exceptions, " ")
exception_text = make_value_error(Command.sleep, value, 'suggestion_number', self.language)
code = try_prefix + textwrap.dedent(f"""\
time.sleep(int({value}))
except ValueError:
raise Exception({exception_text})""")
index_exception = self.make_index_error_check_if_list(args)
ex = make_value_error(Command.sleep, 'suggestion_number', self.language)
code = index_exception + textwrap.dedent(f"time.sleep(int_with_error({value}, {ex}))")
return code

def print_ask_args(self, meta, args):
Expand Down Expand Up @@ -2205,7 +2176,7 @@ def process_token_or_tree_for_calculation(self, argument, command, meta):
latin_numeral = int(argument)
return f'int({latin_numeral})'
self.add_variable_access_location(argument, meta.line)
exception_text = make_value_error(command, argument, 'suggestion_number', self.language)
exception_text = make_value_error(command, 'suggestion_number', self.language)
return f'int_with_error({argument}, {exception_text})'

def process_calculation(self, args, operator, meta):
Expand Down Expand Up @@ -2282,8 +2253,8 @@ def make_repeat(self, meta, args, multiline):
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, 'suggestion_number')
return f"{type_check}for {var_name} in range(int({times})):{self.add_debug_breakpoint()}\n{body}"
ex = make_value_error(Command.repeat, 'suggestion_number', self.language)
return f"for {var_name} in range(int_with_error({times}, {ex})):{self.add_debug_breakpoint()}\n{body}"

def repeat(self, meta, args):
return self.make_repeat(meta, args, multiline=False)
Expand Down Expand Up @@ -2553,10 +2524,11 @@ def forward(self, meta, args):
return self.make_forward(float(args[0]))

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

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

def process_token_or_tree(self, argument, meta):
if isinstance(argument, Tree):
Expand All @@ -2572,20 +2544,12 @@ def process_token_or_tree_for_calculation(self, argument, command, meta):
else:
# this is a variable, add to the table
self.add_variable_access_location(argument, meta.line)
exception_text = make_value_error(command, argument, 'suggestion_number', self.language)
exception_text = make_value_error(command, 'suggestion_number', self.language)
return f'number_with_error({argument}, {exception_text})'

def process_token_or_tree_for_addition(self, argument, meta):
if type(argument) is Tree:
return f'{str(argument.children[0])}'
else:
# this is a variable, add to the table
self.add_variable_access_location(argument, meta.line)
return argument

# From level 12 concatenation should also work, so the args could be either numbers or strings
def addition(self, meta, args):
args = [self.process_token_or_tree_for_addition(a, meta) for a in args]
args = [self.process_token_or_tree(a, meta) for a in args]
if all([self.is_int(a) or self.is_float(a) for a in args]):
return Tree('sum', [f'{args[0]} + {args[1]}'])
else:
Expand Down
12 changes: 12 additions & 0 deletions prefixes/music.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,15 @@
'69': 'A9',
'70': 'B9',
}


def present_in_notes_mapping(value):
note = str(value).upper()
return note in notes_mapping.keys() or note in notes_mapping.values()


def note_with_error(value, err):
note = str(value).upper()
if not present_in_notes_mapping(note):
raise Exception(err.format(value))
return notes_mapping.get(note, note)
12 changes: 12 additions & 0 deletions static/js/appbundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -72053,6 +72053,18 @@ notes_mapping = {
'69': 'A9',
'70': 'B9',
}


def present_in_notes_mapping(value):
note = str(value).upper()
return note in notes_mapping.keys() or note in notes_mapping.values()


def note_with_error(value, err):
note = str(value).upper()
if not present_in_notes_mapping(note):
raise Exception(err.format(value))
return notes_mapping.get(note, note)
`;

// static/js/types.ts
Expand Down
4 changes: 2 additions & 2 deletions static/js/appbundle.js.map

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions static/js/pythonPrefixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,16 @@ notes_mapping = {
'69': 'A9',
'70': 'B9',
}


def present_in_notes_mapping(value):
note = str(value).upper()
return note in notes_mapping.keys() or note in notes_mapping.values()


def note_with_error(value, err):
note = str(value).upper()
if not present_in_notes_mapping(note):
raise Exception(err.format(value))
return notes_mapping.get(note, note)
`;
32 changes: 12 additions & 20 deletions tests/Tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,23 +371,16 @@ def turtle_command_transpiled(command, val, level):
if command == 'forward':
suffix = '\n time.sleep(0.1)'

type = 'int' if level < 12 else 'float'
func = 'int_with_error' if level < 12 else 'number_with_error'

return textwrap.dedent(f'''\
__trtl = {val}
try:
__trtl = {type}(__trtl)
except ValueError:
raise Exception({HedyTester.value_exception_transpiled()})
__trtl = {func}({val}, {HedyTester.value_exception_transpiled()})
t.{command}(min(600, __trtl) if __trtl > 0 else max(-600, __trtl)){suffix}''')

@staticmethod
def sleep_command_transpiled(val):
return textwrap.dedent(f'''\
try:
time.sleep(int({val}))
except ValueError:
raise Exception({HedyTester.value_exception_transpiled()})''')
return textwrap.dedent(f'\
time.sleep(int_with_error({val}, {HedyTester.value_exception_transpiled()}))')

@staticmethod
def turtle_color_command_transpiled(val, lang="en"):
Expand All @@ -398,7 +391,7 @@ def turtle_color_command_transpiled(val, lang="en"):
__trtl = f'{val}'
color_dict = {color_dict}
if __trtl not in {both_colors}:
raise Exception({HedyTester.value_exception_transpiled()})
raise Exception(f{HedyTester.value_exception_transpiled()})
else:
if not __trtl in {hedy.english_colors}:
__trtl = color_dict[__trtl]
Expand All @@ -424,6 +417,13 @@ def remove_transpiled(list_name, value):
except:
pass""")

@staticmethod
def play_transpiled(arg, quotes=True):
argument = f"'{arg}'" if quotes else arg
return textwrap.dedent(f"""\
play(note_with_error({argument}, {HedyTester.value_exception_transpiled()}))
time.sleep(0.5)""")

@staticmethod
def list_access_transpiled(list_access):
return textwrap.dedent(f'''\
Expand All @@ -432,14 +432,6 @@ def list_access_transpiled(list_access):
except IndexError:
raise Exception({HedyTester.index_exception_transpiled()})''')

@staticmethod
def variable_type_check_transpiled(variable, type_,):
return textwrap.dedent(f'''\
try:
{type_}({variable})
except ValueError:
raise Exception(f{HedyTester.value_exception_transpiled()})''')

@staticmethod
def int_cast_transpiled(val, quotes=True):
value = f"'{val}'" if quotes else val
Expand Down
Loading
Loading