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

@ -98,6 +98,10 @@ extern DL_IMPORT(int) PyErr_Warn(PyObject *, char *);
/* In sigcheck.c or signalmodule.c */
extern DL_IMPORT(int) PyErr_CheckSignals(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

View File

@ -381,49 +381,6 @@ int is_free(int v)
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
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)
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) {
Py_INCREF(Py_None);
line = Py_None;
@ -4028,41 +3985,6 @@ get_ref_type(struct compiling *c, char *name)
/* 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)
{
@ -4198,7 +4120,7 @@ symtable_update_flags(struct compiling *c, PySymtableEntryObject *ste,
PyErr_Format(PyExc_SyntaxError,
ILLEGAL_DYNAMIC_SCOPE,
PyString_AS_STRING(ste->ste_name));
set_error_location(c->c_symtable->st_filename,
PyErr_SyntaxLocation(c->c_symtable->st_filename,
ste->ste_lineno);
return -1;
} else {
@ -4273,7 +4195,7 @@ symtable_load_symbols(struct compiling *c)
if (flags & DEF_PARAM) {
PyErr_Format(PyExc_SyntaxError, LOCAL_GLOBAL,
PyString_AS_STRING(name));
set_error_location(st->st_filename,
PyErr_SyntaxLocation(st->st_filename,
ste->ste_lineno);
st->st_errors++;
goto fail;
@ -4581,7 +4503,7 @@ symtable_add_def_o(struct symtable *st, PyObject *dict,
if ((flag & DEF_PARAM) && (val & DEF_PARAM)) {
PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT,
PyString_AsString(name));
set_error_location(st->st_filename,
PyErr_SyntaxLocation(st->st_filename,
st->st_cur->ste_lineno);
return -1;
}
@ -4904,7 +4826,7 @@ symtable_global(struct symtable *st, node *n)
PyErr_Format(PyExc_SyntaxError,
"name '%.400s' is local and global",
name);
set_error_location(st->st_filename,
PyErr_SyntaxLocation(st->st_filename,
st->st_cur->ste_lineno);
st->st_errors++;
return;
@ -4958,7 +4880,7 @@ symtable_import(struct symtable *st, node *n)
if (n->n_lineno >= st->st_future->ff_last_lineno) {
PyErr_SetString(PyExc_SyntaxError,
LATE_FUTURE);
set_error_location(st->st_filename,
PyErr_SyntaxLocation(st->st_filename,
n->n_lineno);
st->st_errors++;
return;

View File

@ -622,3 +622,82 @@ PyErr_Warn(PyObject *category, char *message)
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"
#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)
static int
future_check_features(PyFutureFeatures *ff, node *n)
future_check_features(PyFutureFeatures *ff, node *n, char *filename)
{
int i;
char *feature;
node *ch;
REQ(n, import_stmt); /* must by from __future__ import ... */
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) {
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 {
PyErr_Format(PyExc_SyntaxError,
UNDEFINED_FUTURE_FEATURE, feature);
PyErr_SyntaxLocation(filename, CHILD(ch, 0)->n_lineno);
return -1;
}
}
@ -36,6 +52,7 @@ future_error(node *n, char *filename)
PyErr_SetString(PyExc_SyntaxError,
"from __future__ imports must occur at the "
"beginning of the file");
PyErr_SyntaxLocation(filename, n->n_lineno);
/* XXX set filename and lineno */
}
@ -45,8 +62,10 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
file_input: (NEWLINE | stmt)* ENDMARKER
stmt: simple_stmt | compound_stmt
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
import_stmt: 'import' dotted_as_name (',' dotted_as_name)* | 'from' dotted_name 'import' ('*' | import_as_name (',' import_as_name)*)
small_stmt: expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt
| 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]
dotted_as_name: dotted_name [NAME NAME]
dotted_name: NAME ('.' NAME)*
@ -64,11 +83,6 @@ future_parse(PyFutureFeatures *ff, node *n, char *filename)
int i, r;
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)) {
case single_input:
@ -162,7 +176,7 @@ future_parse(PyFutureFeatures *ff, node *n, char *filename)
name = CHILD(n, 1);
if (strcmp(STR(CHILD(name, 0)), "__future__") != 0)
return 0;
if (future_check_features(ff, n) < 0)
if (future_check_features(ff, n, filename) < 0)
return -1;
ff->ff_last_lineno = n->n_lineno + 1;
return 1;