From 21bbab4e09b3116583431955be683148ddf84fad Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 25 Aug 2015 11:45:20 -0500 Subject: [PATCH 01/15] Remove redundant assignments with nested ifs (closes #842) --- hy/compiler.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index f3cc9ac..e9037be 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -375,6 +375,7 @@ class HyASTCompiler(object): self.anon_var_count = 0 self.imports = defaultdict(set) self.module_name = module_name + self.temp_if = None if not module_name.startswith("hy.core"): # everything in core needs to be explicit. load_stdlib() @@ -1004,8 +1005,16 @@ class HyASTCompiler(object): body = self.compile(expression.pop(0)) orel = Result() + nested = root = False if expression: - orel = self.compile(expression.pop(0)) + orel_expr = expression.pop(0) + if isinstance(orel_expr, HyExpression) and isinstance(orel_expr[0], + HySymbol) and orel_expr[0] == 'if': + # Nested ifs: don't waste temporaries + root = self.temp_if is None + nested = True + self.temp_if = self.temp_if or self.get_anon_var() + orel = self.compile(orel_expr) # We want to hoist the statements from the condition ret = cond @@ -1013,7 +1022,7 @@ class HyASTCompiler(object): if body.stmts or orel.stmts: # We have statements in our bodies # Get a temporary variable for the result storage - var = self.get_anon_var() + var = self.temp_if or self.get_anon_var() name = ast.Name(id=ast_str(var), arg=ast_str(var), ctx=ast.Store(), lineno=expression.start_line, @@ -1026,10 +1035,12 @@ class HyASTCompiler(object): col_offset=expression.start_column) # and of the else clause - orel += ast.Assign(targets=[name], - value=orel.force_expr, - lineno=expression.start_line, - col_offset=expression.start_column) + if not nested or not orel.stmts or (not root and + var != self.temp_if): + orel += ast.Assign(targets=[name], + value=orel.force_expr, + lineno=expression.start_line, + col_offset=expression.start_column) # Then build the if ret += ast.If(test=ret.force_expr, @@ -1052,6 +1063,10 @@ class HyASTCompiler(object): orelse=orel.force_expr, lineno=expression.start_line, col_offset=expression.start_column) + + if root: + self.temp_if = None + return ret @builds("break") From c3190ca07da9258b7bbd5f0bdfbb4583247afc06 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Tue, 22 Sep 2015 12:17:47 -0500 Subject: [PATCH 02/15] Optimize simple cases such as 'if True' --- hy/compiler.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/hy/compiler.py b/hy/compiler.py index e9037be..7ed71a0 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1002,8 +1002,8 @@ class HyASTCompiler(object): def compile_if(self, expression): expression.pop(0) cond = self.compile(expression.pop(0)) - body = self.compile(expression.pop(0)) + orel = Result() nested = root = False if expression: @@ -1016,6 +1016,28 @@ class HyASTCompiler(object): self.temp_if = self.temp_if or self.get_anon_var() orel = self.compile(orel_expr) + if not cond.stmts and isinstance(cond.force_expr, ast.Name): + name = cond.force_expr.id + branch = None + if name == 'True': + branch = body + elif name in ('False', 'None'): + branch = orel + if branch is not None: + if self.temp_if and branch.stmts: + name = ast.Name(id=ast_str(self.temp_if), + arg=ast_str(self.temp_if), + ctx=ast.Store(), + lineno=expression.start_line, + col_offset=expression.start_column) + + branch += ast.Assign(targets=[name], + value=body.force_expr, + lineno=expression.start_line, + col_offset=expression.start_column) + + return branch + # We want to hoist the statements from the condition ret = cond From 0a942a069f17dc93fb71958fa17e0146ffa7c154 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sat, 3 Oct 2015 11:01:48 +0200 Subject: [PATCH 03/15] Support one-arity comparison operators too Comparison operators such as =, !=, <, >, <=, >= should support a one-arity version too, and return true in those cases (except for !=, which returns false). This closes #949. Reported-by: Matthew Egan Odendahl Signed-off-by: Gergely Nagy --- hy/compiler.py | 39 +++++++++++++++++++++++----------- tests/compilers/test_ast.py | 4 ++-- tests/native_tests/language.hy | 15 +++++++++++-- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 4574a3f..3583978 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1774,18 +1774,7 @@ class HyASTCompiler(object): values=[value.force_expr for value in values]) return ret - @builds("=") - @builds("!=") - @builds("<") - @builds("<=") - @builds(">") - @builds(">=") - @builds("is") - @builds("in") - @builds("is_not") - @builds("not_in") - @checkargs(min=2) - def compile_compare_op_expression(self, expression): + def _compile_compare_op_expression(self, expression): ops = {"=": ast.Eq, "!=": ast.NotEq, "<": ast.Lt, "<=": ast.LtE, ">": ast.Gt, ">=": ast.GtE, @@ -1805,6 +1794,32 @@ class HyASTCompiler(object): lineno=e.start_line, col_offset=e.start_column) + @builds("=") + @builds("!=") + @builds("<") + @builds("<=") + @builds(">") + @builds(">=") + @checkargs(min=1) + def compile_compare_op_expression(self, expression): + if len(expression) == 2: + rval = "True" + if expression[0] == "!=": + rval = "False" + return ast.Name(id=rval, + ctx=ast.Load(), + lineno=expression.start_line, + col_offset=expression.start_column) + return self._compile_compare_op_expression(expression) + + @builds("is") + @builds("in") + @builds("is_not") + @builds("not_in") + @checkargs(min=2) + def compile_compare_op_expression_coll(self, expression): + return self._compile_compare_op_expression(expression) + @builds("%") @builds("**") @builds("<<") diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index adb0fa6..3a1f92d 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -468,9 +468,9 @@ def test_ast_unicode_strings(): def test_compile_error(): """Ensure we get compile error in tricky cases""" try: - can_compile("(fn [] (= 1))") + can_compile("(fn [] (in [1 2 3]))") except HyTypeError as e: - assert(e.message == "`=' needs at least 2 arguments, got 1.") + assert(e.message == "`in' needs at least 2 arguments, got 1.") else: assert(False) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 7821e8d..9986ff7 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -208,15 +208,26 @@ (defn test-noteq [] "NATIVE: not eq" - (assert (!= 2 3))) + (assert (!= 2 3)) + (assert (not (!= 1)))) + + +(defn test-eq [] + "NATIVE: eq" + (assert (= 1 1)) + (assert (= 1))) (defn test-numops [] "NATIVE: test numpos" (assert (> 5 4 3 2 1)) + (assert (> 1)) (assert (< 1 2 3 4 5)) + (assert (< 1)) (assert (<= 5 5 5 5 )) - (assert (>= 5 5 5 5 ))) + (assert (<= 1)) + (assert (>= 5 5 5 5 )) + (assert (>= 1))) (defn test-is [] From af6ac92c0a807f9efe20f4a6063f5ea29e301342 Mon Sep 17 00:00:00 2001 From: Tuukka Turto Date: Sat, 3 Oct 2015 13:24:43 +0300 Subject: [PATCH 04/15] Add section about macros in tutorial relates #926 --- docs/tutorial.rst | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index fc5e3ff..cf76538 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -503,6 +503,64 @@ In Hy: address (models.TextField) notes (models.TextField)]) +Macros +====== + +One really powerful feature of Hy are macros. They are small functios that are +used to generate code (or data). When program written in Hy is started, the +macros are executed and their output is placed in program source. After this, +the program starts executing normally. Very simple example: + +.. code-block:: clj + + => (defmacro hello [person] + ... `(print "Hello there," ~person)) + => (Hello "Tuukka") + Hello there, Tuukka + +The thing to notice here is that hello macro doesn't output anything on +screen. Instead it creates piece of code that is then executed and prints on +screen. Macro writes a piece of program that looks like this (provided that +we used "Tuukka" as parameter: + +.. code-block:: clj + + (print "Hello there," Tuukka) + +We can also manipulate code with macros: + +.. code-block:: clj + + => (defmacro rev [code] + ... (let [op (last code) params (list (butlast code))] + ... `(~op ~@params))) + => (rev (1 2 3 +)) + 6 + +The code that was generated with this macro just switched around some the +elements, so by the time program started executing, it actually red: + +.. code-block:: clj + + (+ 1 2 3) + +Sometimes it's nice to have a very short name for macro that doesn't take much +space or use extra parentheses. Reader macros can be pretty useful in these +situations (and since Hy operates well with unicode, we aren't running out of +characters that soon): + +.. code-block:: clj + + => (defreader ↻ [code] + ... (let [op (last code) params (list (butlast code))] + ... `(~op ~@params))) + => #↻(1 2 3 +) + 6 + +Macros are useful when one wished to extend the Hy or write their own +language on top of that. Many features of Hy are macros, like ``when``, +``cond`` and ``->``. + Hy <-> Python interop ===================== From f73c862ffa65d84a9d485b795e33f0bd0a9e067c Mon Sep 17 00:00:00 2001 From: Csilla Nagyne Martinak Date: Sat, 17 Oct 2015 13:56:35 +0200 Subject: [PATCH 05/15] docs: Document the (keyword) and (name) functions Closes #733 Signed-off-by: Csilla Nagyne Martinak --- docs/language/core.rst | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/language/core.rst b/docs/language/core.rst index 58b00d9..b3bb65f 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -436,6 +436,26 @@ themselves as an iterator when ``(iter x)`` is called. Contrast with => (iterator? (iter {:a 1 :b 2 :c 3})) True +.. _keyword-fn: + +keyword +------- + +.. versionadded:: 0.10.1 + +Usage: ``(keyword "foo")`` + +Create a keyword from the given value. Strings, numbers, and even +objects with the `__name__` magic will work. + +.. code-block:: hy + + => (keyword "foo") + u'\ufdd0:foo' + + => (keyword 1) + u'\ufdd0:1' + .. _keyword?-fn: keyword? @@ -536,6 +556,24 @@ calling ``(f val-in-result val-in-latter)``. {u'a': 11L, u'c': 30L, u'b': 20L} +.. _name-fn: + +name +---- + +.. versionadded:: 0.10.1 + +Usage: ``(name :keyword)`` + +Convert the given value to a string. Keyword special character will be +stripped. Strings will be used as is. Even objects with the `__name__` +magic will work. + +.. code-block:: hy + + => (name :foo) + u'foo' + .. _neg?-fn: neg? From d79e56f2ef9754e2d9c3ff6b55e60c96f9b46f6b Mon Sep 17 00:00:00 2001 From: Csilla Nagyne Martinak Date: Sat, 17 Oct 2015 13:32:01 +0200 Subject: [PATCH 06/15] docs/tutorial: Add a short (require) example Adds a short (require) example, along with a few words on why macros can't be imported. Closes #966. Signed-off-by: Csilla Nagyne Martinak --- docs/tutorial.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index cf76538..c78fa83 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -557,10 +557,21 @@ characters that soon): => #↻(1 2 3 +) 6 -Macros are useful when one wished to extend the Hy or write their own +Macros are useful when one wishes to extend the Hy or write their own language on top of that. Many features of Hy are macros, like ``when``, ``cond`` and ``->``. +To use macros defined in a different module, it is not enough to +``import`` the module, because importing happens at run-time, while we +would need macros at compile-time. Instead of importing the module +with macros, it must be ``require``d: + +.. code-block:: clj + + => (require tutorial.macros) + => (rev (1 2 3 +)) + 6 + Hy <-> Python interop ===================== From acd98bb79e68fbc2d073da96ee8d02a362493e67 Mon Sep 17 00:00:00 2001 From: Csilla Nagyne Martinak Date: Sat, 17 Oct 2015 14:07:32 +0200 Subject: [PATCH 07/15] docs/tutorial: Simplify two examples As part of the Grand Language Cleanup, a few examples in the tutorial could be simplified: * The --init-- function of the defclass example does not need an explicit None anymore. * The apply example in the Hy<->Python interop section can use a keyword instead of a string in the last part. This perhaps closes #971. Signed-off-by: Csilla Nagyne Martinak --- docs/tutorial.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index cf76538..0e4dd1a 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -479,8 +479,7 @@ In Hy: "Yet Another Example Class" (defn --init-- [self x] - (setv self.x x) - None) + (setv self.x x)) (defn get-x [self] "Return our copy of x" @@ -604,7 +603,7 @@ To use keyword arguments, you can use in ``greetings.py``:: (import greetings) (.greet greetings "Foo") (.greet greetings "Foo" "Darth") - (apply (. greetings greet) ["Foo"] {"title" "Lord"}) + (apply (. greetings greet) ["Foo"] {:title "Lord"}) Which would output:: From 58579320cda35529bdee7ef9a5ac8033aba9a3fb Mon Sep 17 00:00:00 2001 From: Csilla Nagyne Martinak Date: Sat, 17 Oct 2015 14:31:42 +0200 Subject: [PATCH 08/15] hy.core.macros: Add docstrings for -> and ->> Borrowed from the API docs and Clojure, mostly. Signed-off-by: Csilla Nagyne Martinak --- hy/core/macros.hy | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 54446e9..5d52ed3 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -120,7 +120,10 @@ (defmacro -> [head &rest rest] - ;; TODO: fix the docstring by someone who understands this + "Threads the head through the rest of the forms. Inserts + head as the second item in the first form of rest. If + there are more forms, inserts the first form as the + second item in the second form of rest, etc." (setv ret head) (for* [node rest] (if (not (isinstance node HyExpression)) @@ -143,7 +146,10 @@ ~f)) (defmacro ->> [head &rest rest] - ;; TODO: fix the docstring by someone who understands this + "Threads the head through the rest of the forms. Inserts + head as the last item in the first form of rest. If there + are more forms, inserts the first form as the last item + in the second form of rest, etc." (setv ret head) (for* [node rest] (if (not (isinstance node HyExpression)) From f4afb0ca7efd76cb3bc4a99bc3c719c1ef96584a Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 13 Oct 2015 19:38:15 -0600 Subject: [PATCH 09/15] variadic if The `if` form now supports elif clauses. It works like `cond` but without the implicit `do`. The old `if` is now `if*` variadic lif now supports "ellif" clauses. Update if-no-waste compiler to use `if*` properly. (Sometimes one character is all it takes.) document if reword truthiness --- docs/language/api.rst | 44 ++++++++++++++++++++--------- hy/compiler.py | 4 +-- hy/core/bootstrap.hy | 11 ++++++-- hy/core/macros.hy | 21 ++++++++------ tests/compilers/test_ast.py | 18 ++++++------ tests/native_tests/language.hy | 26 +++++++++++++++++ tests/native_tests/native_macros.hy | 13 +++++++-- 7 files changed, 100 insertions(+), 37 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 91bf707..3ee2374 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -823,26 +823,47 @@ keyword, the second function would have raised a ``NameError``. (set-a 5) (print-a) -if / if-not ------------ +if / if* / if-not +----------------- .. versionadded:: 0.10.0 if-not -``if`` is used to conditionally select code to be executed. It has to contain a -condition block and the block to be executed if the condition block evaluates -to ``True``. Optionally, it may contain a final block that is executed in case -the evaluation of the condition is ``False``. +``if / if* / if-not`` respect Python *truthiness*, that is, a *test* fails if it +evaluates to a "zero" (including values of ``len`` zero, ``nil``, and +``false``), and passes otherwise, but values with a ``__bool__`` method +(``__nonzero__`` in Python 2) can overrides this. -``if-not`` is similar, but the second block will be executed when the condition -fails while the third and final block is executed when the test succeeds -- the -opposite order of ``if``. +The ``if`` macro is for conditionally selecting an expression for evaluation. +The result of the selected expression becomes the result of the entire ``if`` +form. ``if`` can select a group of expressions with the help of a ``do`` block. + +``if`` takes any number of alternating *test* and *then* expressions, plus an +optional *else* expression at the end, which defaults to ``nil``. ``if`` checks +each *test* in turn, and selects the *then* corresponding to the first passed +test. ``if`` does not evaluate any expressions following its selection, similar +to the ``if/elif/else`` control structure from Python. If no tests pass, ``if`` +selects *else*. + +The ``if*`` special form is restricted to 2 or 3 arguments, but otherwise works +exactly like ``if`` (which expands to nested ``if*`` forms), so there is +generally no reason to use it directly. + +``if-not`` is similar to ``if*`` but the second expression will be executed +when the condition fails while the third and final expression is executed when +the test succeeds -- the opposite order of ``if*``. The final expression is +again optional and defaults to ``nil``. Example usage: .. code-block:: clj - (if (money-left? account) + (print (if (< n 0.0) "negative" + (= n 0.0) "zero" + (> n 0.0) "positive" + "not a number")) + + (if* (money-left? account) (print "let's go shopping") (print "let's go and work")) @@ -850,9 +871,6 @@ Example usage: (print "let's go and work") (print "let's go shopping")) -Python truthiness is respected. ``None``, ``False``, zero of any numeric type, -an empty sequence, and an empty dictionary are considered ``False``; everything -else is considered ``True``. lif and lif-not diff --git a/hy/compiler.py b/hy/compiler.py index 2dd13c4..8032153 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -999,7 +999,7 @@ class HyASTCompiler(object): name=name, body=body) - @builds("if") + @builds("if*") @checkargs(min=2, max=3) def compile_if(self, expression): expression.pop(0) @@ -1011,7 +1011,7 @@ class HyASTCompiler(object): if expression: orel_expr = expression.pop(0) if isinstance(orel_expr, HyExpression) and isinstance(orel_expr[0], - HySymbol) and orel_expr[0] == 'if': + HySymbol) and orel_expr[0] == 'if*': # Nested ifs: don't waste temporaries root = self.temp_if is None nested = True diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy index 6667ed8..8d8a3f8 100644 --- a/hy/core/bootstrap.hy +++ b/hy/core/bootstrap.hy @@ -25,12 +25,20 @@ ;;; These macros are the essential hy macros. ;;; They are automatically required everywhere, even inside hy.core modules. +(defmacro if [&rest args] + "if with elif" + (setv n (len args)) + (if* n + (if* (= n 1) + (get args 0) + `(if* ~(get args 0) + ~(get args 1) + (if ~@(cut args 2)))))) (defmacro macro-error [location reason] "error out properly within a macro" `(raise (hy.errors.HyMacroExpansionError ~location ~reason))) - (defmacro defn [name lambda-list &rest body] "define a function `name` with signature `lambda-list` and body `body`" (if (not (= (type name) HySymbol)) @@ -39,7 +47,6 @@ (macro-error name "defn takes a parameter list as second argument")) `(setv ~name (fn ~lambda-list ~@body))) - (defmacro let [variables &rest body] "Execute `body` in the lexical context of `variables`" (if (not (isinstance variables HyList)) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 5d52ed3..d39aabe 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -159,20 +159,25 @@ ret) -(defmacro if-not [test not-branch &optional [yes-branch nil]] +(defmacro if-not [test not-branch &optional yes-branch] "Like `if`, but execute the first branch when the test fails" - (if (nil? yes-branch) - `(if (not ~test) ~not-branch) - `(if (not ~test) ~not-branch ~yes-branch))) + `(if* (not ~test) ~not-branch ~yes-branch)) -(defmacro lif [test &rest branches] +(defmacro lif [&rest args] "Like `if`, but anything that is not None/nil is considered true." - `(if (is-not ~test nil) ~@branches)) + (setv n (len args)) + (if* n + (if* (= n 1) + (get args 0) + `(if* (is-not ~(get args 0) nil) + ~(get args 1) + (lif ~@(cut args 2)))))) -(defmacro lif-not [test &rest branches] + +(defmacro lif-not [test not-branch &optional yes-branch] "Like `if-not`, but anything that is not None/nil is considered true." - `(if (is ~test nil) ~@branches)) + `(if* (is ~test nil) ~not-branch ~yes-branch)) (defmacro when [test &rest body] diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 3a1f92d..41156a3 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -73,15 +73,15 @@ def test_ast_bad_type(): def test_ast_bad_if(): - "Make sure AST can't compile invalid if" - cant_compile("(if)") - cant_compile("(if foobar)") - cant_compile("(if 1 2 3 4 5)") + "Make sure AST can't compile invalid if*" + cant_compile("(if*)") + cant_compile("(if* foobar)") + cant_compile("(if* 1 2 3 4 5)") def test_ast_valid_if(): - "Make sure AST can't compile invalid if" - can_compile("(if foo bar)") + "Make sure AST can compile valid if*" + can_compile("(if* foo bar)") def test_ast_valid_unary_op(): @@ -539,13 +539,13 @@ def test_invalid_list_comprehension(): def test_bad_setv(): """Ensure setv handles error cases""" - cant_compile("(setv if 1)") + cant_compile("(setv if* 1)") cant_compile("(setv (a b) [1 2])") def test_defn(): """Ensure that defn works correctly in various corner cases""" - cant_compile("(defn if [] 1)") + cant_compile("(defn if* [] 1)") cant_compile("(defn \"hy\" [] 1)") cant_compile("(defn :hy [] 1)") can_compile("(defn &hy [] 1)") @@ -561,5 +561,5 @@ def test_setv_builtins(): (defn get [self] 42) (defclass B [] (defn get [self] 42)) - (defn if [self] 0)) + (defn if* [self] 0)) """) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 9986ff7..8c7533a 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -274,6 +274,32 @@ (assert (= (cond) nil))) +(defn test-if [] + "NATIVE: test if if works." + ;; with an odd number of args, the last argument is the default case + (assert (= 1 (if 1))) + (assert (= 1 (if 0 -1 + 1))) + ;; with an even number of args, the default is nil + (assert (is nil (if))) + (assert (is nil (if 0 1))) + ;; test deeper nesting + (assert (= 42 + (if 0 0 + nil 1 + "" 2 + 1 42 + 1 43))) + ;; test shortcutting + (setv x nil) + (if 0 (setv x 0) + "" (setv x "") + 42 (setv x 42) + 43 (setv x 43) + (setv x "default")) + (assert (= x 42))) + + (defn test-index [] "NATIVE: Test that dict access works" (assert (= (get {"one" "two"} "one") "two")) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 0d10609..f833a30 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -190,11 +190,11 @@ (defn test-lif [] "test that lif works as expected" - ; nil is false + ;; nil is false (assert (= (lif None "true" "false") "false")) (assert (= (lif nil "true" "false") "false")) - ; But everything else is True! Even falsey things. + ;; But everything else is True! Even falsey things. (assert (= (lif True "true" "false") "true")) (assert (= (lif False "true" "false") "true")) (assert (= (lif 0 "true" "false") "true")) @@ -202,7 +202,14 @@ (assert (= (lif "" "true" "false") "true")) (assert (= (lif (+ 1 2 3) "true" "false") "true")) (assert (= (lif nil "true" "false") "false")) - (assert (= (lif 0 "true" "false") "true"))) + (assert (= (lif 0 "true" "false") "true")) + + ;; Test ellif [sic] + (assert (= (lif nil 0 + nil 1 + 0 2 + 3) + 2))) (defn test-lif-not [] "test that lif-not works as expected" From 66b16d15f526b64d803c8a935606d4b0d4795854 Mon Sep 17 00:00:00 2001 From: Endre Bakken Stovner Date: Fri, 16 Oct 2015 20:28:30 +0200 Subject: [PATCH 10/15] Add import anamorphic macros info Small change thanks to kirbyfan64 --- docs/contrib/anaphoric.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index db36ffc..cce39e0 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -13,6 +13,9 @@ concise and easy to read. -- Wikipedia (http://en.wikipedia.org/wiki/Anaphoric_macro) +To use these macros you need to require the hy.contrib.anaphoric module like so: + +``(require hy.contrib.anaphoric)`` .. _ap-if: @@ -233,7 +236,7 @@ xi Usage ``(xi body ...)`` -Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for lambda. The xi forms cannot be nested. +Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for lambda. The xi forms cannot be nested. This is similar to Clojure's anonymous function literals (``#()``). @@ -244,5 +247,3 @@ This is similar to Clojure's anonymous function literals (``#()``). => (def add-10 (xi + 10 x1)) => (add-10 6) 16 - - From 283111b4952650e02a2d862028eab5893388d608 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Wed, 14 Oct 2015 12:38:05 -0500 Subject: [PATCH 11/15] Improve error messages related to _storeize --- hy/compiler.py | 33 ++++++++++++++++++------------- tests/native_tests/language.hy | 36 ++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 8032153..8259a2c 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -590,21 +590,22 @@ class HyASTCompiler(object): return ret, args, defaults, varargs, kwonlyargs, kwonlydefaults, kwargs - def _storeize(self, name, func=None): + def _storeize(self, expr, name, func=None): """Return a new `name` object with an ast.Store() context""" if not func: func = ast.Store if isinstance(name, Result): if not name.is_expr(): - raise TypeError("Can't assign / delete a non-expression") + raise HyTypeError(expr, + "Can't assign or delete a non-expression") name = name.expr if isinstance(name, (ast.Tuple, ast.List)): typ = type(name) new_elts = [] for x in name.elts: - new_elts.append(self._storeize(x, func)) + new_elts.append(self._storeize(expr, x, func)) new_name = typ(elts=new_elts) elif isinstance(name, ast.Name): new_name = ast.Name(id=name.id, arg=name.arg) @@ -613,7 +614,9 @@ class HyASTCompiler(object): elif isinstance(name, ast.Attribute): new_name = ast.Attribute(value=name.value, attr=name.attr) else: - raise TypeError("Can't assign / delete a %s object" % type(name)) + raise HyTypeError(expr, + "Can't assign or delete a %s" % + type(expr).__name__) new_name.ctx = func() ast.copy_location(new_name, name) @@ -953,7 +956,7 @@ class HyASTCompiler(object): name = ast_str(name) else: # Python2 requires an ast.Name, set to ctx Store. - name = self._storeize(self.compile(name)) + name = self._storeize(name, self.compile(name)) else: name = None @@ -1343,11 +1346,13 @@ class HyASTCompiler(object): col_offset=root.start_column) return result - ld_targets, ret, _ = self._compile_collect(expr) - del_targets = [] - for target in ld_targets: - del_targets.append(self._storeize(target, ast.Del)) + ret = Result() + for target in expr: + compiled_target = self.compile(target) + ret += compiled_target + del_targets.append(self._storeize(target, compiled_target, + ast.Del)) return ret + ast.Delete( lineno=expr.start_line, @@ -1436,7 +1441,7 @@ class HyASTCompiler(object): thing = None if args != []: - thing = self._storeize(self.compile(args.pop(0))) + thing = self._storeize(args[0], self.compile(args.pop(0))) body = self._compile_branch(expr) @@ -1498,7 +1503,7 @@ class HyASTCompiler(object): gen = [] for target, iterable in paired_gens: comp_target = self.compile(target) - target = self._storeize(comp_target) + target = self._storeize(target, comp_target) gen_res += self.compile(iterable) gen.append(ast.comprehension( target=target, @@ -1963,7 +1968,7 @@ class HyASTCompiler(object): op = ops[expression[0]] - target = self._storeize(self.compile(expression[1])) + target = self._storeize(expression[1], self.compile(expression[1])) ret = self.compile(expression[2]) ret += ast.AugAssign( @@ -2099,7 +2104,7 @@ class HyASTCompiler(object): and '.' not in name: result.rename(name) else: - st_name = self._storeize(ld_name) + st_name = self._storeize(name, ld_name) result += ast.Assign( lineno=start_line, col_offset=start_column, @@ -2127,7 +2132,7 @@ class HyASTCompiler(object): raise HyTypeError(expression, "for requires two forms in the list") - target = self._storeize(self.compile(target_name)) + target = self._storeize(target_name, self.compile(target_name)) ret = Result() diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 8c7533a..4bb4b45 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1,7 +1,8 @@ (import [tests.resources [kwtest function-with-a-dash]] [os.path [exists isdir isfile]] [sys :as systest] - [operator [or_]]) + [operator [or_]] + [hy.errors [HyTypeError]]) (import sys) (import [hy._compat [PY33 PY34 PY35]]) @@ -60,6 +61,7 @@ (setv (get foo 0) 12) (assert (= (get foo 0) 12))) + (defn test-setv-builtin [] "NATIVE: test that setv doesn't work on builtins" (try (eval '(setv False 1)) @@ -93,6 +95,37 @@ (except [e [TypeError]] (assert (in "`setv' needs an even number of arguments" (str e)))))) +(defn test-store-errors [] + "NATIVE: test that setv raises the correct errors when given wrong argument types" + (try + (do + (eval '(setv (do 1 2) 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a non-expression")))) + + (try + (do + (eval '(setv 1 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a HyInteger")))) + + (try + (do + (eval '(setv {1 2} 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a HyDict")))) + + (try + (do + (eval '(del 1 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a HyInteger"))))) + + (defn test-fn-corner-cases [] "NATIVE: tests that fn/defn handles corner cases gracefully" (try (eval '(fn "foo")) @@ -965,7 +998,6 @@ (defn test-eval-failure [] "NATIVE: test eval failure modes" - (import [hy.errors [HyTypeError]]) ; yo dawg (try (eval '(eval)) (except [e HyTypeError]) (else (assert False))) (try (eval '(eval "snafu")) (except [e HyTypeError]) (else (assert False))) From e2614cc24a5011c3c218da56dc6cd945e67265b3 Mon Sep 17 00:00:00 2001 From: Johnathon Sage Date: Sat, 14 Nov 2015 15:48:39 -0500 Subject: [PATCH 12/15] Update tutorial.rst Changed case of function call in Macro --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index a16ac51..4759ec1 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -514,7 +514,7 @@ the program starts executing normally. Very simple example: => (defmacro hello [person] ... `(print "Hello there," ~person)) - => (Hello "Tuukka") + => (hello "Tuukka") Hello there, Tuukka The thing to notice here is that hello macro doesn't output anything on From 7dbd3bcf7cae0ff2ff030c84bc3b1ece2b0d2912 Mon Sep 17 00:00:00 2001 From: Johnathon Sage Date: Sat, 14 Nov 2015 16:18:53 -0500 Subject: [PATCH 13/15] Update api.rst Extra closing '])' in yield "yields 'LexException' --- docs/language/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 3ee2374..3556222 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1514,7 +1514,7 @@ infinite series without consuming infinite amount of memory. => (import random) => (defn random-numbers [low high] ... (while True (yield (.randint random low high)))) - => (list-comp x [x (take 15 (random-numbers 1 50))])]) + => (list-comp x [x (take 15 (random-numbers 1 50))]) [7, 41, 6, 22, 32, 17, 5, 38, 18, 38, 17, 14, 23, 23, 19] From ec0eea127787c5c1ed4d325e8fa9ce5483f05233 Mon Sep 17 00:00:00 2001 From: Johnathon Sage Date: Sat, 14 Nov 2015 16:56:27 -0500 Subject: [PATCH 14/15] Update api.rst (for [[( ... ) ( ... )]] ) --> HyMacroExpansionError: 'for' requires an even number of args. --- docs/language/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 3556222..71b3317 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1502,7 +1502,7 @@ infinite series without consuming infinite amount of memory. .. code-block:: clj => (defn multiply [bases coefficients] - ... (for [[(, base coefficient) (zip bases coefficients)]] + ... (for [(, base coefficient) (zip bases coefficients)] ... (yield (* base coefficient)))) => (multiply (range 5) (range 5)) From 33e1cb17e17e25718dcda1eb449216745c0d443b Mon Sep 17 00:00:00 2001 From: Johnathon Sage Date: Sat, 14 Nov 2015 17:02:00 -0500 Subject: [PATCH 15/15] Update AUTHORS Added @johnathonmlady to the AUTHORS file --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 631d741..26fdb81 100644 --- a/AUTHORS +++ b/AUTHORS @@ -65,3 +65,4 @@ * Antony Woods * Matthew Egan Odendahl * Tim Martin +* Johnathon Mlady