Skip to content

Commit c7f9421

Browse files
authored
Merge pull request #522 from boriel/feature/use-config-file
Refactorize config constants
2 parents cd7861c + 718a490 commit c7f9421

25 files changed

Lines changed: 322 additions & 179 deletions

src/api/config.py

Lines changed: 177 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,68 +9,203 @@
99
# the GNU General License
1010
# ----------------------------------------------------------------------
1111

12+
import os
1213
import sys
14+
import configparser
15+
16+
from src import api
1317

1418
# The options container
1519
from . import options
1620
from . import global_
1721
from .options import ANYTYPE
1822

23+
1924
# ------------------------------------------------------
2025
# Common setup and configuration for all tools
2126
# ------------------------------------------------------
27+
class OPTION:
28+
OUTPUT_FILENAME = 'output_filename'
29+
INPUT_FILENAME = 'input_filename'
30+
STDERR_FILENAME = 'stderr_filename'
31+
DEBUG = 'debug_level'
32+
33+
# File IO
34+
STDIN = 'stdin'
35+
STDOUT = 'stdout'
36+
STDERR = 'stderr'
37+
38+
O_LEVEL = 'optimization_level'
39+
CASE_INS = 'case_insensitive'
40+
ARRAY_BASE = 'array_base'
41+
STR_BASE = 'string_base'
42+
DEFAULT_BYREF = 'default_byref'
43+
MAX_SYN_ERRORS = 'max_syntax_errors'
44+
45+
MEMORY_MAP = 'memory_map'
46+
47+
USE_BASIC_LOADER = 'use_basic_loader'
48+
AUTORUN = 'autorun'
49+
OUTPUT_FILE_TYPE = 'output_file_type'
50+
INCLUDE_PATH = 'include_path'
51+
52+
CHECK_MEMORY = 'memory_check'
53+
CHECK_ARRAYS = 'array_check'
54+
55+
STRICT_BOOL = 'strict_bool'
56+
57+
ENABLE_BREAK = 'enable_break'
58+
EMIT_BACKEND = 'emit_backend'
59+
60+
EXPLICIT = 'explicit'
61+
STRICT = 'strict'
62+
63+
ARCH = 'architecture'
64+
EXPECTED_WARNINGS = 'expected_warnings'
65+
HIDE_WARNING_CODES = 'hide_warning_codes'
66+
67+
# ASM Options
68+
ASM_ZXNEXT = 'zxnext'
69+
FORCE_ASM_BRACKET = 'force_asm_brackets'
70+
2271

2372
OPTIONS = options.Options()
2473

2574

75+
def load_config_from_file(filename: str, section: str, options_: options.Options = None, stop_on_error=True) -> bool:
76+
""" Opens file and read options from the given section. If stop_on_error is set,
77+
the program stop. Otherwise the result of the operation will be
78+
returned (True on success, False on failure)
79+
"""
80+
if options_ is None:
81+
options_ = OPTIONS
82+
83+
try:
84+
cfg = configparser.ConfigParser()
85+
cfg.read(filename, encoding='utf-8')
86+
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError):
87+
api.errmsg.msg_output(f"Invalid config file '{filename}': it has duplicated fields")
88+
if stop_on_error:
89+
sys.exit(1)
90+
return False
91+
except FileNotFoundError:
92+
api.errmsg.msg_output(f"Config file '{filename}' not found")
93+
if stop_on_error:
94+
sys.exit(1)
95+
return False
96+
97+
if section not in cfg.sections():
98+
api.errmsg.msg_output(f"Section '{section}' not found in config file '{filename}'")
99+
if stop_on_error:
100+
sys.exit(1)
101+
return False
102+
103+
parsing = {
104+
int: cfg.getint,
105+
float: cfg.getfloat,
106+
bool: cfg.getboolean
107+
}
108+
109+
for opt in cfg.options(section):
110+
options_[opt].value = parsing.get(options_[opt].type, cfg.get)(option=opt)
111+
112+
return True
113+
114+
115+
def save_config_into_file(filename: str, section: str, options_: options.Options = None, stop_on_error=True) -> bool:
116+
""" Save config into config ini file into the given section. If stop_on_error is set,
117+
the program stop. Otherwise the result of the operation will be
118+
returned (True on success, False on failure)
119+
"""
120+
if options_ is None:
121+
options_ = OPTIONS
122+
123+
cfg = configparser.ConfigParser()
124+
if os.path.exists(filename):
125+
try:
126+
cfg.read(filename, encoding='utf-8')
127+
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError):
128+
api.errmsg.msg_output(f"Invalid config file '{filename}': it has duplicated fields")
129+
if stop_on_error:
130+
sys.exit(1)
131+
return False
132+
133+
cfg[section] = {}
134+
for opt_name, opt in options.Options.get_options(options_):
135+
if opt_name.startswith('__') or opt.value is None or opt_name in ('stderr', 'stdin', 'stdout'):
136+
continue
137+
138+
if opt.type == bool:
139+
cfg[section][opt_name] = str(opt.value).lower()
140+
continue
141+
142+
cfg[section][opt_name] = str(opt.value)
143+
144+
try:
145+
with open(filename, 'wt', encoding='utf-8') as f:
146+
cfg.write(f)
147+
except IOError:
148+
api.errmsg.msg_output(f"Can't write config file '{filename}'")
149+
if stop_on_error:
150+
sys.exit(1)
151+
return False
152+
153+
return True
154+
155+
26156
def init():
157+
"""
158+
Default Options and Compilation Flags
159+
160+
optimization -- Optimization level. Use -O flag to change.
161+
case_insensitive -- Whether user identifiers are case insensitive
162+
or not
163+
array_base -- Default array lower bound
164+
param_byref --Default parameter passing. TRUE => By Reference
165+
"""
166+
27167
OPTIONS.reset()
28-
OPTIONS.add_option('outputFileName', str)
29-
OPTIONS.add_option('inputFileName', str)
30-
OPTIONS.add_option('StdErrFileName', str)
31-
OPTIONS.add_option('Debug', int, 0)
168+
169+
OPTIONS.add_option(OPTION.OUTPUT_FILENAME, str)
170+
OPTIONS.add_option(OPTION.INPUT_FILENAME, str)
171+
OPTIONS.add_option(OPTION.STDERR_FILENAME, str)
172+
OPTIONS.add_option(OPTION.DEBUG, int, 0)
32173

33174
# Default console redirections
34-
OPTIONS.add_option('stdin', ANYTYPE, sys.stdin)
35-
OPTIONS.add_option('stdout', ANYTYPE, sys.stdout)
36-
OPTIONS.add_option('stderr', ANYTYPE, sys.stderr)
37-
38-
# ----------------------------------------------------------------------
39-
# Default Options and Compilation Flags
40-
#
41-
# optimization -- Optimization level. Use -O flag to change.
42-
# case_insensitive -- Whether user identifiers are case insensitive
43-
# or not
44-
# array_base -- Default array lower bound
45-
# param_byref --Default parameter passing. TRUE => By Reference
46-
# ----------------------------------------------------------------------
47-
OPTIONS.add_option('optimization', int, global_.DEFAULT_OPTIMIZATION_LEVEL)
48-
OPTIONS.add_option('case_insensitive', bool, False)
49-
OPTIONS.add_option('array_base', int, 0)
50-
OPTIONS.add_option('byref', bool, False)
51-
OPTIONS.add_option('max_syntax_errors', int, global_.DEFAULT_MAX_SYNTAX_ERRORS)
52-
OPTIONS.add_option('string_base', int, 0)
53-
OPTIONS.add_option('memory_map', str, None)
54-
OPTIONS.add_option('bracket', bool, False)
55-
56-
OPTIONS.add_option('use_loader', bool, False) # Whether to use a loader
57-
OPTIONS.add_option('autorun', bool, False) # Whether to add autostart code (needs basic loader = true)
58-
OPTIONS.add_option('output_file_type', str, 'bin') # bin, tap, tzx etc...
59-
OPTIONS.add_option('include_path', str, '') # Include path, like '/var/lib:/var/include'
60-
61-
OPTIONS.add_option('memoryCheck', bool, False)
62-
OPTIONS.add_option('strictBool', bool, False)
63-
OPTIONS.add_option('arrayCheck', bool, False)
64-
OPTIONS.add_option('enableBreak', bool, False)
65-
OPTIONS.add_option('emitBackend', bool, False)
175+
OPTIONS.add_option(OPTION.STDIN, ANYTYPE, sys.stdin)
176+
OPTIONS.add_option(OPTION.STDOUT, ANYTYPE, sys.stdout)
177+
OPTIONS.add_option(OPTION.STDERR, ANYTYPE, sys.stderr)
178+
179+
OPTIONS.add_option(OPTION.O_LEVEL, int, global_.DEFAULT_OPTIMIZATION_LEVEL)
180+
OPTIONS.add_option(OPTION.CASE_INS, bool, False)
181+
OPTIONS.add_option(OPTION.ARRAY_BASE, int, 0)
182+
OPTIONS.add_option(OPTION.DEFAULT_BYREF, bool, False)
183+
OPTIONS.add_option(OPTION.MAX_SYN_ERRORS, int, global_.DEFAULT_MAX_SYNTAX_ERRORS)
184+
OPTIONS.add_option(OPTION.STR_BASE, int, 0)
185+
OPTIONS.add_option(OPTION.MEMORY_MAP, str, None)
186+
OPTIONS.add_option(OPTION.FORCE_ASM_BRACKET, bool, False)
187+
188+
OPTIONS.add_option(OPTION.USE_BASIC_LOADER, bool, False) # Whether to use a loader
189+
OPTIONS.add_option(OPTION.AUTORUN, bool, False) # Whether to add autostart code (needs basic loader = true)
190+
OPTIONS.add_option(OPTION.OUTPUT_FILE_TYPE, str, 'bin') # bin, tap, tzx etc...
191+
OPTIONS.add_option(OPTION.INCLUDE_PATH, str, '') # Include path, like '/var/lib:/var/include'
192+
193+
OPTIONS.add_option(OPTION.CHECK_MEMORY, bool, False)
194+
OPTIONS.add_option(OPTION.STRICT_BOOL, bool, False)
195+
OPTIONS.add_option(OPTION.CHECK_ARRAYS, bool, False)
196+
197+
OPTIONS.add_option(OPTION.ENABLE_BREAK, bool, False)
198+
OPTIONS.add_option(OPTION.EMIT_BACKEND, bool, False)
66199
OPTIONS.add_option('__DEFINES', dict, {})
67-
OPTIONS.add_option('explicit', bool, False)
200+
OPTIONS.add_option(OPTION.EXPLICIT, bool, False)
68201
OPTIONS.add_option('Sinclair', bool, False)
69-
OPTIONS.add_option('strict', bool, False) # True to force type checking
70-
OPTIONS.add_option('zxnext', bool, False) # True to enable ZX Next ASM opcodes
71-
OPTIONS.add_option('architecture', str, None) # Architecture
72-
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
202+
OPTIONS.add_option(OPTION.STRICT, bool, False) # True to force type checking
203+
OPTIONS.add_option(OPTION.ASM_ZXNEXT, bool, False) # True to enable ZX Next ASM opcodes
204+
OPTIONS.add_option(OPTION.ARCH, str, None) # Architecture
205+
OPTIONS.add_option(OPTION.EXPECTED_WARNINGS, int, 0) # Expected Warnings that will be silenced
206+
OPTIONS.add_option(OPTION.HIDE_WARNING_CODES, bool, False) # Whether to show WXXX warning codes or not
207+
208+
save_config_into_file('project.ini', 'zxbc', stop_on_error=True)
74209

75210

76211
init()

src/api/debug.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
def __DEBUG__(msg, level=1):
18-
if level > OPTIONS.Debug:
18+
if level > OPTIONS.debug_level:
1919
return
2020

2121
line = inspect.getouterframes(inspect.currentframe())[1][2]

src/api/errmsg.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def msg_output(msg: str) -> None:
4242

4343

4444
def info(msg: str) -> None:
45-
if OPTIONS.Debug < 1:
45+
if OPTIONS.debug_level < 1:
4646
return
4747
OPTIONS.stderr.write("info: %s\n" % msg)
4848

@@ -69,7 +69,7 @@ def warning(lineno: int, msg: str, fname: Optional[str] = None) -> None:
6969
""" Generic warning error routine
7070
"""
7171
global_.has_warnings += 1
72-
if global_.has_warnings <= OPTIONS.expect_warnings:
72+
if global_.has_warnings <= OPTIONS.expected_warnings:
7373
return
7474

7575
if fname is None:
@@ -162,7 +162,7 @@ def warning_empty_if(lineno: int):
162162
def warning_not_used(lineno: int, id_: str, kind: str = 'Variable', fname: Optional[str] = None):
163163
""" Emits an optimization warning
164164
"""
165-
if OPTIONS.optimization > 0:
165+
if OPTIONS.optimization_level > 0:
166166
warning(lineno, "%s '%s' is never used" % (kind, id_), fname=fname)
167167

168168

src/api/optimize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class ToVisit(NamedTuple):
3434
class GenericVisitor(NodeVisitor):
3535
@property
3636
def O_LEVEL(self):
37-
return OPTIONS.optimization
37+
return OPTIONS.optimization_level
3838

3939
NOP = symbols.NOP() # Return this for "erased" nodes
4040

src/api/options.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from typing import Dict
1515
from typing import List
1616
from typing import Any
17+
from typing import Tuple
1718

1819
from .errors import Error
1920

@@ -205,3 +206,9 @@ def __setitem__(self, key: str, value: Any):
205206

206207
def __contains__(self, item: str):
207208
return item in self._options
209+
210+
@classmethod
211+
def get_options(cls, instance: 'Options') -> List[Tuple[str, Option]]:
212+
""" Iterate over all options of the given instance
213+
"""
214+
return list(instance._options.items())

src/api/symboltable.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,17 @@ def __delitem__(self, key: str):
8787
self.caseins.pop(key, None)
8888

8989
def values(self, filter_by_opt=True):
90-
if filter_by_opt and OPTIONS.optimization > 1:
90+
if filter_by_opt and OPTIONS.optimization_level > 1:
9191
return [y for x, y in self.symbols.items() if y.accessed]
9292
return [y for x, y in self.symbols.items()]
9393

9494
def keys(self, filter_by_opt=True):
95-
if filter_by_opt and OPTIONS.optimization > 1:
95+
if filter_by_opt and OPTIONS.optimization_level > 1:
9696
return [x for x, y in self.symbols.items() if y.accessed]
9797
return self.symbols.keys()
9898

9999
def items(self, filter_by_opt=True):
100-
if filter_by_opt and OPTIONS.optimization > 1:
100+
if filter_by_opt and OPTIONS.optimization_level > 1:
101101
return [(x, y) for x, y in self.symbols.items() if y.accessed]
102102
return self.symbols.items()
103103

src/arch/zx48k/backend/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2268,7 +2268,7 @@ def convertToBool():
22682268
""" Convert a byte value to boolean (0 or 1) if
22692269
the global flag strictBool is True
22702270
"""
2271-
if not OPTIONS.strictBool:
2271+
if not OPTIONS.strict_bool:
22722272
return []
22732273

22742274
return [
@@ -2347,7 +2347,7 @@ def emit(mem: List[Quad], optimize=True):
23472347
'output' array
23482348
"""
23492349
# Optimization patterns: at this point no more than -O2
2350-
patterns = [x for x in engine.PATTERNS if x.level <= min(OPTIONS.optimization, 2)]
2350+
patterns = [x for x in engine.PATTERNS if x.level <= min(OPTIONS.optimization_level, 2)]
23512351

23522352
def output_join(output: List[str], new_chunk: List[str], optimize: bool = True):
23532353
""" Extends output instruction list
@@ -2373,7 +2373,7 @@ def output_join(output: List[str], new_chunk: List[str], optimize: bool = True):
23732373
if RE_BOOL.match(i.quad[0]): # If it is a boolean operation convert it to 0/1 if the STRICT_BOOL flag is True
23742374
output_join(output, convertToBool(), optimize=optimize)
23752375

2376-
if optimize and OPTIONS.optimization > 1:
2376+
if optimize and OPTIONS.optimization_level > 1:
23772377
remove_unused_labels(output)
23782378
tmp = output
23792379
output = []

src/arch/zx48k/optimizer/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,10 @@ def optimize(initial_memory):
183183
PROC_COUNTER = 0
184184

185185
cleanupmem(initial_memory)
186-
if OPTIONS.optimization <= 2: # if -O2 or lower, do nothing and return
186+
if OPTIONS.optimization_level <= 2: # if -O2 or lower, do nothing and return
187187
return '\n'.join(x for x in initial_memory if not RE_PRAGMA.match(x))
188188

189-
basicblock.BasicBlock.clean_asm_args = OPTIONS.optimization > 3
189+
basicblock.BasicBlock.clean_asm_args = OPTIONS.optimization_level > 3
190190
bb = basicblock.BasicBlock(initial_memory)
191191
cleanup_local_labels(bb)
192192
initialize_memory(bb)
@@ -235,7 +235,7 @@ def optimize(initial_memory):
235235
for x in basic_blocks:
236236
x.compute_cpu_state()
237237

238-
filtered_patterns_list = [p for p in engine.PATTERNS if OPTIONS.optimization >= p.level >= 3]
238+
filtered_patterns_list = [p for p in engine.PATTERNS if OPTIONS.optimization_level >= p.level >= 3]
239239
for x in basic_blocks:
240240
x.optimize(filtered_patterns_list)
241241

src/arch/zx48k/optimizer/basicblock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def optimize(self, patterns_list):
580580
'z': str(self.cpu.Z) if self.cpu.Z is not None else helpers.new_tmp_val()
581581
}.get(x.lower(), helpers.new_tmp_val())
582582

583-
if src.api.config.OPTIONS.optimization > 3:
583+
if src.api.config.OPTIONS.optimization_level > 3:
584584
regs, mems = self.guesses_initial_state_from_origin_blocks()
585585
else:
586586
regs, mems = {}, {}

src/arch/zx48k/translator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def visit_ARGLIST(self, node):
188188
for i in range(len(node) - 1, -1, -1): # visit in reverse order
189189
yield node[i]
190190

191-
if isinstance(node.parent, symbols.ARRAYACCESS) and OPTIONS.arrayCheck and \
191+
if isinstance(node.parent, symbols.ARRAYACCESS) and OPTIONS.array_check and \
192192
node.parent.entry.scope != SCOPE.parameter:
193193
upper = node.parent.entry.bounds[i].upper
194194
lower = node.parent.entry.bounds[i].lower

0 commit comments

Comments
 (0)