#!/usr/bin/env python3 from os.path import realpath, dirname, join import sys sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src')) # ----------------------------------------------------------------- # from argparse import ArgumentParser import codecs import json from pprint import pprint from subprocess import check_call import sys from tempfile import NamedTemporaryFile import traceback from sixtypical.symtab import SymbolTable from sixtypical.parser import Parser, load_program, merge_programs from sixtypical.analyzer import Analyzer from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines from sixtypical.outputter import outputter_class_for from sixtypical.compiler import Compiler def process_input_files(filenames, options): symtab = SymbolTable() include_path = options.include_path.split(':') programs = [] for filename in options.filenames: program = load_program(filename, symtab, include_path, include_file=False) if options.debug: print(symtab) programs.append(program) if options.parse_only: return program = merge_programs(programs) analyzer = Analyzer(symtab, debug=options.debug) 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") callgraph = construct_callgraph(program) if options.dump_callgraph: sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': '))) if options.prune_unreachable_routines: program = prune_unreachable_routines(program, callgraph) compilation_roster = None if options.optimize_fallthru: from sixtypical.fallthru import FallthruAnalyzer fa = FallthruAnalyzer(symtab, debug=options.debug) fa.analyze_program(program) compilation_roster = fa.serialize() if options.dump_fallthru_info: sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': '))) if options.analyze_only or (options.output is None and not options.run_on): return start_addr = None if options.origin is not None: if options.origin.startswith('0x'): start_addr = int(options.origin, 16) else: start_addr = int(options.origin, 10) if options.run_on: fh = NamedTemporaryFile(delete=False) output_filename = fh.name Outputter = outputter_class_for({ 'x64': 'c64-basic-prg', 'xvic': 'vic20-basic-prg', 'stella': 'atari2600-cart', }.get(options.run_on)) else: fh = open(options.output, 'wb') output_filename = options.output Outputter = outputter_class_for(options.output_format) outputter = Outputter(fh, start_addr=start_addr) 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() if options.run_on: emu = { 'x64': "x64 -config vicerc", 'xvic': "xvic -config vicerc", 'stella': "stella" }.get(options.run_on) if not emu: raise ValueError("No emulator configured for selected --run-on '{}'".format(options.output_format)) command = "{} {}".format(emu, output_filename) check_call(command, shell=True) if __name__ == '__main__': argparser = ArgumentParser() argparser.add_argument( 'filenames', metavar='FILENAME', type=str, nargs='+', help="The SixtyPical source files to compile." ) argparser.add_argument( "--output", "-o", type=str, metavar='FILENAME', help="File to which generated 6502 code will be written." ) argparser.add_argument( "--origin", type=str, default=None, help="Location in memory where the `main` routine will be " "located. Default: depends on output format." ) argparser.add_argument( "--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." ) argparser.add_argument( "--include-path", "-I", type=str, metavar='PATH', default='.', help="A colon-separated list of directories in which to look for " "files which are included during `include` directives." ) argparser.add_argument( "--analyze-only", action="store_true", help="Only parse and analyze the program; do not compile it." ) 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." ) argparser.add_argument( "--optimize-fallthru", action="store_true", 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." ) argparser.add_argument( "--dump-fallthru-info", action="store_true", help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program." ) argparser.add_argument( "--prune-unreachable-routines", action="store_true", help="Omit code for unreachable routines (as determined by the callgraph) " "from the final output." ) argparser.add_argument( "--dump-callgraph", action="store_true", help="Dump the call graph, in JSON, to stdout after analyzing the program." ) argparser.add_argument( "--parse-only", action="store_true", help="Only parse the program; do not analyze or compile it." ) argparser.add_argument( "--debug", action="store_true", help="Display debugging information when analyzing and compiling." ) argparser.add_argument( "--run-on", type=str, default=None, help="If given, engage 'load-and-go' operation with the given emulator: write " "the output to a temporary filename using an appropriate --output-format, " "and boot the emulator with it. Options are: x64, xvic, stella." ) argparser.add_argument( "--traceback", action="store_true", help="When an error occurs, display a full Python traceback." ) argparser.add_argument( "--version", action="version", version="%(prog)s 0.21" ) options, unknown = argparser.parse_known_args(sys.argv[1:]) remainder = ' '.join(unknown) try: process_input_files(options.filenames, options) except Exception as e: if options.traceback: raise else: traceback.print_exception(e.__class__, e, None) sys.exit(1)