Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request boriel-basic#575 from boriel/feature/improve_parsi…
Browse files Browse the repository at this point in the history
…ng_errors

Feature/improve parsing errors
  • Loading branch information
boriel committed Oct 12, 2021
2 parents effccbc + 64f3bc3 commit 7d0429f
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 23 deletions.
7 changes: 7 additions & 0 deletions src/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@ def to_string(convention: "CONVENTION"):
return convention.value


@enum.unique
class LoopType(str, enum.Enum):
DO = "DO"
FOR = "FOR"
WHILE = "WHILE"


# ----------------------------------------------------------------------
# Deprecated suffixes for variable names, such as "a$"
# ----------------------------------------------------------------------
Expand Down
14 changes: 14 additions & 0 deletions src/api/errmsg.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,18 @@ def syntax_error_mandatory_param_after_optional(lineno: int, param1: str, param2
error(lineno, f"Can't declare mandatory param '{param2}' after optional param '{param1}'")


# ----------------------------------------
# FOR without NEXT
# ----------------------------------------
def syntax_error_for_without_next(lineno: int):
error(lineno, "FOR without NEXT")


# ----------------------------------------
# FOR without NEXT
# ----------------------------------------
def syntax_error_loop_not_closed(lineno: int, loop_type: str):
error(lineno, f"{loop_type} loop not closed")


# endregion
16 changes: 10 additions & 6 deletions src/api/global_.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@
# This program is Free Software and is released under the terms of
# the GNU General License
# ----------------------------------------------------------------------

from typing import Dict
from typing import Optional
from typing import Set
from typing import Dict, List, NamedTuple, Optional, Set

import src.api

from src.api.opcodestemps import OpcodesTemps
from src.api.constants import TYPE
from src.api.constants import TYPE, LoopType

# ----------------------------------------------------------------------
# Simple global container for internal constants.
Expand All @@ -26,6 +23,13 @@
# Don't touch unless you know what are you doing
# ----------------------------------------------------------------------


class LoopInfo(NamedTuple):
type: LoopType # LOOP type: FOR, DO, LOOP, WHILE ...
lineno: int # line where this loop started
var: Optional[str] = None # Var name used in FOR loop


# ----------------------------------------------------------------------
# Initializes a singleton container
# ----------------------------------------------------------------------
Expand All @@ -38,7 +42,7 @@
# which kind of loop the parser is in: e.g. 'FOR', 'WHILE', or 'DO'.
# Nested loops are appended at the end, and popped out on loop exit.
# ----------------------------------------------------------------------
LOOPS = []
LOOPS: List[LoopInfo] = []

# ----------------------------------------------------------------------
# Each new scope push the current LOOPS state and reset LOOPS. Upon
Expand Down
47 changes: 30 additions & 17 deletions src/zxbc/zxbparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from src.api.opcodestemps import OpcodesTemps
from src.api.errmsg import error
from src.api.errmsg import warning
from src.api.global_ import LoopInfo

from src.api.check import check_and_make_label
from src.api.check import common_type
Expand All @@ -45,6 +46,7 @@
from src.api.constants import CLASS
from src.api.constants import SCOPE
from src.api.constants import CONVENTION
from src.api.constants import LoopType

import src.api.symboltable
import src.api.config
Expand Down Expand Up @@ -1535,8 +1537,8 @@ def p_next1(p):
p1 = p[1]
p3 = p[3]

if p3 != gl.LOOPS[-1][1]:
src.api.errmsg.syntax_error_wrong_for_var(p.lineno(2), gl.LOOPS[-1][1], p3)
if p3 != gl.LOOPS[-1].var:
src.api.errmsg.syntax_error_wrong_for_var(p.lineno(2), gl.LOOPS[-1].var, p3)
p[0] = make_nop()
return

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

def p_for_sentence_start(p):
"""for_start : FOR ID EQ expr TO expr step"""
gl.LOOPS.append(("FOR", p[2]))
gl.LOOPS.append(LoopInfo(type=LoopType.FOR, lineno=p.lineno(1), var=p[2]))
p[0] = None

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

if p[1] == "DO":
gl.LOOPS.append(("DO",))
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))

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

if p[1] == "DO":
gl.LOOPS.append(("DO",))
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))

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

Expand Down Expand Up @@ -1779,7 +1781,7 @@ def p_do_loop_while(p):
r = p[4]

if p[1] == "DO":
gl.LOOPS.append(("DO",))
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))

p[0] = make_sentence(p.lineno(1), "DO_WHILE", r, q)
gl.LOOPS.pop()
Expand Down Expand Up @@ -1827,20 +1829,20 @@ def p_do_until_loop(p):
def p_do_while_start(p):
"""do_while_start : DO WHILE expr"""
p[0] = p[3]
gl.LOOPS.append(("DO",))
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))


def p_do_until_start(p):
"""do_until_start : DO UNTIL expr"""
p[0] = p[3]
gl.LOOPS.append(("DO",))
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))


def p_do_start(p):
"""do_start : DO CO
| DO NEWLINE
"""
gl.LOOPS.append(("DO",))
gl.LOOPS.append(LoopInfo(LoopType.DO, p.lineno(1)))


def p_label_end_while(p):
Expand Down Expand Up @@ -1874,7 +1876,7 @@ def p_while_sentence(p):
def p_while_start(p):
"""while_start : WHILE expr"""
p[0] = p[2]
gl.LOOPS.append(("WHILE",))
gl.LOOPS.append(LoopInfo(LoopType.WHILE, p.lineno(1)))
if is_number(p[2]) and not p[2].value:
src.api.errmsg.warning_condition_is_always(p.lineno(1))

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

for i in gl.LOOPS:
if q == i[0]:
for loop in gl.LOOPS:
if q == loop.type:
return

error(p.lineno(1), "Syntax Error: EXIT %s out of loop" % q)
Expand Down Expand Up @@ -3391,17 +3393,28 @@ def p_abs(p):
# The yyerror function
# ----------------------------------------
def p_error(p):
gl.has_errors += 1

if p is not None:
if p.type != "NEWLINE":
msg = "Syntax Error. Unexpected token '%s' <%s>" % (p.value, p.type)
else:
msg = "Unexpected end of line"
error(p.lineno, msg)
else:
msg = "Unexpected end of file"
error(zxblex.lexer.lineno, msg)
return

# Try to give some hints
if gl.LOOPS: # some loop(s) are not closed
loop_info = gl.LOOPS[-1]
if loop_info.type == LoopType.FOR:
src.api.errmsg.syntax_error_for_without_next(loop_info.lineno)
else:
src.api.errmsg.syntax_error_loop_not_closed(loop_info.lineno, loop_info.type)
# If there were previous errors, stop here
# since this end of file is due to previous errors
if gl.has_errors:
return

msg = "Unexpected end of file"
error(zxblex.lexer.lineno, msg)


# ----------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions tests/functional/test_errmsg.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ dim_str_error0.bas:3: error: Cannot initialize array of type string
>>> process_file('zx48k/dim_str_error1.bas')
dim_str_error1.bas:3: error: Cannot initialize array of type string

# Test parsing error improvements
>>> process_file('zx48k/for_err.bas')
for_err.bas:3: error: FOR without NEXT
>>> process_file('zx48k/while_err.bas')
while_err.bas:3: error: WHILE loop not closed
>>> process_file('zx48k/do_err.bas')
do_err.bas:3: error: DO loop not closed

# Unreachable code detection
# Should not emit warning for these case:
>>> process_file('zx48k/warn_unreach0.bas')
Expand Down
5 changes: 5 additions & 0 deletions tests/functional/zx48k/do_err.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DIM i, c as Ubyte

DO
LET c = c * 2

5 changes: 5 additions & 0 deletions tests/functional/zx48k/for_err.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DIM i, c as Ubyte

FOR i = 1 TO 5
LET c = c * 2

5 changes: 5 additions & 0 deletions tests/functional/zx48k/while_err.bas
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DIM i, c as Ubyte

WHILE i = 1
LET c = c * 2

0 comments on commit 7d0429f

Please sign in to comment.