git @ Cat's Eye Technologies SixtyPical / master bin / sixtypical
master

Tree @master (Download .tar.gz)

sixtypical @masterraw · history · blame

#!/usr/bin/env python3

# SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain.
# For more information, please refer to <https://unlicense.org/>
# SPDX-License-Identifier: Unlicense

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)