Print the offending line of code in the traceback for SyntaxErrors
authorJeremy Hylton <jeremy@alum.mit.edu>
Wed, 28 Feb 2001 07:07:43 +0000 (07:07 +0000)
committerJeremy Hylton <jeremy@alum.mit.edu>
Wed, 28 Feb 2001 07:07:43 +0000 (07:07 +0000)
raised by the compiler.

XXX For now, text entered into the interactive intepreter is not
printed in the traceback.

Inspired by a patch from Roman Sulzhyk

compile.c:

Add helper fetch_program_text() that opens a file and reads until it
finds the specified line number.  The code is a near duplicate of
similar code in traceback.c.

Modify com_error() to pass two arguments to SyntaxError constructor,
where the second argument contains the offending text when possible.

Modify set_error_location(), now used only by the symtable pass, to
set the text attribute on existing exceptions.

pythonrun.c:

Change parse_syntax_error() to continue of the offset attribute of a
SyntaxError is None.  In this case, it sets offset to -1.

Move code from PyErr_PrintEx() into helper function
print_error_text().  In the helper, only print the caret for a
SyntaxError if offset > 0.

Python/compile.c
Python/pythonrun.c

index d9294d4ad90b6cfafaaba3974d29de15a38901e5..c540d6b4e71a6993230c286e2865a22a59ee33ca 100644 (file)
@@ -381,41 +381,54 @@ int is_free(int v)
        return 0;
 }
 
-/* Error message including line number */
+/* com_fetch_program_text will attempt to load the line of text that
+   the exception refers to.  If it fails, it will return NULL but will
+   not set an exception. 
 
-static void
-set_error_location(char *filename, int lineno)
+   XXX The functionality of this function is quite similar to the
+   functionality in tb_displayline() in traceback.c.
+*/
+
+static PyObject *
+fetch_program_text(char *filename, int lineno)
 {
-       PyObject *exc, *v, *tb, *tmp;
+       FILE *fp;
+       int i;
+       char linebuf[1000];
 
-       /* add attributes for the line number and filename for the error */
-       PyErr_Fetch(&exc, &v, &tb);
-       PyErr_NormalizeException(&exc, &v, &tb);
-       tmp = PyInt_FromLong(lineno);
-       if (tmp == NULL)
-               PyErr_Clear();
-       else {
-               if (PyObject_SetAttrString(v, "lineno", tmp))
-                       PyErr_Clear();
-               Py_DECREF(tmp);
-       }
-       if (filename != NULL) {
-               tmp = PyString_FromString(filename);
-               if (tmp == NULL)
-                       PyErr_Clear();
-               else {
-                       if (PyObject_SetAttrString(v, "filename", tmp))
-                               PyErr_Clear();
-                       Py_DECREF(tmp);
-               }
+       if (filename == NULL || lineno <= 0)
+               return NULL;
+       fp = fopen(filename, "r");
+       if (fp == NULL)
+               return NULL;
+       for (i = 0; i < lineno; i++) {
+               char *pLastChar = &linebuf[sizeof(linebuf) - 2];
+               do {
+                       *pLastChar = '\0';
+                       if (fgets(linebuf, sizeof linebuf, fp) == NULL)
+                               break;
+                       /* fgets read *something*; if it didn't get as
+                          far as pLastChar, it must have found a newline
+                          or hit the end of the file;  if pLastChar is \n,
+                          it obviously found a newline; else we haven't
+                          yet seen a newline, so must continue */
+               } while (*pLastChar != '\0' && *pLastChar != '\n');
+       }
+       fclose(fp);
+       if (i == lineno) {
+               char *p = linebuf;
+               while (*p == ' ' || *p == '\t' || *p == '\014')
+                       p++;
+               return PyString_FromString(p);
        }
-       PyErr_Restore(exc, v, tb);
+       return NULL;
 }
 
 static void
 com_error(struct compiling *c, PyObject *exc, char *msg)
 {
-       PyObject *v;
+       PyObject *t = NULL, *v = NULL, *w = NULL, *line = NULL;
+
        if (c == NULL) {
                /* Error occurred via symtable call to
                   is_constant_false */
@@ -423,18 +436,33 @@ com_error(struct compiling *c, PyObject *exc, char *msg)
                return;
        }
        c->c_errors++;
-       if (c->c_lineno <= 1) {
-               /* Unknown line number or single interactive command */
+       if (c->c_lineno < 1 || c->c_interactive) { 
+               /* Unknown line number or interactive input */
                PyErr_SetString(exc, msg);
                return;
        }
        v = PyString_FromString(msg);
        if (v == NULL)
                return; /* MemoryError, too bad */
-       PyErr_SetObject(exc, v);
-       Py_DECREF(v);
 
-       set_error_location(c->c_filename, c->c_lineno);
+       line = fetch_program_text(c->c_filename, c->c_lineno);
+       if (line == NULL) {
+               Py_INCREF(Py_None);
+               line = Py_None;
+       }
+       t = Py_BuildValue("(ziOO)", c->c_filename, c->c_lineno,
+                         Py_None, line);
+       if (t == NULL)
+               goto exit;
+       w = Py_BuildValue("(OO)", v, t);
+       if (w == NULL)
+               goto exit;
+       PyErr_SetObject(exc, w);
+ exit:
+       Py_XDECREF(t);
+       Py_XDECREF(v);
+       Py_XDECREF(w);
+       Py_XDECREF(line);
 }
 
 /* Interface to the block stack */
@@ -3998,6 +4026,43 @@ get_ref_type(struct compiling *c, char *name)
        return -1; /* can't get here */
 }
 
+/* Helper function for setting lineno and filename */
+
+static void
+set_error_location(char *filename, int lineno)
+{
+       PyObject *exc, *v, *tb, *tmp;
+
+       /* add attributes for the line number and filename for the error */
+       PyErr_Fetch(&exc, &v, &tb);
+       PyErr_NormalizeException(&exc, &v, &tb);
+       tmp = PyInt_FromLong(lineno);
+       if (tmp == NULL)
+               PyErr_Clear();
+       else {
+               if (PyObject_SetAttrString(v, "lineno", tmp))
+                       PyErr_Clear();
+               Py_DECREF(tmp);
+       }
+       if (filename != NULL) {
+               tmp = PyString_FromString(filename);
+               if (tmp == NULL)
+                       PyErr_Clear();
+               else {
+                       if (PyObject_SetAttrString(v, "filename", tmp))
+                               PyErr_Clear();
+                       Py_DECREF(tmp);
+               }
+
+               tmp = fetch_program_text(filename, lineno);
+               if (tmp) {
+                       PyObject_SetAttrString(v, "text", tmp);
+                       Py_DECREF(tmp);
+               }
+       }
+       PyErr_Restore(exc, v, tb);
+}
+
 static int
 symtable_build(struct compiling *c, node *n)
 {
index 48b875e149b73d1f5fa2d9e96c0aaa90a5a8b78d..40611b6c5ee19d3c8608a16d2e59a4e0946058c5 100644 (file)
@@ -693,12 +693,18 @@ parse_syntax_error(PyObject *err, PyObject **message, char **filename,
 
        if (!(v = PyObject_GetAttrString(err, "offset")))
                goto finally;
-       hold = PyInt_AsLong(v);
-       Py_DECREF(v);
-       v = NULL;
-       if (hold < 0 && PyErr_Occurred())
-               goto finally;
-       *offset = (int)hold;
+       if (v == Py_None) {
+               *offset = -1;
+               Py_DECREF(v);
+               v = NULL;
+       } else {
+               hold = PyInt_AsLong(v);
+               Py_DECREF(v);
+               v = NULL;
+               if (hold < 0 && PyErr_Occurred())
+                       goto finally;
+               *offset = (int)hold;
+       }
 
        if (!(v = PyObject_GetAttrString(err, "text")))
                goto finally;
@@ -720,6 +726,40 @@ PyErr_Print(void)
        PyErr_PrintEx(1);
 }
 
+static void
+print_error_text(PyObject *f, int offset, char *text)
+{
+       char *nl;
+       if (offset >= 0) {
+               if (offset > 0 && offset == (int)strlen(text))
+                       offset--;
+               for (;;) {
+                       nl = strchr(text, '\n');
+                       if (nl == NULL || nl-text >= offset)
+                               break;
+                       offset -= (nl+1-text);
+                       text = nl+1;
+               }
+               while (*text == ' ' || *text == '\t') {
+                       text++;
+                       offset--;
+               }
+       }
+       PyFile_WriteString("    ", f);
+       PyFile_WriteString(text, f);
+       if (*text == '\0' || text[strlen(text)-1] != '\n')
+               PyFile_WriteString("\n", f);
+       if (offset == -1)
+               return;
+       PyFile_WriteString("    ", f);
+       offset--;
+       while (offset > 0) {
+               PyFile_WriteString(" ", f);
+               offset--;
+       }
+       PyFile_WriteString("^\n", f);
+}
+
 void
 PyErr_PrintEx(int set_sys_last_vars)
 {
@@ -795,36 +835,8 @@ PyErr_PrintEx(int set_sys_last_vars)
                                sprintf(buf, "%d", lineno);
                                PyFile_WriteString(buf, f);
                                PyFile_WriteString("\n", f);
-                               if (text != NULL) {
-                                       char *nl;
-                                       if (offset > 0 &&
-                                           offset == (int)strlen(text))
-                                               offset--;
-                                       for (;;) {
-                                               nl = strchr(text, '\n');
-                                               if (nl == NULL ||
-                                                   nl-text >= offset)
-                                                       break;
-                                               offset -= (nl+1-text);
-                                               text = nl+1;
-                                       }
-                                       while (*text == ' ' || *text == '\t') {
-                                               text++;
-                                               offset--;
-                                       }
-                                       PyFile_WriteString("    ", f);
-                                       PyFile_WriteString(text, f);
-                                       if (*text == '\0' ||
-                                           text[strlen(text)-1] != '\n')
-                                               PyFile_WriteString("\n", f);
-                                       PyFile_WriteString("    ", f);
-                                       offset--;
-                                       while (offset > 0) {
-                                               PyFile_WriteString(" ", f);
-                                               offset--;
-                                       }
-                                       PyFile_WriteString("^\n", f);
-                               }
+                               if (text != NULL)
+                                       print_error_text(f, offset, text);
                                Py_INCREF(message);
                                Py_DECREF(v);
                                v = message;