From 772927c53d9fa71fef52a0ec91e3b1a487578939 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Wed, 8 May 2013 18:49:07 +0200 Subject: [PATCH 1/4] Drop the bare names that were sprinkled everywhere in the generated AST. This closes #162 We make sure not to drop *explicitly written* bare names. --- hy/compiler.py | 6 +++++- tests/compilers/test_compiler.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/hy/compiler.py b/hy/compiler.py index dd6af76..4ec6eb0 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -193,9 +193,13 @@ class Result(object): This is useful when we want to use the stored expression in a statement context (for instance in a code branch). + We drop ast.Names if they are appended to statements, as they + can't have any side effect. "Bare" names still get converted to + statements. + If there is no expression context, return an empty result. """ - if self.expr: + if self.expr and not (isinstance(self.expr, ast.Name) and self.stmts): return Result() + ast.Expr(lineno=self.expr.lineno, col_offset=self.expr.col_offset, value=self.expr) diff --git a/tests/compilers/test_compiler.py b/tests/compilers/test_compiler.py index b5b3846..9291ffd 100644 --- a/tests/compilers/test_compiler.py +++ b/tests/compilers/test_compiler.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Julien Danjou +# Copyright (c) 2013 Nicolas Dandrimont # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -29,6 +30,7 @@ else: from hy.models.expression import HyExpression from hy.models.list import HyList +from hy.models.symbol import HySymbol from hy.compiler import HyASTCompiler @@ -63,3 +65,24 @@ class HyASTCompilerTest(unittest.TestCase): self.assertIsInstance(stmt.body[0], ast.Pass) self.assertIsInstance(ret.expr, ast.Name) + + def test_compiler_bare_names(self): + """Check that the compiler doesn't drop bare names from code branches""" + ret = self.c.compile(self._make_expression(HySymbol("do"), + HySymbol("a"), + HySymbol("b"), + HySymbol("c"))) + + # We expect two statements and a final expr. + self.assertEqual(len(ret.stmts), 2) + stmt = ret.stmts[0] + self.assertIsInstance(stmt, ast.Expr) + self.assertIsInstance(stmt.value, ast.Name) + self.assertEqual(stmt.value.id, "a") + stmt = ret.stmts[1] + self.assertIsInstance(stmt, ast.Expr) + self.assertIsInstance(stmt.value, ast.Name) + self.assertEqual(stmt.value.id, "b") + expr = ret.expr + self.assertIsInstance(expr, ast.Name) + self.assertEqual(expr.id, "c") From 5dbf6c6ca93abb69284f42d20e00e320e316f8e7 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Wed, 8 May 2013 21:10:30 +0200 Subject: [PATCH 2/4] Add tests for &key arguments in functions --- tests/compilers/test_ast.py | 5 +++++ tests/native_tests/language.hy | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 958c7b8..11d4915 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -350,6 +350,11 @@ def test_ast_non_kwapplyable(): pass +def test_ast_lambda_lists(): + """Ensure the compiler chokes on invalid lambda-lists""" + cant_compile('(fn [&key {"a" b} &key {"foo" bar}] [a foo])') + + def test_ast_print(): code = hy_compile(tokenize("(print \"foo\")")).body[0] diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index d4ec580..9abc43c 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -637,6 +637,14 @@ (assert (= (foo 10 20 30) [10 (, 20 30) {}]))) +(defn test-key-arguments [] + "NATIVE: test &key function arguments" + (defn foo [&key {"a" None "b" 1}] [a b]) + (assert (= (foo) [None 1])) + (assert (= (kwapply (foo) {"a" 2}) [2 1])) + (assert (= (kwapply (foo) {"b" 42}) [None 42]))) + + (defn test-quoted-hoistable [] "NATIVE: test quoted hoistable" (setf f (quote (if true true true))) From 4e3f8429723e91c7fc967d3e732e0aacf303a78b Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Wed, 8 May 2013 20:56:16 +0200 Subject: [PATCH 3/4] Add &optional arguments. Python doesn't really have that concept, so make them clash with &key arguments. --- hy/compiler.py | 20 ++++++++++++++++---- tests/compilers/test_ast.py | 2 ++ tests/native_tests/language.hy | 8 ++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index dd6af76..776e304 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -402,6 +402,9 @@ class HyASTCompiler(object): if expr == "&rest" and lambda_keyword is None: lambda_keyword = expr elif expr == "&optional": + if len(defaults) > 0: + raise HyCompileError("There can only be &optional " + "arguments or one &key argument") lambda_keyword = expr elif expr == "&key": lambda_keyword = expr @@ -427,8 +430,8 @@ class HyASTCompiler(object): "argument") else: if len(defaults) > 0: - raise HyCompileError("There can only be " - "one &key argument") + raise HyCompileError("There can only be &optional " + "arguments or one &key argument") # As you can see, Python has a funny way of # defining keyword arguments. for k, v in expr.items(): @@ -436,8 +439,17 @@ class HyASTCompiler(object): ret += self.compile(v) defaults.append(ret.force_expr) elif lambda_keyword == "&optional": - # not implemented yet. - pass + if isinstance(expr, HyList): + if not len(expr) == 2: + raise TypeError("optional args should be bare names " + "or 2-item lists") + k, v = expr + else: + k = expr + v = HySymbol("None").replace(k) + args.append(k) + ret += self.compile(v) + defaults.append(ret.force_expr) elif lambda_keyword == "&kwargs": if kwargs: raise HyCompileError("There can only be one " diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 11d4915..8f11f66 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -353,6 +353,8 @@ def test_ast_non_kwapplyable(): def test_ast_lambda_lists(): """Ensure the compiler chokes on invalid lambda-lists""" cant_compile('(fn [&key {"a" b} &key {"foo" bar}] [a foo])') + cant_compile('(fn [&optional a &key {"foo" bar}] [a foo])') + cant_compile('(fn [&optional [a b c]] a)') def test_ast_print(): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 9abc43c..330824d 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -645,6 +645,14 @@ (assert (= (kwapply (foo) {"b" 42}) [None 42]))) +(defn test-optional-arguments [] + "NATIVE: test &optional function arguments" + (defn foo [a b &optional c [d 42]] [a b c d]) + (assert (= (foo 1 2) [1 2 None 42])) + (assert (= (foo 1 2 3) [1 2 3 42])) + (assert (= (foo 1 2 3 4) [1 2 3 4]))) + + (defn test-quoted-hoistable [] "NATIVE: test quoted hoistable" (setf f (quote (if true true true))) From 72461a5cdbbea2fe5841bb7587d1d08c7d68ff92 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 8 May 2013 20:07:38 -0400 Subject: [PATCH 4/4] style tweak --- tests/compilers/test_compiler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/compilers/test_compiler.py b/tests/compilers/test_compiler.py index 9291ffd..5f93144 100644 --- a/tests/compilers/test_compiler.py +++ b/tests/compilers/test_compiler.py @@ -67,7 +67,9 @@ class HyASTCompilerTest(unittest.TestCase): self.assertIsInstance(ret.expr, ast.Name) def test_compiler_bare_names(self): - """Check that the compiler doesn't drop bare names from code branches""" + """ + Check that the compiler doesn't drop bare names from code branches + """ ret = self.c.compile(self._make_expression(HySymbol("do"), HySymbol("a"), HySymbol("b"),