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

🖊️ Make ask command symmetrical to print command in terms of input #5362

Merged
merged 4 commits into from
Apr 16, 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
2 changes: 1 addition & 1 deletion grammars/level12-Additions.lark
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ call: _CALL function_name (_SPACE _WITH arguments)?

function_name: NAME -> text

?return: _RETURN (_print_argument)? -> returns
?return: _RETURN (_print_ask_argument)? -> returns
?simple_expression:+= call
?turtle_color:+= call

Expand Down
2 changes: 1 addition & 1 deletion grammars/level16-Additions.lark
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ error_assign_list_missing_brackets: var (_IS | _EQUALS) (_LEFT_SQUARE_BRACKET)?

error_list_access_at: var_access _AT (INT | random)

_print_argument: (_SPACE | quoted_text | list_access | error_list_access_at | expression | print_expression)*
_print_ask_argument: (_SPACE | quoted_text | list_access | error_list_access_at | expression | print_expression)*
assign: var (_IS| _EQUALS) (list_access | error_list_access_at | atom | expression)
error_print_no_quotes: _PRINT (textwithoutspaces | list_access | error_list_access_at | var_access) (_SPACE (textwithoutspaces | list_access | var_access))* -> error_print_nq
sleep: _SLEEP (INT | list_access | error_list_access_at | var_access | expression)?
Expand Down
8 changes: 4 additions & 4 deletions grammars/level2-Additions.lark
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//add the rules after += remove those after -= and the ones after >> should be at the end
command: += error_ask_dep_2 | error_ask_missing_variable | assign | error_echo_dep_2 | error_non_decimal | sleep -= echo >> error_invalid
print: _PRINT (_print_argument)?
_print_argument: (_SPACE | textwithoutspaces)*
ask: var _IS _ASK ((_SPACE | text)*)?
error_ask_missing_variable: _STANDALONE_IS _SPACE _ASK text
print: _PRINT (_print_ask_argument)?
ask: var _IS _ASK (_print_ask_argument)?
_print_ask_argument: (_SPACE | textwithoutspaces)*

error_ask_missing_variable: _STANDALONE_IS _SPACE _ASK text
error_non_decimal: ((textwithoutspaces | _SPACE)*)? NON_DECIMAL (text*)?
//level 1 deprecated commands, for now manually added for better errors
error_ask_dep_2: _ASK (error_text_dep_2)?
Expand Down
4 changes: 1 addition & 3 deletions grammars/level3-Additions.lark
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
command:+= assign_list | add | remove | error_add_missing_to | error_remove_missing_from >> error_invalid
_print_argument: (_SPACE | (list_access textwithoutspaces?) | textwithoutspaces)*
ask: var _IS _ASK ((_SPACE | text_ask)*)?
_print_ask_argument: (_SPACE | (list_access textwithoutspaces?) | textwithoutspaces)*

assign: var _IS (list_access | text) -> assign
assign_list: var _IS text_list (_COMMA text_list)+
Expand All @@ -12,7 +11,6 @@ turtle: _FORWARD ((NUMBER | list_access | textwithoutspaces))? -> forward | _TUR
sleep: _SLEEP (INT | list_access | var_access)?
// lists are introduced and list separators (comma and arabic comma) have to excluded from text.
text: /([^\n،,,、#]+)/ -> text
text_ask: /([^\n#]+)/ -> text // the ask command is an exception since it needs to include commas in its value
text_list: /([^\n,،,、#]+)/ -> text // list elements are another exception since they can contain punctuation but not list separators

// FH, jan 22: not exactly sure why this works, while textwithoutspaces parses the whole line in add/remove
Expand Down
15 changes: 6 additions & 9 deletions grammars/level4-Additions.lark
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
// redefining it entirely since it has many order-depending rules (e.g ask_no_quotes should be after ask and before assign)
command: clear | error_non_decimal | print | ask | play | turtle | assign_list | add | remove | error_add_missing_to | error_remove_missing_from | sleep | error_list_access | error_ask_no_quotes| assign | error_invalid_space | error_print_no_quotes | error_print_one_quote_only | error_text_no_print |error_invalid | empty_line

print: _PRINT (_print_argument)? -> print
_print_ask_argument: (_SPACE | list_access | error_list_access | quoted_text | var_access_print)*

error_print_no_quotes: _PRINT text -> error_print_nq
error_ask_no_quotes: var _IS _ASK text -> error_print_nq

textwithoutsinglequote: /([^\n']+)/ -> text
textwithoutdoublequote: /([^\n"]+)/ -> text
error_print_one_quote_only: _PRINT "'" textwithoutsinglequote -> error_print_nq | _PRINT "\"" textwithoutdoublequote -> error_print_nq | _PRINT textwithoutsinglequote "'" -> error_print_nq | _PRINT textwithoutdoublequote "\"" -> error_print_nq


// FH, feb 2023 Shall we rename this 'var' to declare maybe? I confused myself :)
ask: var _IS _ASK (_print_argument)?
error_ask_no_quotes: var _IS _ASK text -> error_print_nq
error_ask_one_quote_only: var _IS _ASK "'" textwithoutsinglequote -> error_print_nq | var _IS _ASK "\"" textwithoutdoublequote -> error_print_nq | var _IS _ASK textwithoutsinglequote "'" -> error_print_nq | var _IS _ASK textwithoutdoublequote "\"" -> error_print_nq

error_list_access: var_access _SPACE textwithoutspaces _SPACE random

list_access: var_access _AT (INT | random | var_access)
_print_argument: (_SPACE | list_access | error_list_access | quoted_text | var_access_print)*

error_text_no_print: quoted_text

list_access: var_access _AT (INT | random | var_access)

// literal strings must be single-quoted in ask and print commands
// anything can be parsed except for a newline, a space and a list separator
textwithoutspaces: /([^\n،,,、 ]+)/ -> text
Expand Down
6 changes: 3 additions & 3 deletions grammars/level6-Additions.lark
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
_print_argument: (_SPACE | quoted_text | list_access | expression | print_expression)*
_print_ask_argument: (_SPACE | quoted_text | list_access | expression | print_expression)*
?print_expression: var_access_print | error_unsupported_number | INT

// redefining it entirely since it has many order-depending rules
command: clear | error_non_decimal | print | turtle | play | add | remove | error_add_missing_to | error_remove_missing_from | sleep | error_print_no_quotes | error_print_one_quote_only | if_pressed_else | error_if_pressed_missing_else | if_pressed_without_else | ifelse | ifs | ask | error_ask_no_quotes| assign_button | assign | assign_list | error_invalid_space | error_text_no_print | empty_line
_if_less_command: print | turtle | play | add | remove | sleep | error_print_no_quotes | error_print_one_quote_only | ask | error_ask_no_quotes | assign_button | assign | assign_list

//splitting these commands into two rules, one for equals and one for is so they can be properly handled in the translator
ask: var (_IS | _EQUALS) _ASK (_print_argument)?
//splitting these commands into two rules, one for equals and one for is so they can be properly handled in the translator
ask: var (_IS | _EQUALS) _ASK (_print_ask_argument)?

play: _PLAY (list_access | expression | textwithoutspaces)

Expand Down
106 changes: 62 additions & 44 deletions hedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,21 +345,23 @@ def command_make_color_local(language):
# Commands and their types per level (only partially filled!)
commands_and_types_per_level = {
Command.print: {
1: [HedyType.string, HedyType.integer, HedyType.input],
1: [HedyType.string, HedyType.integer, HedyType.input, HedyType.list],
4: [HedyType.string, HedyType.integer, HedyType.input],
12: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float],
16: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float, HedyType.list]
},
Command.ask: {
1: [HedyType.string, HedyType.integer, HedyType.input],
1: [HedyType.string, HedyType.integer, HedyType.input, HedyType.list],
4: [HedyType.string, HedyType.integer, HedyType.input],
12: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float],
16: [HedyType.string, HedyType.integer, HedyType.input, HedyType.float, HedyType.list]
},
Command.turn: {1: command_turn_literals,
2: [HedyType.integer, HedyType.input],
12: [HedyType.integer, HedyType.input, HedyType.float]
},
Command.color: {1: english_colors,
2: [english_colors, HedyType.string, HedyType.input]},
Command.color: {1: [english_colors, HedyType.list],
2: [english_colors, HedyType.string, HedyType.input, HedyType.list]},
Command.forward: {1: [HedyType.integer, HedyType.input],
12: [HedyType.integer, HedyType.input, HedyType.float]
},
Expand Down Expand Up @@ -1367,14 +1369,28 @@ def add_debug_breakpoint(self):
else:
return ""

def is_var_defined_before_access(self, variable_name, access_line_number):
all_names_before_access_line = [a.name for a in self.lookup if a.definition_line <= access_line_number]
return variable_name in all_names_before_access_line

def is_var_in_lookup(self, variable_name):
all_names = [a.name for a in self.lookup]
return variable_name in all_names

# The var_to_escape parameter allows the ask command to force the variable defined
# by itself, i.e. the left-hand-side var, to not be defined on the same line
def is_var_defined_before_access(self, variable_name, access_line_number, var_to_escape=''):
def is_before(entry, line):
return entry.definition_line <= line if entry.name != var_to_escape else entry.definition_line < line

all_names_before_access_line = [entry.name for entry in self.lookup if is_before(entry, access_line_number)]
return variable_name in all_names_before_access_line

# In level 3, a list name without index or random should be treated as a literal string, e.g.
# color = red, blue, yellow
# print What is your favorite color? <- color is not a var reference here, but the text 'color'
def is_unreferenced_list(self, variable_name):
for entry in self.lookup:
if entry.name == escape_var(variable_name):
return entry.type_ == HedyType.list and '[' not in variable_name
return False

# default for line number is max lines so if it is not given, there
# is no check on whether the var is defined
def is_variable(self, variable_name, access_line_number=100):
Expand Down Expand Up @@ -1418,9 +1434,10 @@ def process_variable_without_quotes(self, arg, access_line_number=100):
return escape_var(arg)
return arg

def process_variable_for_fstring(self, variable_name, access_line_number=100):
if self.is_var_in_lookup(variable_name) and self.is_var_defined_before_access(variable_name,
access_line_number):
def process_variable_for_fstring(self, variable_name, access_line_number=100, var_to_escape=''):

if (self.is_var_defined_before_access(variable_name, access_line_number, var_to_escape) and
not self.is_unreferenced_list(variable_name)):
self.add_variable_access_location(variable_name, access_line_number)
return "{" + escape_var(variable_name) + "}"
else:
Expand Down Expand Up @@ -1769,34 +1786,35 @@ def var_access_print(self, meta, args):
return self.var_access(meta, args)

def print(self, meta, args):
args_new = []
for a in args:
# list access has been already rewritten since it occurs lower in the tree
# so when we encounter it as a child of print it will not be a subtree, but
# transpiled code (for example: random.choice(dieren))
# 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 a or "[" in a:
args_new.append(self.process_variable_for_fstring(a, meta.line))
else:
# this regex splits words from non-letter characters, such that name! becomes [name, !]
res = regex.findall(
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}]+",
a)
args_new.append(''.join([self.process_variable_for_fstring(x, meta.line) for x in res]))
exception = self.make_index_error_check_if_list(args)
args_new = [self.make_print_ask_arg(a, meta) for a in args]
argument_string = ' '.join(args_new)
if not self.microbit:
exception = self.make_index_error_check_if_list(args)
return exception + f"print(f'{argument_string}'){self.add_debug_breakpoint()}"
else:
return textwrap.dedent(f"""\
display.scroll('{argument_string}')""")
return f"""display.scroll('{argument_string}')"""

def ask(self, meta, args):
var = args[0]
all_parameters = ["'" + process_characters_needing_escape(a) + "'" for a in args[1:]]
return f'{var} = input(' + '+'.join(all_parameters) + ")" + self.add_debug_breakpoint()
args_new = [self.make_print_ask_arg(a, meta, var) for a in args[1:]]
argument_string = ' '.join(args_new)
exception = self.make_index_error_check_if_list(args)
return exception + f"{var} = input(f'{argument_string}'){self.add_debug_breakpoint()}"

def make_print_ask_arg(self, arg, meta, var_to_escape=''):
# list access has been already rewritten since it occurs lower in the tree
# so when we encounter it as a child of print it will not be a subtree, but
# transpiled code (for example: random.choice(dieren))
# 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])

def forward(self, meta, args):
if len(args) == 0:
Expand Down Expand Up @@ -1968,7 +1986,8 @@ def print(self, meta, args):
def ask(self, meta, args):
var = args[0]
argument_string = self.print_ask_args(meta, args[1:])
return f"{var} = input(f'{argument_string}'){self.add_debug_breakpoint()}"
index_check = self.make_index_error_check_if_list(args)
return index_check + f"{var} = input(f'{argument_string}'){self.add_debug_breakpoint()}"

def error_print_nq(self, meta, args):
return ConvertToPython_2.print(self, meta, args)
Expand Down Expand Up @@ -2452,17 +2471,16 @@ def print(self, meta, args):
def ask(self, meta, args):
var = args[0]
argument_string = self.print_ask_args(meta, args[1:])
assign = f"{var} = input(f'''{argument_string}''')" + self.add_debug_breakpoint()

return textwrap.dedent(f"""\
{assign}
try:
{var} = int({var})
except ValueError:
try:
{var} = float({var})
except ValueError:
pass""") # no number? leave as string
exception = self.make_index_error_check_if_list(args)
return exception + textwrap.dedent(f"""\
{var} = input(f'''{argument_string}'''){self.add_debug_breakpoint()}
try:
{var} = int({var})
except ValueError:
try:
{var} = float({var})
except ValueError:
pass""") # no number? leave as string

def assign_list(self, meta, args):
parameter = args[0]
Expand Down
Loading
Loading