- Finally fixed the bug in compile() and exec where a string ending

with an indented code block but no newline would raise SyntaxError.
  This would have been a four-line change in parsetok.c...  Except
  codeop.py depends on this behavior, so a compilation flag had to be
  invented that causes the tokenizer to revert to the old behavior;
  this required extra changes to 2 .h files, 2 .c files, and 2 .py
  files.  (Fixes SF bug #501622.)
This commit is contained in:
Guido van Rossum 2003-02-13 22:07:59 +00:00
parent 5aa3da6495
commit 4b499dd3fb
10 changed files with 47 additions and 13 deletions

View File

@ -21,6 +21,8 @@ typedef struct {
#define PyPARSE_YIELD_IS_KEYWORD 0x0001 #define PyPARSE_YIELD_IS_KEYWORD 0x0001
#endif #endif
#define PyPARSE_DONT_IMPLY_DEDENT 0x0002
PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int, PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int,
perrdetail *); perrdetail *);
PyAPI_FUNC(node *) PyParser_ParseFile (FILE *, const char *, grammar *, int, PyAPI_FUNC(node *) PyParser_ParseFile (FILE *, const char *, grammar *, int,

View File

@ -10,6 +10,7 @@ extern "C" {
#define PyCF_MASK (CO_FUTURE_DIVISION) #define PyCF_MASK (CO_FUTURE_DIVISION)
#define PyCF_MASK_OBSOLETE (CO_GENERATOR_ALLOWED | CO_NESTED) #define PyCF_MASK_OBSOLETE (CO_GENERATOR_ALLOWED | CO_NESTED)
#define PyCF_SOURCE_IS_UTF8 0x0100 #define PyCF_SOURCE_IS_UTF8 0x0100
#define PyCF_DONT_IMPLY_DEDENT 0x0200
typedef struct { typedef struct {
int cf_flags; /* bitmask of CO_xxx flags relevant to future */ int cf_flags; /* bitmask of CO_xxx flags relevant to future */

View File

@ -303,4 +303,5 @@ def interact(banner=None, readfunc=None, local=None):
if __name__ == '__main__': if __name__ == '__main__':
interact() import pdb
pdb.run("interact()\n")

View File

@ -63,6 +63,8 @@ _features = [getattr(__future__, fname)
__all__ = ["compile_command", "Compile", "CommandCompiler"] __all__ = ["compile_command", "Compile", "CommandCompiler"]
PyCF_DONT_IMPLY_DEDENT = 0x200 # Matches pythonrun.h
def _maybe_compile(compiler, source, filename, symbol): def _maybe_compile(compiler, source, filename, symbol):
# Check for source consisting of only blank lines and comments # Check for source consisting of only blank lines and comments
for line in source.split("\n"): for line in source.split("\n"):
@ -103,6 +105,9 @@ def _maybe_compile(compiler, source, filename, symbol):
if not code1 and e1 == e2: if not code1 and e1 == e2:
raise SyntaxError, err1 raise SyntaxError, err1
def _compile(source, filename, symbol):
return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT)
def compile_command(source, filename="<input>", symbol="single"): def compile_command(source, filename="<input>", symbol="single"):
r"""Compile a command and determine whether it is incomplete. r"""Compile a command and determine whether it is incomplete.
@ -121,7 +126,7 @@ def compile_command(source, filename="<input>", symbol="single"):
syntax error (OverflowError and ValueError can be produced by syntax error (OverflowError and ValueError can be produced by
malformed literals). malformed literals).
""" """
return _maybe_compile(compile, source, filename, symbol) return _maybe_compile(_compile, source, filename, symbol)
class Compile: class Compile:
"""Instances of this class behave much like the built-in compile """Instances of this class behave much like the built-in compile
@ -129,7 +134,7 @@ class Compile:
statement, it "remembers" and compiles all subsequent program texts statement, it "remembers" and compiles all subsequent program texts
with the statement in force.""" with the statement in force."""
def __init__(self): def __init__(self):
self.flags = 0 self.flags = PyCF_DONT_IMPLY_DEDENT
def __call__(self, source, filename, symbol): def __call__(self, source, filename, symbol):
codeob = compile(source, filename, symbol, self.flags, 1) codeob = compile(source, filename, symbol, self.flags, 1)

View File

@ -5,13 +5,13 @@
import unittest import unittest
from test.test_support import run_unittest from test.test_support import run_unittest
from codeop import compile_command from codeop import compile_command, PyCF_DONT_IMPLY_DEDENT
class CodeopTests(unittest.TestCase): class CodeopTests(unittest.TestCase):
def assertValid(self, str, symbol='single'): def assertValid(self, str, symbol='single'):
'''succeed iff str is a valid piece of code''' '''succeed iff str is a valid piece of code'''
expected = compile(str, "<input>", symbol) expected = compile(str, "<input>", symbol, PyCF_DONT_IMPLY_DEDENT)
self.assertEquals( compile_command(str, "<input>", symbol), expected) self.assertEquals( compile_command(str, "<input>", symbol), expected)
@ -42,7 +42,8 @@ class CodeopTests(unittest.TestCase):
# special case # special case
self.assertEquals(compile_command(""), self.assertEquals(compile_command(""),
compile("pass", "<input>", 'single')) compile("pass", "<input>", 'single',
PyCF_DONT_IMPLY_DEDENT))
av("3**3","eval") av("3**3","eval")
av("(lambda z: \n z**3)","eval") av("(lambda z: \n z**3)","eval")

View File

@ -89,6 +89,15 @@ expect_error("2.0e+")
expect_error("1e-") expect_error("1e-")
expect_error("3-4e/21") expect_error("3-4e/21")
if verbose:
print "testing compile() of indented block w/o trailing newline"
s = """
if 1:
if 2:
pass"""
compile(s, "<string>", "exec")
if verbose: if verbose:
print "testing literals with leading zeroes" print "testing literals with leading zeroes"

View File

@ -12,6 +12,14 @@ What's New in Python 2.3 alpha 2?
Core and builtins Core and builtins
----------------- -----------------
- Finally fixed the bug in compile() and exec where a string ending
with an indented code block but no newline would raise SyntaxError.
This would have been a four-line change in parsetok.c... Except
codeop.py depends on this behavior, so a compilation flag had to be
invented that causes the tokenizer to revert to the old behavior;
this required extra changes to 2 .h files, 2 .c files, and 2 .py
files.
- If a new-style class defines neither __new__ nor __init__, its - If a new-style class defines neither __new__ nor __init__, its
constructor would ignore all arguments. This is changed now: the constructor would ignore all arguments. This is changed now: the
constructor refuses arguments in this case. This might break code constructor refuses arguments in this case. This might break code

View File

@ -130,6 +130,15 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
if (type == ENDMARKER && started) { if (type == ENDMARKER && started) {
type = NEWLINE; /* Add an extra newline */ type = NEWLINE; /* Add an extra newline */
started = 0; started = 0;
/* Add the right number of dedent tokens,
except if a certain flag is given --
codeop.py uses this. */
if (tok->indent &&
!(flags & PyPARSE_DONT_IMPLY_DEDENT))
{
tok->pendin = -tok->indent;
tok->indent = 0;
}
} }
else else
started = 1; started = 1;

View File

@ -380,7 +380,9 @@ builtin_compile(PyObject *self, PyObject *args)
return NULL; return NULL;
} }
if (supplied_flags & ~(PyCF_MASK | PyCF_MASK_OBSOLETE)) { if (supplied_flags &
~(PyCF_MASK | PyCF_MASK_OBSOLETE | PyCF_DONT_IMPLY_DEDENT))
{
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"compile(): unrecognised flags"); "compile(): unrecognised flags");
return NULL; return NULL;

View File

@ -548,13 +548,9 @@ PyRun_InteractiveOne(FILE *fp, const char *filename)
} }
/* compute parser flags based on compiler flags */ /* compute parser flags based on compiler flags */
#if 0 /* future keyword */
#define PARSER_FLAGS(flags) \ #define PARSER_FLAGS(flags) \
(((flags) && (flags)->cf_flags & CO_GENERATOR_ALLOWED) ? \ (((flags) && (flags)->cf_flags & PyCF_DONT_IMPLY_DEDENT) ? \
PyPARSE_YIELD_IS_KEYWORD : 0) PyPARSE_DONT_IMPLY_DEDENT : 0)
#else
#define PARSER_FLAGS(flags) 0
#endif
int int
PyRun_InteractiveOneFlags(FILE *fp, const char *filename, PyCompilerFlags *flags) PyRun_InteractiveOneFlags(FILE *fp, const char *filename, PyCompilerFlags *flags)