Fix incorrect column offsets of munged names.

This moves munging to AST, so column offsets match source code.
This commit is contained in:
Andy_kl
2025-02-14 03:36:22 +04:00
parent 65edc55eb0
commit 78ff61dd08
2 changed files with 132 additions and 64 deletions
+48 -39
View File
@@ -20,12 +20,13 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from typing import Any from typing import Callable
import re import re
import sys import sys
import os import os
import contextlib import contextlib
import functools
import renpy import renpy
@@ -211,6 +212,38 @@ def unelide_filename(fn):
return fn return fn
def get_string_munger(prefix: str) -> Callable[[str], str]:
if renpy.config.munge_in_strings:
def munge_string(m: re.Match[str]):
g1 = m.group(1)
if "__" in g1:
return m.group(0)
if g1.startswith("_"):
return m.group(0)
return prefix + m.group(1)
return functools.partial(re.sub, r'\b__(\w+)', munge_string)
else:
def munge_string(m: re.Match[str]):
m.groups()
brackets = m.group(1)
if len(brackets) % 2 == 0:
return m.group(0)
if "__" in m.group(2):
return m.group(0)
return brackets + prefix + m.group(2)
return functools.partial(re.sub, r'(\.|\[+)__(\w+)', munge_string)
# The filename that the start and end positions are relative to. # The filename that the start and end positions are relative to.
original_filename = "" original_filename = ""
@@ -225,37 +258,6 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
contents. In that case, `filename` need not exist. contents. In that case, `filename` need not exist.
""" """
if renpy.config.munge_in_strings:
munge_regexp = re.compile(r'\b__(\w+)')
def munge_string(m):
g1 = m.group(1)
if "__" in g1:
return m.group(0)
if g1.startswith("_"):
return m.group(0)
return prefix + m.group(1)
else:
munge_regexp = re.compile(r'(\.|\[+)__(\w+)')
def munge_string(m):
brackets = m.group(1)
if (len(brackets) & 1) == 0:
return m.group(0)
if "__" in m.group(2):
return m.group(0)
return brackets + prefix + m.group(2)
global original_filename global original_filename
original_filename = filename original_filename = filename
@@ -272,6 +274,8 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
filename = elide_filename(filename) filename = elide_filename(filename)
prefix = munge_filename(filename) prefix = munge_filename(filename)
munge_string = get_string_munger(prefix)
# Add some newlines, to fix lousy editors. # Add some newlines, to fix lousy editors.
data += "\n\n" data += "\n\n"
@@ -447,7 +451,7 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
s = "".join(s) s = "".join(s)
if "__" in s: if "__" in s:
s = munge_regexp.sub(munge_string, s) s = munge_string(s)
line.append(s) line.append(s)
@@ -455,11 +459,10 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
word, magic, end = match_logical_word(data, pos) word, magic, end = match_logical_word(data, pos)
if magic: if magic and word[2] != "_":
rest = word[2:] rest = word[2:]
if (u"__" not in rest) and not rest.startswith("_"): if "__" not in rest:
word = prefix + rest word = prefix + rest
line.append(word) line.append(word)
@@ -679,6 +682,10 @@ class Lexer(object):
self.subparses = subparses self.subparses = subparses
def _unmunge_string(self, s: str) -> str:
prefix = munge_filename(self.filename)
return s.replace(prefix, "__")
def advance(self): def advance(self):
""" """
Advances this lexer to the next line in the block. The lexer Advances this lexer to the next line in the block. The lexer
@@ -1232,6 +1239,8 @@ class Lexer(object):
if not expr: if not expr:
return s return s
s = self._unmunge_string(s)
return renpy.ast.PyExpr(s, self.filename, self.number) return renpy.ast.PyExpr(s, self.filename, self.number)
def delimited_python(self, delim, expr=True): def delimited_python(self, delim, expr=True):
@@ -1387,7 +1396,7 @@ class Lexer(object):
if not text: if not text:
return None return None
return renpy.ast.PyExpr(text, self.filename, self.number) return self.expr(text, True)
def comma_expression(self): def comma_expression(self):
""" """
@@ -1470,7 +1479,7 @@ class Lexer(object):
pos = self.pos pos = self.pos
self.pos = len(self.text) self.pos = len(self.text)
return renpy.ast.PyExpr(self.text[pos:].strip(), self.filename, self.number) return self.expr(self.text[pos:].strip(), True)
def rest_statement(self): def rest_statement(self):
""" """
@@ -1508,7 +1517,7 @@ class Lexer(object):
line_holder.line = self.number line_holder.line = self.number
self._process_python_block(self.subblock, '', rv, line_holder) self._process_python_block(self.subblock, '', rv, line_holder)
return ''.join(rv) return self._unmunge_string(''.join(rv))
def arguments(self): def arguments(self):
""" """
+84 -25
View File
@@ -424,31 +424,97 @@ class StarredVariables(ast.NodeVisitor):
# starred assignment. # starred assignment.
find_starred_variables = StarredVariables().find find_starred_variables = StarredVariables().find
class WrapFormattedValue(ast.NodeTransformer): class MungeNodes(ast.NodeTransformer):
""" """
This walks through the children of a FormattedValue, to look for This walks through the tree and munges all identifiers and strings,
nodes with the __name syntax, and format those nodes. so __names in different files don't collide, but column offsets
of the original code stays correct.
""" """
def visit_Name(self, node): def __init__(self, filename: str):
self.prefix = renpy.lexer.munge_filename(filename)
self.string_munger = renpy.lexer.get_string_munger(self.prefix)
name = node.id def _munge_identifier(self, id):
if id is None or len(id) < 3 or id[:2] != "__" or id[2] == "_":
return id
if not name.startswith("__"): rest = id[2:]
if "__" in rest:
return id
return self.prefix + rest
@staticmethod
def _munge_attribute(attr: str):
def visit(self: "MungeNodes", node: ast.AST):
value = getattr(node, attr)
value = self._munge_identifier(value)
setattr(node, attr, value)
self.generic_visit(node)
return node return node
name = name[2:] return visit
if (not name) or ("__" in name): @staticmethod
def _munge_attribute_list(attr: str):
def visit(self: "MungeNodes", node: ast.AST):
value = getattr(node, attr)
value = [self._munge_identifier(i) for i in value]
setattr(node, attr, value)
self.generic_visit(node)
return node return node
prefix = renpy.lexer.munge_filename(compile_filename) return visit
name = prefix + name def visit_Constant(self, node):
if isinstance(node.value, str) and "__" in node.value:
node.value = self.string_munger(node.value)
return ast.Name(id=name, ctx=node.ctx, lineno=node.lineno, col_offset=node.col_offset, end_lineno=node.end_lineno, end_col_offset=node.end_col_offset) return node
wrap_formatted_value = WrapFormattedValue().visit # stmt
visit_FunctionDef = _munge_attribute("name")
visit_AsyncFunctionDef = _munge_attribute("name")
visit_ClassDef = _munge_attribute("name")
def visit_ImportFrom(self, node):
if node.module is not None:
node.module = ".".join(
self._munge_identifier(part)
for part in node.module.split("."))
self.generic_visit(node)
return node
visit_Global = _munge_attribute_list("names")
visit_Nonlocal = _munge_attribute_list("names")
# expr
visit_Attribute = _munge_attribute("attr")
visit_Name = _munge_attribute("id")
# Other
visit_ExceptHandler = _munge_attribute("name")
visit_arg = _munge_attribute("arg")
visit_keyword = _munge_attribute("arg")
def visit_alias(self, node):
node.name = self._munge_identifier(node.name)
node.asname = self._munge_identifier(node.asname)
self.generic_visit(node)
return node
# pattern
visit_MatchMapping = _munge_attribute("rest")
visit_MatchClass = _munge_attribute_list("kwd_attrs")
visit_MatchStar = _munge_attribute("name")
visit_MatchAs = _munge_attribute("name")
# type_param
visit_TypeVar = _munge_attribute("name")
visit_ParamSpec = _munge_attribute("name")
visit_TypeVarTuple = _munge_attribute("name")
class FindStarredMatchPatterns(ast.NodeVisitor): class FindStarredMatchPatterns(ast.NodeVisitor):
@@ -767,10 +833,6 @@ class WrapNode(ast.NodeTransformer):
args=[self.generic_visit(node)], # type: ignore args=[self.generic_visit(node)], # type: ignore
keywords=[]) keywords=[])
def visit_FormattedValue(self, node):
node = wrap_formatted_value(node)
return self.generic_visit(node)
def visit_Match(self, node: ast.Match): def visit_Match(self, node: ast.Match):
node = self.generic_visit(node) # type: ignore node = self.generic_visit(node) # type: ignore
node.cases = [self.wrap_match_case(i) for i in node.cases] node.cases = [self.wrap_match_case(i) for i in node.cases]
@@ -1001,9 +1063,6 @@ def quote_eval(s):
return "".join(rv[:-2]) return "".join(rv[:-2])
# The filename being compiled.
compile_filename = ""
def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=True, py=None, hashcode=None): def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=True, py=None, hashcode=None):
""" """
@@ -1031,7 +1090,6 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
that would be used. that would be used.
""" """
global compile_filename
global compile_warnings global compile_warnings
if ast_node: if ast_node:
@@ -1099,7 +1157,6 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
source = quote_eval(source) source = quote_eval(source)
line_offset = lineno - 1 line_offset = lineno - 1
compile_filename = filename
try: try:
@@ -1112,17 +1169,19 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
try: try:
with save_warnings(): with save_warnings():
tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1) tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, True)
except SyntaxError as orig_e: except SyntaxError as orig_e:
try: try:
fixed_source = renpy.compat.fixes.fix_tokens(source) fixed_source = renpy.compat.fixes.fix_tokens(source)
with save_warnings(): with save_warnings():
tree = compile(fixed_source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1) tree = compile(fixed_source, filename, py_mode, ast.PyCF_ONLY_AST | flags, True)
except Exception: except Exception:
raise orig_e raise orig_e
tree = wrap_node.visit(tree) tree = wrap_node.visit(tree)
tree = MungeNodes(filename).visit(tree)
if mode == "hide": if mode == "hide":
wrap_hide(tree) wrap_hide(tree)
@@ -1136,13 +1195,13 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
try: try:
with save_warnings(): with save_warnings():
rv = compile(tree, filename, py_mode, flags, 1) rv = compile(tree, filename, py_mode, flags, True)
except SyntaxError as orig_e: except SyntaxError as orig_e:
try: try:
tree = renpy.compat.fixes.fix_ast(tree) tree = renpy.compat.fixes.fix_ast(tree)
fix_locations(tree, 1, 0) fix_locations(tree, 1, 0)
with save_warnings(): with save_warnings():
rv = compile(tree, filename, py_mode, flags, 1) rv = compile(tree, filename, py_mode, flags, True)
except Exception: except Exception:
raise orig_e raise orig_e