Skip to content

Commit cfd225b

Browse files
authored
Merge pull request #436 from boriel/feature/allow_disabling_enabling_warnings
Feature/allow disabling enabling warnings
2 parents c712474 + d541e7e commit cfd225b

14 files changed

Lines changed: 248 additions & 106 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/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def init():
7070
OPTIONS.add_option('zxnext', bool, False) # True to enable ZX Next ASM opcodes
7171
OPTIONS.add_option('architecture', str, None) # Architecture
7272
OPTIONS.add_option('expect_warnings', int, 0) # Expected Warnings that will be silenced
73+
OPTIONS.add_option('hide_warning_codes', bool, False) # Whether to show WXXX warning codes or not
7374

7475

7576
init()

src/api/errmsg.py

Lines changed: 69 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,48 @@ 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+
if not OPTIONS.hide_warning_codes:
108+
WARNING_PREFIX = f'warning: [W{code}]'
109+
func(*args, **kwargs)
110+
WARNING_PREFIX = ''
111+
112+
return wraps(func)(wrapper)
113+
114+
return decorator
115+
116+
117+
# region [Warnings]
118+
@register_warning('100')
68119
def warning_implicit_type(lineno: int, id_: str, type_: str = None):
69120
""" Warning: Using default implicit type 'x'
70121
"""
@@ -78,49 +129,59 @@ def warning_implicit_type(lineno: int, id_: str, type_: str = None):
78129
warning(lineno, "Using default implicit type '%s' for '%s'" % (type_, id_))
79130

80131

132+
@register_warning('110')
81133
def warning_condition_is_always(lineno: int, cond: bool = False):
82134
""" Warning: Condition is always false/true
83135
"""
84136
warning(lineno, "Condition is always %s" % cond)
85137

86138

139+
@register_warning('120')
87140
def warning_conversion_lose_digits(lineno: int):
88141
""" Warning: Conversion may lose significant digits
89142
"""
90143
warning(lineno, 'Conversion may lose significant digits')
91144

92145

146+
@register_warning('130')
93147
def warning_empty_loop(lineno: int):
94148
""" Warning: Empty loop
95149
"""
96150
warning(lineno, 'Empty loop')
97151

98152

153+
@register_warning('140')
99154
def warning_empty_if(lineno):
100155
""" Warning: Useless empty IF ignored
101156
"""
102157
warning(lineno, 'Useless empty IF ignored')
103158

104159

105-
# Emits an optimization warning
160+
@register_warning('150')
106161
def warning_not_used(lineno, id_, kind='Variable'):
162+
""" Emits an optimization warning
163+
"""
107164
if OPTIONS.optimization > 0:
108165
warning(lineno, "%s '%s' is never used" % (kind, id_))
109166

167+
# endregion
168+
169+
# region [Syntax Errors]
170+
110171

111172
# ----------------------------------------
112173
# Syntax error: Expected string instead of
113174
# numeric expression.
114175
# ----------------------------------------
115-
def syntax_error_expected_string(lineno: str, _type: str):
176+
def syntax_error_expected_string(lineno: int, _type: str):
116177
error(lineno, "Expected a 'string' type expression, got '%s' instead" % _type)
117178

118179

119180
# ----------------------------------------
120181
# Syntax error: FOR variable should be X
121182
# instead of Y
122183
# ----------------------------------------
123-
def syntax_error_wrong_for_var(lineno: str, x: str, y: str):
184+
def syntax_error_wrong_for_var(lineno: int, x: str, y: str):
124185
error(lineno, "FOR variable should be '%s' instead of '%s'" % (x, y))
125186

126187

@@ -205,3 +266,5 @@ def syntax_error_address_must_be_constant(lineno: int):
205266
# ----------------------------------------
206267
def syntax_error_cannot_pass_array_by_value(lineno: int, id_: str):
207268
error(lineno, "Array parameter '%s' must be passed ByRef" % id_)
269+
270+
# 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/options.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,16 @@ def value(self, value):
9696
if value is not None and self.type is not None and not isinstance(value, self.type):
9797
try:
9898
if isinstance(value, str) and self.type == bool:
99-
value = {'false': False, 'true': True}[value.lower()]
99+
value = {
100+
'false': False,
101+
'true': True,
102+
'off': False,
103+
'on': True,
104+
'-': False,
105+
'+': True,
106+
'no': False,
107+
'yes': True
108+
}[value.lower()]
100109
else:
101110
value = self.type(value)
102111
except TypeError:

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: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
parser_.add_argument('--hide-warning-codes', action='store_true',
100+
help='Hides WXXX codes')
101+
return parser_

0 commit comments

Comments
 (0)