Add PEP 657-style parse error handling. (#5881)
This commit is contained in:
@@ -255,6 +255,7 @@ name_blacklist = {
|
||||
"renpy.gl2.assimp.loader",
|
||||
"renpy.gl2.assimp.loader_lock",
|
||||
"renpy.gl2.gl2draw.default_position",
|
||||
"renpy.lexer._python_updatecache",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -509,50 +509,69 @@ init python:
|
||||
|
||||
return "\n".join(rv)
|
||||
|
||||
def __format_parse_errors(s):
|
||||
def __format_parse_errors(errors: list[str]):
|
||||
"""
|
||||
Takes list of ParseError messages and transforms it
|
||||
to string of sane length to be used as child of viewport.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
rv = ""
|
||||
rv = []
|
||||
rv_len = 0
|
||||
for error in errors:
|
||||
lines = error.split("\n")
|
||||
|
||||
lines = s.split("\n")
|
||||
lines = __limit_lines(lines)
|
||||
len_lines = len(lines)
|
||||
# Do not count first line in total to include those as much as
|
||||
# possible.
|
||||
rv.append(re.sub(
|
||||
r'(File "(.*)", line (\d+))',
|
||||
r'{a=edit:\3:\2}\1{/a}',
|
||||
lines.pop(0)))
|
||||
|
||||
ln = 0
|
||||
error_rv = []
|
||||
error_len = 0
|
||||
for line in lines:
|
||||
# In case we have single insanely lengthy single error.
|
||||
error_len += len(line)
|
||||
if error_len > 65536:
|
||||
error_rv.clear()
|
||||
break
|
||||
|
||||
while ln < len_lines:
|
||||
line = lines[ln]
|
||||
ln += 1
|
||||
if set(line.strip()) == {"^"}:
|
||||
idx = line.find("^")
|
||||
count = line.count("^", idx)
|
||||
|
||||
if ln < len_lines and lines[ln].endswith("^"):
|
||||
highlight = len(lines[ln]) - 1
|
||||
ln += 1
|
||||
else:
|
||||
highlight = -1
|
||||
prev_line = error_rv[-1]
|
||||
|
||||
pos = 0
|
||||
line = prev_line[:idx]
|
||||
|
||||
for c in line:
|
||||
if pos == highlight:
|
||||
rv += u"{color=#c00}\u2192{/color}"
|
||||
highlight = -1
|
||||
if count == 1:
|
||||
if len(prev_line) < idx + count:
|
||||
mark = "\u2190"
|
||||
else:
|
||||
mark = "\u2192"
|
||||
count = 0
|
||||
else:
|
||||
mark = prev_line[idx:idx + count]
|
||||
line += "{b}{color=#a00}"
|
||||
line += mark
|
||||
line += "{/color}{/b}"
|
||||
line += prev_line[idx + count:]
|
||||
|
||||
pos += 1
|
||||
error_rv[-1] = line
|
||||
continue
|
||||
|
||||
if c == "{":
|
||||
rv += "{{"
|
||||
else:
|
||||
rv += c
|
||||
error_rv.append(line)
|
||||
|
||||
if highlight > 0:
|
||||
rv += u"{color=#c00}\u2190{/color}"
|
||||
rv_len += error_len
|
||||
if rv_len > 1048576:
|
||||
break
|
||||
|
||||
rv += "\n"
|
||||
rv.extend(error_rv)
|
||||
rv.append("")
|
||||
|
||||
|
||||
rv = re.sub(r'(File "(.*)", line (\d+))', r'{a=edit:\3:\2}\1{/a}', rv)
|
||||
|
||||
return rv
|
||||
return "\n".join(rv)
|
||||
|
||||
class _EditFile(Action):
|
||||
def __init__(self, filename, line=1):
|
||||
@@ -787,7 +806,7 @@ screen _exception:
|
||||
key "console" action __EnterConsole()
|
||||
|
||||
# The screen that is used for error handling.
|
||||
screen _parse_errors:
|
||||
screen _parse_errors(errors, error_fn, reload_action):
|
||||
modal True
|
||||
layer config.interface_layer
|
||||
zorder 1090
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
# This file contains code to handle GUI-based error reporting.
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
|
||||
import os
|
||||
@@ -143,7 +143,7 @@ def report_exception(short, full, traceback_fn):
|
||||
reload_action=reload_action,
|
||||
ignore_action=ignore_action,
|
||||
traceback_fn=traceback_fn,
|
||||
)
|
||||
)
|
||||
|
||||
renpy.display.im.ignored_images |= renpy.display.im.images_to_ignore
|
||||
|
||||
@@ -162,7 +162,7 @@ def report_exception(short, full, traceback_fn):
|
||||
raise
|
||||
|
||||
|
||||
def report_parse_errors(errors, error_fn):
|
||||
def report_parse_errors(errors: list[str], error_fn: str) -> bool:
|
||||
"""
|
||||
Reports an exception to the user. Returns True if the exception should
|
||||
be raised by the normal reporting mechanisms. Otherwise, should raise
|
||||
@@ -174,7 +174,7 @@ def report_parse_errors(errors, error_fn):
|
||||
|
||||
error_dump()
|
||||
|
||||
if renpy.game.args.command != "run": # @UndefinedVariable
|
||||
if renpy.game.args.command != "run":
|
||||
return True
|
||||
|
||||
if "RENPY_SIMPLE_EXCEPTIONS" in os.environ:
|
||||
@@ -199,7 +199,7 @@ def report_parse_errors(errors, error_fn):
|
||||
reload_action=reload_action,
|
||||
errors=errors,
|
||||
error_fn=error_fn,
|
||||
)
|
||||
)
|
||||
|
||||
except renpy.game.CONTROL_EXCEPTIONS:
|
||||
raise
|
||||
@@ -208,3 +208,5 @@ def report_parse_errors(errors, error_fn):
|
||||
renpy.display.log.write("While handling exception:")
|
||||
renpy.display.log.exception()
|
||||
raise
|
||||
|
||||
return False
|
||||
|
||||
+135
-86
@@ -20,121 +20,144 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals # type: ignore
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals # type: ignore
|
||||
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *
|
||||
|
||||
from typing import Any
|
||||
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import linecache
|
||||
import contextlib
|
||||
|
||||
import renpy
|
||||
|
||||
from renpy.lexersupport import match_logical_word
|
||||
|
||||
# The filename that's in the line text cache.
|
||||
line_text_filename = ""
|
||||
|
||||
# The content of the line text cache.
|
||||
line_text_cache = [ ]
|
||||
_python_updatecache = linecache.updatecache
|
||||
|
||||
|
||||
def get_line_text(filename, lineno):
|
||||
def _updatecache(filename: str, module_globals: dict[str, Any] | None = None) -> list[str]:
|
||||
"""
|
||||
Gets the text of a line, in a best-effort way, for debugging purposes. May
|
||||
return just a newline, if the line doesn't exist.
|
||||
Monkeypatch internal linecache.updatecache to work around RenPy python code
|
||||
being in different files even in the same namespace. Assume filename is one
|
||||
of '_ren.py', '.rpy' or '.rpym'.
|
||||
|
||||
This is needed because a lot of Python modules (namely traceback) assume
|
||||
that the compiled python code is always comes from the python file and uses
|
||||
linecache to get the source code, which tries to use PEP302 loader, which
|
||||
for RenPy python code points to renpy.minstore.
|
||||
"""
|
||||
|
||||
global line_text_filename
|
||||
global line_text_cache
|
||||
|
||||
import linecache
|
||||
full_filename = renpy.exports.unelide_filename(filename)
|
||||
|
||||
|
||||
if full_filename != line_text_filename:
|
||||
|
||||
line_text_filename = full_filename
|
||||
if filename.endswith(("_ren.py", ".rpy", ".rpym")):
|
||||
# Assume filename also relative to basedir or renpy_base.
|
||||
full_fn = renpy.lexer.unelide_filename(filename)
|
||||
# If we can't find absolute path that way, assume we are
|
||||
# in build, so we can't show source.
|
||||
if not (os.path.isabs(full_fn) and os.path.exists(full_fn)):
|
||||
linecache.cache.pop(filename, None)
|
||||
return []
|
||||
|
||||
try:
|
||||
|
||||
with open(full_filename, "rb") as f:
|
||||
with open(full_fn, "rb") as f:
|
||||
data = f.read().decode("utf-8", "python_strict")
|
||||
|
||||
if full_filename.endswith("_ren.py"):
|
||||
if full_fn.endswith("_ren.py"):
|
||||
data = ren_py_to_rpy(data, None)
|
||||
|
||||
data += "\n\n"
|
||||
|
||||
line_text_cache = data.split("\n")
|
||||
lines = data.split("\n")
|
||||
if lines and lines[0].startswith("\ufeff"):
|
||||
lines[0] = lines[0][1:]
|
||||
|
||||
lines = [line.removesuffix("\r") + "\n" for line in lines]
|
||||
|
||||
stat = os.stat(full_fn)
|
||||
linecache.cache[filename] = stat.st_size, stat.st_mtime, lines, full_fn
|
||||
return lines
|
||||
|
||||
except Exception:
|
||||
line_text_cache = [ ]
|
||||
return []
|
||||
|
||||
if lineno <= len(line_text_cache):
|
||||
return line_text_cache[lineno - 1] + "\n"
|
||||
else:
|
||||
return "\n"
|
||||
return _python_updatecache(filename, module_globals)
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
linecache.updatecache = _updatecache
|
||||
|
||||
def __init__(self, filename, number, msg, line=None, pos=None, first=False):
|
||||
message = u"File \"%s\", line %d: %s" % (unicode_filename(filename), number, msg)
|
||||
|
||||
if line:
|
||||
if isinstance(line, list):
|
||||
line = "".join(line)
|
||||
class ParseError(SyntaxError):
|
||||
"""
|
||||
Special exception type for syntax errors in Ren'Py.
|
||||
This exception includes syntax errors of Python code, converted to
|
||||
appropriate report style, and Ren'Py own syntax errors in user script.
|
||||
"""
|
||||
|
||||
lines = line.split('\n')
|
||||
_message: str | None = None
|
||||
|
||||
if '"""' in lines[0]:
|
||||
pass
|
||||
elif "'''" in lines[0]:
|
||||
pass
|
||||
elif len(lines) > 1:
|
||||
open_string = None
|
||||
i = 0
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
filename: str,
|
||||
lineno: int,
|
||||
offset: int | None = None,
|
||||
text: str | None = None,
|
||||
end_lineno: int | None = None,
|
||||
end_offset: int | None = None,
|
||||
):
|
||||
super().__init__(message, (
|
||||
unicode_filename(filename),
|
||||
lineno, offset,
|
||||
text,
|
||||
end_lineno, end_offset))
|
||||
|
||||
while i < len(lines[0]):
|
||||
c = lines[0][i]
|
||||
@property
|
||||
def message(self) -> str:
|
||||
"""
|
||||
Fully formatted message of the error close to the result of
|
||||
`traceback.print_exception_only`.
|
||||
"""
|
||||
if self._message is None:
|
||||
message = f'File "{self.filename}", line {self.lineno}: {self.msg}'
|
||||
if self.text is not None:
|
||||
# Neither Python nor this class does not support multiline syntax error code.
|
||||
# Just strip the first line of provided code.
|
||||
text = self.text.split("\n")[0]
|
||||
|
||||
if c == "\\":
|
||||
i += 1
|
||||
elif c == open_string:
|
||||
open_string = None
|
||||
elif open_string:
|
||||
pass
|
||||
elif c == '`' or c == '\'' or c == '"':
|
||||
open_string = c
|
||||
# Remove ending escape chars, so we can render it.
|
||||
text = text.rstrip()
|
||||
|
||||
i += 1
|
||||
# And also replace any escape chars at the start with an indent.
|
||||
message += f'\n {text.lstrip()}'
|
||||
|
||||
if open_string:
|
||||
message += "\n(Perhaps you left out a %s at the end of the first line.)" % open_string
|
||||
if self.offset is not None:
|
||||
offset = self.offset
|
||||
|
||||
for l in lines:
|
||||
message += "\n " + l
|
||||
|
||||
if pos is not None:
|
||||
if pos <= len(l):
|
||||
message += "\n " + " " * pos + "^"
|
||||
pos = None
|
||||
# Fallback to single caret for cases end_offset is before offset.
|
||||
if self.end_offset is None or self.end_offset <= offset:
|
||||
end_offset = offset + 1
|
||||
else:
|
||||
pos -= len(l)
|
||||
end_offset = self.end_offset
|
||||
|
||||
if first:
|
||||
break
|
||||
left_spaces = len(text) - len(text.lstrip())
|
||||
offset -= left_spaces
|
||||
end_offset -= left_spaces
|
||||
|
||||
self.message = message
|
||||
if offset >= 1:
|
||||
caret_space = ' ' * (offset - 1)
|
||||
carets = '^' * (end_offset - offset)
|
||||
message += f"\n {caret_space}{carets}"
|
||||
|
||||
Exception.__init__(self, message)
|
||||
for note in getattr(self, "__notes__", ()):
|
||||
message += f"\n{note}"
|
||||
|
||||
def __unicode__(self):
|
||||
return self.message
|
||||
self._message = message
|
||||
|
||||
return self._message
|
||||
|
||||
def defer(self, queue):
|
||||
renpy.parser.deferred_parse_errors[queue].append(self.message)
|
||||
@@ -360,7 +383,8 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
|
||||
c = data[pos]
|
||||
|
||||
if c == u'\t':
|
||||
raise ParseError(filename, number, "Tab characters are not allowed in Ren'Py scripts.")
|
||||
raise ParseError("Tab characters are not allowed in Ren'Py scripts.",
|
||||
filename, number)
|
||||
|
||||
if c == u'\n' and not parendepth:
|
||||
|
||||
@@ -500,10 +524,19 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
|
||||
pos = end
|
||||
|
||||
if (pos - startpos) > 65536:
|
||||
raise ParseError(filename, start_number, "Overly long logical line. (Check strings and parenthesis.)", line=line, first=True)
|
||||
err = ParseError(
|
||||
"Overly long logical line.",
|
||||
filename, start_number,
|
||||
text="".join(line))
|
||||
err.add_note("Check strings and parenthesis.")
|
||||
raise err
|
||||
|
||||
if line:
|
||||
raise ParseError(filename, start_number, "is not terminated with a newline. (Check strings and parenthesis.)", line=line, first=True)
|
||||
err = ParseError("is not terminated with a newline.",
|
||||
filename, start_number,
|
||||
text="".join(line))
|
||||
err.add_note("Check strings and parenthesis.")
|
||||
raise err
|
||||
|
||||
return rv
|
||||
|
||||
@@ -527,6 +560,8 @@ def depth_split(l):
|
||||
return depth, l[index:]
|
||||
|
||||
# i, min_depth -> block, new_i
|
||||
|
||||
|
||||
def gll_core(lines, i, min_depth):
|
||||
"""
|
||||
Recursively groups lines into blocks.
|
||||
@@ -551,7 +586,9 @@ def gll_core(lines, i, min_depth):
|
||||
depth = line_depth
|
||||
|
||||
if depth != line_depth:
|
||||
raise ParseError(filename, number, "Indentation mismatch.")
|
||||
raise ParseError(
|
||||
"Indentation mismatch.",
|
||||
filename, number, text=text)
|
||||
|
||||
# Advance to the next line.
|
||||
i += 1
|
||||
@@ -563,6 +600,7 @@ def gll_core(lines, i, min_depth):
|
||||
|
||||
return rv, i
|
||||
|
||||
|
||||
def group_logical_lines(lines):
|
||||
"""
|
||||
This takes as input the list of logical line triples output from
|
||||
@@ -577,7 +615,9 @@ def group_logical_lines(lines):
|
||||
filename, number, text = lines[0]
|
||||
|
||||
if depth_split(text)[0] != 0:
|
||||
raise ParseError(filename, number, "Unexpected indentation at start of file.")
|
||||
raise ParseError(
|
||||
"Unexpected indentation at start of file.",
|
||||
filename, number, text=text)
|
||||
|
||||
return gll_core(lines, 0, 0)[0]
|
||||
|
||||
@@ -826,7 +866,12 @@ class Lexer(object):
|
||||
if (self.line == -1) and self.block:
|
||||
self.filename, self.number, self.text, self.subblock = self.block[0]
|
||||
|
||||
raise ParseError(self.filename, self.number, msg, self.text, self.pos)
|
||||
raise ParseError(
|
||||
msg,
|
||||
self.filename,
|
||||
self.number,
|
||||
self.pos,
|
||||
self.text)
|
||||
|
||||
def deferred_error(self, queue, msg):
|
||||
"""
|
||||
@@ -840,7 +885,10 @@ class Lexer(object):
|
||||
if (self.line == -1) and self.block:
|
||||
self.filename, self.number, self.text, self.subblock = self.block[0]
|
||||
|
||||
ParseError(self.filename, self.number, msg, self.text, self.pos).defer(queue)
|
||||
ParseError(
|
||||
msg, self.filename,
|
||||
self.number, self.pos + 1,
|
||||
self.text).defer(queue)
|
||||
|
||||
def eol(self):
|
||||
"""
|
||||
@@ -895,7 +943,8 @@ class Lexer(object):
|
||||
|
||||
init = self.init or init
|
||||
|
||||
return Lexer(self.subblock, init=init, init_offset=self.init_offset, global_label=self.global_label, monologue_delimiter=self.monologue_delimiter, subparses=self.subparses)
|
||||
return Lexer(self.subblock, init=init, init_offset=self.init_offset, global_label=self.global_label,
|
||||
monologue_delimiter=self.monologue_delimiter, subparses=self.subparses)
|
||||
|
||||
def string(self):
|
||||
"""
|
||||
@@ -951,7 +1000,7 @@ class Lexer(object):
|
||||
# Collapse runs of whitespace into single spaces.
|
||||
s = re.sub(r'[ \n]+', ' ', s)
|
||||
|
||||
s = re.sub(r'\\(u([0-9a-fA-F]{1,4})|.)', dequote, s) # type: ignore
|
||||
s = re.sub(r'\\(u([0-9a-fA-F]{1,4})|.)', dequote, s) # type: ignore
|
||||
|
||||
return s
|
||||
|
||||
@@ -1030,7 +1079,7 @@ class Lexer(object):
|
||||
else:
|
||||
s = re.sub(r' +', ' ', s)
|
||||
|
||||
s = re.sub(r'\\(u([0-9a-fA-F]{1,4})|.)', dequote, s) # type: ignore
|
||||
s = re.sub(r'\\(u([0-9a-fA-F]{1,4})|.)', dequote, s) # type: ignore
|
||||
|
||||
rv.append(s)
|
||||
|
||||
@@ -1046,7 +1095,7 @@ class Lexer(object):
|
||||
|
||||
return self.match(r'(\+|\-)?\d+')
|
||||
|
||||
def float(self): # @ReservedAssignment
|
||||
def float(self):
|
||||
"""
|
||||
Tries to parse a number (float). Returns a string containing the
|
||||
number, or None.
|
||||
@@ -1168,7 +1217,7 @@ class Lexer(object):
|
||||
self.pos = oldpos
|
||||
return None
|
||||
|
||||
if (rv in KEYWORDS ) or (rv in IMAGE_KEYWORDS):
|
||||
if (rv in KEYWORDS) or (rv in IMAGE_KEYWORDS):
|
||||
self.pos = oldpos
|
||||
return None
|
||||
|
||||
@@ -1187,7 +1236,6 @@ class Lexer(object):
|
||||
|
||||
old_pos = self.pos
|
||||
|
||||
|
||||
# Delimiter.
|
||||
start = self.match(r'[urfURF]*("""|\'\'\'|"|\')')
|
||||
|
||||
@@ -1283,7 +1331,7 @@ class Lexer(object):
|
||||
if not pe:
|
||||
self.error("expected python_expression")
|
||||
|
||||
rv = self.expr(pe.strip(), expr) # E1101
|
||||
rv = self.expr(pe.strip(), expr)
|
||||
|
||||
return rv
|
||||
|
||||
@@ -1585,7 +1633,7 @@ class Lexer(object):
|
||||
return sp
|
||||
|
||||
|
||||
def ren_py_to_rpy(text, filename):
|
||||
def ren_py_to_rpy(text: str, filename: str | None) -> str:
|
||||
"""
|
||||
Transforms an _ren.py file into the equivalent .rpy file. This should retain line numbers.
|
||||
|
||||
@@ -1668,8 +1716,9 @@ def ren_py_to_rpy(text, filename):
|
||||
raise Exception('In {!r}, there are no """renpy blocks, so every line is ignored.'.format(filename))
|
||||
|
||||
if state == RENPY:
|
||||
raise Exception('In {!r}, there is a """renpy block at line {} that is not terminated by """.'.format(filename,
|
||||
open_linenumber))
|
||||
raise Exception(
|
||||
'In {!r}, there is a """renpy block at line {} that is not terminated by """.'.format(
|
||||
filename, open_linenumber))
|
||||
|
||||
rv = "\n".join(result)
|
||||
|
||||
|
||||
+6
-3
@@ -43,7 +43,6 @@ from renpy.lexer import (
|
||||
munge_filename,
|
||||
elide_filename,
|
||||
unelide_filename,
|
||||
get_line_text,
|
||||
SubParse,
|
||||
)
|
||||
|
||||
@@ -1773,8 +1772,10 @@ def report_parse_errors():
|
||||
full_text = ""
|
||||
|
||||
f, error_fn = renpy.error.open_error_file("errors.txt", "w")
|
||||
|
||||
screen_parse_errors = []
|
||||
with f:
|
||||
f.write("\ufeff") # BOM
|
||||
f.write("\ufeff") # BOM
|
||||
|
||||
print("I'm sorry, but errors were detected in your script. Please correct the", file=f)
|
||||
print("errors listed below, and try again.", file=f)
|
||||
@@ -1791,6 +1792,8 @@ def report_parse_errors():
|
||||
print("", file=f)
|
||||
print(i, file=f)
|
||||
|
||||
screen_parse_errors.append(i)
|
||||
|
||||
try:
|
||||
print("")
|
||||
print(i)
|
||||
@@ -1801,7 +1804,7 @@ def report_parse_errors():
|
||||
print("Ren'Py Version:", renpy.version, file=f)
|
||||
print(str(time.ctime()), file=f)
|
||||
|
||||
renpy.display.error.report_parse_errors(full_text, error_fn)
|
||||
renpy.display.error.report_parse_errors(screen_parse_errors, error_fn)
|
||||
|
||||
try:
|
||||
if renpy.game.args.command == "run" or renpy.game.args.errors_in_editor: # type: ignore
|
||||
|
||||
@@ -1114,7 +1114,6 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
with save_warnings():
|
||||
tree = compile(source, filename, py_mode, ast.PyCF_ONLY_AST | flags, 1)
|
||||
except SyntaxError as orig_e:
|
||||
|
||||
try:
|
||||
fixed_source = renpy.compat.fixes.fix_tokens(source)
|
||||
with save_warnings():
|
||||
|
||||
+10
-13
@@ -620,12 +620,13 @@ class Script(object):
|
||||
if renpy.config.allow_duplicate_labels:
|
||||
return
|
||||
|
||||
import linecache
|
||||
self.duplicate_labels.append(
|
||||
u'The label {} is defined twice, at File "{}", line {}:\n{}and File "{}", line {}:\n{}'.format(
|
||||
bad_name, old_node.filename, old_node.linenumber,
|
||||
renpy.lexer.get_line_text(old_node.filename, old_node.linenumber),
|
||||
linecache.getline(old_node.filename, old_node.linenumber),
|
||||
bad_node.filename, bad_node.linenumber,
|
||||
renpy.lexer.get_line_text(bad_node.filename, bad_node.linenumber),
|
||||
linecache.getline(bad_node.filename, bad_node.linenumber),
|
||||
))
|
||||
|
||||
self.update_bytecode()
|
||||
@@ -1057,18 +1058,14 @@ class Script(object):
|
||||
i.bytecode = renpy.python.py_compile(i.source, i.mode, filename=i.filename, lineno=i.linenumber, py=i.py, hashcode=i.hashcode)
|
||||
|
||||
except SyntaxError as e:
|
||||
|
||||
text = e.text
|
||||
|
||||
if text is None:
|
||||
text = ''
|
||||
|
||||
assert e.filename is not None
|
||||
assert e.lineno is not None
|
||||
pem = renpy.parser.ParseError(
|
||||
filename=e.filename,
|
||||
number=e.lineno,
|
||||
msg=e.msg,
|
||||
line=text,
|
||||
pos=e.offset)
|
||||
e.msg,
|
||||
e.filename,
|
||||
e.lineno, e.offset,
|
||||
e.text,
|
||||
e.end_lineno, e.end_offset)
|
||||
|
||||
renpy.parser.parse_errors.append(pem.message)
|
||||
|
||||
|
||||
@@ -170,9 +170,9 @@ class ScriptTranslator(object):
|
||||
old_node = self.default_translates[n.identifier]
|
||||
|
||||
err = renpy.lexer.ParseError(
|
||||
n.filename, n.linenumber,
|
||||
f"Line with id {n.identifier} appears twice. "
|
||||
f"The other line is {old_node.filename}:{old_node.linenumber}")
|
||||
f"The other line is {old_node.filename}:{old_node.linenumber}",
|
||||
n.filename, n.linenumber,)
|
||||
err.defer("duplicate_id")
|
||||
|
||||
self.default_translates[n.identifier] = n
|
||||
|
||||
Reference in New Issue
Block a user