-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path4004cli.py
More file actions
393 lines (328 loc) · 12.1 KB
/
4004cli.py
File metadata and controls
393 lines (328 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# Import system libraries
import os
import sys
# Import resources library (part of setuptools)
import pkg_resources
# Import toml library
import toml
from toml import TomlDecodeError
# Import click library
import click
# Import Pyntel4004 functionality
from assembler.assemble import assemble
from disassembler.disassemble import disassemble
from executer.execute import execute
from executer.exe_supporting import retrieve
from hardware.processor import Processor
from shared.shared import print_messages
class Error(Exception):
"""Base class for other exceptions"""
class CoreNotInstalled(Error):
"""Exception for when Pyntel4004 is not installed"""
class ConfigFileNotFound(Error):
"""Exception for when the configuration file specified cannot be found"""
class BadFormat(Error):
"""Exception for when the configuration file is badly formatted"""
def excepthook(exc, value, traceback):
print(value)
def check_quiet(quiet, configuration):
if quiet is None:
if "quiet" in configuration:
quiet = configuration["quiet"]
else:
quiet = False
else:
quiet = True
return quiet
def check_exec(exec, configuration):
if exec is None:
if "exec" in configuration:
exec = configuration["exec"]
else:
exec = False
else:
exec = True
return exec
def check_inst(inst):
if inst is None:
inst = 4096
else:
if inst < 1 or inst > 4096:
raise click.BadOptionUsage("--inst", "Instructions should be" +
"between 1 and 4096")
return inst
def check_monitor(monitor, configuration):
if monitor is None:
if "monitor" in configuration:
monitor = configuration["monitor"]
else:
monitor = False
else:
monitor = True
return monitor
def check_dis_content(configuration, object, inst, labels):
if "object" in configuration and object is None:
object_file = configuration["object"]
if object_file is None:
raise click.BadOptionUsage(
"--object/--config", "No object file specified\n")
if "inst" in configuration and inst is None:
inst = configuration["inst"]
if labels is False and "labels" in configuration:
labels = configuration["labels"]
else:
labels = True
return object_file, inst, labels
def check_asm_content(configuration, input_file, output, type_type):
if "input" in configuration and input_file is None:
input_file = configuration["input"]
if "output" in configuration and output == 'default':
output = configuration["output"]
if "type" in configuration and type_type == ('None',):
type_type = configuration["type"]
if input_file is None and output == 'default' \
and type_type == ('None',):
raise click.BadOptionUsage(
"--config", "Empty 'asm' section in configuration file\n")
return input_file, output, type_type
def check_type(type_type):
# Check --type parameters
# Raise error if not valid
good = True
all_found = False
for i in type_type:
if i.upper() in ('ALL', ):
all_found = True
if i.upper() not in ('ALL', 'OBJ', 'H', 'BIN'):
good = False
if good is False:
raise click.BadOptionUsage("--type", "Invalid output type specified\n")
# Check --type parameters - ALL cannot be specified with others
# Raise error if not valid
if all_found:
others = True
for i in type_type:
if i.upper() in ('OBJ', 'H', 'BIN'):
others = False
if others is False:
raise click.BadOptionUsage("--type", "Cannot specify 'ALL' " +
"with any others\n")
def getcoreversion():
__core_version__ = 'Installed'
try:
__core_version__ = pkg_resources.require(core_name)[0].version
except CoreNotInstalled:
__core_version__ = 'Not Installed'
else:
__core_version__ = 'Installed but no legal version'
return __core_version__
package = "Pyntel4004-cli"
core_name = 'Pyntel4004'
cini = '\n\n' + core_name + ' core is not installed - use \n\n' + \
' pip install ' + core_name + '\n'
module = os.path.basename(sys.argv[0])
__version__ = pkg_resources.require(package)[0].version
__core_version__ = getcoreversion()
sys.excepthook = excepthook
# ----------- Utility Functionality ----------- #
def get_config(toml_file: str):
"""
Retrieve a configuration file
Parameters
----------
toml_file: str, mandatory
Name of the configuration file
Returns
-------
configuration: str
String containing the configuration data
Raises
------
ConfigFileNotFound - the file cannot be opened
BadFormat - The configuration file is badly formatted TOML
Notes
-----
N/A
"""
configuration = None
try:
_ = open(toml_file)
except OSError as e:
if str(e.strerror[0:12]) == 'No such file':
raise ConfigFileNotFound('Error:Configuration file not found.')
try:
configuration = toml.load(toml_file)
except (TypeError, TomlDecodeError):
raise BadFormat('Badly formatted configuration file')
return configuration
# ----------- Check Functionality ----------- #
def is_core_installed(package_name: str):
"""
Check to see if the Pyntel4004 core is installed
Parameters
----------
package_name: str, mandatory
Name of the Pyntel4004 core package
Returns
-------
True - if the core package is installed
False - if not
Raises
------
N/A
Notes
-----
N/A
"""
import importlib.util
spec = importlib.util.find_spec(package_name)
if spec is None:
return False
else:
return True
# ----------- Main Functionality ----------- #
@click.group()
@click.help_option('--help', '-h')
@click.version_option(__version__, '--version', '-v',
prog_name=package + ' (' + module + ')',
message='%(prog)s, Version %(version)s \n' + core_name +
' ' + 'Version: ' + __core_version__ + '\n' +
'Learn more at https://github.com/alshapton/Pyntel4004')
@click.pass_context
def cli(ctx):
'''
Command Line Interface (CLI) for Pyntel4004,
a virtual Intel© 4004 processor written in Python.
Learn more at https://github.com/alshapton/Pyntel4004
'''
pass
@cli.command()
@click.option('--input', '-i',
help='4004 assembler source code.',
type=str, metavar='<filename>')
@click.option('--output', '-o',
help='4004 output file (without extension).', default='default',
metavar='<filename>')
@click.option('--exec', '-x', is_flag=True, help='Execute program',
default=None)
@click.option('--quiet', '-q', is_flag=True, default=None,
help='Output on/off [either/or ]')
@click.option('--monitor', '-m', is_flag=True, default=None,
help='Monitor on/off [but not both]')
@click.option('--type', '-t', multiple=True, default=['None'],
metavar='<extension>',
help='Multiple output types can be specified - bin/obj/h/ALL')
@click.option('--config', '-c', metavar='<filename>',
help='Configuration file', default=None)
@click.help_option('--help', '-h')
def asm(input, output, exec, monitor, quiet, type, config):
"""Assemble the input file"""
# Eliminate the "Shadowing" of builtins
input_file = input
type_type = type
# Ensure that the core Pyntel4004 is installed
# Exit if not
if not is_core_installed(core_name):
raise CoreNotInstalled(cini)
# Get configuration (override from command line if required)
if config is not None:
configuration = get_config(config)
if "asm" in configuration:
asm_configuration = configuration["asm"]
input_file, output, type_type = \
check_asm_content(asm_configuration, input_file,
output, type_type)
exec = check_exec(exec, asm_configuration)
monitor = check_monitor(monitor, asm_configuration)
quiet = check_quiet(quiet, asm_configuration)
else:
raise click.BadOptionUsage(
"--config", "No 'asm' section in configuration file\n")
# Create new instance of a processor
chip = Processor()
# Check exclusiveness of parameters
# Raise an error if not allowed
if quiet and monitor:
raise click.BadParameter("Invalid Parameter Combination: " +
"--quiet and --monitor cannot be used " +
"together\n")
# Check existence of --type parameter
# Raise error if not present
if type_type == ('None',):
raise click.BadOptionUsage("--type", "No output type specified\n")
check_type(type_type)
result = assemble(input_file, output, chip, quiet, str(type_type))
if result and exec:
print_messages(quiet, 'EXEC', chip, '')
did_execute = execute(chip, 'rom', 0, monitor, quiet)
if did_execute:
print_messages(quiet, 'BLANK', chip, '')
print_messages(quiet, 'ACC', chip, '')
print_messages(quiet, 'CARRY', chip, '')
print_messages(quiet, 'BLANK', chip, '')
@cli.command()
@click.option('--object', '-o',
help='4004 object or binary file (specify extension)',
metavar='<filename>', type=str)
@click.option('--inst', '-i',
help='Instuctions to disassemble',
metavar='<Between 1 & 4096>',
type=int)
@click.option('--labels', '-l',
help='Show label table',
is_flag=True, default=False)
@click.option('--config', '-c', metavar='<filename>',
help='Configuration file', default=None)
@click.help_option('--help', '-h')
def dis(object, inst, labels, config) -> None:
"""Disassemble the input file"""
# Ensure that the core Pyntel4004 is installed
# Exit if not
if not is_core_installed(core_name):
raise CoreNotInstalled(cini)
object_file = object
if config is not None:
configuration = get_config(config)
if "dis" in configuration:
dis_configuration = configuration["dis"]
object_file, inst, labels = check_dis_content(dis_configuration,
object, inst, labels)
else:
raise click.BadOptionUsage(
"--config", "No 'dis' section in configuration file\n")
inst = check_inst(inst)
# Create new instance of a processor
chip = Processor()
memory_space, _, lbls = retrieve(object_file, chip, False)
disassemble(chip, memory_space, 0, inst, labels, lbls)
@cli.command()
@click.option('--object', '-o',
help='4004 object or binary file (specify extension)',
metavar='<filename>', type=str)
@click.option('--quiet', '-q', is_flag=True,
help='Output on/off')
@click.option('--config', '-c', metavar='<filename>',
help='Configuration file', default=None)
@click.help_option('--help', '-h')
def exe(object, quiet, config):
"""Execute the object file"""
# Ensure that the core Pyntel4004 is installed
# Exit if not
if not is_core_installed(core_name):
raise CoreNotInstalled(cini)
if config is not None:
configuration = get_config(config)
object_file = object
if "exe" in configuration:
exe_configuration = configuration["exe"]
if "object" in exe_configuration and object_file is None:
object_file = exe_configuration["object"]
quiet = check_quiet(quiet, exe_configuration)
else:
raise click.BadOptionUsage(
"--config", "No 'exe' section in configuration file\n")
# Create new instance of a processor
chip = Processor()
result = retrieve(object_file, chip, quiet)
memory_space = result[0]
execute(chip, memory_space, 0, False, quiet)