main.py 3.86 KB
import argparse, sys, os.path

import ply.yacc as yacc
from . import cparse, cvisitors, cx86
cttc = cx86

ARCH = {"x86" : cx86,
        "ttc" : cttc}

ARCHDOC = """supported target architectures:
  x86    Intel 80x86 assembly (in GNU Assemble format)
  ttc    The Tiny Computer assembly
"""

class CompileError (Exception) :
    "Exception raised when there's been a compilation error."
    pass

class Compiler (object) :
    """This object encapsulates the front-end for the compiler and
    serves as a facade interface to the 'meat' of the compiler
    underneath."""
    def __init__ (self, arch) :
        self.arch = arch
        self.total_errors = 0
        self.total_warnings = 0
    def _parse (self) :
        "Parses the source code."
        self.ast = yacc.parse(self.code)
    def _compile_phase (self, visitor) :
        "Applies a visitor to the abstract syntax tree."
        visitor.visit(self.ast)
        self.total_errors += visitor.errors
        self.total_warnings += visitor.warnings
        if visitor.has_errors():
            raise CompileError()
    def _do_compile (self, outfile, ast_file) :
        """Compiles the code to the given file object.  Enabling
        show_ast prints out the abstract syntax tree."""
        self._parse()
        self._compile_phase(cvisitors.SymtabVisitor())
        self._compile_phase(cvisitors.TypeCheckVisitor())
        self._compile_phase(cvisitors.FlowControlVisitor())
        self._compile_phase(self.arch.CodeGenVisitor(outfile))
        if ast_file is not None:
            self._compile_phase(cvisitors.ASTPrinterVisitor(ast_file))
    def _print_stats (self) :
        "Prints the total number of errors/warnings from compilation."
        if self.total_errors :
            print("%d errors" % self.total_errors)
        if self.total_warnings :
            print("%s warnings" % self.total_warnings)
    def compile (self, code, outfile, show_ast) :
        "Compiles the given code string to the given file object."
        self.code = code
        try:
            self._do_compile(outfile, show_ast)
        except cparse.ParseError:
            print("Errors encountered, bailing.")
            return 1
        except CompileError:
            self._print_stats()
            print("Errors encountered, bailing.")
            return 1
        self._print_stats()
        print("Compile successful.")
        return 0

def main (args=None) :
    parser = argparse.ArgumentParser(prog="cct",
                                     epilog=ARCHDOC,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("-o", action="store", metavar="PATH",
                        type=argparse.FileType('w'), default=sys.stdout,
                        help="write output to PATH")
    parser.add_argument("--ast", action="store_true", default=False,
                        help="dump AST for each C file")
    parser.add_argument("--arch", action="store", choices=list(ARCH),
                        default="ttc",
                        help="output assembly for specified architecture (default 'ttc')")
    parser.add_argument("-t", "--ttc", action="store_const", const="ttc",
                        dest="arch", help="output TTC assembly")
    parser.add_argument("source", nargs="+", metavar="PATH",
                        help="C source files(s) to compile")
    args = parser.parse_args(args)
    for src in args.source :
        print("Compiling %r" % src)
        if args.ast :
            ast_path = os.path.splitext(src)[0] + ".ast"
            print("Dumping AST to %r" % ast_path)
            ast_file = open(ast_path, "w")
        else :
            ast_file = None
        code = "\n".join(open(src).readlines())
        retval = Compiler(ARCH[args.arch]).compile(code, args.o, ast_file)
        if ast_file is not None :
            ast_file.close()
        if retval != 0 :
            sys.exit(retval)