Track column numbers during parsing.
How these are handled varies for PyCode and PyExpr. PyCodes are, for now, parsed with all the indentation present in the file. This is then passed to the parser, which pads the PyCode out with an if True statement so it compiles. PyExprs have the column number determined by the parser, and it's stored in the object, to be passed to the compiler.
This commit is contained in:
+51
-3
@@ -73,11 +73,12 @@ cdef class PyExpr(str):
|
||||
|
||||
cdef public str filename
|
||||
cdef public unsigned int hashcode
|
||||
cdef public int linenumber
|
||||
cdef public unsigned int linenumber
|
||||
cdef public unsigned short column
|
||||
cdef public unsigned char py
|
||||
|
||||
def __reduce__(self):
|
||||
return (PyExpr, (str(self), self.filename, self.linenumber, self.py, self.hashcode))
|
||||
return (PyExpr, (str(self), self.filename, self.linenumber, self.py, self.hashcode, self.column))
|
||||
|
||||
@staticmethod
|
||||
def checkpoint() -> Any:
|
||||
@@ -116,15 +117,20 @@ cdef object PyExpr_new(type cls, PyObject *args, PyObject *kwargs):
|
||||
|
||||
cdef tuple cargs = <tuple> args
|
||||
|
||||
if len(cargs) == 5:
|
||||
if len(cargs) == 6:
|
||||
s, filename, linenumber, py, hashcode, column = cargs
|
||||
elif len(cargs) == 5:
|
||||
s, filename, linenumber, py, hashcode = cargs
|
||||
column = 0
|
||||
elif len(cargs) == 4:
|
||||
s, filename, linenumber, py = cargs
|
||||
hashcode = None
|
||||
column = 0
|
||||
elif len(cargs) == 3:
|
||||
s, filename, linenumber = cargs
|
||||
py = 2
|
||||
hashcode = None
|
||||
column = 0
|
||||
else:
|
||||
raise Exception("PyExpr.__new__ called with invalid arguments.", str(<object> args))
|
||||
|
||||
@@ -136,6 +142,7 @@ cdef object PyExpr_new(type cls, PyObject *args, PyObject *kwargs):
|
||||
|
||||
rv.filename = filename
|
||||
rv.linenumber = linenumber
|
||||
rv.column = column
|
||||
rv.py = py
|
||||
|
||||
if hashcode is not None:
|
||||
@@ -152,3 +159,44 @@ cdef object PyExpr_new(type cls, PyObject *args, PyObject *kwargs):
|
||||
return rv
|
||||
|
||||
PyExprType.tp_new = <newfunc> PyExpr_new
|
||||
|
||||
|
||||
def make_pyexpr(s, str filename, int linenumber, int column, str text, int pos):
|
||||
"""
|
||||
Used by lexer to make a pyexpr, rapidly adjusting line number and column.
|
||||
|
||||
`s`
|
||||
The string that is the expression.
|
||||
|
||||
`filename`
|
||||
The name of the file the expression is in.
|
||||
|
||||
`linenumber`
|
||||
The line number the logical line starts at.
|
||||
|
||||
`column`
|
||||
The column the logical line starts at.
|
||||
|
||||
`text`
|
||||
The text of the line.
|
||||
|
||||
`pos`
|
||||
The position in the text where the expression starts.
|
||||
"""
|
||||
|
||||
cdef Py_UCS4 c
|
||||
cdef int i = 0
|
||||
|
||||
for c in text:
|
||||
if i >= pos:
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
if c == 10: # NL
|
||||
linenumber += 1
|
||||
column = 0
|
||||
else:
|
||||
column += 1
|
||||
|
||||
return PyExpr(s, filename, linenumber, 3, hash32(s), column)
|
||||
|
||||
+49
-27
@@ -20,7 +20,7 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
from typing import Callable
|
||||
from typing import Callable, NamedTuple
|
||||
|
||||
import re
|
||||
import sys
|
||||
@@ -31,6 +31,7 @@ import functools
|
||||
import renpy
|
||||
|
||||
from renpy.lexersupport import match_logical_word
|
||||
from renpy.astsupport import make_pyexpr
|
||||
|
||||
|
||||
class ParseError(SyntaxError):
|
||||
@@ -485,9 +486,9 @@ def list_logical_lines(filename, filedata=None, linenumber=1, add_lines=False):
|
||||
return rv
|
||||
|
||||
|
||||
def depth_split(l):
|
||||
def split_indent(l):
|
||||
"""
|
||||
Returns the length of the line's prefix, and the rest of the line.
|
||||
Returns the length of the line's indentation, and the rest of the line.
|
||||
"""
|
||||
|
||||
depth = 0
|
||||
@@ -506,6 +507,14 @@ def depth_split(l):
|
||||
# i, min_depth -> block, new_i
|
||||
|
||||
|
||||
class GroupedLine(NamedTuple):
|
||||
# The filename the line is from.
|
||||
filename: str
|
||||
number: int
|
||||
indent: int
|
||||
text: str
|
||||
block: list
|
||||
|
||||
def gll_core(lines, i, min_depth):
|
||||
"""
|
||||
Recursively groups lines into blocks.
|
||||
@@ -520,16 +529,16 @@ def gll_core(lines, i, min_depth):
|
||||
|
||||
filename, number, text = lines[i]
|
||||
|
||||
line_depth, rest = depth_split(text)
|
||||
indent, rest = split_indent(text)
|
||||
|
||||
# This catches a block exit.
|
||||
if line_depth < min_depth:
|
||||
if indent < min_depth:
|
||||
break
|
||||
|
||||
if depth is None:
|
||||
depth = line_depth
|
||||
depth = indent
|
||||
|
||||
if depth != line_depth:
|
||||
if depth != indent:
|
||||
raise ParseError(
|
||||
"Indentation mismatch.",
|
||||
filename, number, text=text)
|
||||
@@ -540,17 +549,16 @@ def gll_core(lines, i, min_depth):
|
||||
# Try parsing a block associated with this line.
|
||||
block, i = gll_core(lines, i, depth + 1)
|
||||
|
||||
rv.append((filename, number, rest, block))
|
||||
rv.append(GroupedLine(filename, number, indent, rest, block))
|
||||
|
||||
return rv, i
|
||||
|
||||
|
||||
def group_logical_lines(lines):
|
||||
"""
|
||||
This takes as input the list of logical line triples output from
|
||||
list_logical_lines, and breaks the lines into blocks. Each block
|
||||
is represented as a list of (filename, line number, line text,
|
||||
block) triples, where block is a block list (which may be empty if
|
||||
is represented as a list of (filename, line number, starting column, line text,
|
||||
block) tuples, where block is a block list (which may be empty if
|
||||
no block is associated with this line.)
|
||||
"""
|
||||
|
||||
@@ -558,7 +566,7 @@ def group_logical_lines(lines):
|
||||
|
||||
filename, number, text = lines[0]
|
||||
|
||||
if depth_split(text)[0] != 0:
|
||||
if split_indent(text)[0] != 0:
|
||||
raise ParseError(
|
||||
"Unexpected indentation at start of file.",
|
||||
filename, number, text=text)
|
||||
@@ -653,8 +661,16 @@ class Lexer(object):
|
||||
sub-lexers to lex sub-blocks.
|
||||
"""
|
||||
|
||||
block: list[GroupedLine]
|
||||
|
||||
def __init__(self, block, init=False, init_offset=0, global_label=None, monologue_delimiter="\n\n", subparses=None):
|
||||
|
||||
|
||||
# Older version of Lexer had block being a list of tuples. Those lists can be found in UserStatements,
|
||||
# and so need to be upgraded.
|
||||
if block and len(block[0]) == 4:
|
||||
self.block = [ GroupedLine(filename, line, 0, text, subblock) for filename, line, text, subblock in block ]
|
||||
|
||||
# Are we underneath an init block?
|
||||
self.init = init
|
||||
|
||||
@@ -677,10 +693,14 @@ class Lexer(object):
|
||||
self.word_cache_newpos = -1
|
||||
self.word_cache = ""
|
||||
|
||||
# The column text starts at.
|
||||
self.column = 0
|
||||
|
||||
self.monologue_delimiter = monologue_delimiter
|
||||
|
||||
self.subparses = subparses
|
||||
|
||||
|
||||
def _unmunge_string(self, s: str) -> str:
|
||||
prefix = munge_filename(self.filename)
|
||||
return s.replace(prefix, "__")
|
||||
@@ -703,7 +723,7 @@ class Lexer(object):
|
||||
self.eob = True
|
||||
return False
|
||||
|
||||
self.filename, self.number, self.text, self.subblock = self.block[self.line]
|
||||
self.filename, self.number, self.column, self.text, self.subblock = self.block[self.line]
|
||||
self.pos = 0
|
||||
self.word_cache_pos = -1
|
||||
|
||||
@@ -718,7 +738,7 @@ class Lexer(object):
|
||||
|
||||
self.line -= 1
|
||||
self.eob = False
|
||||
self.filename, self.number, self.text, self.subblock = self.block[self.line]
|
||||
self.filename, self.number, self.column, self.text, self.subblock = self.block[self.line]
|
||||
self.pos = len(self.text)
|
||||
self.word_cache_pos = -1
|
||||
|
||||
@@ -750,8 +770,6 @@ class Lexer(object):
|
||||
Advances the current position beyond any contiguous whitespace.
|
||||
"""
|
||||
|
||||
# print self.text[self.pos].encode('unicode_escape')
|
||||
|
||||
self.match_regexp(r"(\s+|\\\n)+")
|
||||
|
||||
def match(self, regexp):
|
||||
@@ -812,7 +830,7 @@ class Lexer(object):
|
||||
"""
|
||||
|
||||
if (self.line == -1) and self.block:
|
||||
self.filename, self.number, self.text, self.subblock = self.block[0]
|
||||
self.filename, self.number, self.column, self.text, self.subblock = self.block[0]
|
||||
|
||||
raise ParseError(
|
||||
msg,
|
||||
@@ -831,7 +849,7 @@ class Lexer(object):
|
||||
"""
|
||||
|
||||
if (self.line == -1) and self.block:
|
||||
self.filename, self.number, self.text, self.subblock = self.block[0]
|
||||
self.filename, self.number, self.column, self.text, self.subblock = self.block[0]
|
||||
|
||||
ParseError(
|
||||
msg, self.filename,
|
||||
@@ -1238,9 +1256,10 @@ class Lexer(object):
|
||||
if not expr:
|
||||
return s
|
||||
|
||||
pos = self.pos - len(s)
|
||||
s = self._unmunge_string(s)
|
||||
|
||||
return renpy.ast.PyExpr(s, self.filename, self.number)
|
||||
return make_pyexpr(s, self.filename, self.number, self.column, self.text, pos)
|
||||
|
||||
def delimited_python(self, delim, expr=True):
|
||||
"""
|
||||
@@ -1417,7 +1436,7 @@ class Lexer(object):
|
||||
passed to revert to back the lexer up.
|
||||
"""
|
||||
|
||||
return self.line, self.filename, self.number, self.text, self.subblock, self.pos, renpy.ast.PyExpr.checkpoint()
|
||||
return self.line, self.filename, self.number, self.text, self.subblock, self.pos, self.column, renpy.ast.PyExpr.checkpoint()
|
||||
|
||||
def revert(self, state):
|
||||
"""
|
||||
@@ -1425,7 +1444,7 @@ class Lexer(object):
|
||||
by a previous checkpoint operation on this lexer.
|
||||
"""
|
||||
|
||||
self.line, self.filename, self.number, self.text, self.subblock, self.pos, pyexpr_checkpoint = state
|
||||
self.line, self.filename, self.number, self.text, self.subblock, self.pos, self.column, pyexpr_checkpoint = state
|
||||
|
||||
renpy.ast.PyExpr.revert(pyexpr_checkpoint)
|
||||
|
||||
@@ -1489,19 +1508,22 @@ class Lexer(object):
|
||||
self.pos = len(self.text)
|
||||
return self.text[pos:].strip()
|
||||
|
||||
def _process_python_block(self, block, indent, rv, line_holder):
|
||||
for _fn, ln, text, subblock in block:
|
||||
def _process_python_block(self, block, rv, line_holder):
|
||||
|
||||
for _fn, ln, indent, text, subblock in block:
|
||||
|
||||
prefix = " " * indent
|
||||
|
||||
while line_holder.line < ln:
|
||||
rv.append(indent + '\n')
|
||||
rv.append(prefix + '\n')
|
||||
line_holder.line += 1
|
||||
|
||||
linetext = indent + text + '\n'
|
||||
linetext = prefix + text + '\n'
|
||||
|
||||
rv.append(linetext)
|
||||
line_holder.line += linetext.count('\n')
|
||||
|
||||
self._process_python_block(subblock, indent + ' ', rv, line_holder)
|
||||
self._process_python_block(subblock, rv, line_holder)
|
||||
|
||||
def python_block(self):
|
||||
"""
|
||||
@@ -1515,7 +1537,7 @@ class Lexer(object):
|
||||
line_holder = LineNumberHolder()
|
||||
line_holder.line = self.number
|
||||
|
||||
self._process_python_block(self.subblock, '', rv, line_holder)
|
||||
self._process_python_block(self.subblock, rv, line_holder)
|
||||
return self._unmunge_string(''.join(rv))
|
||||
|
||||
def arguments(self):
|
||||
|
||||
+14
-2
@@ -1112,6 +1112,14 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
if py is None:
|
||||
py = 3
|
||||
|
||||
# This determines if the lines are indented. If so, we adjust the
|
||||
# ast to match.
|
||||
indented = source and (source[0] == " ")
|
||||
|
||||
if indented:
|
||||
lineno -= 1
|
||||
source = "if True:\n" + source
|
||||
|
||||
flags = file_compiler_flags.get(filename, 0)
|
||||
|
||||
if cache:
|
||||
@@ -1130,6 +1138,7 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
return rv
|
||||
|
||||
bytecode = renpy.game.script.bytecode_oldcache.get(key, None)
|
||||
|
||||
if bytecode is not None:
|
||||
|
||||
try:
|
||||
@@ -1192,8 +1201,11 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
if not handled:
|
||||
raise
|
||||
|
||||
tree = wrap_node.visit(tree)
|
||||
# If the body is indented, it's wrapped in an "if True:" statement, which needs to be eliminated.
|
||||
if indented:
|
||||
tree.body = tree.body[0].body
|
||||
|
||||
tree = wrap_node.visit(tree)
|
||||
tree = MungeNodes(filename).visit(tree)
|
||||
|
||||
if mode == "hide":
|
||||
@@ -1223,7 +1235,7 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False, cache=
|
||||
|
||||
if not handled:
|
||||
raise
|
||||
|
||||
|
||||
if cache:
|
||||
py_compile_cache[key] = rv
|
||||
|
||||
|
||||
Reference in New Issue
Block a user