diff --git a/renpy/ast.py b/renpy/ast.py index 996594bc1..a90fae580 100644 --- a/renpy/ast.py +++ b/renpy/ast.py @@ -89,18 +89,22 @@ class PyCode(Object): hashcode : int def __getstate__(self): - return (1, self.source, (self.filename, self.linenumber), self.mode, self.py, self.hashcode) + return (1, self.source, (self.filename, self.linenumber), self.mode, self.py, self.hashcode, self.col_offset) def __setstate__(self, state): + col_offset = 0 + py = 2 + hashcode = None match state: - case (_, source, location, mode): - py = 2 - hashcode = None - case (_, source, location, mode, py): - hashcode = None + case (_, source, location, mode, py, hashcode, col_offset): + pass case (_, source, location, mode, py, hashcode): pass + case (_, source, location, mode, py): + pass + case (_, source, location, mode): + pass case _: raise Exception("Invalid state:", state) @@ -108,6 +112,7 @@ class PyCode(Object): self.source = source self.filename = location[0] self.linenumber = location[1] + self.col_offset = col_offset self.mode = mode if hashcode is None: @@ -121,14 +126,14 @@ class PyCode(Object): if renpy.game.script.record_pycode: renpy.game.script.all_pycode.append(self) - def __init__(self, source: str, loc: tuple[str, int] = ('', 1), - mode: Literal["eval", "exec", "hide"] = 'exec'): + def __init__( + self, + source: str, + loc: tuple[str, int] = ('', 1), + mode: Literal["eval", "exec", "hide"] = 'exec'): self.py = 3 - # The source code. - self.source = source - if isinstance(source, PyExpr): self.filename = source.filename self.linenumber = source.linenumber @@ -138,6 +143,13 @@ class PyCode(Object): self.linenumber = loc[1] self.hashcode = hash32(source) + # The source code. + if self.mode != "eval": + self.source, self.col_offset = PyCode.dedent(source) + else: + self.source = source + self.col_offset = 0 + self.mode = mode # This will be initialized later on, after we are serialized. @@ -146,6 +158,53 @@ class PyCode(Object): if renpy.game.script.record_pycode: renpy.game.script.all_pycode.append(self) + + _leading_whitespace_re = re.compile('(^[ ]*)(?:[^ ])', re.MULTILINE) + + @staticmethod + def dedent(text: str): + """ + Removes leading whitespace from a block of text. Entirely blank lines + are normalized to a newline character. + + This returns the dedented text, and the amount of whitespace removed, + """ + + # Look for the longest leading string of spaces and tabs common to + # all lines. + margin = None + indents = PyCode._leading_whitespace_re.findall(text) + for indent in indents: + if margin is None: + margin = indent + + # Current line more deeply indented than previous winner: + # no change (previous winner is still on top). + elif indent.startswith(margin): + pass + + # Current line consistent with and no deeper than previous winner: + # it's the new winner. + elif margin.startswith(indent): + margin = indent + + # Find the largest common whitespace between current line and previous + # winner. + else: + for i, (x, y) in enumerate(zip(margin, indent)): + if x != y: + margin = margin[:i] + break + + if margin: + text = re.sub(r'(?m)^' + margin, '', text) + + if margin: + return text, len(margin) + else: + return text, 0 + + DoesNotExtend = renpy.object.Sentinel("DoesNotExtend") diff --git a/renpy/python.py b/renpy/python.py index 0710305e3..d7a494c3e 100644 --- a/renpy/python.py +++ b/renpy/python.py @@ -1047,7 +1047,6 @@ def py_compile(source, mode, filename='', lineno=1, ast_node=False, cache= `column` A column offset to add to the column numbers of the source. """ - global compile_warnings first_line_column_delta = column @@ -1078,7 +1077,7 @@ def py_compile(source, mode, filename='', lineno=1, ast_node=False, cache= # This determines if the lines are indented. If so, we adjust the # ast to match. - indented = source and (source[0] == " ") + indented = source and (source[0] == " ") and (mode != "eval") if indented: lineno -= 1 diff --git a/renpy/script.py b/renpy/script.py index 0048ff639..f314d9796 100644 --- a/renpy/script.py +++ b/renpy/script.py @@ -1055,7 +1055,7 @@ class Script(object): renpy.game.exception_info = "While compiling python block starting at line %d of %s." % (i.linenumber, i.filename) - i.bytecode = renpy.python.py_compile(i.source, i.mode, filename=i.filename, lineno=i.linenumber, py=i.py, hashcode=i.hashcode) + i.bytecode = renpy.python.py_compile(i.source, i.mode, filename=i.filename, lineno=i.linenumber, py=i.py, hashcode=i.hashcode, column=i.col_offset) except SyntaxError as e: assert e.filename is not None