Handle more syntax errors.

Invoke compiler.syntax.check() after building AST.  If a SyntaxError
occurs, print the error and exit without generating a .pyc file.

Refactor code to use compiler.misc.set_filename() rather than passing
filename argument around to each CodeGenerator instance.
This commit is contained in:
Jeremy Hylton 2001-09-17 18:03:55 +00:00
parent 09392b77a4
commit 37c9351cf6
4 changed files with 154 additions and 58 deletions

View File

@ -8,7 +8,7 @@ import sys
import types import types
from cStringIO import StringIO from cStringIO import StringIO
from compiler import ast, parse, walk from compiler import ast, parse, walk, syntax
from compiler import pyassem, misc, future, symbols from compiler import pyassem, misc, future, symbols
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL
from compiler.consts import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,\ from compiler.consts import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,\
@ -41,14 +41,16 @@ class BlockStack(misc.Stack):
self.__super_init(self) self.__super_init(self)
self.loop = None self.loop = None
def compile(filename, display=0): def compile(filename, display=0):
f = open(filename) f = open(filename)
buf = f.read() buf = f.read()
f.close() f.close()
mod = Module(buf, filename) mod = Module(buf, filename)
try:
mod.compile(display) mod.compile(display)
except SyntaxError, err:
print "SyntaxError:", err
else:
f = open(filename + "c", "wb") f = open(filename + "c", "wb")
mod.dump(f) mod.dump(f)
f.close() f.close()
@ -61,7 +63,9 @@ class Module:
def compile(self, display=0): def compile(self, display=0):
tree = parse(self.source) tree = parse(self.source)
gen = ModuleCodeGenerator(self.filename, tree) misc.set_filename(self.filename, tree)
syntax.check(tree)
gen = ModuleCodeGenerator(tree)
if display: if display:
import pprint import pprint
print pprint.pprint(tree) print pprint.pprint(tree)
@ -149,12 +153,11 @@ class CodeGenerator:
__initialized = None __initialized = None
class_name = None # provide default for instance variable class_name = None # provide default for instance variable
def __init__(self, filename): def __init__(self):
if self.__initialized is None: if self.__initialized is None:
self.initClass() self.initClass()
self.__class__.__initialized = 1 self.__class__.__initialized = 1
self.checkClass() self.checkClass()
self.filename = filename
self.locals = misc.Stack() self.locals = misc.Stack()
self.setups = misc.Stack() self.setups = misc.Stack()
self.curStack = 0 self.curStack = 0
@ -306,7 +309,7 @@ class CodeGenerator:
self._visitFuncOrLambda(node, isLambda=1) self._visitFuncOrLambda(node, isLambda=1)
def _visitFuncOrLambda(self, node, isLambda=0): def _visitFuncOrLambda(self, node, isLambda=0):
gen = self.FunctionGen(node, self.filename, self.scopes, isLambda, gen = self.FunctionGen(node, self.scopes, isLambda,
self.class_name, self.get_module()) self.class_name, self.get_module())
walk(node.code, gen) walk(node.code, gen)
gen.finish() gen.finish()
@ -324,7 +327,7 @@ class CodeGenerator:
self.emit('MAKE_FUNCTION', len(node.defaults)) self.emit('MAKE_FUNCTION', len(node.defaults))
def visitClass(self, node): def visitClass(self, node):
gen = self.ClassGen(node, self.filename, self.scopes, gen = self.ClassGen(node, self.scopes,
self.get_module()) self.get_module())
if node.doc: if node.doc:
self.emit('LOAD_CONST', node.doc) self.emit('LOAD_CONST', node.doc)
@ -430,14 +433,14 @@ class CodeGenerator:
def visitBreak(self, node): def visitBreak(self, node):
if not self.setups: if not self.setups:
raise SyntaxError, "'break' outside loop (%s, %d)" % \ raise SyntaxError, "'break' outside loop (%s, %d)" % \
(self.filename, node.lineno) (node.filename, node.lineno)
self.set_lineno(node) self.set_lineno(node)
self.emit('BREAK_LOOP') self.emit('BREAK_LOOP')
def visitContinue(self, node): def visitContinue(self, node):
if not self.setups: if not self.setups:
raise SyntaxError, "'continue' outside loop (%s, %d)" % \ raise SyntaxError, "'continue' outside loop (%s, %d)" % \
(self.filename, node.lineno) (node.filename, node.lineno)
kind, block = self.setups.top() kind, block = self.setups.top()
if kind == LOOP: if kind == LOOP:
self.set_lineno(node) self.set_lineno(node)
@ -454,12 +457,12 @@ class CodeGenerator:
break break
if kind != LOOP: if kind != LOOP:
raise SyntaxError, "'continue' outside loop (%s, %d)" % \ raise SyntaxError, "'continue' outside loop (%s, %d)" % \
(self.filename, node.lineno) (node.filename, node.lineno)
self.emit('CONTINUE_LOOP', loop_block) self.emit('CONTINUE_LOOP', loop_block)
self.nextBlock() self.nextBlock()
elif kind == END_FINALLY: elif kind == END_FINALLY:
msg = "'continue' not allowed inside 'finally' clause (%s, %d)" msg = "'continue' not allowed inside 'finally' clause (%s, %d)"
raise SyntaxError, msg % (self.filename, node.lineno) raise SyntaxError, msg % (node.filename, node.lineno)
def visitTest(self, node, jump): def visitTest(self, node, jump):
end = self.newBlock() end = self.newBlock()
@ -1085,10 +1088,10 @@ class ModuleCodeGenerator(NestedScopeMixin, CodeGenerator):
scopes = None scopes = None
def __init__(self, filename, tree): def __init__(self, tree):
self.graph = pyassem.PyFlowGraph("<module>", filename) self.graph = pyassem.PyFlowGraph("<module>", tree.filename)
self.futures = future.find_futures(tree) self.futures = future.find_futures(tree)
self.__super_init(filename) self.__super_init()
walk(tree, self) walk(tree, self)
def get_module(self): def get_module(self):
@ -1098,7 +1101,7 @@ class AbstractFunctionCode:
optimized = 1 optimized = 1
lambdaCount = 0 lambdaCount = 0
def __init__(self, func, filename, scopes, isLambda, class_name, mod): def __init__(self, func, scopes, isLambda, class_name, mod):
self.class_name = class_name self.class_name = class_name
self.module = mod self.module = mod
if isLambda: if isLambda:
@ -1108,10 +1111,10 @@ class AbstractFunctionCode:
else: else:
name = func.name name = func.name
args, hasTupleArg = generateArgList(func.argnames) args, hasTupleArg = generateArgList(func.argnames)
self.graph = pyassem.PyFlowGraph(name, filename, args, self.graph = pyassem.PyFlowGraph(name, func.filename, args,
optimized=1) optimized=1)
self.isLambda = isLambda self.isLambda = isLambda
self.super_init(filename) self.super_init()
if not isLambda and func.doc: if not isLambda and func.doc:
self.setDocstring(func.doc) self.setDocstring(func.doc)
@ -1162,10 +1165,10 @@ class FunctionCodeGenerator(NestedScopeMixin, AbstractFunctionCode,
__super_init = AbstractFunctionCode.__init__ __super_init = AbstractFunctionCode.__init__
def __init__(self, func, filename, scopes, isLambda, class_name, mod): def __init__(self, func, scopes, isLambda, class_name, mod):
self.scopes = scopes self.scopes = scopes
self.scope = scopes[func] self.scope = scopes[func]
self.__super_init(func, filename, scopes, isLambda, class_name, mod) self.__super_init(func, scopes, isLambda, class_name, mod)
self.graph.setFreeVars(self.scope.get_free_vars()) self.graph.setFreeVars(self.scope.get_free_vars())
self.graph.setCellVars(self.scope.get_cell_vars()) self.graph.setCellVars(self.scope.get_cell_vars())
if self.graph.checkFlag(CO_GENERATOR_ALLOWED): if self.graph.checkFlag(CO_GENERATOR_ALLOWED):
@ -1174,12 +1177,12 @@ class FunctionCodeGenerator(NestedScopeMixin, AbstractFunctionCode,
class AbstractClassCode: class AbstractClassCode:
def __init__(self, klass, filename, scopes, module): def __init__(self, klass, scopes, module):
self.class_name = klass.name self.class_name = klass.name
self.module = module self.module = module
self.graph = pyassem.PyFlowGraph(klass.name, filename, self.graph = pyassem.PyFlowGraph(klass.name, klass.filename,
optimized=0, klass=1) optimized=0, klass=1)
self.super_init(filename) self.super_init()
lnf = walk(klass.code, self.NameFinder(), verbose=0) lnf = walk(klass.code, self.NameFinder(), verbose=0)
self.locals.push(lnf.getLocals()) self.locals.push(lnf.getLocals())
self.graph.setFlag(CO_NEWLOCALS) self.graph.setFlag(CO_NEWLOCALS)
@ -1200,10 +1203,10 @@ class ClassCodeGenerator(NestedScopeMixin, AbstractClassCode, CodeGenerator):
__super_init = AbstractClassCode.__init__ __super_init = AbstractClassCode.__init__
def __init__(self, klass, filename, scopes, module): def __init__(self, klass, scopes, module):
self.scopes = scopes self.scopes = scopes
self.scope = scopes[klass] self.scope = scopes[klass]
self.__super_init(klass, filename, scopes, module) self.__super_init(klass, scopes, module)
self.graph.setFreeVars(self.scope.get_free_vars()) self.graph.setFreeVars(self.scope.get_free_vars())
self.graph.setCellVars(self.scope.get_cell_vars()) self.graph.setCellVars(self.scope.get_cell_vars())
## self.graph.setFlag(CO_NESTED) ## self.graph.setFlag(CO_NESTED)

45
Lib/compiler/syntax.py Normal file
View File

@ -0,0 +1,45 @@
"""Check for errs in the AST.
The Python parser does not catch all syntax errors. Others, like
assignments with invalid targets, are caught in the code generation
phase.
The compiler package catches some errors in the transformer module.
But it seems clearer to write checkers that use the AST to detect
errors.
"""
from compiler import ast, walk
def check(tree, multi=None):
v = SyntaxErrorChecker(multi)
walk(tree, v)
return v.errors
class SyntaxErrorChecker:
"""A visitor to find syntax errors in the AST."""
def __init__(self, multi=None):
"""Create new visitor object.
If optional argument multi is not None, then print messages
for each error rather than raising a SyntaxError for the
first.
"""
self.multi = multi
self.errors = 0
def error(self, node, msg):
self.errors = self.errors + 1
if self.multi is not None:
print "%s:%s: %s" % (node.filename, node.lineno, msg)
else:
raise SyntaxError, "%s (%s:%s)" % (msg, node.filename, node.lineno)
def visitAssign(self, node):
# the transformer module handles many of these
for target in node.nodes:
if isinstance(target, ast.AssList):
if target.lineno is None:
target.lineno = node.lineno
self.error(target, "can't assign to list comprehension")

View File

@ -8,7 +8,7 @@ import sys
import types import types
from cStringIO import StringIO from cStringIO import StringIO
from compiler import ast, parse, walk from compiler import ast, parse, walk, syntax
from compiler import pyassem, misc, future, symbols from compiler import pyassem, misc, future, symbols
from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL from compiler.consts import SC_LOCAL, SC_GLOBAL, SC_FREE, SC_CELL
from compiler.consts import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,\ from compiler.consts import CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS,\
@ -41,14 +41,16 @@ class BlockStack(misc.Stack):
self.__super_init(self) self.__super_init(self)
self.loop = None self.loop = None
def compile(filename, display=0): def compile(filename, display=0):
f = open(filename) f = open(filename)
buf = f.read() buf = f.read()
f.close() f.close()
mod = Module(buf, filename) mod = Module(buf, filename)
try:
mod.compile(display) mod.compile(display)
except SyntaxError, err:
print "SyntaxError:", err
else:
f = open(filename + "c", "wb") f = open(filename + "c", "wb")
mod.dump(f) mod.dump(f)
f.close() f.close()
@ -61,7 +63,9 @@ class Module:
def compile(self, display=0): def compile(self, display=0):
tree = parse(self.source) tree = parse(self.source)
gen = ModuleCodeGenerator(self.filename, tree) misc.set_filename(self.filename, tree)
syntax.check(tree)
gen = ModuleCodeGenerator(tree)
if display: if display:
import pprint import pprint
print pprint.pprint(tree) print pprint.pprint(tree)
@ -149,12 +153,11 @@ class CodeGenerator:
__initialized = None __initialized = None
class_name = None # provide default for instance variable class_name = None # provide default for instance variable
def __init__(self, filename): def __init__(self):
if self.__initialized is None: if self.__initialized is None:
self.initClass() self.initClass()
self.__class__.__initialized = 1 self.__class__.__initialized = 1
self.checkClass() self.checkClass()
self.filename = filename
self.locals = misc.Stack() self.locals = misc.Stack()
self.setups = misc.Stack() self.setups = misc.Stack()
self.curStack = 0 self.curStack = 0
@ -306,7 +309,7 @@ class CodeGenerator:
self._visitFuncOrLambda(node, isLambda=1) self._visitFuncOrLambda(node, isLambda=1)
def _visitFuncOrLambda(self, node, isLambda=0): def _visitFuncOrLambda(self, node, isLambda=0):
gen = self.FunctionGen(node, self.filename, self.scopes, isLambda, gen = self.FunctionGen(node, self.scopes, isLambda,
self.class_name, self.get_module()) self.class_name, self.get_module())
walk(node.code, gen) walk(node.code, gen)
gen.finish() gen.finish()
@ -324,7 +327,7 @@ class CodeGenerator:
self.emit('MAKE_FUNCTION', len(node.defaults)) self.emit('MAKE_FUNCTION', len(node.defaults))
def visitClass(self, node): def visitClass(self, node):
gen = self.ClassGen(node, self.filename, self.scopes, gen = self.ClassGen(node, self.scopes,
self.get_module()) self.get_module())
if node.doc: if node.doc:
self.emit('LOAD_CONST', node.doc) self.emit('LOAD_CONST', node.doc)
@ -430,14 +433,14 @@ class CodeGenerator:
def visitBreak(self, node): def visitBreak(self, node):
if not self.setups: if not self.setups:
raise SyntaxError, "'break' outside loop (%s, %d)" % \ raise SyntaxError, "'break' outside loop (%s, %d)" % \
(self.filename, node.lineno) (node.filename, node.lineno)
self.set_lineno(node) self.set_lineno(node)
self.emit('BREAK_LOOP') self.emit('BREAK_LOOP')
def visitContinue(self, node): def visitContinue(self, node):
if not self.setups: if not self.setups:
raise SyntaxError, "'continue' outside loop (%s, %d)" % \ raise SyntaxError, "'continue' outside loop (%s, %d)" % \
(self.filename, node.lineno) (node.filename, node.lineno)
kind, block = self.setups.top() kind, block = self.setups.top()
if kind == LOOP: if kind == LOOP:
self.set_lineno(node) self.set_lineno(node)
@ -454,12 +457,12 @@ class CodeGenerator:
break break
if kind != LOOP: if kind != LOOP:
raise SyntaxError, "'continue' outside loop (%s, %d)" % \ raise SyntaxError, "'continue' outside loop (%s, %d)" % \
(self.filename, node.lineno) (node.filename, node.lineno)
self.emit('CONTINUE_LOOP', loop_block) self.emit('CONTINUE_LOOP', loop_block)
self.nextBlock() self.nextBlock()
elif kind == END_FINALLY: elif kind == END_FINALLY:
msg = "'continue' not allowed inside 'finally' clause (%s, %d)" msg = "'continue' not allowed inside 'finally' clause (%s, %d)"
raise SyntaxError, msg % (self.filename, node.lineno) raise SyntaxError, msg % (node.filename, node.lineno)
def visitTest(self, node, jump): def visitTest(self, node, jump):
end = self.newBlock() end = self.newBlock()
@ -1085,10 +1088,10 @@ class ModuleCodeGenerator(NestedScopeMixin, CodeGenerator):
scopes = None scopes = None
def __init__(self, filename, tree): def __init__(self, tree):
self.graph = pyassem.PyFlowGraph("<module>", filename) self.graph = pyassem.PyFlowGraph("<module>", tree.filename)
self.futures = future.find_futures(tree) self.futures = future.find_futures(tree)
self.__super_init(filename) self.__super_init()
walk(tree, self) walk(tree, self)
def get_module(self): def get_module(self):
@ -1098,7 +1101,7 @@ class AbstractFunctionCode:
optimized = 1 optimized = 1
lambdaCount = 0 lambdaCount = 0
def __init__(self, func, filename, scopes, isLambda, class_name, mod): def __init__(self, func, scopes, isLambda, class_name, mod):
self.class_name = class_name self.class_name = class_name
self.module = mod self.module = mod
if isLambda: if isLambda:
@ -1108,10 +1111,10 @@ class AbstractFunctionCode:
else: else:
name = func.name name = func.name
args, hasTupleArg = generateArgList(func.argnames) args, hasTupleArg = generateArgList(func.argnames)
self.graph = pyassem.PyFlowGraph(name, filename, args, self.graph = pyassem.PyFlowGraph(name, func.filename, args,
optimized=1) optimized=1)
self.isLambda = isLambda self.isLambda = isLambda
self.super_init(filename) self.super_init()
if not isLambda and func.doc: if not isLambda and func.doc:
self.setDocstring(func.doc) self.setDocstring(func.doc)
@ -1162,10 +1165,10 @@ class FunctionCodeGenerator(NestedScopeMixin, AbstractFunctionCode,
__super_init = AbstractFunctionCode.__init__ __super_init = AbstractFunctionCode.__init__
def __init__(self, func, filename, scopes, isLambda, class_name, mod): def __init__(self, func, scopes, isLambda, class_name, mod):
self.scopes = scopes self.scopes = scopes
self.scope = scopes[func] self.scope = scopes[func]
self.__super_init(func, filename, scopes, isLambda, class_name, mod) self.__super_init(func, scopes, isLambda, class_name, mod)
self.graph.setFreeVars(self.scope.get_free_vars()) self.graph.setFreeVars(self.scope.get_free_vars())
self.graph.setCellVars(self.scope.get_cell_vars()) self.graph.setCellVars(self.scope.get_cell_vars())
if self.graph.checkFlag(CO_GENERATOR_ALLOWED): if self.graph.checkFlag(CO_GENERATOR_ALLOWED):
@ -1174,12 +1177,12 @@ class FunctionCodeGenerator(NestedScopeMixin, AbstractFunctionCode,
class AbstractClassCode: class AbstractClassCode:
def __init__(self, klass, filename, scopes, module): def __init__(self, klass, scopes, module):
self.class_name = klass.name self.class_name = klass.name
self.module = module self.module = module
self.graph = pyassem.PyFlowGraph(klass.name, filename, self.graph = pyassem.PyFlowGraph(klass.name, klass.filename,
optimized=0, klass=1) optimized=0, klass=1)
self.super_init(filename) self.super_init()
lnf = walk(klass.code, self.NameFinder(), verbose=0) lnf = walk(klass.code, self.NameFinder(), verbose=0)
self.locals.push(lnf.getLocals()) self.locals.push(lnf.getLocals())
self.graph.setFlag(CO_NEWLOCALS) self.graph.setFlag(CO_NEWLOCALS)
@ -1200,10 +1203,10 @@ class ClassCodeGenerator(NestedScopeMixin, AbstractClassCode, CodeGenerator):
__super_init = AbstractClassCode.__init__ __super_init = AbstractClassCode.__init__
def __init__(self, klass, filename, scopes, module): def __init__(self, klass, scopes, module):
self.scopes = scopes self.scopes = scopes
self.scope = scopes[klass] self.scope = scopes[klass]
self.__super_init(klass, filename, scopes, module) self.__super_init(klass, scopes, module)
self.graph.setFreeVars(self.scope.get_free_vars()) self.graph.setFreeVars(self.scope.get_free_vars())
self.graph.setCellVars(self.scope.get_cell_vars()) self.graph.setCellVars(self.scope.get_cell_vars())
## self.graph.setFlag(CO_NESTED) ## self.graph.setFlag(CO_NESTED)

View File

@ -0,0 +1,45 @@
"""Check for errs in the AST.
The Python parser does not catch all syntax errors. Others, like
assignments with invalid targets, are caught in the code generation
phase.
The compiler package catches some errors in the transformer module.
But it seems clearer to write checkers that use the AST to detect
errors.
"""
from compiler import ast, walk
def check(tree, multi=None):
v = SyntaxErrorChecker(multi)
walk(tree, v)
return v.errors
class SyntaxErrorChecker:
"""A visitor to find syntax errors in the AST."""
def __init__(self, multi=None):
"""Create new visitor object.
If optional argument multi is not None, then print messages
for each error rather than raising a SyntaxError for the
first.
"""
self.multi = multi
self.errors = 0
def error(self, node, msg):
self.errors = self.errors + 1
if self.multi is not None:
print "%s:%s: %s" % (node.filename, node.lineno, msg)
else:
raise SyntaxError, "%s (%s:%s)" % (msg, node.filename, node.lineno)
def visitAssign(self, node):
# the transformer module handles many of these
for target in node.nodes:
if isinstance(target, ast.AssList):
if target.lineno is None:
target.lineno = node.lineno
self.error(target, "can't assign to list comprehension")