Improve SyntaxErrors for bad future statements. Set file and location

for errors raised in future.c.

Move some helper functions from compile.c to errors.c and make them
API functions: PyErr_SyntaxLocation() and PyErr_ProgramText().
This commit is contained in:
Jeremy Hylton 2001-02-28 17:47:12 +00:00
parent 5687ffe0c5
commit ad3d3f2f3f
4 changed files with 113 additions and 94 deletions

View File

@ -99,6 +99,10 @@ extern DL_IMPORT(int) PyErr_Warn(PyObject *, char *);
extern DL_IMPORT(int) PyErr_CheckSignals(void); extern DL_IMPORT(int) PyErr_CheckSignals(void);
extern DL_IMPORT(void) PyErr_SetInterrupt(void); extern DL_IMPORT(void) PyErr_SetInterrupt(void);
/* Support for adding program text to SyntaxErrors */
extern DL_IMPORT(void) PyErr_SyntaxLocation(char *, int);
extern DL_IMPORT(PyObject *) PyErr_ProgramText(char *, int);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -381,49 +381,6 @@ int is_free(int v)
return 0; return 0;
} }
/* 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.
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)
{
FILE *fp;
int i;
char linebuf[1000];
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);
}
return NULL;
}
static void static void
com_error(struct compiling *c, PyObject *exc, char *msg) com_error(struct compiling *c, PyObject *exc, char *msg)
{ {
@ -445,7 +402,7 @@ com_error(struct compiling *c, PyObject *exc, char *msg)
if (v == NULL) if (v == NULL)
return; /* MemoryError, too bad */ return; /* MemoryError, too bad */
line = fetch_program_text(c->c_filename, c->c_lineno); line = PyErr_ProgramText(c->c_filename, c->c_lineno);
if (line == NULL) { if (line == NULL) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
line = Py_None; line = Py_None;
@ -4028,41 +3985,6 @@ get_ref_type(struct compiling *c, char *name)
/* Helper function for setting lineno and filename */ /* 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 static int
symtable_build(struct compiling *c, node *n) symtable_build(struct compiling *c, node *n)
{ {
@ -4198,7 +4120,7 @@ symtable_update_flags(struct compiling *c, PySymtableEntryObject *ste,
PyErr_Format(PyExc_SyntaxError, PyErr_Format(PyExc_SyntaxError,
ILLEGAL_DYNAMIC_SCOPE, ILLEGAL_DYNAMIC_SCOPE,
PyString_AS_STRING(ste->ste_name)); PyString_AS_STRING(ste->ste_name));
set_error_location(c->c_symtable->st_filename, PyErr_SyntaxLocation(c->c_symtable->st_filename,
ste->ste_lineno); ste->ste_lineno);
return -1; return -1;
} else { } else {
@ -4273,7 +4195,7 @@ symtable_load_symbols(struct compiling *c)
if (flags & DEF_PARAM) { if (flags & DEF_PARAM) {
PyErr_Format(PyExc_SyntaxError, LOCAL_GLOBAL, PyErr_Format(PyExc_SyntaxError, LOCAL_GLOBAL,
PyString_AS_STRING(name)); PyString_AS_STRING(name));
set_error_location(st->st_filename, PyErr_SyntaxLocation(st->st_filename,
ste->ste_lineno); ste->ste_lineno);
st->st_errors++; st->st_errors++;
goto fail; goto fail;
@ -4581,7 +4503,7 @@ symtable_add_def_o(struct symtable *st, PyObject *dict,
if ((flag & DEF_PARAM) && (val & DEF_PARAM)) { if ((flag & DEF_PARAM) && (val & DEF_PARAM)) {
PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT, PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT,
PyString_AsString(name)); PyString_AsString(name));
set_error_location(st->st_filename, PyErr_SyntaxLocation(st->st_filename,
st->st_cur->ste_lineno); st->st_cur->ste_lineno);
return -1; return -1;
} }
@ -4904,7 +4826,7 @@ symtable_global(struct symtable *st, node *n)
PyErr_Format(PyExc_SyntaxError, PyErr_Format(PyExc_SyntaxError,
"name '%.400s' is local and global", "name '%.400s' is local and global",
name); name);
set_error_location(st->st_filename, PyErr_SyntaxLocation(st->st_filename,
st->st_cur->ste_lineno); st->st_cur->ste_lineno);
st->st_errors++; st->st_errors++;
return; return;
@ -4958,7 +4880,7 @@ symtable_import(struct symtable *st, node *n)
if (n->n_lineno >= st->st_future->ff_last_lineno) { if (n->n_lineno >= st->st_future->ff_last_lineno) {
PyErr_SetString(PyExc_SyntaxError, PyErr_SetString(PyExc_SyntaxError,
LATE_FUTURE); LATE_FUTURE);
set_error_location(st->st_filename, PyErr_SyntaxLocation(st->st_filename,
n->n_lineno); n->n_lineno);
st->st_errors++; st->st_errors++;
return; return;

View File

@ -622,3 +622,82 @@ PyErr_Warn(PyObject *category, char *message)
return 0; return 0;
} }
} }
void
PyErr_SyntaxLocation(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);
/* XXX check that it is, indeed, a syntax error */
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 = PyErr_ProgramText(filename, lineno);
if (tmp) {
PyObject_SetAttrString(v, "text", tmp);
Py_DECREF(tmp);
}
}
PyErr_Restore(exc, v, tb);
}
/* 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.
XXX The functionality of this function is quite similar to the
functionality in tb_displayline() in traceback.c.
*/
PyObject *
PyErr_ProgramText(char *filename, int lineno)
{
FILE *fp;
int i;
char linebuf[1000];
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);
}
return NULL;
}

View File

@ -6,24 +6,40 @@
#include "symtable.h" #include "symtable.h"
#define UNDEFINED_FUTURE_FEATURE "future feature %.100s is not defined" #define UNDEFINED_FUTURE_FEATURE "future feature %.100s is not defined"
#define FUTURE_IMPORT_STAR "future statement does not support import *"
#define FUTURE_POSSIBLE(FF) ((FF)->ff_last_lineno == -1) #define FUTURE_POSSIBLE(FF) ((FF)->ff_last_lineno == -1)
static int static int
future_check_features(PyFutureFeatures *ff, node *n) future_check_features(PyFutureFeatures *ff, node *n, char *filename)
{ {
int i; int i;
char *feature; char *feature;
node *ch;
REQ(n, import_stmt); /* must by from __future__ import ... */ REQ(n, import_stmt); /* must by from __future__ import ... */
for (i = 3; i < NCH(n); ++i) { for (i = 3; i < NCH(n); ++i) {
feature = STR(CHILD(CHILD(n, i), 0)); ch = CHILD(n, i);
if (TYPE(ch) == STAR) {
PyErr_SetString(PyExc_SyntaxError,
FUTURE_IMPORT_STAR);
PyErr_SyntaxLocation(filename, ch->n_lineno);
return -1;
}
REQ(ch, import_as_name);
feature = STR(CHILD(ch, 0));
if (strcmp(feature, FUTURE_NESTED_SCOPES) == 0) { if (strcmp(feature, FUTURE_NESTED_SCOPES) == 0) {
ff->ff_nested_scopes = 1; ff->ff_nested_scopes = 1;
} else if (strcmp(feature, "braces") == 0) {
PyErr_SetString(PyExc_SyntaxError,
"not a chance");
PyErr_SyntaxLocation(filename, CHILD(ch, 0)->n_lineno);
return -1;
} else { } else {
PyErr_Format(PyExc_SyntaxError, PyErr_Format(PyExc_SyntaxError,
UNDEFINED_FUTURE_FEATURE, feature); UNDEFINED_FUTURE_FEATURE, feature);
PyErr_SyntaxLocation(filename, CHILD(ch, 0)->n_lineno);
return -1; return -1;
} }
} }
@ -36,6 +52,7 @@ future_error(node *n, char *filename)
PyErr_SetString(PyExc_SyntaxError, PyErr_SetString(PyExc_SyntaxError,
"from __future__ imports must occur at the " "from __future__ imports must occur at the "
"beginning of the file"); "beginning of the file");
PyErr_SyntaxLocation(filename, n->n_lineno);
/* XXX set filename and lineno */ /* XXX set filename and lineno */
} }
@ -45,8 +62,10 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
file_input: (NEWLINE | stmt)* ENDMARKER file_input: (NEWLINE | stmt)* ENDMARKER
stmt: simple_stmt | compound_stmt stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | exec_stmt | assert_stmt small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt
import_stmt: 'import' dotted_as_name (',' dotted_as_name)* | 'from' dotted_name 'import' ('*' | import_as_name (',' import_as_name)*) | import_stmt | global_stmt | exec_stmt | assert_stmt
import_stmt: 'import' dotted_as_name (',' dotted_as_name)*
| 'from' dotted_name 'import' ('*' | import_as_name (',' import_as_name)*)
import_as_name: NAME [NAME NAME] import_as_name: NAME [NAME NAME]
dotted_as_name: dotted_name [NAME NAME] dotted_as_name: dotted_name [NAME NAME]
dotted_name: NAME ('.' NAME)* dotted_name: NAME ('.' NAME)*
@ -64,11 +83,6 @@ future_parse(PyFutureFeatures *ff, node *n, char *filename)
int i, r; int i, r;
loop: loop:
/* fprintf(stderr, "future_parse(%d, %d, %s, %d)\n",
TYPE(n), NCH(n), (n == NULL) ? "NULL" : STR(n),
n->n_lineno);
*/
switch (TYPE(n)) { switch (TYPE(n)) {
case single_input: case single_input:
@ -162,7 +176,7 @@ future_parse(PyFutureFeatures *ff, node *n, char *filename)
name = CHILD(n, 1); name = CHILD(n, 1);
if (strcmp(STR(CHILD(name, 0)), "__future__") != 0) if (strcmp(STR(CHILD(name, 0)), "__future__") != 0)
return 0; return 0;
if (future_check_features(ff, n) < 0) if (future_check_features(ff, n, filename) < 0)
return -1; return -1;
ff->ff_last_lineno = n->n_lineno + 1; ff->ff_last_lineno = n->n_lineno + 1;
return 1; return 1;