2018-09-06 15:18:41 +00:00
|
|
|
#!/usr/bin/env python
|
2015-10-16 08:30:24 +00:00
|
|
|
|
|
|
|
from os.path import realpath, dirname, join
|
|
|
|
import sys
|
|
|
|
|
|
|
|
sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------- #
|
|
|
|
|
2018-03-06 16:28:34 +00:00
|
|
|
from argparse import ArgumentParser
|
2019-04-08 10:50:54 +00:00
|
|
|
import codecs
|
|
|
|
import json
|
2015-10-22 14:45:16 +00:00
|
|
|
from pprint import pprint
|
2019-04-11 15:53:43 +00:00
|
|
|
from subprocess import check_call
|
2015-10-16 08:30:24 +00:00
|
|
|
import sys
|
2019-04-11 15:53:43 +00:00
|
|
|
from tempfile import NamedTemporaryFile
|
2015-10-16 08:30:24 +00:00
|
|
|
import traceback
|
|
|
|
|
2019-05-14 14:01:10 +00:00
|
|
|
from sixtypical.symtab import SymbolTable
|
|
|
|
from sixtypical.parser import Parser, merge_programs
|
2015-10-21 14:45:14 +00:00
|
|
|
from sixtypical.analyzer import Analyzer
|
2018-09-07 08:10:20 +00:00
|
|
|
from sixtypical.outputter import outputter_class_for
|
2015-10-17 14:06:50 +00:00
|
|
|
from sixtypical.compiler import Compiler
|
2015-10-16 08:30:24 +00:00
|
|
|
|
|
|
|
|
2018-03-27 14:55:29 +00:00
|
|
|
def process_input_files(filenames, options):
|
2019-04-10 07:48:33 +00:00
|
|
|
symtab = SymbolTable()
|
2018-03-27 15:23:22 +00:00
|
|
|
|
2018-03-27 14:55:29 +00:00
|
|
|
programs = []
|
|
|
|
|
|
|
|
for filename in options.filenames:
|
|
|
|
text = open(filename).read()
|
2019-04-10 07:48:33 +00:00
|
|
|
parser = Parser(symtab, text, filename)
|
2018-03-27 15:23:22 +00:00
|
|
|
if options.debug:
|
2019-04-10 07:48:33 +00:00
|
|
|
print(symtab)
|
2018-03-27 14:55:29 +00:00
|
|
|
program = parser.program()
|
|
|
|
programs.append(program)
|
|
|
|
|
|
|
|
if options.parse_only:
|
|
|
|
return
|
|
|
|
|
2018-03-27 15:23:22 +00:00
|
|
|
program = merge_programs(programs)
|
2018-03-27 14:55:29 +00:00
|
|
|
|
2019-04-10 07:48:33 +00:00
|
|
|
analyzer = Analyzer(symtab, debug=options.debug)
|
2019-04-08 10:50:54 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
analyzer.analyze_program(program)
|
|
|
|
finally:
|
|
|
|
if options.dump_exit_contexts:
|
|
|
|
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ':')))
|
|
|
|
sys.stdout.write("\n")
|
2018-03-27 14:55:29 +00:00
|
|
|
|
2019-10-21 13:03:35 +00:00
|
|
|
if options.dump_callgraph:
|
2019-10-21 13:43:21 +00:00
|
|
|
graph = {}
|
|
|
|
|
2019-10-21 13:03:35 +00:00
|
|
|
for routine in program.routines:
|
2019-10-21 13:43:21 +00:00
|
|
|
potentially_calls = []
|
2019-10-21 13:03:35 +00:00
|
|
|
for called_routine in routine.called_routines:
|
2019-10-21 13:43:21 +00:00
|
|
|
# TODO if called_routine is a vector, this should be all routines which can be assigned to this vector
|
|
|
|
potentially_calls.append(called_routine.name)
|
|
|
|
graph[routine.name] = {
|
|
|
|
'potentially-calls': potentially_calls,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Reflexive closure
|
|
|
|
|
|
|
|
for routine in program.routines:
|
|
|
|
potentially_called_by = []
|
|
|
|
for (name, node) in graph.items():
|
|
|
|
potential_calls = node['potentially-calls']
|
|
|
|
if routine.name in potential_calls:
|
|
|
|
potentially_called_by.append(name)
|
|
|
|
graph[routine.name]['potentially-called-by'] = potentially_called_by
|
|
|
|
|
|
|
|
sys.stdout.write(json.dumps(graph, indent=4, sort_keys=True, separators=(',', ':')))
|
2019-10-21 13:03:35 +00:00
|
|
|
|
2018-04-05 09:52:14 +00:00
|
|
|
compilation_roster = None
|
2018-04-04 10:54:50 +00:00
|
|
|
if options.optimize_fallthru:
|
2018-04-04 14:09:48 +00:00
|
|
|
from sixtypical.fallthru import FallthruAnalyzer
|
|
|
|
|
2018-04-05 08:57:14 +00:00
|
|
|
def dump(data, label=None):
|
2018-04-04 13:20:56 +00:00
|
|
|
if not options.dump_fallthru_info:
|
|
|
|
return
|
2018-04-04 13:13:53 +00:00
|
|
|
if label:
|
|
|
|
sys.stdout.write("*** {}:\n".format(label))
|
2018-09-06 15:32:48 +00:00
|
|
|
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
|
2018-04-04 13:13:53 +00:00
|
|
|
sys.stdout.write("\n")
|
|
|
|
|
2019-04-10 07:48:33 +00:00
|
|
|
fa = FallthruAnalyzer(symtab, debug=options.debug)
|
2018-04-04 10:09:39 +00:00
|
|
|
fa.analyze_program(program)
|
2018-04-05 09:52:14 +00:00
|
|
|
compilation_roster = fa.serialize()
|
|
|
|
dump(compilation_roster)
|
2018-04-04 14:09:48 +00:00
|
|
|
|
2019-04-16 09:35:59 +00:00
|
|
|
if options.analyze_only or (options.output is None and not options.run_on):
|
2018-03-27 14:55:29 +00:00
|
|
|
return
|
|
|
|
|
2018-09-07 08:10:20 +00:00
|
|
|
start_addr = None
|
2018-04-23 12:18:01 +00:00
|
|
|
if options.origin is not None:
|
|
|
|
if options.origin.startswith('0x'):
|
2018-09-07 08:10:20 +00:00
|
|
|
start_addr = int(options.origin, 16)
|
2018-04-23 12:18:01 +00:00
|
|
|
else:
|
2018-09-07 08:10:20 +00:00
|
|
|
start_addr = int(options.origin, 10)
|
2018-09-06 17:25:29 +00:00
|
|
|
|
2019-04-16 09:35:59 +00:00
|
|
|
if options.run_on:
|
2019-04-11 15:53:43 +00:00
|
|
|
fh = NamedTemporaryFile(delete=False)
|
|
|
|
output_filename = fh.name
|
2019-04-16 09:35:59 +00:00
|
|
|
Outputter = outputter_class_for({
|
|
|
|
'x64': 'c64-basic-prg',
|
|
|
|
'xvic': 'vic20-basic-prg',
|
|
|
|
'stella': 'atari2600-cart',
|
|
|
|
}.get(options.run_on))
|
2019-04-11 15:53:43 +00:00
|
|
|
else:
|
|
|
|
fh = open(options.output, 'wb')
|
|
|
|
output_filename = options.output
|
2019-04-16 09:35:59 +00:00
|
|
|
Outputter = outputter_class_for(options.output_format)
|
2019-04-11 15:53:43 +00:00
|
|
|
|
2019-04-16 09:35:59 +00:00
|
|
|
outputter = Outputter(fh, start_addr=start_addr)
|
2019-04-11 15:53:43 +00:00
|
|
|
outputter.write_prelude()
|
|
|
|
compiler = Compiler(symtab, outputter.emitter)
|
|
|
|
compiler.compile_program(program, compilation_roster=compilation_roster)
|
|
|
|
outputter.write_postlude()
|
|
|
|
if options.debug:
|
|
|
|
pprint(outputter.emitter)
|
|
|
|
else:
|
|
|
|
outputter.emitter.serialize_to(fh)
|
|
|
|
|
|
|
|
fh.close()
|
|
|
|
|
2019-04-16 09:35:59 +00:00
|
|
|
if options.run_on:
|
2019-04-11 15:53:43 +00:00
|
|
|
emu = {
|
2019-04-16 09:35:59 +00:00
|
|
|
'x64': "x64 -config vicerc",
|
|
|
|
'xvic': "xvic -config vicerc",
|
|
|
|
'stella': "stella"
|
|
|
|
}.get(options.run_on)
|
2019-04-11 15:53:43 +00:00
|
|
|
if not emu:
|
2019-04-16 09:35:59 +00:00
|
|
|
raise ValueError("No emulator configured for selected --run-on '{}'".format(options.output_format))
|
2019-04-11 15:53:43 +00:00
|
|
|
|
|
|
|
command = "{} {}".format(emu, output_filename)
|
|
|
|
check_call(command, shell=True)
|
2018-03-27 14:55:29 +00:00
|
|
|
|
|
|
|
|
2015-10-16 08:30:24 +00:00
|
|
|
if __name__ == '__main__':
|
2019-04-10 09:42:50 +00:00
|
|
|
argparser = ArgumentParser()
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2018-03-06 16:28:34 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
'filenames', metavar='FILENAME', type=str, nargs='+',
|
|
|
|
help="The SixtyPical source files to compile."
|
|
|
|
)
|
2018-03-29 14:07:44 +00:00
|
|
|
|
2018-09-06 15:18:41 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--output", "-o", type=str, metavar='FILENAME',
|
|
|
|
help="File to which generated 6502 code will be written."
|
|
|
|
)
|
2018-03-13 13:33:01 +00:00
|
|
|
argparser.add_argument(
|
2018-04-23 12:18:01 +00:00
|
|
|
"--origin", type=str, default=None,
|
2018-03-13 13:33:01 +00:00
|
|
|
help="Location in memory where the `main` routine will be "
|
2018-04-23 12:18:01 +00:00
|
|
|
"located. Default: depends on output format."
|
2018-03-13 13:33:01 +00:00
|
|
|
)
|
2018-03-06 16:28:34 +00:00
|
|
|
argparser.add_argument(
|
2018-04-23 12:18:01 +00:00
|
|
|
"--output-format", type=str, default='raw',
|
|
|
|
help="Executable format to produce; also sets a default origin. "
|
|
|
|
"Options are: raw, prg, c64-basic-prg, vic20-basic-prg, atari2600-cart."
|
|
|
|
"Default: raw."
|
2018-03-06 16:28:34 +00:00
|
|
|
)
|
2018-03-29 14:07:44 +00:00
|
|
|
|
2018-03-06 16:28:34 +00:00
|
|
|
argparser.add_argument(
|
2018-03-29 14:07:44 +00:00
|
|
|
"--analyze-only",
|
2018-03-06 16:28:34 +00:00
|
|
|
action="store_true",
|
2018-03-29 14:07:44 +00:00
|
|
|
help="Only parse and analyze the program; do not compile it."
|
|
|
|
)
|
2019-04-08 10:50:54 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--dump-exit-contexts",
|
|
|
|
action="store_true",
|
|
|
|
help="Dump a map, in JSON, of the analysis context at each exit of each routine "
|
|
|
|
"after analyzing the program."
|
|
|
|
)
|
2018-03-29 14:07:44 +00:00
|
|
|
argparser.add_argument(
|
2018-04-04 10:54:50 +00:00
|
|
|
"--optimize-fallthru",
|
2018-03-29 14:07:44 +00:00
|
|
|
action="store_true",
|
2018-04-04 10:54:50 +00:00
|
|
|
help="Reorder the routines in the program to maximize the number of tail calls "
|
|
|
|
"that can be removed by having execution 'fall through' to the next routine."
|
2018-03-06 16:28:34 +00:00
|
|
|
)
|
2018-04-04 10:09:39 +00:00
|
|
|
argparser.add_argument(
|
2018-04-04 10:54:50 +00:00
|
|
|
"--dump-fallthru-info",
|
2018-04-04 10:09:39 +00:00
|
|
|
action="store_true",
|
2019-04-08 10:50:54 +00:00
|
|
|
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
|
2018-04-04 10:09:39 +00:00
|
|
|
)
|
2019-10-21 13:03:35 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--dump-callgraph",
|
|
|
|
action="store_true",
|
|
|
|
help="Dump the call graph, in JSON, to stdout after analyzing the program."
|
|
|
|
)
|
2018-03-06 16:28:34 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--parse-only",
|
|
|
|
action="store_true",
|
|
|
|
help="Only parse the program; do not analyze or compile it."
|
|
|
|
)
|
2018-03-29 14:07:44 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--debug",
|
|
|
|
action="store_true",
|
|
|
|
help="Display debugging information when analyzing and compiling."
|
|
|
|
)
|
2019-04-11 15:53:43 +00:00
|
|
|
argparser.add_argument(
|
2019-04-16 09:35:59 +00:00
|
|
|
"--run-on", type=str, default=None,
|
|
|
|
help="If given, engage 'load-and-go' operation with the given emulator: write "
|
2019-04-16 10:37:46 +00:00
|
|
|
"the output to a temporary filename using an appropriate --output-format, "
|
2019-04-16 09:35:59 +00:00
|
|
|
"and boot the emulator with it. Options are: x64, xvic, stella."
|
2019-04-11 15:53:43 +00:00
|
|
|
)
|
2018-03-06 16:28:34 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--traceback",
|
|
|
|
action="store_true",
|
|
|
|
help="When an error occurs, display a full Python traceback."
|
|
|
|
)
|
2019-04-10 09:42:50 +00:00
|
|
|
argparser.add_argument(
|
|
|
|
"--version",
|
|
|
|
action="version",
|
2019-10-21 13:07:41 +00:00
|
|
|
version="%(prog)s 0.21"
|
2019-04-10 09:42:50 +00:00
|
|
|
)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2018-03-06 16:28:34 +00:00
|
|
|
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
|
|
|
remainder = ' '.join(unknown)
|
2015-10-16 08:30:24 +00:00
|
|
|
|
2018-03-27 14:55:29 +00:00
|
|
|
try:
|
|
|
|
process_input_files(options.filenames, options)
|
|
|
|
except Exception as e:
|
|
|
|
if options.traceback:
|
|
|
|
raise
|
2018-02-06 16:14:44 +00:00
|
|
|
else:
|
2018-03-27 14:55:29 +00:00
|
|
|
traceback.print_exception(e.__class__, e, None)
|
|
|
|
sys.exit(1)
|