Merge branch 'jd/finally' of git://github.com/jd/hy into pr-94

This commit is contained in:
Paul R. Tagliamonte 2013-04-14 21:57:11 -04:00
commit 7b4d4fb371
3 changed files with 137 additions and 60 deletions

View File

@ -39,26 +39,36 @@ from collections import defaultdict
import codecs import codecs
import ast import ast
import sys import sys
import traceback
class HyCompileError(HyError): class HyCompileError(HyError):
def __init__(self, exception, def __init__(self, exception, traceback=None):
start_line=0, start_column=0):
self.exception = exception self.exception = exception
self.start_line = start_line self.traceback = traceback
self.start_column = start_column
def __str__(self): def __str__(self):
if self.start_line == 0: if isinstance(self.exception, HyTypeError):
return("Internal Compiler Bug\n%s: %s" return str(self.exception)
% (self.exception.__class__.__name__, if self.traceback:
self.exception)) tb = "".join(traceback.format_tb(self.traceback)).strip()
return ("Compilation error at line %d, column %d\n%s: %s" else:
% (self.start_line, self.start_column, tb = "No traceback available. 😟"
self.exception.__class__.__name__, return("Internal Compiler Bug 😱\n%s: %s\nCompilation traceback:\n%s"
self.exception)) % (self.exception.__class__.__name__,
self.exception, tb))
class HyTypeError(TypeError):
def __init__(self, expression, message):
super(HyTypeError, self).__init__(message)
self.expression = expression
def __str__(self):
return (self.message + " (line %s, column %d)"
% (self.expression.start_line,
self.expression.start_column))
_compile_table = {} _compile_table = {}
@ -87,10 +97,9 @@ def builds(_type):
def _raise_wrong_args_number(expression, error): def _raise_wrong_args_number(expression, error):
err = TypeError(error % (expression.pop(0), len(expression))) raise HyTypeError(expression,
err.start_line = expression.start_line error % (expression.pop(0),
err.start_column = expression.start_column len(expression)))
raise err
def checkargs(exact=None, min=None, max=None): def checkargs(exact=None, min=None, max=None):
@ -134,15 +143,10 @@ class HyASTCompiler(object):
# another HyCompileError! # another HyCompileError!
raise raise
except Exception as e: except Exception as e:
if isinstance(e, HyError): raise HyCompileError(e, sys.exc_info()[2])
raise HyCompileError(
exception=e,
start_line=getattr(e, "start_line", 0),
start_column=getattr(e, "start_column", 0))
raise HyCompileError(exception=e)
raise HyCompileError( raise HyCompileError(
"Unknown type - `%s' - %s" % (str(type(tree)), tree)) Exception("Unknown type: `%s'" % (str(type(tree)))))
def _mangle_branch(self, tree, start_line, start_column): def _mangle_branch(self, tree, start_line, start_column):
tree = list(flatten_literal_list(tree)) tree = list(flatten_literal_list(tree))
@ -238,12 +242,6 @@ class HyASTCompiler(object):
def compile_try_expression(self, expr): def compile_try_expression(self, expr):
expr.pop(0) # try expr.pop(0) # try
if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
# Python 3.3 features a rename of TryExcept to Try.
Try = ast.Try
else:
Try = ast.TryExcept
try: try:
body = expr.pop(0) body = expr.pop(0)
except IndexError: except IndexError:
@ -255,8 +253,45 @@ class HyASTCompiler(object):
expr.start_column) expr.start_column)
orelse = [] orelse = []
if len(expr) == 0: finalbody = []
# (try) or (try body) handlers = []
for e in expr:
if not len(e):
raise HyTypeError(e, "Empty list not allowed in `try'")
if e[0] in (HySymbol("except"), HySymbol("catch")):
handlers.append(self.compile(e))
elif e[0] == HySymbol("else"):
if orelse:
raise HyTypeError(
e,
"`try' cannot have more than one `else'")
else:
orelse = self._code_branch(self.compile(e[1:]),
e.start_line,
e.start_column)
elif e[0] == HySymbol("finally"):
if finalbody:
raise HyTypeError(
e,
"`try' cannot have more than one `finally'")
else:
finalbody = self._code_branch(self.compile(e[1:]),
e.start_line,
e.start_column)
else:
raise HyTypeError(e, "Unknown expression in `try'")
# Using (else) without (except) is verboten!
if orelse and not handlers:
raise HyTypeError(
e,
"`try' cannot have `else' without `except'")
# (try) or (try BODY)
# Generate a default handler for Python >= 3.3 and pypy
if not handlers and not finalbody and not orelse:
handlers = [ast.ExceptHandler( handlers = [ast.ExceptHandler(
lineno=expr.start_line, lineno=expr.start_line,
col_offset=expr.start_column, col_offset=expr.start_column,
@ -264,35 +299,41 @@ class HyASTCompiler(object):
name=None, name=None,
body=[ast.Pass(lineno=expr.start_line, body=[ast.Pass(lineno=expr.start_line,
col_offset=expr.start_column)])] col_offset=expr.start_column)])]
else:
handlers = []
for e in expr:
if not len(e):
raise TypeError("Empty list not allowed in `try'")
if e[0] in (HySymbol("except"), HySymbol("catch")): if sys.version_info[0] >= 3 and sys.version_info[1] >= 3:
handlers.append(self.compile(e)) # Python 3.3 features a merge of TryExcept+TryFinally into Try.
elif e[0] == HySymbol("else"): return ast.Try(
if orelse: lineno=expr.start_line,
raise TypeError( col_offset=expr.start_column,
"`try' cannot have more than one `else'") body=body,
else: handlers=handlers,
orelse = self._code_branch(self.compile(e[1:]), orelse=orelse,
e.start_line, finalbody=finalbody)
e.start_column)
else:
raise TypeError("Unknown expression in `try'")
if handlers == []: if finalbody:
raise TypeError( if handlers:
"`try' must have at least `except' or `finally'") return ast.TryFinally(
lineno=expr.start_line,
col_offset=expr.start_column,
body=[ast.TryExcept(
lineno=expr.start_line,
col_offset=expr.start_column,
handlers=handlers,
body=body,
orelse=orelse)],
finalbody=finalbody)
return Try( return ast.TryFinally(
lineno=expr.start_line,
col_offset=expr.start_column,
body=body,
finalbody=finalbody)
return ast.TryExcept(
lineno=expr.start_line, lineno=expr.start_line,
col_offset=expr.start_column, col_offset=expr.start_column,
body=body,
handlers=handlers, handlers=handlers,
finalbody=[], body=body,
orelse=orelse) orelse=orelse)
@builds("catch") @builds("catch")
@ -315,9 +356,11 @@ class HyASTCompiler(object):
# or # or
# [] # []
if not isinstance(exceptions, HyList): if not isinstance(exceptions, HyList):
raise TypeError("`%s' exceptions list is not a list" % catch) raise HyTypeError(exceptions,
"`%s' exceptions list is not a list" % catch)
if len(exceptions) > 2: if len(exceptions) > 2:
raise TypeError("`%s' exceptions list is too long" % catch) raise HyTypeError(exceptions,
"`%s' exceptions list is too long" % catch)
# [variable [list of exceptions]] # [variable [list of exceptions]]
# let's pop variable and use it as name # let's pop variable and use it as name
@ -355,7 +398,8 @@ class HyASTCompiler(object):
elif isinstance(exceptions_list, HySymbol): elif isinstance(exceptions_list, HySymbol):
_type = self.compile(exceptions_list) _type = self.compile(exceptions_list)
else: else:
raise TypeError("`%s' needs a valid exception list" % catch) raise HyTypeError(exceptions,
"`%s' needs a valid exception list" % catch)
body = self._code_branch([self.compile(x) for x in expr], body = self._code_branch([self.compile(x) for x in expr],
expr.start_line, expr.start_line,
@ -614,7 +658,7 @@ class HyASTCompiler(object):
expr.pop(0) # decorate-with expr.pop(0) # decorate-with
fn = self.compile(expr.pop(-1)) fn = self.compile(expr.pop(-1))
if type(fn) != ast.FunctionDef: if type(fn) != ast.FunctionDef:
raise TypeError("Decorated a non-function") raise HyTypeError(expr, "Decorated a non-function")
fn.decorator_list = [self.compile(x) for x in expr] fn.decorator_list = [self.compile(x) for x in expr]
return fn return fn
@ -625,7 +669,7 @@ class HyASTCompiler(object):
args = expr.pop(0) args = expr.pop(0)
if len(args) > 2 or len(args) < 1: if len(args) > 2 or len(args) < 1:
raise TypeError("with needs [arg (expr)] or [(expr)]") raise HyTypeError(expr, "with needs [arg (expr)] or [(expr)]")
args.reverse() args.reverse()
ctx = self.compile(args.pop(0)) ctx = self.compile(args.pop(0))
@ -699,7 +743,7 @@ class HyASTCompiler(object):
kwargs = expr.pop(0) kwargs = expr.pop(0)
if type(call) != ast.Call: if type(call) != ast.Call:
raise TypeError("kwapplying a non-call") raise HyTypeError(expr, "kwapplying a non-call")
if type(kwargs) != HyDict: if type(kwargs) != HyDict:
raise TypeError("kwapplying with a non-dict") raise TypeError("kwapplying with a non-dict")

View File

@ -123,6 +123,11 @@ def test_ast_good_try():
hy_compile(tokenize("(try 1)")) hy_compile(tokenize("(try 1)"))
hy_compile(tokenize("(try 1 (except) (else 1))")) hy_compile(tokenize("(try 1 (except) (else 1))"))
hy_compile(tokenize("(try 1 (else 1) (except))")) hy_compile(tokenize("(try 1 (else 1) (except))"))
hy_compile(tokenize("(try 1 (finally 1) (except))"))
hy_compile(tokenize("(try 1 (finally 1))"))
hy_compile(tokenize("(try 1 (except) (finally 1))"))
hy_compile(tokenize("(try 1 (except) (finally 1) (else 1))"))
hy_compile(tokenize("(try 1 (except) (else 1) (finally 1))"))
def test_ast_bad_try(): def test_ast_bad_try():

View File

@ -198,6 +198,34 @@
(setv passed true))) (setv passed true)))
(assert passed)) (assert passed))
;; Test (finally)
(let [[passed false]]
(try
(pass)
(finally (setv passed true)))
(assert passed))
;; Test (finally) + (raise)
(let [[passed false]]
(try
(raise Exception)
(except)
(finally (setv passed true)))
(assert passed))
;; Test (finally) + (raise) + (else)
(let [[passed false]
[not-elsed true]]
(try
(raise Exception)
(except)
(else (setv not-elsed false))
(finally (setv passed true)))
(assert passed)
(assert not-elsed))
(try (try
(raise (KeyError)) (raise (KeyError))
(catch [[IOError]] (assert false)) (catch [[IOError]] (assert false))