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
|
import contextlib
from lexer import Token
from typing import TextIO, Iterator
class CWriter:
"A writer that understands tokens and how to format C code"
last_token: Token | None
def __init__(self, out: TextIO, indent: int, line_directives: bool):
self.out = out
self.base_column = indent * 4
self.indents = [i * 4 for i in range(indent + 1)]
self.line_directives = line_directives
self.last_token = None
self.newline = True
def set_position(self, tkn: Token) -> None:
if self.last_token is not None:
if self.last_token.line < tkn.line:
self.out.write("\n")
if self.line_directives:
self.out.write(f'#line {tkn.line} "{tkn.filename}"\n')
self.out.write(" " * self.indents[-1])
else:
gap = tkn.column - self.last_token.end_column
self.out.write(" " * gap)
elif self.newline:
self.out.write(" " * self.indents[-1])
self.last_token = tkn
self.newline = False
def emit_at(self, txt: str, where: Token) -> None:
self.set_position(where)
self.out.write(txt)
def maybe_dedent(self, txt: str) -> None:
parens = txt.count("(") - txt.count(")")
if parens < 0:
self.indents.pop()
braces = txt.count("{") - txt.count("}")
if braces < 0 or is_label(txt):
self.indents.pop()
def maybe_indent(self, txt: str) -> None:
parens = txt.count("(") - txt.count(")")
if parens > 0:
if self.last_token:
offset = self.last_token.end_column - 1
if offset <= self.indents[-1] or offset > 40:
offset = self.indents[-1] + 4
else:
offset = self.indents[-1] + 4
self.indents.append(offset)
if is_label(txt):
self.indents.append(self.indents[-1] + 4)
else:
braces = txt.count("{") - txt.count("}")
if braces > 0:
assert braces == 1
if 'extern "C"' in txt:
self.indents.append(self.indents[-1])
else:
self.indents.append(self.indents[-1] + 4)
def emit_text(self, txt: str) -> None:
self.out.write(txt)
def emit_multiline_comment(self, tkn: Token) -> None:
self.set_position(tkn)
lines = tkn.text.splitlines(True)
first = True
for line in lines:
text = line.lstrip()
if first:
spaces = 0
else:
spaces = self.indents[-1]
if text.startswith("*"):
spaces += 1
else:
spaces += 3
first = False
self.out.write(" " * spaces)
self.out.write(text)
def emit_token(self, tkn: Token) -> None:
if tkn.kind == "COMMENT" and "\n" in tkn.text:
return self.emit_multiline_comment(tkn)
self.maybe_dedent(tkn.text)
self.set_position(tkn)
self.emit_text(tkn.text)
self.maybe_indent(tkn.text)
def emit_str(self, txt: str) -> None:
self.maybe_dedent(txt)
if self.newline and txt:
if txt[0] != "\n":
self.out.write(" " * self.indents[-1])
self.newline = False
self.emit_text(txt)
if txt.endswith("\n"):
self.newline = True
self.maybe_indent(txt)
self.last_token = None
def emit(self, txt: str | Token) -> None:
if isinstance(txt, Token):
self.emit_token(txt)
elif isinstance(txt, str):
self.emit_str(txt)
else:
assert False
def start_line(self) -> None:
if not self.newline:
self.out.write("\n")
self.newline = True
self.last_token = None
@contextlib.contextmanager
def header_guard(self, name: str) -> Iterator[None]:
self.out.write(
f"""
#ifndef {name}
#define {name}
#ifdef __cplusplus
extern "C" {{
#endif
"""
)
yield
self.out.write(
f"""
#ifdef __cplusplus
}}
#endif
#endif /* !{name} */
"""
)
def is_label(txt: str) -> bool:
return not txt.startswith("//") and txt.endswith(":")
|