diff --git a/NEWS b/NEWS index b2a7106..2a26aed 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,13 @@ +Changes from Hy 0.9.4 + + [ Syntax Fixes ] + + * `try' now accepts `else': (JD) + (try BODY + (except [] BODY) + (else BODY)) + + Changes from Hy 0.9.4 [ Syntax Fixes ] diff --git a/hy/compiler.py b/hy/compiler.py index 249a0cc..c4952cf 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -38,7 +38,21 @@ import sys class HyCompileError(HyError): - pass + def __init__(self, exception, + start_line=0, start_column=0): + self.exception = exception + self.start_line = start_line + self.start_column = start_column + + def __str__(self): + if self.start_line == 0: + return("Internal Compiler Bug\n⤷ %s: %s" + % (self.exception.__class__.__name__, + self.exception)) + return ("Compilation error at line %d, column %d\n%s: %s" + % (self.start_line, self.start_column, + self.exception.__class__.__name__, + self.exception)) _compile_table = {} @@ -111,12 +125,15 @@ class HyASTCompiler(object): for _type in _compile_table: if type(tree) == _type: return _compile_table[_type](self, tree) + except HyCompileError: + # compile calls compile, so we're going to have multiple raise + # nested; so let's re-raise this exception, let's not wrap it in + # another HyCompileError! + raise except Exception as e: - err = HyCompileError(str(e)) - err.exception = e - err.start_line = getattr(e, "start_line", None) - err.start_column = getattr(e, "start_column", None) - raise err + raise HyCompileError(exception=e, + start_line=getattr(e, "start_line", 0), + start_column=getattr(e, "start_column", 0)) raise HyCompileError("Unknown type - `%s'" % (str(type(tree)))) @@ -183,10 +200,10 @@ class HyASTCompiler(object): @builds("throw") @builds("raise") - @checkargs(min=1) + @checkargs(max=1) def compile_throw_expression(self, expr): expr.pop(0) - exc = self.compile(expr.pop(0)) + exc = self.compile(expr.pop(0)) if expr else None return ast.Raise( lineno=expr.start_line, col_offset=expr.start_column, @@ -215,6 +232,7 @@ class HyASTCompiler(object): expr.start_line, expr.start_column) + orelse = [] if len(expr) == 0: # (try) or (try body) handlers = [ast.ExceptHandler( @@ -225,8 +243,27 @@ class HyASTCompiler(object): body=[ast.Pass(lineno=expr.start_line, col_offset=expr.start_column)])] else: - # (try body except except…) - handlers = [self.compile(s) for s in expr] + 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")): + handlers.append(self.compile(e)) + elif e[0] == HySymbol("else"): + if orelse: + raise TypeError( + "`try' cannot have more than one `else'") + else: + orelse = self._code_branch(self.compile(e[1:]), + e.start_line, + e.start_column) + else: + raise TypeError("Unknown expression in `try'") + + if handlers == []: + raise TypeError( + "`try' must have at least `except' or `finally'") return Try( lineno=expr.start_line, @@ -234,17 +271,17 @@ class HyASTCompiler(object): body=body, handlers=handlers, finalbody=[], - orelse=[]) + orelse=orelse) @builds("catch") @builds("except") def compile_catch_expression(self, expr): - expr.pop(0) # catch + catch = expr.pop(0) # catch try: exceptions = expr.pop(0) except IndexError: - exceptions = [] + exceptions = HyList() # exceptions catch should be either: # [[list of exceptions]] # or @@ -255,8 +292,10 @@ class HyASTCompiler(object): # [exception] # or # [] + if not isinstance(exceptions, HyList): + raise TypeError("`%s' exceptions list is not a list" % catch) if len(exceptions) > 2: - raise TypeError("`catch' exceptions list is too long") + raise TypeError("`%s' exceptions list is too long" % catch) # [variable [list of exceptions]] # let's pop variable and use it as name @@ -294,7 +333,7 @@ class HyASTCompiler(object): elif isinstance(exceptions_list, HySymbol): _type = self.compile(exceptions_list) else: - raise TypeError("`catch' needs a valid exception list to catch") + raise TypeError("`%s' needs a valid exception list" % catch) body = self._code_branch([self.compile(x) for x in expr], expr.start_line, diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index f839feb..0bed73f 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -94,30 +94,40 @@ def test_ast_good_do(): def test_ast_good_throw(): "Make sure AST can compile valid throw" + hy_compile(tokenize("(throw)")) hy_compile(tokenize("(throw 1)")) def test_ast_bad_throw(): "Make sure AST can't compile invalid throw" - cant_compile("(throw)") + cant_compile("(raise 1 2 3)") def test_ast_good_raise(): "Make sure AST can compile valid raise" + hy_compile(tokenize("(raise)")) hy_compile(tokenize("(raise 1)")) def test_ast_bad_raise(): "Make sure AST can't compile invalid raise" - cant_compile("(raise)") + cant_compile("(raise 1 2 3)") def test_ast_good_try(): "Make sure AST can compile valid try" hy_compile(tokenize("(try)")) hy_compile(tokenize("(try 1)")) - hy_compile(tokenize("(try 1 bla)")) - hy_compile(tokenize("(try 1 bla bla)")) + hy_compile(tokenize("(try 1 (except) (else 1))")) + hy_compile(tokenize("(try 1 (else 1) (except))")) + + +def test_ast_bad_try(): + "Make sure AST can't compile invalid try" + cant_compile("(try 1 bla)") + cant_compile("(try 1 bla bla)") + cant_compile("(try (do) (else 1) (else 2))") + cant_compile("(try 1 (else 1))") def test_ast_good_catch(): @@ -134,6 +144,7 @@ def test_ast_good_catch(): def test_ast_bad_catch(): "Make sure AST can't compile invalid catch" cant_compile("(catch 1)") + cant_compile("(catch \"A\")") cant_compile("(catch [1 3])") cant_compile("(catch [x [FooBar] BarBar])") diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index ef2de95..c835605 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -178,6 +178,26 @@ (try (pass) (except [IOError]) (except)) + ;; Test correct (raise) + (let [[passed false]] + (try + (try + (raise IndexError) + (except [IndexError] (raise))) + (except [IndexError] + (setv passed true))) + (assert passed)) + + ;; Test incorrect (raise) + (let [[passed false]] + (try + (raise) + ;; Python 2 raises TypeError + ;; Python 3 raises RuntimeError + (except [[TypeError RuntimeError]] + (setv passed true))) + (assert passed)) + (try (raise (KeyError)) (catch [[IOError]] (assert false)) @@ -235,7 +255,39 @@ (print foobar42ofthebaz) (catch [] (setv foobar42ofthebaz 42) - (assert (= foobar42ofthebaz 42))))) + (assert (= foobar42ofthebaz 42)))) + + (let [[passed false]] + (try + (try (pass) (except) (else (bla))) + (except [NameError] (setv passed true))) + (assert passed)) + + (let [[x 0]] + (try + (raise IOError) + (except [IOError] + (setv x 45)) + (else (setv x 44))) + (assert (= x 45))) + + (let [[x 0]] + (try + (raise KeyError) + (except [] + (setv x 45)) + (else (setv x 44))) + (assert (= x 45))) + + (let [[x 0]] + (try + (try + (raise KeyError) + (except [IOError] + (setv x 45)) + (else (setv x 44))) + (except)) + (assert (= x 0)))) (defn test-earmuffs [] "NATIVE: Test earmuffs"