Files
rpmroute/src/rpmroute/ses/indent.py
T
rsp2k a6aa1991a1 Rename to rpmroute
The router began as a port of FreeRouting and has grown its own engine
(negotiated congestion, tree nets, exact checks, axis escapes), and
'freeroute' was one letter from the project it no longer follows. The
package, module and command are now rpmroute; the freeroute command stays
as an alias, and the RPMROUTE / RPMROUTE_ORACLE_JAVA variables fall back
to the old FREEROUTE names. Version 2026.10.07. Routing is unchanged: a
PCBench board routes to a byte-identical session either way.
2026-10-07 14:10:36 -06:00

104 lines
3.2 KiB
Python

"""Indented S-expression output and identifier quoting for SES writing.
Ports two FreeRouting helpers used by the session writer:
* :class:`IndentWriter` mirrors ``datastructures/IndentFileWriter`` — a 2-space
indenting writer with ``start_scope`` / ``end_scope`` / ``new_line``.
* :class:`Identifier` mirrors ``datastructures/IdentifierType`` — quotes a name
when it contains a reserved character, a non-ASCII character, or starts with a
digit; strips surrounding quotes and any embedded quote character first.
The SES reserved-character set is the one ``SesWriter`` constructs:
``( ) <space> ; - _ / ~ { }``. Note that ``-`` and ``_`` are reserved, so net
names like ``NET_A`` and padstacks like ``Via[0-1]_600:300_um`` are quoted.
"""
from __future__ import annotations
from io import StringIO
import re
__all__ = ["IndentWriter", "Identifier", "SES_RESERVED_CHARS"]
_INDENT = " "
#: Reserved characters for SES identifiers (from SesWriter.write).
SES_RESERVED_CHARS: tuple[str, ...] = (
"(",
")",
" ",
";",
"-",
"_",
"/",
"~",
"{",
"}",
)
_LEADING_DIGIT = re.compile(r"-?\d")
class IndentWriter:
"""Accumulates indented S-expression text, mirroring ``IndentFileWriter``."""
def __init__(self) -> None:
self._buf = StringIO()
self._level = 0
def write(self, text: str) -> None:
self._buf.write(text)
def new_line(self) -> None:
"""Start a new line at the current indent level."""
self._buf.write("\n")
self._buf.write(_INDENT * self._level)
def start_scope(self, new_line: bool = True) -> None:
"""Open a ``(`` scope, optionally on a fresh line, and indent."""
if new_line:
self.new_line()
self._buf.write("(")
self._level += 1
def end_scope(self) -> None:
"""Close the innermost scope with ``)`` on its own aligned line."""
self._level -= 1
self.new_line()
self._buf.write(")")
def getvalue(self) -> str:
return self._buf.getvalue()
class Identifier:
"""Formats a name as a DSN/SES token, quoting it when required."""
def __init__(
self, string_quote: str = '"', reserved: tuple[str, ...] = SES_RESERVED_CHARS
) -> None:
self.quote = string_quote or '"'
self.reserved = tuple(reserved)
def format(self, name: str) -> str:
"""Return ``name`` quoted if it needs quoting, else unchanged."""
# Strip a surrounding matching pair of quote characters.
while len(name) >= 2 and name[0] == self.quote and name[-1] == self.quote:
name = name[1:-1]
# Remove any embedded quote character (it cannot be escaped).
if self.quote in name:
name = name.replace(self.quote, "")
need_quotes = any(rc in name for rc in self.reserved)
if not need_quotes and any(ord(ch) > 127 for ch in name):
need_quotes = True
if not need_quotes and _LEADING_DIGIT.match(name):
need_quotes = True
if need_quotes:
return f"{self.quote}{name}{self.quote}"
return name
def write(self, name: str, out: IndentWriter) -> None:
out.write(self.format(name))