main.py
5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import argparse, sys, os, os.path, subprocess
import ply.yacc as yacc
from . import cparse, cvisitors, cx86, cttc
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, path, test) :
self.arch = arch
self.path = path
self.test = test
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, 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())
comp = self.arch.CodeGenVisitor(self.path, self.test)
self._compile_phase(comp)
if ast_file is not None:
self._compile_phase(cvisitors.ASTPrinterVisitor(ast_file))
return comp
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, show_ast) :
"Compiles the given code string to the given file object."
self.code = code
try:
chunk = self._do_compile(show_ast)
except cparse.ParseError:
print("... errors encountered, bailing")
return
except CompileError:
self._print_stats()
print("... errors encountered, bailing")
return
self._print_stats()
print("... compile successful")
return chunk
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("--cpp", "-p", action="store_true", default=False,
help="process input file through cpp")
parser.add_argument("--arch", action="store", choices=list(ARCH), default="ttc",
help="output assembly for specified architecture"
" (default 'ttc')")
parser.add_argument("--compile", "-c", action="store_true", default=False,
help="compile generated ASM")
parser.add_argument("--test", "-t", action="store_true", default=False,
help="test compiler")
parser.add_argument("source", nargs="+", metavar="PATH",
help="C source files(s) to compile")
args = parser.parse_args(args)
chunks = []
if args.test :
print("Generating tests")
print("..$ gcc -o cct-test -D CC %s" % " ".join(args.source))
subprocess.run(["gcc", "-o", "cct-test", "-D", "CC"] + args.source)
print("..$ cct-test")
testdata = subprocess.check_output("cct-test").decode()
args.test = dict(line.rsplit(" => ", 1) for line in testdata.splitlines())
for src in args.source :
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
if args.cpp :
print("Preprocessing %r" % src)
cpp = ARCH[args.arch].cpp(args, src)
print("..$ %s" % " ".join(cpp))
code = subprocess.check_output(cpp).decode()
print("Compiling preprocessed %r (line numbers may have changed)" % src)
else :
code = "\n".join(l.rstrip() for l in open(src).readlines())
print("Compiling %r" % src)
ret = Compiler(ARCH[args.arch], src, args.test).compile(code, ast_file)
if ast_file is not None :
ast_file.close()
if ret is None :
sys.exit(1)
chunks.append(ret)
ARCH[args.arch].CodeGenVisitor.concat(args.o, chunks)
args.o.flush()
if args.compile :
tgt, cmd = ARCH[args.arch].compile(args.o.name)
print("Building %r" % tgt)
print(".. $ %s" % " ".join(cmd))
ret = subprocess.run(cmd)
print("... exited with status %s" % ret.returncode)