(original) (raw)
Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 46701) +++ Python/pythonrun.c (working copy) @@ -885,15 +885,18 @@ static int parse_syntax_error(PyObject *err, PyObject **message, const char **filename, - int *lineno, int *offset, const char **text) + int *lineno, int *offset, const char **text, + int *stmt_lineno) { long hold; PyObject *v; - /* old style errors */ + *stmt_lineno = 0; /* may not be specified in 'err' */ + + /* old style errors. 'err' is a tuple. */ if (PyTuple_Check(err)) - return PyArg_ParseTuple(err, "O(ziiz)", message, filename, - lineno, offset, text); + return PyArg_ParseTuple(err, "O(ziiz|i)", message, filename, + lineno, offset, text, stmt_lineno); /* new style errors. `err' is an instance */ @@ -940,6 +943,15 @@ else if (! (*text = PyString_AsString(v))) goto finally; Py_DECREF(v); + + if ((v = PyObject_GetAttrString(err, "stmt_lineno")) != NULL && + v != Py_None) { + hold = PyInt_AsLong(v); + Py_CLEAR(v); + if (hold < 0 && PyErr_Occurred()) + goto finally; + *stmt_lineno = (int)hold; + } return 1; finally: @@ -1103,12 +1115,13 @@ { PyObject *message; const char *filename, *text; - int lineno, offset; + int lineno, offset, stmt_lineno; if (!parse_syntax_error(value, &message, &filename, - &lineno, &offset, &text)) + &lineno, &offset, &text, + &stmt_lineno)) PyErr_Clear(); else { - char buf[10]; + char buf[25]; PyFile_WriteString(" File \"", f); if (filename == NULL) PyFile_WriteString("", f); @@ -1117,6 +1130,11 @@ PyFile_WriteString("\", line ", f); PyOS_snprintf(buf, sizeof(buf), "%d", lineno); PyFile_WriteString(buf, f); + if (stmt_lineno && stmt_lineno != lineno) { + PyOS_snprintf(buf, sizeof(buf), + " (continuing line %d)", stmt_lineno); + PyFile_WriteString(buf, f); + } PyFile_WriteString("\n", f); if (text != NULL) print_error_text(f, offset, text); @@ -1478,8 +1496,9 @@ msg = "unknown parsing error"; break; } - v = Py_BuildValue("(ziiz)", err->filename, - err->lineno, err->offset, err->text); + v = Py_BuildValue(err->stmt_lineno ? "(ziizi)" : "(ziiz)", + err->filename, err->lineno, err->offset, err->text, + err->stmt_lineno); if (err->text != NULL) { PyObject_FREE(err->text); err->text = NULL; Index: Include/parsetok.h =================================================================== --- Include/parsetok.h (revision 46701) +++ Include/parsetok.h (working copy) @@ -13,6 +13,7 @@ int lineno; int offset; char *text; + int stmt_lineno; int token; int expected; } perrdetail; Index: Include/pyerrors.h =================================================================== --- Include/pyerrors.h (revision 46701) +++ Include/pyerrors.h (working copy) @@ -23,6 +23,7 @@ PyObject *lineno; PyObject *offset; PyObject *text; + PyObject *stmt_lineno; PyObject *print_file_and_line; } PySyntaxErrorObject; Index: Objects/exceptions.c =================================================================== --- Objects/exceptions.c (revision 46701) +++ Objects/exceptions.c (working copy) @@ -988,11 +988,13 @@ Py_INCREF(self->msg); } if (lenargs == 2) { + int infosize; info = PyTuple_GET_ITEM(args, 1); info = PySequence_Tuple(info); if (!info) return -1; + infosize = PyTuple_GET_SIZE(info); - if (PyTuple_GET_SIZE(info) != 4) { + if (infosize != 4 && infosize != 5) { /* not a very good error message, but it's what Python 2.4 gives */ PyErr_SetString(PyExc_IndexError, "tuple index out of range"); Py_DECREF(info); @@ -1015,6 +1017,11 @@ self->text = PyTuple_GET_ITEM(info, 3); Py_INCREF(self->text); + Py_CLEAR(self->stmt_lineno); + self->stmt_lineno = (infosize == 5) ? PyTuple_GET_ITEM(info, 4) + : Py_None; + Py_INCREF(self->stmt_lineno); + Py_DECREF(info); } return 0; @@ -1028,6 +1035,7 @@ Py_CLEAR(self->lineno); Py_CLEAR(self->offset); Py_CLEAR(self->text); + Py_CLEAR(self->stmt_lineno); Py_CLEAR(self->print_file_and_line); return BaseException_clear((PyBaseExceptionObject *)self); } @@ -1047,6 +1055,7 @@ Py_VISIT(self->lineno); Py_VISIT(self->offset); Py_VISIT(self->text); + Py_VISIT(self->stmt_lineno); Py_VISIT(self->print_file_and_line); return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg); } @@ -1107,6 +1116,7 @@ if (buffer == NULL) return str; + /* We don't include stmt_lineno here. Should we? */ if (have_filename && have_lineno) PyOS_snprintf(buffer, bufsize, "%s (%s, line %ld)", PyString_AS_STRING(str), @@ -1144,6 +1154,8 @@ PyDoc_STR("exception offset")}, {"text", T_OBJECT, offsetof(PySyntaxErrorObject, text), 0, PyDoc_STR("exception text")}, + {"stmt_lineno", T_OBJECT, offsetof(PySyntaxErrorObject, stmt_lineno), 0, + PyDoc_STR("exception stmt_lineno")}, {"print_file_and_line", T_OBJECT, offsetof(PySyntaxErrorObject, print_file_and_line), 0, PyDoc_STR("exception print_file_and_line")}, Index: Parser/parsetok.c =================================================================== --- Parser/parsetok.c (revision 46701) +++ Parser/parsetok.c (working copy) @@ -113,6 +113,7 @@ parser_state *ps; node *n; int started = 0, handling_import = 0, handling_with = 0; + int stmt_lineno = 0; if ((ps = PyParser_New(g, start)) == NULL) { fprintf(stderr, "no mem for new parser\n"); @@ -163,6 +164,8 @@ if (len > 0) strncpy(str, a, len); str[len] = '\0'; + if (stmt_lineno == 0) /* starting a new statement */ + stmt_lineno = tok->lineno; #ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD /* This is only necessary to support the "as" warning, but @@ -200,6 +203,9 @@ } break; } + if ((tok->atbol && tok->level == 0 && !tok->cont_line) || + type == SEMI || type == COLON) + stmt_lineno = 0; /* next token starts new statement */ } if (err_ret->error == E_DONE) { @@ -215,6 +221,7 @@ if (tok->lineno <= 1 && tok->done == E_EOF) err_ret->error = E_EOF; err_ret->lineno = tok->lineno; + err_ret->stmt_lineno = stmt_lineno; if (tok->buf != NULL) { size_t len; assert(tok->cur - tok->buf < INT_MAX); @@ -247,6 +254,7 @@ err_ret->error = E_OK; err_ret->filename = filename; err_ret->lineno = 0; + err_ret->stmt_lineno = 0; err_ret->offset = 0; err_ret->text = NULL; err_ret->token = -1; Index: Lib/traceback.py =================================================================== --- Lib/traceback.py (revision 46701) +++ Lib/traceback.py (working copy) @@ -167,13 +167,17 @@ else: if issubclass(etype, SyntaxError): try: - msg, (filename, lineno, offset, line) = value + msg, location = value + filename, lineno, offset, line = location[:4] + stmt_lineno = location[4:5] # may not be present except: pass else: if not filename: filename = "" - list.append(' File "%s", line %d\n' % - (filename, lineno)) + loc = ' File "%s", line %d' % (filename, lineno) + if stmt_lineno: + loc += ' (continuing line %d)' % stmt_lineno + list.append(loc + '\n') if line is not None: i = 0 while i < len(line) and line[i].isspace(): Index: Lib/test/test_traceback.py =================================================================== --- Lib/test/test_traceback.py (revision 46701) +++ Lib/test/test_traceback.py (working copy) @@ -24,6 +24,9 @@ # XXX why doesn't compile raise the same traceback? import test.badsyntax_nocaret + def syntax_error_continuation(self): + compile("x = (1\n+\n&2\n", "?", "exec") + def syntax_error_bad_indentation(self): compile("def spam():\n print 1\n print 2", "?", "exec") @@ -43,6 +46,11 @@ self.assert_(len(err) == 3) self.assert_(err[1].strip() == "[x for x in x] = x") + def test_continuation(self): + err = self.get_exception_format(self.syntax_error_continuation, + SyntaxError) + self.assert_("(continuing line" in err[0]) + def test_bad_indentation(self): err = self.get_exception_format(self.syntax_error_bad_indentation, IndentationError)