Skip to content

Commit 7d0429f

Browse files
authored
Merge pull request #575 from boriel/feature/improve_parsing_errors
Feature/improve parsing errors
2 parents effccbc + 64f3bc3 commit 7d0429f

8 files changed

Lines changed: 84 additions & 23 deletions

File tree

src/api/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ def to_string(convention: "CONVENTION"):
202202
return convention.value
203203

204204

205+
@enum.unique
206+
class LoopType(str, enum.Enum):
207+
DO = "DO"
208+
FOR = "FOR"
209+
WHILE = "WHILE"
210+
211+
205212
# ----------------------------------------------------------------------
206213
# Deprecated suffixes for variable names, such as "a$"
207214
# ----------------------------------------------------------------------

src/api/errmsg.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,18 @@ def syntax_error_mandatory_param_after_optional(lineno: int, param1: str, param2
333333
error(lineno, f"Can't declare mandatory param '{param2}' after optional param '{param1}'")
334334

335335

336+
# ----------------------------------------
337+
# FOR without NEXT
338+
# ----------------------------------------
339+
def syntax_error_for_without_next(lineno: int):
340+
error(lineno, "FOR without NEXT")
341+
342+
343+
# ----------------------------------------
344+
# FOR without NEXT
345+
# ----------------------------------------
346+
def syntax_error_loop_not_closed(lineno: int, loop_type: str):
347+
error(lineno, f"{loop_type} loop not closed")
348+
349+
336350
# endregion

src/api/global_.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,12 @@
88
# This program is Free Software and is released under the terms of
99
# the GNU General License
1010
# ----------------------------------------------------------------------
11-
12-
from typing import Dict
13-
from typing import Optional
14-
from typing import Set
11+
from typing import Dict, List, NamedTuple, Optional, Set
1512

1613
import src.api
1714

1815
from src.api.opcodestemps import OpcodesTemps
19-
from src.api.constants import TYPE
16+
from src.api.constants import TYPE, LoopType
2017

2118
# ----------------------------------------------------------------------
2219
# Simple global container for internal constants.
@@ -26,6 +23,13 @@
2623
# Don't touch unless you know what are you doing
2724
# ----------------------------------------------------------------------
2825

26+
27+
class LoopInfo(NamedTuple):
28+
type: LoopType # LOOP type: FOR, DO, LOOP, WHILE ...
29+
lineno: int # line where this loop started
30+
var: Optional[str] = None # Var name used in FOR loop
31+
32+
2933
# ----------------------------------------------------------------------
3034
# Initializes a singleton container
3135
# ----------------------------------------------------------------------
@@ -38,7 +42,7 @@
3842
# which kind of loop the parser is in: e.g. 'FOR', 'WHILE', or 'DO'.
3943
# Nested loops are appended at the end, and popped out on loop exit.
4044
# ----------------------------------------------------------------------
41-
LOOPS = []
45+
LOOPS: List[LoopInfo] = []
4246

4347
# ----------------------------------------------------------------------
4448
# Each new scope push the current LOOPS state and reset LOOPS. Upon

src/zxbc/zxbparser.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from src.api.opcodestemps import OpcodesTemps
3030
from src.api.errmsg import error
3131
from src.api.errmsg import warning
32+
from src.api.global_ import LoopInfo
3233

3334
from src.api.check import check_and_make_label
3435
from src.api.check import common_type
@@ -45,6 +46,7 @@
4546
from src.api.constants import CLASS
4647
from src.api.constants import SCOPE
4748
from src.api.constants import CONVENTION
49+
from src.api.constants import LoopType
4850

4951
import src.api.symboltable
5052
import src.api.config
@@ -1535,8 +1537,8 @@ def p_next1(p):
15351537
p1 = p[1]
15361538
p3 = p[3]
15371539

1538-
if p3 != gl.LOOPS[-1][1]:
1539-
src.api.errmsg.syntax_error_wrong_for_var(p.lineno(2), gl.LOOPS[-1][1], p3)
1540+
if p3 != gl.LOOPS[-1].var:
1541+
src.api.errmsg.syntax_error_wrong_for_var(p.lineno(2), gl.LOOPS[-1].var, p3)
15401542
p[0] = make_nop()
15411543
return
15421544

@@ -1545,7 +1547,7 @@ def p_next1(p):
15451547

15461548
def p_for_sentence_start(p):
15471549
"""for_start : FOR ID EQ expr TO expr step"""
1548-
gl.LOOPS.append(("FOR", p[2]))
1550+
gl.LOOPS.append(LoopInfo(type=LoopType.FOR, lineno=p.lineno(1), var=p[2]))
15491551
p[0] = None
15501552

15511553
if p[4] is None or p[6] is None or p[7] is None:
@@ -1633,7 +1635,7 @@ def p_do_loop(p):
16331635
q = p[2]
16341636

16351637
if p[1] == "DO":
1636-
gl.LOOPS.append(("DO",))
1638+
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))
16371639

16381640
if q is None:
16391641
warning(p.lineno(1), "Infinite empty loop")
@@ -1656,7 +1658,7 @@ def p_do_loop_until(p):
16561658
r = p[4]
16571659

16581660
if p[1] == "DO":
1659-
gl.LOOPS.append(("DO",))
1661+
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))
16601662

16611663
p[0] = make_sentence(p.lineno(1), "DO_UNTIL", r, q)
16621664

@@ -1779,7 +1781,7 @@ def p_do_loop_while(p):
17791781
r = p[4]
17801782

17811783
if p[1] == "DO":
1782-
gl.LOOPS.append(("DO",))
1784+
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))
17831785

17841786
p[0] = make_sentence(p.lineno(1), "DO_WHILE", r, q)
17851787
gl.LOOPS.pop()
@@ -1827,20 +1829,20 @@ def p_do_until_loop(p):
18271829
def p_do_while_start(p):
18281830
"""do_while_start : DO WHILE expr"""
18291831
p[0] = p[3]
1830-
gl.LOOPS.append(("DO",))
1832+
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))
18311833

18321834

18331835
def p_do_until_start(p):
18341836
"""do_until_start : DO UNTIL expr"""
18351837
p[0] = p[3]
1836-
gl.LOOPS.append(("DO",))
1838+
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))
18371839

18381840

18391841
def p_do_start(p):
18401842
"""do_start : DO CO
18411843
| DO NEWLINE
18421844
"""
1843-
gl.LOOPS.append(("DO",))
1845+
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))
18441846

18451847

18461848
def p_label_end_while(p):
@@ -1874,7 +1876,7 @@ def p_while_sentence(p):
18741876
def p_while_start(p):
18751877
"""while_start : WHILE expr"""
18761878
p[0] = p[2]
1877-
gl.LOOPS.append(("WHILE",))
1879+
gl.LOOPS.append(LoopInfo(LoopType.WHILE, p.lineno(1)))
18781880
if is_number(p[2]) and not p[2].value:
18791881
src.api.errmsg.warning_condition_is_always(p.lineno(1))
18801882

@@ -1887,8 +1889,8 @@ def p_exit(p):
18871889
q = p[2]
18881890
p[0] = make_sentence(p.lineno(1), "EXIT_%s" % q)
18891891

1890-
for i in gl.LOOPS:
1891-
if q == i[0]:
1892+
for loop in gl.LOOPS:
1893+
if q == loop.type:
18921894
return
18931895

18941896
error(p.lineno(1), "Syntax Error: EXIT %s out of loop" % q)
@@ -3391,17 +3393,28 @@ def p_abs(p):
33913393
# The yyerror function
33923394
# ----------------------------------------
33933395
def p_error(p):
3394-
gl.has_errors += 1
3395-
33963396
if p is not None:
33973397
if p.type != "NEWLINE":
33983398
msg = "Syntax Error. Unexpected token '%s' <%s>" % (p.value, p.type)
33993399
else:
34003400
msg = "Unexpected end of line"
34013401
error(p.lineno, msg)
3402-
else:
3403-
msg = "Unexpected end of file"
3404-
error(zxblex.lexer.lineno, msg)
3402+
return
3403+
3404+
# Try to give some hints
3405+
if gl.LOOPS: # some loop(s) are not closed
3406+
loop_info = gl.LOOPS[-1]
3407+
if loop_info.type == LoopType.FOR:
3408+
src.api.errmsg.syntax_error_for_without_next(loop_info.lineno)
3409+
else:
3410+
src.api.errmsg.syntax_error_loop_not_closed(loop_info.lineno, loop_info.type)
3411+
# If there were previous errors, stop here
3412+
# since this end of file is due to previous errors
3413+
if gl.has_errors:
3414+
return
3415+
3416+
msg = "Unexpected end of file"
3417+
error(zxblex.lexer.lineno, msg)
34053418

34063419

34073420
# ----------------------------------------

tests/functional/test_errmsg.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ dim_str_error0.bas:3: error: Cannot initialize array of type string
206206
>>> process_file('zx48k/dim_str_error1.bas')
207207
dim_str_error1.bas:3: error: Cannot initialize array of type string
208208

209+
# Test parsing error improvements
210+
>>> process_file('zx48k/for_err.bas')
211+
for_err.bas:3: error: FOR without NEXT
212+
>>> process_file('zx48k/while_err.bas')
213+
while_err.bas:3: error: WHILE loop not closed
214+
>>> process_file('zx48k/do_err.bas')
215+
do_err.bas:3: error: DO loop not closed
216+
209217
# Unreachable code detection
210218
# Should not emit warning for these case:
211219
>>> process_file('zx48k/warn_unreach0.bas')

tests/functional/zx48k/do_err.bas

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DIM i, c as Ubyte
2+
3+
DO
4+
LET c = c * 2
5+

tests/functional/zx48k/for_err.bas

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DIM i, c as Ubyte
2+
3+
FOR i = 1 TO 5
4+
LET c = c * 2
5+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DIM i, c as Ubyte
2+
3+
WHILE i = 1
4+
LET c = c * 2
5+

0 commit comments

Comments
 (0)