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

💻 Implemented "is" and sleep command for Micro:bit for level 2 #5587

Merged
merged 28 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a74ab64
Implemented "is" in Micro:bit
rmagedon97 Jun 3, 2024
6a2fa46
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
f32d60a
Adding the sleep command in Micro:bit
rmagedon97 Jun 3, 2024
ff08dec
Merge branch 'Microbit-Level_1' of https://github.com/hedyorg/hedy in…
rmagedon97 Jun 3, 2024
9db42bf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
23b0c95
Adding a test for "is"
rmagedon97 Jun 3, 2024
4cab818
Merge branch 'Microbit-Level_1' of https://github.com/hedyorg/hedy in…
rmagedon97 Jun 3, 2024
4c2a803
fixed "is" test and added sleep test for Micro:bit
rmagedon97 Jun 3, 2024
e050f1a
Merge branch 'main' into Microbit-Level_1
rmagedon97 Jun 4, 2024
a7abf51
Merge branch 'main' into Microbit-Level_1
rmagedon97 Jun 4, 2024
40472ec
Add microbit transpiler classes with a separate lookup
boryanagoncharenko Jun 5, 2024
bad8416
Refactoring
rmagedon97 Jun 7, 2024
66fe55b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 7, 2024
a53594d
Merge branch 'main' into Microbit-Level_1
rmagedon97 Jun 7, 2024
7818514
Fixed the sleep command for level 2 and added tests
rmagedon97 Jun 9, 2024
1e82607
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 9, 2024
8c34c0f
Fixed assign function for MBit
rmagedon97 Jun 11, 2024
c77adae
Merge branch 'Microbit-Level_1' of https://github.com/hedyorg/hedy in…
rmagedon97 Jun 11, 2024
92c9445
Merge branch 'main' into Microbit-Level_1
rmagedon97 Jun 11, 2024
b576232
Merge branch 'main' into Microbit-Level_1
rmagedon97 Jun 14, 2024
34d6f71
Fixed the tests for sleep
rmagedon97 Jun 14, 2024
b81a2d8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 14, 2024
c178cc2
Fixed the 2 failing tests
rmagedon97 Jun 16, 2024
956cf14
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2024
a021e34
Merge branch 'main' into Microbit-Level_1
rmagedon97 Jun 16, 2024
9b10444
fix
rmagedon97 Jun 16, 2024
e5062e0
clear implemented
rmagedon97 Jun 16, 2024
2864866
Merge branch 'main' into Microbit-Level_1
mergify[bot] Jun 18, 2024
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
105 changes: 69 additions & 36 deletions hedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def add_level(commands, level, add=None, remove=None):
add = []
if not remove:
remove = []
commands[level] = [c for c in commands[level-1] if c not in remove] + add
commands[level] = [c for c in commands[level - 1] if c not in remove] + add


# Commands per Hedy level which are used to suggest the closest command when kids make a mistake
Expand All @@ -313,7 +313,6 @@ def add_level(commands, level, add=None, remove=None):
add_level(commands_per_level, level=17, add=['elif'])
add_level(commands_per_level, level=18, add=['input'], remove=['ask'])


command_turn_literals = ['right', 'left']
english_colors = ['black', 'blue', 'brown', 'gray', 'green', 'orange', 'pink', 'purple', 'red', 'white', 'yellow']

Expand Down Expand Up @@ -1119,7 +1118,8 @@ def __default__(self, args, children, meta):
# for the achievements we want to be able to also detect which operators were used by a kid
operators = ['addition', 'subtraction', 'multiplication', 'division']

if production_rule_name in commands_per_level[self.level] or production_rule_name in operators or production_rule_name == 'if_pressed_else':
if production_rule_name in commands_per_level[
self.level] or production_rule_name in operators or production_rule_name == 'if_pressed_else':
# if_pressed_else is not in the yamls, upsetting lookup code to get an alternative later
# lookup should be fixed instead, making a special case for now
if production_rule_name == 'else': # use of else also has an if
Expand Down Expand Up @@ -1318,22 +1318,22 @@ def if_pressed_no_colon(self, meta, args):

def if_pressed_elifs_no_colon(self, meta, args):
# if_pressed_elifs starts with _EOL, so we need to add +1 to its line
raise exceptions.MissingColonException(command=Command.elif_, line_number=meta.line+1)
raise exceptions.MissingColonException(command=Command.elif_, line_number=meta.line + 1)

def if_pressed_elses_no_colon(self, meta, args):
# if_pressed_elses starts with _EOL, so we need to add +1 to its line
raise exceptions.MissingColonException(command=Command.else_, line_number=meta.line+1)
raise exceptions.MissingColonException(command=Command.else_, line_number=meta.line + 1)

def ifs_no_colon(self, meta, args):
raise exceptions.MissingColonException(command=Command.if_, line_number=meta.line)

def elifs_no_colon(self, meta, args):
# elifs starts with _EOL, so we need to add +1 to its line
raise exceptions.MissingColonException(command=Command.elif_, line_number=meta.line+1)
raise exceptions.MissingColonException(command=Command.elif_, line_number=meta.line + 1)

def elses_no_colon(self, meta, args):
# elses starts with _EOL, so we need to add +1 to its line
raise exceptions.MissingColonException(command=Command.else_, line_number=meta.line+1)
raise exceptions.MissingColonException(command=Command.else_, line_number=meta.line + 1)

def for_list_no_colon(self, meta, args):
raise exceptions.MissingColonException(command=Command.for_list, line_number=meta.line)
Expand Down Expand Up @@ -1873,7 +1873,8 @@ def print(self, meta, args):
exception = self.make_index_error_check_if_list(args)
return exception + f"print(f'{argument_string}'){self.add_debug_breakpoint()}"
else:
return f"""display.scroll('{argument_string}')"""
argument_string = ' '.join(args_new)
return f"display.scroll({argument_string})"

def ask(self, meta, args):
var = args[0]
Expand All @@ -1889,13 +1890,19 @@ def make_print_ask_arg(self, arg, meta, var_to_escape=''):
# therefore we should not process it anymore and thread it as a variable:
# we set the line number to 100 so there is never an issue with variable access before
# assignment (regular code will not work since random.choice(dieren) is never defined as var as such)
if "random.choice" in arg or "[" in arg:
return self.process_variable_for_fstring(arg, meta.line, var_to_escape)

# this regex splits words from non-letter characters, such that name! becomes [name, !]
p = r"[·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]+|[^·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]+"
res = regex.findall(p, arg)
return ''.join([self.process_variable_for_fstring(x, meta.line, var_to_escape) for x in res])
if not self.microbit:
if "random.choice" in arg or "[" in arg:
return self.process_variable_for_fstring(arg, meta.line, var_to_escape)
# this regex splits words from non-letter characters, such that name! becomes [name, !]
p = r"[·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]+|[^·\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]+"
res = regex.findall(p, arg)
return ''.join([self.process_variable_for_fstring(x, meta.line, var_to_escape) for x in res])
else:
if self.is_variable(arg, meta.line):
return arg
else:
# If the argument is not a variable, return it as a string literal with quotes
return f"'{arg}'"
rmagedon97 marked this conversation as resolved.
Show resolved Hide resolved

def forward(self, meta, args):
if len(args) == 0:
Expand Down Expand Up @@ -1932,31 +1939,56 @@ def play(self, meta, args):
def assign(self, meta, args):
variable_name = args[0]
value = args[1]

if self.is_random(value) or self.is_list(value):
exception = self.make_index_error_check_if_list([value])
return exception + variable_name + " = " + value + self.add_debug_breakpoint()
if not self.microbit:
if self.is_random(value) or self.is_list(value):
rmagedon97 marked this conversation as resolved.
Show resolved Hide resolved
exception = self.make_index_error_check_if_list([value])
return exception + variable_name + " = " + value + self.add_debug_breakpoint()
else:
if self.is_variable(value, meta.line): # if the value is a variable, this is a reassign
value = self.process_variable(value, meta.line)
return variable_name + " = " + value + self.add_debug_breakpoint()
else:
# if the assigned value is not a variable and contains single quotes, escape them
value = process_characters_needing_escape(value)
return variable_name + " = '" + value + "'" + self.add_debug_breakpoint()
else:
if self.is_variable(value, meta.line): # if the value is a variable, this is a reassign
value = self.process_variable(value, meta.line)
return variable_name + " = " + value + self.add_debug_breakpoint()
if self.is_random(value) or self.is_list(value):
exception = self.make_index_error_check_if_list([value])
return " " + exception + str(variable_name) + " = " + value + self.add_debug_breakpoint()
rmagedon97 marked this conversation as resolved.
Show resolved Hide resolved
else:
# if the assigned value is not a variable and contains single quotes, escape them
value = process_characters_needing_escape(value)
return variable_name + " = '" + value + "'" + self.add_debug_breakpoint()
if self.is_variable(value, meta.line): # if the value is a variable, this is a reassign
value = self.process_variable(value, meta.line)
return " " + str(variable_name) + " = " + value + self.add_debug_breakpoint()
else:
# if the assigned value is not a variable and contains single quotes, escape them
value = process_characters_needing_escape(value)
return " " + str(variable_name) + " = '" + value + "'" + self.add_debug_breakpoint()

def sleep(self, meta, args):
if not args:
return f"time.sleep(1){self.add_debug_breakpoint()}"
if not self.microbit:
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)
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
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)
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
if not args:
rmagedon97 marked this conversation as resolved.
Show resolved Hide resolved
return f"sleep(1000){self.add_debug_breakpoint()}" # Default 1 second sleep in milliseconds
else:
value = args[0]
if self.is_int(value):
# Direct conversion of seconds to milliseconds
milliseconds = int(value) * 1000
else:
# If the value is a variable, ensure it's used correctly
milliseconds = f"{value} * 1000"
return f" sleep({milliseconds}){self.add_debug_breakpoint()}"


@v_args(meta=True)
Expand Down Expand Up @@ -3073,7 +3105,8 @@ def get_parser(level, lang="en", keep_all_tokens=False, skip_faulty=False):


ParseResult = namedtuple('ParseResult', ['code', 'source_map', 'has_turtle',
'has_pressed', 'has_clear', 'has_music', 'has_sleep', 'commands', 'roles_of_variables'])
'has_pressed', 'has_clear', 'has_music', 'has_sleep', 'commands',
'roles_of_variables'])


def transpile_inner_with_skipping_faulty(input_string, level, lang="en", unused_allowed=True):
Expand Down
12 changes: 12 additions & 0 deletions tests/test_level/test_level_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,12 @@ def test_sleep(self):

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

def test_sleep_micro_bit(self):
code = "sleep 1"
expected = " sleep(1000)"

self.multi_level_tester(code=code, expected=expected, max_level=5, microbit=True)

def test_sleep_with_default_number(self):
code = "sleep 1"
expected = HedyTester.sleep_command_transpiled('"1"')
Expand Down Expand Up @@ -654,6 +660,12 @@ def test_assign(self):

self.multi_level_tester(code=code, expected=expected, max_level=11, unused_allowed=True)

def test_assign_micro_bit(self):
code = "naam is Felienne"
expected = " naam = 'Felienne'"
rmagedon97 marked this conversation as resolved.
Show resolved Hide resolved

self.multi_level_tester(code=code, expected=expected, max_level=5, unused_allowed=True, microbit=True)

def test_assign_catalan_var_name(self):
code = textwrap.dedent("""\
print És hora una nit de Netflix
Expand Down
Loading