Repository navigation
Expand file tree
/
Copy pathcodegen_c.py
More file actions
157 lines (127 loc) · 5.22 KB
/
Copy pathcodegen_c.py
File metadata and controls
157 lines (127 loc) · 5.22 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# codegen_c.py
# C Code Generator — Second compilation target
#
# WHY TWO TARGETS?
# A cross compiler is defined by having a TARGET different from the HOST.
# Generating BOTH x86 assembly AND valid C code from the same TAC proves
# your compiler is genuinely target-independent.
# The IR (TAC) is the bridge — write once, target many.
#
# This is exactly how LLVM works:
# Source → LLVM IR → x86 / ARM / WASM / ...
# Our version:
# CPL → TAC → x86 assembly
# → C code ← this file
from ir import TACInstruction
class CCodeGenerator:
"""
Translates TAC instructions into compilable C code.
The output can be compiled with: gcc output.c -o program
"""
def __init__(self):
self.output = []
self.declared = set() # track which C variables we've declared
self.indent_lvl = 1
def emit(self, line=''):
pad = ' ' * self.indent_lvl
self.output.append(pad + line if line else '')
def emit_raw(self, line):
self.output.append(line)
def declare(self, name: str, type_='int'):
"""Emit a C variable declaration if not already declared."""
if name not in self.declared:
self.declared.add(name)
c_type = 'double' if type_ == 'float' else 'int'
self.emit(f"{c_type} {name};")
def generate(self, instructions: list) -> str:
self.emit_raw("/* CPL Compiled Output — C Target */")
self.emit_raw("/* Generated by cplc (Bootstrap-Enabled Cross Compiler) */")
self.emit_raw("")
self.emit_raw("#include <stdio.h>")
self.emit_raw("#include <stdlib.h>")
self.emit_raw("")
# Check if there are explicit function declarations
has_funcs = any(i.op == 'FUNC_BEGIN' for i in instructions)
in_func = False
if not has_funcs:
self.emit_raw("int main() {")
# Pre-declare all variables used
self._predeclare_vars(instructions)
for instr in instructions:
self._emit(instr)
self.emit("return 0;")
self.emit_raw("}")
else:
for instr in instructions:
self._emit(instr)
return "\n".join(self.output)
def _predeclare_vars(self, instructions):
"""
Pre-declare all temp and user variables at top of main().
WHY? C requires declarations before use. We scan all instructions
to find every variable that gets written to, then declare them all upfront.
"""
seen = set()
for instr in instructions:
if instr.result and isinstance(instr.result, str) \
and not instr.result.startswith('L'):
if instr.result not in seen:
seen.add(instr.result)
self.emit(f"int {instr.result}; /* {'temp' if instr.result.startswith('t') else 'user var'} */")
if seen:
self.emit("")
def _emit(self, instr: TACInstruction):
op = instr.op
if op == 'FUNC_BEGIN':
self.emit_raw(f"\nint {instr.result}() {{")
self.indent_lvl = 1
elif op == 'FUNC_END':
self.indent_lvl = 1
self.emit_raw("}")
elif op == 'LABEL':
# C uses goto labels (dedented)
self.emit_raw(f" {instr.result}:")
elif op == 'ASSIGN':
val = self._val(instr.arg1)
self.emit(f"{instr.result} = {val};")
elif op == 'BINOP':
l = self._val(instr.arg1)
r = self._val(instr.arg2)
self.emit(f"{instr.result} = {l} {instr.op_sym} {r};")
elif op == 'UNARY':
v = self._val(instr.arg1)
self.emit(f"{instr.result} = {instr.op_sym}{v};")
elif op == 'JUMP':
self.emit(f"goto {instr.result};")
elif op == 'JUMPIF':
self.emit(f"if ({self._val(instr.arg1)}) goto {instr.result};")
elif op == 'JUMPIFNOT':
self.emit(f"if (!({self._val(instr.arg1)})) goto {instr.result};")
elif op == 'PRINT':
self.emit(f'printf("%d\\n", {self._val(instr.arg1)});')
elif op == 'INPUT':
self.emit(f'scanf("%d", &{instr.arg1});')
elif op == 'RETURN':
val = self._val(instr.arg1) if instr.arg1 else '0'
self.emit(f"return {val};")
elif op == 'PARAM':
# Store params for the next CALL
if not hasattr(self, '_pending_params'):
self._pending_params = []
self._pending_params.append(self._val(instr.arg1))
elif op == 'CALL':
params = getattr(self, '_pending_params', [])
args = ', '.join(params)
self.emit(f"{instr.result} = {instr.arg1}({args});")
self._pending_params = []
elif op == 'ARRAY_GET':
self.emit(f"{instr.result} = {instr.arg1}[{self._val(instr.arg2)}];")
elif op == 'ARRAY_SET':
self.emit(f"{instr.result}[{self._val(instr.arg1)}] = {self._val(instr.arg2)};")
def _val(self, v):
"""Format a value for C output — booleans become 1/0."""
if isinstance(v, bool):
return '1' if v else '0'
if v is None:
return '0'
return str(v)