Skip to content

Commit 41630d6

Browse files
committed
Allow enabling / disabling warning
1 parent 3961057 commit 41630d6

8 files changed

Lines changed: 225 additions & 104 deletions

File tree

examples/pong.bas

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
10 REM Pong! Game
1+
ccvfcxbnbnm10 REM Pong! Game
22

33
14 REM Digit data
44
REM 128 => "\ "

src/api/errmsg.py

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@
1010
# ----------------------------------------------------------------------
1111

1212
import sys
13+
from functools import wraps
14+
15+
from typing import Callable
1316
from typing import Optional
1417

1518
from . import global_
1619
from .config import OPTIONS
1720

21+
1822
# Exports only these functions. Others
19-
__all__ = ['error', 'warning']
23+
__all__ = [
24+
'error',
25+
'is_valid_warning_code',
26+
'warning',
27+
'warning_not_used'
28+
]
29+
30+
31+
WARNING_PREFIX: str = '' # will be prepended to warning messages
32+
ERROR_PREFIX: str = '' # will be prepended to error messages
2033

2134

2235
def msg_output(msg: str) -> None:
@@ -42,7 +55,7 @@ def error(lineno: int, msg: str, fname: Optional[str] = None) -> None:
4255
if global_.has_errors > OPTIONS.max_syntax_errors:
4356
msg = 'Too many errors. Giving up!'
4457

45-
msg = "%s:%i: error: %s" % (fname, lineno, msg)
58+
msg = "%s:%i: error:%s %s" % (fname, lineno, ERROR_PREFIX, msg)
4659
msg_output(msg)
4760

4861
if global_.has_errors > OPTIONS.max_syntax_errors:
@@ -61,10 +74,47 @@ def warning(lineno: int, msg: str, fname: Optional[str] = None) -> None:
6174
if fname is None:
6275
fname = global_.FILENAME
6376

64-
msg = "%s:%i: warning: %s" % (fname, lineno, msg)
77+
msg = "%s:%i: %s %s" % (fname, lineno, WARNING_PREFIX or 'warning:', msg)
6578
msg_output(msg)
6679

6780

81+
def is_valid_warning_code(code: str) -> bool:
82+
return code in global_.ENABLED_WARNINGS
83+
84+
85+
def assert_is_valid_warning_code(code: str):
86+
assert is_valid_warning_code(code), f"Invalid warning code '{code}'"
87+
88+
89+
def enable_warning(code: str):
90+
assert_is_valid_warning_code(code)
91+
global_.ENABLED_WARNINGS[code] = True
92+
93+
94+
def disable_warning(code: str):
95+
assert_is_valid_warning_code(code)
96+
global_.ENABLED_WARNINGS[code] = False
97+
98+
99+
def register_warning(code: str) -> Callable:
100+
assert code not in global_.ENABLED_WARNINGS, f"Duplicated warning code '{code}'"
101+
global_.ENABLED_WARNINGS[code] = True
102+
103+
def decorator(func: Callable) -> Callable:
104+
def wrapper(*args, **kwargs):
105+
global WARNING_PREFIX
106+
if global_.ENABLED_WARNINGS.get(code, True):
107+
WARNING_PREFIX = f'warning: [W{code}]'
108+
func(*args, **kwargs)
109+
WARNING_PREFIX = ''
110+
111+
return wraps(func)(wrapper)
112+
113+
return decorator
114+
115+
116+
# region [Warnings]
117+
@register_warning('100')
68118
def warning_implicit_type(lineno: int, id_: str, type_: str = None):
69119
""" Warning: Using default implicit type 'x'
70120
"""
@@ -78,49 +128,59 @@ def warning_implicit_type(lineno: int, id_: str, type_: str = None):
78128
warning(lineno, "Using default implicit type '%s' for '%s'" % (type_, id_))
79129

80130

131+
@register_warning('110')
81132
def warning_condition_is_always(lineno: int, cond: bool = False):
82133
""" Warning: Condition is always false/true
83134
"""
84135
warning(lineno, "Condition is always %s" % cond)
85136

86137

138+
@register_warning('120')
87139
def warning_conversion_lose_digits(lineno: int):
88140
""" Warning: Conversion may lose significant digits
89141
"""
90142
warning(lineno, 'Conversion may lose significant digits')
91143

92144

145+
@register_warning('130')
93146
def warning_empty_loop(lineno: int):
94147
""" Warning: Empty loop
95148
"""
96149
warning(lineno, 'Empty loop')
97150

98151

152+
@register_warning('140')
99153
def warning_empty_if(lineno):
100154
""" Warning: Useless empty IF ignored
101155
"""
102156
warning(lineno, 'Useless empty IF ignored')
103157

104158

105-
# Emits an optimization warning
159+
@register_warning('150')
106160
def warning_not_used(lineno, id_, kind='Variable'):
161+
""" Emits an optimization warning
162+
"""
107163
if OPTIONS.optimization > 0:
108164
warning(lineno, "%s '%s' is never used" % (kind, id_))
109165

166+
# endregion
167+
168+
# region [Syntax Errors]
169+
110170

111171
# ----------------------------------------
112172
# Syntax error: Expected string instead of
113173
# numeric expression.
114174
# ----------------------------------------
115-
def syntax_error_expected_string(lineno: str, _type: str):
175+
def syntax_error_expected_string(lineno: int, _type: str):
116176
error(lineno, "Expected a 'string' type expression, got '%s' instead" % _type)
117177

118178

119179
# ----------------------------------------
120180
# Syntax error: FOR variable should be X
121181
# instead of Y
122182
# ----------------------------------------
123-
def syntax_error_wrong_for_var(lineno: str, x: str, y: str):
183+
def syntax_error_wrong_for_var(lineno: int, x: str, y: str):
124184
error(lineno, "FOR variable should be '%s' instead of '%s'" % (x, y))
125185

126186

@@ -205,3 +265,5 @@ def syntax_error_address_must_be_constant(lineno: int):
205265
# ----------------------------------------
206266
def syntax_error_cannot_pass_array_by_value(lineno: int, id_: str):
207267
error(lineno, "Array parameter '%s' must be passed ByRef" % id_)
268+
269+
# endregion

src/api/global_.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#
1717
# Don't touch unless you know what are you doing
1818
# ----------------------------------------------------------------------
19+
from typing import Dict
1920

2021
from .opcodestemps import OpcodesTemps
2122
from .constants import TYPE
@@ -156,3 +157,9 @@
156157
# Cache of Message errors to avoid repetition
157158
# ----------------------------------------------------------------------
158159
error_msg_cache = set()
160+
161+
162+
# ----------------------------------------------------------------------
163+
# Warning codes and whether they're enabled or not
164+
# ----------------------------------------------------------------------
165+
ENABLED_WARNINGS: Dict[str, bool] = {}

src/api/optimize.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
import types
5+
46
from typing import NamedTuple
7+
from typing import Set
8+
9+
import src.api.global_ as gl
10+
import src.api.utils
11+
import src.api.symboltable
12+
import src.api.check as chk
13+
14+
from src import symbols
515
from src.ast import NodeVisitor
6-
from .config import OPTIONS
716
from src.api.errmsg import warning
8-
import src.api.check as chk
917
from src.api.constants import TYPE, SCOPE, CLASS
10-
import src.api.global_ as gl
11-
from .. import symbols
12-
import types
1318
from src.api.debug import __DEBUG__
1419
from src.api.errmsg import warning_not_used
15-
import src.api.utils
16-
import src.api.symboltable
20+
21+
from .config import OPTIONS
1722

1823

1924
class ToVisit(object):
@@ -327,7 +332,7 @@ def has_circular_dependency(self, var_dependency: VarDependency) -> bool:
327332
return False
328333

329334
def get_var_dependencies(self, var_entry: symbols.VAR):
330-
visited = set()
335+
visited: Set[symbols.Var] = set()
331336
result = set()
332337

333338
def visit_var(entry):

src/api/utils.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@
2525
from .. import symbols
2626

2727
__all__ = [
28-
'read_txt_file',
28+
'flatten_list',
2929
'open_file',
30+
'read_txt_file',
3031
'sanitize_filename',
31-
'flatten_list',
3232
'timeout'
3333
]
3434

35-
__doc__ = """Utils module contains many helpers for several task, like reading files
36-
or path management"""
35+
__doc__ = """Utils module contains many helpers for several task,
36+
like reading files or path management"""
3737

3838
SHELVE_PATH = os.path.join(constants.ZXBASIC_ROOT, 'parsetab', 'tabs.dbm')
3939
SHELVE = shelve.open(SHELVE_PATH)

src/libzxbc/args_parser.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import argparse
4+
5+
from src import arch
6+
from src.api import errmsg
7+
from src.api.config import OPTIONS
8+
9+
from .version import VERSION
10+
11+
12+
def parse_warning_option(code: str) -> str:
13+
if not errmsg.is_valid_warning_code(code):
14+
raise argparse.ArgumentTypeError(f"Invalid warning option 'W{code}'")
15+
return code
16+
17+
18+
# ------------------------------------------------------------
19+
# Command line parser
20+
# ------------------------------------------------------------
21+
def parser() -> argparse.ArgumentParser:
22+
parser_ = argparse.ArgumentParser(
23+
prefix_chars='-+'
24+
)
25+
parser_.add_argument('PROGRAM', type=str,
26+
help='BASIC program file')
27+
parser_.add_argument('-d', '--debug', dest='debug', default=OPTIONS.Debug, action='count',
28+
help='Enable verbosity/debugging output. Additional -d increase verbosity/debug level')
29+
parser_.add_argument('-O', '--optimize', type=int, default=OPTIONS.optimization,
30+
help='Sets optimization level. '
31+
'0 = None (default level is {0})'.format(OPTIONS.optimization))
32+
parser_.add_argument('-o', '--output', type=str, dest='output_file', default=None,
33+
help='Sets output file. Default is input filename with .bin extension')
34+
parser_.add_argument('-T', '--tzx', action='store_true',
35+
help="Sets output format to tzx (default is .bin)")
36+
parser_.add_argument('-t', '--tap', action='store_true',
37+
help="Sets output format to tap (default is .bin)")
38+
parser_.add_argument('-B', '--BASIC', action='store_true', dest='basic',
39+
help="Creates a BASIC loader which loads the rest of the CODE. Requires -T ot -t")
40+
parser_.add_argument('-a', '--autorun', action='store_true',
41+
help="Sets the program to be run once loaded")
42+
parser_.add_argument('-A', '--asm', action='store_true',
43+
help="Sets output format to asm")
44+
parser_.add_argument('-S', '--org', type=str, default=str(OPTIONS.org),
45+
help="Start of machine code. By default %i" % OPTIONS.org)
46+
parser_.add_argument('-e', '--errmsg', type=str, dest='stderr', default=OPTIONS.StdErrFileName,
47+
help='Error messages file (standard error console by default)')
48+
parser_.add_argument('--array-base', type=int, default=OPTIONS.array_base,
49+
help='Default lower index for arrays ({0} by default)'.format(OPTIONS.array_base))
50+
parser_.add_argument('--string-base', type=int, default=OPTIONS.string_base,
51+
help='Default lower index for strings ({0} by default)'.format(OPTIONS.array_base))
52+
parser_.add_argument('-Z', '--sinclair', action='store_true',
53+
help='Enable by default some more original ZX Spectrum Sinclair BASIC features:'
54+
' ATTR, SCREEN$, POINT')
55+
parser_.add_argument('-H', '--heap-size', type=int, default=OPTIONS.heap_size,
56+
help='Sets heap size in bytes (default {0} bytes)'.format(OPTIONS.heap_size))
57+
parser_.add_argument('--debug-memory', action='store_true',
58+
help='Enables out-of-memory debug')
59+
parser_.add_argument('--debug-array', action='store_true',
60+
help='Enables array boundary checking')
61+
parser_.add_argument('--strict-bool', action='store_true',
62+
help='Enforce boolean values to be 0 or 1')
63+
parser_.add_argument('--enable-break', action='store_true',
64+
help='Enables program execution BREAK detection')
65+
parser_.add_argument('-E', '--emit-backend', action='store_true',
66+
help='Emits backend code instead of ASM or binary')
67+
parser_.add_argument('--explicit', action='store_true',
68+
help='Requires all variables and functions to be declared before used')
69+
parser_.add_argument('-D', '--define', type=str, dest='defines', action='append',
70+
help='Defines de given macro. Eg. -D MYDEBUG or -D NAME=Value')
71+
parser_.add_argument('-M', '--mmap', type=str, dest='memory_map', default=None,
72+
help='Generate label memory map')
73+
parser_.add_argument('-i', '--ignore-case', action='store_true',
74+
help='Ignore case. Makes variable names are case insensitive')
75+
parser_.add_argument('-I', '--include-path', type=str, default='',
76+
help='Add colon separated list of directories to add to include path. e.g. -I dir1:dir2')
77+
parser_.add_argument('--strict', action='store_true',
78+
help='Enables strict mode. Force explicit type declaration')
79+
parser_.add_argument('--headerless', action='store_true',
80+
help='Header-less mode: omit asm prologue and epilogue')
81+
parser_.add_argument('--version', action='version', version='%(prog)s {0}'.format(VERSION))
82+
parser_.add_argument('--parse-only', action='store_true',
83+
help='Only parses to check for syntax and semantic errors')
84+
parser_.add_argument('--append-binary', default=[], action='append',
85+
help='Appends binary to tape file (only works with -t or -T)')
86+
parser_.add_argument('--append-headless-binary', default=[], action='append',
87+
help='Appends binary to tape file (only works with -t or -T)')
88+
parser_.add_argument('-N', '--zxnext', action='store_true',
89+
help='Enables ZX Next asm extended opcodes')
90+
parser_.add_argument('--arch', type=str, default=arch.AVAILABLE_ARCHITECTURES[0],
91+
help=f"Target architecture (defaults is'{arch.AVAILABLE_ARCHITECTURES[0]}'). "
92+
f"Available architectures: {','.join(arch.AVAILABLE_ARCHITECTURES)}")
93+
parser_.add_argument('--expect-warnings', default=OPTIONS.expect_warnings, type=int,
94+
help='Expects N warnings: first N warnings will be silenced')
95+
parser_.add_argument('-W', '--disable-warning', type=parse_warning_option, action='append',
96+
help='Disables warning WXXX (i.e. -W100 disables warning with code W100)')
97+
parser_.add_argument('+W', '--enable-warning', type=parse_warning_option, action='append',
98+
help='Disables warning WXXX (i.e. -W100 disables warning with code W100)')
99+
return parser_

0 commit comments

Comments
 (0)