Skip to content

Commit 7c24207

Browse files
committed
Refactorize options (again)
Now it's possible to create an option which will ignore None (NULL) values on assignation. Also use some extra argparse features (like mutual exclusion) so it's possible to load options from file first and then check new ones set from the command line.
1 parent 4c84576 commit 7c24207

13 files changed

Lines changed: 142 additions & 137 deletions

File tree

src/api/config.py

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from . import options
2020
from . import global_
2121

22-
from .options import ANYTYPE, Actions
22+
from .options import ANYTYPE, Action
2323

2424

2525
# ------------------------------------------------------
@@ -86,7 +86,7 @@ class OPTION:
8686

8787
def load_config_from_file(filename: str, section: str, options_: options.Options = None, stop_on_error=True) -> bool:
8888
""" Opens file and read options from the given section. If stop_on_error is set,
89-
the program stop. Otherwise the result of the operation will be
89+
the program stop if any error is found. Otherwise the result of the operation will be
9090
returned (True on success, False on failure)
9191
"""
9292
if options_ is None:
@@ -166,63 +166,55 @@ def save_config_into_file(filename: str, section: str, options_: options.Options
166166

167167

168168
def init():
169+
""" Default Options and Compilation Flags
169170
"""
170-
Default Options and Compilation Flags
171-
172-
optimization -- Optimization level. Use -O flag to change.
173-
case_insensitive -- Whether user identifiers are case insensitive
174-
or not
175-
array_base -- Default array lower bound
176-
param_byref --Default parameter passing. TRUE => By Reference
177-
"""
178-
179-
OPTIONS(Actions.CLEAR)
171+
OPTIONS(Action.CLEAR)
180172

181-
OPTIONS(Actions.ADD, name=OPTION.OUTPUT_FILENAME, type=str)
182-
OPTIONS(Actions.ADD, name=OPTION.INPUT_FILENAME, type=str)
183-
OPTIONS(Actions.ADD, name=OPTION.STDERR_FILENAME, type=str)
184-
OPTIONS(Actions.ADD, name=OPTION.DEBUG, type=int, default=0)
173+
OPTIONS(Action.ADD, name=OPTION.OUTPUT_FILENAME, type=str)
174+
OPTIONS(Action.ADD, name=OPTION.INPUT_FILENAME, type=str)
175+
OPTIONS(Action.ADD, name=OPTION.STDERR_FILENAME, type=str, ignore_none=True)
176+
OPTIONS(Action.ADD, name=OPTION.DEBUG, type=int, default=0, ignore_none=True)
185177

186178
# Default console redirections
187-
OPTIONS(Actions.ADD, name=OPTION.STDIN, type=ANYTYPE, default=sys.stdin)
188-
OPTIONS(Actions.ADD, name=OPTION.STDOUT, type=ANYTYPE, default=sys.stdout)
189-
OPTIONS(Actions.ADD, name=OPTION.STDERR, type=ANYTYPE, default=sys.stderr)
179+
OPTIONS(Action.ADD, name=OPTION.STDIN, type=ANYTYPE, default=sys.stdin)
180+
OPTIONS(Action.ADD, name=OPTION.STDOUT, type=ANYTYPE, default=sys.stdout)
181+
OPTIONS(Action.ADD, name=OPTION.STDERR, type=ANYTYPE, default=sys.stderr)
190182

191-
OPTIONS(Actions.ADD, name=OPTION.O_LEVEL, type=int, default=global_.DEFAULT_OPTIMIZATION_LEVEL)
192-
OPTIONS(Actions.ADD, name=OPTION.CASE_INS, type=bool, default=False)
193-
OPTIONS(Actions.ADD, name=OPTION.ARRAY_BASE, type=int, default=0)
194-
OPTIONS(Actions.ADD, name=OPTION.DEFAULT_BYREF, type=bool, default=False)
195-
OPTIONS(Actions.ADD, name=OPTION.MAX_SYN_ERRORS, type=int, default=global_.DEFAULT_MAX_SYNTAX_ERRORS)
196-
OPTIONS(Actions.ADD, name=OPTION.STR_BASE, type=int, default=0)
197-
OPTIONS(Actions.ADD, name=OPTION.MEMORY_MAP, type=str, default=None)
198-
OPTIONS(Actions.ADD, name=OPTION.FORCE_ASM_BRACKET, type=bool, default=False)
183+
OPTIONS(Action.ADD, name=OPTION.O_LEVEL, type=int, default=global_.DEFAULT_OPTIMIZATION_LEVEL, ignore_none=True)
184+
OPTIONS(Action.ADD, name=OPTION.CASE_INS, type=bool, default=False, ignore_none=True)
185+
OPTIONS(Action.ADD, name=OPTION.ARRAY_BASE, type=int, default=0, ignore_none=True)
186+
OPTIONS(Action.ADD, name=OPTION.DEFAULT_BYREF, type=bool, default=False, ignore_none=True)
187+
OPTIONS(Action.ADD, name=OPTION.MAX_SYN_ERRORS, type=int, default=global_.DEFAULT_MAX_SYNTAX_ERRORS)
188+
OPTIONS(Action.ADD, name=OPTION.STR_BASE, type=int, default=0, ignore_none=True)
189+
OPTIONS(Action.ADD, name=OPTION.MEMORY_MAP, type=str, default=None, ignore_none=True)
190+
OPTIONS(Action.ADD, name=OPTION.FORCE_ASM_BRACKET, type=bool, default=False, ignore_none=True)
199191

200-
OPTIONS(Actions.ADD, name=OPTION.USE_BASIC_LOADER, type=bool, default=False) # Whether to use a loader
192+
OPTIONS(Action.ADD, name=OPTION.USE_BASIC_LOADER, type=bool, default=False) # Whether to use a loader
201193

202194
# Whether to add autostart code (needs basic loader = true)
203-
OPTIONS(Actions.ADD, name=OPTION.AUTORUN, type=bool, default=False)
204-
OPTIONS(Actions.ADD, name=OPTION.OUTPUT_FILE_TYPE, type=str, default='bin') # bin, tap, tzx etc...
205-
OPTIONS(Actions.ADD, name=OPTION.INCLUDE_PATH, type=str, default='') # Include path, like '/var/lib:/var/include'
206-
207-
OPTIONS(Actions.ADD, name=OPTION.CHECK_MEMORY, type=bool, default=False)
208-
OPTIONS(Actions.ADD, name=OPTION.STRICT_BOOL, type=bool, default=False)
209-
OPTIONS(Actions.ADD, name=OPTION.CHECK_ARRAYS, type=bool, default=False)
210-
211-
OPTIONS(Actions.ADD, name=OPTION.ENABLE_BREAK, type=bool, default=False)
212-
OPTIONS(Actions.ADD, name=OPTION.EMIT_BACKEND, type=bool, default=False)
213-
OPTIONS(Actions.ADD, name='__DEFINES', type=dict, default={})
214-
OPTIONS(Actions.ADD, name=OPTION.EXPLICIT, type=bool, default=False)
215-
OPTIONS(Actions.ADD, name='sinclair', type=bool, default=False)
216-
OPTIONS(Actions.ADD, name=OPTION.STRICT, type=bool, default=False) # True to force type checking
217-
OPTIONS(Actions.ADD, name=OPTION.ASM_ZXNEXT, type=bool, default=False) # True to enable ZX Next ASM opcodes
218-
OPTIONS(Actions.ADD, name=OPTION.ARCH, type=str, default=None) # Architecture
219-
OPTIONS(Actions.ADD, name=OPTION.EXPECTED_WARNINGS, type=int, default=0) # Expected Warnings that will be silenced
195+
OPTIONS(Action.ADD, name=OPTION.AUTORUN, type=bool, default=False)
196+
OPTIONS(Action.ADD, name=OPTION.OUTPUT_FILE_TYPE, type=str, default='bin') # bin, tap, tzx etc...
197+
OPTIONS(Action.ADD, name=OPTION.INCLUDE_PATH, type=str, default='') # Include path, like '/var/lib:/var/include'
198+
199+
OPTIONS(Action.ADD, name=OPTION.CHECK_MEMORY, type=bool, default=False, ignore_none=True)
200+
OPTIONS(Action.ADD, name=OPTION.STRICT_BOOL, type=bool, default=False, ignore_none=True)
201+
OPTIONS(Action.ADD, name=OPTION.CHECK_ARRAYS, type=bool, default=False, ignore_none=True)
202+
203+
OPTIONS(Action.ADD, name=OPTION.ENABLE_BREAK, type=bool, default=False, ignore_none=True)
204+
OPTIONS(Action.ADD, name=OPTION.EMIT_BACKEND, type=bool, default=False)
205+
OPTIONS(Action.ADD, name='__DEFINES', type=dict, default={})
206+
OPTIONS(Action.ADD, name=OPTION.EXPLICIT, type=bool, default=False, ignore_none=True)
207+
OPTIONS(Action.ADD, name='sinclair', type=bool, default=False)
208+
OPTIONS(Action.ADD, name=OPTION.STRICT, type=bool, default=False, ignore_none=True) # True to force type checking
209+
OPTIONS(Action.ADD, name=OPTION.ASM_ZXNEXT, type=bool, default=False, ignore_none=True) # Enable ZX Next ASM
210+
OPTIONS(Action.ADD, name=OPTION.ARCH, type=str, default=None, ignore_none=True) # Architecture
211+
OPTIONS(Action.ADD, name=OPTION.EXPECTED_WARNINGS, type=int, default=0, ignore_none=True)
220212

221213
# Whether to show WXXX warning codes or not
222-
OPTIONS(Actions.ADD, name=OPTION.HIDE_WARNING_CODES, type=bool, default=False)
214+
OPTIONS(Action.ADD, name=OPTION.HIDE_WARNING_CODES, type=bool, default=False, ignore_none=True)
223215

224-
OPTIONS(Actions.ADD, name=OPTION.PROJECT_FILENAME, type=str, default=os.path.join(os.path.abspath(os.path.curdir),
225-
'project.ini'))
216+
OPTIONS(Action.ADD, name=OPTION.PROJECT_FILENAME, type=str, default=os.path.join(os.path.abspath(os.path.curdir),
217+
'project.ini'))
226218

227219

228220
init()

src/api/options.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from .errors import Error
1919

20-
__all__ = ['Option', 'Options', 'ANYTYPE', 'Actions']
20+
__all__ = ['Option', 'Options', 'ANYTYPE', 'Action']
2121

2222

2323
class ANYTYPE:
@@ -98,9 +98,11 @@ class Option:
9898
""" A simple container for options with optional type checking
9999
on vale assignation.
100100
"""
101-
def __init__(self, name: str, type_, value=None):
101+
def __init__(self, name: str, type_, value=None, ignore_none=False):
102102
self.name = name
103103
self.type = type_
104+
self.ignore_none = ignore_none
105+
self.__value = None
104106
self.value = value
105107
self.stack: List[Any] = [] # An option stack
106108

@@ -110,6 +112,9 @@ def value(self) -> Any:
110112

111113
@value.setter
112114
def value(self, value):
115+
if value is None and self.ignore_none:
116+
return
117+
113118
if value is not None and self.type is not None and not isinstance(value, self.type):
114119
try:
115120
if isinstance(value, str) and self.type == bool:
@@ -156,7 +161,7 @@ def pop(self) -> Any:
156161
# ----------------------------------------------------------------------
157162
# Options commands
158163
# ----------------------------------------------------------------------
159-
class Actions:
164+
class Action:
160165
ADD = 'add'
161166
ADD_IF_NOT_DEFINED = 'add_if_not_defined'
162167
CLEAR = 'clear'
@@ -182,7 +187,7 @@ def __init__(self, init_value=None):
182187
else:
183188
raise InvalidConfigInitialization(invalid_value=init_value)
184189

185-
def __add_option(self, name, type_=None, default=None):
190+
def __add_option(self, name, type_=None, default=None, ignore_none=False):
186191
if name in self._options:
187192
raise DuplicatedOptionError(name)
188193

@@ -191,12 +196,12 @@ def __add_option(self, name, type_=None, default=None):
191196
elif type_ is ANYTYPE:
192197
type_ = None
193198

194-
self._options[name] = Option(name, type_, default)
199+
self._options[name] = Option(name, type_, default, ignore_none)
195200

196-
def __add_option_if_not_defined(self, name, type_=None, default=None):
201+
def __add_option_if_not_defined(self, name, type_=None, default=None, ignore_none=False):
197202
if name in self._options:
198203
return
199-
self.__add_option(name, type_, default)
204+
self.__add_option(name, type_, default, ignore_none)
200205

201206
def __delattr__(self, item: str):
202207
del self[item]
@@ -250,33 +255,39 @@ def check_allowed_args(action: str, kwargs_, allowed_args, required_args=None):
250255

251256
# With no parameters
252257
if not kwargs:
253-
if not args or args == (Actions.LIST, ):
258+
if not args or args == (Action.LIST,):
254259
return {x: y for x, y in self._options.items()}
255260

256-
assert args, f"Missing one action of {', '.join(Actions.allowed)}"
257-
assert len(args) == 1 and args[0] in Actions.allowed, \
258-
f"Only one action of {', '.join(Actions.allowed)} can be specified"
261+
assert args, f"Missing one action of {', '.join(Action.allowed)}"
262+
assert len(args) == 1 and args[0] in Action.allowed, \
263+
f"Only one action of {', '.join(Action.allowed)} can be specified"
259264

260265
# clear
261-
if args[0] == Actions.CLEAR:
262-
check_allowed_args(Actions.CLEAR, kwargs, {})
266+
if args[0] == Action.CLEAR:
267+
check_allowed_args(Action.CLEAR, kwargs, {})
263268
self._options.clear()
264269
return
265270

266271
# list
267-
if args[0] == Actions.LIST:
268-
check_allowed_args(Actions.LIST, kwargs, {'options'})
272+
if args[0] == Action.LIST:
273+
check_allowed_args(Action.LIST, kwargs, {'options'})
269274
options = set(kwargs['options'])
270275
return {x: y for x, y in self._options.items() if x in options}
271276

272-
if args[0] == Actions.ADD:
277+
if args[0] == Action.ADD:
273278
kwargs['type'] = kwargs.get('type')
274279
kwargs['default'] = kwargs.get('default')
275-
check_allowed_args(Actions.ADD, kwargs, {'name', 'type', 'default'}, ['name'])
276-
self.__add_option(kwargs['name'], kwargs['type'], kwargs['default'])
280+
kwargs['ignore_none'] = kwargs.get('ignore_none', False)
281+
check_allowed_args(Action.ADD, kwargs, {'name', 'type', 'default', 'ignore_none'}, ['name'])
282+
kwargs['type_'] = kwargs['type']
283+
del kwargs['type']
284+
self.__add_option(**kwargs)
277285

278-
if args[0] == Actions.ADD_IF_NOT_DEFINED:
286+
if args[0] == Action.ADD_IF_NOT_DEFINED:
279287
kwargs['type'] = kwargs.get('type')
280288
kwargs['default'] = kwargs.get('default')
281-
check_allowed_args(Actions.ADD, kwargs, {'name', 'type', 'default'}, ['name'])
282-
self.__add_option_if_not_defined(kwargs['name'], kwargs['type'], kwargs['default'])
289+
kwargs['ignore_none'] = kwargs.get('ignore_none', False)
290+
check_allowed_args(Action.ADD, kwargs, {'name', 'type', 'default', 'ignore_none'}, ['name'])
291+
kwargs['type_'] = kwargs['type']
292+
del kwargs['type']
293+
self.__add_option_if_not_defined(**kwargs)

src/arch/zx48k/backend/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
# External functions
9696
from ..optimizer.helpers import HI16, LO16
9797
from src.arch.zx48k.optimizer.asm import Asm
98-
from src.api.config import OPTIONS, Actions
98+
from src.api.config import OPTIONS, Action
9999
from src.arch.zx48k.peephole import engine
100100

101101
import src.api.fp
@@ -134,10 +134,10 @@
134134
FLAG_end_emitted = False
135135

136136
# Default code ORG
137-
OPTIONS(Actions.ADD_IF_NOT_DEFINED, name='org', type=int, default=32768)
137+
OPTIONS(Action.ADD_IF_NOT_DEFINED, name='org', type=int, default=32768)
138138

139139
# Default HEAP SIZE (Dynamic memory) in bytes
140-
OPTIONS(Actions.ADD_IF_NOT_DEFINED, name='heap_size', type=int, default=4768) # A bit more than 4K
140+
OPTIONS(Action.ADD_IF_NOT_DEFINED, name='heap_size', type=int, default=4768) # A bit more than 4K
141141

142142
# List of modules (in alphabetical order) that, if included, should call MEM_INIT
143143
MEMINITS = {
@@ -196,15 +196,15 @@ def init():
196196
FLAG_end_emitted = False
197197

198198
# Default code ORG
199-
OPTIONS(Actions.ADD, name='org', type=int, default=32768)
199+
OPTIONS(Action.ADD, name='org', type=int, default=32768)
200200
# Default HEAP SIZE (Dynamic memory) in bytes
201-
OPTIONS(Actions.ADD, name='heap_size', type=int, default=4768) # A bit more than 4K
201+
OPTIONS(Action.ADD, name='heap_size', type=int, default=4768, ignore_none=True) # A bit more than 4K
202202
# Labels for HEAP START (might not be used if not needed)
203-
OPTIONS(Actions.ADD, name='heap_start_label', type=str, default=f'{NAMESPACE}.ZXBASIC_MEM_HEAP')
203+
OPTIONS(Action.ADD, name='heap_start_label', type=str, default=f'{NAMESPACE}.ZXBASIC_MEM_HEAP')
204204
# Labels for HEAP SIZE (might not be used if not needed)
205-
OPTIONS(Actions.ADD, name='heap_size_label', type=str, default=f'{NAMESPACE}.ZXBASIC_HEAP_SIZE')
205+
OPTIONS(Action.ADD, name='heap_size_label', type=str, default=f'{NAMESPACE}.ZXBASIC_HEAP_SIZE')
206206
# Flag for headerless mode (No prologue / epilogue)
207-
OPTIONS(Actions.ADD, name='headerless', type=bool, default=False)
207+
OPTIONS(Action.ADD, name='headerless', type=bool, default=False, ignore_none=True)
208208

209209
engine.main() # inits the optimizer
210210

src/zxbc/args_config.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def parse_options(args: List[str] = None):
5252
OPTIONS.sinclair = options.sinclair
5353
OPTIONS.heap_size = options.heap_size
5454
OPTIONS.memory_check = options.debug_memory
55-
OPTIONS.strict_bool = options.strict_bool or OPTIONS.sinclair
55+
OPTIONS.strict_bool = options.strict_bool
5656
OPTIONS.array_check = options.debug_array
5757
OPTIONS.emit_backend = options.emit_backend
5858
OPTIONS.enable_break = options.enable_break
@@ -88,9 +88,9 @@ def parse_options(args: List[str] = None):
8888

8989
# endregion
9090

91-
OPTIONS.org = src.api.utils.parse_int(options.org)
91+
OPTIONS.org = OPTIONS.org if options.org is None else src.api.utils.parse_int(options.org)
9292
if OPTIONS.org is None:
93-
parser.error("Invalid --org option '{}'".format(options.org))
93+
parser.error(f"Invalid --org option '{options.org}'")
9494

9595
if options.defines:
9696
for i in options.defines:
@@ -106,16 +106,9 @@ def parse_options(args: List[str] = None):
106106
OPTIONS.strictBool = True
107107
OPTIONS.case_insensitive = True
108108

109-
if options.ignore_case:
110-
OPTIONS.case_insensitive = True
111-
109+
OPTIONS.case_insensitive = options.ignore_case
112110
debug.ENABLED = OPTIONS.debug_level > 0
113111

114-
if int(options.tzx) + int(options.tap) + int(options.asm) + int(options.emit_backend) + \
115-
int(options.parse_only) > 1:
116-
parser.error("Options --tap, --tzx, --emit-backend, --parse-only and --asm are mutually exclusive")
117-
return 3
118-
119112
if options.basic and not options.tzx and not options.tap:
120113
parser.error('Option --BASIC and --autorun requires --tzx or tap format')
121114
return 4

0 commit comments

Comments
 (0)