#!/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) if options.dump_fallthru_map: import json sys.stdout.write(json.dumps(program.fallthru_map, indent=4, sort_keys=True)) sys.stdout.write("\n") if options.analyze_only: return fh = sys.stdout if options.origin.startswith('0x'): start_addr = int(options.origin, 16) else: start_addr = int(options.origin, 10) output_format = options.output_format prelude = [] if options.prelude == 'c64': output_format = 'prg' start_addr = 0x0801 prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32, 0x30, 0x36, 0x31, 0x00, 0x00, 0x00] elif options.prelude == 'vic20': output_format = 'prg' start_addr = 0x1001 prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34, 0x31, 0x30, 0x39, 0x00, 0x00, 0x00] elif options.prelude == 'atari2600': output_format = 'crtbb' start_addr = 0xf000 prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9, 0x00,0x95, 0x00, 0xca, 0xd0, 0xfb] elif options.prelude: raise NotImplementedError("Unknown prelude: {}".format(options.prelude)) # 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 output_format == '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) # 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 output_format == 'crtbb': 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='0xc000', help="Location in memory where the `main` routine will be " "located. Default: 0xc000." ) argparser.add_argument( "--output-format", type=str, default='prg', help="Executable format to produce. Options are: prg, crtbb. " "Default: prg." ) argparser.add_argument( "--prelude", type=str, help="Insert a snippet of code before the compiled program so that " "it can be booted automatically on a particular platform. " "Also sets the origin and format. " "Options are: c64, vic20, atari2600." ) argparser.add_argument( "--analyze-only", action="store_true", help="Only parse and analyze the program; do not compile it." ) argparser.add_argument( "--dump-fallthru-map", action="store_true", help="Dump the fallthru map 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)