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

Tree @0.16 (Download .tar.gz)

sixtypical @0.16raw · history · blame

#!/usr/bin/env python

"""Usage: sixtypical [OPTIONS] FILES

Analyzes and compiles a Sixtypical program.
"""

from os.path import realpath, dirname, join
import sys

sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))

# ----------------------------------------------------------------- #

import codecs
from argparse import ArgumentParser
from pprint import pprint
import sys
import traceback

from sixtypical.parser import Parser, ParsingContext
from sixtypical.analyzer import Analyzer
from sixtypical.emitter import Emitter, Byte, Word
from sixtypical.compiler import Compiler


def merge_programs(programs):
    """Assumes that the programs do not have any conflicts."""

    from sixtypical.ast import Program

    full = Program(1, defns=[], routines=[])
    for p in programs:
        full.defns.extend(p.defns)
        full.routines.extend(p.routines)

    return full


def process_input_files(filenames, options):
    context = ParsingContext()

    programs = []

    for filename in options.filenames:
        text = open(filename).read()
        parser = Parser(context, text, filename)
        if options.debug:
            print(context)
        program = parser.program()
        programs.append(program)

    if options.parse_only:
        return

    program = merge_programs(programs)

    analyzer = Analyzer(debug=options.debug)
    analyzer.analyze_program(program)

    compilation_roster = None
    if options.optimize_fallthru:
        from sixtypical.fallthru import FallthruAnalyzer

        def dump(data, label=None):
            import json
            if not options.dump_fallthru_info:
                return
            if label:
                sys.stdout.write("*** {}:\n".format(label))
            sys.stdout.write(json.dumps(data, indent=4, sort_keys=True))
            sys.stdout.write("\n")

        fa = FallthruAnalyzer(debug=options.debug)
        fa.analyze_program(program)
        compilation_roster = fa.serialize()
        dump(compilation_roster)

    if options.analyze_only:
        return

    fh = sys.stdout

    if options.output_format == 'raw':
        start_addr = 0x0000
        prelude = []
    elif options.output_format == 'prg':
        start_addr = 0xc000
        prelude = []
    elif options.output_format == 'c64-basic-prg':
        start_addr = 0x0801
        prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
                   0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
    elif options.output_format == 'vic20-basic-prg':
        start_addr = 0x1001
        prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
                   0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
    elif options.output_format == 'atari2600-cart':
        start_addr = 0xf000
        prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
                   0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb]
    else:
        raise NotImplementedError("Unknown output format: {}".format(options.output_format))

    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 we are outputting a .PRG, we output the load address first.
    # We don't use the Emitter for this b/c not part of addr space.
    if options.output_format in ('prg', 'c64-basic-prg', 'vic20-basic-prg'):
        fh.write(Word(start_addr).serialize(0))

    emitter = Emitter(start_addr)
    for byte in prelude:
        emitter.emit(Byte(byte))
    compiler = Compiler(emitter)
    compiler.compile_program(program, compilation_roster=compilation_roster)

    # If we are outputting a cartridge with boot and BRK address
    # at the end, pad to ROM size minus 4 bytes, and emit addresses.
    if options.output_format == 'atari2600-cart':
        emitter.pad_to_size(4096 - 4)
        emitter.emit(Word(start_addr))
        emitter.emit(Word(start_addr))

    if options.debug:
        pprint(emitter.accum)
    else:
        emitter.serialize(fh)


if __name__ == '__main__':
    argparser = ArgumentParser(__doc__.strip())

    argparser.add_argument(
        'filenames', metavar='FILENAME', type=str, nargs='+',
        help="The SixtyPical source files to compile."
    )

    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(
        "--analyze-only",
        action="store_true",
        help="Only parse and analyze the program; do not compile it."
    )
    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 fallthru map and ordering 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(
        "--traceback",
        action="store_true",
        help="When an error occurs, display a full Python traceback."
    )

    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)