mirror of https://github.com/python/cpython
150 lines
4.4 KiB
Python
150 lines
4.4 KiB
Python
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.end_line < tkn.line:
|
|
self.out.write("\n")
|
|
if self.last_token.line < tkn.line:
|
|
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)
|
|
if tkn.kind == "CMACRO":
|
|
self.newline = True
|
|
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(":")
|