Skip to content

Commit cb223a1

Browse files
committed
Isolate config setup into a module
This refact moves part of the OPTIONS config done after args parsing.
1 parent c0b496b commit cb223a1

4 files changed

Lines changed: 183 additions & 146 deletions

File tree

src/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
from src.api import debug # noqa
1313
from src.api import errors # noqa
1414
from src.api import errmsg # noqa
15+
from src.api import utils # noqa

src/outfmt/codeemitter.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@
1515
class CodeEmitter(object):
1616
""" The base code emission interface.
1717
"""
18-
1918
pass

src/zxbc/args_config.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
5+
from typing import List
6+
7+
import src.api.config
8+
import src.api.global_ as gl
9+
10+
from src import arch
11+
12+
from src.api.utils import open_file
13+
from src.api.config import OPTIONS
14+
from src.zxbc import args_parser
15+
from src.api import errmsg
16+
from src.api import debug
17+
from src.zxbpp import zxbpp
18+
from src.zxbc import zxbparser
19+
20+
__all__ = [
21+
'FileType',
22+
'parse_options'
23+
]
24+
25+
26+
class FileType:
27+
ASM = 'asm'
28+
IC = 'ic'
29+
TAP = 'tap'
30+
TZX = 'tzx'
31+
32+
33+
def parse_options(args: List[str] = None):
34+
""" Parses command line options and setup global Options container
35+
"""
36+
parser = args_parser.parser()
37+
options = parser.parse_args(args=args)
38+
39+
if os.path.isfile(options.config_file):
40+
if src.api.config.load_config_from_file(options.config_file, src.api.config.ConfigSections.ZXBC):
41+
src.api.errmsg.info(f"Config file {options.config_file} loaded")
42+
43+
# ------------------------------------------------------------
44+
# Setting of internal parameters according to command line
45+
# ------------------------------------------------------------
46+
OPTIONS.debug_level = options.debug
47+
OPTIONS.optimization_level = options.optimize
48+
OPTIONS.output_filename = options.output_file
49+
OPTIONS.stderr_filename = options.stderr
50+
OPTIONS.array_base = options.array_base
51+
OPTIONS.string_base = options.string_base
52+
OPTIONS.sinclair = options.sinclair
53+
OPTIONS.heap_size = options.heap_size
54+
OPTIONS.memory_check = options.debug_memory
55+
OPTIONS.strict_bool = options.strict_bool or OPTIONS.sinclair
56+
OPTIONS.array_check = options.debug_array
57+
OPTIONS.emit_backend = options.emit_backend
58+
OPTIONS.enable_break = options.enable_break
59+
OPTIONS.explicit = options.explicit
60+
OPTIONS.memory_map = options.memory_map
61+
OPTIONS.strict = options.strict
62+
OPTIONS.headerless = options.headerless
63+
OPTIONS.zxnext = options.zxnext
64+
OPTIONS.expected_warnings = gl.EXPECTED_WARNINGS = options.expect_warnings
65+
OPTIONS.hide_warning_codes = options.hide_warning_codes
66+
67+
if options.arch not in arch.AVAILABLE_ARCHITECTURES:
68+
parser.error(f"Invalid architecture '{options.arch}'")
69+
return 2
70+
71+
OPTIONS.architecture = options.arch
72+
73+
# region [Enable/Disable Warnings]
74+
enabled_warnings = set(options.enable_warning or [])
75+
disabled_warnings = set(options.disable_warning or [])
76+
duplicated_options = [f"W{x}" for x in enabled_warnings.intersection(disabled_warnings)]
77+
78+
if duplicated_options:
79+
parser.error(f"Warning(s) {', '.join(duplicated_options)} cannot be enabled "
80+
f"and disabled simultaneously")
81+
return 2
82+
83+
for warn_code in enabled_warnings:
84+
errmsg.enable_warning(warn_code)
85+
86+
for warn_code in disabled_warnings:
87+
errmsg.disable_warning(warn_code)
88+
89+
# endregion
90+
91+
OPTIONS.org = src.api.utils.parse_int(options.org)
92+
if OPTIONS.org is None:
93+
parser.error("Invalid --org option '{}'".format(options.org))
94+
95+
if options.defines:
96+
for i in options.defines:
97+
macro = list(i.split('=', 1))
98+
name = macro[0]
99+
val = ''.join(macro[1:])
100+
OPTIONS.__DEFINES[name] = val
101+
zxbpp.ID_TABLE.define(name, value=val, lineno=0)
102+
103+
if OPTIONS.sinclair:
104+
OPTIONS.array_base = 1
105+
OPTIONS.string_base = 1
106+
OPTIONS.strictBool = True
107+
OPTIONS.case_insensitive = True
108+
109+
if options.ignore_case:
110+
OPTIONS.case_insensitive = True
111+
112+
debug.ENABLED = OPTIONS.debug_level > 0
113+
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+
119+
if options.basic and not options.tzx and not options.tap:
120+
parser.error('Option --BASIC and --autorun requires --tzx or tap format')
121+
return 4
122+
123+
if options.append_binary and not options.tzx and not options.tap:
124+
parser.error('Option --append-binary needs either --tap or --tzx')
125+
return 5
126+
127+
if options.asm and options.memory_map:
128+
parser.error('Option --asm and --mmap cannot be used together')
129+
return 6
130+
131+
OPTIONS.use_basic_loader = options.basic
132+
OPTIONS.autorun = options.autorun
133+
134+
if options.tzx:
135+
OPTIONS.output_file_type = FileType.TZX
136+
elif options.tap:
137+
OPTIONS.output_file_type = FileType.TAP
138+
elif options.asm:
139+
OPTIONS.output_file_type = FileType.ASM
140+
elif options.emit_backend:
141+
OPTIONS.output_file_type = FileType.IC
142+
143+
args = [options.PROGRAM]
144+
if not os.path.exists(options.PROGRAM):
145+
parser.error("No such file or directory: '%s'" % args[0])
146+
return 2
147+
148+
if OPTIONS.memory_check:
149+
OPTIONS.__DEFINES['__MEMORY_CHECK__'] = ''
150+
zxbpp.ID_TABLE.define('__MEMORY_CHECK__', lineno=0)
151+
152+
if OPTIONS.array_check:
153+
OPTIONS.__DEFINES['__CHECK_ARRAY_BOUNDARY__'] = ''
154+
zxbpp.ID_TABLE.define('__CHECK_ARRAY_BOUNDARY__', lineno=0)
155+
156+
if OPTIONS.enable_break:
157+
OPTIONS.__DEFINES['__ENABLE_BREAK__'] = ''
158+
zxbpp.ID_TABLE.define('__ENABLE_BREAK__', lineno=0)
159+
160+
OPTIONS.include_path = options.include_path
161+
OPTIONS.input_filename = zxbparser.FILENAME = os.path.basename(args[0])
162+
163+
if not OPTIONS.output_filename:
164+
OPTIONS.output_filename = \
165+
os.path.splitext(os.path.basename(OPTIONS.input_filename))[0] + os.path.extsep + \
166+
OPTIONS.output_file_type
167+
168+
if OPTIONS.stderr_filename:
169+
OPTIONS.stderr = open_file(OPTIONS.stderr_filename, 'wt', 'utf-8')
170+
171+
return options

src/zxbc/zxbc.py

Lines changed: 11 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# vim: ts=4:sw=4:et:
44

55
import sys
6-
import os
76
import re
87

98
from io import StringIO
@@ -12,7 +11,6 @@
1211

1312
import src.api.optimize
1413

15-
from src.api import errmsg
1614
from src.api import config
1715
from src.api import debug
1816
from src.api import global_ as gl
@@ -23,9 +21,9 @@
2321
from src.api.config import OPTIONS
2422
from src.api.utils import open_file
2523

26-
from . import zxbparser
27-
from . import zxblex
28-
from . import args_parser
24+
from src.zxbc import zxbparser
25+
from src.zxbc import zxblex
26+
from src.zxbc.args_config import parse_options, FileType
2927

3028

3129
RE_INIT = re.compile(r'^#[ \t]*init[ \t]+((?:[._a-zA-Z][._a-zA-Z0-9]*)|(?:"[._a-zA-Z][._a-zA-Z0-9]*"))[ \t]*$',
@@ -72,8 +70,8 @@ def output(memory, ofile=None):
7270

7371
def main(args=None, emitter=None):
7472
""" Entry point when executed from command line.
75-
You can use zxbc.py as a module with import, and this
76-
function won't be executed.
73+
zxbc can be used as python module. If so, bear in mind this function
74+
won't be executed unless explicitly called.
7775
"""
7876
# region [Initialization]
7977
config.init()
@@ -84,143 +82,11 @@ def main(args=None, emitter=None):
8482
asmparse.init()
8583
# endregion
8684

87-
parser = args_parser.parser()
88-
options = parser.parse_args(args=args)
89-
90-
if os.path.isfile(options.config_file):
91-
if src.api.config.load_config_from_file(options.config_file, src.api.config.ConfigSections.ZXBC):
92-
src.api.errmsg.info(f"Config file {options.config_file} loaded")
93-
94-
# ------------------------------------------------------------
95-
# Setting of internal parameters according to command line
96-
# ------------------------------------------------------------
97-
OPTIONS.debug_level = options.debug
98-
OPTIONS.optimization_level = options.optimize
99-
OPTIONS.output_filename = options.output_file
100-
OPTIONS.stderr_filename = options.stderr
101-
OPTIONS.array_base = options.array_base
102-
OPTIONS.string_base = options.string_base
103-
OPTIONS.sinclair = options.sinclair
104-
OPTIONS.heap_size = options.heap_size
105-
OPTIONS.memory_check = options.debug_memory
106-
OPTIONS.strict_bool = options.strict_bool or OPTIONS.sinclair
107-
OPTIONS.array_check = options.debug_array
108-
OPTIONS.emit_backend = options.emit_backend
109-
OPTIONS.enable_break = options.enable_break
110-
OPTIONS.explicit = options.explicit
111-
OPTIONS.memory_map = options.memory_map
112-
OPTIONS.strict = options.strict
113-
OPTIONS.headerless = options.headerless
114-
OPTIONS.zxnext = options.zxnext
115-
OPTIONS.expected_warnings = gl.EXPECTED_WARNINGS = options.expect_warnings
116-
OPTIONS.hide_warning_codes = options.hide_warning_codes
117-
118-
if options.arch not in arch.AVAILABLE_ARCHITECTURES:
119-
parser.error(f"Invalid architecture '{options.arch}'")
120-
return 2
121-
122-
OPTIONS.architecture = options.arch
123-
arch.set_target_arch(options.arch)
85+
options = parse_options(args)
86+
arch.set_target_arch(OPTIONS.architecture)
12487
backend = arch.target.backend
125-
126-
# region [Enable/Disable Warnings]
127-
enabled_warnings = set(options.enable_warning or [])
128-
disabled_warnings = set(options.disable_warning or [])
129-
duplicated_options = [f"W{x}" for x in enabled_warnings.intersection(disabled_warnings)]
130-
131-
if duplicated_options:
132-
parser.error(f"Warning(s) {', '.join(duplicated_options)} cannot be enabled "
133-
f"and disabled simultaneously")
134-
return 2
135-
136-
for warn_code in enabled_warnings:
137-
errmsg.enable_warning(warn_code)
138-
139-
for warn_code in disabled_warnings:
140-
errmsg.disable_warning(warn_code)
141-
142-
# endregion
143-
144-
OPTIONS.org = src.api.utils.parse_int(options.org)
145-
if OPTIONS.org is None:
146-
parser.error("Invalid --org option '{}'".format(options.org))
147-
148-
if options.defines:
149-
for i in options.defines:
150-
macro = list(i.split('=', 1))
151-
name = macro[0]
152-
val = ''.join(macro[1:])
153-
OPTIONS.__DEFINES[name] = val
154-
zxbpp.ID_TABLE.define(name, value=val, lineno=0)
155-
156-
if OPTIONS.sinclair:
157-
OPTIONS.array_base = 1
158-
OPTIONS.string_base = 1
159-
OPTIONS.strictBool = True
160-
OPTIONS.case_insensitive = True
161-
162-
if options.ignore_case:
163-
OPTIONS.case_insensitive = True
164-
165-
debug.ENABLED = OPTIONS.debug_level > 0
166-
167-
if int(options.tzx) + int(options.tap) + int(options.asm) + int(options.emit_backend) + \
168-
int(options.parse_only) > 1:
169-
parser.error("Options --tap, --tzx, --emit-backend, --parse-only and --asm are mutually exclusive")
170-
return 3
171-
172-
if options.basic and not options.tzx and not options.tap:
173-
parser.error('Option --BASIC and --autorun requires --tzx or tap format')
174-
return 4
175-
176-
if options.append_binary and not options.tzx and not options.tap:
177-
parser.error('Option --append-binary needs either --tap or --tzx')
178-
return 5
179-
180-
if options.asm and options.memory_map:
181-
parser.error('Option --asm and --mmap cannot be used together')
182-
return 6
183-
184-
OPTIONS.use_basic_loader = options.basic
185-
OPTIONS.autorun = options.autorun
186-
187-
if options.tzx:
188-
OPTIONS.output_file_type = 'tzx'
189-
elif options.tap:
190-
OPTIONS.output_file_type = 'tap'
191-
elif options.asm:
192-
OPTIONS.output_file_type = 'asm'
193-
elif options.emit_backend:
194-
OPTIONS.output_file_type = 'ic'
195-
196-
args = [options.PROGRAM]
197-
if not os.path.exists(options.PROGRAM):
198-
parser.error("No such file or directory: '%s'" % args[0])
199-
return 2
200-
201-
if OPTIONS.memory_check:
202-
OPTIONS.__DEFINES['__MEMORY_CHECK__'] = ''
203-
zxbpp.ID_TABLE.define('__MEMORY_CHECK__', lineno=0)
204-
205-
if OPTIONS.array_check:
206-
OPTIONS.__DEFINES['__CHECK_ARRAY_BOUNDARY__'] = ''
207-
zxbpp.ID_TABLE.define('__CHECK_ARRAY_BOUNDARY__', lineno=0)
208-
209-
if OPTIONS.enable_break:
210-
OPTIONS.__DEFINES['__ENABLE_BREAK__'] = ''
211-
zxbpp.ID_TABLE.define('__ENABLE_BREAK__', lineno=0)
212-
213-
OPTIONS.include_path = options.include_path
214-
OPTIONS.input_filename = zxbparser.FILENAME = \
215-
os.path.basename(args[0])
216-
217-
if not OPTIONS.output_filename:
218-
OPTIONS.output_filename = \
219-
os.path.splitext(os.path.basename(OPTIONS.input_filename))[0] + os.path.extsep + \
220-
OPTIONS.output_file_type
221-
222-
if OPTIONS.stderr_filename:
223-
OPTIONS.stderr = open_file(OPTIONS.stderr_filename, 'wt', 'utf-8')
88+
args = [options.PROGRAM] # Strip out other options, because they're already set in the OPTIONS container
89+
input_filename = options.PROGRAM
22490

22591
zxbpp.setMode('basic')
22692
zxbpp.main(args)
@@ -297,7 +163,7 @@ def main(args=None, emitter=None):
297163
# Now filter them against the preprocessor again
298164
zxbpp.setMode('asm')
299165
zxbpp.OUTPUT = ''
300-
zxbpp.filter_(asm_output, args[0])
166+
zxbpp.filter_(asm_output, filename=input_filename)
301167

302168
# Now output the result
303169
asm_output = zxbpp.OUTPUT.split('\n')
@@ -318,7 +184,7 @@ def main(args=None, emitter=None):
318184
+ ['%s:' % backend.DATA_END_LABEL, '%s:' % backend.MAIN_LABEL] \
319185
+ asm_output + backend.emit_end()
320186

321-
if options.asm: # Only output assembler file
187+
if OPTIONS.output_file_type == FileType.ASM: # Only output assembler file
322188
with open_file(OPTIONS.output_filename, 'wt', 'utf-8') as output_file:
323189
output(asm_output, output_file)
324190
elif not options.parse_only:

0 commit comments

Comments
 (0)