Skip to content

Commit 49b9b5e

Browse files
🖊️ Trim leading and trailing spaces in assignments (#5936)
With this PR we explicitly trim leading and trailing spaces in assignments in levels 2-11. Note that in these levels the assignment and list assignment statements do not quotes. The program `a is three spaces ` used to be transpiled to `a = 'three spaces '` but is now transpiled to `a = 'three spaces'`. Similarly, the list `a is cat , dog , chicken ` is now transpiled to `a = ['cat', 'dog', 'chicken']`. Fixes #5684 **How to test** Ensure that the following snippet yields `[4]` twice in level 9. Ensure that there is no grammar ambiguity. ``` i = 4 # met comment print '[' i ']' if 1 = 1 j = 4 # met comment print '[' j ']' ```
1 parent 5b0b730 commit 49b9b5e

11 files changed

+96
-30
lines changed

grammars/level2-Additions.lark

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ sleep: _SLEEP (INT | var_access)?
2020

2121
//in level 2, forward may also depend on a variable
2222
turtle: _FORWARD (NUMBER | textwithoutspaces)? -> forward | _TURN ((NUMBER | textwithoutspaces))? -> turn | _COLOR ((black | blue | brown | gray | green | orange | pink | purple | red | white | yellow | textwithoutspaces))? -> color
23-
assign: var _IS text -> assign
23+
assign: var _IS textwithinnerspaces _SPACE? -> assign
2424

2525
textwithoutspaces: /([^\n #]+)/ -> text
2626
text: /([^\n#]+)/ -> text
27+
textwithinnerspaces: /([^\n#]*[^\n# ])/ -> text
2728

2829
var: NAME // used for variable definitions, e.g. a = 1
2930
var_access: NAME // used for variable references, e.g. for i in range. It parses the same as var, but does not result in a lookup table entry

grammars/level3-Additions.lark

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
command:+= assign_list | add | remove | error_add_missing_to | error_remove_missing_from | error_add_missing_list | error_remove_missing_list >> error_invalid
22
_print_ask_argument: (_SPACE | (list_access textwithoutspaces?) | textwithoutspaces)*
33

4-
assign: var _IS (list_access | text) -> assign
5-
assign_list: var _IS text_list (_COMMA text_list)+
4+
assign: var _IS (list_access | textwithinnerspaces) _SPACE? -> assign
5+
assign_list: var _IS textwithinnerspaces (_COMMA textwithinnerspaces)+ _SPACE?
66
play: _PLAY (list_access | textwithoutspaces)
77

88
list_access: var_access _AT (INT | random)
@@ -11,7 +11,8 @@ turtle: _FORWARD ((NUMBER | list_access | textwithoutspaces))? -> forward | _TUR
1111
sleep: _SLEEP (INT | list_access | var_access)?
1212
// lists are introduced and list separators (comma and arabic comma) have to excluded from text.
1313
text: /([^\n،,,、#]+)/ -> text
14-
text_list: /([^\n,،,、#]+)/ -> text // list elements are another exception since they can contain punctuation but not list separators
14+
// list elements are another exception since they can contain punctuation but not list separators. Also, they cannot end with space.
15+
textwithinnerspaces: /([^\n،,,、#]*[^\n،,,、# ])/ -> text
1516

1617
// Values which are added or removed from lists can contain spaces, so we need to escape the 'to_list' and 'from' keywords
1718
// The part " (?!<expand_keyword to_list>|<expand_keyword from>)" will be processed to a space with a negative look ahead of all to_list and from values

grammars/level5-Additions.lark

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ _if_less_command: print | ask | play | turtle | assign_list | add | remove | sle
1212
// just to be sure that if Lark tries to resolve ambiguity, error_invalid will be considered last
1313
error_invalid.-100: textwithoutspaces _SPACE* (quoted_text | textwithspaces)?
1414

15-
assign_list: var _IS textwithspaces (_COMMA textwithspaces)+
16-
assign: var _IS (list_access | textwithspaces)
17-
1815
error_print_no_quotes: _PRINT (textwithoutspaces | list_access | var_access) (_SPACE (textwithoutspaces | list_access | var_access))* -> error_print_nq
1916

2017
// new commands for level 5

grammars/level6-Additions.lark

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ play: _PLAY (list_access | expression | textwithoutspaces)
1313
equality_check: (INT | textwithoutspaces) (_IS | _EQUALS) (INT | quoted_text | textwithoutspaces)
1414
condition: >> equality_check
1515

16-
assign_list: var (_IS | _EQUALS) (INT | textwithspaces) (_COMMA (INT | textwithspaces))+
17-
assign: var (_IS | _EQUALS) (INT | list_access | expression | textwithoutspaces | textwithspaces)
16+
assign_list: var (_IS | _EQUALS) (INT | textwithinnerspaces) (_COMMA (INT | textwithinnerspaces))+ _SPACE?
17+
assign: var (_IS | _EQUALS) (INT | list_access | expression | textwithoutspaces | textwithinnerspaces) _SPACE?
1818

1919
add: _ADD_LIST (INT | text_add_remove_list) _TO_LIST _SPACE var_access
2020
remove: _REMOVE (INT | text_add_remove_list) _FROM _SPACE var_access

tests/Tester.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,12 @@ def source_map_tester(self, code, expected_source_map: dict):
388388
result = hedy.transpile(code, self.level, 'en')
389389
self.assertDictEqual(result.source_map.get_compressed_mapping(), expected_source_map)
390390

391-
def assert_translated_code_equal(self, orignal, translation):
391+
def assert_translated_code_equal(self, original, translation):
392392
# When we translate a program we lose information about the whitespaces of the original program.
393393
# So when comparing the original and the translated code, we compress multiple whitespaces into one.
394-
self.assertEqual(re.sub('\\s+', ' ', orignal), re.sub('\\s+', ' ', translation))
394+
# Also, we lose the trailing spaces, so we strip before comparing.
395+
self.assertEqual(re.sub('\\s+', ' ', original).strip(),
396+
re.sub('\\s+', ' ', translation).strip())
395397

396398
@staticmethod
397399
def validate_Python_code(parseresult):

tests/test_level/test_level_02.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def test_print_comment(self):
242242

243243
def test_assign_comment(self):
244244
code = "test is Welkom bij Hedy # This is a comment"
245-
expected = "test = 'Welkom bij Hedy '"
245+
expected = "test = 'Welkom bij Hedy'"
246246
self.multi_level_tester(
247247
max_level=3,
248248
code=code,

tests/test_level/test_level_03.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,18 @@ def test_assign_var_to_var(self):
324324

325325
self.multi_level_tester(max_level=5, code=code, expected=expected)
326326

327+
def test_assign_var_trims_spaces(self):
328+
code = "answer is This is long "
329+
expected = "answer = 'This is long'"
330+
331+
self.multi_level_tester(max_level=5, code=code, expected=expected, unused_allowed=True)
332+
333+
def test_assign_var_trims_spaces_with_comment(self):
334+
code = "answer is This is long # comment"
335+
expected = "answer = 'This is long'"
336+
337+
self.multi_level_tester(max_level=5, code=code, expected=expected, unused_allowed=True)
338+
327339
def test_assign_list(self):
328340
code = "dieren is Hond, Kat, Kangoeroe"
329341
expected = "dieren = ['Hond', 'Kat', 'Kangoeroe']"
@@ -354,11 +366,21 @@ def test_assign_list_to_hungarian_var(self):
354366

355367
self.multi_level_tester(max_level=5, code=code, expected=expected)
356368

357-
def test_assign_list_with_spaces(self):
358-
# spaces are parsed in the text here, that is fine (could be avoided if we say text
359-
# can't *end* (or start) in a space but I find this ok for now
360-
code = "dieren is Hond , Kat , Kangoeroe"
361-
expected = "dieren = ['Hond ', 'Kat ', 'Kangoeroe']"
369+
def test_assign_list_trims_elements_trailing_spaces(self):
370+
code = "dieren is Hond , Kat , Kangoeroe "
371+
expected = "dieren = ['Hond', 'Kat', 'Kangoeroe']"
372+
373+
self.multi_level_tester(max_level=5, code=code, expected=expected, unused_allowed=True)
374+
375+
def test_assign_list_trims_elements_leading_spaces(self):
376+
code = "dieren is Hond, Kat, Kangoeroe"
377+
expected = "dieren = ['Hond', 'Kat', 'Kangoeroe']"
378+
379+
self.multi_level_tester(max_level=5, code=code, expected=expected, unused_allowed=True)
380+
381+
def test_assign_list_trims_elements_spaces(self):
382+
code = "dieren is I am , waiting for , the summer "
383+
expected = "dieren = ['I am', 'waiting for', 'the summer']"
362384

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

tests/test_level/test_level_04.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ def test_print_comment(self):
517517

518518
def test_assign_comment(self):
519519
code = 'test is "Welkom bij Hedy" # This is a comment'
520-
expected = 'test = \'"Welkom bij Hedy" \''
520+
expected = """test = '"Welkom bij Hedy"'"""
521521
self.multi_level_tester(
522522
max_level=5,
523523
unused_allowed=True,

tests/test_level/test_level_06.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ def test_assign_catalan_var_name(self):
927927

928928
def test_assign_comment(self):
929929
code = 'test is "Welkom bij Hedy" # This is a comment'
930-
expected = 'test = Value(\'"Welkom bij Hedy" \')'
930+
expected = """test = Value('"Welkom bij Hedy"')"""
931931
self.multi_level_tester(
932932
max_level=11,
933933
unused_allowed=True,
@@ -954,6 +954,18 @@ def test_assign_var_to_var(self):
954954

955955
self.multi_level_tester(max_level=11, code=code, expected=expected)
956956

957+
def test_assign_var_trims_spaces(self):
958+
code = "answer is This is long "
959+
expected = "answer = Value('This is long')"
960+
961+
self.multi_level_tester(max_level=11, code=code, expected=expected, unused_allowed=True)
962+
963+
def test_assign_var_trims_spaces_with_comment(self):
964+
code = "answer is This is long # comment"
965+
expected = "answer = Value('This is long')"
966+
967+
self.multi_level_tester(max_level=11, code=code, expected=expected, unused_allowed=True)
968+
957969
def test_assign_text_with_inner_single_quote(self):
958970
code = "var is Hedy's"
959971
expected = "var = Value('Hedy\\'s')"
@@ -1042,11 +1054,21 @@ def test_assign_list_with_arabic_comma_and_is(self):
10421054
lang='ar'
10431055
)
10441056

1045-
def test_assign_list_with_spaces(self):
1046-
# spaces are parsed in the text here, that is fine (could be avoided if we say text
1047-
# can't *end* (or start) in a space but I find this ok for now
1048-
code = "dieren is Hond , Kat , Kangoeroe"
1049-
expected = "dieren = Value([Value('Hond '), Value('Kat '), Value('Kangoeroe')])"
1057+
def test_assign_list_trims_elements_trailing_spaces(self):
1058+
code = "dieren is Hond , Kat , Kangoeroe "
1059+
expected = "dieren = Value([Value('Hond'), Value('Kat'), Value('Kangoeroe')])"
1060+
1061+
self.multi_level_tester(max_level=11, code=code, expected=expected, unused_allowed=True)
1062+
1063+
def test_assign_list_trims_elements_leading_spaces(self):
1064+
code = "dieren is Hond, Kat, Kangoeroe"
1065+
expected = "dieren = Value([Value('Hond'), Value('Kat'), Value('Kangoeroe')])"
1066+
1067+
self.multi_level_tester(max_level=11, code=code, expected=expected, unused_allowed=True)
1068+
1069+
def test_assign_list_trims_elements_spaces(self):
1070+
code = "dieren is I am , waiting for , the summer "
1071+
expected = "dieren = Value([Value('I am'), Value('waiting for'), Value('the summer')])"
10501072

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

tests/test_level/test_level_08.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,32 @@ def test_equality_arabic_and_latin_vars(self):
385385

386386
self.multi_level_tester(code=code, expected=expected, max_level=11, output='jahoor!')
387387

388+
def test_assign_var_in_if_trims_spaces(self):
389+
code = self.dedent(
390+
"if 1 = 1",
391+
("j = 4 ", " "),
392+
(" print '[' j ']'", " "))
393+
394+
expected = textwrap.dedent(f"""\
395+
if localize('1') == localize('1'):
396+
j = Value('4', num_sys='Latin')
397+
print(f'[{{j}}]')""")
398+
399+
self.multi_level_tester(code=code, expected=expected, max_level=11, output='[4]')
400+
401+
def test_assign_var_in_if_trims_spaces_with_comment(self):
402+
code = textwrap.dedent("""\
403+
if 1 = 1
404+
j = 4 # met comment
405+
print '[' j ']'""")
406+
407+
expected = textwrap.dedent(f"""\
408+
if localize('1') == localize('1'):
409+
j = Value('4', num_sys='Latin')
410+
print(f'[{{j}}]')""")
411+
412+
self.multi_level_tester(code=code, expected=expected, max_level=11, output='[4]')
413+
388414
#
389415
# in/not in list
390416
#

tests/test_level/test_level_12.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -938,12 +938,7 @@ def test_print_comment(self):
938938
def test_assign_comment(self):
939939
code = 'test = "Welkom bij Hedy" # This is a comment'
940940
expected = "test = Value('Welkom bij Hedy')"
941-
self.multi_level_tester(
942-
max_level=18,
943-
code=code,
944-
expected=expected,
945-
unused_allowed=True
946-
)
941+
self.multi_level_tester(code=code, expected=expected, unused_allowed=True)
947942

948943
#
949944
# ask tests

0 commit comments

Comments
 (0)