From 25bf3dec42fa05810cb07bcc1e77325700459d60 Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Thu, 3 Oct 2013 23:38:30 +0200 Subject: [PATCH 001/202] Add a method for casting into byte string or unicode depending on python version --- hy/core/language.hy | 8 +++++++- tests/native_tests/language.hy | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 10f81e9..45ad5eb 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -177,6 +177,12 @@ "Return second item from `coll`" (get coll 1)) +(defn string [x] + "Cast x as current string implementation" + (if-python2 + (unicode x) + (str x))) + (defn string? [x] "Return True if x is a string" (if-python2 @@ -218,5 +224,5 @@ "even?" "filter" "float?" "inc" "instance?" "integer?" "iterable?" "iterate" "iterator?" "neg?" "none?" "nth" "numeric?" "odd?" "pos?" "remove" "repeat" - "repeatedly" "second" "string?" "take" "take_nth" "take_while" + "repeatedly" "second" "string" "string?" "take" "take_nth" "take_while" "zero?"]) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6dd2986..6956734 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -779,3 +779,7 @@ "Evaluate an empty list to a []" (assert (= () []))) +(defn test-string [] + (assert (string? (string "a"))) + (assert (string? (string 1))) + (assert (= u"unicode" (string "unicode")))) From f61d7027880dd6c04fd81bfc36eb3e77689611dc Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Wed, 21 Aug 2013 01:09:03 +0200 Subject: [PATCH 002/202] Int conversion to long in py2.x Updated to current master Droped HyInt/HyLong commit --- hy/compiler.py | 4 ++-- hy/macros.py | 7 +++++-- hy/models/integer.py | 8 +++++--- tests/native_tests/core.hy | 2 +- tests/native_tests/math.hy | 4 ++++ 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index f14c79f..67918e0 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -37,7 +37,7 @@ from hy.models.list import HyList from hy.models.dict import HyDict from hy.macros import require, macroexpand -from hy._compat import str_type +from hy._compat import str_type, long_type import hy.importer import traceback @@ -1753,7 +1753,7 @@ class HyASTCompiler(object): @builds(HyInteger) def compile_integer(self, number): - return ast.Num(n=int(number), + return ast.Num(n=long_type(number), lineno=number.start_line, col_offset=number.start_column) diff --git a/hy/macros.py b/hy/macros.py index 20d0cac..046320a 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -26,10 +26,10 @@ from hy.models.integer import HyInteger from hy.models.float import HyFloat from hy.models.complex import HyComplex from hy.models.dict import HyDict -from hy._compat import str_type +from hy._compat import str_type, long_type from collections import defaultdict - +import sys CORE_MACROS = [ "hy.core.bootstrap", @@ -87,6 +87,9 @@ _wrappers = { list: lambda l: HyList(_wrap_value(x) for x in l) } +if sys.version_info[0] < 3: # do not add long on python3 + _wrappers[long_type] = HyInteger + def _wrap_value(x): """Wrap `x` into the corresponding Hy type. diff --git a/hy/models/integer.py b/hy/models/integer.py index cb46af4..0eacef7 100644 --- a/hy/models/integer.py +++ b/hy/models/integer.py @@ -19,14 +19,16 @@ # DEALINGS IN THE SOFTWARE. from hy.models import HyObject +from hy._compat import long_type -class HyInteger(HyObject, int): +class HyInteger(HyObject, long_type): """ Internal represntation of a Hy Integer. May raise a ValueError as if - int(foo) was caled, given HyInteger(foo). + int(foo) was called, given HyInteger(foo). On python 2.x long will + be used instead """ def __new__(cls, number, *args, **kwargs): - number = int(number) + number = long_type(number) return super(HyInteger, cls).__new__(cls, number) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 6e81f39..e9d73c7 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -163,7 +163,7 @@ (assert-false (instance? Foo2 foo)) (assert-true (instance? Foo foo3)) (assert-true (instance? float 1.0)) - (assert-true (instance? int 3)) + (assert-true (instance? int (int 3))) (assert-true (instance? str (str "hello")))) (defn test-integer? [] diff --git a/tests/native_tests/math.hy b/tests/native_tests/math.hy index ba350e6..eda77b0 100644 --- a/tests/native_tests/math.hy +++ b/tests/native_tests/math.hy @@ -132,3 +132,7 @@ (let [[x 1]] (^= x 1) (assert (= x 0)))) + +(defn overflow-int-to-long [] + "NATIVE: test if int does not raise an overflow exception" + (assert (integer? (+ 1 1000000000000000000000000)))) From 80feaf57fcc826f55d037cabb393a48f5e460786 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Fri, 11 Oct 2013 08:09:28 +0200 Subject: [PATCH 003/202] Make HyObject.replace more resilient This makes it work on quoted objects --- hy/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/models/__init__.py b/hy/models/__init__.py index 42032ca..31820f1 100644 --- a/hy/models/__init__.py +++ b/hy/models/__init__.py @@ -29,7 +29,7 @@ class HyObject(object): if isinstance(other, HyObject): for attr in ["start_line", "end_line", "start_column", "end_column"]: - if not hasattr(self, attr): + if not hasattr(self, attr) and hasattr(other, attr): setattr(self, attr, getattr(other, attr)) else: raise TypeError("Can't replace a non Hy object with a Hy object") From 34275fab606e1e7c3f48112d9ea7d7422a249fa1 Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Fri, 11 Oct 2013 08:35:32 +0200 Subject: [PATCH 004/202] Added type coercing to the right integer for the platform --- hy/core/language.hy | 19 +++++++++++-------- tests/native_tests/core.hy | 2 ++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index a96b25d..ff98a11 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -23,6 +23,8 @@ ;;;; to make functional programming slightly easier. ;;;; +(import [hy._compat [long-type]]) ; long for python2, int for python3 + (defn _numeric-check [x] (if (not (numeric? x)) (raise (TypeError (.format "{0!r} is not a number" x))))) @@ -97,11 +99,13 @@ (defn instance? [klass x] (isinstance x klass)) +(defn integer [x] + "Return Hy kind of integer" + (long-type x)) + (defn integer? [x] "Return True if x in an integer" - (if-python2 - (isinstance x (, int long)) - (isinstance x int))) + (isinstance x (, int long-type))) (defn iterable? [x] "Return true if x is iterable" @@ -213,8 +217,7 @@ (= n 0)) (def *exports* ["cycle" "dec" "distinct" "drop" "drop_while" "empty?" - "even?" "filter" "float?" "inc" - "instance?" "integer?" "iterable?" "iterate" "iterator?" "neg?" - "none?" "nth" "numeric?" "odd?" "pos?" "remove" "repeat" - "repeatedly" "second" "string?" "take" "take_nth" "take_while" - "zero?"]) + "even?" "filter" "float?" "inc" "instance?" "integer" "integer?" + "iterable?" "iterate" "iterator?" "neg?" "none?" "nth" + "numeric?" "odd?" "pos?" "remove" "repeat" "repeatedly" "second" + "string?" "take" "take_nth" "take_while" "zero?"]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index e9d73c7..2f6b717 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -171,6 +171,8 @@ (assert-true (integer? 0)) (assert-true (integer? 3)) (assert-true (integer? -3)) + (assert-true (integer? (integer "-3"))) + (assert-true (integer? (integer 3))) (assert-false (integer? 4.2)) (assert-false (integer? None)) (assert-false (integer? "foo"))) From a34db9119b008ea5997aa6ebc291a7021f40293c Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Fri, 11 Oct 2013 11:55:14 +0100 Subject: [PATCH 005/202] Add macroexpand to core/language.hy --- hy/core/language.hy | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index a96b25d..83634b3 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -119,6 +119,11 @@ (try (= x (iter x)) (catch [TypeError] false))) +(defn macroexpand [form] + "Return the macro expansion of form" + (import hy.macros) + (hy.macros.macroexpand form --name--)) + (defn neg? [n] "Return true if n is < 0" (_numeric-check n) @@ -212,9 +217,8 @@ (_numeric_check n) (= n 0)) -(def *exports* ["cycle" "dec" "distinct" "drop" "drop_while" "empty?" - "even?" "filter" "float?" "inc" - "instance?" "integer?" "iterable?" "iterate" "iterator?" "neg?" - "none?" "nth" "numeric?" "odd?" "pos?" "remove" "repeat" - "repeatedly" "second" "string?" "take" "take_nth" "take_while" - "zero?"]) +(def *exports* ["cycle" "dec" "distinct" "drop" "drop_while" "empty?" "even?" + "filter" "float?" "inc" "instance?" "integer?" "iterable?" + "iterate" "iterator?" "macroexpand" "neg?" "none?" "nth" + "numeric?" "odd?" "pos?" "remove" "repeat" "repeatedly" + "second" "string?" "take" "take_nth" "take_while" "zero?"]) From 2e60d6b47b517a9573fcd4bb9e562d5f3d3c5b60 Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Fri, 11 Oct 2013 12:03:52 +0100 Subject: [PATCH 006/202] Added a simple test for macroexpand --- tests/native_tests/language.hy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index d19ca17..eeb505f 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -775,7 +775,13 @@ (.append y x)) (assert (= y [5]))) + (defn test-empty-list [] "Evaluate an empty list to a []" (assert (= () []))) + +(defn test-macroexpand [] + "Test macroexpand on ->" + (assert (= (macroexpand '(-> (a b) (x y))) + '(x (a b) y)))) From 033198a90eabce89276f60ad9831475f61d47ea4 Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Fri, 11 Oct 2013 12:06:22 +0100 Subject: [PATCH 007/202] Added documentation for macroexpand --- docs/language/core.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/language/core.rst b/docs/language/core.rst index 8a34549..6124911 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -222,6 +222,18 @@ Contrast with :ref:`iterable?-fn`. .. _neg?-fn: +macroexpand +----------- + +Usage: ``(macroexpand form)`` + +Returns the macro expansion of form. + +.. code-block:: clojure + + => (macroexpand '(-> (a b) (x y))) + (u'x' (u'a' u'b') u'y') + neg? ---- From defccc6853f0a9dbc2ef4b9760f0b34ea4a1353f Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Fri, 11 Oct 2013 13:50:10 +0100 Subject: [PATCH 008/202] Added macroexpand-1 --- docs/language/core.rst | 17 ++++++++++++++++- hy/core/language.hy | 14 ++++++++++---- tests/native_tests/language.hy | 14 +++++++++++--- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index 6124911..81a277e 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -227,13 +227,28 @@ macroexpand Usage: ``(macroexpand form)`` -Returns the macro expansion of form. +Returns the full macro expansion of form. .. code-block:: clojure => (macroexpand '(-> (a b) (x y))) (u'x' (u'a' u'b') u'y') + => (macroexpand '(-> (a b) (-> (c d) (e f)))) + (u'e' (u'c' (u'a' u'b') u'd') u'f') + +macroexpand-1 +------------- + +Usage: ``(macroexpand-1 form)`` + +Returns the single step macro expansion of form. + +.. code-block:: clojure + + => (macroexpand-1 '(-> (a b) (-> (c d) (e f)))) + (u'_>' (u'a' u'b') (u'c' u'd') (u'e' u'f')) + neg? ---- diff --git a/hy/core/language.hy b/hy/core/language.hy index 83634b3..33db9b3 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -120,10 +120,15 @@ (catch [TypeError] false))) (defn macroexpand [form] - "Return the macro expansion of form" + "Return the full macro expansion of form" (import hy.macros) (hy.macros.macroexpand form --name--)) +(defn macroexpand-1 [form] + "Return the single step macro expansion of form" + (import hy.macros) + (hy.macros.macroexpand-1 form --name--)) + (defn neg? [n] "Return true if n is < 0" (_numeric-check n) @@ -219,6 +224,7 @@ (def *exports* ["cycle" "dec" "distinct" "drop" "drop_while" "empty?" "even?" "filter" "float?" "inc" "instance?" "integer?" "iterable?" - "iterate" "iterator?" "macroexpand" "neg?" "none?" "nth" - "numeric?" "odd?" "pos?" "remove" "repeat" "repeatedly" - "second" "string?" "take" "take_nth" "take_while" "zero?"]) + "iterate" "iterator?" "macroexpand" "macroexpand_1" "neg?" + "none?" "nth" "numeric?" "odd?" "pos?" "remove" "repeat" + "repeatedly" "second" "string?" "take" "take_nth" "take_while" + "zero?"]) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index eeb505f..fc54e0b 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -782,6 +782,14 @@ (defn test-macroexpand [] - "Test macroexpand on ->" - (assert (= (macroexpand '(-> (a b) (x y))) - '(x (a b) y)))) + "Test macroexpand on ->" + (assert (= (macroexpand '(-> (a b) (x y))) + '(x (a b) y))) + (assert (= (macroexpand '(-> (a b) (-> (c d) (e f)))) + '(e (c (a b) d) f)))) + + +(defn test-macroexpand-1 [] + "Test macroexpand-1 on ->" + (assert (= (macroexpand-1 '(-> (a b) (-> (c d) (e f)))) + '(-> (a b) (c d) (e f))))) From c34db29ba8801eed0bfd0ea6a3dc25bd943e1bc1 Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Fri, 11 Oct 2013 14:30:55 +0100 Subject: [PATCH 009/202] Fixed --name-- scope problem using ugly inspect code --- hy/core/language.hy | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 33db9b3..6450edc 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -121,13 +121,21 @@ (defn macroexpand [form] "Return the full macro expansion of form" + (import inspect) (import hy.macros) - (hy.macros.macroexpand form --name--)) + + (setv f (get (get (.stack inspect) 1) 0)) + (setv name (get f.f_globals "__name__")) + (hy.macros.macroexpand form name)) (defn macroexpand-1 [form] "Return the single step macro expansion of form" + (import inspect) (import hy.macros) - (hy.macros.macroexpand-1 form --name--)) + + (setv f (get (get (.stack inspect) 1) 0)) + (setv name (get f.f_globals "__name__")) + (hy.macros.macroexpand-1 form name)) (defn neg? [n] "Return true if n is < 0" From f5754b404e24d7621a4e0f9bf604788848285e1e Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Wed, 24 Jul 2013 00:25:48 +0200 Subject: [PATCH 010/202] Define kwapply as a macro Define apply if python3 Added apply tests --- hy/compiler.py | 36 ++++++++++++++++++++++++---------- hy/core/language.hy | 4 ++-- hy/core/macros.hy | 13 ++++++++++++ tests/native_tests/language.hy | 17 +++++++++++++++- 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index f14c79f..a1ef47d 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1255,19 +1255,35 @@ class HyASTCompiler(object): return ret - @builds("kwapply") - @checkargs(2) - def compile_kwapply_expression(self, expr): - expr.pop(0) # kwapply - call = self.compile(expr.pop(0)) - kwargs = self.compile(expr.pop(0)) + # apply only needs to be defined for python3 + if sys.version_info[0] >= 3: + @builds("apply") + @checkargs(min=2, max=3) + def compile_apply_expression(self, expr): + expr.pop(0) # apply + call = self.compile(expr.pop(0)) + call = ast.Call(func=call.expr, + args=[], + keywords=[], + starargs=None, + kwargs=None, + lineno=expr.start_line, + col_offset=expr.start_column) + ret = call - if type(call.expr) != ast.Call: - raise HyTypeError(expr, "kwapplying a non-call") + #add star args if any + stargs = expr.pop(0) + if stargs is not None: + stargs = self.compile(stargs) + call.starargs = stargs.force_expr + ret = stargs + ret - call.expr.kwargs = kwargs.force_expr + if expr != []: + kwargs = self.compile(expr.pop(0)) + call.kwargs = kwargs.force_expr + ret = kwargs + ret - return kwargs + call + return ret @builds("not") @builds("~") diff --git a/hy/core/language.hy b/hy/core/language.hy index a96b25d..7574dc0 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -213,8 +213,8 @@ (= n 0)) (def *exports* ["cycle" "dec" "distinct" "drop" "drop_while" "empty?" - "even?" "filter" "float?" "inc" - "instance?" "integer?" "iterable?" "iterate" "iterator?" "neg?" + "even?" "filter" "float?" "inc" "instance?" "integer?" + "iterable?" "iterate" "iterator?" "neg?" "none?" "nth" "numeric?" "odd?" "pos?" "remove" "repeat" "repeatedly" "second" "string?" "take" "take_nth" "take_while" "zero?"]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 17792f9..9498b87 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -25,6 +25,7 @@ ;;; These macros form the hy language ;;; They are automatically required in every module, except inside hy.core +(import [functools [reduce]]) (defmacro for [args &rest body] "shorthand for nested foreach loops: @@ -116,3 +117,15 @@ ;; TODO: this needs some gensym love `(foreach [_hy_yield_from_x ~iterable] (yield _hy_yield_from_x))) + +(defmacro kwapply [call kwargs] + "Use a dictionary as keyword arguments" + (let [[-fun (car call)] + [-args (cdr call)] + [-okwargs kwargs]] + (while (= -fun "kwapply") ;; join any further kw + (setv -okwargs (+ (car (cdr -args)) -okwargs)) + (setv -fun (car (car -args))) + (setv -args (cdr (car -args)))) + + `(apply ~-fun [~@-args] ~-okwargs))) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index d19ca17..4bf35d5 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -152,8 +152,23 @@ (assert (= (kwapply (kwtest) {"one" "two"}) {"one" "two"})) (setv mydict {"one" "three"}) (assert (= (kwapply (kwtest) mydict) mydict)) - (assert (= (kwapply (kwtest) ((fn [] {"one" "two"}))) {"one" "two"}))) + (assert (= (kwapply (kwtest) ((fn [] {"one" "two"}))) {"one" "two"})) + (assert (= (kwapply + (kwapply + (kwapply + (kwapply (kwtest) {"x" 4}) + {"x" 8}) + {"x" (- 3 2) "y" 2}) + {"y" 5 "z" 3}) + {"x" 1 "y" 5 "z" 3}))) +(defn test-apply [] + "NATIVE: test working with args and functions" + (defn sumit [a b c] (+ a b c)) + (assert (= (apply sumit [1] {"b" 2 "c" 3}) 6)) + (assert (= (apply sumit [1 2 2]) 5)) + (assert (= (apply sumit [] {"a" 1 "b" 1 "c" 2}) 4)) + (assert (= (apply sumit ((fn [] [1 1])) {"c" 1}) 3))) (defn test-dotted [] "NATIVE: test dotted invocation" From 83bb1513db59e72b362688b6082868d0d36676e7 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 2 Nov 2013 20:11:18 +0100 Subject: [PATCH 011/202] Make apply api-compatible with python (args is not mandatory) --- hy/compiler.py | 40 ++++++++++++++++------------------ tests/native_tests/language.hy | 4 +++- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index a1ef47d..cd69df2 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1255,35 +1255,33 @@ class HyASTCompiler(object): return ret - # apply only needs to be defined for python3 - if sys.version_info[0] >= 3: - @builds("apply") - @checkargs(min=2, max=3) - def compile_apply_expression(self, expr): - expr.pop(0) # apply - call = self.compile(expr.pop(0)) - call = ast.Call(func=call.expr, - args=[], - keywords=[], - starargs=None, - kwargs=None, - lineno=expr.start_line, - col_offset=expr.start_column) - ret = call + @builds("apply") + @checkargs(min=1, max=3) + def compile_apply_expression(self, expr): + expr.pop(0) # apply + call = self.compile(expr.pop(0)) + call = ast.Call(func=call.expr, + args=[], + keywords=[], + starargs=None, + kwargs=None, + lineno=expr.start_line, + col_offset=expr.start_column) + ret = call - #add star args if any + if expr: stargs = expr.pop(0) if stargs is not None: stargs = self.compile(stargs) call.starargs = stargs.force_expr ret = stargs + ret - if expr != []: - kwargs = self.compile(expr.pop(0)) - call.kwargs = kwargs.force_expr - ret = kwargs + ret + if expr: + kwargs = self.compile(expr.pop(0)) + call.kwargs = kwargs.force_expr + ret = kwargs + ret - return ret + return ret @builds("not") @builds("~") diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4bf35d5..6521589 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -168,7 +168,9 @@ (assert (= (apply sumit [1] {"b" 2 "c" 3}) 6)) (assert (= (apply sumit [1 2 2]) 5)) (assert (= (apply sumit [] {"a" 1 "b" 1 "c" 2}) 4)) - (assert (= (apply sumit ((fn [] [1 1])) {"c" 1}) 3))) + (assert (= (apply sumit ((fn [] [1 1])) {"c" 1}) 3)) + (defn noargs [] [1 2 3]) + (assert (= (apply noargs) [1 2 3]))) (defn test-dotted [] "NATIVE: test dotted invocation" From 59e51166fb8ac082ea749710c69653d3ff50a136 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 2 Nov 2013 20:11:53 +0100 Subject: [PATCH 012/202] Allow calling kwapply with mixed names and dicts --- hy/core/macros.hy | 11 ++++++++--- tests/native_tests/language.hy | 16 +++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 9498b87..82eb342 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -122,10 +122,15 @@ "Use a dictionary as keyword arguments" (let [[-fun (car call)] [-args (cdr call)] - [-okwargs kwargs]] + [-okwargs `[(list (.items ~kwargs))]]] (while (= -fun "kwapply") ;; join any further kw - (setv -okwargs (+ (car (cdr -args)) -okwargs)) + (if (not (= (len -args) 2)) + (macro-error + call + (.format "Trying to call nested kwapply with {0} args instead of 2" + (len -args)))) + (.insert -okwargs 0 `(list (.items ~(car (cdr -args))))) (setv -fun (car (car -args))) (setv -args (cdr (car -args)))) - `(apply ~-fun [~@-args] ~-okwargs))) + `(apply ~-fun [~@-args] (dict (sum ~-okwargs []))))) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6521589..0fedb67 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -153,14 +153,16 @@ (setv mydict {"one" "three"}) (assert (= (kwapply (kwtest) mydict) mydict)) (assert (= (kwapply (kwtest) ((fn [] {"one" "two"}))) {"one" "two"})) - (assert (= (kwapply - (kwapply - (kwapply - (kwapply (kwtest) {"x" 4}) - {"x" 8}) - {"x" (- 3 2) "y" 2}) + (assert (= (kwapply + (kwapply + (kwapply + (kwapply + (kwapply (kwtest) {"x" 4}) + mydict) + {"x" 8}) + {"x" (- 3 2) "y" 2}) {"y" 5 "z" 3}) - {"x" 1 "y" 5 "z" 3}))) + {"x" 1 "y" 5 "z" 3 "one" "three"}))) (defn test-apply [] "NATIVE: test working with args and functions" From b8406dd920fb0d1d175091ca02b028705993650b Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 2 Nov 2013 20:49:20 +0100 Subject: [PATCH 013/202] Bootstrap a macro error-reporting test file --- tests/compilers/test_error_reporting.py | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/compilers/test_error_reporting.py diff --git a/tests/compilers/test_error_reporting.py b/tests/compilers/test_error_reporting.py new file mode 100644 index 0000000..879f358 --- /dev/null +++ b/tests/compilers/test_error_reporting.py @@ -0,0 +1,27 @@ +# 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"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from .test_ast import can_compile, cant_compile + + +def test_macro_nested_kwapply(): + "Make sure nested kwapply compile correctly" + can_compile("(kwapply (kwapply (foo) bar) baz)") + cant_compile("(kwapply (kwapply (foo)) bar)") From 26b052c76e240adc22a422d5f08b266c2d880406 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 2 Nov 2013 20:49:55 +0100 Subject: [PATCH 014/202] language.hy whitespace fix --- tests/native_tests/language.hy | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 0fedb67..1c22567 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -788,13 +788,12 @@ (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [x (range 10)] - (if (!= x 5) - (continue)) + (for [x (range 10)] + (if (!= x 5) + (continue)) (.append y x)) (assert (= y [5]))) (defn test-empty-list [] "Evaluate an empty list to a []" (assert (= () []))) - From 0f525a691d556919d21349ae1009635ed9ddd622 Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Sun, 3 Nov 2013 11:11:11 +0100 Subject: [PATCH 015/202] removed unnecessary import --- hy/core/macros.hy | 1 - 1 file changed, 1 deletion(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 9498b87..509526a 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -25,7 +25,6 @@ ;;; These macros form the hy language ;;; They are automatically required in every module, except inside hy.core -(import [functools [reduce]]) (defmacro for [args &rest body] "shorthand for nested foreach loops: From 3cbef507e7865e6d04c72ed287d0183cee79e221 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sun, 10 Nov 2013 15:12:44 +0200 Subject: [PATCH 016/202] PyPI is now behind a CDN. The `--use-mirrors` option is deprecated. See PEP 449 and https://mail.python.org/pipermail/distutils-sig/2013-May/020848.html for more information. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0ba4a9e..8c6d5c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,10 @@ python: - "2.6" # command to install dependencies install: - - pip install -r requirements.txt --use-mirrors - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor --use-mirrors; fi - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor --use-mirrors; fi - - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor --use-mirrors; fi + - pip install -r requirements.txt + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor; fi + - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor; fi - python setup.py -q install # # command to run tests script: make travis From 2106a0e5d4beb6899d1fb49af655bc90a2c8850e Mon Sep 17 00:00:00 2001 From: agentultra Date: Thu, 28 Nov 2013 13:23:09 -0500 Subject: [PATCH 017/202] Add anaphoric versions of map, filter, and foreach Anaphoric macros reduce the need to specify a lambda by binding a special name in a form passed as a parameter to the macro. This allows you to write more concise code: (= (list (--filter (even? it) [1 2 3 4])) [2 4]) This patch just adds a few basic ones. Other forms that can be converted to anaphoric versions include reduce, remove, enumerate, etc. --- hy/core/macros.hy | 18 ++++++++++++++++++ tests/native_tests/core.hy | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 17792f9..91c477d 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -3,6 +3,7 @@ ;; Copyright (c) 2013 Nicolas Dandrimont ;; Copyright (c) 2013 Paul Tagliamonte ;; Copyright (c) 2013 Konrad Hinsen +;; Copyright (c) 2013 James King ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -116,3 +117,20 @@ ;; TODO: this needs some gensym love `(foreach [_hy_yield_from_x ~iterable] (yield _hy_yield_from_x))) + + +(defmacro --each [lst &rest body] + `(foreach [it ~list] ~@body)) + + +(defmacro --map [form lst] + `(let [[f (lambda [it] ~form)]] + (foreach [v ~lst] + (yield (f v))))) + + +(defmacro --filter [form lst] + `(let [[pred (lambda [it] ~form)]] + (foreach [val ~lst] + (if (pred val) + (yield val))))) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 6e81f39..cde0cb6 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -1,5 +1,6 @@ ;; Copyright (c) 2013 Paul Tagliamonte ;; Copyright (c) 2013 Bob Tolbert +;; Copyright (c) 2013 James King ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -391,3 +392,23 @@ (assert-equal res [None None]) (setv res (list (take-while (fn [x] (not (none? x))) [1 2 3 4 None 5 6 None 7]))) (assert-equal res [1 2 3 4])) + +(defn test-anaphoric-each [] + "NATIVE: testing anaphoric each" + (setv res []) + (--each [1 2 3 4] (.append res it)) + (assert-equal res [1 2 3 4])) + +(defn test-anaphoric-map [] + "NATIVE: testing anaphoric map" + (assert-equal (list (--map (* it 3) [1 2 3])) + [3 6 9]) + (assert-equal (list (--map (* it 3) [])) + [])) + +(defn test-anaphoric-filter [] + "NATIVE: testing anaphoric filter" + (assert-equal (list (--filter (> it 2) [1 2 3 4])) + [3 4]) + (assert-equal (list (--filter (even? it) [1 2 3 4])) + [2 4])) From 8e44cc3d9a4600eb6e256e14e4c50a22dd0b0d50 Mon Sep 17 00:00:00 2001 From: agentultra Date: Thu, 28 Nov 2013 16:15:23 -0500 Subject: [PATCH 018/202] Add --each-while and --map-when A couple of more macros: hy> (--each-while [1 2 3 4 5] (< it 3) (print it)) 1 2 3 hy> ```--each-while``` continues to evaluate the body form while the predicate form is true for each element in the list. ```--map-when``` uses a predicate form to determine when to apply the map form upon the element in the list: hy> (list (--map-when (even? it) (* it 3) [1 2 3 4])) [1, 6, 3, 12] --- hy/core/macros.hy | 17 +++++++++++++++++ tests/native_tests/core.hy | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 91c477d..a3261ab 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -123,12 +123,29 @@ `(foreach [it ~list] ~@body)) +(defmacro --each-while [lst pred &rest body] + `(let [[p (lambda [it] ~pred)]] + (foreach [it ~lst] + (if (p it) + ~@body + (break))))) + + (defmacro --map [form lst] `(let [[f (lambda [it] ~form)]] (foreach [v ~lst] (yield (f v))))) +(defmacro --map-when [pred rep lst] + `(let [[p (lambda [it] ~pred)] + [f (lambda [it] ~rep)]] + (foreach [v ~lst] + (if (p v) + (yield (r v)) + (yield v))))) + + (defmacro --filter [form lst] `(let [[pred (lambda [it] ~form)]] (foreach [val ~lst] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index cde0cb6..e14dc0f 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -399,6 +399,13 @@ (--each [1 2 3 4] (.append res it)) (assert-equal res [1 2 3 4])) + +(defn test-anaphoric-each-while [] + "NATIVE: testing anaphoric each-while" + (setv res []) + (--each-while [2 2 4 3 4 5 6] (even? it) (.append res it)) + (assert-equal res [2 2 4])) + (defn test-anaphoric-map [] "NATIVE: testing anaphoric map" (assert-equal (list (--map (* it 3) [1 2 3])) @@ -406,6 +413,12 @@ (assert-equal (list (--map (* it 3) [])) [])) + +(defn test-anaphoric-map-when [] + "NATIVE: testing anaphoric map-when" + (assert-equal (list (--map-when (even? it) (* it 2) [1 2 3 4])) + [1 4 3 8])) + (defn test-anaphoric-filter [] "NATIVE: testing anaphoric filter" (assert-equal (list (--filter (> it 2) [1 2 3 4])) From 20df6a5532cf3a38e4ece8abae7e81d2258a8f94 Mon Sep 17 00:00:00 2001 From: agentultra Date: Thu, 28 Nov 2013 16:45:07 -0500 Subject: [PATCH 019/202] Make --map-when accept a predicate function instead of a form This makes it look a little cleaner: (list (--map-when odd? (* it 3) [1 2 3 4 5])) --- hy/core/macros.hy | 13 ++++++------- tests/native_tests/core.hy | 3 +-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index a3261ab..5ccfba7 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -137,13 +137,12 @@ (yield (f v))))) -(defmacro --map-when [pred rep lst] - `(let [[p (lambda [it] ~pred)] - [f (lambda [it] ~rep)]] - (foreach [v ~lst] - (if (p v) - (yield (r v)) - (yield v))))) +(defmacro --map-when [predfn rep lst] + `(let [[f (lambda [it] ~rep)]] + (foreach [it ~lst] + (if (~pred it) + (yield (f it)) + (yield it))))) (defmacro --filter [form lst] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index e14dc0f..4c2ebaa 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -413,10 +413,9 @@ (assert-equal (list (--map (* it 3) [])) [])) - (defn test-anaphoric-map-when [] "NATIVE: testing anaphoric map-when" - (assert-equal (list (--map-when (even? it) (* it 2) [1 2 3 4])) + (assert-equal (list (--map-when even? (* it 2) [1 2 3 4])) [1 4 3 8])) (defn test-anaphoric-filter [] From 179017b9bdb2c6049922d6a08ca3a9d929794388 Mon Sep 17 00:00:00 2001 From: agentultra Date: Thu, 28 Nov 2013 23:53:02 -0500 Subject: [PATCH 020/202] Move anaphoric macros to contrib module --- hy/contrib/anaphoric.hy | 56 ++++++++++++++++++++++ hy/core/macros.hy | 33 ------------- tests/native_tests/contrib/anaphoric.hy | 63 +++++++++++++++++++++++++ tests/native_tests/core.hy | 33 ------------- 4 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 hy/contrib/anaphoric.hy create mode 100644 tests/native_tests/contrib/anaphoric.hy diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy new file mode 100644 index 0000000..bd8f3c1 --- /dev/null +++ b/hy/contrib/anaphoric.hy @@ -0,0 +1,56 @@ +;;; Hy anaphoric macros +;; +;; Copyright (c) 2013 James King +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. +;; +;;; These macros make writing functional programs more concise + + +(defmacro ap-each [lst &rest body] + `(foreach [it ~list] ~@body)) + + +(defmacro ap-each-while [lst pred &rest body] + `(let [[p (lambda [it] ~pred)]] + (foreach [it ~lst] + (if (p it) + ~@body + (break))))) + + +(defmacro ap-map [form lst] + `(let [[f (lambda [it] ~form)]] + (foreach [v ~lst] + (yield (f v))))) + + +(defmacro ap-map-when [predfn rep lst] + `(let [[f (lambda [it] ~rep)]] + (foreach [it ~lst] + (if (~pred it) + (yield (f it)) + (yield it))))) + + +(defmacro ap-filter [form lst] + `(let [[pred (lambda [it] ~form)]] + (foreach [val ~lst] + (if (pred val) + (yield val))))) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 5ccfba7..0d08946 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -117,36 +117,3 @@ ;; TODO: this needs some gensym love `(foreach [_hy_yield_from_x ~iterable] (yield _hy_yield_from_x))) - - -(defmacro --each [lst &rest body] - `(foreach [it ~list] ~@body)) - - -(defmacro --each-while [lst pred &rest body] - `(let [[p (lambda [it] ~pred)]] - (foreach [it ~lst] - (if (p it) - ~@body - (break))))) - - -(defmacro --map [form lst] - `(let [[f (lambda [it] ~form)]] - (foreach [v ~lst] - (yield (f v))))) - - -(defmacro --map-when [predfn rep lst] - `(let [[f (lambda [it] ~rep)]] - (foreach [it ~lst] - (if (~pred it) - (yield (f it)) - (yield it))))) - - -(defmacro --filter [form lst] - `(let [[pred (lambda [it] ~form)]] - (foreach [val ~lst] - (if (pred val) - (yield val))))) diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy new file mode 100644 index 0000000..7fb08f4 --- /dev/null +++ b/tests/native_tests/contrib/anaphoric.hy @@ -0,0 +1,63 @@ +;; Copyright (c) 2013 James King + +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: + +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. + +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +;;;; some simple helpers + +(require hy.contrib.anaphoric) + +(defn assert-true [x] + (assert (= True x))) + +(defn assert-false [x] + (assert (= False x))) + +(defn assert-equal [x y] + (assert (= x y))) + +(defn test-ap-each [] + "NATIVE: testing anaphoric each" + (setv res []) + (ap-each [1 2 3 4] (.append res it)) + (assert-equal res [1 2 3 4])) + +(defn test-ap-each-while [] + "NATIVE: testing anaphoric each-while" + (setv res []) + (ap-each-while [2 2 4 3 4 5 6] (even? it) (.append res it)) + (assert-equal res [2 2 4])) + +(defn test-ap-map [] + "NATIVE: testing anaphoric map" + (assert-equal (list (ap-map (* it 3) [1 2 3])) + [3 6 9]) + (assert-equal (list (ap-map (* it 3) [])) + [])) + +(defn test-ap-map-when [] + "NATIVE: testing anaphoric map-when" + (assert-equal (list (ap-map-when even? (* it 2) [1 2 3 4])) + [1 4 3 8])) + +(defn test-ap-filter [] + "NATIVE: testing anaphoric filter" + (assert-equal (list (ap-filter (> it 2) [1 2 3 4])) + [3 4]) + (assert-equal (list (ap-filter (even? it) [1 2 3 4])) + [2 4])) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 4c2ebaa..6e81f39 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -1,6 +1,5 @@ ;; Copyright (c) 2013 Paul Tagliamonte ;; Copyright (c) 2013 Bob Tolbert -;; Copyright (c) 2013 James King ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -392,35 +391,3 @@ (assert-equal res [None None]) (setv res (list (take-while (fn [x] (not (none? x))) [1 2 3 4 None 5 6 None 7]))) (assert-equal res [1 2 3 4])) - -(defn test-anaphoric-each [] - "NATIVE: testing anaphoric each" - (setv res []) - (--each [1 2 3 4] (.append res it)) - (assert-equal res [1 2 3 4])) - - -(defn test-anaphoric-each-while [] - "NATIVE: testing anaphoric each-while" - (setv res []) - (--each-while [2 2 4 3 4 5 6] (even? it) (.append res it)) - (assert-equal res [2 2 4])) - -(defn test-anaphoric-map [] - "NATIVE: testing anaphoric map" - (assert-equal (list (--map (* it 3) [1 2 3])) - [3 6 9]) - (assert-equal (list (--map (* it 3) [])) - [])) - -(defn test-anaphoric-map-when [] - "NATIVE: testing anaphoric map-when" - (assert-equal (list (--map-when even? (* it 2) [1 2 3 4])) - [1 4 3 8])) - -(defn test-anaphoric-filter [] - "NATIVE: testing anaphoric filter" - (assert-equal (list (--filter (> it 2) [1 2 3 4])) - [3 4]) - (assert-equal (list (--filter (even? it) [1 2 3 4])) - [2 4])) From cb6889314a81f3b78ea4085b896ea432382aebee Mon Sep 17 00:00:00 2001 From: agentultra Date: Fri, 29 Nov 2013 19:59:20 -0500 Subject: [PATCH 021/202] Add documentation and doc strings --- docs/contrib/anaphoirc.rst | 96 ++++++++++++++++++++++++++++++++++++++ docs/contrib/index.rst | 10 ++++ docs/index.rst | 1 + hy/contrib/anaphoric.hy | 11 ++++- 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 docs/contrib/anaphoirc.rst create mode 100644 docs/contrib/index.rst diff --git a/docs/contrib/anaphoirc.rst b/docs/contrib/anaphoirc.rst new file mode 100644 index 0000000..0a487c2 --- /dev/null +++ b/docs/contrib/anaphoirc.rst @@ -0,0 +1,96 @@ +==================== +Anaphoric Macros +==================== + +The anaphoric macros module makes functional programming in Hy very +concise and easy to read. + + An anaphoric macro is a type of programming macro that + deliberately captures some form supplied to the macro which may be + referred to by an anaphor (an expression referring to another). + + -- Wikipedia (http://en.wikipedia.org/wiki/Anaphoric_macro) + +Macros +====== + +.. _ap-each: + +ap-each +------- + +Usage: ``(ap-each [1 2 3 4 5] (print it))`` + +Evaluate the form for each element in the list for side-effects. + + +.. _ap-each-while: + +ap-each-while +============= + +Usage: ``(ap-each-while list pred body)`` + +Evaluate the form for each element where the predicate form returns +True. + +.. code-block:: clojure + + => (ap-each-while [1 2 3 4 5 6] (< it 4) (print it)) + 1 + 2 + 3 + +.. _ap-map: + +ap-map +====== + +Usage: ``(ap-map form list)`` + +The anaphoric form of map works just like regular map except that +instead of a function object it takes a Hy form. The special name, +``it`` is bound to the current object from the list in the iteration. + +.. code-block:: clojure + + => (list (ap-map (* it 2) [1 2 3])) + [2, 4, 6] + + +.. _ap-map-when: + +ap-map-when +=========== + +Usage: ``(ap-map-when predfn rep list)`` + +Evaluate a mapping over the list using a predicate function to +determin when to apply the form. + +.. code-block:: clojure + + => (list (ap-map-when odd? (* it 2) [1 2 3 4])) + [2, 2, 6, 4] + + => (list (ap-map-when even? (* it 2) [1 2 3 4])) + [1, 4, 3, 8] + + +.. _ap-filter: + +ap-filter +========= + +Usage: ``(ap-filter form list)`` + +As with ``ap-map`` we take a special form instead of a function to +filter the elements of the list. The special name ``it`` is bound to +the current element in the iteration. + +.. code-block:: clojure + + => (list (ap-filter (> (* it 2) 6) [1 2 3 4 5])) + [4, 5] + + diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst new file mode 100644 index 0000000..80b7697 --- /dev/null +++ b/docs/contrib/index.rst @@ -0,0 +1,10 @@ + +Contrib Modules Index +===================== + +Contents: + +.. toctree:: + :maxdepth: 3 + + anaphoric diff --git a/docs/index.rst b/docs/index.rst index 299c59e..102929a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,3 +49,4 @@ Contents: tutorial hacking language/index + contrib/index diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index bd8f3c1..13825b5 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -24,11 +24,14 @@ (defmacro ap-each [lst &rest body] + "Evaluate the body form for each element in the list." `(foreach [it ~list] ~@body)) -(defmacro ap-each-while [lst pred &rest body] - `(let [[p (lambda [it] ~pred)]] +(defmacro ap-each-while [lst form &rest body] + "Evalutate the body form for each element in the list while the + predicate form evaluates to True." + `(let [[p (lambda [it] ~form)]] (foreach [it ~lst] (if (p it) ~@body @@ -36,12 +39,15 @@ (defmacro ap-map [form lst] + "Yield elements evaluated in the form for each element in the list." `(let [[f (lambda [it] ~form)]] (foreach [v ~lst] (yield (f v))))) (defmacro ap-map-when [predfn rep lst] + "Yield elements evaluated for each element in the list when the + predicate function returns True." `(let [[f (lambda [it] ~rep)]] (foreach [it ~lst] (if (~pred it) @@ -50,6 +56,7 @@ (defmacro ap-filter [form lst] + "Yield elements returned when the predicate form evaluates to True." `(let [[pred (lambda [it] ~form)]] (foreach [val ~lst] (if (pred val) From 62bc86a0b58d27374ae3341f7f1d9269a3bcbb5e Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Alves Date: Fri, 29 Nov 2013 23:51:41 -0200 Subject: [PATCH 022/202] Fix sys.argv to reflect default Python behaviour By default, calling the Python REPL without arguments causes sys.argv to be a non-empty list with an empty string, and some modules (e.g., pyNN) rely on that. --- hy/cmdline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index 031eb33..5950a59 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -228,7 +228,7 @@ def cmdline_handler(scriptname, argv): options = parser.parse_args(argv[1:]) # reset sys.argv like Python - sys.argv = options.args + sys.argv = options.args or [""] if options.command: # User did "hy -c ..." From 77db7790191ef927d4fd560496a290bf47bcbd64 Mon Sep 17 00:00:00 2001 From: agentultra Date: Sat, 30 Nov 2013 10:29:41 -0500 Subject: [PATCH 023/202] Fix typo in ap-each --- hy/contrib/anaphoric.hy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index 13825b5..c9a0fef 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -25,7 +25,7 @@ (defmacro ap-each [lst &rest body] "Evaluate the body form for each element in the list." - `(foreach [it ~list] ~@body)) + `(foreach [it ~lst] ~@body)) (defmacro ap-each-while [lst form &rest body] From 5753fc0789391bcdbbfc09970044c4539e23e895 Mon Sep 17 00:00:00 2001 From: agentultra Date: Sat, 30 Nov 2013 10:30:42 -0500 Subject: [PATCH 024/202] Fix typo in ap-map-when --- hy/contrib/anaphoric.hy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index c9a0fef..d0a163a 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -50,7 +50,7 @@ predicate function returns True." `(let [[f (lambda [it] ~rep)]] (foreach [it ~lst] - (if (~pred it) + (if (~predfn it) (yield (f it)) (yield it))))) From 0b6d047239346278db3264d50dcb0acf835bb09f Mon Sep 17 00:00:00 2001 From: J Kenneth King Date: Sat, 30 Nov 2013 10:35:57 -0500 Subject: [PATCH 025/202] Remove james@agentultra.com from copyright in core/macros.py --- hy/core/macros.hy | 1 - 1 file changed, 1 deletion(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 0d08946..17792f9 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -3,7 +3,6 @@ ;; Copyright (c) 2013 Nicolas Dandrimont ;; Copyright (c) 2013 Paul Tagliamonte ;; Copyright (c) 2013 Konrad Hinsen -;; Copyright (c) 2013 James King ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), From 3773be1d1a284e4c779aa78747b6146679286e6d Mon Sep 17 00:00:00 2001 From: agentultra Date: Sat, 30 Nov 2013 10:47:08 -0500 Subject: [PATCH 026/202] Rename anaphoirc.rst to anaphoric.rst --- docs/contrib/{anaphoirc.rst => anaphoric.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/contrib/{anaphoirc.rst => anaphoric.rst} (100%) diff --git a/docs/contrib/anaphoirc.rst b/docs/contrib/anaphoric.rst similarity index 100% rename from docs/contrib/anaphoirc.rst rename to docs/contrib/anaphoric.rst From d8c29f51d68f655e3d568583c2d9ce54589dca35 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Sun, 1 Dec 2013 01:20:58 +0530 Subject: [PATCH 027/202] docs/api:formatting fixes in `quote' and `unquote' A newline was omitted which caused the examples not to render for quote and unquote forms --- docs/language/api.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index bac3138..2598b57 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -736,6 +736,7 @@ using the backquote (`) symbol. .. code-block:: clj + ;; let `qux' be a variable with value (bar baz) `(foo ~qux) ; equivalent to '(foo (bar baz)) @@ -751,6 +752,7 @@ be alternatively written using the (') symbol .. code-block:: clj + => (setv x '(print "Hello World")) ; variable x is set to expression & not evaluated => x @@ -758,6 +760,7 @@ be alternatively written using the (') symbol => (eval x) Hello World + require ------- From 7c446ed81af8cdefdc109d809bf19c9ab0d8e98b Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Sun, 1 Dec 2013 09:43:45 -0500 Subject: [PATCH 028/202] Removing this file because it's nonfree. The terms aren't clear, so I'm just going to get rid of this. Sad, but meh. --- eg/nonfree/halting-problem/halting.hy | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100755 eg/nonfree/halting-problem/halting.hy diff --git a/eg/nonfree/halting-problem/halting.hy b/eg/nonfree/halting-problem/halting.hy deleted file mode 100755 index 9c33c08..0000000 --- a/eg/nonfree/halting-problem/halting.hy +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env hy - -;; Very much a knockoff (straight port) of Dan Gulotta's 2013 MIT Mystery Hunt -;; puzzle "The Halting Problem". His Copyright terms are unclear, so presume -;; that this is distributable, but not free. - - -(defn evaluate [f] ((f (lambda [x] (+ x 1))) 0)) - -(defn successor [n] (lambda [f] (lambda [x] (f ((n f) x))))) -(defn plus [m n] ((n successor) m)) -(defn exponent [m n] (n m)) -(defn zero [f] (lambda [x] x)) -(defn one [f] (lambda [x] (f x))) - -(defn predecessor [n] (lambda [f] (lambda [x] - (((n (lambda [g] (lambda [h] (h (g f))))) (lambda [y] x)) (lambda [z] z))))) - -(defn subtract [m n] ((m predecessor) n)) - -(setv two (plus one one)) -(setv three (plus two one)) - -(print (evaluate (exponent three three))) From c2556072051f3381541836385da9016012662eb7 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Sun, 1 Dec 2013 09:48:16 -0500 Subject: [PATCH 029/202] don't test the halting problem anymore. --- tests/test_bin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_bin.py b/tests/test_bin.py index f916f98..155e06a 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -75,12 +75,6 @@ def test_bin_hy_icmd(): assert "figlet" in output -def test_bin_hy_file(): - ret = run_cmd("hy eg/nonfree/halting-problem/halting.hy") - assert ret[0] == 0 - assert "27" in ret[1] - - def test_bin_hy_missing_file(): ret = run_cmd("hy foobarbaz") assert ret[0] == 2 From 9531d772cf385cfa974a759f85b72ef1480d5892 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Sun, 1 Dec 2013 12:56:26 -0500 Subject: [PATCH 030/202] add the start of hacking rules --- docs/hacking.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/hacking.rst b/docs/hacking.rst index 77e0036..e77aee0 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -83,3 +83,15 @@ To build the docs in HTML:: $ make html Write docs---docs are good! Even this doc! + + +Core Development Rules +====================== + +All incoming changes need to be acked by 2 different members of Hylang's +core team. Additional review is clearly welcome, but we need a minimum of +2 signoffs for any change. + +If a core member is sending in a PR, please find 2 core members that don't +include them PR submitter. The idea here is that one can work with the PR +author, and a second acks the entire change set. From fd60a864eb9219f6f577a708f21331d2e0b635ba Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Sun, 1 Dec 2013 13:19:53 -0500 Subject: [PATCH 031/202] Translate all foo? -> is_foo. Close #334 The fancypants Hy award goes to Nick for coming up with the quoted symbol hack for exports. This broke with foo?, since the export string needs to be is_foo, but using a quoted string will pick up the change due to it being a Symbol. Mad clown love for that, @olasd. --- NEWS | 7 +++++++ hy/core/language.hy | 10 ++++------ hy/lex/parser.py | 3 +++ tests/native_tests/language.hy | 6 ++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 9f199da..1a654ab 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +Changes from Hy 0.9.11 + + [ Misc. Fixes ] + [ Syntax Fixes ] + [ Language Changes ] + * Translate foo? -> is_foo, for better Python interop. (PT) + Changes from Hy 0.9.10 * Many thanks to Guillermo Vayá (Willyfrog) for preparing this release's diff --git a/hy/core/language.hy b/hy/core/language.hy index a96b25d..dfa62eb 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -212,9 +212,7 @@ (_numeric_check n) (= n 0)) -(def *exports* ["cycle" "dec" "distinct" "drop" "drop_while" "empty?" - "even?" "filter" "float?" "inc" - "instance?" "integer?" "iterable?" "iterate" "iterator?" "neg?" - "none?" "nth" "numeric?" "odd?" "pos?" "remove" "repeat" - "repeatedly" "second" "string?" "take" "take_nth" "take_while" - "zero?"]) +(def *exports* '[cycle dec distinct drop drop-while empty? even? filter float? + inc instance? integer? iterable? iterate iterator? neg? none? + nth numeric? odd? pos? remove repeat repeatedly second string? + take take-nth take-while zero?]) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index ce3b170..e4a3ece 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -238,6 +238,9 @@ def t_identifier(p): if "-" in obj and obj != "-": obj = obj.replace("-", "_") + if obj.endswith("?") and obj != "?": + obj = "is_%s" % (obj[:-1]) + return HySymbol(obj) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index d19ca17..bfe7d2e 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -551,6 +551,12 @@ (assert (= -_- "what?")))) +(defn test-symbol-question-mark [] + "NATIVE: test foo? -> is_foo behavior" + (let [[foo? "nachos"]] + (assert (= is_foo "nachos")))) + + (defn test-and [] "NATIVE: test the and function" (let [[and123 (and 1 2 3)] From 3cc0f8a717ebbc5622dca8809c4d14ccfb7a1aba Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Mon, 2 Dec 2013 12:07:05 +0100 Subject: [PATCH 032/202] Added info about tox, so people can use it prior to a PR --- docs/hacking.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/hacking.rst b/docs/hacking.rst index e77aee0..08a1fe6 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -70,6 +70,10 @@ To run the tests:: Write tests---tests are good! +Also, it is good to run the tests for all the platforms supported and for pep8 compliant code. +You can do so by running tox:: + + $ tox Document! ========= From 3972878d98071fa7dfb397f531a783e43b2a3b2d Mon Sep 17 00:00:00 2001 From: J Kenneth King Date: Mon, 2 Dec 2013 09:30:26 -0500 Subject: [PATCH 033/202] Add link to try-hy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbf6c99..6ca48b6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Hy ![](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png) -Lisp and Python should love each other. Let's make it happen. +Lisp and Python should love each other. Let's make it happen. [Try it](http://try-hy.appspot.com/). [![Build Status](https://travis-ci.org/hylang/hy.png?branch=master)](https://travis-ci.org/hylang/hy) [![Downloads](https://pypip.in/d/hy/badge.png)](https://crate.io/packages/hy) From ba3ba376654e7dd156348936faa9677919f61ed2 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Mon, 2 Dec 2013 19:54:14 +0530 Subject: [PATCH 034/202] Adding support for coverage tests at coveralls This commit adds support for coverage tests at coveralls.io, using coverage plugin from nosetests --- .coveragerc | 11 +++++++++++ .travis.yml | 2 ++ Makefile | 2 +- README.md | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..a238df8 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,11 @@ +[run] +omit = + */python?.?/* + */lib-python/?.?/*.py + */lib_pypy/_*.py + */site-packages/nose/* + */pypy/* + +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8c6d5c8..b6e97e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,14 @@ python: # command to install dependencies install: - pip install -r requirements.txt + - pip install coveralls - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse importlib unittest2 astor; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install astor; fi - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then pip install astor; fi - python setup.py -q install # # command to run tests script: make travis +after_success: coveralls notifications: email: - paultag@gmail.com diff --git a/Makefile b/Makefile index 07ae6db..b8815e2 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ diff: r: d tox diff travis: - nosetests -s + nosetests -s --with-coverage --cover-package hy ifeq (PyPy,$(findstring PyPy,$(shell python -V 2>&1 | tail -1))) @echo "skipping flake8 on pypy" else diff --git a/README.md b/README.md index dbf6c99..072702a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Lisp and Python should love each other. Let's make it happen. [![Build Status](https://travis-ci.org/hylang/hy.png?branch=master)](https://travis-ci.org/hylang/hy) [![Downloads](https://pypip.in/d/hy/badge.png)](https://crate.io/packages/hy) [![version](https://pypip.in/v/hy/badge.png)](https://crate.io/packages/hy) - +[![Coverage Status](https://coveralls.io/repos/hylang/hy/badge.png)](https://coveralls.io/r/hylang/hy) Hylarious Hacks --------------- From 1b7d7bcef7f7f58bf20cf79af1c051d1decfe013 Mon Sep 17 00:00:00 2001 From: Henrique Carvalho Alves Date: Tue, 3 Dec 2013 00:58:59 -0200 Subject: [PATCH 035/202] Fix REPL completions on OS X --- hy/completer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hy/completer.py b/hy/completer.py index 96c46af..d32deaf 100644 --- a/hy/completer.py +++ b/hy/completer.py @@ -26,6 +26,7 @@ # DEALINGS IN THE SOFTWARE. import os +import sys from contextlib import contextmanager docomplete = True @@ -40,6 +41,12 @@ except ImportError: except ImportError: docomplete = False +if sys.platform == 'darwin': + readline_bind = "bind ^I rl_complete" +else: + readline_bind = "tab: complete" + + import hy.macros import hy.compiler @@ -94,7 +101,7 @@ def completion(completer=None): except IOError: open(history, 'a').close() - readline.parse_and_bind("tab: complete") + readline.parse_and_bind(readline_bind) yield From 887f8819d406a85d0f80f7a574812cd390e66281 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 2 Dec 2013 23:46:30 -0500 Subject: [PATCH 036/202] add coveralls token --- .coveralls.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .coveralls.yml diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..490aace --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +repo_token: NBPVY1m10GQe6yhqbTptbWkdZyl9ailBq From 31ad46c550400c450796acf3b7d18e25dad5c87c Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 2 Dec 2013 23:47:43 -0500 Subject: [PATCH 037/202] Erm, revert that. Apparently that wasn't proper. --- .coveralls.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .coveralls.yml diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index 490aace..0000000 --- a/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -repo_token: NBPVY1m10GQe6yhqbTptbWkdZyl9ailBq From 2f845c2a1eaa57596ad692f7e226a1abf72ec8f8 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 3 Dec 2013 00:03:13 -0500 Subject: [PATCH 038/202] Add @hcarvalhoalves to AUTHORS. We fucked up that PR :) --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 311903b..85fdbd4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,3 +18,4 @@ * Ralph Möritz * Josh McLaughlin * Berker Peksag +* Henrique Carvalho Alves From f9a1995be8641867612761e95b66200b103823e1 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Fri, 11 Oct 2013 08:09:28 +0200 Subject: [PATCH 039/202] Make HyObject.replace more resilient This makes it work on quoted objects --- hy/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/models/__init__.py b/hy/models/__init__.py index 42032ca..31820f1 100644 --- a/hy/models/__init__.py +++ b/hy/models/__init__.py @@ -29,7 +29,7 @@ class HyObject(object): if isinstance(other, HyObject): for attr in ["start_line", "end_line", "start_column", "end_column"]: - if not hasattr(self, attr): + if not hasattr(self, attr) and hasattr(other, attr): setattr(self, attr, getattr(other, attr)) else: raise TypeError("Can't replace a non Hy object with a Hy object") From 2c76d8ec903e24904d6bf467ada8717d6a28c5dd Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 5 Dec 2013 15:00:13 +0200 Subject: [PATCH 040/202] Document command line options. --- docs/language/cli.rst | 35 +++++++++++++++++++++++++++++++++++ docs/language/index.rst | 1 + 2 files changed, 36 insertions(+) create mode 100644 docs/language/cli.rst diff --git a/docs/language/cli.rst b/docs/language/cli.rst new file mode 100644 index 0000000..a0169d0 --- /dev/null +++ b/docs/language/cli.rst @@ -0,0 +1,35 @@ +====================== +Command Line Interface +====================== + +Command line options +-------------------- + +.. cmdoption:: -c + + Execute the Hy code in *command*. + + .. code-block:: bash + + $ hy -c "(print (+ 2 2))" + 4 + +.. cmdoption:: -i + + Execute the Hy code in *command*, then stay in REPL. + +.. cmdoption:: --spy + + Print equivalent Hy code before executing. For example:: + + => (defn salutationsnm [name] (print (+ "Hy " name "!"))) + def salutationsnm(name): + return print(((u'Hy ' + name) + u'!')) + => (salutationsnm "YourName") + salutationsnm(u'YourName') + Hy YourName! + => + +.. cmdoption:: -v + + Print the Hy version number and exit. diff --git a/docs/language/index.rst b/docs/language/index.rst index dc06211..5132647 100644 --- a/docs/language/index.rst +++ b/docs/language/index.rst @@ -7,6 +7,7 @@ Contents: .. toctree:: :maxdepth: 3 + cli api core internals From 61112138ca097434333ae00c2d9eec580d79de5f Mon Sep 17 00:00:00 2001 From: "Joe H. Rahme" Date: Tue, 3 Dec 2013 09:51:51 +0100 Subject: [PATCH 041/202] Adds try-hy to the docs As requested in PR #350. This commit also adds Joe "rahmu" Hakim Rahme to the AUTHORS file --- AUTHORS | 1 + docs/index.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index 85fdbd4..1cb2182 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,3 +19,4 @@ * Josh McLaughlin * Berker Peksag * Henrique Carvalho Alves +* Joe Hakim Rahme diff --git a/docs/index.rst b/docs/index.rst index 102929a..a8ad82f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,8 @@ Meet our mascot, "Cuddles": .. \|\||/ +You can try Hy `in your browser `_. + Read more about Hy in these docs! We're also on IRC! Join From f1c68bd51a7940c9e047f38f8652728af7d0b45e Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Thu, 5 Dec 2013 03:29:49 +0530 Subject: [PATCH 042/202] Allow macros to return None, fixes #357 Allows Hy macros to return None, to test this (defmacro foo []) (foo) Should work now --- hy/macros.py | 3 ++- tests/native_tests/native_macros.hy | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hy/macros.py b/hy/macros.py index 20d0cac..d3f08d8 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -84,7 +84,8 @@ _wrappers = { complex: HyComplex, str_type: HyString, dict: lambda d: HyDict(_wrap_value(x) for x in sum(d.items(), ())), - list: lambda l: HyList(_wrap_value(x) for x in l) + list: lambda l: HyList(_wrap_value(x) for x in l), + type(None): lambda foo: HySymbol("None"), } diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index f5b1b74..b9f1cf0 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -34,6 +34,9 @@ (defmacro a-dict [] {1 2}) (assert (= (a-dict) {1 2})) +(defmacro a-none []) +(assert (= (a-none) None)) + ; A macro calling a previously defined function (eval-when-compile (defn foo [x y] From ecd13ef6594326456e9ff42d1041291f937f66f5 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Fri, 6 Dec 2013 07:48:50 +0200 Subject: [PATCH 043/202] Add versionadded directive to cli.rst. --- docs/language/cli.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index a0169d0..4bbca65 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -30,6 +30,8 @@ Command line options Hy YourName! => + .. versionadded:: 0.9.11 + .. cmdoption:: -v Print the Hy version number and exit. From 8970662dcb694d0bdc585f8b213c03c1b64c6b9a Mon Sep 17 00:00:00 2001 From: Kevin Zita Date: Sat, 7 Dec 2013 18:23:45 -0500 Subject: [PATCH 044/202] Update requirements.txt and setup.py to use rply upstream. Closes #364. --- requirements.txt | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index ef71c52..ab8080a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ # Check site / dev for more deps! flake8 --e git+https://github.com/hylang/rply.git#egg=rply +rply>=0.7.0 diff --git a/setup.py b/setup.py index 24621ee..e8e370e 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ long_description = """Hy is a Python <--> Lisp layer. It helps make things work nicer, and lets Python and the Hy lisp variant play nice together. """ -install_requires = ['rply>=0.6.2'] +install_requires = ['rply>=0.7.0'] if sys.version_info[:2] < (2, 7): install_requires.append('argparse>=1.2.1') install_requires.append('importlib>=1.0.2') @@ -56,7 +56,6 @@ setup( name=PKG, version=__version__, install_requires=install_requires, - dependency_links=['https://github.com/hylang/rply/zipball/master#egg=rply-0.6.2'], entry_points={ 'console_scripts': [ 'hy = hy.cmdline:hy_main', From f913c2aa8c64a14854d7a5fce919a3ae7a579d5e Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Mon, 18 Nov 2013 07:58:40 +0100 Subject: [PATCH 045/202] meth wasn't accepting correctly its parameters Now also accepts more than 1 statement in the code part of the macro refactored to use only 1 codebase (no more route != route-with-macros) --- hy/contrib/meth.hy | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index 1f4ae70..b6301f2 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -1,31 +1,30 @@ ;;; Meth ;; based on paultag's meth library to access a Flask based application -(defmacro route [name path params code] - "Default get request" - `(let [[deco (.route app ~path)]] - (with-decorator deco - (defn ~name ~params ~@code)))) - -(defmacro route-with-methods [name path params code methods] +(defmacro route-with-methods [name path methods params &rest code] "Same as route but with an extra methods array to specify HTTP methods" `(let [[deco (kwapply (.route app ~path) {"methods" ~methods})]] (with-decorator deco - (defn ~name ~params ~@code)))) + (defn ~name ~params + (progn ~@code))))) ;; Some macro examples -(defmacro post-route [name path params code] +(defmacro route [name path params &rest code] "Post request" - `(route-with-methods ~name ~path ~params ~code ["POST"])) + `(route-with-methods ~name ~path ["GET"] ~params ~@code)) -(defmacro put-route [name path params code] +(defmacro post-route [name path params &rest code] + "Post request" + `(route-with-methods ~name ~path ["POST"] ~params ~@code)) + +(defmacro put-route [name path params &rest code] "Put request" - `(route-with-methods ~name ~path ~params ~code ["PUT"])) + `(route-with-methods ~name ~path ["PUT"] ~params ~@code)) -(defmacro delete-route [name path params code] +(defmacro delete-route [name path params &rest code] "Delete request" - `(route-with-methods ~name ~path ~params ~code ["DELETE"])) + `(route-with-methods ~name ~path ["DELETE"] ~params ~@code)) ;;; Simple example application @@ -34,7 +33,7 @@ ;; (import [flask [Flask]]) ;; (setv app (Flask "__main__")) -;; (require methy) +;; (require hy.contrib.meth) ;; (print "setup / with GET") ;; (route get-index "/" [] (str "Hy world!")) From aeb0cd4c72ced7aebb0edb5025bbaaecbb684cba Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Mon, 9 Dec 2013 11:49:17 +0100 Subject: [PATCH 046/202] Fixed docstring --- hy/contrib/meth.hy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index b6301f2..18203ea 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -11,7 +11,7 @@ ;; Some macro examples (defmacro route [name path params &rest code] - "Post request" + "Get request" `(route-with-methods ~name ~path ["GET"] ~params ~@code)) (defmacro post-route [name path params &rest code] From 870c136469d41fb594be1d70bf901ad2776e74a0 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 16 Nov 2013 00:19:18 +0200 Subject: [PATCH 047/202] Add astor to install_requires. `hy --spy` fails on hy 0.9.11. $ hy --spy hy 0.9.11 => (type "hy") Traceback (most recent call last): File "/usr/local/lib/python2.7/dist-packages/hy/cmdline.py", line 68, in print_python_code import astor.codegen ImportError: No module named astor.codegen --- setup.py | 2 +- tests/test_bin.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/setup.py b/setup.py index e8e370e..1808022 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ long_description = """Hy is a Python <--> Lisp layer. It helps make things work nicer, and lets Python and the Hy lisp variant play nice together. """ -install_requires = ['rply>=0.7.0'] +install_requires = ['rply>=0.7.0', 'astor>=0.3'] if sys.version_info[:2] < (2, 7): install_requires.append('argparse>=1.2.1') install_requires.append('importlib>=1.0.2') diff --git a/tests/test_bin.py b/tests/test_bin.py index 155e06a..0eab72e 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -22,7 +22,6 @@ # DEALINGS IN THE SOFTWARE. import os import subprocess -import sys def run_cmd(cmd, stdin_data=None): @@ -116,10 +115,6 @@ def test_bin_hyc_missing_file(): def test_hy2py(): - # XXX Astor doesn't seem to support Python3 :( - if sys.version_info[0] == 3: - return - # and running this script this way doesn't work on Windows if os.name == "nt": return From 3e8941cdde01635890db524c4789f0640fe665c3 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 10 Dec 2013 18:59:06 +0200 Subject: [PATCH 048/202] Convert stdout and stderr to UTF-8 properly in the run_cmd helper. --- tests/test_bin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_bin.py b/tests/test_bin.py index 0eab72e..55a23b2 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -39,8 +39,8 @@ def run_cmd(cmd, stdin_data=None): # Read stdout and stderr otherwise if the PIPE buffer is full, we might # wait for ever… while p.poll() is None: - stdout += str(p.stdout.read()) - stderr += str(p.stderr.read()) + stdout += p.stdout.read().decode('utf-8') + stderr += p.stderr.read().decode('utf-8') return p.returncode, stdout, stderr From e674eb4b56ae49588e891d5fcd591f71c71fe75e Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 10 Dec 2013 19:41:49 +0200 Subject: [PATCH 049/202] Explicitly skip the skipped test in test_bin. --- tests/test_bin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_bin.py b/tests/test_bin.py index 155e06a..4216662 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -24,6 +24,8 @@ import os import subprocess import sys +from nose.plugins.skip import SkipTest + def run_cmd(cmd, stdin_data=None): p = subprocess.Popen(cmd, @@ -122,7 +124,7 @@ def test_hy2py(): # and running this script this way doesn't work on Windows if os.name == "nt": - return + raise SkipTest("doesn't work on Windows") i = 0 for dirpath, dirnames, filenames in os.walk("tests/native_tests"): From f72ff53f4177a877fa697f6f5242a9f74cba80db Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Sun, 15 Dec 2013 11:42:57 +0530 Subject: [PATCH 050/202] Operators + and * work without args, fixes #372 Like other lisps, operators `+` and `*` return their identity values when called with no arguments. Also with a single operand they return the operand. This fixes #372 --- hy/compiler.py | 19 +++++++++++++++++-- tests/native_tests/math.hy | 8 ++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 67918e0..cd50fa6 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1343,11 +1343,9 @@ class HyASTCompiler(object): lineno=e.start_line, col_offset=e.start_column) - @builds("+") @builds("%") @builds("/") @builds("//") - @builds("*") @builds("**") @builds("<<") @builds(">>") @@ -1384,6 +1382,23 @@ class HyASTCompiler(object): col_offset=child.start_column) return ret + @builds("+") + @builds("*") + def compile_maths_expression_mul(self, expression): + if len(expression) > 2: + return self.compile_maths_expression(expression) + else: + id_op = {"+": HyInteger(0), "*": HyInteger(1)} + + op = expression.pop(0) + arg = expression.pop(0) if expression else id_op[op] + expr = HyExpression([ + HySymbol(op), + id_op[op], + arg + ]).replace(expression) + return self.compile_maths_expression(expr) + @builds("-") @checkargs(min=1) def compile_maths_expression_sub(self, expression): diff --git a/tests/native_tests/math.hy b/tests/native_tests/math.hy index eda77b0..0824876 100644 --- a/tests/native_tests/math.hy +++ b/tests/native_tests/math.hy @@ -8,7 +8,9 @@ (setv test_mult (fn [] "NATIVE: Test multiplication." - (assert (= 4 (square 2))))) + (assert (= 4 (square 2))) + (assert (= 8 (* 8))) + (assert (= 1 (*))))) (setv test_sub (fn [] @@ -19,7 +21,9 @@ (setv test_add (fn [] "NATIVE: Test addition" - (assert (= 4 (+ 1 1 1 1))))) + (assert (= 4 (+ 1 1 1 1))) + (assert (= 8 (+ 8))) + (assert (= 0 (+))))) (setv test_div (fn [] From f5d88bb108bb3d5ffa8e54d5f858489f0bbe466f Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sat, 14 Dec 2013 17:33:56 -0700 Subject: [PATCH 051/202] gensym in Hy Simple implementation of gensym in Hy. Returns a new HySymbol. Usable in macros like: (defmacro nif [expr pos zero neg] (let [[g (gensym)]] `(let [[~g ~expr]] (cond [(pos? ~g) ~pos] [(zero? ~g) ~zero] [(neg? ~g) ~neg])))) This addresses all the general comments about (gensym), and doesn't try to implement "auto-gensym" yet. But clearly the macro approach instead of the pre-processor approach (as described in the letoverlambda (http://letoverlambda.com/index.cl/guest/chap3.html#sec_5) is the way to go --- hy/core/language.hy | 16 ++++++++++++++++ hy/core/macros.hy | 6 +++--- tests/native_tests/core.hy | 13 +++++++++++++ tests/native_tests/native_macros.hy | 26 +++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 04b2705..e1b8083 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -23,6 +23,7 @@ ;;;; to make functional programming slightly easier. ;;;; + (import [hy._compat [long-type]]) ; long for python2, int for python3 (defn _numeric-check [x] @@ -91,6 +92,20 @@ "Return True if x is float" (isinstance x float)) +(import [threading [Lock]]) +(setv _gensym_counter 1234) +(setv _gensym_lock (Lock)) + +(defn gensym [&optional [g "G"]] + (let [[new_symbol None]] + (global _gensym_counter) + (global _gensym_lock) + (.acquire _gensym_lock) + (try (do (setv _gensym_counter (inc _gensym_counter)) + (setv new_symbol (HySymbol (.format ":{0}_{1}" g _gensym_counter)))) + (finally (.release _gensym_lock))) + new_symbol)) + (defn inc [n] "Increment n by 1" (_numeric-check n) @@ -223,6 +238,7 @@ (= n 0)) (def *exports* '[cycle dec distinct drop drop-while empty? even? filter float? + gensym inc instance? integer integer? iterable? iterate iterator? neg? none? nth numeric? odd? pos? remove repeat repeatedly second string string? take take-nth take-while zero?]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 17792f9..79c7983 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -113,6 +113,6 @@ (defmacro yield-from [iterable] "Yield all the items from iterable" - ;; TODO: this needs some gensym love - `(foreach [_hy_yield_from_x ~iterable] - (yield _hy_yield_from_x))) + (let [[x (gensym)]] + `(foreach [~x ~iterable] + (yield ~x)))) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 2f6b717..39dee93 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -141,6 +141,18 @@ (assert-true (float? -3.2)) (assert-false (float? "foo"))) +(defn test-gensym [] + "NATIVE: testing the gensym function" + (import [hy.models.symbol [HySymbol]]) + (setv s1 (gensym)) + (assert (isinstance s1 HySymbol)) + (assert (= 0 (.find s1 ":G_"))) + (setv s2 (gensym "xx")) + (setv s3 (gensym "xx")) + (assert (= 0 (.find s2 ":xx_"))) + (assert (not (= s2 s3))) + (assert (not (= (str s2) (str s3))))) + (defn test-inc [] "NATIVE: testing the inc function" (assert-equal 3 (inc 2)) @@ -393,3 +405,4 @@ (assert-equal res [None None]) (setv res (list (take-while (fn [x] (not (none? x))) [1 2 3 4 None 5 6 None 7]))) (assert-equal res [1 2 3 4])) + diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index b9f1cf0..ea252e5 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -94,7 +94,6 @@ (assert initialized) (assert (test-initialized)) - (defn test-yield-from [] "NATIVE: testing yield from" (defn yield-from-test [] @@ -107,3 +106,28 @@ (import sys) (assert (= (get sys.version_info 0) (if-python2 2 3)))) + +(defn test-gensym-in-macros [] + (import ast) + (import [astor.codegen [to_source]]) + (import [hy.importer [import_buffer_to_ast]]) + (setv macro1 "(defmacro nif [expr pos zero neg] + (let [[g (gensym)]] + `(let [[~g ~expr]] + (cond [(pos? ~g) ~pos] + [(zero? ~g) ~zero] + [(neg? ~g) ~neg])))) + + (print (nif (inc -1) 1 0 -1)) + ") + ;; expand the macro twice, should use a different + ;; gensym each time + (setv _ast1 (import_buffer_to_ast macro1 "foo")) + (setv _ast2 (import_buffer_to_ast macro1 "foo")) + (setv s1 (to_source _ast1)) + (setv s2 (to_source _ast2)) + ;; and make sure there is something new that starts with :G_ + (assert (in ":G_" s1)) + (assert (in ":G_" s2)) + ;; but make sure the two don't match each other + (assert (not (= s1 s2)))) From c80e3c75a04d07c42987d0881a20d42f62af4075 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sun, 15 Dec 2013 18:47:46 -0700 Subject: [PATCH 052/202] Adding automatic gensym macro Adding to the manual gensym for macros are 2 new macros, but very literal from the CL in letoverlambda. The first is the (with-gensyms ...) macro that can generate a small set of syms for a macro. Works something like: (defmacro adder2 [A B] (with-gensyms [a b] `(let [[~a ~A] [~b ~B]] (+ ~a ~b)))) and ~a and ~b will be replaced with (gensym "a") and (gensym "b") respectively. Then the final macro is a new defmacro that will automatically replace symbols prefaced with "g!" with a new gensym based on the rest of the symbol. So in this final version of 'nif': (defmacro/g! nif4 (expr pos zero neg) `(let [[~g!result ~expr]] (cond [(pos? ~g!result) ~pos] [(zero? ~g!result) ~zero] [(neg? ~g!result) ~neg]))) all uses of ~g!result will be replaced with (gensym "result"). --- hy/core/language.hy | 17 +++++++++-- hy/core/macros.hy | 10 +++++++ tests/native_tests/core.hy | 20 +++++++++++++ tests/native_tests/native_macros.hy | 45 +++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index e1b8083..d112853 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -88,6 +88,19 @@ (if (pred val) (yield val))))) +(defn flatten [coll] + "Return a single flat list expanding all members of coll" + (if (and (iterable? coll) (not (string? coll))) + (_flatten coll []) + (raise (TypeError (.format "{0!r} is not a collection" coll))))) + +(defn _flatten [coll result] + (if (and (iterable? coll) (not (string? coll))) + (do (foreach [b coll] + (_flatten b result))) + (.append result coll)) + result) + (defn float? [x] "Return True if x is float" (isinstance x float)) @@ -237,8 +250,8 @@ (_numeric_check n) (= n 0)) -(def *exports* '[cycle dec distinct drop drop-while empty? even? filter float? - gensym +(def *exports* '[cycle dec distinct drop drop-while empty? even? filter flatten + float? gensym inc instance? integer integer? iterable? iterate iterator? neg? none? nth numeric? odd? pos? remove repeat repeatedly second string string? take take-nth take-while zero?]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 79c7983..a82bceb 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -116,3 +116,13 @@ (let [[x (gensym)]] `(foreach [~x ~iterable] (yield ~x)))) + +(defmacro with-gensyms [args &rest body] + `(let ~(HyList (map (fn [x] `[~x (gensym '~x)]) args)) + ~@body)) + +(defmacro defmacro/g! [name args &rest body] + (let [[syms (list (distinct (filter (fn [x] (.startswith x "g!")) (flatten body))))]] + `(defmacro ~name [~@args] + (let ~(HyList (map (fn [x] `[~x (gensym (slice '~x 2))]) syms)) + ~@body)))) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 39dee93..aedf99c 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -133,6 +133,26 @@ (setv res (list (filter none? [1 2 None 3 4 None 4 6]))) (assert-equal res [None None])) +(defn test-flatten [] + "NATIVE: testing the flatten function" + (setv res (flatten [1 2 [3 4] 5])) + (assert-equal res [1 2 3 4 5]) + (setv res (flatten ["foo" (, 1 2) [1 [2 3] 4] "bar"])) + (assert-equal res ["foo" 1 2 1 2 3 4 "bar"]) + (setv res (flatten [1])) + (assert-equal res [1]) + (setv res (flatten [])) + (assert-equal res []) + (setv res (flatten (, 1))) + (assert-equal res [1]) + ;; test with None + (setv res (flatten (, 1 (, None 3)))) + (assert-equal res [1 None 3]) + (try (flatten "foo") + (catch [e [TypeError]] (assert (in "not a collection" (str e))))) + (try (flatten 12.34) + (catch [e [TypeError]] (assert (in "not a collection" (str e)))))) + (defn test-float? [] "NATIVE: testing the float? function" (assert-true (float? 4.2)) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index ea252e5..911a9c9 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -131,3 +131,48 @@ (assert (in ":G_" s2)) ;; but make sure the two don't match each other (assert (not (= s1 s2)))) + +(defn test-with-gensym [] + (import ast) + (import [astor.codegen [to_source]]) + (import [hy.importer [import_buffer_to_ast]]) + (setv macro1 "(defmacro nif [expr pos zero neg] + (with-gensyms [a] + `(let [[~a ~expr]] + (cond [(pos? ~a) ~pos] + [(zero? ~a) ~zero] + [(neg? ~a) ~neg])))) + + (print (nif (inc -1) 1 0 -1)) + ") + ;; expand the macro twice, should use a different + ;; gensym each time + (setv _ast1 (import_buffer_to_ast macro1 "foo")) + (setv _ast2 (import_buffer_to_ast macro1 "foo")) + (setv s1 (to_source _ast1)) + (setv s2 (to_source _ast2)) + (assert (in ":a_" s1)) + (assert (in ":a_" s2)) + (assert (not (= s1 s2)))) + +(defn test-defmacro-g! [] + (import ast) + (import [astor.codegen [to_source]]) + (import [hy.importer [import_buffer_to_ast]]) + (setv macro1 "(defmacro/g! nif [expr pos zero neg] + `(let [[~g!res ~expr]] + (cond [(pos? ~g!res) ~pos] + [(zero? ~g!res) ~zero] + [(neg? ~g!res) ~neg]))) + + (print (nif (inc -1) 1 0 -1)) + ") + ;; expand the macro twice, should use a different + ;; gensym each time + (setv _ast1 (import_buffer_to_ast macro1 "foo")) + (setv _ast2 (import_buffer_to_ast macro1 "foo")) + (setv s1 (to_source _ast1)) + (setv s2 (to_source _ast2)) + (assert (in ":res_" s1)) + (assert (in ":res_" s2)) + (assert (not (= s1 s2)))) From 58313884b27a6cc4ea04bb73ce15bb3adeef78a9 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Tue, 3 Dec 2013 21:44:11 +0530 Subject: [PATCH 053/202] Added information about core team members Signed-off-by: Vasudev Kamath --- docs/coreteam.rst | 13 +++++++++++++ docs/hacking.rst | 8 ++++++++ 2 files changed, 21 insertions(+) create mode 100644 docs/coreteam.rst diff --git a/docs/coreteam.rst b/docs/coreteam.rst new file mode 100644 index 0000000..7ebead2 --- /dev/null +++ b/docs/coreteam.rst @@ -0,0 +1,13 @@ + - `Paul Richard Tagliamonte `_ + - `J Kenneth King `_ + - `Julien Danjou `_ + - `Nicolas Dandrimont `_ + - `Morten Linderud `_ + - `Gergely Nagy `_ + - `Karen Rustad `_ + - `Berker Peksag `_ + - `Christopher Allan Webber `_ + - `Bob Tolbert `_ + - `Tuukka Turto `_ + - `Will Kahn-Greene `_ + - `Konrad Hinsen `_ diff --git a/docs/hacking.rst b/docs/hacking.rst index 08a1fe6..6ee479c 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -99,3 +99,11 @@ core team. Additional review is clearly welcome, but we need a minimum of If a core member is sending in a PR, please find 2 core members that don't include them PR submitter. The idea here is that one can work with the PR author, and a second acks the entire change set. + + +Core Team +========= + +Core development team of hy consists of following developers. + +.. include:: coreteam.rst From abcd97b1f1aba334a034c7cbfdd2843292a36c46 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 17 Dec 2013 14:41:55 +0200 Subject: [PATCH 054/202] Add update_coreteam script. --- docs/coreteam.rst | 27 ++++++++++++----------- scripts/update_coreteam.py | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 scripts/update_coreteam.py diff --git a/docs/coreteam.rst b/docs/coreteam.rst index 7ebead2..02b9c30 100644 --- a/docs/coreteam.rst +++ b/docs/coreteam.rst @@ -1,13 +1,14 @@ - - `Paul Richard Tagliamonte `_ - - `J Kenneth King `_ - - `Julien Danjou `_ - - `Nicolas Dandrimont `_ - - `Morten Linderud `_ - - `Gergely Nagy `_ - - `Karen Rustad `_ - - `Berker Peksag `_ - - `Christopher Allan Webber `_ - - `Bob Tolbert `_ - - `Tuukka Turto `_ - - `Will Kahn-Greene `_ - - `Konrad Hinsen `_ +* `Julien Danjou `_ +* `Morten Linderud `_ +* `J Kenneth King `_ +* `Gergely Nagy `_ +* `Tuukka Turto `_ +* `Karen Rustad `_ +* `Abhishek L `_ +* `Christopher Allan Webber `_ +* `Konrad Hinsen `_ +* `Will Kahn-Greene `_ +* `Paul Tagliamonte `_ +* `Nicolas Dandrimont `_ +* `Bob Tolbert `_ +* `Berker Peksag `_ diff --git a/scripts/update_coreteam.py b/scripts/update_coreteam.py new file mode 100644 index 0000000..dda9754 --- /dev/null +++ b/scripts/update_coreteam.py @@ -0,0 +1,45 @@ +""" +You need to install the requests package first:: + + $ pip install requests + +""" + +import os.path +import requests + +API_URL = 'https://api.github.com/%s' + +RST_FORMAT = '* `%s <%s>`_' +MISSING_NAMES = { + 'khinsen': 'Konrad Hinsen', +} +# We have three concealed members on the hylang organization +# and GitHub only shows public members if the requester is not +# an owner of the organization. +CONCEALED_MEMBERS = [ + ('aldeka', 'Karen Rustad'), + ('rwtolbert', 'Bob Tolbert'), + ('tuturto', 'Tuukka Turto'), +] + + +def get_dev_name(login): + name = requests.get(API_URL % 'users/' + login).json()['name'] + if not name: + return MISSING_NAMES.get(login) + return name + +coredevs = requests.get(API_URL % 'orgs/hylang/members') + +result = set() +for dev in coredevs.json(): + result.add(RST_FORMAT % (get_dev_name(dev['login']), dev['html_url'])) + +for login, name in CONCEALED_MEMBERS: + result.add(RST_FORMAT % (name, 'https://github.com/' + login)) + +filename = os.path.abspath(os.path.join(os.path.pardir, + 'docs', 'coreteam.rst')) +with open(filename, 'w+') as fobj: + fobj.write('\n'.join(result) + '\n') From 3204c2a574070b416c1f9848527dec0f1bb89790 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Fri, 20 Dec 2013 07:23:35 -0700 Subject: [PATCH 055/202] Fix for readline REPL problem Darwin Python3 On OS X Mavericks, the Hy REPL doesn't allow the 'b' character when running with Python 3.3. This appears to be a common problem for Darwin and readline as mentioned in this post http://superuser.com/questions/297527/cant-type-the-b-letter-in-python-shell-in-os-x --- hy/completer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/completer.py b/hy/completer.py index d32deaf..05ac88f 100644 --- a/hy/completer.py +++ b/hy/completer.py @@ -41,7 +41,7 @@ except ImportError: except ImportError: docomplete = False -if sys.platform == 'darwin': +if sys.platform == 'darwin' and 'libedit' in readline.__doc__: readline_bind = "bind ^I rl_complete" else: readline_bind = "tab: complete" From 0109234eb7fea7a3e5307b0ecbae09e7c8975749 Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Fri, 20 Dec 2013 20:29:48 +0100 Subject: [PATCH 056/202] Added meth tests by mocking a Flask app --- tests/__init__.py | 1 + tests/contrib/__init__.hy | 0 tests/contrib/test_meth.hy | 54 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/contrib/__init__.hy create mode 100644 tests/contrib/test_meth.hy diff --git a/tests/__init__.py b/tests/__init__.py index 5e935a2..c58d7b4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,3 +11,4 @@ from .native_tests.unless import * # noqa from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa +from .contrib.test_meth import * # noqa diff --git a/tests/contrib/__init__.hy b/tests/contrib/__init__.hy new file mode 100644 index 0000000..e69de29 diff --git a/tests/contrib/test_meth.hy b/tests/contrib/test_meth.hy new file mode 100644 index 0000000..2f13cfd --- /dev/null +++ b/tests/contrib/test_meth.hy @@ -0,0 +1,54 @@ +(require hy.contrib.meth) + +(defclass FakeMeth [] + "Mocking decorator class" + [[rules {}] + [route (fn [self rule &kwargs options] + (fn [f] + (assoc self.rules rule (, f options)) + f))]]) + + +(defn test_route [] + (let [[app (FakeMeth)]] + (route get-index "/" [] (str "Hy world!")) + (setv app-rules (getattr app "rules")) + (assert (in "/" app-rules)) + (let [[(, rule-fun rule-opt) (get app-rules "/")]] + (assert (not (empty? rule-opt))) + (assert (in "GET" (get rule-opt "methods"))) + (assert (= (getattr rule-fun "__name__") "get_index")) + (assert (= "Hy world!" (rule-fun)))))) + +(defn test_post_route [] + (let [[app (FakeMeth)]] + (post-route get-index "/" [] (str "Hy world!")) + (setv app-rules (getattr app "rules")) + (assert (in "/" app-rules)) + (let [[(, rule-fun rule-opt) (get app-rules "/")]] + (assert (not (empty? rule-opt))) + (assert (in "POST" (get rule-opt "methods"))) + (assert (= (getattr rule-fun "__name__") "get_index")) + (assert (= "Hy world!" (rule-fun)))))) + +(defn test_put_route [] + (let [[app (FakeMeth)]] + (put-route get-index "/" [] (str "Hy world!")) + (setv app-rules (getattr app "rules")) + (assert (in "/" app-rules)) + (let [[(, rule-fun rule-opt) (get app-rules "/")]] + (assert (not (empty? rule-opt))) + (assert (in "PUT" (get rule-opt "methods"))) + (assert (= (getattr rule-fun "__name__") "get_index")) + (assert (= "Hy world!" (rule-fun)))))) + +(defn test_delete_route [] + (let [[app (FakeMeth)]] + (delete-route get-index "/" [] (str "Hy world!")) + (setv app-rules (getattr app "rules")) + (assert (in "/" app-rules)) + (let [[(, rule-fun rule-opt) (get app-rules "/")]] + (assert (not (empty? rule-opt))) + (assert (in "DELETE" (get rule-opt "methods"))) + (assert (= (getattr rule-fun "__name__") "get_index")) + (assert (= "Hy world!" (rule-fun)))))) From 0f96c249659a90324524fa016af1985ee90b90c8 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 21 Dec 2013 23:33:14 +0100 Subject: [PATCH 057/202] Make _storeize generic This allows to change an expression's context to something else than ast.Store if needed. --- hy/compiler.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 67918e0..90550bd 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -534,18 +534,21 @@ class HyASTCompiler(object): return ret, args, defaults, varargs, kwargs - def _storeize(self, name): + def _storeize(self, 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 to a non-expr") + raise TypeError("Can't assign / 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)) + new_elts.append(self._storeize(x, func)) new_name = typ(elts=new_elts) elif isinstance(name, ast.Name): new_name = ast.Name(id=name.id, arg=name.arg) @@ -554,9 +557,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 to a %s object" % type(name)) + raise TypeError("Can't assign / delete a %s object" % type(name)) - new_name.ctx = ast.Store() + new_name.ctx = func() ast.copy_location(new_name, name) return new_name From 799c39ffad8e51f99d2c9ddea617ec95c68a356f Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 21 Dec 2013 23:33:44 +0100 Subject: [PATCH 058/202] Implement del Closes #385. --- docs/language/api.rst | 33 +++++++++++++++++++++++++++++++++ hy/compiler.py | 15 +++++++++++++++ tests/native_tests/language.hy | 15 +++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 2598b57..9934b4d 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -378,6 +378,39 @@ between the operands. => (infix (1 + 1)) 2 +del +--- + +.. versionadded:: 0.9.12 + +`del` removes an object from the current namespace. + +.. code-block:: clj + + => (setv foo 42) + => (del foo) + => foo + Traceback (most recent call last): + File "", line 1, in + NameError: name 'foo' is not defined + +`del` can also remove objects from a mapping, a list, ... + +.. code-block:: clj + + => (setv test (list (range 10))) + => test + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + => (del (slice test 2 4)) ;; remove items from 2 to 4 excluded + => test + [0, 1, 4, 5, 6, 7, 8, 9] + => (setv dic {"foo" "bar"}) + => dic + {"foo": "bar"} + => (del (get dic "foo")) + => dic + {} + eval ---- diff --git a/hy/compiler.py b/hy/compiler.py index 90550bd..b716d31 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1099,6 +1099,21 @@ class HyASTCompiler(object): slice=ast.Index(value=sli.force_expr), ctx=ast.Load()) + @builds("del") + @checkargs(min=1) + def compile_del_expression(self, expr): + expr.pop(0) + ld_targets, ret = self._compile_collect(expr) + + del_targets = [] + for target in ld_targets: + del_targets.append(self._storeize(target, ast.Del)) + + return ret + ast.Delete( + lineno=expr.start_line, + col_offset=expr.start_column, + targets=del_targets) + @builds("slice") @checkargs(min=1, max=4) def compile_slice_expression(self, expr): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4d841a0..ca1de7d 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -789,3 +789,18 @@ (assert (string? (string "a"))) (assert (string? (string 1))) (assert (= u"unicode" (string "unicode")))) + +(defn test-del [] + "NATIVE: Test the behavior of del" + (setv foo 42) + (assert (= foo 42)) + (del foo) + (assert (= 'good + (try + (do foo 'bad) + (except [NameError] 'good)))) + (setv test (list (range 5))) + (del (get test 4)) + (assert (= test [0 1 2 3])) + (del (get test 2)) + (assert (= test [0 1 3]))) From 1a701d4dc497beeb12ff894dc815384341c8b2dc Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 22 Dec 2013 20:04:12 +0100 Subject: [PATCH 059/202] Whitespace fix --- tests/native_tests/language.hy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 5999ac6..6a0e8dd 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -794,9 +794,9 @@ (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [x (range 10)] - (if (!= x 5) - (continue)) + (for [x (range 10)] + (if (!= x 5) + (continue)) (.append y x)) (assert (= y [5]))) From 27691e4d821473cf0984c381cea990e0b944acdf Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 23 Dec 2013 08:12:22 +0200 Subject: [PATCH 060/202] Remove astor from requirements-dev.txt. This is not necessary since 870c136. --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index abeab4f..a2797cf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,4 @@ -r requirements.txt -astor tox nose Sphinx From c9fdd40c9f1537e55bb334c49d7aa21e3e29f315 Mon Sep 17 00:00:00 2001 From: Foxboron Date: Sun, 15 Dec 2013 17:47:24 +0100 Subject: [PATCH 061/202] Hy reader macros #377 Added first iteration of reader macros Refactored defmacro and defreader Added test inn hy/tests/lex/test_lex.py Added new test in hy/tests/native/tests Added new test in hy/tests/macros. changed the error given in the dispatch macro and added some handling for missing symbol and invalid characters --- hy/compiler.py | 46 +++++++++++++++++++++++------ hy/core/macros.hy | 11 ++++++- hy/lex/lexer.py | 1 + hy/lex/parser.py | 9 ++++++ hy/macros.py | 31 +++++++++++++++++++ tests/__init__.py | 1 + tests/lex/test_lex.py | 8 +++++ tests/macros/test_reader_macros.py | 11 +++++++ tests/native_tests/reader_macros.hy | 32 ++++++++++++++++++++ 9 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 tests/macros/test_reader_macros.py create mode 100644 tests/native_tests/reader_macros.hy diff --git a/hy/compiler.py b/hy/compiler.py index 8d848b6..36dd12a 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -36,6 +36,7 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict +import hy.macros from hy.macros import require, macroexpand from hy._compat import str_type, long_type import hy.importer @@ -1753,6 +1754,19 @@ class HyASTCompiler(object): bases=bases_expr, body=body.stmts) + def _compile_time_hack(self, expression): + """Compile-time hack: we want to get our new macro now + We must provide __name__ in the namespace to make the Python + compiler set the __module__ attribute of the macro function.""" + hy.importer.hy_eval(expression, + compile_time_ns(self.module_name), + self.module_name) + + # We really want to have a `hy` import to get hy.macro in + ret = self.compile(expression) + ret.add_imports('hy', [None]) + return ret + @builds("defmacro") @checkargs(min=1) def compile_macro(self, expression): @@ -1768,16 +1782,30 @@ class HyASTCompiler(object): HyExpression([HySymbol("fn")] + expression), ]).replace(expression) - # Compile-time hack: we want to get our new macro now - # We must provide __name__ in the namespace to make the Python - # compiler set the __module__ attribute of the macro function. - hy.importer.hy_eval(new_expression, - compile_time_ns(self.module_name), - self.module_name) + ret = self._compile_time_hack(new_expression) - # We really want to have a `hy` import to get hy.macro in - ret = self.compile(new_expression) - ret.add_imports('hy', [None]) + return ret + + @builds("defreader") + @checkargs(min=2, max=3) + def compile_reader(self, expression): + expression.pop(0) + name = expression.pop(0) + NOT_READERS = [":", "&"] + if name in NOT_READERS: + raise NameError("%s can't be used as a macro reader symbol" % name) + if not isinstance(name, HySymbol): + raise HyTypeError(name, + ("received a `%s' instead of a symbol " + "for reader macro name" % type(name).__name__)) + name = HyString(name).replace(name) + new_expression = HyExpression([ + HySymbol("with_decorator"), + HyExpression([HySymbol("hy.macros.reader"), name]), + HyExpression([HySymbol("fn")] + expression), + ]).replace(expression) + + ret = self._compile_time_hack(new_expression) return ret diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 026fc86..d7c8b15 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -25,7 +25,6 @@ ;;; These macros form the hy language ;;; They are automatically required in every module, except inside hy.core - (defmacro for [args &rest body] "shorthand for nested foreach loops: (for [x foo y bar] baz) -> @@ -144,3 +143,13 @@ (setv -args (cdr (car -args)))) `(apply ~-fun [~@-args] (dict (sum ~-okwargs []))))) + + +(defmacro dispatch-reader-macro [char &rest body] + "Dispatch a reader macro based on the character" + (import [hy.macros]) + (setv str_char (get char 1)) + (if (not (in str_char hy.macros._hy_reader_chars)) + (raise (hy.compiler.HyTypeError char (.format "There is no reader macro with the character `{0}`" str_char)))) + `(do (import [hy.macros [_hy_reader]]) + ((get (get _hy_reader --name--) ~char) ~(get body 0)))) diff --git a/hy/lex/lexer.py b/hy/lex/lexer.py index ce3eae3..d4a5638 100644 --- a/hy/lex/lexer.py +++ b/hy/lex/lexer.py @@ -40,6 +40,7 @@ lg.add('QUASIQUOTE', r'`%s' % end_quote) lg.add('UNQUOTESPLICE', r'~@%s' % end_quote) lg.add('UNQUOTE', r'~%s' % end_quote) lg.add('HASHBANG', r'#!.*[^\r\n]') +lg.add('HASHREADER', r'#.') lg.add('STRING', r'''(?x) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index e4a3ece..70d43f8 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -150,6 +150,15 @@ def term_unquote_splice(p): return HyExpression([HySymbol("unquote_splice"), p[1]]) +@pg.production("term : HASHREADER term") +@set_quote_boundaries +def hash_reader(p): + st = p[0].getstr()[1] + str_object = HyExpression([HySymbol("quote"), HyString(st)]) + expr = HyExpression([HySymbol("quote"), p[1]]) + return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr]) + + @pg.production("dict : LCURLY list_contents RCURLY") @set_boundaries def t_dict(p): diff --git a/hy/macros.py b/hy/macros.py index 1074fc5..4e2389c 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -40,6 +40,8 @@ EXTRA_MACROS = [ ] _hy_macros = defaultdict(dict) +_hy_reader = defaultdict(dict) +_hy_reader_chars = set() def macro(name): @@ -63,6 +65,30 @@ def macro(name): return _ +def reader(name): + """Decorator to define a macro called `name`. + + This stores the macro `name` in the namespace for the module where it is + defined. + + If the module where it is defined is in `hy.core`, then the macro is stored + in the default `None` namespace. + + This function is called from the `defmacro` special form in the compiler. + + """ + def _(fn): + module_name = fn.__module__ + if module_name.startswith("hy.core"): + module_name = None + _hy_reader[module_name][name] = fn + + # Ugly hack to get some error handling + _hy_reader_chars.add(name) + return fn + return _ + + def require(source_module, target_module): """Load the macros from `source_module` in the namespace of `target_module`. @@ -75,6 +101,11 @@ def require(source_module, target_module): for name, macro in macros.items(): refs[name] = macro + readers = _hy_reader[source_module] + reader_refs = _hy_reader[target_module] + for name, reader in readers.items(): + reader_refs[name] = reader + # type -> wrapping function mapping for _wrap_value _wrappers = { diff --git a/tests/__init__.py b/tests/__init__.py index 5e935a2..582ceae 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,3 +11,4 @@ from .native_tests.unless import * # noqa from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa +from .native_tests.reader_macros import * # noqa diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index 359999b..bda1e3f 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -252,3 +252,11 @@ def test_complex(): assert entry == HyComplex("1.0j") entry = tokenize("(j)")[0][0] assert entry == HySymbol("j") + + +def test_reader_macro(): + """Ensure reader macros are handles properly""" + entry = tokenize("#^()") + assert entry[0][0] == HySymbol("dispatch_reader_macro") + assert entry[0][1] == HyExpression([HySymbol("quote"), HyString("^")]) + assert len(entry[0]) == 3 diff --git a/tests/macros/test_reader_macros.py b/tests/macros/test_reader_macros.py new file mode 100644 index 0000000..791a046 --- /dev/null +++ b/tests/macros/test_reader_macros.py @@ -0,0 +1,11 @@ +from hy.macros import macroexpand +from hy.compiler import HyTypeError +from hy.lex import tokenize + + +def test_reader_macro_error(): + """Check if we get correct error with wrong disptach character""" + try: + macroexpand(tokenize("(dispatch_reader_macro '- '())")[0], __name__) + except HyTypeError as e: + assert "with the character `-`" in str(e) diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy new file mode 100644 index 0000000..e43220b --- /dev/null +++ b/tests/native_tests/reader_macros.hy @@ -0,0 +1,32 @@ +(defn test-reader-macro [] + "Test a basic redaer macro" + (defreader ^ [expr] + expr) + + (assert (= #^"works" "works"))) + + +(defn test-reader-macro-expr [] + "Test basic exprs like lists and arrays" + (defreader n [expr] + (get expr 1)) + + (assert (= #n[1 2] 2)) + (assert (= #n(1 2) 2))) + + +(defn test-reader-macro-override [] + "Test if we can override function symbols" + (defreader + [n] + (+ n 1)) + + (assert (= #+2 3))) + + +(defn test-reader-macro-compile-docstring [] + "Test if we can compile with a docstring" + (try + (defreader d [] + "Compiles with docstrings") + (except [Exception] + (assert False)))) From 744cd71171dbdf3953f98d5a73d66eeee4453037 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Mon, 23 Dec 2013 15:31:36 +0100 Subject: [PATCH 062/202] Nuke the rply git checkout from tox.ini --- tox.ini | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tox.ini b/tox.ini index 9b17b7a..44c1903 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,7 @@ commands = nosetests deps = nose setuptools - -e - git+https://github.com/hylang/rply.git#egg=rply + rply [testenv:pypy] commands = nosetests @@ -14,8 +13,7 @@ deps = astor nose setuptools - -e - git+https://github.com/hylang/rply.git#egg=rply + rply [testenv:py27] commands = nosetests @@ -23,8 +21,7 @@ deps = astor nose setuptools - -e - git+https://github.com/hylang/rply.git#egg=rply + rply [testenv:py26] deps = @@ -33,12 +30,10 @@ deps = setuptools unittest2 importlib - -e - git+https://github.com/hylang/rply.git#egg=rply + rply [testenv:flake8] deps = flake8 - -e - git+https://github.com/hylang/rply.git#egg=rply + rply commands = flake8 hy bin tests From cfbc792957b0ace401e8e2febd01f41fdebb04e8 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Mon, 23 Dec 2013 19:38:10 +0100 Subject: [PATCH 063/202] Fix apply with method calls Apply didn't work on method calls (i.e. `(apply .foo [bar]) broke). This slipped through because there were no tests of this behavior. I noticed it while trying to merge the `meth` fixes. --- hy/compiler.py | 73 +++++++++++++++++++++++++++++----- tests/native_tests/language.hy | 13 ++++++ 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 36dd12a..7608553 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1278,26 +1278,77 @@ class HyASTCompiler(object): @checkargs(min=1, max=3) def compile_apply_expression(self, expr): expr.pop(0) # apply - call = self.compile(expr.pop(0)) - call = ast.Call(func=call.expr, - args=[], - keywords=[], - starargs=None, - kwargs=None, - lineno=expr.start_line, - col_offset=expr.start_column) - ret = call + + ret = Result() + + fun = expr.pop(0) + + # We actually defer the compilation of the function call to + # @builds(HyExpression), allowing us to work on method calls + call = HyExpression([fun]).replace(fun) + + if isinstance(fun, HySymbol) and fun.startswith("."): + # (apply .foo lst) needs to work as lst[0].foo(*lst[1:]) + if not expr: + raise HyTypeError( + expr, "apply of a method needs to have an argument" + ) + + # We need to grab the arguments, and split them. + + # Assign them to a variable if they're not one already + if type(expr[0]) == HyList: + if len(expr[0]) == 0: + raise HyTypeError( + expr, "apply of a method needs to have an argument" + ) + call.append(expr[0].pop(0)) + else: + if isinstance(expr[0], HySymbol): + tempvar = expr[0] + else: + tempvar = HySymbol(self.get_anon_var()).replace(expr[0]) + assignment = HyExpression( + [HySymbol("setv"), tempvar, expr[0]] + ).replace(expr[0]) + + # and add the assignment to our result + ret += self.compile(assignment) + + # The first argument is the object on which to call the method + # So we translate (apply .foo args) to (.foo (get args 0)) + call.append(HyExpression( + [HySymbol("get"), tempvar, HyInteger(0)] + ).replace(tempvar)) + + # We then pass the other arguments to the function + expr[0] = HyExpression( + [HySymbol("slice"), tempvar, HyInteger(1)] + ).replace(expr[0]) + + ret += self.compile(call) + + if not isinstance(ret.expr, ast.Call): + raise HyTypeError( + fun, "compiling the application of `{}' didn't return a " + "function call, but `{}'".format(fun, type(ret.expr).__name__) + ) + if ret.expr.starargs or ret.expr.kwargs: + raise HyTypeError( + expr, "compiling the function application returned a function " + "call with arguments" + ) if expr: stargs = expr.pop(0) if stargs is not None: stargs = self.compile(stargs) - call.starargs = stargs.force_expr + ret.expr.starargs = stargs.force_expr ret = stargs + ret if expr: kwargs = self.compile(expr.pop(0)) - call.kwargs = kwargs.force_expr + ret.expr.kwargs = kwargs.force_expr ret = kwargs + ret return ret diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6a0e8dd..4efe768 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -164,6 +164,7 @@ {"y" 5 "z" 3}) {"x" 1 "y" 5 "z" 3 "one" "three"}))) + (defn test-apply [] "NATIVE: test working with args and functions" (defn sumit [a b c] (+ a b c)) @@ -174,6 +175,18 @@ (defn noargs [] [1 2 3]) (assert (= (apply noargs) [1 2 3]))) + +(defn test-apply-with-methods [] + "NATIVE: test apply to call a method" + (setv str "foo {bar}") + (assert (= (apply .format [str] {"bar" "baz"}) + (apply .format ["foo {0}" "baz"]) + "foo baz")) + (setv lst ["a {0} {1} {foo} {bar}" "b" "c"]) + (assert (= (apply .format lst {"foo" "d" "bar" "e"}) + "a b c d e"))) + + (defn test-dotted [] "NATIVE: test dotted invocation" (assert (= (.join " " ["one" "two"]) "one two"))) From 6172b60e759fba743ada0a4cfaa0a507ce77290a Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 25 Dec 2013 12:11:25 -0500 Subject: [PATCH 064/202] Add anaphoric if statement --- hy/contrib/anaphoric.hy | 5 +++++ tests/native_tests/contrib/anaphoric.hy | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index d0a163a..38fd0bb 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -1,6 +1,7 @@ ;;; Hy anaphoric macros ;; ;; Copyright (c) 2013 James King +;; 2013 Paul R. Tagliamonte ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -23,6 +24,10 @@ ;;; These macros make writing functional programs more concise +(defmacro ap-if (test-form &rest args) + `(let [[it ~test-form]] (if it ~@args))) + + (defmacro ap-each [lst &rest body] "Evaluate the body form for each element in the list." `(foreach [it ~lst] ~@body)) diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy index 7fb08f4..e57f184 100644 --- a/tests/native_tests/contrib/anaphoric.hy +++ b/tests/native_tests/contrib/anaphoric.hy @@ -31,6 +31,12 @@ (defn assert-equal [x y] (assert (= x y))) + +(defn test-ap-if [] + "NATIVE: testing anaphoric if" + (ap-if true (assert-true it)) + (ap-if false true (assert-false it))) + (defn test-ap-each [] "NATIVE: testing anaphoric each" (setv res []) From c7070f82b5b9a83b67927c8401583dd7739b5c65 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 25 Dec 2013 20:28:32 -0500 Subject: [PATCH 065/202] Add ap-if docs. --- docs/contrib/anaphoric.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index 0a487c2..f7d1109 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -14,6 +14,18 @@ concise and easy to read. Macros ====== + +.. _ap-if: + +ap-if +------- + +Usage: ``(ap-if (foo) (print it))`` + +Evaluate the first form for trutheyness, and bind it to ``it`` in both the +true and false branch. + + .. _ap-each: ap-each From 81d5f08100fa29f7749cf60cf04792a89a772211 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 26 Dec 2013 03:00:24 +0100 Subject: [PATCH 066/202] Only call macroexpand when needed We only need to call macroexpand on HyExpressions, as this is already guarded in macroexpand_1. This saves us a few funcalls. --- hy/compiler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hy/compiler.py b/hy/compiler.py index 36dd12a..0c73027 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -420,7 +420,6 @@ class HyASTCompiler(object): def compile(self, tree): try: - tree = macroexpand(tree, self.module_name) _type = type(tree) ret = self.compile_atom(_type, tree) if ret: @@ -1497,6 +1496,13 @@ class HyASTCompiler(object): def compile_expression(self, expression): if expression == []: return self.compile_list(expression) + + # Perform macro expansions + expression = macroexpand(expression, self.module_name) + if not isinstance(expression, HyExpression): + # Go through compile again if the type changed. + return self.compile(expression) + fn = expression[0] func = None if isinstance(fn, HyKeyword): From c69c14cc7d107018d22032d7fb063fb1cd4a222b Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Thu, 26 Dec 2013 00:48:09 +0530 Subject: [PATCH 067/202] contrib/anaphoric: More anaphoric macros added * hy/contrib/anaphoric.hy: The following anaphoric macros have been added `ap-reject` : Opposite of ap-filter, yields the elements when a `pred` evaluates to false `ap-dotimes` : Execute body forms (possibly for side-effects) n times with `it` bound from 0 to n-1 `ap-first` : return the first element that passes predicate `ap-last` : return the last element that passes predicate `ap-reduce`: anaphoric form of reduce that allows `acc` and `it` to create a function that is applied over the list * docs/contrib/anaphoric.rst: updated docs to reflect these changes * tests/__init__.py: updated to explicitly include tests for anaphoric macros --- docs/contrib/anaphoric.rst | 93 ++++++++++++++++++++++++- hy/contrib/anaphoric.hy | 40 +++++++++++ tests/__init__.py | 1 + tests/native_tests/contrib/__init__.hy | 0 tests/native_tests/contrib/anaphoric.hy | 32 +++++++++ 5 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 tests/native_tests/contrib/__init__.hy diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index 0a487c2..0c7f584 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -1,6 +1,8 @@ -==================== +================ Anaphoric Macros -==================== +================ + +.. versionadded:: 0.9.12 The anaphoric macros module makes functional programming in Hy very concise and easy to read. @@ -94,3 +96,90 @@ the current element in the iteration. [4, 5] +.. _ap-reject: + +ap-reject +========= + +Usage: ``(ap-reject form list)`` + +This function does the opposite of ``ap-filter``, it rejects the +elements passing the predicate . The special name ``it`` is bound to +the current element in the iteration. + +.. code-block:: clojure + + => (list (ap-reject (> (* it 2) 6) [1 2 3 4 5])) + [1, 2, 3] + + +.. _ap-dotimes: + +ap-dotimes +========== + +Usage ``(ap-dotimes n body)`` + +This function evaluates the body *n* times, with the special +variable ``it`` bound from *0* to *1-n*. It is useful for side-effects. + +.. code-block:: clojure + + => (setv n []) + => (ap-dotimes 3 (.append n it)) + => n + [0, 1, 2] + + +.. _ap-first: + +ap-first +======== + +Usage ``(ap-first predfn list)`` + +This function returns the first element that passes the predicate or +``None``, with the special variable ``it`` bound to the current element in +iteration. + +.. code-block:: clojure + + =>(ap-first (> it 5) (range 10)) + 6 + + +.. _ap-last: + +ap-last +======== + +Usage ``(ap-last predfn list)`` + +This function returns the last element that passes the predicate or +``None``, with the special variable ``it`` bound to the current element in +iteration. + +.. code-block:: clojure + + =>(ap-last (> it 5) (range 10)) + 9 + + +.. _ap-reduce: + +ap-reduce +========= + +Usage ``(ap-reduce form list &optional initial-value)`` + +This function returns the result of applying form to the first 2 +elements in the body and applying the result and the 3rd element +etc. until the list is exhausted. Optionally an initial value can be +supplied so the function will be applied to initial value and the +first element instead. This exposes the element being iterated as +``it`` and the current accumulated value as ``acc``. + +.. code-block:: clojure + + =>(ap-reduce (+ it acc) (range 10)) + 45 diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index d0a163a..e12be6b 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -1,6 +1,7 @@ ;;; Hy anaphoric macros ;; ;; Copyright (c) 2013 James King +;; 2013 Abhishek L ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -61,3 +62,42 @@ (foreach [val ~lst] (if (pred val) (yield val))))) + + +(defmacro ap-reject [form lst] + "Yield elements returned when the predicate form evaluates to False" + `(ap-filter (not ~form) ~lst)) + + +(defmacro ap-dotimes [n &rest body] + "Execute body for side effects `n' times, with it bound from 0 to n-1" + (unless (numeric? n) + (raise (TypeError (.format "{0!r} is not a number" n)))) + `(ap-each (range ~n) ~@body)) + + +(defmacro ap-first [predfn lst] + "Yield the first element that passes `predfn`" + `(let [[n (gensym)]] + (ap-each ~lst (when ~predfn (setv n it) (break))) + n)) + + +(defmacro ap-last [predfn lst] + "Yield the last element that passes `predfn`" + `(let [[n (gensym)]] + (ap-each ~lst (none? n) + (when ~predfn + (setv n it))) + n)) + + +(defmacro ap-reduce [form lst &optional [initial-value None]] + "Anaphoric form of reduce, `acc' and `it' can be used for a form" + (if (none? initial-value) + `(let [[acc (car ~lst)]] + (ap-each (cdr ~lst) (setv acc ~form)) + acc) + `(let [[acc ~initial-value]] + (ap-each ~lst (setv acc ~form)) + acc))) diff --git a/tests/__init__.py b/tests/__init__.py index 582ceae..bbd24e2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,3 +12,4 @@ from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa +from .native_tests.contrib.anaphoric import * # noqa diff --git a/tests/native_tests/contrib/__init__.hy b/tests/native_tests/contrib/__init__.hy new file mode 100644 index 0000000..e69de29 diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy index 7fb08f4..7f88838 100644 --- a/tests/native_tests/contrib/anaphoric.hy +++ b/tests/native_tests/contrib/anaphoric.hy @@ -61,3 +61,35 @@ [3 4]) (assert-equal (list (ap-filter (even? it) [1 2 3 4])) [2 4])) + +(defn test-ap-reject [] + "NATIVE: testing anaphoric filter" + (assert-equal (list (ap-reject (> it 2) [1 2 3 4])) + [1 2]) + (assert-equal (list (ap-reject (even? it) [1 2 3 4])) + [1 3])) + +(defn test-ap-dotimes [] + "NATIVE: testing anaphoric dotimes" + (assert-equal (let [[n []]] (ap-dotimes 3 (.append n 3)) n) + [3 3 3]) + (assert-equal (let [[n []]] (ap-dotimes 3 (.append n it)) n) + [0 1 2])) + +(defn test-ap-first [] + "NATIVE: testing anaphoric first" + (assert-equal (ap-first (> it 5) (range 10)) 6) + (assert-equal (ap-first (even? it) [1 2 3 4]) 2)) + +(defn test-ap-last [] + "NATIVE: testing anaphoric last" + (assert-equal (ap-last (> it 5) (range 10)) 9) + (assert-equal (ap-last (even? it) [1 2 3 4]) 4)) + +(defn test-ap-reduce [] + "NATIVE: testing anaphoric reduce" + (assert-equal (ap-reduce (* acc it) [1 2 3]) 6) + (assert-equal (ap-reduce (* acc it) [1 2 3] 6) 36) + (assert-equal (ap-reduce (+ acc " on " it) ["Hy" "meth"]) + "Hy on meth") + (assert-equal (ap-reduce (+ acc it) [] 1) 1)) From 55a7ab1667583d95a514e33a5df62c376078c2f5 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Fri, 27 Dec 2013 13:50:19 -0700 Subject: [PATCH 068/202] add nil as synonym for None --- hy/lex/parser.py | 1 + tests/native_tests/language.hy | 7 ++++--- tests/native_tests/unless.hy | 2 ++ tests/native_tests/when.hy | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 70d43f8..9e11069 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -229,6 +229,7 @@ def t_identifier(p): table = { "true": "True", "false": "False", + "nil": "None", "null": "None", } diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4efe768..ec110ae 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -86,9 +86,10 @@ (defn test-is [] "NATIVE: test is can deal with None" - (setv a null) - (assert (is a null)) - (assert (is-not a "b"))) + (setv a nil) + (assert (is a nil)) + (assert (is-not a "b")) + (assert (none? a))) (defn test-branching [] diff --git a/tests/native_tests/unless.hy b/tests/native_tests/unless.hy index f8c298a..2b957fe 100644 --- a/tests/native_tests/unless.hy +++ b/tests/native_tests/unless.hy @@ -4,5 +4,7 @@ (assert (= (unless false 1 2) 2)) (assert (= (unless false 1 3) 3)) (assert (= (unless true 2) null)) + (assert (= (unless true 2) nil)) (assert (= (unless (!= 1 2) 42) null)) + (assert (= (unless (!= 1 2) 42) nil)) (assert (= (unless (!= 2 2) 42) 42))) diff --git a/tests/native_tests/when.hy b/tests/native_tests/when.hy index 1409b3b..c281439 100644 --- a/tests/native_tests/when.hy +++ b/tests/native_tests/when.hy @@ -5,4 +5,6 @@ (assert (= (when true 1 3) 3)) (assert (= (when false 2) null)) (assert (= (when (= 1 2) 42) null)) + (assert (= (when false 2) nil)) + (assert (= (when (= 1 2) 42) nil)) (assert (= (when (= 2 2) 42) 42))) From 9656c7f53cca11e7ce92575c539c6c54af68fdb5 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 27 Dec 2013 16:06:48 -0500 Subject: [PATCH 069/202] Add profiling macros --- hy/contrib/profile.hy | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 hy/contrib/profile.hy diff --git a/hy/contrib/profile.hy b/hy/contrib/profile.hy new file mode 100644 index 0000000..3992890 --- /dev/null +++ b/hy/contrib/profile.hy @@ -0,0 +1,43 @@ +;;; Hy profiling macros +;; +;; Copyright (c) 2013 Paul R. Tagliamonte +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. +;; +;;; These macros make debugging where bottlenecks exist easier. + + +(defmacro/g! profile [&rest body] + " Profile a bit of code " + `(do + (import cProfile pstats) + + (if-python2 + (import [StringIO [StringIO]]) + (import [io [StringIO]])) + + (setv ~g!hy-pr (.Profile cProfile)) + (.enable ~g!hy-pr) + (do ~@body) + (.disable ~g!hy-pr) + (setv ~g!hy-s (StringIO)) + (setv ~g!hy-ps + (.sort-stats (kwapply (.Stats pstats ~g!hy-pr) {"stream" ~g!hy-s}))) + (.print-stats ~g!hy-ps) + (print (.getvalue ~g!hy-s)))) From 3f590f89161a504c0de5e99c85bf4fde6d14275b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kenan=20B=C3=B6l=C3=BCkba=C5=9F=C4=B1?= Date: Sun, 29 Dec 2013 00:25:55 +0200 Subject: [PATCH 070/202] Reflect parameters of code.InteractiveConsele in HyREPL in order to make hy run inside Blender. --- hy/cmdline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index 5950a59..eb621b0 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -73,9 +73,10 @@ def print_python_code(_ast): class HyREPL(code.InteractiveConsole): - def __init__(self, spy=False): + def __init__(self, spy=False, locals=None, filename=""): self.spy = spy - code.InteractiveConsole.__init__(self) + code.InteractiveConsole.__init__(self, locals=locals, + filename=filename) def runsource(self, source, filename='', symbol='single'): try: From f189f0a45774692b284069b9adb1651ea2ac7805 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Sat, 28 Dec 2013 18:48:47 -0500 Subject: [PATCH 071/202] =?UTF-8?q?Add=20Kenan=20B=C3=B6l=C3=BCkba=C5=9F?= =?UTF-8?q?=C4=B1=20to=20AUTHORS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 1cb2182..643541b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,3 +20,4 @@ * Berker Peksag * Henrique Carvalho Alves * Joe Hakim Rahme +* Kenan Bölükbaşı From 8120a25c0822598c9ed974562f068a94b0097ace Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sun, 29 Dec 2013 12:57:48 +0200 Subject: [PATCH 072/202] Add py_compile.{MAGIC, wr_long} to hy._compat. Closes #344. --- hy/_compat.py | 14 ++++++++++++++ hy/importer.py | 8 +++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/hy/_compat.py b/hy/_compat.py index e30d0dc..65aa29b 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -1,5 +1,6 @@ # Copyright (c) 2013 Paul Tagliamonte # Copyright (c) 2013 Julien Danjou +# Copyright (c) 2013 Berker Peksag # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -23,9 +24,22 @@ try: import __builtin__ as builtins except ImportError: import builtins # NOQA +try: + from py_compile import MAGIC, wr_long +except ImportError: + # py_compile.MAGIC removed and imp.get_magic() deprecated in Python 3.4 + from importlib.util import MAGIC_NUMBER as MAGIC # NOQA + + def wr_long(f, x): + """Internal; write a 32-bit int to a file in little-endian order.""" + f.write(bytes([x & 0xff, + (x >> 8) & 0xff, + (x >> 16) & 0xff, + (x >> 24) & 0xff])) import sys PY3 = sys.version_info[0] >= 3 +PY33 = sys.version_info[0] >= 3 and sys.version_info[1] >= 3 if PY3: str_type = str diff --git a/hy/importer.py b/hy/importer.py index b32b71a..63cc55a 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -18,12 +18,10 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from py_compile import wr_long, MAGIC from hy.compiler import hy_compile from hy.models import HyObject from hy.lex import tokenize - from io import open import marshal import imp @@ -32,7 +30,7 @@ import ast import os import __future__ -from hy._compat import builtins, long_type +from hy._compat import PY3, PY33, MAGIC, builtins, long_type, wr_long def ast_compile(ast, filename, mode): @@ -128,12 +126,12 @@ def write_hy_as_pyc(fname): open_ = builtins.open with open_(cfile, 'wb') as fc: - if sys.version_info[0] >= 3: + if PY3: fc.write(b'\0\0\0\0') else: fc.write('\0\0\0\0') wr_long(fc, timestamp) - if (sys.version_info[0] >= 3 and sys.version_info[1] >= 3): + if PY33: wr_long(fc, st.st_size) marshal.dump(code, fc) fc.flush() From d82636958b010d5cdf34af22576f04ecb4ffd098 Mon Sep 17 00:00:00 2001 From: Foxboron Date: Sun, 10 Nov 2013 19:00:01 +0100 Subject: [PATCH 073/202] added for and with macros Fixed up anaphoric.hy and added the tests too native_tests/__init__.py added __init__.hy --- docs/language/api.rst | 52 +++++++++++------------------ hy/compiler.py | 33 +++++++++++++----- hy/contrib/anaphoric.hy | 10 +++--- hy/core/bootstrap.hy | 4 +-- hy/core/language.hy | 28 ++++++++-------- hy/core/macros.hy | 33 ++++++++++-------- tests/__init__.py | 1 + tests/compilers/test_ast.py | 18 +++++----- tests/native_tests/language.hy | 36 ++++++++++---------- tests/native_tests/native_macros.hy | 6 ++-- tests/native_tests/with_test.hy | 44 ++++++++++++++++++++++++ 11 files changed, 160 insertions(+), 105 deletions(-) create mode 100644 tests/native_tests/with_test.hy diff --git a/docs/language/api.rst b/docs/language/api.rst index 9934b4d..60f4d89 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -441,52 +441,36 @@ first / car for ---- - -`for` macro is used to build nested `foreach` loops. The macro takes two -parameters, first being a vector specifying collections to iterate over and -variables to bind. The second parameter is a statement which is executed during -each loop: - -.. code-block:: clj - - (for [x iter y iter] stmt) - - (foreach [x iter] - (foreach [y iter] stmt)) - - -foreach ------- -`foreach` is used to call a function for each element in a list or vector. +`for` is used to call a function for each element in a list or vector. Results are discarded and None is returned instead. Example code iterates over collection and calls side-effect to each element in the collection: .. code-block:: clj ;; assuming that (side-effect) is a function that takes a single parameter - (foreach [element collection] (side-effect element)) + (for [[element collection]] (side-effect element)) - ;; foreach can have an optional else block - (foreach [element collection] (side-effect element) - (else (side-effect-2))) + ;; for can have an optional else block + (for [[element collection]] (side-effect element) + (else (side-effect-2))) -The optional `else` block is executed only if the `foreach` loop terminates +The optional `else` block is executed only if the `for` loop terminates normally. If the execution is halted with `break`, the `else` does not execute. .. code-block:: clj - => (foreach [element [1 2 3]] (if (< element 3) - ... (print element) - ... (break)) + => (for [[element [1 2 3]]] (if (< element 3) + ... (print element) + ... (break)) ... (else (print "loop finished"))) 1 2 - => (foreach [element [1 2 3]] (if (< element 4) - ... (print element) - ... (break)) + => (for [[element [1 2 3]]] (if (< element 4) + ... (print element) + ... (break)) ... (else (print "loop finished"))) 1 2 @@ -635,7 +619,7 @@ function is defined and passed to another function for filtering output. ... {:name "Dave" :age 5}]) => (defn display-people [people filter] - ... (foreach [person people] (if (filter person) (print (:name person))))) + ... (for [[person people]] (if (filter person) (print (:name person))))) => (display-people people (fn [person] (< (:age person) 25))) Alice @@ -949,16 +933,18 @@ context to an argument or ignore it completely, as shown below: .. code-block:: clj - (with [arg (expr)] block) + (with [[arg (expr)]] block) - (with [(expr)] block) + (with [[(expr)]] block) + + (with [[arg (expr)] [(expr)]] block) The following example will open file `NEWS` and print its content on screen. The file is automatically closed after it has been processed. .. code-block:: clj - (with [f (open "NEWS")] (print (.read f))) + (with [[f (open "NEWS")]] (print (.read f))) with-decorator @@ -996,7 +982,7 @@ infinite series without consuming infinite amount of memory. .. code-block:: clj => (defn multiply [bases coefficients] - ... (foreach [(, base coefficient) (zip bases coefficients)] + ... (for [[(, base coefficient) (zip bases coefficients)]] ... (yield (* base coefficient)))) => (multiply (range 5) (range 5)) diff --git a/hy/compiler.py b/hy/compiler.py index 9df4fb9..9a3b710 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1177,14 +1177,18 @@ class HyASTCompiler(object): fn.stmts[-1].decorator_list = decorators return ret + fn - @builds("with") + @builds("with*") @checkargs(min=2) def compile_with_expression(self, expr): - expr.pop(0) # with + expr.pop(0) # with* args = expr.pop(0) - if len(args) > 2 or len(args) < 1: - raise HyTypeError(expr, "with needs [arg (expr)] or [(expr)]") + if not isinstance(args, HyList): + raise HyTypeError(expr, + "with expects a list, received `{0}'".format( + type(args).__name__)) + if len(args) < 1: + raise HyTypeError(expr, "with needs [[arg (expr)]] or [[(expr)]]]") args.reverse() ctx = self.compile(args.pop(0)) @@ -1623,23 +1627,36 @@ class HyASTCompiler(object): result += ld_name return result - @builds("foreach") + @builds("for*") @checkargs(min=1) def compile_for_expression(self, expression): expression.pop(0) # for - target_name, iterable = expression.pop(0) + + args = expression.pop(0) + + if not isinstance(args, HyList): + raise HyTypeError(expression, + "for expects a list, received `{0}'".format( + type(args).__name__)) + + try: + target_name, iterable = args + except ValueError: + raise HyTypeError(expression, + "for requires two forms in the list") + target = self._storeize(self.compile(target_name)) ret = Result() orel = Result() - # (foreach [] body (else …)) + # (for* [] body (else …)) if expression and expression[-1][0] == HySymbol("else"): else_expr = expression.pop() if len(else_expr) > 2: raise HyTypeError( else_expr, - "`else' statement in `foreach' is too long") + "`else' statement in `for' is too long") elif len(else_expr) == 2: orel += self.compile(else_expr[1]) orel += orel.expr_as_stmt() diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index c451559..9a281d0 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -31,14 +31,14 @@ (defmacro ap-each [lst &rest body] "Evaluate the body form for each element in the list." - `(foreach [it ~lst] ~@body)) + `(for [[it ~lst]] ~@body)) (defmacro ap-each-while [lst form &rest body] "Evalutate the body form for each element in the list while the predicate form evaluates to True." `(let [[p (lambda [it] ~form)]] - (foreach [it ~lst] + (for [[it ~lst]] (if (p it) ~@body (break))))) @@ -47,7 +47,7 @@ (defmacro ap-map [form lst] "Yield elements evaluated in the form for each element in the list." `(let [[f (lambda [it] ~form)]] - (foreach [v ~lst] + (for [[v ~lst]] (yield (f v))))) @@ -55,7 +55,7 @@ "Yield elements evaluated for each element in the list when the predicate function returns True." `(let [[f (lambda [it] ~rep)]] - (foreach [it ~lst] + (for [[it ~lst]] (if (~predfn it) (yield (f it)) (yield it))))) @@ -64,7 +64,7 @@ (defmacro ap-filter [form lst] "Yield elements returned when the predicate form evaluates to True." `(let [[pred (lambda [it] ~form)]] - (foreach [val ~lst] + (for [[val ~lst]] (if (pred val) (yield val))))) diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy index 007d92b..5d3d367 100644 --- a/hy/core/bootstrap.hy +++ b/hy/core/bootstrap.hy @@ -34,7 +34,7 @@ (defmacro defmacro-alias [names lambda-list &rest body] "define one macro with several names" (setv ret `(do)) - (foreach [name names] + (for* [name names] (.append ret `(defmacro ~name ~lambda-list ~@body))) ret) @@ -52,7 +52,7 @@ (setv macroed_variables []) (if (not (isinstance variables HyList)) (macro-error variables "let lexical context must be a list")) - (foreach [variable variables] + (for* [variable variables] (if (isinstance variable HyList) (do (if (!= (len variable) 2) diff --git a/hy/core/language.hy b/hy/core/language.hy index d03db93..9ac2fee 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -33,11 +33,11 @@ (defn cycle [coll] "Yield an infinite repetition of the items in coll" (setv seen []) - (foreach [x coll] + (for* [x coll] (yield x) (.append seen x)) (while seen - (foreach [x seen] + (for* [x seen] (yield x)))) (defn dec [n] @@ -49,7 +49,7 @@ "Return a generator from the original collection with duplicates removed" (let [[seen []] [citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (not_in val seen) (do (yield val) @@ -58,7 +58,7 @@ (defn drop [count coll] "Drop `count` elements from `coll` and yield back the rest" (let [[citer (iter coll)]] - (try (foreach [i (range count)] + (try (for* [i (range count)] (next citer)) (catch [StopIteration])) citer)) @@ -66,10 +66,10 @@ (defn drop-while [pred coll] "Drop all elements of `coll` until `pred` is False" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (not (pred val)) (do (yield val) (break)))) - (foreach [val citer] + (for* [val citer] (yield val)))) (defn empty? [coll] @@ -84,7 +84,7 @@ (defn filter [pred coll] "Return all elements from `coll` that pass `pred`" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (pred val) (yield val))))) @@ -96,7 +96,7 @@ (defn _flatten [coll result] (if (and (iterable? coll) (not (string? coll))) - (do (foreach [b coll] + (do (for* [b coll] (_flatten b result))) (.append result coll)) result) @@ -187,7 +187,7 @@ (defn remove [pred coll] "Return coll with elements removed that pass `pred`" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (not (pred val)) (yield val))))) @@ -195,7 +195,7 @@ "Yield x forever or optionally n times" (if (none? n) (setv dispatch (fn [] (while true (yield x)))) - (setv dispatch (fn [] (foreach [_ (range n)] (yield x))))) + (setv dispatch (fn [] (for* [_ (range n)] (yield x))))) (dispatch)) (defn repeatedly [func] @@ -223,7 +223,7 @@ "Take `count` elements from `coll`, or the whole set if the total number of entries in `coll` is less than `count`." (let [[citer (iter coll)]] - (foreach [_ (range count)] + (for* [_ (range count)] (yield (next citer))))) (defn take-nth [n coll] @@ -231,16 +231,16 @@ raises ValueError for (not (pos? n))" (if (pos? n) (let [[citer (iter coll)] [skip (dec n)]] - (foreach [val citer] + (for* [val citer] (yield val) - (foreach [_ (range skip)] + (for* [_ (range skip)] (next citer)))) (raise (ValueError "n must be positive")))) (defn take-while [pred coll] "Take all elements while `pred` is true" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (pred val) (yield val) (break))))) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index d7c8b15..39338c1 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -26,17 +26,24 @@ ;;; They are automatically required in every module, except inside hy.core (defmacro for [args &rest body] - "shorthand for nested foreach loops: - (for [x foo y bar] baz) -> - (foreach [x foo] - (foreach [y bar] + "shorthand for nested for loops: + (for [[x foo] [y bar]] baz) -> + (for* [x foo] + (for* [y bar] baz))" - ;; TODO: that signature sucks. - ;; (for [[x foo] [y bar]] baz) would be more consistent - (if (% (len args) 2) - (macro-error args "for needs an even number of elements in its first argument")) (if args - `(foreach [~(.pop args 0) ~(.pop args 0)] (for ~args ~@body)) + `(for* ~(.pop args 0) (for ~args ~@body)) + `(do ~@body))) + + +(defmacro with [args &rest body] + "shorthand for nested for* loops: + (with [[x foo] [y bar]] baz) -> + (with* [x foo] + (with* [y bar] + baz))" + (if args + `(with* ~(.pop args 0) (with ~args ~@body)) `(do ~@body))) @@ -71,7 +78,7 @@ (setv root (check-branch branch)) (setv latest-branch root) - (foreach [branch branches] + (for* [branch branches] (setv cur-branch (check-branch branch)) (.append latest-branch cur-branch) (setv latest-branch cur-branch)) @@ -81,7 +88,7 @@ (defmacro -> [head &rest rest] ;; TODO: fix the docstring by someone who understands this (setv ret head) - (foreach [node rest] + (for* [node rest] (if (not (isinstance node HyExpression)) (setv node `(~node))) (.insert node 1 ret) @@ -92,7 +99,7 @@ (defmacro ->> [head &rest rest] ;; TODO: fix the docstring by someone who understands this (setv ret head) - (foreach [node rest] + (for* [node rest] (if (not (isinstance node HyExpression)) (setv node `(~node))) (.append node ret) @@ -113,7 +120,7 @@ (defmacro yield-from [iterable] "Yield all the items from iterable" (let [[x (gensym)]] - `(foreach [~x ~iterable] + `(for* [~x ~iterable] (yield ~x)))) (defmacro with-gensyms [args &rest body] diff --git a/tests/__init__.py b/tests/__init__.py index bbd24e2..5b9f090 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,4 +12,5 @@ from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa +from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index a362d66..ebcfabd 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -295,9 +295,9 @@ def test_ast_bad_assoc(): def test_ast_bad_with(): "Make sure AST can't compile invalid with" - cant_compile("(with)") - cant_compile("(with [])") - cant_compile("(with [] (pass))") + cant_compile("(with*)") + cant_compile("(with* [])") + cant_compile("(with* [] (pass))") def test_ast_valid_while(): @@ -305,14 +305,14 @@ def test_ast_valid_while(): can_compile("(while foo bar)") -def test_ast_valid_foreach(): - "Make sure AST can compile valid foreach" - can_compile("(foreach [a 2])") +def test_ast_valid_for(): + "Make sure AST can compile valid for" + can_compile("(for [[a 2]])") -def test_ast_invalid_foreach(): - "Make sure AST can't compile invalid foreach" - cant_compile("(foreach [a 1] (else 1 2))") +def test_ast_invalid_for(): + "Make sure AST can't compile invalid for" + cant_compile("(for* [a 1] (else 1 2))") def test_ast_expression_basics(): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4efe768..25ecc33 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -31,12 +31,12 @@ (defn test-for-loop [] "NATIVE: test for loops?" (setv count 0) - (for [x [1 2 3 4 5]] + (for [[x [1 2 3 4 5]]] (setv count (+ count x))) (assert (= count 15)) (setv count 0) - (for [x [1 2 3 4 5] - y [1 2 3 4 5]] + (for [[x [1 2 3 4 5]] + [y [1 2 3 4 5]]] (setv count (+ count x y))) (assert (= count 150))) @@ -394,9 +394,9 @@ (defn test-yield [] "NATIVE: test yielding" - (defn gen [] (for [x [1 2 3 4]] (yield x))) + (defn gen [] (for [[x [1 2 3 4]]] (yield x))) (setv ret 0) - (for [y (gen)] (setv ret (+ ret y))) + (for [[y (gen)]] (setv ret (+ ret y))) (assert (= ret 10))) @@ -439,37 +439,37 @@ (defn test-context [] "NATIVE: test with" - (with [fd (open "README.md" "r")] (assert fd)) - (with [(open "README.md" "r")] (do))) + (with [[fd (open "README.md" "r")]] (assert fd)) + (with [[(open "README.md" "r")]] (do))) (defn test-with-return [] "NATIVE: test that with returns stuff" (defn read-file [filename] - (with [fd (open filename "r")] (.read fd))) + (with [[fd (open filename "r")]] (.read fd))) (assert (!= 0 (len (read-file "README.md"))))) (defn test-for-doodle [] "NATIVE: test for-do" (do (do (do (do (do (do (do (do (do (setv (, x y) (, 0 0))))))))))) - (foreach [- [1 2]] + (for [[- [1 2]]] (do (setv x (+ x 1)) (setv y (+ y 1)))) (assert (= y x 2))) -(defn test-foreach-else [] - "NATIVE: test foreach else" +(defn test-for-else [] + "NATIVE: test for else" (let [[x 0]] - (foreach [a [1 2]] + (for* [a [1 2]] (setv x (+ x a)) (else (setv x (+ x 50)))) (assert (= x 53))) (let [[x 0]] - (foreach [a [1 2]] + (for* [a [1 2]] (setv x (+ x a)) (else)) (assert (= x 3)))) @@ -636,7 +636,7 @@ (defn test-nested-if [] "NATIVE: test nested if" - (for [x (range 10)] + (for [[x (range 10)]] (if (in "foo" "foobar") (do (if true true true)) @@ -800,16 +800,16 @@ (defn test-break-breaking [] "NATIVE: test checking if break actually breaks" - (defn holy-grail [] (for [x (range 10)] (if (= x 5) (break))) x) + (defn holy-grail [] (for [[x (range 10)]] (if (= x 5) (break))) x) (assert (= (holy-grail) 5))) (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [x (range 10)] - (if (!= x 5) - (continue)) + (for [[x (range 10)]] + (if (!= x 5) + (continue)) (.append y x)) (assert (= y [5]))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 911a9c9..0841b1e 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -56,7 +56,7 @@ (defn test-midtree-yield-in-for [] "NATIVE: test yielding in a for with a return" (defn kruft-in-for [] - (for [i (range 5)] + (for* [i (range 5)] (yield i)) (+ 1 2))) @@ -72,7 +72,7 @@ (defn test-multi-yield [] "NATIVE: testing multiple yields" (defn multi-yield [] - (for [i (range 3)] + (for* [i (range 3)] (yield i)) (yield "a") (yield "end")) @@ -97,7 +97,7 @@ (defn test-yield-from [] "NATIVE: testing yield from" (defn yield-from-test [] - (for [i (range 3)] + (for* [i (range 3)] (yield i)) (yield-from [1 2 3])) (assert (= (list (yield-from-test)) [0 1 2 1 2 3]))) diff --git a/tests/native_tests/with_test.hy b/tests/native_tests/with_test.hy new file mode 100644 index 0000000..b094f35 --- /dev/null +++ b/tests/native_tests/with_test.hy @@ -0,0 +1,44 @@ +(defclass WithTest [object] + [(--init-- + (fn [self val] + (setv self.val val) + None)) + + (--enter-- + (fn [self] + self.val)) + + (--exit-- + (fn [self type value traceback] + (setv self.val None)))]) + +(defn test-single-with [] + "NATIVE: test a single with" + (with [[t (WithTest 1)]] + (assert (= t 1)))) + +(defn test-two-with [] + "NATIVE: test two withs" + (with [[t1 (WithTest 1)] + [t2 (WithTest 2)]] + (assert (= t1 1)) + (assert (= t2 2)))) + +(defn test-thrice-with [] + "NATIVE: test three withs" + (with [[t1 (WithTest 1)] + [t2 (WithTest 2)] + [t3 (WithTest 3)]] + (assert (= t1 1)) + (assert (= t2 2)) + (assert (= t3 3)))) + +(defn test-quince-with [] + "NATIVE: test four withs, one with no args" + (with [[t1 (WithTest 1)] + [t2 (WithTest 2)] + [t3 (WithTest 3)] + [(WithTest 4)]] + (assert (= t1 1)) + (assert (= t2 2)) + (assert (= t3 3)))) From 62522a5f8693dfb13f589f520739b1281776e379 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 30 Dec 2013 12:04:40 +0100 Subject: [PATCH 074/202] Allow get with multiple arguments When calling get with more than two arguments, treat the rest as indexes into the expression from the former. That is, (get foo "bar" "baz") would translate to foo["bar"]["baz"], and so on. This fixes #362. Requested-by: Sean B. Palmer Signed-off-by: Gergely Nagy --- hy/compiler.py | 25 ++++++++++++++++--------- tests/compilers/test_ast.py | 1 - tests/native_tests/language.hy | 11 ++++++++++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 9df4fb9..230cdda 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1086,18 +1086,25 @@ class HyASTCompiler(object): return rimports @builds("get") - @checkargs(2) + @checkargs(min=2) def compile_index_expression(self, expr): expr.pop(0) # index - val = self.compile(expr.pop(0)) # target - sli = self.compile(expr.pop(0)) # slice - return val + sli + ast.Subscript( - lineno=expr.start_line, - col_offset=expr.start_column, - value=val.force_expr, - slice=ast.Index(value=sli.force_expr), - ctx=ast.Load()) + val = self.compile(expr.pop(0)) + slices, ret = self._compile_collect(expr) + + if val.stmts: + ret += val + + for sli in slices: + val = Result() + ast.Subscript( + lineno=expr.start_line, + col_offset=expr.start_column, + value=val.force_expr, + slice=ast.Index(value=sli), + ctx=ast.Load()) + + return ret + val @builds("del") @checkargs(min=1) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index a362d66..98ac7ac 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -253,7 +253,6 @@ def test_ast_bad_get(): "Make sure AST can't compile invalid get" cant_compile("(get)") cant_compile("(get 1)") - cant_compile("(get 1 2 3)") def test_ast_good_slice(): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4efe768..65cca5c 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -129,7 +129,16 @@ (defn test-index [] "NATIVE: Test that dict access works" (assert (= (get {"one" "two"} "one") "two")) - (assert (= (get [1 2 3 4 5] 1) 2))) + (assert (= (get [1 2 3 4 5] 1) 2)) + (assert (= (get {"first" {"second" {"third" "level"}}} + "first" "second" "third") + "level")) + (assert (= (get ((fn [] {"first" {"second" {"third" "level"}}})) + "first" "second" "third") + "level")) + (assert (= (get {"first" {"second" {"third" "level"}}} + ((fn [] "first")) "second" "third") + "level"))) (defn test-lambda [] From 4e3b6fd4cf6d36302bbc7f80c11ca3aa5ac5961b Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Mon, 30 Dec 2013 14:42:55 -0700 Subject: [PATCH 075/202] Add some docs for gensym and siblings --- docs/language/api.rst | 71 +++++++++++++++++++++++++++ docs/language/internals.rst | 97 ++++++++++++++++++++++++++++++++++--- 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 9934b4d..12a7e06 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -357,6 +357,8 @@ Parameters may have following keywords in front of them: => (zig-zag-sum 1 2 3 4 5 6) -3 +.. _defmacro: + defmacro -------- @@ -378,6 +380,24 @@ between the operands. => (infix (1 + 1)) 2 +.. _defmacro/g!: + +defmacro/g! +------------ + +.. versionadded:: 0.9.12 + +`defmacro/g!` is a special version of `defmacro` that is used to +automatically generate :ref:`gensym` for any symbol that +starts with ``g!``. + +So ``g!a`` would become ``(gensym "a")``. + +.. seealso:: + + Section :ref:`using-gensym` + + del --- @@ -494,6 +514,28 @@ normally. If the execution is halted with `break`, the `else` does not execute. loop finished +.. _gensym: + +gensym +------ + +.. versionadded:: 0.9.12 + +`gensym` form is used to generate a unique symbol to allow writing macros +without accidental variable name clashes. + +.. code-block:: clj + + => (gensym) + u':G_1235' + + => (gensym "x") + u':x_1236' + +.. seealso:: + + Section :ref:`using-gensym` + get --- @@ -983,6 +1025,35 @@ values that are incremented by 1. When decorated `addition` is called with value 4 +.. _with-gensyms: + +with-gensyms +------------- + +.. versionadded:: 0.9.12 + +`with-gensym` form is used to generate a set of :ref:`gensym` for use +in a macro. + +.. code-block:: clojure + + (with-gensyms [a b c] + ...) + +expands to: + +.. code-block:: clojure + + (let [[a (gensym) + [b (gensym) + [c (gensym)]] + ...) + +.. seealso:: + + Section :ref:`using-gensym` + + yield ----- diff --git a/docs/language/internals.rst b/docs/language/internals.rst index ccf8263..cf4f99c 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -2,26 +2,111 @@ Internal Hy Documentation ========================= -.. info:: - These bits are for folks who hack on Hy it's self, mostly! +.. note:: + These bits are for folks who hack on Hy itself, mostly! Hy Models ========= -.. TODO:: +.. todo:: Write this. Hy Macros ========= -.. TODO:: - Write this. +.. _using-gensym: + +Using gensym for safer macros +------------------------------ + +When writing macros, one must be careful to avoid capturing external variables +or using variable names that might conflict with user code. + +We will use an example macro ``nif`` (see http://letoverlambda.com/index.cl/guest/chap3.html#sec_5 +for a more complete description.) ``nif`` is an example, something like a numeric ``if``, +where based on the expression, one of the 3 forms is called depending on if the +expression is positive, zero or negative. + +A first pass might be someting like: + +.. code-block:: clojure + + (defmacro nif [expr pos-form zero-form neg-form] + `(let [[obscure-name ~expr]] + (cond [(pos? obscure-name) ~pos-form] + [(zero? obscure-name) ~zero-form] + [(neg? obscure-name) ~neg-form]))) + +where ``obsure-name`` is an attempt to pick some variable name as not to +conflict with other code. But of course, while well-intentioned, +this is no guarantee. + +The method :ref:`gensym` is designed to generate a new, unique symbol for just +such an occasion. A much better version of ``nif`` would be: + +.. code-block:: clojure + + (defmacro nif [expr pos-form zero-form neg-form] + (let [[g (gensym)]] + `(let [[~g ~expr]] + (cond [(pos? ~g) ~pos-form] + [(zero? ~g) ~zero-form] + [(neg? ~g) ~neg-form])))) + +This is an easy case, since there is only one symbol. But if there is +a need for several gensym's there is a second macro :ref:`with-gensyms` that +basically expands to a series of ``let`` statements: + +.. code-block:: clojure + + (with-gensyms [a b c] + ...) + +expands to: + +.. code-block:: clojure + + (let [[a (gensym) + [b (gensym) + [c (gensym)]] + ...) + +so our re-written ``nif`` would look like: + +.. code-block:: clojure + + (defmacro nif [expr pos-form zero-form neg-form] + (with-gensyms [g] + `(let [[~g ~expr]] + (cond [(pos? ~g) ~pos-form] + [(zero? ~g) ~zero-form] + [(neg? ~g) ~neg-form])))) + +Finally, though we can make a new macro that does all this for us. :ref:`defmacro/g!` +will take all symbols that begin with ``g!`` and automatically call ``gensym`` with the +remainder of the symbol. So ``g!a`` would become ``(gensym "a")``. + +Our final version of ``nif``, built with ``defmacro/g!`` becomes: + +.. code-block:: clojure + + (defmacro/g! nif [expr pos-form zero-form neg-form] + `(let [[~g!res ~expr]] + (cond [(pos? ~g!res) ~pos-form] + [(zero? ~g!res) ~zero-form] + [(neg? ~g!res) ~neg-form])))) + + + +Checking macro arguments and raising exceptions +----------------------------------------------- + Hy Compiler Builtins ==================== -.. TODO:: +.. todo:: Write this. From d7956d03c35d23407172a69663576b5d95a4f892 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Mon, 30 Dec 2013 15:09:17 -0700 Subject: [PATCH 076/202] Adding documentation for flatten --- docs/language/core.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/language/core.rst b/docs/language/core.rst index acdc025..9b24747 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -575,6 +575,26 @@ See also :ref:`remove-fn`. => (list (filter even? [1 2 3 -4 5 -7])) [2, -4] +.. _flatten-fn: + +flatten +------- + +.. versionadded:: 0.9.12 + +Usage: ``(flatten coll)`` + +Return a single list of all the items in ``coll``, by flattening all +contained lists and/or tuples. + +.. code-block:: clojure + + => (flatten [1 2 [3 4] 5]) + [1, 2, 3, 4, 5] + + => (flatten ["foo" (, 1 2) [1 [2 3] 4] "bar"]) + ['foo', 1, 2, 1, 2, 3, 4, 'bar'] + .. _iterate-fn: From 67fd0ddbbeb7f6418cb151c1b4217dd29eafc62e Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 30 Dec 2013 17:32:57 -0500 Subject: [PATCH 077/202] Document the compiler a little. --- docs/language/internals.rst | 167 ++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index cf4f99c..df43061 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -13,6 +13,173 @@ Hy Models Write this. +Hy Internal Theory +================== + +.. _overview: + +Overview +-------- + +The Hy internals work by acting as a front-end to Python bytecode, so that +Hy it's self compiles down to Python Bytecode, allowing an unmodified Python +runtime to run Hy. + +The way we do this is by translating Hy into Python AST, and building that AST +down into Python bytecode using standard internals, so that we don't have +to duplicate all the work of the Python internals for every single Python +release. + +Hy works in four stages. The following sections will cover each step of Hy +from source to runtime. + +.. _lexing: + +Lexing / tokenizing +------------------- + +The first stage of compiling hy is to lex the source into tokens that we can +deal with. We use a project called rply, which is a really nice (and fast) +parser, written in a subset of Python called rply. + +The lexing code is all defined in ``hy.lex.lexer``. This code is mostly just +defining the Hy grammer, and all the actual hard parts are taken care of by +rply -- we just define "callbacks" for rply in ``hy.lex.parser``, which take +the tokens generated, and return the Hy models. + +You can think of the Hy models as the "AST" for Hy, it's what Macros operate +on (directly), and it's what the compiler uses when it compiles Hy down. + +Check the documentation for more information on the Hy models for more +information regarding the Hy models, and what they mean. + +.. TODO: Uh, we should, like, document models. + + +.. _compiling: + +Compiling +--------- + +This is where most of the magic in Hy happens. This is where we take Hy AST +(the models), and compile them into Python AST. A couple of funky things happen +here to work past a few problems in AST, and working in the compiler is some +of the most important work we do have. + +The compiler is a bit complex, so don't feel bad if you don't grok it on the +first shot, it may take a bit of time to get right. + +The main entry-point to the Compiler is ``HyASTCompiler.compile``. This method +is invoked, and the only real "public" method on the class (that is to say, +we don't really promise the API beyond that method). + +In fact, even internally, we don't recurse directly hardly ever, we almost +always force the Hy tree through ``compile``, and will often do this with +sub-elements of an expression that we have. It's up to the Type-based dispatcher +to properly dispatch sub-elements. + +All methods that preform a compilation are marked with the ``@builds()`` +decorator. You can either pass the class of the Hy model that it compiles, +or you can use a string for expressions. I'll clear this up in a second. + +First stage type-dispatch +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let's start in the ``compile`` method. The first thing we do is check the +Type of the thing we're building. We look up to see if we have a method that +can build the ``type()`` that we have, and dispatch to the method that can +handle it. If we don't have any methods that can build that type, we raise +an internal ``Exception``. + +For instance, if we have a ``HyString``, we have an almost 1-to-1 mapping of +Hy AST to Python AST. The ``compile_string`` method takes the ``HyString``, and +returns an ``ast.Str()`` that's populated with the correct line-numbers and +content. + +Macro-expand +~~~~~~~~~~~~ + +If we get a ``HyExpression``, we'll attempt to see if this is a known +Macro, and push to have it expanded by invoking ``hy.macros.macroexpand``, then +push the result back into ``HyASTCompiler.compile``. + +Second stage expression-dispatch +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The only special case is the ``HyExpression``, since we need to create different +AST depending on the special form in question. For instance, when we hit an +``(if true true false)``, we need to generate a ``ast.If``, and properly +compile the sub-nodes. This is where the ``@builds()`` with a String as an +argument comes in. + +For the ``compile_expression`` (which is defined with an +``@builds(HyExpression)``) will dispatch based on the string of the first +argument. If, for some reason, the first argument is not a string, it will +properly handle that case as well (most likely by raising an ``Exception``). + +If the String isn't known to Hy, it will default to create an ``ast.Call``, +which will try to do a runtime call (in Python, something like ``foo()``). + +Issues hit with Python AST +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Python AST is great; it's what's enabled us to write such a powerful project +on top of Python without having to fight Python too hard. Like anything, we've +had our fair share of issues, and here's a short list of the common ones you +might run into. + +*Python differentiates between Statements and Expressions*. + +This might not sound like a big deal -- in fact, to most Python programmers, +this will shortly become a "Well, yeah" moment. + +In Python, doing something like: + +``print for x in range(10): pass``, because ``print`` prints expressions, and +``for`` isn't an expression, it's a control flow statement. Things like +``1 + 1`` are Expressions, as is ``lambda x: 1 + x``, but other language +features, such as ``if``, ``for``, or ``while`` are statements. + +Since they have no "value" to Python, this makes working in Hy hard, since +doing something like ``(print (if true true false))`` is not just common, it's +expected. + +As a result, we auto-mangle things using a ``Result`` object, where we offer +up any ``ast.stmt`` that need to get run, and a single ``ast.expr`` that can +be used to get the value of whatever was just run. Hy does this by forcing +assignment to things while running. + +As example, the Hy:: + + (print (if true true false)) + +Will turn into:: + + if True: + _mangled_name_here = True + else: + _mangled_name_here = False + + print _mangled_name_here + + +OK, that was a bit of a lie, since we actually turn that statement +into:: + + print True if True else False + +By forcing things into an ``ast.expr`` if we can, but the general idea holds. + + +Runtime +------- + +After we have a Python AST tree that's complete, we can try and compile it to +Python bytecode by pushing it through ``eval``. From here on out, we're no +longer in control, and Python is taking care of everything. This is why things +like Python tracebacks, pdb and django apps work. + + Hy Macros ========= From b011048b419d32135bfe20cf67aa9347cca1e022 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 30 Dec 2013 18:02:03 -0500 Subject: [PATCH 078/202] allow one-shot merge --- docs/hacking.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/hacking.rst b/docs/hacking.rst index 6ee479c..0a35898 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -100,6 +100,9 @@ If a core member is sending in a PR, please find 2 core members that don't include them PR submitter. The idea here is that one can work with the PR author, and a second acks the entire change set. +If the change is adding documentation, feel free to just merge after one +ACK. We've got low coverage, so it'd be great to keep that barrier low. + Core Team ========= From 426d34288f37c61182c7c0ef054a127dfbd83c6d Mon Sep 17 00:00:00 2001 From: Foxboron Date: Tue, 31 Dec 2013 02:06:51 +0100 Subject: [PATCH 079/202] Added docs and one small bug fix in defreader --- docs/language/api.rst | 19 +++++++++++ docs/language/index.rst | 1 + docs/language/readermacros.rst | 61 ++++++++++++++++++++++++++++++++++ hy/compiler.py | 2 +- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docs/language/readermacros.rst diff --git a/docs/language/api.rst b/docs/language/api.rst index aff6745..4e70b7d 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -397,6 +397,25 @@ So ``g!a`` would become ``(gensym "a")``. Section :ref:`using-gensym` +defreader +--------- + +.. versionadded:: 0.9.12 + +`defreader` defines a reader macro, enabling you to restructure or +modify syntax. + +.. code-block:: clj + + => (defreader ^ [expr] (print expr)) + => #^(1 2 3 4) + (1 2 3 4) + => #^"Hello" + "Hello" + +.. seealso:: + + Section :ref:`Reader Macros ` del --- diff --git a/docs/language/index.rst b/docs/language/index.rst index 5132647..4721eb3 100644 --- a/docs/language/index.rst +++ b/docs/language/index.rst @@ -11,3 +11,4 @@ Contents: api core internals + readermacros diff --git a/docs/language/readermacros.rst b/docs/language/readermacros.rst new file mode 100644 index 0000000..0c05d9c --- /dev/null +++ b/docs/language/readermacros.rst @@ -0,0 +1,61 @@ +.. _reader-macros: + +.. highlight:: clj + +============= +Reader Macros +============= + +Reader macros gives LISP the power to modify and alter syntax on the fly. +You don't want polish notation? A reader macro can easily do just that. Want +Clojure's way of having a regex? Reader macros can also do this easily. + + +Syntax +====== + +:: + + => (defreader ^ [expr] (print expr)) + => #^(1 2 3 4) + (1 2 3 4) + => #^"Hello" + "Hello" + => #^1+2+3+4+3+2 + 1+2+3+4+3+2 + + +Implementation +============== + +Hy uses ``defreader`` to define the reader symbol, and ``#`` as the dispatch +character. ``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol +and expression is quoted, and then passed along to the correct function:: + + => (defreader ^ ...) + => #^() + ;=> (dispatch_reader_macro '^ '()) + + +``defreader`` takes a single character as symbol name for the reader macro, +anything longer will return an error. Implementation wise, ``defreader`` +expands into a lambda covered with a decorator, this decorater saves the +lambda in a dict with its module name and symbol. + +:: + + => (defreader ^ [expr] (print expr)) + ;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr))) + + +Anything passed along is quoted, thus given to the function defined. + +:: + + => #^"Hello" + "Hello" + +.. warning:: + Because of a limitation in Hy's lexer and parser, reader macros can't + redefine defined syntax such as ``()[]{}``. This will most likely be + adressed in the future. diff --git a/hy/compiler.py b/hy/compiler.py index 9a3b710..8b34a9d 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1866,7 +1866,7 @@ class HyASTCompiler(object): expression.pop(0) name = expression.pop(0) NOT_READERS = [":", "&"] - if name in NOT_READERS: + if name in NOT_READERS or len(name) > 1: raise NameError("%s can't be used as a macro reader symbol" % name) if not isinstance(name, HySymbol): raise HyTypeError(name, From 9b1990901c619d6f8e728d3bed61607dd9d06e83 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 31 Dec 2013 15:51:58 +0200 Subject: [PATCH 080/202] Fix |Unknown directive type "todo"| error. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 40eb062..6b45309 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ import hy # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +extensions = ['sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] From 931ce889f9912f75b483d5c7b877976dbab05504 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 11:26:31 -0500 Subject: [PATCH 081/202] s/rply/rpython/ --- docs/language/internals.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index df43061..b9513f6 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -40,7 +40,7 @@ Lexing / tokenizing The first stage of compiling hy is to lex the source into tokens that we can deal with. We use a project called rply, which is a really nice (and fast) -parser, written in a subset of Python called rply. +parser, written in a subset of Python called rpython. The lexing code is all defined in ``hy.lex.lexer``. This code is mostly just defining the Hy grammer, and all the actual hard parts are taken care of by From 48f916b34f8cda4fcb24290801b55b9d000c2253 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Tue, 31 Dec 2013 21:58:40 +0530 Subject: [PATCH 082/202] Add myself to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 643541b..63e699a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,3 +21,4 @@ * Henrique Carvalho Alves * Joe Hakim Rahme * Kenan Bölükbaşı +* Abhishek L \ No newline at end of file From f6160c755a59a8d8203fb58ad66b03b4f13e158e Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sun, 22 Dec 2013 12:56:03 -0700 Subject: [PATCH 083/202] Much better version of new error messages. This version is much simpler. At the point that the exception is raised, we don't have access to the actual source, just the current expression. but as the exception percolates up, we can intercept it, add the source and the re-raise it. Then at the final point, in the cmdline handler, we can choose to let the entire traceback print, or just the simpler, direct error message. And even with the full traceback, the last bit is nicely formatted just like the shorter, simpler message. The error message is colored if clint is installed, but to avoid yet another dependency, you get monochrome without clint. I'm sure there is a better way to do the markup, the current method is kludgy but works. I wish there was more shared code between HyTypeError and LexException but they are kind of different in some fundamental ways. This doesn't work (yet) with runtime errors generated from Python, like NameError, but I have a method that can catch NameError and turn it into a more pleasing output. Finally, there is no obvious way to raise HyTypeError from pure Hy code, so methods in core/language.hy throw ugly TypeError/ValueError. --- hy/cmdline.py | 49 ++++++++++++++++++++++++++------- hy/compiler.py | 55 ++++++++++++++++++++++++++++++++++--- hy/errors.py | 39 ++++++++++++++++++++++++++ hy/importer.py | 26 +++++++++++++----- hy/lex/__init__.py | 5 ++-- hy/lex/exceptions.py | 39 ++++++++++++++++++++++++-- hy/lex/parser.py | 7 ++--- tests/compilers/test_ast.py | 14 ++++++---- tests/test_bin.py | 2 +- 9 files changed, 200 insertions(+), 36 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index eb621b0..e40862d 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -5,6 +5,7 @@ # Copyright (c) 2013 Konrad Hinsen # Copyright (c) 2013 Thom Neale # Copyright (c) 2013 Will Kahn-Greene +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -32,7 +33,7 @@ import sys import hy from hy.lex import LexException, PrematureEndOfInput, tokenize -from hy.compiler import hy_compile +from hy.compiler import hy_compile, HyTypeError from hy.importer import ast_compile, import_buffer_to_module from hy.completer import completion @@ -83,8 +84,11 @@ class HyREPL(code.InteractiveConsole): tokens = tokenize(source) except PrematureEndOfInput: return True - except LexException: - self.showsyntaxerror(filename) + except LexException as e: + if e.source is None: + e.source = source + e.filename = filename + print(e) return False try: @@ -92,6 +96,12 @@ class HyREPL(code.InteractiveConsole): if self.spy: print_python_code(_ast) code = ast_compile(_ast, filename, symbol) + except HyTypeError as e: + if e.source is None: + e.source = source + e.filename = filename + print(e) + return False except Exception: self.showtraceback() return False @@ -154,22 +164,34 @@ def ideas_macro(): require("hy.cmdline", "__console__") require("hy.cmdline", "__main__") +SIMPLE_TRACEBACKS = True + def run_command(source): try: import_buffer_to_module("__main__", source) - except LexException as exc: - # TODO: This would be better if we had line, col info. - print(source) - print(repr(exc)) - return 1 + except (HyTypeError, LexException) as e: + if SIMPLE_TRACEBACKS: + print(e) + return 1 + raise + except Exception: + raise return 0 def run_file(filename): from hy.importer import import_file_to_module - import_file_to_module("__main__", filename) - return 0 # right? + try: + import_file_to_module("__main__", filename) + except (HyTypeError, LexException) as e: + if SIMPLE_TRACEBACKS: + print(e) + return 1 + raise + except Exception: + raise + return 0 def run_repl(hr=None, spy=False): @@ -218,6 +240,9 @@ def cmdline_handler(scriptname, argv): parser.add_argument("-v", action="version", version=VERSION) + parser.add_argument("--show_tracebacks", action="store_true", + help="show complete tracebacks for Hy exceptions") + # this will contain the script/program name and any arguments for it. parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) @@ -228,6 +253,10 @@ def cmdline_handler(scriptname, argv): options = parser.parse_args(argv[1:]) + if options.show_tracebacks: + global SIMPLE_TRACEBACKS + SIMPLE_TRACEBACKS = False + # reset sys.argv like Python sys.argv = options.args or [""] diff --git a/hy/compiler.py b/hy/compiler.py index 1c7c156..9fa4c69 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -4,6 +4,7 @@ # Copyright (c) 2013 Julien Danjou # Copyright (c) 2013 Nicolas Dandrimont # Copyright (c) 2013 James King +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -93,11 +94,52 @@ class HyTypeError(TypeError): def __init__(self, expression, message): super(HyTypeError, self).__init__(message) self.expression = expression + self.message = message + self.source = None + self.filename = None def __str__(self): - return (super(HyTypeError, self).__str__() + " (line %s, column %d)" - % (self.expression.start_line, - self.expression.start_column)) + from hy.errors import colored + + line = self.expression.start_line + start = self.expression.start_column + end = self.expression.end_column + + source = [] + if self.source is not None: + source = self.source.split("\n")[line-1:self.expression.end_line] + + if line == self.expression.end_line: + length = end - start + else: + length = len(source[0]) - start + + result = "" + + result += ' File "%s", line %d, column %d\n\n' % (self.filename, + line, + start) + + if len(source) == 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*(length-1) + '^')) + if len(source) > 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*length)) + if len(source) > 2: # write the middle lines + for line in source[1:-1]: + result += ' %s\n' % colored.red("".join(line)) + result += ' %s\n' % colored.green("-"*len(line)) + + # write the last line + result += ' %s\n' % colored.red("".join(source[-1])) + result += ' %s\n' % colored.green('-'*(end-1) + '^') + + result += colored.yellow("HyTypeError: %s\n\n" % self.message) + + return result _compile_table = {} @@ -341,7 +383,7 @@ def checkargs(exact=None, min=None, max=None, even=None): if min is not None and (len(expression) - 1) < min: _raise_wrong_args_number( expression, - "`%%s' needs at least %d arguments, got %%d" % (min)) + "`%%s' needs at least %d arguments, got %%d." % (min)) if max is not None and (len(expression) - 1) > max: _raise_wrong_args_number( @@ -430,6 +472,8 @@ class HyASTCompiler(object): # nested; so let's re-raise this exception, let's not wrap it in # another HyCompileError! raise + except HyTypeError as e: + raise except Exception as e: raise HyCompileError(e, sys.exc_info()[2]) @@ -1584,6 +1628,9 @@ class HyASTCompiler(object): fn.replace(ofn) # Get the object we want to take an attribute from + if len(expression) < 2: + raise HyTypeError(expression, + "attribute access requires object") func = self.compile(expression.pop(1)) # And get the attribute diff --git a/hy/errors.py b/hy/errors.py index 323d67e..be932eb 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -25,3 +26,41 @@ class HyError(Exception): Exception. """ pass + + +try: + from clint.textui import colored +except: + class colored: + + @staticmethod + def black(foo): + return foo + + @staticmethod + def red(foo): + return foo + + @staticmethod + def green(foo): + return foo + + @staticmethod + def yellow(foo): + return foo + + @staticmethod + def blue(foo): + return foo + + @staticmethod + def magenta(foo): + return foo + + @staticmethod + def cyan(foo): + return foo + + @staticmethod + def white(foo): + return foo diff --git a/hy/importer.py b/hy/importer.py index b32b71a..35cd685 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -19,10 +20,9 @@ # DEALINGS IN THE SOFTWARE. from py_compile import wr_long, MAGIC -from hy.compiler import hy_compile +from hy.compiler import hy_compile, HyTypeError from hy.models import HyObject -from hy.lex import tokenize - +from hy.lex import tokenize, LexException from io import open import marshal @@ -73,17 +73,29 @@ def import_file_to_module(module_name, fpath): mod = imp.new_module(module_name) mod.__file__ = fpath eval(ast_compile(_ast, fpath, "exec"), mod.__dict__) + except (HyTypeError, LexException) as e: + if e.source is None: + fp = open(fpath, 'rt') + e.source = fp.read() + fp.close() + e.filename = fpath + raise except Exception: sys.modules.pop(module_name, None) raise - return mod def import_buffer_to_module(module_name, buf): - _ast = import_buffer_to_ast(buf, module_name) - mod = imp.new_module(module_name) - eval(ast_compile(_ast, "", "exec"), mod.__dict__) + try: + _ast = import_buffer_to_ast(buf, module_name) + mod = imp.new_module(module_name) + eval(ast_compile(_ast, "", "exec"), mod.__dict__) + except (HyTypeError, LexException) as e: + if e.source is None: + e.source = buf + e.filename = '' + raise return mod diff --git a/hy/lex/__init__.py b/hy/lex/__init__.py index 651f89a..8153aa8 100644 --- a/hy/lex/__init__.py +++ b/hy/lex/__init__.py @@ -33,6 +33,5 @@ def tokenize(buf): return parser.parse(lexer.lex(buf)) except LexingError as e: pos = e.getsourcepos() - raise LexException( - "Could not identify the next token at line %s, column %s" % ( - pos.lineno, pos.colno)) + raise LexException("Could not identify the next token.", + pos.lineno, pos.colno) diff --git a/hy/lex/exceptions.py b/hy/lex/exceptions.py index 21b2700..4c4b760 100644 --- a/hy/lex/exceptions.py +++ b/hy/lex/exceptions.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Nicolas Dandrimont +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -23,9 +24,43 @@ from hy.errors import HyError class LexException(HyError): """Error during the Lexing of a Hython expression.""" - pass + def __init__(self, message, lineno, colno): + super(LexException, self).__init__(message) + self.message = message + self.lineno = lineno + self.colno = colno + self.source = None + self.filename = '' + + def __str__(self): + from hy.errors import colored + + line = self.lineno + start = self.colno + + result = "" + + source = self.source.split("\n") + + if line > 0 and start > 0: + result += ' File "%s", line %d, column %d\n\n' % (self.filename, + line, + start) + + if len(self.source) > 0: + source_line = source[line-1] + else: + source_line = "" + + result += ' %s\n' % colored.red(source_line) + result += ' %s%s\n' % (' '*(start-1), colored.green('^')) + + result += colored.yellow("LexException: %s\n\n" % self.message) + + return result class PrematureEndOfInput(LexException): """We got a premature end of input""" - pass + def __init__(self, message): + super(PrematureEndOfInput, self).__init__(message, -1, -1) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 9e11069..72f0d8f 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -258,12 +258,11 @@ def t_identifier(p): def error_handler(token): tokentype = token.gettokentype() if tokentype == '$end': - raise PrematureEndOfInput + raise PrematureEndOfInput("Premature end of input") else: raise LexException( - "Ran into a %s where it wasn't expected at line %s, column %s" % - (tokentype, token.source_pos.lineno, token.source_pos.colno) - ) + "Ran into a %s where it wasn't expected." % tokentype, + token.source_pos.lineno, token.source_pos.colno) parser = pg.build() diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index c6590fa..43b33ea 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -22,6 +22,7 @@ from __future__ import unicode_literals from hy import HyString +from hy.models import HyObject from hy.compiler import hy_compile, HyCompileError, HyTypeError from hy.lex import tokenize @@ -42,10 +43,14 @@ def can_compile(expr): def cant_compile(expr): - expr = tokenize(expr) try: - hy_compile(expr, "__main__") + hy_compile(tokenize(expr), "__main__") assert False + except HyTypeError as e: + # Anything that can't be compiled should raise a user friendly + # error, otherwise it's a compiler bug. + assert isinstance(e.expression, HyObject) + assert e.message except HyCompileError as e: # Anything that can't be compiled should raise a user friendly # error, otherwise it's a compiler bug. @@ -422,8 +427,7 @@ def test_compile_error(): """Ensure we get compile error in tricky cases""" try: can_compile("(fn [] (= 1))") - except HyCompileError as e: - assert(str(e) - == "`=' needs at least 2 arguments, got 1 (line 1, column 8)") + except HyTypeError as e: + assert(e.message == "`=' needs at least 2 arguments, got 1.") else: assert(False) diff --git a/tests/test_bin.py b/tests/test_bin.py index 06cd455..8f73f56 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -64,7 +64,7 @@ def test_bin_hy_cmd(): ret = run_cmd("hy -c \"(koan\"") assert ret[0] == 1 - assert "PrematureEndOfInput" in ret[1] + assert "Premature end of input" in ret[1] def test_bin_hy_icmd(): From f064d3f1217c4bf0bdd122cec1c98b1240da55de Mon Sep 17 00:00:00 2001 From: Foxboron Date: Thu, 26 Dec 2013 04:02:20 +0100 Subject: [PATCH 084/202] Errors into errors.py, added HyMacroExpansionError, fixed macro arg fail --- hy/compiler.py | 74 ++---------------------------------------------- hy/errors.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ hy/macros.py | 8 +++++- 3 files changed, 85 insertions(+), 73 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 9fa4c69..01ddfce 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -24,8 +24,6 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from hy.errors import HyError - from hy.models.lambdalist import HyLambdaListKeyword from hy.models.expression import HyExpression from hy.models.keyword import HyKeyword @@ -37,12 +35,13 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict +from hy.errors import HyMacroExpansionError, HyCompileError, HyTypeError + import hy.macros from hy.macros import require, macroexpand from hy._compat import str_type, long_type import hy.importer -import traceback import importlib import codecs import ast @@ -73,75 +72,6 @@ def load_stdlib(): _stdlib[e] = module -class HyCompileError(HyError): - def __init__(self, exception, traceback=None): - self.exception = exception - self.traceback = traceback - - def __str__(self): - if isinstance(self.exception, HyTypeError): - return str(self.exception) - if self.traceback: - tb = "".join(traceback.format_tb(self.traceback)).strip() - else: - tb = "No traceback available. 😟" - return("Internal Compiler Bug 😱\n⤷ %s: %s\nCompilation traceback:\n%s" - % (self.exception.__class__.__name__, - self.exception, tb)) - - -class HyTypeError(TypeError): - def __init__(self, expression, message): - super(HyTypeError, self).__init__(message) - self.expression = expression - self.message = message - self.source = None - self.filename = None - - def __str__(self): - from hy.errors import colored - - line = self.expression.start_line - start = self.expression.start_column - end = self.expression.end_column - - source = [] - if self.source is not None: - source = self.source.split("\n")[line-1:self.expression.end_line] - - if line == self.expression.end_line: - length = end - start - else: - length = len(source[0]) - start - - result = "" - - result += ' File "%s", line %d, column %d\n\n' % (self.filename, - line, - start) - - if len(source) == 1: - result += ' %s\n' % colored.red(source[0]) - result += ' %s%s\n' % (' '*(start-1), - colored.green('^' + '-'*(length-1) + '^')) - if len(source) > 1: - result += ' %s\n' % colored.red(source[0]) - result += ' %s%s\n' % (' '*(start-1), - colored.green('^' + '-'*length)) - if len(source) > 2: # write the middle lines - for line in source[1:-1]: - result += ' %s\n' % colored.red("".join(line)) - result += ' %s\n' % colored.green("-"*len(line)) - - # write the last line - result += ' %s\n' % colored.red("".join(source[-1])) - result += ' %s\n' % colored.green('-'*(end-1) + '^') - - result += colored.yellow("HyTypeError: %s\n\n" % self.message) - - return result - - _compile_table = {} diff --git a/hy/errors.py b/hy/errors.py index be932eb..9f3f580 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -19,6 +19,8 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +import traceback + class HyError(Exception): """ @@ -64,3 +66,77 @@ except: @staticmethod def white(foo): return foo + + +class HyCompileError(HyError): + def __init__(self, exception, traceback=None): + self.exception = exception + self.traceback = traceback + + def __str__(self): + if isinstance(self.exception, HyTypeError): + return str(self.exception) + if self.traceback: + tb = "".join(traceback.format_tb(self.traceback)).strip() + else: + tb = "No traceback available. 😟" + return("Internal Compiler Bug 😱\n⤷ %s: %s\nCompilation traceback:\n%s" + % (self.exception.__class__.__name__, + self.exception, tb)) + + +class HyTypeError(TypeError): + def __init__(self, expression, message): + super(HyTypeError, self).__init__(message) + self.expression = expression + self.message = message + self.source = None + self.filename = None + + def __str__(self): + + line = self.expression.start_line + start = self.expression.start_column + end = self.expression.end_column + + source = [] + if self.source is not None: + source = self.source.split("\n")[line-1:self.expression.end_line] + + if line == self.expression.end_line: + length = end - start + else: + length = len(source[0]) - start + + result = "" + + result += ' File "%s", line %d, column %d\n\n' % (self.filename, + line, + start) + + if len(source) == 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*(length-1) + '^')) + if len(source) > 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*length)) + if len(source) > 2: # write the middle lines + for line in source[1:-1]: + result += ' %s\n' % colored.red("".join(line)) + result += ' %s\n' % colored.green("-"*len(line)) + + # write the last line + result += ' %s\n' % colored.red("".join(source[-1])) + result += ' %s\n' % colored.green('-'*(end-1) + '^') + + + result += colored.yellow("%s: %s\n\n" % (self.__class__.__name__, + self.message)) + + return result + + +class HyMacroExpansionError(HyTypeError): + pass diff --git a/hy/macros.py b/hy/macros.py index 4e2389c..dd31609 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -28,6 +28,8 @@ from hy.models.complex import HyComplex from hy.models.dict import HyDict from hy._compat import str_type, long_type +from hy.errors import HyMacroExpansionError + from collections import defaultdict import sys @@ -192,7 +194,11 @@ def macroexpand_1(tree, module_name): if m is None: m = _hy_macros[None].get(fn) if m is not None: - obj = _wrap_value(m(*ntree[1:])) + try: + obj = _wrap_value(m(*ntree[1:])) + except Exception as e: + msg = str(tree[0]) + " " + " ".join(str(e).split()[1:]) + raise HyMacroExpansionError(tree, msg) obj.replace(tree) return obj From 765dba3e56d40c7bed00273a7012f061908c3955 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Thu, 26 Dec 2013 09:36:45 -0700 Subject: [PATCH 085/202] More updates, including from Foxboron, for errors like (for) --- hy/cmdline.py | 12 ++++++++---- hy/compiler.py | 3 ++- hy/core/bootstrap.hy | 2 +- hy/core/macros.hy | 2 ++ hy/errors.py | 8 +++++--- hy/macros.py | 9 +++++++-- tests/compilers/test_ast.py | 35 ++++++++++++++++++++++++++++++++++- tests/test_bin.py | 2 +- 8 files changed, 60 insertions(+), 13 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index e40862d..09fe366 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -80,6 +80,7 @@ class HyREPL(code.InteractiveConsole): filename=filename) def runsource(self, source, filename='', symbol='single'): + global SIMPLE_TRACEBACKS try: tokens = tokenize(source) except PrematureEndOfInput: @@ -88,7 +89,7 @@ class HyREPL(code.InteractiveConsole): if e.source is None: e.source = source e.filename = filename - print(e) + sys.stderr.write(str(e)) return False try: @@ -100,7 +101,10 @@ class HyREPL(code.InteractiveConsole): if e.source is None: e.source = source e.filename = filename - print(e) + if SIMPLE_TRACEBACKS: + sys.stderr.write(str(e)) + else: + self.showtraceback() return False except Exception: self.showtraceback() @@ -172,7 +176,7 @@ def run_command(source): import_buffer_to_module("__main__", source) except (HyTypeError, LexException) as e: if SIMPLE_TRACEBACKS: - print(e) + sys.stderr.write(str(e)) return 1 raise except Exception: @@ -186,7 +190,7 @@ def run_file(filename): import_file_to_module("__main__", filename) except (HyTypeError, LexException) as e: if SIMPLE_TRACEBACKS: - print(e) + sys.stderr.write(str(e)) return 1 raise except Exception: diff --git a/hy/compiler.py b/hy/compiler.py index 01ddfce..c418dba 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -35,13 +35,14 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict -from hy.errors import HyMacroExpansionError, HyCompileError, HyTypeError +from hy.errors import HyCompileError, HyTypeError import hy.macros from hy.macros import require, macroexpand from hy._compat import str_type, long_type import hy.importer +import traceback import importlib import codecs import ast diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy index 5d3d367..3afd2c5 100644 --- a/hy/core/bootstrap.hy +++ b/hy/core/bootstrap.hy @@ -28,7 +28,7 @@ (defmacro macro-error [location reason] "error out properly within a macro" - `(raise (hy.compiler.HyTypeError ~location ~reason))) + `(raise (hy.errors.HyMacroExpansionError ~location ~reason))) (defmacro defmacro-alias [names lambda-list &rest body] diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 39338c1..a2a240e 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -31,6 +31,8 @@ (for* [x foo] (for* [y bar] baz))" + (if (empty? body) + (macro-error None "`for' requires a body to evaluate")) (if args `(for* ~(.pop args 0) (for ~args ~@body)) `(do ~@body))) diff --git a/hy/errors.py b/hy/errors.py index 9f3f580..2f269d5 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -1,3 +1,5 @@ +# -*- encoding: utf-8 -*- +# # Copyright (c) 2013 Paul Tagliamonte # Copyright (c) 2013 Bob Tolbert # @@ -131,9 +133,9 @@ class HyTypeError(TypeError): result += ' %s\n' % colored.red("".join(source[-1])) result += ' %s\n' % colored.green('-'*(end-1) + '^') - - result += colored.yellow("%s: %s\n\n" % (self.__class__.__name__, - self.message)) + result += colored.yellow("%s: %s\n\n" % + (self.__class__.__name__, + self.message)) return result diff --git a/hy/macros.py b/hy/macros.py index dd31609..d98b328 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -28,7 +28,7 @@ from hy.models.complex import HyComplex from hy.models.dict import HyDict from hy._compat import str_type, long_type -from hy.errors import HyMacroExpansionError +from hy.errors import HyTypeError, HyMacroExpansionError from collections import defaultdict import sys @@ -196,8 +196,13 @@ def macroexpand_1(tree, module_name): if m is not None: try: obj = _wrap_value(m(*ntree[1:])) + except HyTypeError as e: + if e.expression is None: + e.expression = tree + raise except Exception as e: - msg = str(tree[0]) + " " + " ".join(str(e).split()[1:]) + msg = "`" + str(tree[0]) + "' " + \ + " ".join(str(e).split()[1:]) raise HyMacroExpansionError(tree, msg) obj.replace(tree) return obj diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 43b33ea..76a9e1d 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -23,7 +23,9 @@ from __future__ import unicode_literals from hy import HyString from hy.models import HyObject -from hy.compiler import hy_compile, HyCompileError, HyTypeError +from hy.compiler import hy_compile +from hy.errors import HyCompileError, HyTypeError +from hy.lex.exceptions import LexException from hy.lex import tokenize import ast @@ -431,3 +433,34 @@ def test_compile_error(): assert(e.message == "`=' needs at least 2 arguments, got 1.") else: assert(False) + + +def test_for_compile_error(): + """Ensure we get compile error in tricky 'for' cases""" + try: + can_compile("(fn [] (for)") + except LexException as e: + assert(e.message == "Premature end of input") + else: + assert(False) + + try: + can_compile("(fn [] (for)))") + except LexException as e: + assert(e.message == "Ran into a RPAREN where it wasn't expected.") + else: + assert(False) + + try: + can_compile("(fn [] (for [x]))") + except HyTypeError as e: + assert(e.message == "`for' requires an even number of elements in its first argument") # noqa + else: + assert(False) + + try: + can_compile("(fn [] (for [x xx]))") + except HyTypeError as e: + assert(e.message == "`for' requires a body to evaluate") + else: + assert(False) diff --git a/tests/test_bin.py b/tests/test_bin.py index 8f73f56..d18dd31 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -64,7 +64,7 @@ def test_bin_hy_cmd(): ret = run_cmd("hy -c \"(koan\"") assert ret[0] == 1 - assert "Premature end of input" in ret[1] + assert "Premature end of input" in ret[2] def test_bin_hy_icmd(): From 5040c2994632e60d891399f4a2e755319eb8aa2f Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Thu, 26 Dec 2013 20:46:48 -0700 Subject: [PATCH 086/202] Cleaning up some comments from berker --- docs/language/cli.rst | 4 ++++ hy/errors.py | 2 +- hy/importer.py | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 4bbca65..2cccd65 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -32,6 +32,10 @@ Command line options .. versionadded:: 0.9.11 +.. cmdoption:: --show_tracebacks + + Print extended tracebacks for Hy exceptions. + .. cmdoption:: -v Print the Hy version number and exit. diff --git a/hy/errors.py b/hy/errors.py index 2f269d5..0e786e8 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -34,7 +34,7 @@ class HyError(Exception): try: from clint.textui import colored -except: +except Exception: class colored: @staticmethod diff --git a/hy/importer.py b/hy/importer.py index 35cd685..1f8b7eb 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -75,9 +75,8 @@ def import_file_to_module(module_name, fpath): eval(ast_compile(_ast, fpath, "exec"), mod.__dict__) except (HyTypeError, LexException) as e: if e.source is None: - fp = open(fpath, 'rt') - e.source = fp.read() - fp.close() + with open(fpath, 'rt') as fp: + e.source = fp.read() e.filename = fpath raise except Exception: From 05899423df1b27a981f8e42f3cf480e93a0caa8d Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sat, 28 Dec 2013 06:58:08 -0700 Subject: [PATCH 087/202] Changing --show_tracebacks to --show-tracebacks --- docs/language/cli.rst | 4 +++- hy/cmdline.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 2cccd65..875efa5 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -32,10 +32,12 @@ Command line options .. versionadded:: 0.9.11 -.. cmdoption:: --show_tracebacks +.. cmdoption:: --show-tracebacks Print extended tracebacks for Hy exceptions. + .. versionadded:: 0.9.12 + .. cmdoption:: -v Print the Hy version number and exit. diff --git a/hy/cmdline.py b/hy/cmdline.py index 09fe366..b09595f 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -244,7 +244,7 @@ def cmdline_handler(scriptname, argv): parser.add_argument("-v", action="version", version=VERSION) - parser.add_argument("--show_tracebacks", action="store_true", + parser.add_argument("--show-tracebacks", action="store_true", help="show complete tracebacks for Hy exceptions") # this will contain the script/program name and any arguments for it. From faf782560cbfdfc064744c048e07479c480087cb Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Tue, 31 Dec 2013 09:53:43 -0700 Subject: [PATCH 088/202] Fixing tests for new 'for' syntax --- tests/compilers/test_ast.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 76a9e1d..b191c17 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -313,7 +313,7 @@ def test_ast_valid_while(): def test_ast_valid_for(): "Make sure AST can compile valid for" - can_compile("(for [[a 2]])") + can_compile("(for [[a 2]] (print a))") def test_ast_invalid_for(): @@ -452,14 +452,14 @@ def test_for_compile_error(): assert(False) try: - can_compile("(fn [] (for [x]))") - except HyTypeError as e: - assert(e.message == "`for' requires an even number of elements in its first argument") # noqa - else: - assert(False) - - try: - can_compile("(fn [] (for [x xx]))") + can_compile("(fn [] (for [[x]]))") + except HyTypeError as e: + assert(e.message == "`for' requires a body to evaluate") + else: + assert(False) + + try: + can_compile("(fn [] (for [[x xx]]))") except HyTypeError as e: assert(e.message == "`for' requires a body to evaluate") else: From 8cabf22749478751911ebb69816b600f0165e5c7 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 12:04:43 -0500 Subject: [PATCH 089/202] add news for 0.9.12 --- NEWS | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 1a654ab..eea9ae5 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,76 @@ Changes from Hy 0.9.11 - [ Misc. Fixes ] - [ Syntax Fixes ] + tl;dr: + + 0.9.12 comes with some massive changes, + We finally took the time to implement gensym, as well as a few + other bits that help macro writing. Check the changelog for + what exactly was added. + + The biggest feature, Reader Macros, landed later + in the cycle, but were big enough to warrent a release on it's + own. A huge thanks goes to Foxboron for implementing them + and a massive hug goes out to olasd for providing ongoing + reviews during the development. + + Welcome to the new Hy contributors, Henrique Carvalho Alves + an Kenan Bölükbaşı. Thanks for your work so far, folks! + + Hope y'all enjoy the finest that 2013 has to offer, + - Hy Society + + + * Special thanks goes to Willyfrog, Foxboron and theanalyst for writing + 0.9.12's NEWS. Thanks, y'all! (PT) + + [ Language Changes ] - * Translate foo? -> is_foo, for better Python interop. (PT) + * Translate foo? -> is_foo, for better Python interop. (PT) + * Reader Macros! + * Operators + and * now can work without arguments + * Define kwapply as a macro + * Added apply as a function + * Instant symbol generation with gensym + * Allow macros to return None + * Add a method for casting into byte string or unicode depending on python version + * flatten function added to language + * Add a method for casting into byte string or unicode depending on python version + * Added type coercing to the right integer for the platform + + + [ Misc. Fixes ] + * Added information about core team members + * Documentation fixed and extended + * Add astor to install_requires to fix hy --spy failing on hy 0.9.11. + * Convert stdout and stderr to UTF-8 properly in the run_cmd helper. + * Update requirements.txt and setup.py to use rply upstream. + * tryhy link added in documentation and README + * Command line options documented + * Adding support for coverage tests at coveralls.io + * Added info about tox, so people can use it prior to a PR + * Added the start of hacking rules + * Halting Problem removed from example as it was nonfree + * Fixed PyPI is now behind a CDN. The --use-mirrors option is deprecated. + * Badges for pypi version and downloads. + + + [ Syntax Fixes ] + * for changed syntax from (for [] ...) -> (for [[]] ...) + * with changed syntax from (with [] ...) -> (with [[]] ...) + * get allows multiple arguments + + + [ Bug Fixes ] + * OSX: Fixes for readline Repl problem which caused HyREPL not allowing 'b' + * Fix REPL completions on OSX + * Make HyObject.replace more resilient to prevent compiler breakage. + + + [ Contrib changes ] + * Anaphoric macros added to contrib + * Modified eg/twisted to follow the newer hy syntax + * Added (experimental) profile module + Changes from Hy 0.9.10 From 3774a05d879062bd5f649009ae59aca15a7c48e6 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 31 Dec 2013 19:39:37 +0200 Subject: [PATCH 090/202] Add Kevin Zita to NEWS for 0.9.12. --- NEWS | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index eea9ae5..002ed0e 100644 --- a/NEWS +++ b/NEWS @@ -13,11 +13,12 @@ Changes from Hy 0.9.11 and a massive hug goes out to olasd for providing ongoing reviews during the development. - Welcome to the new Hy contributors, Henrique Carvalho Alves - an Kenan Bölükbaşı. Thanks for your work so far, folks! + Welcome to the new Hy contributors, Henrique Carvalho Alves, + Kevin Zita and Kenan Bölükbaşı. Thanks for your work so far, + folks! - Hope y'all enjoy the finest that 2013 has to offer, - - Hy Society + Hope y'all enjoy the finest that 2013 has to offer, + - Hy Society * Special thanks goes to Willyfrog, Foxboron and theanalyst for writing From eeef65b505cc0f8b7585db35772428e2b87a24c9 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 13:35:31 -0500 Subject: [PATCH 091/202] Change the signature of (for) and (with). (for) is restored to clojure-like flatness. (with) can be used without the nested list if you don't care about the name. Tests and examples updated. --- NEWS | 2 -- docs/language/api.rst | 21 +++++++++++---------- eg/python3/futures/hello-world.hy | 2 +- hy/contrib/anaphoric.hy | 10 +++++----- hy/core/macros.hy | 31 ++++++++++++++++++++++++++----- tests/compilers/test_ast.py | 8 ++++---- tests/native_tests/language.hy | 18 +++++++++--------- 7 files changed, 56 insertions(+), 36 deletions(-) diff --git a/NEWS b/NEWS index eea9ae5..cdfb630 100644 --- a/NEWS +++ b/NEWS @@ -55,8 +55,6 @@ Changes from Hy 0.9.11 [ Syntax Fixes ] - * for changed syntax from (for [] ...) -> (for [[]] ...) - * with changed syntax from (with [] ...) -> (with [[]] ...) * get allows multiple arguments diff --git a/docs/language/api.rst b/docs/language/api.rst index 4e70b7d..57c64e7 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -205,11 +205,12 @@ however is called only for every other value in the list. ;; assuming that (side-effect1) and (side-effect2) are functions and ;; collection is a list of numerical values - (for (x collection) (do - (side-effect1 x) - (if (% x 2) - (continue)) - (side-effect2 x))) + (for [x collection] + (do + (side-effect1 x) + (if (% x 2) + (continue)) + (side-effect2 x))) do / progn @@ -489,10 +490,10 @@ collection and calls side-effect to each element in the collection: .. code-block:: clj ;; assuming that (side-effect) is a function that takes a single parameter - (for [[element collection]] (side-effect element)) + (for [element collection] (side-effect element)) ;; for can have an optional else block - (for [[element collection]] (side-effect element) + (for [element collection] (side-effect element) (else (side-effect-2))) The optional `else` block is executed only if the `for` loop terminates @@ -500,14 +501,14 @@ normally. If the execution is halted with `break`, the `else` does not execute. .. code-block:: clj - => (for [[element [1 2 3]]] (if (< element 3) + => (for [element [1 2 3]] (if (< element 3) ... (print element) ... (break)) ... (else (print "loop finished"))) 1 2 - => (for [[element [1 2 3]]] (if (< element 4) + => (for [element [1 2 3]] (if (< element 4) ... (print element) ... (break)) ... (else (print "loop finished"))) @@ -680,7 +681,7 @@ function is defined and passed to another function for filtering output. ... {:name "Dave" :age 5}]) => (defn display-people [people filter] - ... (for [[person people]] (if (filter person) (print (:name person))))) + ... (for [person people] (if (filter person) (print (:name person))))) => (display-people people (fn [person] (< (:age person) 25))) Alice diff --git a/eg/python3/futures/hello-world.hy b/eg/python3/futures/hello-world.hy index 19d99d4..99f691f 100644 --- a/eg/python3/futures/hello-world.hy +++ b/eg/python3/futures/hello-world.hy @@ -7,5 +7,5 @@ (with-as (ThreadPoolExecutor 10) executor (setv jobs (list-comp (.submit executor task-to-do) (x (range 0 10)))) - (for (future (as-completed jobs)) + (for [future (as-completed jobs)] (.result future))) diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index 9a281d0..2f37b47 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -31,14 +31,14 @@ (defmacro ap-each [lst &rest body] "Evaluate the body form for each element in the list." - `(for [[it ~lst]] ~@body)) + `(for [it ~lst] ~@body)) (defmacro ap-each-while [lst form &rest body] "Evalutate the body form for each element in the list while the predicate form evaluates to True." `(let [[p (lambda [it] ~form)]] - (for [[it ~lst]] + (for [it ~lst] (if (p it) ~@body (break))))) @@ -47,7 +47,7 @@ (defmacro ap-map [form lst] "Yield elements evaluated in the form for each element in the list." `(let [[f (lambda [it] ~form)]] - (for [[v ~lst]] + (for [v ~lst] (yield (f v))))) @@ -55,7 +55,7 @@ "Yield elements evaluated for each element in the list when the predicate function returns True." `(let [[f (lambda [it] ~rep)]] - (for [[it ~lst]] + (for [it ~lst] (if (~predfn it) (yield (f it)) (yield it))))) @@ -64,7 +64,7 @@ (defmacro ap-filter [form lst] "Yield elements returned when the predicate form evaluates to True." `(let [[pred (lambda [it] ~form)]] - (for [[val ~lst]] + (for [val ~lst] (if (pred val) (yield val))))) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index a2a240e..cdc5d17 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -25,16 +25,29 @@ ;;; These macros form the hy language ;;; They are automatically required in every module, except inside hy.core + +(import [hy.models.list [HyList]] + [hy.models.symbol [HySymbol]]) + + + (defmacro for [args &rest body] "shorthand for nested for loops: - (for [[x foo] [y bar]] baz) -> + (for [x foo + y bar] + baz) -> (for* [x foo] (for* [y bar] baz))" + + (if (odd? (len args)) + (macro-error args "`for' requires an even number of args.")) + (if (empty? body) (macro-error None "`for' requires a body to evaluate")) (if args - `(for* ~(.pop args 0) (for ~args ~@body)) + `(for* [~(.pop args 0) ~(.pop args 0)] + (for ~args ~@body)) `(do ~@body))) @@ -44,9 +57,17 @@ (with* [x foo] (with* [y bar] baz))" - (if args - `(with* ~(.pop args 0) (with ~args ~@body)) - `(do ~@body))) + + (if (not (empty? args)) + (let [[primary (.pop args 0)]] + (if (isinstance primary HyList) + ;;; OK. if we have a list, we can go ahead and unpack that + ;;; as the argument to with. + `(with* [~@primary] (with ~args ~@body)) + ;;; OK, let's just give it away. This may not be something we + ;;; can do, but that's really the programmer's problem. + `(with* [~primary] (with ~args ~@body)))) + `(do ~@body))) (defmacro-alias [car first] [thing] diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index b191c17..716f2bb 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -313,7 +313,7 @@ def test_ast_valid_while(): def test_ast_valid_for(): "Make sure AST can compile valid for" - can_compile("(for [[a 2]] (print a))") + can_compile("(for [a 2] (print a))") def test_ast_invalid_for(): @@ -452,14 +452,14 @@ def test_for_compile_error(): assert(False) try: - can_compile("(fn [] (for [[x]]))") + can_compile("(fn [] (for [x]))") except HyTypeError as e: - assert(e.message == "`for' requires a body to evaluate") + assert(e.message == "`for' requires an even number of args.") else: assert(False) try: - can_compile("(fn [] (for [[x xx]]))") + can_compile("(fn [] (for [x xx]))") except HyTypeError as e: assert(e.message == "`for' requires a body to evaluate") else: diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 8bd1776..30a63e4 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -31,12 +31,12 @@ (defn test-for-loop [] "NATIVE: test for loops?" (setv count 0) - (for [[x [1 2 3 4 5]]] + (for [x [1 2 3 4 5]] (setv count (+ count x))) (assert (= count 15)) (setv count 0) - (for [[x [1 2 3 4 5]] - [y [1 2 3 4 5]]] + (for [x [1 2 3 4 5] + y [1 2 3 4 5]] (setv count (+ count x y))) (assert (= count 150))) @@ -404,9 +404,9 @@ (defn test-yield [] "NATIVE: test yielding" - (defn gen [] (for [[x [1 2 3 4]]] (yield x))) + (defn gen [] (for [x [1 2 3 4]] (yield x))) (setv ret 0) - (for [[y (gen)]] (setv ret (+ ret y))) + (for [y (gen)] (setv ret (+ ret y))) (assert (= ret 10))) @@ -463,7 +463,7 @@ (defn test-for-doodle [] "NATIVE: test for-do" (do (do (do (do (do (do (do (do (do (setv (, x y) (, 0 0))))))))))) - (for [[- [1 2]]] + (for [- [1 2]] (do (setv x (+ x 1)) (setv y (+ y 1)))) @@ -646,7 +646,7 @@ (defn test-nested-if [] "NATIVE: test nested if" - (for [[x (range 10)]] + (for [x (range 10)] (if (in "foo" "foobar") (do (if true true true)) @@ -810,14 +810,14 @@ (defn test-break-breaking [] "NATIVE: test checking if break actually breaks" - (defn holy-grail [] (for [[x (range 10)]] (if (= x 5) (break))) x) + (defn holy-grail [] (for [x (range 10)] (if (= x 5) (break))) x) (assert (= (holy-grail) 5))) (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [[x (range 10)]] + (for [x (range 10)] (if (!= x 5) (continue)) (.append y x)) From dfbdbdfb73d43c63a6370a1afe2a3e834cbff783 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 14:24:38 -0500 Subject: [PATCH 092/202] We're 0.9.12 --- hy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/version.py b/hy/version.py index e96cb4c..2c33563 100644 --- a/hy/version.py +++ b/hy/version.py @@ -20,4 +20,4 @@ __appname__ = "hy" -__version__ = "0.9.11" +__version__ = "0.9.12" From 032200bcb40a8d007bb1764516039f1ca4d06750 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Tue, 31 Dec 2013 16:14:05 -0700 Subject: [PATCH 093/202] Some small doc fixes This cleans up a number of doc warnings, including a bad underline for zero? While there, added a nil? function to match up with the new nil is None. Also un-hid myself from coreteam. --- docs/conf.py | 2 +- docs/index.rst | 20 ++++++++++---------- docs/language/core.rst | 32 +++++++++++++++++++++++++++++++- hy/core/language.hy | 6 +++++- scripts/update_coreteam.py | 1 - tests/native_tests/core.hy | 9 +++++++++ 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6b45309..1d5b516 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ release = hy.__version__ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ['_build', 'coreteam.rst'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None diff --git a/docs/index.rst b/docs/index.rst index a8ad82f..2f568c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,17 +19,17 @@ Meet our mascot, "Cuddles": .. image:: http://fc07.deviantart.net/fs70/i/2013/138/f/0/cuddles_the_hacker_by_doctormo-d65l7lq.png :alt: Paul riding cuddles into the distance - -.. Our old ascii art mascot version -.. Retained as an easter egg for those who read the docs via .rst! .. -.. LET'S CUDDLEFISH -.. ______ -.. _.----'#' # ' -.. ,' #' ,# ; -.. (' (w) _,-'_/ -.. /// / /'.____.' -.. \|\||/ + Our old ascii art mascot version + Retained as an easter egg for those who read the docs via .rst! + + LET'S CUDDLEFISH + ______ + _.----'#' # ' + ,' #' ,# ; + (' (w) _,-'_/ + /// / /'.____.' + \|\||/ You can try Hy `in your browser `_. diff --git a/docs/language/core.rst b/docs/language/core.rst index 9b24747..300da15 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -242,6 +242,36 @@ Raises ``TypeError`` if ``(not (numeric? x))``. => (neg? 0) False + +.. _nil?-fn: + +nil? +----- + +Usage: ``(nil? x)`` + +Return True if x is nil/None. + +.. code-block:: clojure + + => (nil? nil) + True + + => (nil? None) + True + + => (nil? 0) + False + + => (setf x nil) + => (nil? x) + True + + => ;; list.append always returns None + => (nil? (.append [1 2 3] 4)) + True + + .. _none?-fn: none? @@ -397,7 +427,7 @@ Return True if x is a string. .. _zero?-fn: zero? ----- +----- Usage: ``(zero? x)`` diff --git a/hy/core/language.hy b/hy/core/language.hy index 9ac2fee..5c69071 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -160,6 +160,10 @@ "Return true if x is None" (is x None)) +(defn nil? [x] + "Return true if x is nil (None)" + (is x None)) + (defn numeric? [x] (import numbers) (instance? numbers.Number x)) @@ -253,5 +257,5 @@ (def *exports* '[cycle dec distinct drop drop-while empty? even? filter flatten float? gensym inc instance? integer integer? iterable? iterate iterator? neg? - none? nth numeric? odd? pos? remove repeat repeatedly second + nil? none? nth numeric? odd? pos? remove repeat repeatedly second string string? take take-nth take-while zero?]) diff --git a/scripts/update_coreteam.py b/scripts/update_coreteam.py index dda9754..90f2d86 100644 --- a/scripts/update_coreteam.py +++ b/scripts/update_coreteam.py @@ -19,7 +19,6 @@ MISSING_NAMES = { # an owner of the organization. CONCEALED_MEMBERS = [ ('aldeka', 'Karen Rustad'), - ('rwtolbert', 'Bob Tolbert'), ('tuturto', 'Tuukka Turto'), ] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index aedf99c..1427187 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -293,6 +293,15 @@ (assert-false (none? 0)) (assert-false (none? ""))) +(defn test-nil? [] + "NATIVE: testing for `is nil`" + (assert-true (nil? nil)) + (assert-true (nil? None)) + (setv f nil) + (assert-true (nil? f)) + (assert-false (nil? 0)) + (assert-false (nil? ""))) + (defn test-nth [] "NATIVE: testing the nth function" (assert-equal 2 (nth [1 2 4 7] 1)) From 553337080a4268f2b75d6f03c20807c737a5e006 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 12:38:44 -0500 Subject: [PATCH 094/202] Adding missing AUTHORs from the git log. Sorry, folks. --- AUTHORS | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 63e699a..3e28c80 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,4 +21,18 @@ * Henrique Carvalho Alves * Joe Hakim Rahme * Kenan Bölükbaşı -* Abhishek L \ No newline at end of file +* Abhishek L +* Christopher Browne +* Clinton N. Dreisbach +* D. Joe +* Duncan McGreggor +* E. Anders Lannerback +* Foxboron +* Jack +* Johan Euphrosine +* Kevin Zita +* Matt Fenwick +* Sean B. Palmer +* Thom Neale +* Tuukka Turto +* Vasudev Kamath From 8adae9a01f0acaf96df0da6d770eaec56cab8fcd Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 12:41:22 -0500 Subject: [PATCH 095/202] No fox for you. --- AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 3e28c80..09c2e2e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -27,7 +27,6 @@ * D. Joe * Duncan McGreggor * E. Anders Lannerback -* Foxboron * Jack * Johan Euphrosine * Kevin Zita From 74739bc43e80565d66c91e8c617a7c591f3916df Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 00:52:29 +0100 Subject: [PATCH 096/202] Whitespace fix --- tests/native_tests/language.hy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 579c510..952dc49 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -817,9 +817,9 @@ (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [x (range 10)] - (if (!= x 5) - (continue)) + (for [x (range 10)] + (if (!= x 5) + (continue)) (.append y x)) (assert (= y [5]))) From 26e2fb3606d4c56585d329773cd7d5a6f0ff6915 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 18:56:09 -0500 Subject: [PATCH 097/202] Give a whack at Python 3.4 support This adds ast.arg for Python 3.4+, for FunctionDef args and other args (starargs, kwargs) --- hy/compiler.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index c418dba..fd45eb1 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1699,12 +1699,32 @@ class HyASTCompiler(object): arglist = expression.pop(0) ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist) + if sys.version_info[0] >= 3 and sys.version_info[1] >= 4: + # Python 3.4+ requres that args are an ast.arg object, rather + # than an ast.Name or bare string. + args = [ast.arg(arg=ast_str(x), + annotation=None, ### Fix me! + lineno=x.start_line, + col_offset=x.start_column) for x in args] + + # XXX: Beware. Beware. This wasn't put into the parse lambda + # list because it's really just an internal parsing thing. + + if kwargs: + kwargs = ast.arg(arg=kwargs, annotation=None) + + if stararg: + stararg = ast.arg(arg=stararg, annotation=None) + + # Let's find a better home for these guys. + else: + args = [ast.Name(arg=ast_str(x), id=ast_str(x), + ctx=ast.Param(), + lineno=x.start_line, + col_offset=x.start_column) for x in args] + args = ast.arguments( - args=[ast.Name(arg=ast_str(x), id=ast_str(x), - ctx=ast.Param(), - lineno=x.start_line, - col_offset=x.start_column) - for x in args], + args=args, vararg=stararg, kwarg=kwargs, kwonlyargs=[], From 1d58e5258452e6dd5f60799cf6f1ed017bbd7dab Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 19:12:17 -0500 Subject: [PATCH 098/202] Flake8 style fix. --- hy/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/compiler.py b/hy/compiler.py index fd45eb1..0a7071c 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1703,7 +1703,7 @@ class HyASTCompiler(object): # Python 3.4+ requres that args are an ast.arg object, rather # than an ast.Name or bare string. args = [ast.arg(arg=ast_str(x), - annotation=None, ### Fix me! + annotation=None, # Fix me! lineno=x.start_line, col_offset=x.start_column) for x in args] From a44e53f4de38b02726b8348505a112999d3b8411 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 03:09:18 +0100 Subject: [PATCH 099/202] Comments end when the input ends or a newline occurs This fixes #382, which occured because the REPL doesn't use trailing newlines. --- hy/lex/lexer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/lex/lexer.py b/hy/lex/lexer.py index d4a5638..8032fc0 100644 --- a/hy/lex/lexer.py +++ b/hy/lex/lexer.py @@ -60,7 +60,7 @@ lg.add('STRING', r'''(?x) lg.add('IDENTIFIER', r'[^()\[\]{}\'"\s;]+') -lg.ignore(r';.*[\r\n]+') +lg.ignore(r';.*(?=\r|\n|$)') lg.ignore(r'\s+') From c1d5948d73280e61e48cf42f9aed944e87392aa3 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 03:13:49 +0100 Subject: [PATCH 100/202] Add regression test for #382 --- tests/lex/test_lex.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index bda1e3f..af9f286 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -260,3 +260,9 @@ def test_reader_macro(): assert entry[0][0] == HySymbol("dispatch_reader_macro") assert entry[0][1] == HyExpression([HySymbol("quote"), HyString("^")]) assert len(entry[0]) == 3 + + +def test_lex_comment_382(): + """Ensure that we can tokenize sources with a comment at the end""" + entry = tokenize("foo ;bar\n;baz") + assert entry == [HySymbol("foo")] From f452558f5e7b795887130891ea0c9fe6fa7022cb Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 04:10:31 +0100 Subject: [PATCH 101/202] Force output encoding of hy2py as utf-8 Fixes hy2py in py34 --- bin/hy2py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/hy2py b/bin/hy2py index 125fd26..85186b7 100755 --- a/bin/hy2py +++ b/bin/hy2py @@ -11,15 +11,15 @@ import ast module_name = "" hst = import_file_to_hst(sys.argv[1]) -print(hst) +print(str(hst).encode("utf-8")) print("") print("") _ast = import_file_to_ast(sys.argv[1], module_name) print("") print("") -print(ast.dump(_ast)) +print(ast.dump(_ast).encode("utf-8")) print("") print("") -print(astor.codegen.to_source(_ast)) +print(astor.codegen.to_source(_ast).encode("utf-8")) import_file_to_module(module_name, sys.argv[1]) From 590d3bf1df4bf5ef8d8297dc7a189b4a9f08e747 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 22:30:09 -0500 Subject: [PATCH 102/202] Clean up compare to use the tuple. Thanks @olasd --- hy/_compat.py | 2 +- hy/compiler.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hy/_compat.py b/hy/_compat.py index 65aa29b..e25f9cd 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -39,7 +39,7 @@ except ImportError: import sys PY3 = sys.version_info[0] >= 3 -PY33 = sys.version_info[0] >= 3 and sys.version_info[1] >= 3 +PY33 = sys.version_info >= (3, 3) if PY3: str_type = str diff --git a/hy/compiler.py b/hy/compiler.py index 0a7071c..dbb3ee5 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -754,7 +754,7 @@ class HyASTCompiler(object): ret = handler_results - if sys.version_info[0] >= 3 and sys.version_info[1] >= 3: + if sys.version_info >= (3, 3): # Python 3.3 features a merge of TryExcept+TryFinally into Try. return ret + ast.Try( lineno=expr.start_line, @@ -1199,7 +1199,7 @@ class HyASTCompiler(object): optional_vars=thing, body=body.stmts) - if sys.version_info[0] >= 3 and sys.version_info[1] >= 3: + if sys.version_info >= (3, 3): the_with.items = [ast.withitem(context_expr=ctx.force_expr, optional_vars=thing)] @@ -1699,7 +1699,7 @@ class HyASTCompiler(object): arglist = expression.pop(0) ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist) - if sys.version_info[0] >= 3 and sys.version_info[1] >= 4: + if sys.version_info >= (3, 4): # Python 3.4+ requres that args are an ast.arg object, rather # than an ast.Name or bare string. args = [ast.arg(arg=ast_str(x), From 715158c7dbddd222a5bc3e80266dd648ce47e0d5 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Tue, 31 Dec 2013 15:40:58 -0700 Subject: [PATCH 103/202] Add a "clean" target to make and make.bat --- Makefile | 9 +++++++++ make.bat | 24 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b8815e2..21f2b21 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ all: @echo " - tox" @echo " - d" @echo " - r" + @echo " - clean" @echo "" docs: @@ -60,4 +61,12 @@ else flake8 hy bin tests endif +clean: + @find . -name "*.pyc" -exec rm {} \; + @find -name __pycache__ -delete + @${RM} -r -f .tox + @${RM} -r -f dist + @${RM} -r -f *.egg-info + @${RM} -r -f docs/_build + .PHONY: docs diff --git a/make.bat b/make.bat index ccf6138..7046e96 100644 --- a/make.bat +++ b/make.bat @@ -20,8 +20,9 @@ if "%1" == "help" ( echo. - tox echo. - d echo. - r + echo. - clean echo. - goto end + goto :EOF ) if "%1" == "docs" ( @@ -109,8 +110,25 @@ if "%1" == "r" ( goto :EOF ) -if "%1" == full ( +if "%1" == "full" ( call :docs call :d call :tox -) \ No newline at end of file +goto :EOF +) + +if "%1" == "clean" ( +:clean + if EXIST hy\*.pyc cmd /C del /S /Q hy\*.pyc + if EXIST tests\*pyc cmd /C del /S /Q tests\*pyc + for /r %%R in (__pycache__) do if EXIST %%R (rmdir /S /Q %%R) + if EXIST .tox\NUL cmd /C rmdir /S /Q .tox + if EXIST dist\NUL cmd /C rmdir /S /Q dist + if EXIST hy.egg-info\NUL cmd /C rmdir /S /Q hy.egg-info + if EXIST docs\_build\NUL cmd /C rmdir /S /Q docs\_build + goto :EOF +) + +echo.Error: '%1' - unknown target +echo. +goto :help From 9a128edcb23294f6bb6044dab6431a0253ab9447 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Fri, 3 Jan 2014 17:17:51 +0100 Subject: [PATCH 104/202] Pretty print the AST in hy2py --- bin/hy2py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/hy2py b/bin/hy2py index 125fd26..9a1199e 100755 --- a/bin/hy2py +++ b/bin/hy2py @@ -6,7 +6,6 @@ from hy.importer import (import_file_to_ast, import_file_to_module, import astor.codegen import sys -import ast module_name = "" @@ -17,7 +16,7 @@ print("") _ast = import_file_to_ast(sys.argv[1], module_name) print("") print("") -print(ast.dump(_ast)) +print(astor.dump(_ast)) print("") print("") print(astor.codegen.to_source(_ast)) From 9e02eaca2663eb7c428fe55e73443985ec44a3b0 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 15:50:42 +0100 Subject: [PATCH 105/202] Whitespace fix --- docs/language/internals.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index b9513f6..f6d1c3c 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -251,7 +251,7 @@ so our re-written ``nif`` would look like: [(zero? ~g) ~zero-form] [(neg? ~g) ~neg-form])))) -Finally, though we can make a new macro that does all this for us. :ref:`defmacro/g!` +Finally, though we can make a new macro that does all this for us. :ref:`defmacro/g!` will take all symbols that begin with ``g!`` and automatically call ``gensym`` with the remainder of the symbol. So ``g!a`` would become ``(gensym "a")``. From 72a300c6e3980478fc45e3cce022d93f49cb063c Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 16:27:32 +0100 Subject: [PATCH 106/202] Add some documentation about models --- docs/language/internals.rst | 170 ++++++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 6 deletions(-) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index f6d1c3c..05bd4f1 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -5,12 +5,172 @@ Internal Hy Documentation .. note:: These bits are for folks who hack on Hy itself, mostly! +.. _models: Hy Models ========= -.. todo:: - Write this. +Introduction to Hy models +------------------------- + +Hy models are a very thin layer on top of regular Python objects, +representing Hy source code as data. Models only add source position +information, and a handful of methods to support clean manipulation of +Hy source code, for instance in macros. To achieve that goal, Hy models +are mixins of a base Python class and :ref:`HyObject`. + +.. _hyobject: + +HyObject +~~~~~~~~ + +``hy.models.HyObject`` is the base class of Hy models. It only +implements one method, ``replace``, which replaces the source position +of the current object with the one passed as argument. This allows us to +keep track of the original position of expressions that get modified by +macros, be that in the compiler or in pure hy macros. + +``HyObject`` is not intended to be used directly to instantiate Hy +models, but only as a mixin for other classes. + +Compound models +--------------- + +Parenthesized and bracketed lists are parsed as compound models by the +Hy parser. + +.. _hylist: + +HyList +~~~~~~ + +``hy.models.list.HyList`` is the base class of "iterable" Hy models. Its +basic use is to represent bracketed ``[]`` lists, which, when used as a +top-level expression, translate to Python list literals in the +compilation phase. + +Adding a HyList to another iterable object reuses the class of the +left-hand-side object, a useful behavior when you want to concatenate Hy +objects in a macro, for instance. + +.. _hyexpression: + +HyExpression +~~~~~~~~~~~~ + +``hy.models.expression.HyExpression`` inherits :ref:`HyList` for +parenthesized ``()`` expressions. The compilation result of those +expressions depends on the first element of the list: the compiler +dispatches expressions between compiler special-forms, user-defined +macros, and regular Python function calls. + +.. _hydict: + +HyDict +~~~~~~ + +``hy.models.dict.HyDict`` inherits :ref:`HyList` for curly-bracketed ``{}`` +expressions, which compile down to a Python dictionary literal. + +The decision of using a list instead of a dict as the base class for +``HyDict`` allows easier manipulation of dicts in macros, with the added +benefit of allowing compound expressions as dict keys (as, for instance, +the :ref:`HyExpression` Python class isn't hashable). + +Atomic models +------------- + +In the input stream, double-quoted strings, respecting the Python +notation for strings, are parsed as a single token, which is directly +parsed as a :ref:`HyString`. + +An ininterrupted string of characters, excluding spaces, brackets, +quotes, double-quotes and comments, is parsed as an identifier. + +Identifiers are resolved to atomic models during the parsing phase in +the following order: + + - :ref:`HyInteger ` + - :ref:`HyFloat ` + - :ref:`HyComplex ` (if the atom isn't a bare ``j``) + - :ref:`HyKeyword` (if the atom starts with ``:``) + - :ref:`HyLambdaListKeyword` (if the atom starts with ``&``) + - :ref:`HySymbol` + +.. _hystring: + +HyString +~~~~~~~~ + +``hy.models.string.HyString`` is the base class of string-equivalent Hy +models. It also represents double-quoted string literals, ``""``, which +compile down to unicode string literals in Python. ``HyStrings`` inherit +unicode objects in Python 2, and string objects in Python 3 (and are +therefore not encoding-dependent). + +``HyString`` based models are immutable. + +Hy literal strings can span multiple lines, and are considered by the +parser as a single unit, respecting the Python escapes for unicode +strings. + +.. _hy_numeric_models: + +Numeric models +~~~~~~~~~~~~~~ + +``hy.models.integer.HyInteger`` represents integer literals (using the +``long`` type on Python 2, and ``int`` on Python 3). + +``hy.models.float.HyFloat`` represents floating-point literals. + +``hy.models.complex.HyComplex`` represents complex literals. + +Numeric models are parsed using the corresponding Python routine, and +valid numeric python literals will be turned into their Hy counterpart. + +.. _hysymbol: + +HySymbol +~~~~~~~~ + +``hy.models.symbol.HySymbol`` is the model used to represent symbols +in the Hy language. It inherits :ref:`HyString`. + +``HySymbol`` objects are mangled in the parsing phase, to help Python +interoperability: + + - Symbols surrounded by asterisks (``*``) are turned into uppercase; + - Dashes (``-``) are turned into underscores (``_``); + - One trailing question mark (``?``) is turned into a leading ``is_``. + +Caveat: as the mangling is done during the parsing phase, it is possible +to programmatically generate HySymbols that can't be generated with Hy +source code. Such a mechanism is used by :ref:`gensym` to generate +"uninterned" symbols. + +.. _hykeyword: + +HyKeyword +~~~~~~~~~ + +``hy.models.keyword.HyKeyword`` represents keywords in Hy. Keywords are +symbols starting with a ``:``. The class inherits :ref:`HyString`. + +To distinguish :ref:`HyKeywords ` from :ref:`HySymbols +`, without the possibility of (involuntary) clashes, the +private-use unicode character ``"\uFDD0"`` is prepended to the keyword +literal before storage. + +.. _hylambdalistkeyword: + +HyLambdaListKeyword +~~~~~~~~~~~~~~~~~~~ + +``hy.models.lambdalist.HyLambdaListKeyword`` represents lambda-list +keywords, that is keywords used by the language definition inside +function signatures. Lambda-list keywords are symbols starting with a +``&``. The class inherits :ref:`HyString` Hy Internal Theory @@ -50,11 +210,9 @@ the tokens generated, and return the Hy models. You can think of the Hy models as the "AST" for Hy, it's what Macros operate on (directly), and it's what the compiler uses when it compiles Hy down. -Check the documentation for more information on the Hy models for more -information regarding the Hy models, and what they mean. - -.. TODO: Uh, we should, like, document models. +.. seealso:: + Section :ref:`models` for more information on Hy models and what they mean. .. _compiling: From 1d5847823bc2202f6750adc292a0bf10b2b87abd Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 16:28:03 +0100 Subject: [PATCH 107/202] more precise disclaimer --- docs/language/internals.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index 05bd4f1..33511b5 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -2,8 +2,8 @@ Internal Hy Documentation ========================= -.. note:: - These bits are for folks who hack on Hy itself, mostly! +.. note:: These bits are mostly useful for folks who hack on Hy itself, + but can also be used for those delving deeper in macro programming. .. _models: From a1895f635c6a17e0a4476af580338aee60fac519 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 2 Jan 2014 18:09:33 +0100 Subject: [PATCH 108/202] reword the four steps a bit --- docs/language/internals.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index 33511b5..a8de44e 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -181,13 +181,14 @@ Hy Internal Theory Overview -------- -The Hy internals work by acting as a front-end to Python bytecode, so that -Hy it's self compiles down to Python Bytecode, allowing an unmodified Python -runtime to run Hy. +The Hy internals work by acting as a front-end to Python bytecode, so +that Hy itself compiles down to Python Bytecode, allowing an unmodified +Python runtime to run Hy code, without even noticing it. -The way we do this is by translating Hy into Python AST, and building that AST -down into Python bytecode using standard internals, so that we don't have -to duplicate all the work of the Python internals for every single Python +The way we do this is by translating Hy into an internal Python AST +datastructure, and building that AST down into Python bytecode using +modules from the Python standard library, so that we don't have to +duplicate all the work of the Python internals for every single Python release. Hy works in four stages. The following sections will cover each step of Hy @@ -195,8 +196,8 @@ from source to runtime. .. _lexing: -Lexing / tokenizing -------------------- +Steps 1 and 2: Tokenizing and parsing +------------------------------------- The first stage of compiling hy is to lex the source into tokens that we can deal with. We use a project called rply, which is a really nice (and fast) @@ -216,8 +217,8 @@ on (directly), and it's what the compiler uses when it compiles Hy down. .. _compiling: -Compiling ---------- +Step 3: Hy compilation to Python AST +------------------------------------ This is where most of the magic in Hy happens. This is where we take Hy AST (the models), and compile them into Python AST. A couple of funky things happen @@ -329,8 +330,8 @@ into:: By forcing things into an ``ast.expr`` if we can, but the general idea holds. -Runtime -------- +Step 4: Python bytecode output and runtime +------------------------------------------ After we have a Python AST tree that's complete, we can try and compile it to Python bytecode by pushing it through ``eval``. From here on out, we're no From 2f7d40b409997c041d43914bfd01db1c59cd1983 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Fri, 3 Jan 2014 21:41:14 +0100 Subject: [PATCH 109/202] Factor the calling-module-name function --- hy/core/language.hy | 28 ++++++++++++++++------------ tests/native_tests/language.hy | 6 ++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 0b8b0d3..bf4fa54 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -119,6 +119,14 @@ (finally (.release _gensym_lock))) new_symbol)) +(defn calling-module-name [&optional [n 1]] + "Get the name of the module calling `n` levels up the stack from the + `calling-module-name` function call (by default, one level up)" + (import inspect) + + (setv f (get (.stack inspect) (+ n 1) 0)) + (get f.f_globals "__name__")) + (defn inc [n] "Increment n by 1" (_numeric-check n) @@ -153,20 +161,16 @@ (defn macroexpand [form] "Return the full macro expansion of form" - (import inspect) (import hy.macros) - (setv f (get (get (.stack inspect) 1) 0)) - (setv name (get f.f_globals "__name__")) + (setv name (calling-module-name)) (hy.macros.macroexpand form name)) (defn macroexpand-1 [form] "Return the single step macro expansion of form" - (import inspect) (import hy.macros) - (setv f (get (get (.stack inspect) 1) 0)) - (setv name (get f.f_globals "__name__")) + (setv name (calling-module-name)) (hy.macros.macroexpand-1 form name)) (defn neg? [n] @@ -272,9 +276,9 @@ (_numeric_check n) (= n 0)) -(def *exports* '[cycle dec distinct drop drop-while empty? even? filter - flatten float? gensym inc instance? integer integer? - iterable? iterate iterator? macroexpand macroexpand-1 - neg? nil? none? nth numeric? odd? pos? remove repeat - repeatedly second string string? take take-nth - take-while zero?]) +(def *exports* '[calling-module-name cycle dec distinct drop + drop-while empty? even? filter flatten float? gensym + inc instance? integer integer? iterable? iterate + iterator? macroexpand macroexpand-1 neg? nil? none? + nth numeric? odd? pos? remove repeat repeatedly second + string string? take take-nth take-while zero?]) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 952dc49..7153337 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -862,3 +862,9 @@ "Test macroexpand-1 on ->" (assert (= (macroexpand-1 '(-> (a b) (-> (c d) (e f)))) '(-> (a b) (c d) (e f))))) + + +(defn test-calling-module-name [] + "NATIVE: Test the calling-module-name function" + (assert (= (calling-module-name -1) "hy.core.language")) + (assert (= (calling-module-name 0) "tests.native_tests.language"))) From de31aea5d2d7ffd22ce82ffc609c8cbdccbd4d01 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 3 Jan 2014 20:02:36 -0500 Subject: [PATCH 110/202] Cleanup use of PY3* in the compiler. --- hy/_compat.py | 1 + hy/compiler.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/hy/_compat.py b/hy/_compat.py index e25f9cd..37bb023 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -40,6 +40,7 @@ import sys PY3 = sys.version_info[0] >= 3 PY33 = sys.version_info >= (3, 3) +PY34 = sys.version_info >= (3, 4) if PY3: str_type = str diff --git a/hy/compiler.py b/hy/compiler.py index dbb3ee5..d788c8a 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -39,7 +39,7 @@ from hy.errors import HyCompileError, HyTypeError import hy.macros from hy.macros import require, macroexpand -from hy._compat import str_type, long_type +from hy._compat import str_type, long_type, PY33, PY3, PY34 import hy.importer import traceback @@ -77,7 +77,7 @@ _compile_table = {} def ast_str(foobar): - if sys.version_info[0] >= 3: + if PY3: return str(foobar) try: @@ -754,7 +754,7 @@ class HyASTCompiler(object): ret = handler_results - if sys.version_info >= (3, 3): + if PY33: # Python 3.3 features a merge of TryExcept+TryFinally into Try. return ret + ast.Try( lineno=expr.start_line, @@ -831,7 +831,7 @@ class HyASTCompiler(object): exceptions, "Exception storage target name must be a symbol.") - if sys.version_info[0] >= 3: + if PY3: # Python3 features a change where the Exception handler # moved the name from a Name() to a pure Python String type. # @@ -1199,7 +1199,7 @@ class HyASTCompiler(object): optional_vars=thing, body=body.stmts) - if sys.version_info >= (3, 3): + if PY33: the_with.items = [ast.withitem(context_expr=ctx.force_expr, optional_vars=thing)] @@ -1699,7 +1699,7 @@ class HyASTCompiler(object): arglist = expression.pop(0) ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist) - if sys.version_info >= (3, 4): + if PY34: # Python 3.4+ requres that args are an ast.arg object, rather # than an ast.Name or bare string. args = [ast.arg(arg=ast_str(x), From d3fa1fd1a8cc526fd06a7920833630ebf2a243b4 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sat, 4 Jan 2014 01:47:49 +0100 Subject: [PATCH 111/202] Add disassemble function to hy.core.language This function dumps the AST or the python code generated by evaluating a quoted expression to stdout. Fixes #58 --- hy/core/language.hy | 24 +++++++++++++++++++++++- tests/native_tests/language.hy | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index bf4fa54..8fcb615 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -45,6 +45,19 @@ (_numeric-check n) (- n 1)) +(defn disassemble [tree &optional [codegen false]] + "Dump the python AST for a given Hy tree to standard output + If the second argument is true, generate python code instead." + (import astor) + (import hy.compiler) + + (fake-source-positions tree) + (setv compiled (hy.compiler.hy_compile tree (calling-module-name))) + (print ((if codegen + astor.codegen.to_source + astor.dump) + compiled))) + (defn distinct [coll] "Return a generator from the original collection with duplicates removed" @@ -81,6 +94,15 @@ (_numeric-check n) (= (% n 2) 0)) +(defn fake-source-positions [tree] + "Fake the source positions for a given tree" + (if (and (iterable? tree) (not (string? tree))) + (for* [subtree tree] + (fake-source-positions subtree))) + (for* [attr '[start-line end-line start-column end-column]] + (if (not (hasattr tree attr)) + (setattr tree attr 1)))) + (defn filter [pred coll] "Return all elements from `coll` that pass `pred`" (let [[citer (iter coll)]] @@ -276,7 +298,7 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name cycle dec distinct drop +(def *exports* '[calling-module-name cycle dec distinct disassemble drop drop-while empty? even? filter flatten float? gensym inc instance? integer integer? iterable? iterate iterator? macroexpand macroexpand-1 neg? nil? none? diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 7153337..b188811 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -868,3 +868,23 @@ "NATIVE: Test the calling-module-name function" (assert (= (calling-module-name -1) "hy.core.language")) (assert (= (calling-module-name 0) "tests.native_tests.language"))) + + +(defn test-disassemble [] + "NATIVE: Test the disassemble function" + (import sys) + (if-python2 + (import [io [BytesIO :as StringIO]]) + (import [io [StringIO]])) + (setv prev-stdout sys.stdout) + (setv sys.stdout (StringIO)) + (disassemble '(do (leaky) (leaky) (macros))) + (setv stdout (.getvalue sys.stdout)) + (setv sys.stdout prev-stdout) + (assert (in "leaky" stdout)) + (assert (in "macros" stdout)) + (setv sys.stdout (StringIO)) + (disassemble '(do (leaky) (leaky) (macros)) true) + (setv stdout (.getvalue sys.stdout)) + (setv sys.stdout prev-stdout) + (assert (= stdout "leaky()\nleaky()\nmacros()\n"))) From 1bdf0d04c33c426221edac69cca17be544fe8ec3 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Sun, 5 Jan 2014 19:12:19 +0530 Subject: [PATCH 112/202] Added documentation for new core function disassemble Signed-off-by: Vasudev Kamath --- docs/language/core.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/language/core.rst b/docs/language/core.rst index c9b67f2..c11672f 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -29,6 +29,27 @@ Raises ``TypeError`` if ``(not (numeric? x))``. 11.3 +.. _disassemble-fn: + +disassemble +----------- + +Usage: ``(disassemble tree &optional [codegen false])`` + +Dump the Python AST for given Hy tree to standard output. If codegen +is true function prints Python code instead. + +.. code-block:: clojure + + => (disassemble '(print "Hello World!")) + Module( + body=[ + Expr(value=Call(func=Name(id='print'), args=[Str(s='Hello World!')], keywords=[], starargs=None, kwargs=None))]) + + => (disassemble '(print "Hello World!")) + print('Hello World!') + + .. _emtpy?-fn: empty? From 5b3f6879c60f2aa0380a6485aa58a0d22381f1cc Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Sun, 5 Jan 2014 19:55:41 +0530 Subject: [PATCH 113/202] Added .. versionadded: 0.9.13 and quoted the arguments. Also added missing true for python code generation example. Signed-off-by: Vasudev Kamath --- docs/language/core.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index c11672f..9585053 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -34,10 +34,12 @@ Raises ``TypeError`` if ``(not (numeric? x))``. disassemble ----------- +.. versionadded:: 0.9.13 + Usage: ``(disassemble tree &optional [codegen false])`` -Dump the Python AST for given Hy tree to standard output. If codegen -is true function prints Python code instead. +Dump the Python AST for given Hy ``tree`` to standard output. If *codegen* +is ``true`` function prints Python code instead. .. code-block:: clojure @@ -46,7 +48,7 @@ is true function prints Python code instead. body=[ Expr(value=Call(func=Name(id='print'), args=[Str(s='Hello World!')], keywords=[], starargs=None, kwargs=None))]) - => (disassemble '(print "Hello World!")) + => (disassemble '(print "Hello World!") true) print('Hello World!') From d9d113670ee012f35140d34e2a159d2709c21798 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 2 Jan 2014 12:49:40 +0200 Subject: [PATCH 114/202] Add versionadded directives for macroexpand and macroexpand-1. --- docs/language/core.rst | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index 9585053..b2e4e3e 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -221,7 +221,7 @@ iterator? Usage: ``(iterator? x)`` -Return True if x is an iterator. Iterators are objects that return +Return True if x is an iterator. Iterators are objects that return themselves as an iterator when ``(iter x)`` is called. Contrast with :ref:`iterable?-fn`. @@ -243,11 +243,13 @@ Contrast with :ref:`iterable?-fn`. => (iterator? (iter {:a 1 :b 2 :c 3})) True -.. _neg?-fn: +.. _macroexpand-fn: macroexpand ----------- +.. versionadded:: 0.9.13 + Usage: ``(macroexpand form)`` Returns the full macro expansion of form. @@ -260,9 +262,13 @@ Returns the full macro expansion of form. => (macroexpand '(-> (a b) (-> (c d) (e f)))) (u'e' (u'c' (u'a' u'b') u'd') u'f') +.. _macroexpand-1-fn: + macroexpand-1 ------------- +.. versionadded:: 0.9.13 + Usage: ``(macroexpand-1 form)`` Returns the single step macro expansion of form. @@ -272,6 +278,8 @@ Returns the single step macro expansion of form. => (macroexpand-1 '(-> (a b) (-> (c d) (e f)))) (u'_>' (u'a' u'b') (u'c' u'd') (u'e' u'f')) +.. _neg?-fn: + neg? ---- @@ -372,7 +380,7 @@ if the `n` is outside the range of `coll`. => (nth (take 3 (drop 2 [1 2 3 4 5 6])) 2)) 5 - + .. _numeric?-fn: numeric? @@ -549,7 +557,7 @@ To get the Fibonacci number at index 9, (starting from 0): .. code-block:: clojure => (nth (fib) 9) - 34 + 34 .. _cycle-fn: @@ -567,7 +575,7 @@ Return an infinite iterator of the members of coll. [1, 2, 3, 1, 2, 3, 1] => (list (take 2 (cycle [1 2 3]))) - [1, 2] + [1, 2] .. _distinct-fn: @@ -767,7 +775,7 @@ Return an iterator containing the first ``n`` members of ``coll``. => (list (take 4 (repeat "s"))) [u's', u's', u's', u's'] - + => (list (take 0 (repeat "s"))) [] @@ -793,7 +801,7 @@ Return an iterator containing every ``nth`` member of ``coll``. => (list (take-nth 10 [1 2 3 4 5 6 7])) [1] - + .. _take-while-fn: From 0999886d35a862215fe8b895b56de47f02fa0c88 Mon Sep 17 00:00:00 2001 From: Will Kahn-Greene Date: Mon, 6 Jan 2014 17:13:04 -0500 Subject: [PATCH 115/202] Update quickstart.rst Nixing the float: left inducing thing so that it looks better on RTD. --- docs/quickstart.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 18c554c..5bdb130 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -4,7 +4,6 @@ Quickstart .. image:: _static/cuddles.png :alt: Karen Rustard's Cuddles - :align: left (thanks to Karen Rustad for Cuddles!) From 3279cd9719a0549cf7d48e1347529273a3cf82cb Mon Sep 17 00:00:00 2001 From: Foxboron Date: Wed, 8 Jan 2014 03:53:02 +0100 Subject: [PATCH 116/202] fixed readthedocs links and updated the quickstart URL --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5475a34..81d4f14 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Project ------- * Code: https://github.com/hylang/hy -* Docs: http://hy.rtfd.org/ -* Quickstart: http://hy.rtfd.org/quickstart +* Docs: http://hylang.org/ +* Quickstart: http://hylang.org/en/latest/quickstart.html * Bug reports: We have no bugs! Your bugs are your own! (https://github.com/hylang/hy/issues) * License: MIT (Expat) From d4357fe35c2540fc5cb03796085b26f5fcbe7e91 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 8 Jan 2014 20:51:03 -0500 Subject: [PATCH 117/202] Add @kenanb's logo to the docs. It's so amazing. Thanks so much! Read more at: http://www.kenanb.com/posts/Hy-Programming-Language-Logo.html --- docs/_static/hy-logo-full.png | Bin 0 -> 46322 bytes docs/_static/hy-logo.svg | 127 +++++++++++++++++++++++++++++++++ docs/_static/hy_logo-about.txt | 3 + docs/index.rst | 2 +- 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 docs/_static/hy-logo-full.png create mode 100644 docs/_static/hy-logo.svg diff --git a/docs/_static/hy-logo-full.png b/docs/_static/hy-logo-full.png new file mode 100644 index 0000000000000000000000000000000000000000..5f7d92fc4ef4eee7c37c3377230ca17d2add90e8 GIT binary patch literal 46322 zcmYg&1ymHy7w+!TEiJ8pBGN4^C4z*60us{QAt1HHg3{6|4I(81(p?KEA*Is2h|;li z%bWH8e($_J$0O?O#GM=8{q7y39&0L-5i=4)AP_QD6-8|b1m_0!7a>0Qn;Ca}d+-~s zr-G_3A@~FkT1SFE6S=DxdV=v2*k4eEI~l&ou1=m- z7Vb9uZXR|S+tQ2>$PI|9;(cA;%*{EUOxnZ4tKEq?g>vnDLKyppkA!*bM-O=!G}L*2 zq)rM8bzcwWW~9~-lM#v%3ZzPQ6J<2AWH8!{v}B^Jyzw*txT+>BY;J)qr?sKkXM3AZ z#K+Z7jxGM&d}lUIgn;fHV!J3)QAdZG{og0-F|7~m-%r|-y2wcEN2TK1ijfc3z7AXb83 zmCSA=+MTeV0@Q_s34B>n*Ad%4rI(^3Bj50Wb+z?)XV&o5yJ?tuV9MqXg^3+x1#3@t zGj|&INdkhw2|g5njjN^Gz~@4!PuyR1!5pnL?v7=kwoqAZEBsM#1~BGcF@!QP$$Dz2 z14WP2!fCbCnd7|2ph``~w>z=aUcz8wFdBSbrCP+`Au%6$kBYhL%>Nh=Nb{gHf zvt9$C`dY}2DjBizLS1oP$3}T6DY@yv4RU(F+uDR)5S~aU*ce`O zwr5L~hTV=u{r#1PkWB z=bd4QG&Bzqvi;63&~@JVv`0x|@GjdzY=<)wLT13!mq zLAv&0pPJz1FjK6k_XY|1-Q03~MPS=uBEmkSwBe0*YFqgn&DjL*wHGL}%6)@65vVWo z^#r%lCzk^sdm?4_hD#pnjdrjVr5)a+G@vjhXlPqmj{skw5d-&3CE4#ChcEr!NA|hn zCv;1cZKB@`%6R{pm9*o<@)3)TIF(K<0{&weA3e?=N{WgK?}2u)+e{4^raCCh?L~=v zk~0p>G~9o(PWSocj-2CJw4yl|#tGfrw(`)E0Nk@QR#Q|A>P83}7)HjV1$tt`dE!H+ z*Dv<((($aL5%KAT;go4O#+!)kgMYV^goE9cRKK?-!Cx5ZAeZJ-m@J}U%Cw%@WfFhE z?>e1Z`Nn`mh(-mjO&96q+f!U?Z=t~vEbf9Ber9s@*e$pc$~7>(OFA1i+2HS=s2Ej3Y~kZsCf4sIIcqq@GM9-95XzrFqW$% zRd?Go*XoRt+vsDR#bQak)-7UmN!!Z2;rHg~NR_WxZ|WfCBXmV3D7`sJgx*)~C_ay1 zX_OWfPg7bG;Nv|h%Yl79kU4G*T`&YIgn|`REvAODcaH$skOL09IJ6o`ZL$==V|;QPS`Ch6L+NXN|FEyXouu^FuG=4&6gM|e z$-BcTd89!{Hw#xHXf!=Cmaf;Yuz)D@adMlVo&A;)IMW})YfsUa1RZ|UsbX|X_#Iy( zdeTGN_SxB!D1mvsUGX>fo4k(z*30ZxXs#-~iL=<7SUJ~*aP|Q^O!@#$+If;~L}1L~ zJsZQ-fn>E%e(P-F$4k1+(qDEsM-NVc-K9LxV;pHly_QNu`m!%oyKAlcmg@K7LUjFj z9nODifuN3PyZ?#G{q#wpKp0H~B?7e)&wJvsLVpnlv@;24r|z8vPkM;Xeyry-beXK2 zCMCH$RT(;XcXbZP!x}3u=lDOK&vQPzcjQ_Wuv?s8B;xUq*T1I(aHB7Q_*&w(cluC| zKF;c8`DmRY#%k7lr-OyOwYC*;r677BGZL)KQg<46ATgeeN2m_>&D8|zr0-*HL} zx}10E*;oy8-YEwTo6xITgOwg(9Y#qGP84n?LPfV&MYn0R^KH*7R&Op$+4nqP@P<0T z;0D71eg+tkKOSyPsoapmCsW*=&&ayKO&u#**3vSp$@iy13@2kp< z72zGbr!Kh{?CwVQrazex;zY_&p+`bU0b{LCKc~uSnxYAj=4@CMF zD^hRELaywxtnzPa@k8!@+pWe@5vc%U>0+_nTfRJ)b$f_lmDy;qN-m2KEROZ z1T}D)oLFhUv4LkHM}#FMI{BxmL{?O)1!W%DDJEbiAjD4455@QK(vBaJ3|8a6^1l85-GLAb@>BzOSVXs3O? z@$v@+GT#2|>C}cU&We$tnRluU+${bE3%CtW>`p*zmqBg_OCG=P5UlY5vpl)?d?B;W z@Nb$8{22vs0)|+T__^ZaTYG$<%h8Wh1Yyv3Jmo3W$Dq#h@d-mzq)MYU7;>L?rV+P6 zG}#mJLTIK&?GF%b!|U72wLfuud#kcS7VW^@{87O=lnUn+bM$dqcJRsk~r$DB4&kQ_qKJ2M5JeGOD{ z7APFI=b+K{Ji+U}gY?g}?wEvCkECDTix!gcK`gi}AddZZ7onm|^uXHeYs0z`7GK=d zq`?=|5yi*Kx0q&0WQE07`Uzme26g*u7~%g$s)LavCv5HkbrY<)s-m0DFTDoQ-vt!a^j};Yh zv3KFkJ0nT#%W{u5WrKp1c+RnFP*rWDc<~@ak|5P_O7n>60;~U6N!|wWrP6uj902M_ z<0bpEx11W+@e$$GmD{rU4|$P~mJxgOSFt+$jIEv=kAZl=LP9H#asE zvWX|{HZ|1qAm$DjsR~9CRXZ0=S7nt$L_#?BVkgSsq`Ewk4vYDh3UicYaQ7S=IDG`& z6A&Kaz?rB3tja|>0cgW&@rAkB?)sDb%!5r*<#=K-Yg+db8@T=}Mi9=`!m(pP62OIY zK;5+Ho-@OX#3g*rsHY7(d9c5l^?!}wR-xb7HK(A9|(yw8i95$ceW--YlZPU-WllMsSp7qD6wz~=o zvEnUYW-KBs;HU~NH*wOSR!BTLSR>{*SN4yo1Q00hfE=JR_m7RLf$^X9fx`K>As+4mKObKB&x&+@Vq5RvGNs9vYSa7B%&vu;jmAo zUfU8aZA^R!BsK{G12^9lDcqbrm)jsIE0QFEi#--Zz)IjjwG{$}1Mz(C45T8wUQVwI za;P;jednlhK*R(NXHoyV7e?&83`>M`SA-sJGV5^`a}topac(ZKN8JSp6E=!Q?aCax zC}c*DRX;LF%ugaE51yX)M@!@?)BfN45)CAnWPNxe7OBFIe@H6xyF36ShyJ?2^+;hS zPuOXU30VlEe8h1hDMqG?Prfks2L)wfbS_ppZ(zvL+J5 z#8IrgKunx~%Joac0g=(0IKJ1K00sv$Mk8GBj1YlfB#OOB7YUsHeWLG=@FIWT8Fnzx z!ZF*G(a#!I0&WkhyUU1eJ#=A!r9JVx-*@934EjT~ut2QE8EAqo_WOascplku3yrtG zsONw9T}eHz0g@bYAzE;Po7fW=?~fPMFZ%~3-$tp#d1QvxGU$lL01tS?p!on88g{dLLLwl8r z|KCi#4n{hB3Ms}edLUx5OJaQxJ~o%0P3y14uJEa3c&&;OPzT|eSaRSR*x8>Byttu1 zoqTYGA;r57<8&%Mh3Iq+0*XQ{3M(%Fq9j6wm;EohU2fjypuN=t!$t4kEc|cpz{#vo zqvH3Xzy*5LO4f55K#Gj&L`1*n0(ri|%JT^@6sRi>)#;ZhkiU7n=^<1Rb9eAf183pJ zo~7wCwCN8M$T`7vfVn+EY8>(Azs=pkh7nYl#TOvm#_2cbG`z?~l;5H?7N~-+s5mrH ztx`R_L3<;qL|R!M^10fXtDO54&=-HKzFfyfOEXd)Q>?hYeB=2*ve=wQ;{N5(V}(GTH^7T(3;!EH-AUeH{a})^U^cKqw&rm% z-;AVijXn71gMa@lWI;f>JD9H>e#Y8fnfWuL zNp}PBkPhTl(f{22FT^&v`BR$8g=gd%&!YGl4E~2QUt+&T-6vW)IAbwCjopi3Baygk z3~#`2FFUY^A6V`4&yK!}$#|3(&HLw9>)XWeV2PirKy)>*1n7LYGLjgxEGhF+KSbWEbWj%#}2hxk{Xn5->cbM+o4a{J*Q`i+)emjGnVE znp7d0X``llSP!|BNL2dosG@UFMPWT5oeg=;*hVLFeJjpfdg!qd74}^JLf&u0HZgkp zj-}JhrhMu}*nt3W1R_<~?df8-_qiO()I~1kMpin{?5)d9u%lG{cZqz#lP#LF3dk8t zRNeY^Jo^WAXwl;W%g&+44x;3hHzPA<`RDK)v9~$H7NQSkd5RZVQ|UqNOxO-xOsUy_ z3-S@H@^=OdyWqCT_mcdSGq7W2SEW>MZvtGH%D52FqUTsG`URXe%Zc~ob1`CezOWWZ zpp3iHM@r!Atg*(0)3)*#w}E>g)y-al*Wu=G%7k+9o!Z$3;hiq>Ck;D|SA=}&zzt-kH&9;VcpQjRIzx=`>X$3l zn6cRRzCO!y1!76sBa1Ty-$R`JOWf6QqRr>-+qfTx&{iL>FF0~fc8hN*o=J$!Hjot#g*mYaj)ZVB?v#a=Ur02Z*gMQKo?MQS{tByx1af1NAG3pv zs$w@94mP?cbG(9jg1RtXverS0Zibr4;Yj!{$GRW@XXV2tHlfmB%NMaBV^GUoy}w}K z_^@P}`X^zp1PEzVYMdY-IDi}e2C}Gf7&Q{^(b)E9RSB`qgzoOCo@w&gqVwHrHLgnX z0+S8r3%K&mr>a;s_;Pi9FLcaz9tsD5XY zGZQExMojgtj104MmA0c?x4v#+o4&JT~N66Ha zeRRC>LjLDDnA`H>29-~8?U0y#1O5H=59pg{Lk%Zc|uK7=)(TWM)qcq|&NjUy4K zETzY*y`ioz9Xi`*USWY}cC_niFQfr-@&XpGdu_o}ABW+f z?eAlgjIdQey_`dx@b)Mba-yPZ{5_Xjp9xpm5IE0MUQBm95GMC5+G(thc!Rxyo7g3} zO8ib;l;t~_FIBXmNf}-`g?m2=ZkPAdL-k)JM}bj1Sow7y8&mk+9oe7x3?)Jw;0(~P z_04}cTL#}nEP7O{d=?;_Y zfZC5TorYb1FU_lG_<4MC`!W(GYk|ObrD0EUW5II)@>d|)Q{UO~GT?m3-IHOGx@k0= zsapHMxyZa%sp$o)=Y-d!&7}jndC?XS?|;;Qtp7AYSKY6QkM(PV+W3GoAwq;Pl_wpn zQ@OT0h$vun>J*i7?ioQbw5^0+NC1@b4VxAd|3PdEqv=_)>K)*v_CNNm;$0X2B&eX$ z2*T*gf0C}#nwGBLO}+jYDJ@E3YL-VYbPV zAYB@UA8~TK>F4W79C8Lm%~GB2!q3pOW_(wK*w(H1++Sc>4*(?cLxH!(yGLd)G4IFc z(go}1<0=Xd8$_5KPq`GjiKMpc(VgAPSE)#pB$!!b|MUbJk0hath< z#nADr50pt|Cf3S3h9$w@8=uGglV)9;`y20e;;z(?(_p!w`ey?63uA;5awPp5+L~EJ z%%HMT9ym<4ng&qyPa!Z%=uYEE$Uf5Ke)+Ho&Q9GO1zDT>E7FYP8Vn>{n;GYaKc{V! zf6qVqQ-g!fTapc!pTuqVBR6%G5psiLWYF&5qP~e~0h2^3>Lkm4p`rw3iGE}|eye!U zoq(MhiX5#hg7%=>=ZF9=W@F(#UjKI(-Z{vEAMUt~5VzwugN9%BBQbdIiyKc;rRv8V z;K*taFgRF{-Vfic0 zlASU0*DtWSwcV$^jyisAN$WFafE%e^Z@hky{ssSKc0r>bwwAKshxIT;3sbRr` z8dT72V=R`n?Fmy+V`bEtqTR0pHm5|Y-V}sQ@hl$R!W?;|R)6E=+?gQ@KL~wr6e>cl zo~3Xc;eh$FsTbi5TRF`>&*`)QvV03%QY0?m9XUWn=`UY=uqQEdo6q{`A=$FV&gR>~ zYQK0*@RLQ?*qXE^+&ldR%T^%8HfAxKp^SVB#t$OhwtY1AhUl~Ion-#rx4`! zEA=G1Kzmi6C%tJ?UI=M#nOuKN?Vbn>_iJ(@FiAKc4sBvhz_1A^Gsy+NpYyrq$>I`b zeD7p0$-SikGP}kd(O!U6xexh(S?i0mf#<-C-xJ8QL+w~d=?$iTe4^odMq$=2XEQgH z6ENJ$Uib#!UWHYcmyy7uD0wF1g8P?B3q)D%C`Tz-V3;7H!I6j%1m6q;t}$T% z`6LCp!%)zo{5tg;d9gA%|4%S-yu$7zP|wq4js;Bm9U#C(#I`_4e^QTaxp3Z1ZR7Am z4ao!gesBCQ{|GLC(AlZ#CXg)VO?l;0%%0*&ovbMy_Ht;4es66vp#4N?;6Ow(rt&U( zs8!jUMoAg<`foC2Vui{5{iBvFr}u94snv<{X7C?0 z>@HsTLFCd-Q$rP@T!7%Gy$_V>Rd-cZwb12#$UW&p9dB-`+A;SzntLAB=>8sv2;a=9 zqD3{}e$?K}f~^sQLKLXCH6a;br>8yUHSfgfTUVAl%}igF!C_{9Mcm&4=n*!k_CJ^R zuYQvp5`hfrv!XX}A9{7-k+fMl1#1tav*n`*ICVt_EO;`Jq`v_gc1xp!DzODnO%7;= zXEba*v3k2t9M60rc5=vbWSBqcT-n{D7oh_AOp*jvl$yLVst`GdtG!>qW;W*K$RHyQ zx5oYiN+2iv&kJsW%b&-yPL?XFU>}iK4ZxOckykiFu6_Ym_c4Rq->W^P{ZxI&T|?ab zDjapbJt{iSKDQ_32B9Pkz;SlNN@qI(G}PR(e~BwDgeOjKA>O0v19{u&HRs9kbqu>X zs0lj?af8&cI0_C0w8*Ol2r8fkIHh>Yz(wC|BUMsVOL_Res!;&GyU^e|Wc3+|nIx2) zi4{!N3J+{RJS=$-i-NYTw9#ulTm-z8gl*-e5I|-h$@v{%w$Nc5osbBCy2k&4%Z1&2 zwd+9mUoQZd7w6qp0J%=UrucgT<15NZM0{#eQmDfup4p`UufwI^)lT5>rM5bE7|@46 z!92+n^M{fiwag8@$Fp1ts_K9v#QI5Z!#dB~7&i0cHa)9j(U?KRizopBZr)&_3(T%w zggDNFz+sI!o)TO@fJ$SokRpU`kIrtRi%>pbTMXLjl!;Z?;y-XaD|gUD@16E0iOxIe z<9}H0tBs7}>d%t#80|#AsNHF7kL51N6r~?sz?t6C9Vx~KZt-TTg+1GOrgA!6^nb+P9sHl zVmshn0mNYE2K*{cD$(X!X>mVJW6uv$hiZ3qOk|9`5G4DaIv&ll#+}B6UcRbG6~CXH#FRuH9DLF7FC$R;ar(PY z*8E=x8SU47C%8_nC7uSDPD`}dGz}X8SvZqsh|y4pPWQ5D0P^&BcS8$xk{cpm25gza zGoGJaJ4h79&Rzz@E{KsDj2Sv!qma9}<5e6hA;3*8A%iorTq|{8xZvp)!<|(Fs1EWA zqK1k;yZp6<7R#0AuY!jQZ#TFVPSrIC9k!4615J2z;zLWxErWHsb*}l1Cm%lx2g>RO zsQhfZ)FIc1z8MQNVT8dqLHuh^ZxE&=URfsq$#7Hp{940q-gmykg#ik_nKefDG;F1k z%ZqYquqvw!<v&Y@A+)zNV^daas6=R>A0G^qP2P890KA6B#wp-+b&{~=N_ zpRu~_&Wjg%PGs-4T5wvR1NK1!2QofZUrwWN0UO1x`Z8oL`7{1eq!2Z?bWClU*Dc1#WHI-pNA^z)W$fc~JniA~#*u{zU-g_Ik8|)Z| z0<@v{t2e5qf&79 zlP^mYUv@%s_s1x1d<%|G3%(K4p4!v=RF>I`lQ^2$Lhi#?>@fNL zVBY4G!OxH8I+Sq<1BvNS)$*nzvW_K-{XJ=Xp>U65QiE~ZWd!h1Q`Y;5?g^((=xG9U z`N-1%6(t68V?1fRQs76$`@LlyrZf%U6RKEpm7S08QBbAsBkEhRNEl~1B}mqoLYnuZ zp10Z55p4HFco#5)HJ{}OM2^6>0%i-WZ zI46HbiYFp<8~BlAv(SNh=M|@STL;mST>og{{DSjjvz9hO?{$q7+>aWUEh0O7Bmjvs z7$m&lAH)2y63OtdeL>K2%{^l2A%Vo4an=2=5i0oM&~{upqq_Y^{odhUvl3|J08aY( zQ#5@d#MuRD*e`XjxpytrO7xS?!e&ug%bVI@J46QqLFac;{rA!m^xn9J zk0}Zlr_XO4aAU4oNqx>9FIZ-bl40jhdy;qq;T2(&Ey8g-qmKUeu(tl&Gr6AZo8iOu zjC$-)wqf^0$yC=rE-!X)^-#`-43ErX>vHLcMxdyd@3v+^rEDH7vx_x^GEhvR^<_T| zn7+84+}8}ge+alM_H(`G!bhMNt-+Ug($k@Q2f zR29*cz$<0jbweRaV7#oGKAkf$wjt1?{#^+1}Nu z%wQ92Ei7nsLT58{l*h6{xLyl2bIu*zHGVG9QYm`D+^>YnIKp*zyKs_3mgv8D@Gejpt(@$0J_FY(nn!40wNE3K??J8&4zZ|svwW|ROHt`Qe&3Hj4zL*a-LNV<)#@B!GO|#%J%=RDr?Hw&evTkHKns=HO_e?m4 z45mkOX%n8~42$~cQ+P5Ky@7zsP6EtN7Aihzm<_>o$rx=Z*(%x^CDO3Zy@UrV%954bg%XerN%vO8+NzltS&gVK65rKR z%r_^;F|Bo8dDTFjXphB}M;Fuv5L+6~x!(Jg_q=>Db)Q^ZoRB^9WIn<1#?6B`VoC5OHhBL& z^h4n{XX)?baaow5XkKBO#5)HBe?hV4Dd^()frWGqwJdmkCtJVVrhTR;m&t6ETk3zh z)g#udMRGnCgqyfONdfi5sl-rjd^3$>RaxkGcz!YpzH9?=2Eplcp5iRmXPJfu-rt`K zcflN-ca)QDP`3=3Z##<6mL)vggc49V1eUfzUH!XTEaD!egJMxml{1%bPU&WWdY6>F z%6Eq~yccp>5|?U50x>jMkC1kJGkuo73f{o5-i9A|d}M#}@PYxc&PsRcWBj2s$YqmH zz&wtdf=)VRtj;loEbHhKxZ{1ir#jTL7o}?^R1u;&6(}#I9IBr#+XUX_{s6&TR!s)g zR}#q*ri#~_rIX1m5ZdeAVXuV4d;yeG1>#%8ijM_w!wl&0-}~*YxEax2F$RKg(ZJ=3 zxM3qxf)RTBcJE2|>)$yJPdZ;zG@zUWZzAhD9=1hS81Mo9==IRv;6L;C(a;AHOZ%P(O(b#2zS>b zte3=@GNHb;@y@pn-5hI8`fC}EqLz0SXE`OmTJ)4{9i49&Ueg| z)xG`Pqg>AK{`=$TQolsHOB6}*BR+NY5u{tUr9o3=#i>`BSj^!JWw&Ul0B8H^giI!7 z!h61V8Ig80s)`;*4?zFG_gx2VZ0%g7?lVLjr}X2?X4@}7RI-w7OP;p8L_yWW?$6R4 z40tv?hPm~VmGRl!I-L`Z?OA|&ffhH>B{G(3Z2v^!?|ts`wA-(y46c123s8`!lnB>J0o!MiSPE=`u;J$EMTnuTg?FWt^Sy)Z>0Q6Ic~txy^~0k_GOWsS zZ@R*Z%wAp28c&WHOa^q~#F2G%+bUNN4?aFAOmJ{eQfFCwIiAni+ScIi z2O-^HYLRg#q2`-W6^SY{X@_{bg=T){dV~ml_%SrP{tQ30RrvgbU0bvXN?}x&e4peQ zHy!pM022j}oEY|7UF~U@MO%v2Xg$ms-&aVVEAxM$8o1Bc11h*(5d=E}cwtwZa?r^o zDQUivC)coj!2Bw*Y;vR0RbzGXo5nZpQBV(&mdP$rtsUhupFWF07$I* zy^YbzqQk>lpWKk`E!VY7(|WB53iRm;7RELv1E0yoTM^UA*K4jVBnTLgY}C_<=PVtE zZD&o({26(-WnB}{(zw&Z*S7)^j;Y_Xdya5Cr&%=mP$Z01j4H906qa4#Ty*)|kee8L zB+%@7fXfP#P1(+zpzA1!Ax_Z|KU!PZl{_TFJWH3zkJxdMRnmG^%iZ=uV9mP==G%(O zyJ6yp9ysEX-6UhqvS={FiQvr1^p=k>x-0~zQ?K_f{2pw|PF4J*=H%Lg=oApS*BnJMY^R#wyPTikJ42XaEJN8hTs z03oRl)xVWGh z+HBzBwg)(Z&jCN4=K3zC`?kT@hl=YWg1(h4UD&M3&6 zv9TS9GwvL9yC}d~n7GQy?##iOI--`}^4GIS^0f?z>lD!#=;qYc3+Tz}y%XQ;42Qalwu5z;G9HbHkvFQDXRkRvlBTwrcc!nHK2cE% zsA0bSU){5v8egl_GWz8%qy7hvxM|YpZiu`~ZM$***>*VX3ZV*OhPcQ`g{>CXeXLUO zX2asbHS~RRCJbv~{6Gkw7D&d8y%qoZ)y#$-|Go_MY4?8~V%WpMZnFjYnmi_ZjXq%e zvb_NtHeQt#Y%cqDXr|qighS0&m`@($X8mlq?c%P2q?-e<)^$ktn z;;slM2?}Ly*%Yg|hy^SQo}5J`^xAjLAKj`)YjC39RCcULOGsRugChL~&IR8#C+ELH z&QIyXKP2zAEDXr58_a zb=>9+9LKA?$WOTaI@>UAY~rpj&$0^XA2Ndmvo(@XGsJUfW>c`mzQe}HC$`VV_V)*C z>W=l(nCwgAL_Y{P>v zfKjKYosc%Dzz#(Vsk7?7|9qiz3K52yqH zH_UAXGAiO(&GWBHMdYT#kF2PD-IQr%5L4>=ezyjtt|6&o%o)dJFGsF|(vdxO^rBfG zDqchwc6pkn-wr1k@BDBWHmIr?-MNyUv93mmZMi4}JRQ+R4Bgcb!2uGqDr*fPowV*6 z^J@)-yEvqu(>{Aaw;yO~M5^Jiyspj)or8ZhLXPTn^#-k(1^a70_b!P48LeTa$Pux@dqrb6(yda~j6uq3w;(Wk zIh{O6*xT)bF-=!Gs2wX5@t-C6aqLnwu~WE(QAihK|Kr&2UHx7Li<~bbB6UlLLBr6! zZBA@IPoorlYC?cV@8gEO>#NfrN|bXmDq5d#KDw%!b0}_gFyM-fs%NxHdi3b&hU9-uP3tjz0%zVYuY3X=gTuZ8B>jE!ZCIKo^Y`Pn{R665#fRx;?L8WxZbOx#q|Q;F5U%oHbvt7TsoT7xD+N8~e+;b(;MjC@4&dk*Ah3G0dJGqko-{;D>XnDXCd2br0GVlI2_F3UsXr_m1@>30&y<(xJ7=34vDM>~PBtW0SUxuFitVZMifWP7NCS81c9TflK(aXp4UT zI~n(Tf%s(~4&sXlNl6`JQ*E-o?B9b>!=>jZ^BsA-7A|>4uSY7p!z&8yx~Wd~S~1^^ z3aUMIb&?HJ_G1Yw{7h!2K+E#E*DR4PBbf9SAWmYC0yWa(1$CFOyt--U5hMEuviRHE z$vc7fV(-|Wjg7MidOF-Yer8JG8RI;sII|V*`LvzdHL$NG_sZPlo>1QRQETE3fdv8y zndG0G9(0di*?fqksRV=sGlLJ+TdcbQ-HUe8n4Y?xnT(*fUZWogsn$_b5nchBb)f&w zr|HyRp5mUh{kmj1-?zXl#RSWYa9VHAo@4#4iS39SX^U~)S5MKuON#q0rp#WjU92-0 zBsH;>C3Kv%K7|7xK3KM;A2eDjpnL?m@tcNO;g`y$Kcz}uGquh46y z&Ba4Br`V{V599>GXTJg>>Q{PUja2B+Q8VM9UY%SZjK8((^=sbO4!YKVZu?%CG_0GR z9EyZ6K}!if!!!?N>d70bze`4|^uc9;>Zp=uNJOj5Vk?PPxJN|(GRZJ&d$-BpYb{1d z(`$3^j<5*O2tsr+QbQq!w=+j8E5(128=7{c-J6_bR%16}+MY#io< z92+!~u)g-0-u+HRv!uRBt0B|gcca2c=^(S~@M|Eg&tg9@vmWtItsPL$$Y>m;x#1uR z3l9`0%Ez29l{1Xzx*!tP`$>PsQP@`ByYva-N?ib~f~Q$2Ff{$H}wIP*(0p9*>(H!Z8x@GfK~6&00hhb(_%$%c6HZquMuGf5IOsu%rdxeOs$<(!>{WN+#lHLTboeF zS`s;LA__jwmujT8?sZw=_@}Pv-!Bv(-I?bbGJv}aM zR?s65vqo}=?FoS9;C${-pGrDnq4$BkW&fe8&cm(5S+;EZau_GV$IJC8Y`g7C0995o z0F3b?>T;0*ubD_w)I$T$R_Ke$4EuqnOzrmL1H7pmeB>f~=4`_Y^n?4_a!t_Y|kcDpNDb^wJFPX?V%}7+?T1ROdYvsfcv$a#tz+h|0rGA&YYug(g4N_Y)Jp62VJo) zXhO$tn{AQoazRlF73r2rS^ag+j+KnB!oCN<#T8@P?B$86;*n1xzi!Q%!UEl&=&T~E zsGOHxue-m(xH>`x&`_I+h?!SrjKluok^aa|sMowAc(Zjjf1y16Vi7E7*Un2;P zzfM*R@)B-;v{?MOJQqvsa-?>0-fx@eBb9gFWk^8Kel-6Hg9xtK6)(K{b_dzsYF!vY zI}K_L%>FGHL(mKEU^?-l7}TjC61m5rE)FazPW;Y5SzN7lwu@&upE18P z$7d$#UjBJeSc#Tkb8J_F62$-43vjZkjiXm(kF*0be+T5qG=e8l2$av#P+so=i51*a zRg2>W``3+<-%kRN4%^-lIe!oPpoM`QEs#L6m2Wesf7ztl zjf+zyUq^DU60v4O4OSxRBjK2#{aSAlUSm5c<6#prk$21ESr-R5C|SYFjrw-{e(~kZ zWpVZPn1%D~j3)wQ;N?AFyC{5#Kl&jWxsm+YZ#>Ca96P;27QXuzp9TjEXE6nYfxBpf zFyn19`LHWl%ApGylJD4oHw)U<=|!e81kBWW1Fl(UE;|&aZEe`FhBfFfC$Gycewz*% z=^7pmB0VuFL}p+D2O+=n*|x^|TywIrpN6KdT*56vX~Voj%AjFfz53h4le_`%`nB=E zmIhQQTVIFt0Q*(@iqvm_kWz$ZIB_tw)acbfk8cGJt^%q5o~o5RC<%!I!DOlfydMFt zqK1mW9Aw)1R6;H>H!5r9;bKwF6bwEh)Ky%E<*cOQ6;cCa!kky?vK(e`$RP>F^L zB}t9ed3P$+n?&Y5h-3J|!wr`e|1U&(8{s-HM+n$-vW9EwiC$}zm>>@liihC8rvYlp8XkP37GMa*t=!ZU1L`dTSPue(AQ5~qM5_px! z6_Wg+ZP<@`QHL5XyYun|rWbA-@yR8vy@TRww#?#mvbpXRki=M5J)8JQk1*zTAttg^ zAN=1Ct%4gWni{N3ay0yDxQLa>n?ZAW)yr1PKi{NmL-AL=_*=-Xqj0Ub;ZiGqE9wCQnASk}1YOZ) z=@`U#1>rhvQVyQuLH%(8Lu3cz=jC0-+jDLe(oA&QBy&v1LwU(PKp&;GHdx^*hb<$~_;&vXS+uvyB87znDSv-^#-?gy#QPry= z>#umk6f?XCT6U}Zs#GBT-lmYa*Ki7Tp*`Wk>wcrhvlyciZ?L}=q!mfv0=s8bVZ%f0 z8*s`B=N=qc#)(ViV~Lo;W41RlhyCI~2CoN9t#sRu zl%)C{NS@5<)ORD=SEkN0_x;Mg zJ~O0jKThU4&Rz*PJ`r2jIU3TcO#fy%x$L`pYvjZb4`CEN_G|WLxEficitSK@NJ#wh z5cJS?r+&vNnCEQo59aslJbwv;^6XuMB1Y6#iE*F6(&frpniG28pgycmEq21qN7bPEVvMM8l~-Ak7WTpFaCw+_zlz4w=Y z4ELP9*IxTuYklimXUe59A82m)7>B*yef~;Qi}cEi*!Ay^Fr65d>gyvTCeni|%`U6U zC0*EcnNMFo^K5^TF{U}*E5{$%KlkbXInsBxaygl1MAP}c46!%ss-L!QI!FFyKYjiU z*N~X+4^gA{0@YA^0xniz_!PqTQF; zYJ2s-u|wtEdYP%o_>M#RoZSz{8auNC3*7drqB!RAa28fC)FOHE;6b_bjF|tHh%8m; z8&}k#a$q72T9imsf^9s>3_rcN`#PeISg#XbFJNpZt03?Ym8>HDhfx6qTZnR_*75oe z#|5jYKP55YRZC7*7Q3!H{n3-}AIyhur|KQvdJ=*z*DT&bDRI>;6%|E%}{T z|89w17(S!sN!#0xJYHz=L6V)&b<;Y0N~oxM8p+WErOJI-syiV#bf`r)ck^z@sCa)f z&86JqpuNwZrK^fXqZ=1@H?7uGXC7h}=#;6CS6h*ZbhfBQizDAciI~D*4yH46mJpWA3OPK2SN-bQ}oO3F&6OxCI8}kIt$~LewcF{TOcS zGMJ$6SAa?U=w+Xoe9U<+ZJD$g8V}S*GAIc-_dLXh29uZlW@km17=!vQ&PaCOaO$NAFES6E$XK8rvf%N~)BltGd))T$3~%cr!xlVawz*OcI)Jiuy{u&=g}u zUun|A0m@K!v(T}|JGMtRR+Hh4Qz4k|eaX8nsAR?Z(R-arjg-3_ni4uGOuDOXjl>@ zmDHuquq2tiu04{&hDhhB5A6Bk{3t(lE0s6*2Vu^+hP-*K!`RQXj-s6lWulFx7Go13 zI{MoyD%G1@jHh`%kvH&;Tg7r3(ZngEiRrEvc@p1t1~nL~N`(sSWM|X`*;zG7uk=QG zk@9u2rgM%%1@bUn2D4ity=i+lkkVlg`N>Gko|R85-WBB#v2?zY!#CBGN0{AAjVd%0 zP9+YLHihk>W4lwjN8k-auYb?N=%5d455!;KbrmZu1ER>QI#|u_aZ~7D5-q@Us?SjK zTfhxpJhV2cgM*nY)+#tEYiFDE6FdEZda$xR?ds6dil6txWm?UTH-+DyqvD$%{hX^d zalq|`z6@w?miy?8k1^aL7ZFxt?5v+Rh0C8M9`Q0tVI@RjSG`Vi>A3aG#@4v0B;dK* zbSZGlU92W;aO(&6j)}&UOMFD{uk6n{_!q8!@5)U|er9+`$%TERSZz6$0^cB^(4M$$ z?kJ1g81MkaWSU~7B-4Q@4HJzpyp|!o7s_GEPnYsQgJCnvrg6rPzs66M+%|%0{-d>Q zGR-;0l|Q&U(e8J^Fe%3glCm@ho?&=LLwct;UBBRm1_IY^h_=M5=#dhdiZDktZWbmt zNy`f5A{#V+zm7baCMR7hu74_-K0n%BV{o@b(nWf;g?C=r8FXQvt_t+&Zd2$j5;HEW z-RSd;%w-NfGBOV}V~L3dj%t4F0Jx;c)Zm!0KAJa9hc?jG}GG zGv&eZGbtx-gB6{Yr;Xr<%7RSCDuI zm>ZQ$!$%r$)hp`p7)|m}vp*X)MCKH>tmwv*n==~D31TdRD6deJWf7mP_r$Skxv>4Z z#?MxfOSC)`*n6e51F)6v?vq^w3*X+d%=-;q_B#F5sUvdMP|>4h7%hcwZn&L*Y=#0G zA|F&egViW$R4uLjT${W8JrWZVMAlzvdB5NKqq6fU7nB1vOOm9LqKwe{@;Z%_hMuG7 zD8$^%@PVpf(I6UK1^}*8A1)?ka9HkOJ6L7QB!YZox^?wB(Gi<5G(3&-Z{RL^;5q^# zerbIly?LIiJ-w5Mpu@jRr)M$t@!M{R^)l^?&NuPKL7ylg^m4ZNwuLdpAn(!h z3VbCg`{{_kd48W2Cmr&Omn^1+d0%<|u);P1_r0TJTMW#lW{$7Ved^0*ZtM@=euA! zC;s<>Gvr@>TzI5M1=q~})9$LB`%3Hk1KTjXIyWq-iu908ra-54>fJ^1zV7k=sUR%j#%kpVo$%iZ? zWlz!2wvKGaxc_zvjc-O@Sf=DTw4iGUtuHpjG3Y9|#nWcWws!Q*l>|*v0WvsH?IjxY zrd!DMZeOi}IM4grq^vmlguCq}!E^K`rpvTsy1|{S>=YMqvw}RI5=r2EZuD^DyI&nS zFl6fb_rq;5)v36vTf5zBl@?1G&eT3!$fX^95y$98y0%WAzwZ`ukresSHnC88Rk|@r zU zzEfC@&^pb6W&T{jnQ5K39J*Q=4&0yBDN-3E}k|YEUB<6uuL4etP>&19C6xC>=3yoa^8LFOW+lJ@Ad&eytXu)RL26p zG5NX_u*o4X)@<`1rOM;C8MP!b#6_e<&PO%x!lk9IB-pmRUrOG+08!vHi_r1)`mNWa z#4UAEm1HxDR2(XhrZS*BFb~5M1g+1{0hI8Wq=6M(?Zfb* zRP&3`%a~e}7rIn~^ose`rTGjo%O8!|x*nA(O1?6S2d8tnDE7v`Qo*>Jt%qqPhZ z)5TuOHe4QYtOn#;XAgXqS)SFf?$|IqgX>Mi>8assfucCL>H<*NSJM=#qU`3kJqI^x zh7O|KIC?bA%)@t?)*M{7W!@3fkGO7^q+i~lbj3q=R0GLV`urqUit}HmWGBhszfCc7 zX;*hvL-diH#FIbTQz>U-oP8$SGGAXqNPOSs~5~n;c{&q%QQ7x%! zq`*pMoRXXWJzi({I5x;i*1|oRCWMHuDKj3ww(H9G$M1~(;0QPSJ@0R+&?uZAgV2kA! z#WXrY+PHIw&H<{n<$%XJ{i943D)QDEIfW?|ll;-ku<4yT#APm!9KQ=6Cf$&mx74W} zkhc&?3BX&q?!H|&m7mXuY*iCvPp~-?7x6rfw9>z`ovatzt^;pkp$q>y+odCEAuAtd__1SOJqNb+JLd&2cEU_+?Tx8a(!Ol{-`Zv|pH znGD+Cj3~!Of_UK6?!+_DEQiOf7JM_roZ-?|pQbJ&XKQDD%g^+z>!*v!J{;hh`DFNo zXHXH48(C=N6Dp3c3JS9e=yobfveu9EPd7gq-Y<2HCT`8v9ZQpJ)8C<7?So&r#;nV= z-c?UyYODe}pgqFQR=arX8Ndl7FO)+`LGjbn8Zo?lm;4xA92hEnoy1q8=<}J~DD;>+ zdp~l(f-R&cH^7o#c1y&4;tJZ3<;NN0yQaIQsBe1O$y&6zZ#X@G)gPfs-ewH@Z{=`l zGt5wzMxjzAWle>cWDGgqKB2X$g-VbaS-md_Uu7=dZ9=WISH;;1ejk%eQ%9_XICT;4 zCBMtH{0^2VvID^Dg}%x>zlugwFM5$N%7RK`n+-m+8jF84uO{?M2jcE|D@OYTPid$V#S) zVjO>aDRn=EKA&xx`X)^{0)BH?3V&nI6`>6yfj&{K0;kgJID@vj=-Y*2T8co{C-!HX!~OQ+PxQYSQ)8uq+^zQD46ZhIs<$H1x9cLp1x3Nf5t z*xS)qt+aL|JqGgmNskMatyUN&}_$FP7V*DDC&SU zKOky_^Hk`}Dy?1dr3CScQC822#GXmLM2SwfWc|S2C7em}~ zUK;5NmNlJJf+}Ctm%jEP`|4xFQH5NBOr?1m-}_g!a?(*p!LMIjMYAHOX9(hy?4g_!8N`NXr3L{W7fWhP>rvs_ktu^fNBMhR zV0xXFmp>G3Od*2U&)|ibhy5}wqv5Fo0a!ZAPrtz{UZIyXfA)L&hT(OnP8L6%6=jC3 z{>8ef^gOwO9Sso!QVkc-98`IXUD|ISC3d}6>I0wDM7HqQXsiGEx+$rd-sX7`#O3|bq=gGxxStqnxW00b@?GR* zWJGVG``kiifkv8{9jd=lJ`_E(k^iy~mM_3QLwz|vn_OXUmeM2xE>-$a^s*xfu!stB zh}ARp>OdGECOxO_CAbD5t-k~wJ&fckvf_W;m+QN-wa9?Jq z47o{B7KXUplFO^kg$vXeH}p@iK`59$Wt@eE_l6gf58=(6?W9zb4<{)zQ~yGFl4gNZ z7wau;bsEySNUJAfjBv51-qYtxO6K~WT|JE4?M;c9EP#nLJ^s;2>e5;21Y487;i<==To3>!2NE!fVo*C z{|b@-BAygO4V6-#!IIR}yL4$PrTVFoPYV*jWZ~uAD+?-YmApWS5)WI{izEXT3n{)r zd&%&Iz@X>v;DF$>eULO_>Nw-gr$)1x-kqn1^$<%zi>nyu0BH<4D~|O_y@i?qxzsCg zoG7S+;^E(o>fSAWml{)PM^u{k=^Z~P={(`8u(42n6oaecesjN+ixxIL$f#;A39)t5h6$HF;Sft2%qdp`B~w0rr~w2Da* zoD@DBEjk58=V`L*6U?7ENzx*Q)^1GlV79<#)!K`FfC8ZJ+$}ldVum0MTumCu-2Db> zJL;6UI2AwQ50bE`MX0xVyh2H4oimqElXgu$dw!g$wkX`Z%W0e8B0ithf48 zkq6~Ae$iRI=weFY;jyti>Ol2T7~XBes68^6W|*V75)yl+{o0{|30;$qfAwEpM0%od zNpw5!3Me543Wq0Ea?kn=l8W_~&9p@va;)osCzCiKi9sD)G%&v6-iS>d^qzc9c2L0Q z`HBmS#_`~3)U%YYe!G9J8p-B}caA*(r2WrTBiA^3c1v!Q1R@Cgao?PCzVC9DXU(tX zD1-#cw8%JEGkWHW?$W#9j&gXutdme2iGGRX2;!JB1Rrw6|MqZjU*C0z%P~%g8lSN0 zJu1J{%U44G6=-Sxndx&k^pnjj56!mKOay->nrB|sftw}M@ur-u(kdwJb=>)6ojw!!{rM1nxXi~!!Mu{{r*f93`( zzT`TP`_VqfA-I6BC9uVNYXnXu-dG$6mrrHc2j6V5AqZ!OgaAwpEEvjy ziqXrpP!3We5#uuwF;L;RupvF%(+`xrNa7eIzr8{pW%Jbf2LUtPB1}VUWM-gc*F`}M zjUM)s;zmBODuu`af`ciy(rh$Yi~E~TiQIQjAf*G?Qm){2({qz=kc#~;V6T5B4*QMs zEE(GY4M_EY2MqYJcXt&|Rw8rau!=KJ{b{+t2Dzu<+}fv9*guE(`9TC1bom;6F1aLI zswPZ(LzV;KGgKs_nre-scemu~=Hh!51K?p|ZeJ!eZDAfDnBWx&?oA12cUS1K$U}R< z#}0ETm|H&+d$(9{Q=JuJUg*!s<1AScLOJiYpZ}@H+o*ra!9=%srDE5QXaf^-1tAlu zk-NiI$(`Lv&=PI~pS)9A%j!s9~eu%A>CQ)eeVM?~wA{8KlL#EHkm}s@;X$>G< zF~t?hGtTV@ncNh)FJFuc**4NMr(&Od-?LnLAnUZ>A>_F@GgMs}zNmmK#o`xp%(E8ZW2#|{m}Bun)JMpvAJ3}ARJ|*hlcDv7jPsdzV@;^rcrpzI5nolV zFzrC5?pTuK(?NKT^62Rk z%(o81dz@fC3FiYG)9Hn9Rt_kgUnD7ZUt1MrbeMzKgeM6l<^ZcjL%U__H`Pu@Yzqv%v-1mmQeZCnsBn4fqkAEpXx38R?#dlwH zNYl4|-L!i2@{C;LNIBdoF9_}?;81FF^D}QCSkvOBTWK*@d zc8Uw=C*0KyfnQLUCUKt?XREsPL`hKkUVXF*IYgVO%<~n`p~&^`djDqK)E{!pwPB|E z+|EoW2K@cWQ{nG!UxT4MEp#O1!sZxB)aEPIh2Wnuj;9yRBdoOI^ z%1UA)k;2Ymm#oHdAFo2Yo>jC9u(>k<+X> z;(eS%+qIk2^9S*yYra|Mo%y0kAZr8z(EfB%b7z0Ci?{hRhgtKrYzG3ya_RG)Yt%yqEdss*ku z8$2^+?=eT7URS`W4N*f0;Yqr*bd;HvU5FUlJtLH!&y!bArcc+SCPX>|D3gt}EfS-M zs@hr8Y1{c(h@BGayx@qi+;(yL5k_tm+0)aG{?(UUe3ofJsqr7qOPw|_)yUoTi!RR< zX19|i2O3G`rK0z>qPxI4l#G{R)~Ocmih2^AfV_S%UVC0}?RUoS_U=3J6*#DdSTm0Y z%e1}F!WZ}x8^VJ&M?6y`Em|j!FF`BbZ1Z1PJ|?{p;nv-Fg~M z$M!d<-;z@Fr_4J;XmYaor(QkE3uRN`X7+HI=o#0%WyMgu>m7uCyt(=){8lF+pRoN! z5ZW=z8;7eo4(-bwKP2Y1~|4ftzPVrKKaeEOZP{RhP z0NE?8$4Cv#byvJie4Bg(SVJV)+;cuZHIf((^XBqEEs`z11R~Q33j5&O@2nc5Q4`NY zZq^QJe9MWyl@NTTLFL@9n@xGXXk*a=j=pl#X79PY6bf!6h61kN8x+j3-gcmgVen=#&)x4oBF4ZfPam#fb!bidErS-NeH-( zEosFQokJD7A0^qKi-fE<8s6Fa38MxH$qz1*=l_DFgBd+mIDc2fn%;BDP6QHp6K@$T z5t7XOdXjX=$vM3wtzHYP_95BXsN5b_UI-gp1(6YgRcAP z^)$Dy{m0<95c=bJx@kX%8 zdrl63Z{n!LnXbj49P~Nvv%>p6<7NPlab_Q+^!DVbLfvm)wV`7WUKlA%MwZ-n&NvB* zOVxcnpT9Sc=n{6^!|;3&b}3I$6A1+Ef4XkmhNR~6y8Qhjiri-HqYmcUjB;)bUHx7ORm;JpfIaBxaSo} znZ&fM=r%e>KUAKopbm>!KFRG+%MT)68DOr+IfjB z54gGqIbGU>Ca4~34;ZC41SkJS@=aq>BS*065q@W1!v}g~x{yyz~sogg> z@RSua5=5xb&GM~N^M@P)58cA>k&2bnr)u9+&gZv-A-=KRT7w<0&-e|Jh#~QJc9|xD zPj~|ef?}d$XvD~EJwz>9>Z&)|m+Lv92G;ULcpCozQesf-BniJI^<-&dO)t1IQ%21u z$|!F5$0+ex^IjoHm5Hck6Y6PNb!Sw9Y>Zh$nbD4^AwX(WlD>J@mpgt!E_ z;m676{s+(^iLHUgka?rx%{ltG+Q{{UO)=bkd zR=Hlu6O=(%Gj`aDPBvw>rUg=qxxJe*y8!Ij5n>c|?Gg#EC~^-^O;!T4ia@iG+3?IB zB`U^;?YW8+v4Mp~+inSu%ZJ;AK_9L)&!>Vtxgoz$OB^xuXxRO78z5DmzITkRj6`^k zF71>`oH+HDSdeSXtWNZ;;2h!~@Vp~aBfr3eXCk3uX+wTmL{07qk}Y;5O%o{KILwJ} zhJM{o%rm4El<)tQ>qKa3!Q37DGA9h$qu?Ve%PdHU4CborC zn|?43(+H6gqA!SiUsbpyxJzFl?sI$FtorFz7A7C56CqDf2T6q`u&oQgjIp{X{60zn z9=ikJA|}WiAh64AfeOcy=cry#QoUCuPT9soz#@tKti@|EPp!U!zkE5a;^1EPv<=~j z2#xyebhcw9m0h{I#W9!V938Y~BS$m~8WO=M02l=~B0sD7NjA^dLF!m1;|VDkzvvA0 z{g}w^UwMKl^8QU_xC`QJ+M;5aNnO@;WAhv}2|>Mws&q{3F?d*|Js|u4L*mFWhialm zgg>r$n%y&6aIgyyy=E|3fDily_7!3LI}Z-cRb;mCjDiqEU$h)}TMbD3!YsdI<<2`VBxG{{I;<&ljLa zCh&gqg2{snefd%r-BAbXN|vm}Ix-usPDrrT!8u~gQ)rS3rvi&1tC_e5bc;se6ErL2 zxk2zhtcnDq=>yP&Y+diL*wrj}sby6s(maoy3G9#8OES6!R;M9NLrU)?O=GLQ8=Vcb zk=QP%WX5#QCPUr%N*(sXQz)J0%_P=^WdGtgC^kll6QSp+P|v1d=L;D;2uRrN?vH3Y z+^5MIXS+fNXC`_|4e?JM@Xwh4o}y8%^!*iLupy8fMcT2FO10+p-cYRje@vJ!1Ml=^B8D-LCh$w^>_pUUwdbQ zC;L+6t6|&DmVqASQvI-=h$-0u_42=RhiF_K#>H;Ky&dzu_yMGqa}@qTUxyK={fTKt z3NRQNkd3d>yK2)J5W}Y~bFUQ9LBn=V$d)ctx%%62hAVW+LF+tcg{e~Gy44I~%(CP! zTNHxxBkJ0qOC^GaAfE`;D13>22ASOtxyiME53Cc1o%DKc0sm96QjJ`M|Gn@jACYzmfa82Oi9J@(p%vFwiZ$D* zavB<{)vGJmA?bQ+(^I3z79LoU0?=a6i9(0{mE_Zna3fwz520F#hkP!P2rJW0WcZip z2$S)pB*yD6=^V(-NGEZK2k>s*es|>t(gBx%&HXVf3T?L2!bhM-Lbu&RI2h<;D9QZ? zbc$H3y35V;zV^E-^KEz$Dte*BbGKwtBtz)u9GdiV-f>iP=?5>(6ET9SzwB?HJXc%u=<8q!zk;?uLAN5wO*TQm3fF19{B>bF2P`OQYCveRPsHELE0tyUYI`m=5{=3y+xJnq{$B^Bp%#3-?4nu{l~E024$Q$q$xKfs0X2Td~CD( zZ#6F{WgMc8$JFmbv>E2YhCG8*>1|Ey(3-Q(-U65u^5b`O1PdcrXV^Gs7Ubg0H~xTf z_q5oj^nOgoqf>=LqHB6h-2T-(E*3~hFWx)9!(gI1=nm-SK{br(!bD1eR6u`cciXQEA* zb2kBb%~>$?n7)}y&Ch1s{+&$Eigmfj<)=n3T9my4-3cQ38FQ8&VVx?l=3hcC4T#fS zKv%<6Sq~7m*@`wWi?O^eLav4cYQp`-&~H1)N-s+!TV5015^%PYJq2;|Mo>MHcv}N^ zDEi(*YBT(u%IFKT*AaGe?IlnN!zI}!GHy$eFB2On>DM@E(5W$e`t}hbt2O8pWKC6; zk8Gfvc8F7Lvd-Opd1pzM&;(NeaE7>Fr49S?5K)WNOKYV8pJasD*A~eh)!u#d<6%VH z=7~JVPQzX{&Rpzq|Ke3Wvwxk5F1*e5%07qYgyE*VGUyWsPTD}}@h;d%1?nAiQ#aWo z!P`wEhU&MO_jTMIfoWO)S^-ENPXXn28?qH4 z51FJ#GvxVKTQ+l8=?Sw_@D%P>4CRImKG#Ejz5(ZpSq()6YM z(2InJX3PSRLv31^N38gMWve9e3)hNHZTt|TW-I+0nC~c`XK36;#Gjy2p889uUaAjC zteEh;(*+Y>&q@F)*jnR@=xy2g;P-w6hNet zG41Egv)T)h(iJ-80}*ztM2lws*k4~JX%Nav+brceOm65FKw^s{8b49Zpz_Pp(+J4-xJ4WEDq?mJ}AV+cxQL!ZYR zhmHXG%Vqs56<6}7b(rt7ja_%tVwSVtN=TL|OdB9&e%rX6fF(=Cvfffj7(48_Pb%xuz|XgS)Cgd{}M4b2EzdZ#C3Z3 zg&NDjOR9WpH^4>^J%+iBwJRS!tYYcJ#OH3OoItYj@9Xis1g=Nd>T~-OJ;PJVmrOCS z4^a+OJ7AqM%8WyJqH54jNG;n02bYWWZgd9FMUGb5Ua;DkBXfu6O++w_7H^nsS7qTvC>!5`?mt))Ll~B>L;*Of8(T@qO0DE64`ZUX@O;K zi>6f@g8n2|qmV9$D$nIP~hW=(kzAho?SBctsIe|J?YW7T~diay{12ZYWU65{q&$PzU${SxB#; zcrTYQ`_=EDR2me9l5dp4z!Is7uDZOvT}WI*v!k+1ON9gxMs4856lJTWw!1VZjS=Q` zi)gDH7Vcwpj{tiPQS=&)%hc85>C}6A6sK*myNewFboNKk*)$x%?D)c?8juL(KnLwm z>>&uqbr1vhDfgWQfpt16K<64%a1WXtj-0l34G?wW{~(&E9sbZ`?HFcB*)F`*`;>r- zY5xZqucj5NT8X5om2Wje2EfDQ=GBG6Olf zV?e@1JYk9nazi;hQ>#y~6|cG~l>-x9Bnw>w3gv9mbV!XSjg#tfCtg*6rn{oLneTRM zwvG4?6}vB%b~rTU#2JhtQkJzt`$?bP6(wdugRHm^PZ%Nguc?~TU55KDkFkXHG8w)y z+Z1f_X(df!2FXMgG!y09ykXO*NLQr&SRLm7)p_d>q}EDuM$3NQmf(wATV6T=%?|62 zPGSp)4}d^n1ZIg8d==&50tv#SKTh&_(B1s>vi!jnftsM6<0w~6>fr>uCebc>pjpsC zea>dt*{A&9dgAVKjqH0=Z6zs>j{>ForlDwncuhOl1M%7bvR^pVev_LLt{&NaiB>)v zngwy10i@DaYyw5wA{&Q01tC3uC?p6R{-R2XJ6)+auKKRav+9RZH(Y8W1EgKyd8<5A z@XupODNS`Eph5G%%)rgg(UK?p4QrkT0j;+ota+ErZ z|2NY!eckjW_@?IQ&0e#H-2>JZkl9>vKc8dqmfYgQe;3L}@Ns`cW@1M$)t4DeDV$%X zXk+`;Ut{$7_Jwc_L}VwT1$jU}69@hg%@jinLgvWqcLEU?2_;~-k{|!yw@O3#p zOFAE}8%;k4u|`jzYH$aUnJRdN7=ZspLKBI*Ommp~u1U4ec3JyV zNCBQ~@9i#u4JjmchIGPbdok`i0J>B$1q}FtZhM?N?@hJU(;a@qBjv;J+c2BCSJ@D;vDnz`M%Jf z<6NLB^3OTRB#lWJrm;6QqfL(=7bJCYUZUWI9#D%aJi+E^L+9G`nvhA5Y5nEd!_)e| zu^rL{Cx}=G28LrchX&q$I??&}xlQ z2JWyORH~f0+vRtb=m|}qM=#&&9;F={@mG`ffYT6-u(#XAe>ueDxP{PakP5IMF{w_b zR`|{N3Sh(TN4qOKN=}BtqXphfY^Iis`!#U6ys+#{h5Y57*XSL1H#rQ8lCAb5wfNOZ zrY0YFf+yQ*!sz7)1OiSn+6j_|B+1r0QjZV6x+B0duqY_Kka-%`{oUK-C$es}a(DO+ zIaLkPrDlg!2Ul6lU73tWK<$l{iM*YUZBmpQY)1IxI#*}uf_DA%dGIrOrgzTeetO;3 zk|UEwEs61$+U3vfIhjv>fv1*T)CFbeX%HX`pZ6|@MKh0DB}n z2KZiR?K~yWPJ(VD*re^MO&W0kawDkSQH#m&Z}35#F-hN0bc>`!?vT!ZDBo6&LvN(T zomS;;@+8ebjZ{rjdY>v3XSBKtaHL0*LL}NphM@x?F0Sd~@e;HksTM8u$_Y4GWexZt z++*pv-@!J7ATM$Mp8+z?J(4f6jpjM&u;{!5vXtS8<$FmSp;7VT8e~J zFV$9)MVMV0;FhktZR$%FWE;8=njEOT5gdFjB zU6luRAQ=J`l7e<7-|M4Z@;QyG-3iM*k$G#-vJN;9-S7VcEBrVJozvVIRUi$N1N*X9ZLHX->X|sVLF9jy=06-qwB#a;M zP#i(C0J6e|G_|r?XS2vN(<kJ4yc+ux9;HU!BAf4*2*~(AJ3tRv zAB*(HxycMxoWByq|M31weg=7_@fJnJDYMTG?hQ4Azg~l^@)qC3<{1Gm1TYU_MgjS^ z2TO}>l7TF=w+!ZGv?l**PlV6l!EFHW(qjb+xEcVqKRPQmQIcvU^BeXRoH_;0BgrxN zsdA7K=w@TB(SfsXmmgwgSaC}?s4m_81?vw7<*Pvt%WJ#aRCh-3P=o{XeA+*uIwe+8 zmMby#Al#KO8w*OkfI&G!kPG~}UiHKc8IA4*X+?ZSJB4+bK|oeGt975U7vzR@m};ax zTn2UpDLsy~`%`y&>Ok+>GhHf}l5{eMbj3cMJW~eBWFYxmn|u-vZG0&s=Zo$DeW@QT z{7`rm5(uBkU>Ldey1z|na!5VDg%5F~pw(lxlifoyF|2dcos{YsGbL7@VbAA^-B!D& zHoppJO5%4*_`O2^5E5054rs*b?~FvvBN$%@9X)73mBAMPD~4@!3Q=OB9koRgsP(VDYy<-mbT77kNkLBw9nA$p z_Zj70{ptNIkYy)_>`%9YG{N>8p%(YJzn#L0Tg=ayn+f<68Q7kq5N4+fFLdhmfYG{S z8-$90uK7zzg?GFpDl2@mHRdqSh_om=9~Zz_Bt>Dkh*w~;Eu#9zeSOvo9N1Y9biVLO zh{16;PDP)p5-1Qoki zU~fNXvWlVdIFMIDC$+D5*y(QRo+9X^w4U@Ml*UXzI3J7*v9D)%F|RD=I~KO4Yjl#At$t0%D3r?TqsH+IGoGzdVI_hdc3RDn%ZmB6ars zr@9zg6Fy2*cZR5mMY=#GqQh8xwO?6lq!RM{8R{N7rfDY-);wS8c`;-?cALUC8`P;8 z)8&FlPi_X_v;Q?yOxfJl`k8VKYh=>F^yFIM?cv)j_Lyyih8o9)=zzULk3$=D(l%*W zQ*yUOOv=I4UR%jz#&5jk1|dsT z>wUdwVwm}K1ZnUN2dlyAbpa|n*>9D6weR-zKy-Haxg<4vWu%Dz$gfp)-*Q;Z4Ha>? zKjnpv1jBt%g1}w@1JZ*u-dSL`I(B5>>$1X(*`o194lw)kbA=G?U_#jDUuSmrrfmtB z^(Xy+Od>3Hgs$AMvtNId6lZ+bIggktK5C+nhm`kbLG@`}L?Cz) z0@8DUM{w*&>ItOlE?lithXTwFQJD+7gdy){9h1B&olTx83!@e2c#w&Q!T`}Bt|S#k zA|M20naji1FXs>MYD$8-7QRu@2TjCKpWWBES_vPRF}a4!n80)(qO52$fHm}ehe1k2 z1yW30jQ9;xb6Up#Q*}qIPeypXJ@o0SeYRJI<+XumEik!=>|LJfPJ+9FgQK`htf_Qo zK*jF>0J_Qnk@uFm2vH#ebC2wS6V16F*T2hF{B4pATRFV$bvkzUKB?xLnZFX`;efvk z0M7`n-cB)s?V&&VX!!q2y1&pLQkb+tVEDzp+5?u`%1}+}%G$=EU$qjCfLELbAuhG| z^MC7ux@QliU?QH_AO_&!0!hvc^-U^J#S=FoZ~+SY-viYYY86Ez<}^fUkP2Hgz+)hX zzXoz#sDzgQ$C6+&{IsyEqq$!2X3nhQvb8knL0XhuBONj*>c8;|K1Tp!>(JI_pVlDN z+=owRH0sma#Z^X{{o~0xn>B=GK-VGnEdU!b<{B~!V;<_01Bep?li{TYD8)?_&{XTx zWxM;D@-b7)W1u@p7t`R*b;KDHJCk?{6byKE5v73&?0TZV5CNGUe%JN*faZkxUGrka z`wQ~VT6wZ&(dRouSz{(gbw`g{L94T?cTQ)lM&d+^M6M`!&+&;PeC#8H$Qj8(L^E|g zJQ7o#P|g63lmL!pz$nKH=GeDp;64;sebAjPQ{>Lah$X?tWIu@PiT z{3#s)(1kZi-u_nbjm{?fUl1DJ(Q_v@?F65B(4A){C{1nv!#A>drS{A}xZ74p_mM8$ zKAM9SAHYx|2VZHLO$Ud92v`dEMvI6crNJmq)f(rq2*m?Ob)Rs5;y!of?aqi#>@dzo zd=L+}Wj(M?EH+PE>@QuI^5AKFAqO^(5(TOcnSpNrjaf~WuGWqtQ`X8I2(Uq2$ixJq zVhkzi7Sd#s^WCuWfsYv}RYsE<4Qec*YU!jOSb)az6+|YxxN^2ulCGOz?``6=udHU1 zxXDT?OUC{{5~J79?>s?fjiret7=%k1ORnM+xCp@Bo$OJ>7?X&s8)_qKn5O#>j&4N` z{3bS}6RIy7LK6zZ2Jrm$7=K#>X`T=xGjNueAplvBooEK@kaU4Ai)fo<05QHplWbhz z#ok#zm|wa?%!`~>5)miZL#LC=$C~t_aEBgds!w|IX zmCv^oHhTH{c(i@(q4haix%r5(pG|{?_U=gFN;-TrGjvUf zWGGP1CnVWs;hN?HYHk>7kVaZ})={b)TyT~d{(x}K)M@TRCkRrcfq13Z%N$CPN& z389p4jC$83g}l(WX4gI~Lwn&Blvuw--iu=fQ#uthBp;hGMm6R1^L%w;)K$Bq$T%F~ zNW6Kz6yT~ho&2vxsBQ0~mH2DZKJTvnF)0jn^AE1ck1#lTFVgq{n@GknnTtDw+FCl@ zUX7)nXhY=P?dao|l27l%msol1irha6Wng0lbPW)!V9uJ&^cc*>?N=JxJ53496ylxlKS$=pxjx)hBOj;VpFpI zPf_0<&-DBLzm1J%4x=&UxR9JDZ#hqyQwQacL(YdBa!42{hpkoSOvx!ytQ>MooSQ+A{Jk+ zrJ3)2iz~ytH%Hix;P_>DF}xZ=O~3BC4)ZcQZiE}vUr;6xa>=WTHD+9o-; z+Ki@?t=b)+$0tcV$MWzXP*LD+YR2>(8Cz&W84Q+Jj@8y1;*j??8W0CKxYTg- zSnT*`!O+cEQY1z88@4)?FK_-o*sb^pdE#Q}#s5VA+_5Z$!It+_mjpUWe8f__2Mw-U z*|(IsVK_qf?O)}7($t6^Mlm9x-RJU~K_Mg9{7eQOC*a(p zXXENFMm^nR94${B&y0_F9fp^sCXN6FKLG`Qc9M-msNKF?{U(p@=Bo!HwEq0f&9+uB z4hil~>esWGnsM9KQ~J8BTX^D;>#TX94V>;@tL!Nd;$8gQxoSK!^L}}eKp~czI1S$F z?q=JSUKJ&UtPYYY7YIvXdfxJ_SM7JFbOO;00zT|@SCDk>!|b|?DYg^xX%AK8+Ty~! zKO`s?&!t3Qs;(xQ2rpldDi4mh7x!>Hvu9x@azP!FwXwDh{~61;^IYhXc`Keh{pD-a z%Q6NAm@*>V{I`Eq&Es~vJDGwZVx-Ye49!SXwE1rhuS}^%*X3F4)}^C4zYEmoFV5-fczHyyxoB=>=$q}f>wU6?MfE+QJc<*~Yv@Nz{SM9Rm^ z8XlUn9kdd__YOJ6c}^?$!sa+Mc{b`svuD!)g>euFKpCO7jC#GCa@xzr7tM{60XpxE ziG91$xyO0R;CjOY0T4xhk&8wB7E%m-*hid`vSv#EqkJmD4wc~e=sl29l*t@^3MvR| zINc+jAQK_ZZs(1RF8fd=kJ;9mGPzxG&mh(LuF#emS4bjHhhgus_}EB0u7dnFGkkZ_7C!4q0g6qteR zi@WX19;-I%Lyq*{alQ-PI+4MwRbJfV#5#yo$s$l|3EIbCME7d9=;S!4X{1EZYx>A8sHD-NhUfP8Yh*VCs zV7Iml{DaqgFL~E59^xL^`mtfX@7OrhIFu8{g%t*VNqEIwI9(R-!7m?4JGbWj_ljtN|c%m!a-<0tQk7Ajw4b zv8U5_UU!8Ydzt#2?jct3y$@YEupWUTnYR+^%`)Drm&Xu@;tynJ7suw*olnDJl=2&| zUXyfxP(CY-rcPeSeY;SF-IBEv1{+k^)VGm_tVQfr6ToVr1)OdY?*VPqK$QG#?pj=B z*DBn3sjqz$%9o;c_k$1=t0cemJ>iu@;x{{F!}uWXY|Jdu zcr&(|k<47Ok*GAwjUkX_p}~HIpZw2li?|?xcYnrij)$-|cq4ZVm}wda zT|}E$e*TpT)qZ12CjH(Nbh4MF*(fAG>YyUGHvdCl-rkpeSnTyk>ndhD>x27kO3;Kn zy|GJ-T9OxWU$y@%S+>0BJ%}KYSoog}NUN&yH5EB63B6u}>)-H~>jJ7tLJRb}luMGc z3FylwZnlQT*rRp`H2_2fNGs!w4w!ZnR8ne%Gr{ZfA?&|Aco9pdyKK%S+ zb2~E6Q@mtKyX9(Y;dsFD%h5V>& zr*hb>&$$y&)FAJ7Yc0!zUd$KRtsmgb;$JJL*U_L>eZjhTQvVHR2Fp5lC?LAeouLT= z;wcD-g#E63T2TR|@xAAuBliDJ)0hOLn|HC^HZlIz=y4Q-;B?%$Hd5o{%R6nN zZ+dP&skR_@r-R=?(%SU<`SIQd_B!h_e0gKUrcv>_)C_(}8W{-Jz`qMv6@8$R{(HrvX48IhdYzDFRU) z#*L37JUhl9K7JqgP^k3en1iWygn8?}ZDahm5n?2OSgEYZ)v)m7kN-^Q(?^t`B`3IU z53KS%KWS~;Zq`)_qTMkKY2^|BDFMzVmg)m(1%gE&Z|pm2?vM-aJ1Q$k;5XR^zH{)g zwnYh1cYBfOnDzhGf2-#Kxzs$dq|053S_162&s%ML(-bFgW$(L#Yv-vD5WYNr!dFuM zxos~lz7%%LsYJjk1(S73>~)~3@tQaUBu(h#xw$Nh0_d_Kr!hN6wSTSRcwK@C_=!dX z8FxRu4aDudW-q}2hij6dKmQP1Sw&X=z$`TBC97^&XIFb z`YxVc{^>~;c594bKB52O?HGg4hfNs9f5gnVQp6%(f6ua{tbnAk0Fp+>Q+TjmeB9qx zWy8|}5*09GJB5g4P#>wRkjkGxAbSs66OTIdhCAgv=J`q)k9fV3brBSl&j7&yzKC5T z!TtPEJ2!YEH(FrY8i+cvOh0Q4Sd{j;-7T4ShY%${O!9A2LbZohu2nJ?0I1slsO!4K zvm9c`KQF0FB6V)d9T1t)f>Zwg`3H^>hCDa1auRQ~O^|%hM z(6cG~rW=!W2+x@U8KSK6{;ytPGLJ7?b9Mvblyemt@U8H(eBKL|CRaa2u6}FMSqgAJ z_S4GKr;CQ32+5_#sE=<#M%^``C8^9r#kuH{PC14=b5P%icnizd@C=aMNB;sFYtBnE z`ug_-tewaBX@qA0(=;=M>0hfOt#pT>=BS+jeFCVWn;lm6)@1*PH26<1>tfUYwyv$= zIwuv1)96B_-K$|09!5qTwl z9$HWs?s$74>9~s$bu6otBxF5nUZrkgIFm5?Jte}dYPG={?8#c0HG-JZhwZ}dWcP3# zFnGQ35#*{u(IoLOITIAy-fAIUEZK2t5 zcD2`{e!m;}x6R$j@ePp$UlLiZvLM@&Lw(p16Td+b)GZv>HZ;6U_I$}Y2#VGP1FpC> z7%WJ@DsQFtjeUrVJf0&#{!tUzXO|A4ECzmh?#phHIrKec)pZ`$IrEo*9ycA!&MW@x za4;36F+T%CBiX+k4)6ba2)iW+tT{de69TRkh)rJHrT|8J_ICRVHO3?XDdo5x5W~Qe zima4yDsruz#Kher9tZ#J@SUF?^QcMatFNVB$qYQOB=;W_ONW(&=WMFMK>H%bfwPaM z4qt5DPmy&7Dhq@%`#HFWp`Q%a0(h~2x;})mch2NJQ*LJ3Ga4BFc+PI9 zvxzQmLVAC1NwtR*?@W4x9=BC(Fdzi_>g zqUoG8|A3Ej8*(u|GgGzy*%^0jj?#dXdUPCW#~*Yi@uk3M_nruO1-8HC;oGb{ZDl+O z@~pKep2PpqySU6U?4g0n;wd~w_OJVK%RF1+cQP>~fp7q^?5+{Op<^IL^*6v^Y?7Bf z>vqBHq_^()c|Tlbkzib}0Ws+la!ZtR7L`y%!g78L%=W@|lM-YvYp1n3xYqf*@}U>@ zbA9G!svB-@`ZQM{*aFY`5hKhrms(6~}vv zC%nbKr9}8fwZ|d}X-v@cXCv@URMYG4?Qz6`_ z6PJME;eNuP=;5=x;g6=S*D5EPtv#PKjEJ{6_TTJib?_7<0afiIO@ClPCfJ466l!zY zIU3P8e@k29pp_ctIaj*ciQidbeg1P6yw;N~z;Ak0ICj z^+InTCGwHbSQpgJB%{vVHVm=FGcI2LPv8*YjeYj{qtx(UcY zg^Ucw5Vh`zUCrxip6Z?_CHr(T7b_P_Jsa~@Kj-}r4U zPqjyy?9Cfp5n=y41wI$65*yqRm)?8(Lw7HW03wCxVEP2q3rH>MtP$ne>KZogtHu@Z z529=W!qfNija5+W1l+MFwhe>!h+)e>Kb$HnF&?AWTY-??jS0mbb z(||Y7l~hn-xY^2Blb}BLRpeD<*YuAd2w*ZmQ?pT(`wi*`@A9_NxbX0RB%0M+OX*f{ z)p!P#GM@JJ=eSPQ=6xca5`}aGQvyqh_oT{{UPG|@ zlNzfS&j|!a%{}?2<0*$sxy}XV^N~Bx9wXAz0r(%aeTzEH+IEmAzTkB9a1Neh588vi z(S^I`jg`t^PbwyPx=H50ld)Uy32!3N1N8Do@ZI4Z!p^^8_|Vx2!0UVJ)$8}4{qhct4c&JE0@@weqYkjQO(j1P#F_pvzF5UGeXD#uc?ruMTnNS8ByNgF02~CV<0nxB*Kz z-2$AFuldQ4Jgx@6|D7I!5dZFIt@5tvnq8BX#Qc+^+2Uij=~vuCrA>+F&SO{ylKqQ| z12e3|w#oo@uY%Sz_!by=1{Ir$4oV7lEPSmm(Y&|)*WYPH+%n?t~|cp0h+rt%;;rJ(dDw_m*pfb)MZja+y|0VdzybA8G(@{#{9aH}-xO_rypS zK7A)Jx7piT%m~^+KoAB2mfo7dUpS+7))^1~IKuAJp4Ahknj0w(11Oy!#6i~0-l!n! zJja9K4*jeUAn{eMnA1G+M`f}x|KiSo9vNgX+?pLUh>YD@ArEl%39c4i&gP0rzM;>s z^kq*1uFu}6Bn@+<%Xe764HvNhgApL z88k?uJw0|S)EUn)r0lnQEh?!eeAyGVa|sajs2UJQ+I# zDKKOm;l3vmX=T>K3V@WrX3qtnvn}@XNM!|1=rc|v`&$zRd3FvV5{amUdn7D6NC}gL zNM-Jd>x}@BI@8QUH-VSU*3qw0u4XSr|48Jr*We@AXxwMsum&M{T>7str<}VA+}L`8 zli`WZWR_e6#x6V!r++~_+nXqwA6jfq+!w6Kx=(Bz;=6ZR=fuVro2uKjJ6>Sfhof*h z+=p#XK7Hn&8VuUymP5JsAVdj3xieh7J{Ywl!#yB?vO1bdkkI?c)oCIF^uR@2Q@whk zmjrYw=fb_cg8URPKmdcl^~=lStJ(%ydoJsZs4d)+pbYo0b^}lex!eQFVzP3RnTZmd z2P7eM5FJGREbOcO>sv#un9IE^N$SG>IIX8S!Sa6->ZVMHw`Pm&=) zi2UcCdo9+W$0aFr!yg(%afZbdDoMuI+ zAV-}+?KpZ_g7{Ly`OKEpD)$0d7#?k#%Va!oIOyi?BW*$4Fo)C2eAr`@PRi}-Mj9=7 z5_VOokc8JV9#LyplMBbzlTZn6TrA3=7fL|wb|s*7$;~#QXb~|r4mMcTynM+b-1Es& z?cWuM)9q2i|Em{n6n-3)@CZ0};Rq({9vJY8vko#gYryKzdwjYlFI{a??f>*9yCd`` zDjXNS%6dUWqa#&a`LeFOV6DJ@MVZHnj5k-GN;{Bjl~4!$1s^)46|Ai#_|Ax__Ua;n zUB_7GbBzEXHd`V8Jc-(oU^p^fku~x~7rLnSsAOg)#24VF+MkDjn3kgMR`mmrW;c7S z<%0$XS`KM<2L222hwkrTl}JVd#cRk@Ntp_guS->EpJhIJY2rqm&p{U&uv>!+L&~j1 zgd2L{-dT!pId(6#n}>GD-&%WDa7(P-8nts6R41c|D}3}h#FUB+$y^`JF<}RJ<%FuB zt(Qeu1kphQf$(JiS1GXwEMJj0)k%JD_QZ&|nhW?aL7k!rDYaL+eEhgNhpi6L@2V%7^E=))B<3^Tds<9K?owC&@+ z$f{BN`EX?#s5L9eeaQVr@NGyvfqm4=eTs-%RS8RD9@J;{V`e-U4-fgT^{G3Y|B7UF zgZ!TMgGS3g1I88*`1u4G`hCJrG4WpsZ2gX;)zrUycvfi!XClp<~v zwDxxG%;=h$%KpAz0$Xhv=QXlrVy~ih9&zPNTdb|Os#;Xo(68`w##l=pvvn6fUd%s5 zq>DK=3(0MZQ4{y%8*mJM%Z;1Cn+|z$wWrsvZ5}5DU=}LO$O`c5izZoL zMF;zXblFC1y&nTT*A&0bd?tW#WGKdtNEdc6rQ<;alYe!u26y>>61))M96luoc&yEBh_%POuKgbdh=Kk9 literal 0 HcmV?d00001 diff --git a/docs/_static/hy-logo.svg b/docs/_static/hy-logo.svg new file mode 100644 index 0000000..2eab75b --- /dev/null +++ b/docs/_static/hy-logo.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/docs/_static/hy_logo-about.txt b/docs/_static/hy_logo-about.txt index 203a1c3..b72c081 100644 --- a/docs/_static/hy_logo-about.txt +++ b/docs/_static/hy_logo-about.txt @@ -13,3 +13,6 @@ into the public domain under CC0... see CC0_1.0.txt! You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . + +The new Hy logo is Copyright CC-BY-SA 4.0 Kenan Bölükbaşı, thanks, Kenan! +http://www.kenanb.com/posts/Hy-Programming-Language-Logo.html diff --git a/docs/index.rst b/docs/index.rst index 2f568c4..6080c8f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ Welcome to Hy's documentation! -.. image:: _static/hy_logo-smaller.png +.. image:: _static/hy-logo-full.png :alt: Hy logo :align: left From d18e752d335b1e7f454e82239061a13e001ba0ec Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 9 Jan 2014 02:41:49 +0100 Subject: [PATCH 118/202] Implement the . attribute access DSL This fixes #399 --- hy/compiler.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/hy/compiler.py b/hy/compiler.py index d788c8a..6e65b30 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1081,6 +1081,47 @@ class HyASTCompiler(object): return ret + val + @builds(".") + @checkargs(min=1) + def compile_attribute_access(self, expr): + expr.pop(0) # dot + + ret = self.compile(expr.pop(0)) + + for attr in expr: + if isinstance(attr, HySymbol): + ret += ast.Attribute(lineno=attr.start_line, + col_offset=attr.start_column, + value=ret.force_expr, + attr=ast_str(attr), + ctx=ast.Load()) + elif type(attr) == HyList: + if len(attr) != 1: + raise HyTypeError( + attr, + "The attribute access DSL only accepts HySymbols " + "and one-item lists, got {0}-item list instead".format( + len(attr), + ), + ) + compiled_attr = self.compile(attr.pop(0)) + ret = compiled_attr + ret + ast.Subscript( + lineno=attr.start_line, + col_offset=attr.start_column, + value=ret.force_expr, + slice=ast.Index(value=compiled_attr.force_expr), + ctx=ast.Load()) + else: + raise HyTypeError( + attr, + "The attribute access DSL only accepts HySymbols " + "and one-item lists, got {0} instead".format( + type(attr).__name__, + ), + ) + + return ret + @builds("del") @checkargs(min=1) def compile_del_expression(self, expr): From 78f6301e272d8664e1261e57f20026e11d5261bd Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 9 Jan 2014 03:34:29 +0100 Subject: [PATCH 119/202] Add tests for the attribute access DSL --- tests/compilers/test_ast.py | 11 +++++++++++ tests/native_tests/language.hy | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 716f2bb..e13cf53 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -464,3 +464,14 @@ def test_for_compile_error(): assert(e.message == "`for' requires a body to evaluate") else: assert(False) + + +def test_attribute_access(): + """Ensure attribute access compiles correctly""" + can_compile("(. foo bar baz)") + can_compile("(. foo [bar] baz)") + can_compile("(. foo bar [baz] [0] quux [frob])") + can_compile("(. foo bar [(+ 1 2 3 4)] quux [frob])") + cant_compile("(. foo bar :baz [0] quux [frob])") + cant_compile("(. foo bar baz (0) quux [frob])") + cant_compile("(. foo bar baz [0] quux {frob})") diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index b188811..f116233 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -888,3 +888,23 @@ (setv stdout (.getvalue sys.stdout)) (setv sys.stdout prev-stdout) (assert (= stdout "leaky()\nleaky()\nmacros()\n"))) + + +(defn test-attribute-access [] + "NATIVE: Test the attribute access DSL" + (defclass mycls [object]) + + (setv foo [(mycls) (mycls) (mycls)]) + (assert (is (. foo) foo)) + (assert (is (. foo [0]) (get foo 0))) + (assert (is (. foo [0] --class--) mycls)) + (assert (is (. foo [1] --class--) mycls)) + (assert (is (. foo [(+ 1 1)] --class--) mycls)) + (assert (= (. foo [(+ 1 1)] --class-- --name-- [0]) "m")) + (assert (= (. foo [(+ 1 1)] --class-- --name-- [1]) "y")) + + (setv bar (mycls)) + (setv (. foo [1]) bar) + (assert (is bar (get foo 1))) + (setv (. foo [1] test) "hello") + (assert (= (getattr (. foo [1]) "test") "hello"))) From 5017e3c21176303cce2eb3a1ec12d1476a689948 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 9 Jan 2014 03:46:35 +0100 Subject: [PATCH 120/202] Add documentation for the attribute access DSL --- docs/language/api.rst | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 57c64e7..cd57464 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -38,6 +38,37 @@ Hy features a number special forms that are used to help generate correct Python AST. The following are "special" forms, which may have behavior that's slightly unexpected in some situations. +. +- + +.. versionadded:: 0.9.13 + + +`.` is used to perform attribute access on objects. It uses a small DSL +to allow quick access to attributes and items in a nested datastructure. + +For instance, + +.. code-block:: clj + + (. foo bar baz [(+ 1 2)] frob) + +Compiles down to + +.. code-block:: python + + foo.bar.baz[1 + 2].frob + +`.` compiles its first argument (in the example, `foo`) as the object on +which to do the attribute dereference. It uses bare symbols as +attributes to access (in the example, `bar`, `baz`, `frob`), and +compiles the contents of lists (in the example, ``[(+ 1 2)]``) for +indexation. Other arguments throw a compilation error. + +Access to unknown attributes throws an :exc:`AttributeError`. Access to +unknown keys throws an :exc:`IndexError` (on lists and tuples) or a +:exc:`KeyError` (on dicts). + -> -- @@ -560,8 +591,8 @@ Example usages: .. note:: `get` raises a KeyError if a dictionary is queried for a non-existing key. -.. note:: `get` raises an IndexError if a list is queried for an index that is - out of bounds. +.. note:: `get` raises an IndexError if a list or a tuple is queried for an index + that is out of bounds. global From 849244f3b42df5590bcd1700dba80ed61a6f8bbe Mon Sep 17 00:00:00 2001 From: "Clinton N. Dreisbach" Date: Fri, 10 Jan 2014 13:35:21 -0500 Subject: [PATCH 121/202] Make first and rest fns instead of macros car and cdr are still macros, but I'd recommend either making them fns or removing them. --- hy/core/language.hy | 18 +++++++++++++----- hy/core/macros.hy | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 8fcb615..91c933e 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -149,6 +149,10 @@ (setv f (get (.stack inspect) (+ n 1) 0)) (get f.f_globals "__name__")) +(defn first [coll] + "Return first item from `coll`" + (get coll 0)) + (defn inc [n] "Increment n by 1" (_numeric-check n) @@ -239,6 +243,10 @@ (if (not (pred val)) (yield val))))) +(defn rest [coll] + "Get all the elements of a coll, except the first." + (slice coll 1)) + (defn repeat [x &optional n] "Yield x forever or optionally n times" (if (none? n) @@ -298,9 +306,9 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name cycle dec distinct disassemble drop - drop-while empty? even? filter flatten float? gensym - inc instance? integer integer? iterable? iterate +(def *exports* '[calling-module-name cycle dec distinct disassemble + drop drop-while empty? even? filter first flatten float? + gensym inc instance? integer integer? iterable? iterate iterator? macroexpand macroexpand-1 neg? nil? none? - nth numeric? odd? pos? remove repeat repeatedly second - string string? take take-nth take-while zero?]) + nth numeric? odd? pos? remove repeat repeatedly rest + second string string? take take-nth take-while zero?]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index cdc5d17..cde669f 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -70,12 +70,12 @@ `(do ~@body))) -(defmacro-alias [car first] [thing] +(defmacro car [thing] "Get the first element of a list/cons" `(get ~thing 0)) -(defmacro-alias [cdr rest] [thing] +(defmacro cdr [thing] "Get all the elements of a thing, except the first" `(slice ~thing 1)) From c03e798356b65a1f026ee2b1cc4bff872e8b204d Mon Sep 17 00:00:00 2001 From: "Clinton N. Dreisbach" Date: Fri, 10 Jan 2014 16:09:56 -0500 Subject: [PATCH 122/202] Added docs for apply --- docs/language/api.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index cd57464..30cfeb4 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -97,6 +97,31 @@ appends it as the last argument. The following code demonstrates this: 5 10 +apply +----- + +`apply` is used to apply a list of arguments and an optional dictionary of +kwargs to a function. + +Usage: `(apply fn-name args [kwargs])` + +Examples: + +.. code-block:: clj + + (defn total-purchase [price amount &optional [fees 1.05] [vat 1.1]] + (* price amount fees vat)) + + (apply total-purchase [10 15]) + ;=> 173.25 + + (apply total-purchase [10 15] {"vat" 1.05}) + ;=> 165.375 + + (apply total-purchase [] {"price" 10 "amount" 15 "vat" 1.05}) + ;=> 165.375 + + and --- From 977b47d3b4009bae0198494658d40a65af82bfae Mon Sep 17 00:00:00 2001 From: "Clinton N. Dreisbach" Date: Fri, 10 Jan 2014 16:14:20 -0500 Subject: [PATCH 123/202] Noted that args are optional with apply --- docs/language/api.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 30cfeb4..6c01d0a 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -100,14 +100,19 @@ appends it as the last argument. The following code demonstrates this: apply ----- -`apply` is used to apply a list of arguments and an optional dictionary of -kwargs to a function. +`apply` is used to apply an optional list of arguments and an optional +dictionary of kwargs to a function. -Usage: `(apply fn-name args [kwargs])` +Usage: `(apply fn-name [args] [kwargs])` Examples: .. code-block:: clj + (defn thunk [] + "hy there") + + (apply thunk) + ;=> "hy there" (defn total-purchase [price amount &optional [fees 1.05] [vat 1.1]] (* price amount fees vat)) From c0a654e6cb58df75cd973cd04da50cee39daa36a Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 10 Jan 2014 21:02:14 -0500 Subject: [PATCH 124/202] Add callgraph stuff. Slick look. --- hy/contrib/profile.hy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hy/contrib/profile.hy b/hy/contrib/profile.hy index 3992890..485f46b 100644 --- a/hy/contrib/profile.hy +++ b/hy/contrib/profile.hy @@ -23,7 +23,15 @@ ;;; These macros make debugging where bottlenecks exist easier. -(defmacro/g! profile [&rest body] +(defmacro profile/calls [&rest body] + `(do + (import [pycallgraph [PyCallGraph]] + [pycallgraph.output [GraphvizOutput]]) + (with* [(apply PyCallGraph [] {"output" (GraphvizOutput)})] + ~@body))) + + +(defmacro/g! profile/cpu [&rest body] " Profile a bit of code " `(do (import cProfile pstats) From 2167c211203fc1d43cb3a9d8c0d10598f7c55388 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 10 Jan 2014 22:00:30 -0500 Subject: [PATCH 125/202] Adjust (for) to just use itertools.product This is to avoid nesting in for loops, helping make clear what (break) and (else) do. This commit is hereby dedicated to @nedbat --- hy/core/macros.hy | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index cdc5d17..7c10e70 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -45,10 +45,18 @@ (if (empty? body) (macro-error None "`for' requires a body to evaluate")) - (if args - `(for* [~(.pop args 0) ~(.pop args 0)] - (for ~args ~@body)) - `(do ~@body))) + + (if (= (len args) 2) + ; basecase, let's just slip right in. + `(for* [~@args] ~@body) + ; otherwise, let's do some legit handling. + (let [[it (iter args)] + [az (list (zip it it))] + [alist (list-comp (get x 0) [x az])] + [ilist (list-comp (get x 1) [x az])]] + `(do + (import itertools) + (for* [(, ~@alist) (itertools.product ~@ilist)] ~@body))))) (defmacro with [args &rest body] From abd0669911393188de3c45247625dd9054edf2f4 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 10 Jan 2014 22:16:35 -0500 Subject: [PATCH 126/202] add test for new for logic. --- tests/native_tests/language.hy | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index f116233..268c6f2 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -41,6 +41,41 @@ (assert (= count 150))) +(defn test-nasty-nesting [] + "NATIVE: test nesting for loops harder" + ;; This test and feature is dedicated to @nedbat. + + ;; OK. This first test will ensure that the else is hooked up to the + ;; for when we break out of it. + (for [x (range 2) + y (range 2)] + (break) + (else (throw Exception))) + + ;; OK. This next test will ensure that the else is hooked up to the + ;; "inner" iteration + (for [x (range 2) + y (range 2)] + (if (= y 1) (break)) + (else (throw Exception))) + + ;; OK. This next test will ensure that the else is hooked up to the + ;; "outter" iteration + (for [x (range 2) + y (range 2)] + (if (= x 1) (break)) + (else (throw Exception))) + + ;; OK. This next test will ensure that we call the else branch exactly + ;; once. + (setv flag 0) + (for [x (range 2) + y (range 2)] + (+ 1 1) + (else (setv flag (+ flag 2)))) + (assert (= flag 2))) + + (defn test-while-loop [] "NATIVE: test while loops?" (setv count 5) From 0f74b1ddf3d5268fcefde26aa4b8c12f9e90620b Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 10 Jan 2014 22:29:03 -0500 Subject: [PATCH 127/202] Add handling for (for []). Nice catch, @olasd. --- hy/core/macros.hy | 24 +++++++++++++----------- tests/native_tests/language.hy | 5 +++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 7c10e70..f6b30af 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -46,17 +46,19 @@ (if (empty? body) (macro-error None "`for' requires a body to evaluate")) - (if (= (len args) 2) - ; basecase, let's just slip right in. - `(for* [~@args] ~@body) - ; otherwise, let's do some legit handling. - (let [[it (iter args)] - [az (list (zip it it))] - [alist (list-comp (get x 0) [x az])] - [ilist (list-comp (get x 1) [x az])]] - `(do - (import itertools) - (for* [(, ~@alist) (itertools.product ~@ilist)] ~@body))))) + (if (empty? args) + `(do ~@body) + (if (= (len args) 2) + ; basecase, let's just slip right in. + `(for* [~@args] ~@body) + ; otherwise, let's do some legit handling. + (let [[it (iter args)] + [az (list (zip it it))] + [alist (list-comp (get x 0) [x az])] + [ilist (list-comp (get x 1) [x az])]] + `(do + (import itertools) + (for* [(, ~@alist) (itertools.product ~@ilist)] ~@body)))))) (defmacro with [args &rest body] diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 268c6f2..f954909 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -45,6 +45,11 @@ "NATIVE: test nesting for loops harder" ;; This test and feature is dedicated to @nedbat. + ;; let's ensure empty iterating is an implicit do + (setv t 0) + (for [] (setv t 1)) + (assert (= t 1)) + ;; OK. This first test will ensure that the else is hooked up to the ;; for when we break out of it. (for [x (range 2) From 110476901ca6f0dc7710c0b9dfc240be31530d57 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 10 Jan 2014 22:31:00 -0500 Subject: [PATCH 128/202] Fix a few typos in the tests. Thanks again, @olasd --- tests/native_tests/language.hy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index f954909..11bf01a 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -41,7 +41,7 @@ (assert (= count 150))) -(defn test-nasty-nesting [] +(defn test-nasty-for-nesting [] "NATIVE: test nesting for loops harder" ;; This test and feature is dedicated to @nedbat. @@ -65,7 +65,7 @@ (else (throw Exception))) ;; OK. This next test will ensure that the else is hooked up to the - ;; "outter" iteration + ;; "outer" iteration (for [x (range 2) y (range 2)] (if (= x 1) (break)) From cc2f45be69916247f7493c2f2ae64b940660c681 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Fri, 10 Jan 2014 22:50:20 -0500 Subject: [PATCH 129/202] slice is amazing, @olasd is amazing, I love everything. --- hy/core/macros.hy | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index f6b30af..b5e5ee0 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -52,10 +52,8 @@ ; basecase, let's just slip right in. `(for* [~@args] ~@body) ; otherwise, let's do some legit handling. - (let [[it (iter args)] - [az (list (zip it it))] - [alist (list-comp (get x 0) [x az])] - [ilist (list-comp (get x 1) [x az])]] + (let [[alist (slice args 0 nil 2)] + [ilist (slice args 1 nil 2)]] `(do (import itertools) (for* [(, ~@alist) (itertools.product ~@ilist)] ~@body)))))) From 18c19043c7843e4898e17082fee089b063710990 Mon Sep 17 00:00:00 2001 From: "Clinton N. Dreisbach" Date: Sat, 11 Jan 2014 09:58:47 -0500 Subject: [PATCH 130/202] Add note about underscore/dash shadowing --- docs/language/api.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 6c01d0a..c7c288e 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -28,7 +28,9 @@ languages. and `i♥u` will become `hy_iu_t0x`. * Symbols that contain dashes will have them replaced with underscores. For - example, `render-template` will become `render_template`. + example, `render-template` will become `render_template`. This means that + symbols with dashes will shadow their underscore equivalents, and vice + versa. Builtins From 3907c5bc5371651c25c54ccc271af4817d3d59dc Mon Sep 17 00:00:00 2001 From: "Clinton N. Dreisbach" Date: Sat, 11 Jan 2014 23:29:48 -0500 Subject: [PATCH 131/202] Added docs for unquote and unquote-splice --- docs/language/api.rst | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index c7c288e..7883e9f 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1022,6 +1022,42 @@ given conditional is False. The following shows how the macro expands into code. None (do statement)) + +unquote +------- + +Within a quasiquoted form, `unquote` forces evaluation of a symbol. `unquote` +is aliased to the `~` symbol. + +.. code-block:: clj + + (def name "Cuddles") + (quasiquote (= name (unquote name))) + ;=> (u'=' u'name' u'Cuddles') + + `(= name ~name) + ;=> (u'=' u'name' u'Cuddles') + + +unquote-splice +-------------- + +`unquote-splice` forces the evaluation of a symbol within a quasiquoted form, +much like `unquote`. `unquote-splice` can only be used when the symbol being +unquoted contains an iterable value, as it "splices" that iterable into the +quasiquoted form. `unquote-splice` is aliased to the `~@` symbol. + +.. code-block:: clj + + (def nums [1 2 3 4]) + (quasiquote (+ (unquote-splice nums))) + ;=> (u'+' 1L 2L 3L 4L) + + `(+ ~@nums) + ;=> (u'+' 1L 2L 3L 4L) + + + when ---- From f159f1499b698ba88b536b572d6a20148f2269d1 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Sun, 12 Jan 2014 15:49:09 +0530 Subject: [PATCH 132/202] Adding a simple `coll?` function to the core * hy/core/language.hy: -Added a simple coll? function that checks whether the given argument is an iterable and not a string, - Also replaced the check in `flatten` by coll? * tests/native_tests/core.hy: Tests updated for checking coll? --- hy/core/language.hy | 10 +++++++--- tests/native_tests/core.hy | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 8fcb615..0ccd3c6 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -30,6 +30,10 @@ (if (not (numeric? x)) (raise (TypeError (.format "{0!r} is not a number" x))))) +(defn coll? [coll] + "Checks whether item is a collection" + (and (iterable? coll) (not (string? coll)))) + (defn cycle [coll] "Yield an infinite repetition of the items in coll" (setv seen []) @@ -112,7 +116,7 @@ (defn flatten [coll] "Return a single flat list expanding all members of coll" - (if (and (iterable? coll) (not (string? coll))) + (if (coll? coll) (_flatten coll []) (raise (TypeError (.format "{0!r} is not a collection" coll))))) @@ -298,8 +302,8 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name cycle dec distinct disassemble drop - drop-while empty? even? filter flatten float? gensym +(def *exports* '[calling-module-name coll? cycle dec distinct disassemble + drop drop-while empty? even? filter flatten float? gensym inc instance? integer integer? iterable? iterate iterator? macroexpand macroexpand-1 neg? nil? none? nth numeric? odd? pos? remove repeat repeatedly second diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 1427187..db2c9e5 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -30,6 +30,14 @@ (defn assert-equal [x y] (assert (= x y))) +(defn test-coll? [] + "NATIVE: testing coll?" + (assert-true (coll? [1 2 3])) + (assert-true (coll? {"a" 1 "b" 2})) + (assert-true (coll? (range 10))) + (assert-false (coll? "abc")) + (assert-false (coll? 1))) + (defn test-cycle [] "NATIVE: testing cycle" (assert-equal (list (cycle [])) []) From bd53416a727f06df702250ad303425578840b9ee Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Sun, 12 Jan 2014 15:52:14 +0530 Subject: [PATCH 133/202] docs: update docs with coll? functionality --- docs/language/core.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/language/core.rst b/docs/language/core.rst index b2e4e3e..deaf16b 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -6,6 +6,29 @@ Hy Core Core Functions =============== +.. _is-coll-fn: + +coll? +---- + +.. versionadded:: 0.9.13 + +Usage: ``(coll? x)`` + +Returns true if argument is iterable and not a string. + +.. code-block:: clojure + + => (coll? [1 2 3 4]) + True + + => (coll? {"a" 1 "b" 2}) + True + + => (coll? "abc") + False + + .. _dec-fn: dec From 1e793e696a6ab731d47b8640b0778ee9b429fdb2 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 12 Jan 2014 17:34:05 +0100 Subject: [PATCH 134/202] Fix indentation of *exports* --- hy/core/language.hy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 0ccd3c6..786d4de 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -302,9 +302,9 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name coll? cycle dec distinct disassemble - drop drop-while empty? even? filter flatten float? gensym - inc instance? integer integer? iterable? iterate - iterator? macroexpand macroexpand-1 neg? nil? none? - nth numeric? odd? pos? remove repeat repeatedly second - string string? take take-nth take-while zero?]) +(def *exports* '[calling-module-name coll? cycle dec distinct + disassemble drop drop-while empty? even? filter flatten + float? gensym inc instance? integer integer? iterable? + iterate iterator? macroexpand macroexpand-1 neg? nil? + none? nth numeric? odd? pos? remove repeat repeatedly + second string string? take take-nth take-while zero?]) From 47d67b006293a3cdec7e0572200f9221f147b2c8 Mon Sep 17 00:00:00 2001 From: "Clinton N. Dreisbach" Date: Wed, 8 Jan 2014 22:54:49 -0500 Subject: [PATCH 135/202] Added loop/recur macro for tail-call optimization --- docs/contrib/index.rst | 1 + docs/contrib/loop.rst | 56 +++++++++++++++++++++ hy/contrib/loop.hy | 79 ++++++++++++++++++++++++++++++ tests/__init__.py | 1 + tests/native_tests/contrib/loop.hy | 46 +++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 docs/contrib/loop.rst create mode 100644 hy/contrib/loop.hy create mode 100644 tests/native_tests/contrib/loop.hy diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst index 80b7697..ba0e3a4 100644 --- a/docs/contrib/index.rst +++ b/docs/contrib/index.rst @@ -8,3 +8,4 @@ Contents: :maxdepth: 3 anaphoric + loop diff --git a/docs/contrib/loop.rst b/docs/contrib/loop.rst new file mode 100644 index 0000000..9c2c859 --- /dev/null +++ b/docs/contrib/loop.rst @@ -0,0 +1,56 @@ +========== +loop/recur +========== + +.. versionadded:: 0.9.13 + +The loop/recur macro gives programmers a simple way to use tail-call +optimization (TCO) in their Hy code. + + A tail call is a subroutine call that happens inside another + procedure as its final action; it may produce a return value which + is then immediately returned by the calling procedure. If any call + that a subroutine performs, such that it might eventually lead to + this same subroutine being called again down the call chain, is in + tail position, such a subroutine is said to be tail-recursive, + which is a special case of recursion. Tail calls are significant + because they can be implemented without adding a new stack frame + to the call stack. Most of the frame of the current procedure is + not needed any more, and it can be replaced by the frame of the + tail call. The program can then jump to the called + subroutine. Producing such code instead of a standard call + sequence is called tail call elimination, or tail call + optimization. Tail call elimination allows procedure calls in tail + position to be implemented as efficiently as goto statements, thus + allowing efficient structured programming. + + -- Wikipedia (http://en.wikipedia.org/wiki/Tail_call) + +Macros +====== + +.. _loop: + +loop +----- + +``loop`` establishes a recursion point. With ``loop``, ``recur`` +rebinds the variables set in the recursion point and sends code +execution back to that recursion point. If ``recur`` is used in a +non-tail position, an exception is thrown. + +Usage: `(loop bindings &rest body)` + +Example: + +.. code-block:: clojure + + (require hy.contrib.loop) + + (defn factorial [n] + (loop [[i n] [acc 1]] + (if (zero? i) + acc + (recur (dec i) (* acc i))))) + + (factorial 1000) diff --git a/hy/contrib/loop.hy b/hy/contrib/loop.hy new file mode 100644 index 0000000..73526fa --- /dev/null +++ b/hy/contrib/loop.hy @@ -0,0 +1,79 @@ +;;; Hy tail-call optimization +;; +;; Copyright (c) 2014 Clinton Dreisbach +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. +;; +;;; The loop/recur macro allows you to construct functions that use tail-call +;;; optimization to allow arbitrary levels of recursion. + +(defn --trampoline-- [f] + "Wrap f function and make it tail-call optimized." + ;; Takes the function "f" and returns a wrapper that may be used for tail- + ;; recursive algorithms. Note that the returned function is not side-effect + ;; free and should not be called from anywhere else during tail recursion. + + (setv result None) + ;; We have to put this in a list because of Python's + ;; weirdness around local variables. + ;; Assigning directly to it later would cause it to + ;; shadow in a new scope. + (setv active [False]) + (setv accumulated []) + + (fn [&rest args] + (.append accumulated args) + (when (not (first active)) + (assoc active 0 True) + (while (> (len accumulated) 0) + (setv result (apply f (.pop accumulated)))) + (assoc active 0 False) + result))) + +(defn recursive-replace [old-term new-term body] + "Recurses through lists of lists looking for old-term and replacing it with new-term." + ((type body) + (list-comp (cond + [(= term old-term) new-term] + [(instance? hy.HyList term) + (recursive-replace old-term new-term term)] + [True term]) [term body]))) + +(defmacro loop [bindings &rest body] + ;; Use inside functions like so: + ;; (defun factorial [n] + ;; (loop [[i n] + ;; [acc 1]] + ;; (if (= i 0) + ;; acc + ;; (recur (dec i) (* acc i))))) + ;; + ;; If recur is used in a non-tail-call position, None is returned, which + ;; causes chaos. Fixing this to detect if recur is in a tail-call position + ;; and erroring if not is a giant TODO. + (with-gensyms [recur-fn] + (let [[fnargs (map (fn [x] (first x)) bindings)] + [initargs (map second bindings)] + [new-body (recursive-replace 'recur recur-fn body)]] + `(do + (import [hy.contrib.loop [--trampoline--]]) + (def ~recur-fn + (--trampoline-- (fn [~@fnargs] + ~@new-body))) + (~recur-fn ~@initargs))))) diff --git a/tests/__init__.py b/tests/__init__.py index ab4036b..5508950 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -14,4 +14,5 @@ from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa +from .native_tests.contrib.loop import * # noqa from .contrib.test_meth import * # noqa diff --git a/tests/native_tests/contrib/loop.hy b/tests/native_tests/contrib/loop.hy new file mode 100644 index 0000000..520b840 --- /dev/null +++ b/tests/native_tests/contrib/loop.hy @@ -0,0 +1,46 @@ +(require hy.contrib.loop) +(import sys) + +(defn tco-sum [x y] + (loop [[x x] [y y]] + (cond + [(> y 0) (recur (inc x) (dec y))] + [(< y 0) (recur (dec x) (inc y))] + [True x]))) + +(defn non-tco-sum [x y] + (cond + [(> y 0) (inc (non-tco-sum x (dec y)))] + [(< y 0) (dec (non-tco-sum x (inc y)))] + [True x])) + +(defn test-loop [] + ;; non-tco-sum should fail + (try + (setv n (non-tco-sum 100 10000)) + (catch [e RuntimeError] + (assert true)) + (else + (assert false))) + + ;; tco-sum should not fail + (try + (setv n (tco-sum 100 10000)) + (catch [e RuntimeError] + (assert false)) + (else + (assert (= n 10100))))) + +(defn test-recur-in-wrong-loc [] + (defn bad-recur [n] + (loop [[i n]] + (if (= i 0) + 0 + (inc (recur (dec i)))))) + + (try + (bad-recur 3) + (catch [e TypeError] + (assert true)) + (else + (assert false)))) From 0afbbeb68c88215fc440a0d3f8723f6070f6f658 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Sun, 12 Jan 2014 18:53:08 -0500 Subject: [PATCH 136/202] Add (recur) for functions and stuff. --- hy/contrib/loop.hy | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/hy/contrib/loop.hy b/hy/contrib/loop.hy index 73526fa..91b24d9 100644 --- a/hy/contrib/loop.hy +++ b/hy/contrib/loop.hy @@ -55,6 +55,23 @@ (recursive-replace old-term new-term term)] [True term]) [term body]))) + +(defmacro/g! fnr [signature &rest body] + (let [[new-body (recursive-replace 'recur g!recur-fn body)]] + `(do + (import [hy.contrib.loop [--trampoline--]]) + (def ~g!recur-fn + (--trampoline-- (fn [~@signature] + ~@new-body))) + ~g!recur-fn))) + +(defmacro defnr [name lambda-list &rest body] + (if (not (= (type name) HySymbol)) + (macro-error name "defnr takes a name as first argument")) + `(setv ~name (fnr ~lambda-list ~@body))) + + + (defmacro loop [bindings &rest body] ;; Use inside functions like so: ;; (defun factorial [n] From 5b78735011d43cf4a95e780c4885bfeb8e9123d8 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 13 Jan 2014 16:15:43 +0100 Subject: [PATCH 137/202] hy/cmdline.py: Add support for running with --spy -i There's no reason why one would need to choose between --spy and -i, so pass down options.spy to run_icommand, and then to HyREPL, so we can have both. Signed-off-by: Gergely Nagy --- hy/cmdline.py | 6 +++--- tests/test_bin.py | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index b09595f..98f10cd 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -214,8 +214,8 @@ def run_repl(hr=None, spy=False): return 0 -def run_icommand(source): - hr = HyREPL() +def run_icommand(source, spy=False): + hr = HyREPL(spy) hr.runsource(source, filename='', symbol='single') return run_repl(hr) @@ -270,7 +270,7 @@ def cmdline_handler(scriptname, argv): if options.icommand: # User did "hy -i ..." - return run_icommand(options.icommand) + return run_icommand(options.icommand, spy=options.spy) if options.args: if options.args[0] == "-": diff --git a/tests/test_bin.py b/tests/test_bin.py index d18dd31..3d3f3e1 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -76,6 +76,14 @@ def test_bin_hy_icmd(): assert "figlet" in output +def test_bin_hy_icmd_and_spy(): + ret = run_cmd("hy -i \"(+ [] [])\" --spy", "(+ 1 1)") + assert ret[0] == 0 + output = ret[1] + + assert "([] + [])" in output + + def test_bin_hy_missing_file(): ret = run_cmd("hy foobarbaz") assert ret[0] == 2 From 8ababcb905efe14bca982550ae25a644ed6b3f00 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 13 Jan 2014 20:24:12 -0500 Subject: [PATCH 138/202] add clinton --- docs/coreteam.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/coreteam.rst b/docs/coreteam.rst index 02b9c30..982df9d 100644 --- a/docs/coreteam.rst +++ b/docs/coreteam.rst @@ -12,3 +12,4 @@ * `Nicolas Dandrimont `_ * `Bob Tolbert `_ * `Berker Peksag `_ +* `Clinton N. Dreisbach `_ From 5a64f187264eef2ef9a8bcffac6ad3adc0ca5a3d Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 13 Jan 2014 20:37:25 -0500 Subject: [PATCH 139/202] Refactor (loop) in terms of fnr; change to use decorator. --- hy/contrib/loop.hy | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/hy/contrib/loop.hy b/hy/contrib/loop.hy index 91b24d9..2c2690a 100644 --- a/hy/contrib/loop.hy +++ b/hy/contrib/loop.hy @@ -1,6 +1,7 @@ ;;; Hy tail-call optimization ;; ;; Copyright (c) 2014 Clinton Dreisbach +;; Copyright (c) 2014 Paul R. Tagliamonte ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -60,19 +61,19 @@ (let [[new-body (recursive-replace 'recur g!recur-fn body)]] `(do (import [hy.contrib.loop [--trampoline--]]) - (def ~g!recur-fn - (--trampoline-- (fn [~@signature] - ~@new-body))) + (with-decorator + --trampoline-- + (def ~g!recur-fn (fn [~@signature] ~@new-body))) ~g!recur-fn))) + (defmacro defnr [name lambda-list &rest body] (if (not (= (type name) HySymbol)) (macro-error name "defnr takes a name as first argument")) `(setv ~name (fnr ~lambda-list ~@body))) - -(defmacro loop [bindings &rest body] +(defmacro/g! loop [bindings &rest body] ;; Use inside functions like so: ;; (defun factorial [n] ;; (loop [[i n] @@ -84,13 +85,7 @@ ;; If recur is used in a non-tail-call position, None is returned, which ;; causes chaos. Fixing this to detect if recur is in a tail-call position ;; and erroring if not is a giant TODO. - (with-gensyms [recur-fn] - (let [[fnargs (map (fn [x] (first x)) bindings)] - [initargs (map second bindings)] - [new-body (recursive-replace 'recur recur-fn body)]] - `(do - (import [hy.contrib.loop [--trampoline--]]) - (def ~recur-fn - (--trampoline-- (fn [~@fnargs] - ~@new-body))) - (~recur-fn ~@initargs))))) + (let [[fnargs (map (fn [x] (first x)) bindings)] + [initargs (map second bindings)]] + `(do (defnr ~g!recur-fn [~@fnargs] ~@body) + (~g!recur-fn ~@initargs)))) From 25459177ebd380dbf2808a940914bab051439380 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 14 Jan 2014 05:59:44 +0200 Subject: [PATCH 140/202] Move "hy on meth" example to eg/. --- eg/flask/meth_example.hy | 27 +++++++++++++++++++++++++++ hy/contrib/meth.hy | 30 ++---------------------------- 2 files changed, 29 insertions(+), 28 deletions(-) create mode 100644 eg/flask/meth_example.hy diff --git a/eg/flask/meth_example.hy b/eg/flask/meth_example.hy new file mode 100644 index 0000000..4df70d7 --- /dev/null +++ b/eg/flask/meth_example.hy @@ -0,0 +1,27 @@ +;;; Simple Flask application +;;; +;;; Requires to have Flask installed +;;; +;;; You can test it via: +;;; +;;; $ curl 127.0.0.1:5151 +;;; $ curl -X POST 127.0.0.1:5151/post +;;; $ curl -X POST 127.0.0.1:5151/both +;;; $ curl 127.0.0.1:5151/both + +(import [flask [Flask]]) + +(require hy.contrib.meth) + +(setv app (Flask "__main__")) + +(route get-index "/" [] + (str "Hy world!")) + +(post-route post-index "/post" [] + (str "Hy post world!")) + +(route-with-methods both-index "/both" ["GET" "POST"] [] + (str "Hy to both worlds!")) + +(apply app.run [] {"port" 5151}) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index 18203ea..11e637c 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -1,5 +1,5 @@ -;;; Meth -;; based on paultag's meth library to access a Flask based application +;;; Hy on Meth +;;; based on paultag's meth library to access a Flask based application (defmacro route-with-methods [name path methods params &rest code] "Same as route but with an extra methods array to specify HTTP methods" @@ -25,29 +25,3 @@ (defmacro delete-route [name path params &rest code] "Delete request" `(route-with-methods ~name ~path ["DELETE"] ~params ~@code)) - - -;;; Simple example application -;;; Requires to have Flask installed - -;; (import [flask [Flask]]) -;; (setv app (Flask "__main__")) - -;; (require hy.contrib.meth) - -;; (print "setup / with GET") -;; (route get-index "/" [] (str "Hy world!")) - -;; (print "setup /post with POST") -;; (post-route post-index "/post" [] (str "Hy post world!")) - -;; (route-with-methods both-index "/both" [] -;; (str "Hy to both worlds!") ["GET" "POST"]) - -;; (.run app) - -;;; Now you can do: -;;; curl 127.0.0.1:5000 -;;; curl -X POST 127.0.0.1:5000/post -;;; curl -X POST 127.0.0.1:5000/both -;;; curl 127.0.0.1:5000/both From 94e1fc40f7117b95ff19dc1f2e9b0efeb52c3be6 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 14 Jan 2014 06:08:54 +0200 Subject: [PATCH 141/202] Fix a markup error in the apply doc. See http://docs.hylang.org/en/latest/language/api.html#apply for the example. --- docs/language/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 7883e9f..96c4086 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -110,6 +110,7 @@ Usage: `(apply fn-name [args] [kwargs])` Examples: .. code-block:: clj + (defn thunk [] "hy there") From a619295dd7807ec75145572979cdaefda7bdc353 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 13 Jan 2014 21:41:01 +0100 Subject: [PATCH 142/202] bin/hy2py: Add a bunch of command-line options The hy2py tool has been very useful for me, but most of the time, it's only a part of its output that one is interested in. The whole output, with source code, AST and python code together is one big monstrosity. So instead of printing all that, lets have a few handy command-line options to control which part gets printed. By default, only the generated python source is, as that's what the name of the tool implies. Also, don't run it. That's what hy is for. Signed-off-by: Gergely Nagy --- bin/hy2py | 45 ++++++++++++++++++++++++++++++--------------- tests/test_bin.py | 2 +- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/bin/hy2py b/bin/hy2py index 3af8a62..3bcade4 100755 --- a/bin/hy2py +++ b/bin/hy2py @@ -1,24 +1,39 @@ #!/usr/bin/env python from __future__ import print_function +from hy.importer import (import_file_to_ast, import_file_to_hst) -from hy.importer import (import_file_to_ast, import_file_to_module, - import_file_to_hst) +import argparse +import sys import astor.codegen -import sys module_name = "" -hst = import_file_to_hst(sys.argv[1]) -print(str(hst).encode("utf-8")) -print("") -print("") -_ast = import_file_to_ast(sys.argv[1], module_name) -print("") -print("") -print(astor.dump(_ast).encode("utf-8")) -print("") -print("") -print(astor.codegen.to_source(_ast).encode("utf-8")) +parser = argparse.ArgumentParser( + prog="hy2py", + usage="%(prog)s [options] FILE", + formatter_class=argparse.RawDescriptionHelpFormatter) +parser.add_argument("--with-source", "-s", action="store_true", + help="Show the parsed source structure") +parser.add_argument("--with-ast", "-a", action="store_true", + help="Show the generated AST") +parser.add_argument("--without-python", "-np", action="store_true", + help="Do not show the python code generated from the AST") +parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) -import_file_to_module(module_name, sys.argv[1]) +options = parser.parse_args(sys.argv[1:]) + +if options.with_source: + hst = import_file_to_hst(options.args[0]) + print(str(hst).encode("utf-8")) + print() + print() + +_ast = import_file_to_ast(options.args[0], module_name) +if options.with_ast: + print(astor.dump(_ast).encode("utf-8")) + print() + print() + +if not options.without_python: + print(astor.codegen.to_source(_ast).encode("utf-8")) diff --git a/tests/test_bin.py b/tests/test_bin.py index d18dd31..d3ac8a2 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -126,7 +126,7 @@ def test_hy2py(): for f in filenames: if f.endswith(".hy"): i += 1 - ret = run_cmd("bin/hy2py " + os.path.join(dirpath, f)) + ret = run_cmd("bin/hy2py -s -a " + os.path.join(dirpath, f)) assert ret[0] == 0, f assert len(ret[1]) > 1, f assert len(ret[2]) == 0, f From 65e9d5ae7eb33a46bff5c2baba5cb4f126bd04ac Mon Sep 17 00:00:00 2001 From: rubberduckdev Date: Thu, 16 Jan 2014 01:25:43 +0200 Subject: [PATCH 143/202] Missing "of" --- 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 96c4086..b38e38c 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -36,7 +36,7 @@ languages. Builtins ======== -Hy features a number special forms that are used to help generate +Hy features a number of special forms that are used to help generate correct Python AST. The following are "special" forms, which may have behavior that's slightly unexpected in some situations. From 7d5399570ca8a92c9fafbb9c24a4a99a4df49406 Mon Sep 17 00:00:00 2001 From: rubberduckdev Date: Thu, 16 Jan 2014 01:45:34 +0200 Subject: [PATCH 144/202] Missing comma --- 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 96c4086..d0200e1 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -89,7 +89,7 @@ The following code demonstrates this: --- `->>` or `threading tail macro` is similar to `threading macro` but instead of -inserting each expression into the next expression’s first argument place it +inserting each expression into the next expression’s first argument place, it appends it as the last argument. The following code demonstrates this: .. code-block:: clj From 8bfa4f33fc1193941a6bf5c41474f06798b91241 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Wed, 15 Jan 2014 23:55:33 +0100 Subject: [PATCH 145/202] Add set comprehensions, dict comprehensions and generator expressions Closes: #14 (woo, two-digit tickets) --- hy/_compat.py | 1 + hy/compiler.py | 113 +++++++++++++++++++++++++++------ tests/native_tests/language.hy | 37 ++++++++++- 3 files changed, 130 insertions(+), 21 deletions(-) diff --git a/hy/_compat.py b/hy/_compat.py index 37bb023..096282a 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -38,6 +38,7 @@ except ImportError: (x >> 24) & 0xff])) import sys +PY27 = sys.version_info >= (2, 7) PY3 = sys.version_info[0] >= 3 PY33 = sys.version_info >= (3, 3) PY34 = sys.version_info >= (3, 4) diff --git a/hy/compiler.py b/hy/compiler.py index 6e65b30..e9be303 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -39,7 +39,7 @@ from hy.errors import HyCompileError, HyTypeError import hy.macros from hy.macros import require, macroexpand -from hy._compat import str_type, long_type, PY33, PY3, PY34 +from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34 import hy.importer import traceback @@ -1265,41 +1265,114 @@ class HyASTCompiler(object): ctx=ast.Load()) return ret + def _compile_generator_iterables(self, trailers): + """Helper to compile the "trailing" parts of comprehensions: + generators and conditions""" + + generators = trailers.pop(0) + + cond = self.compile(trailers.pop(0)) if trailers != [] else Result() + + gen_it = iter(generators) + paired_gens = zip(gen_it, gen_it) + + gen_res = Result() + gen = [] + for target, iterable in paired_gens: + comp_target = self.compile(target) + target = self._storeize(comp_target) + gen_res += self.compile(iterable) + gen.append(ast.comprehension( + target=target, + iter=gen_res.force_expr, + ifs=[])) + + if cond.expr: + gen[-1].ifs.append(cond.expr) + + return gen_res + cond, gen + @builds("list_comp") @checkargs(min=2, max=3) def compile_list_comprehension(self, expr): # (list-comp expr (target iter) cond?) expr.pop(0) expression = expr.pop(0) - tar_it = iter(expr.pop(0)) - targets = zip(tar_it, tar_it) - cond = self.compile(expr.pop(0)) if expr != [] else Result() - - generator_res = Result() - generators = [] - for target, iterable in targets: - comp_target = self.compile(target) - target = self._storeize(comp_target) - generator_res += self.compile(iterable) - generators.append(ast.comprehension( - target=target, - iter=generator_res.force_expr, - ifs=[])) - - if cond.expr: - generators[-1].ifs.append(cond.expr) + gen_res, gen = self._compile_generator_iterables(expr) compiled_expression = self.compile(expression) - ret = compiled_expression + generator_res + cond + ret = compiled_expression + gen_res ret += ast.ListComp( lineno=expr.start_line, col_offset=expr.start_column, elt=compiled_expression.force_expr, - generators=generators) + generators=gen) return ret + @builds("set_comp") + @checkargs(min=2, max=3) + def compile_set_comprehension(self, expr): + if PY27: + ret = self.compile_list_comprehension(expr) + expr = ret.expr + ret.expr = ast.SetComp( + lineno=expr.lineno, + col_offset=expr.col_offset, + elt=expr.elt, + generators=expr.generators) + + return ret + + expr[0] = HySymbol("list_comp").replace(expr[0]) + expr = HyExpression([HySymbol("set"), expr]).replace(expr) + return self.compile(expr) + + @builds("dict_comp") + @checkargs(min=3, max=4) + def compile_dict_comprehension(self, expr): + if PY27: + expr.pop(0) # dict-comp + key = expr.pop(0) + value = expr.pop(0) + + gen_res, gen = self._compile_generator_iterables(expr) + + compiled_key = self.compile(key) + compiled_value = self.compile(value) + ret = compiled_key + compiled_value + gen_res + ret += ast.DictComp( + lineno=expr.start_line, + col_offset=expr.start_column, + key=compiled_key.force_expr, + value=compiled_value.force_expr, + generators=gen) + + return ret + + # In Python 2.6, turn (dict-comp key value [foo]) into + # (dict (list-comp (, key value) [foo])) + + expr[0] = HySymbol("list_comp").replace(expr[0]) + expr[1:3] = [HyExpression( + [HySymbol(",")] + + expr[1:3] + ).replace(expr[1])] + expr = HyExpression([HySymbol("dict"), expr]).replace(expr) + return self.compile(expr) + + @builds("genexpr") + def compile_genexpr(self, expr): + ret = self.compile_list_comprehension(expr) + expr = ret.expr + ret.expr = ast.GeneratorExp( + lineno=expr.lineno, + col_offset=expr.col_offset, + elt=expr.elt, + generators=expr.generators) + return ret + @builds("apply") @checkargs(min=1, max=3) def compile_apply_expression(self, expr): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 11bf01a..cb75e66 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -525,7 +525,7 @@ (assert (= x 3)))) -(defn test-comprehensions [] +(defn test-list-comprehensions [] "NATIVE: test list comprehensions" (assert (= (list-comp (* x 2) (x (range 2))) [0 2])) (assert (= (list-comp (* x 2) (x (range 4)) (% x 2)) [2 6])) @@ -536,6 +536,41 @@ (assert (= (list-comp j (j [1 2])) [1 2]))) +(defn test-set-comprehensions [] + "NATIVE: test set comprehensions" + (assert (instance? set (set-comp x [x (range 2)]))) + (assert (= (set-comp (* x 2) (x (range 2))) (set [0 2]))) + (assert (= (set-comp (* x 2) (x (range 4)) (% x 2)) (set [2 6]))) + (assert (= (set-comp (* y 2) ((, x y) (.items {"1" 1 "2" 2}))) + (set [2 4]))) + (assert (= (set-comp (, x y) (x (range 2) y (range 2))) + (set [(, 0 0) (, 0 1) (, 1 0) (, 1 1)]))) + (assert (= (set-comp j (j [1 2])) (set [1 2])))) + + +(defn test-dict-comprehensions [] + "NATIVE: test dict comprehensions" + (assert (instance? dict (dict-comp x x [x (range 2)]))) + (assert (= (dict-comp x (* x 2) (x (range 2))) {1 2 0 0})) + (assert (= (dict-comp x (* x 2) (x (range 4)) (% x 2)) {3 6 1 2})) + (assert (= (dict-comp x (* y 2) ((, x y) (.items {"1" 1 "2" 2}))) + {"2" 4 "1" 2})) + (assert (= (dict-comp (, x y) (+ x y) (x (range 2) y (range 2))) + {(, 0 0) 0 (, 1 0) 1 (, 0 1) 1 (, 1 1) 2}))) + + +(defn test-generator-expressions [] + "NATIVE: test generator expressions" + (assert (not (instance? list (genexpr x [x (range 2)])))) + (assert (= (list (genexpr (* x 2) (x (range 2)))) [0 2])) + (assert (= (list (genexpr (* x 2) (x (range 4)) (% x 2))) [2 6])) + (assert (= (list (sorted (genexpr (* y 2) ((, x y) (.items {"1" 1 "2" 2}))))) + [2 4])) + (assert (= (list (genexpr (, x y) (x (range 2) y (range 2)))) + [(, 0 0) (, 0 1) (, 1 0) (, 1 1)])) + (assert (= (list (genexpr j (j [1 2]))) [1 2]))) + + (defn test-defn-order [] "NATIVE: test defn evaluation order" (setv acc []) From 6c15f14ff715389d0116dc3d6529a357ed9c02f7 Mon Sep 17 00:00:00 2001 From: rubberduckdev Date: Thu, 16 Jan 2014 02:13:05 +0200 Subject: [PATCH 146/202] Correcting a typo --- 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 96c4086..0776f40 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -283,7 +283,7 @@ do / progn the `do` and `progn` forms are used to evaluate each of their arguments and return the last one. Return values from every other than the last argument are discarded. It can be used in `lambda` or `list-comp` to perform more complex -logic as show by one of the examples. +logic as shown by one of the examples. Some example usage: From cd66f18e2e7adc79acb9f5e4fd42352b38b7a186 Mon Sep 17 00:00:00 2001 From: rubberduckdev Date: Thu, 16 Jan 2014 02:40:46 +0200 Subject: [PATCH 147/202] Rewording Not sure if the rewording is much better. --- 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 96c4086..902dd92 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -548,7 +548,7 @@ for ------- `for` is used to call a function for each element in a list or vector. -Results are discarded and None is returned instead. Example code iterates over +The results of each call are discarded and the for expression returns None instead. Example code iterates over collection and calls side-effect to each element in the collection: .. code-block:: clj From e50ca4fc078df08c5a8bd9c45e8eb19bc176f4a9 Mon Sep 17 00:00:00 2001 From: Yuval Langer Date: Wed, 15 Jan 2014 18:44:16 +0200 Subject: [PATCH 148/202] Correcting typos. --- AUTHORS | 1 + docs/hacking.rst | 2 +- docs/language/api.rst | 4 ++-- docs/tutorial.rst | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 09c2e2e..0d77a98 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,3 +35,4 @@ * Thom Neale * Tuukka Turto * Vasudev Kamath +* Yuval Langer diff --git a/docs/hacking.rst b/docs/hacking.rst index 0a35898..4329c68 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -97,7 +97,7 @@ core team. Additional review is clearly welcome, but we need a minimum of 2 signoffs for any change. If a core member is sending in a PR, please find 2 core members that don't -include them PR submitter. The idea here is that one can work with the PR +include the PR submitter. The idea here is that one can work with the PR author, and a second acks the entire change set. If the change is adding documentation, feel free to just merge after one diff --git a/docs/language/api.rst b/docs/language/api.rst index 96c4086..7ae94f8 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -430,7 +430,7 @@ defmacro `defmacro` is used to define macros. The general format is `(defmacro [parameters] expr)`. -Following example defines a macro that can be used to swap order of elements in +The following example defines a macro that can be used to swap order of elements in code, allowing the user to write code in infix notation, where operator is in between the operands. @@ -1167,7 +1167,7 @@ yield The generator is iterable and therefore can be used in loops, list comprehensions and other similar constructs. -Especially the second example shows how generators can be used to generate +The function random-numbers shows how generators can be used to generate infinite series without consuming infinite amount of memory. .. code-block:: clj diff --git a/docs/tutorial.rst b/docs/tutorial.rst index c856cf7..819b7cd 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -406,7 +406,7 @@ The same thing in Hy:: ... [3, 2, 1, 4] -See how we use kwapply to handle the fancy pssing? :) +See how we use kwapply to handle the fancy passing? :) There's also a dictionary-style keyword arguments construction that looks like: From 685a1b80fa19e0d91d5e3a7b835587d6cdd2e9a9 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 16 Jan 2014 14:08:18 +0200 Subject: [PATCH 149/202] Silence a "Title underline too short" warning. --- docs/language/core.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index deaf16b..1b31dc7 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -9,7 +9,7 @@ Core Functions .. _is-coll-fn: coll? ----- +----- .. versionadded:: 0.9.13 From ceb615d0102f92fee4703fb888f73f57d9ec5e2c Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 13 Jan 2014 22:19:25 +0100 Subject: [PATCH 150/202] hy/lex/parser.py: Move most of the identifier mangling into a def As a refactoring step, move the identifier mangling done in t_identifier into a separate def. Signed-off-by: Gergely Nagy --- hy/lex/parser.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 72f0d8f..1eee798 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -242,14 +242,19 @@ def t_identifier(p): if obj.startswith("&"): return HyLambdaListKeyword(obj) - if obj.startswith("*") and obj.endswith("*") and obj not in ("*", "**"): - obj = obj[1:-1].upper() + def mangle(p): + if p.startswith("*") and p.endswith("*") and p not in ("*", "**"): + p = p[1:-1].upper() - if "-" in obj and obj != "-": - obj = obj.replace("-", "_") + if "-" in p and p != "-": + p = p.replace("-", "_") - if obj.endswith("?") and obj != "?": - obj = "is_%s" % (obj[:-1]) + if p.endswith("?") and p != "?": + p = "is_%s" % (p[:-1]) + + return p + + obj = mangle(obj) return HySymbol(obj) From e49ad3b3d5c53f236db6c8f747a4c64424104f89 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 13 Jan 2014 22:27:44 +0100 Subject: [PATCH 151/202] tests/lex/test_lex.py: Add a few tests to cover identifier mangling Signed-off-by: Gergely Nagy --- tests/lex/test_lex.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index af9f286..e8b7a9c 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -266,3 +266,31 @@ def test_lex_comment_382(): """Ensure that we can tokenize sources with a comment at the end""" entry = tokenize("foo ;bar\n;baz") assert entry == [HySymbol("foo")] + + +def test_lex_mangling_star(): + """Ensure that mangling starred identifiers works according to plan""" + entry = tokenize("*foo*") + assert entry == [HySymbol("FOO")] + entry = tokenize("*") + assert entry == [HySymbol("*")] + entry = tokenize("*foo") + assert entry == [HySymbol("*foo")] + + +def test_lex_mangling_hyphen(): + """Ensure that hyphens get translated to underscores during mangling""" + entry = tokenize("foo-bar") + assert entry == [HySymbol("foo_bar")] + entry = tokenize("-") + assert entry == [HySymbol("-")] + + +def test_lex_mangling_qmark(): + """Ensure that identifiers ending with a question mark get mangled ok""" + entry = tokenize("foo?") + assert entry == [HySymbol("is_foo")] + entry = tokenize("?") + assert entry == [HySymbol("?")] + entry = tokenize("im?foo") + assert entry == [HySymbol("im?foo")] From 8ef02a54b5f3e6471963eac6c1308f043dfd7e24 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 13 Jan 2014 22:36:53 +0100 Subject: [PATCH 152/202] hy/lex/parser.py: Add support for sub-object mangling With this patch, every identifier is split up along dots, each part gets separately mangled, and then it is all joined back together. This allows for fun stuff like (.foo? (Foo)), and even more contrived examples. Signed-off-by: Gergely Nagy --- hy/lex/parser.py | 2 +- tests/lex/test_lex.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 1eee798..f7264fb 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -254,7 +254,7 @@ def t_identifier(p): return p - obj = mangle(obj) + obj = ".".join([mangle(part) for part in obj.split(".")]) return HySymbol(obj) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index e8b7a9c..93f862c 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -294,3 +294,11 @@ def test_lex_mangling_qmark(): assert entry == [HySymbol("?")] entry = tokenize("im?foo") assert entry == [HySymbol("im?foo")] + entry = tokenize(".foo?") + assert entry == [HySymbol(".is_foo")] + entry = tokenize("foo.bar?") + assert entry == [HySymbol("foo.is_bar")] + entry = tokenize("foo?.bar") + assert entry == [HySymbol("is_foo.bar")] + entry = tokenize(".foo?.bar.baz?") + assert entry == [HySymbol(".is_foo.bar.is_baz")] From a35ecc41bd925888b7c6572b556974d2960ba346 Mon Sep 17 00:00:00 2001 From: Foxboron Date: Tue, 14 Jan 2014 02:38:16 +0100 Subject: [PATCH 153/202] Fix reader macros to actually be macros --- docs/language/readermacros.rst | 34 +++++++++++++++++++---------- hy/compiler.py | 21 ++++++++++++++++-- hy/core/macros.hy | 10 --------- hy/lex/parser.py | 4 ++-- hy/macros.py | 20 ++++++++++++++--- tests/lex/test_lex.py | 2 +- tests/native_tests/reader_macros.hy | 18 +++++++++------ 7 files changed, 73 insertions(+), 36 deletions(-) diff --git a/docs/language/readermacros.rst b/docs/language/readermacros.rst index 0c05d9c..a87b98d 100644 --- a/docs/language/readermacros.rst +++ b/docs/language/readermacros.rst @@ -24,19 +24,28 @@ Syntax => #^1+2+3+4+3+2 1+2+3+4+3+2 +Hy got no literal for tuples. Lets say you dislike `(, ...)` and want something +else. This is a problem reader macros are able to solve in a neat way. + +:: + + => (defreader t [expr] `(, ~@expr)) + => #t(1 2 3) + (1, 2, 3) + +You could even do like clojure, and have a litteral for regular expressions! + +:: + + => (import re) + => (defreader r [expr] `(re.compile ~expr)) + => #r".*" + <_sre.SRE_Pattern object at 0xcv7713ph15#> + Implementation ============== -Hy uses ``defreader`` to define the reader symbol, and ``#`` as the dispatch -character. ``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol -and expression is quoted, and then passed along to the correct function:: - - => (defreader ^ ...) - => #^() - ;=> (dispatch_reader_macro '^ '()) - - ``defreader`` takes a single character as symbol name for the reader macro, anything longer will return an error. Implementation wise, ``defreader`` expands into a lambda covered with a decorator, this decorater saves the @@ -47,14 +56,17 @@ lambda in a dict with its module name and symbol. => (defreader ^ [expr] (print expr)) ;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr))) - -Anything passed along is quoted, thus given to the function defined. +``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol +and expression is passed to the correct function. :: + => #^() + ;=> (dispatch_reader_macro ^ ()) => #^"Hello" "Hello" + .. warning:: Because of a limitation in Hy's lexer and parser, reader macros can't redefine defined syntax such as ``()[]{}``. This will most likely be diff --git a/hy/compiler.py b/hy/compiler.py index e9be303..93e6526 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -38,8 +38,8 @@ from hy.models.dict import HyDict from hy.errors import HyCompileError, HyTypeError import hy.macros -from hy.macros import require, macroexpand from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34 +from hy.macros import require, macroexpand, reader_macroexpand import hy.importer import traceback @@ -1980,7 +1980,7 @@ class HyASTCompiler(object): return ret @builds("defreader") - @checkargs(min=2, max=3) + @checkargs(min=2) def compile_reader(self, expression): expression.pop(0) name = expression.pop(0) @@ -2002,6 +2002,23 @@ class HyASTCompiler(object): return ret + @builds("dispatch_reader_macro") + @checkargs(exact=2) + def compile_dispatch_reader_macro(self, expression): + expression.pop(0) # dispatch-reader-macro + str_char = expression.pop(0) + if not type(str_char) == HyString: + raise HyTypeError( + str_char, + "Trying to expand a reader macro using `{0}' instead " + "of string".format(type(str_char).__name__), + ) + + module = self.module_name + expr = reader_macroexpand(str_char, expression.pop(0), module) + + return self.compile(expr) + @builds("eval_and_compile") def compile_eval_and_compile(self, expression): expression[0] = HySymbol("progn") diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 79d7c12..9219975 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -181,13 +181,3 @@ (setv -args (cdr (car -args)))) `(apply ~-fun [~@-args] (dict (sum ~-okwargs []))))) - - -(defmacro dispatch-reader-macro [char &rest body] - "Dispatch a reader macro based on the character" - (import [hy.macros]) - (setv str_char (get char 1)) - (if (not (in str_char hy.macros._hy_reader_chars)) - (raise (hy.compiler.HyTypeError char (.format "There is no reader macro with the character `{0}`" str_char)))) - `(do (import [hy.macros [_hy_reader]]) - ((get (get _hy_reader --name--) ~char) ~(get body 0)))) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 72f0d8f..aac626a 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -154,8 +154,8 @@ def term_unquote_splice(p): @set_quote_boundaries def hash_reader(p): st = p[0].getstr()[1] - str_object = HyExpression([HySymbol("quote"), HyString(st)]) - expr = HyExpression([HySymbol("quote"), p[1]]) + str_object = HyString(st) + expr = p[1] return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr]) diff --git a/hy/macros.py b/hy/macros.py index d98b328..b155799 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -43,7 +43,6 @@ EXTRA_MACROS = [ _hy_macros = defaultdict(dict) _hy_reader = defaultdict(dict) -_hy_reader_chars = set() def macro(name): @@ -85,8 +84,6 @@ def reader(name): module_name = None _hy_reader[module_name][name] = fn - # Ugly hack to get some error handling - _hy_reader_chars.add(name) return fn return _ @@ -209,3 +206,20 @@ def macroexpand_1(tree, module_name): return ntree return tree + + +def reader_macroexpand(char, tree, module_name): + """Expand the reader macro "char" with argument `tree`.""" + load_macros(module_name) + + if not char in _hy_reader[module_name]: + raise HyTypeError( + char, + "`{0}' is not a reader macro in module '{1}'".format( + char, + module_name, + ), + ) + + expr = _hy_reader[module_name][char](tree) + return _wrap_value(expr).replace(tree) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index af9f286..eb95e80 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -258,7 +258,7 @@ def test_reader_macro(): """Ensure reader macros are handles properly""" entry = tokenize("#^()") assert entry[0][0] == HySymbol("dispatch_reader_macro") - assert entry[0][1] == HyExpression([HySymbol("quote"), HyString("^")]) + assert entry[0][1] == HyString("^") assert len(entry[0]) == 3 diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy index e43220b..84a48a5 100644 --- a/tests/native_tests/reader_macros.hy +++ b/tests/native_tests/reader_macros.hy @@ -23,10 +23,14 @@ (assert (= #+2 3))) -(defn test-reader-macro-compile-docstring [] - "Test if we can compile with a docstring" - (try - (defreader d [] - "Compiles with docstrings") - (except [Exception] - (assert False)))) +(defn test-reader-macros-macros [] + "Test if defreader is actually a macro" + (defreader t [expr] + `(, ~@expr)) + + (def a #t[1 2 3]) + + (assert (= (type a) tuple)) + (assert (= (, 1 2 3) a))) + + From db9b9c68765eea2fb766ba2ed3676514ed0e72e6 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Fri, 17 Jan 2014 03:56:12 +0200 Subject: [PATCH 154/202] Move all contrib.meth tests to tests/native_tests/contrib/. --- tests/__init__.py | 2 +- tests/contrib/__init__.hy | 0 tests/{contrib/test_meth.hy => native_tests/contrib/meth.hy} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 tests/contrib/__init__.hy rename tests/{contrib/test_meth.hy => native_tests/contrib/meth.hy} (97%) diff --git a/tests/__init__.py b/tests/__init__.py index 5508950..2df7cf0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -15,4 +15,4 @@ from .native_tests.reader_macros import * # noqa from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa -from .contrib.test_meth import * # noqa +from .native_tests.contrib.meth import * # noqa diff --git a/tests/contrib/__init__.hy b/tests/contrib/__init__.hy deleted file mode 100644 index e69de29..0000000 diff --git a/tests/contrib/test_meth.hy b/tests/native_tests/contrib/meth.hy similarity index 97% rename from tests/contrib/test_meth.hy rename to tests/native_tests/contrib/meth.hy index 2f13cfd..446bb81 100644 --- a/tests/contrib/test_meth.hy +++ b/tests/native_tests/contrib/meth.hy @@ -3,7 +3,7 @@ (defclass FakeMeth [] "Mocking decorator class" [[rules {}] - [route (fn [self rule &kwargs options] + [route (fn [self rule &kwargs options] (fn [f] (assoc self.rules rule (, f options)) f))]]) From 8ec2719e2d814471e5dfc834d9e1a6c68a20e43e Mon Sep 17 00:00:00 2001 From: f Date: Thu, 16 Jan 2014 11:01:32 +0200 Subject: [PATCH 155/202] Visual and text changes in quickstart page. Kind of beautifying. It also includes transparent versions of Cuddles. --- docs/_static/cuddles-transparent-small.png | Bin 0 -> 38102 bytes docs/_static/cuddles-transparent.png | Bin 0 -> 46963 bytes docs/quickstart.rst | 33 ++++++++++----------- 3 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 docs/_static/cuddles-transparent-small.png create mode 100644 docs/_static/cuddles-transparent.png diff --git a/docs/_static/cuddles-transparent-small.png b/docs/_static/cuddles-transparent-small.png new file mode 100644 index 0000000000000000000000000000000000000000..1570db62db265a1095b17a589b7e688433a5ddd2 GIT binary patch literal 38102 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 literal 0 HcmV?d00001 diff --git a/docs/_static/cuddles-transparent.png b/docs/_static/cuddles-transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..8f16ebdbb4ff15e90ec19ada7cc7769462d04d60 GIT binary patch literal 46963 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 QU^lPzJw2R^U2C<47S1 zAz;Yr14Vv$xG@oq&U-#awPTv+jeDIsAeUExC<)ZGN8I~FO{cH}80pIyB zBiTZcfUM8ocWMfMo=e?gL1OK9%}&bDD_B{fE=psF{1rYu7bjS0iJCn0G+1=BS=PkR zWPKTpCFFe@zCJI!`={z8_Opt$wh;G6>fVn_UQQzIp^(rSJ)6V>&x>*T+gETc90rOa z+t&my$j>;CL^HOktSA!zGBc5O@trzaGuYbD(cQxNB+12EEoDgKiU|zO&fQwtq@L#*fMms|66#&`Sqlei6{3DnVv6h|t6G3sJVL6)GCKV740X>CcALgTbg!5B-|n0@GU zHvF$y37;lIg4J{oPHIZpZp-_8V-8qr`-f1FI53ky$M=IP?9sQK}4zaFFHB>kQ0 zPRNc*Q%qU%_~_|R8Dq{YwBqB6D4MZ*~4c!V{m$lQAZ^Q`*G%`XN5naLb`oKIwu-f>xvjfIsxmh!aN5-A0}Sp60x| zNsgF9M*lXTk&Gs6I3`pOMP<2dhODC;mxPGQX~Aut=(TxAefm9af~r66WKciqu6P7d zw0nJ$B%}(fh7e4&>Z|D-QMe+q8~L7Af0=9%bX;*NAH3K3i3*%oSbc<_sd`uLt{H9R zmYG&%L@eODw4@A5jYOh)B-kjzHYsR}*?+^d{|2~v>Lip5Awd)b<3(}+WvnrOWsu0O zeU7|mI{2?jj~J7>wOYp3v(0pR3~5BlJNv~=E+Z360M8bDa-U}*dct1HEJIy`>=sZo z;lwoH3%0F4s8Xle`B}@+elIQR_ilW`IalTn{%v$_Ka;^L$x1Gv%9xW57W{lBprxW-!q zd#_&XFfaY}Eg-?_v%i561C>YXW&}aoF?z?2DbcwPcK@fJ z2JA@r=~#KD2qLI}v^8_Dq0?CcBt|shfWX)_K0VfDQ#55Ex9hMOPd`*RF&1`ie-&vd z`r^Wh`L z-%|9lT~r?Vddpz+o8#eq5Pm0sS^nPEpW9!w5>5FNLPP?KHJVaKTuI}PI60=8sb`=+ z7+Q_s6}=W>ArkV)BX6W$&V{d$C&NQAGrRw+DF1C;el6dDqjFJvb-X-rnkT{L!oBrx zqZgvQB`isHzPZ39a?MU4N|n0k<=P?V=U@STZ3Ev|Q;v%Oqy^96VRxy~i0WtxZCt9~ z=9-d-z~p*W8C{8iVZ2UocGH+O5t~a2tBe}lzz>uN0w zbotHwC?BfJ&9!PE8Ja!u$7HFRRy3H%_s+$FKIVC!8%S)IcB=9~HP{_vqBW8mEpvDr?A-iciOXC(lSLJi?vEXaZrR&=+@xcF1Ei>( zc4&+dGgNkg$7X`9Zvq5op6}Z}vhqRsPZ)KwidS<|Vz*OS1C5At9ml1Mg$Rd85Y
  • gp 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 literal 0 HcmV?d00001 diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5bdb130..79a9255 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -2,20 +2,20 @@ Quickstart ========== -.. image:: _static/cuddles.png +.. image:: _static/cuddles-transparent-small.png :alt: Karen Rustard's Cuddles -(thanks to Karen Rustad for Cuddles!) +(Thanks to Karen Rustad for Cuddles!) -HOW TO GET HY REAL FAST: +**HOW TO GET HY REAL FAST**: -1. create a `Python virtual environment +1. Create a `Virtual Python Environment `_ -2. activate your Python virtual environment -3. ``pip install hy`` -4. start a REPL with ``hy`` -5. type stuff in the REPL:: +2. Activate your Virtual Python Environment +3. Install `hy from PyPI `_ with ``pip install hy`` +4. Start a REPL with ``hy`` +5. Type stuff in the REPL:: => (print "Hy!") Hy! @@ -25,20 +25,19 @@ HOW TO GET HY REAL FAST: etc -6. hit CTRL-D when you're done +6. Hit CTRL-D when you're done OMG! That's amazing! I want to write a hy program. -7. open up an elite programming editor -8. type:: +7. Open up an elite programming editor and type:: - (print "i was going to code in python syntax, but then i got hy") + (print "I was going to code in python syntax, but then I got hy.") -9. save as ``test_program_of_awesome.hy`` -10. run:: +8. Save as ``awesome.hy`` +9. And run your first Hy program:: - hy test_program_of_awesome.hy + hy awesome.hy -11. take a deep breath so as to not hyperventilate -12. smile villainously and sneak off to your hydeaway and do +10. Take a deep breath so as to not hyperventilate +11. Smile villainously and sneak off to your hydeaway and do unspeakable things From d4bc17461ca6eff97829319d4c491cb1a0c3ef0a Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Fri, 17 Jan 2014 04:04:23 +0200 Subject: [PATCH 156/202] Add @f to AUTHORS. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 0d77a98..775db60 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,3 +36,4 @@ * Tuukka Turto * Vasudev Kamath * Yuval Langer +* Fatih Kadir Akın From ac49818956ab815c3323dac8999246daa13e622c Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 17 Jan 2014 12:02:19 +0100 Subject: [PATCH 157/202] docs: Correct the defmacro syntax in the API docs Signed-off-by: Gergely Nagy --- 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 6cb52ac..76a8918 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -428,7 +428,7 @@ defmacro -------- `defmacro` is used to define macros. The general format is -`(defmacro [parameters] expr)`. +`(defmacro name [parameters] expr)`. The following example defines a macro that can be used to swap order of elements in code, allowing the user to write code in infix notation, where operator is in From 59ca6e4cd05f1e6df0cdb5131617d0044db00247 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 17 Jan 2014 12:05:35 +0100 Subject: [PATCH 158/202] docs: Document (defmacro-alias) Signed-off-by: Gergely Nagy --- docs/language/api.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 76a8918..8d58fc9 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -445,6 +445,32 @@ between the operands. => (infix (1 + 1)) 2 +.. _defmacro-alias: + +defmacro-alias +-------------- + +`defmacro-alias` is used to define macros with multiple names +(aliases). The general format is `(defmacro-alias [names] [parameters] +expr)`. It creates multiple macros with the same parameter list and +body, under the specified list of names. + +The following example defines two macros, both of which allow the user +to write code in infix notation. + +.. code-block:: clj + + => (defmacro-alias [infix infi] [code] + ... (quasiquote ( + ... (unquote (get code 1)) + ... (unquote (get code 0)) + ... (unquote (get code 2))))) + + => (infix (1 + 1)) + 2 + => (infi (1 + 1)) + 2 + .. _defmacro/g!: defmacro/g! From d273ad112f795b79d11879b7189a0600aba84d71 Mon Sep 17 00:00:00 2001 From: rubberduckdev Date: Fri, 17 Jan 2014 18:20:41 +0200 Subject: [PATCH 159/202] Rewording and reformatting plaintext Is this rewording unwieldy? --- docs/language/api.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 902dd92..7e06579 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -548,8 +548,10 @@ for ------- `for` is used to call a function for each element in a list or vector. -The results of each call are discarded and the for expression returns None instead. Example code iterates over -collection and calls side-effect to each element in the collection: +The results of each call are discarded and the for expression returns +None instead. The example code iterates over `collection` and +for each `element` in `collection` calls the `side-effect` +function with `element` as its argument: .. code-block:: clj From 27962fb4412f2cbc1bdd664702d0e1cb2bc0198b Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Fri, 17 Jan 2014 20:28:24 +0100 Subject: [PATCH 160/202] Light copy-editing --- docs/language/readermacros.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/language/readermacros.rst b/docs/language/readermacros.rst index a87b98d..392feb6 100644 --- a/docs/language/readermacros.rst +++ b/docs/language/readermacros.rst @@ -24,7 +24,7 @@ Syntax => #^1+2+3+4+3+2 1+2+3+4+3+2 -Hy got no literal for tuples. Lets say you dislike `(, ...)` and want something +Hy has no literal for tuples. Lets say you dislike `(, ...)` and want something else. This is a problem reader macros are able to solve in a neat way. :: @@ -33,7 +33,7 @@ else. This is a problem reader macros are able to solve in a neat way. => #t(1 2 3) (1, 2, 3) -You could even do like clojure, and have a litteral for regular expressions! +You could even do like clojure, and have a literal for regular expressions! :: From 06d41f7cd05652e8e80f266ff73bbba71a69eb5d Mon Sep 17 00:00:00 2001 From: Jack Hooper Date: Sat, 18 Jan 2014 16:33:40 +1100 Subject: [PATCH 161/202] Corrected chessboard example A chessboard is 8 x 8. Yours was 8 x 9. --- docs/tutorial.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 819b7cd..f00fd98 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -363,7 +363,7 @@ In hy, you could do these like: (list-comp (, x y) - (x (range 9) + (x (range 8) y "ABCDEFGH")) ; [(0, 'A'), (0, 'B'), (0, 'C'), (0, 'D'), (0, 'E'), (0, 'F'), (0, 'G'), (0, 'H'), @@ -374,7 +374,6 @@ In hy, you could do these like: ; (5, 'A'), (5, 'B'), (5, 'C'), (5, 'D'), (5, 'E'), (5, 'F'), (5, 'G'), (5, 'H'), ; (6, 'A'), (6, 'B'), (6, 'C'), (6, 'D'), (6, 'E'), (6, 'F'), (6, 'G'), (6, 'H'), ; (7, 'A'), (7, 'B'), (7, 'C'), (7, 'D'), (7, 'E'), (7, 'F'), (7, 'G'), (7, 'H'), - ; (8, 'A'), (8, 'B'), (8, 'C'), (8, 'D'), (8, 'E'), (8, 'F'), (8, 'G'), (8, 'H')] Python has support for various fancy argument and keyword arguments. From d028b8a7b23f138358a0ca863b7420aab7389250 Mon Sep 17 00:00:00 2001 From: Jack Hooper Date: Sat, 18 Jan 2014 22:22:33 +1100 Subject: [PATCH 162/202] Added closing bracket ']' --- docs/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index f00fd98..ceae5a8 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -373,7 +373,7 @@ In hy, you could do these like: ; (4, 'A'), (4, 'B'), (4, 'C'), (4, 'D'), (4, 'E'), (4, 'F'), (4, 'G'), (4, 'H'), ; (5, 'A'), (5, 'B'), (5, 'C'), (5, 'D'), (5, 'E'), (5, 'F'), (5, 'G'), (5, 'H'), ; (6, 'A'), (6, 'B'), (6, 'C'), (6, 'D'), (6, 'E'), (6, 'F'), (6, 'G'), (6, 'H'), - ; (7, 'A'), (7, 'B'), (7, 'C'), (7, 'D'), (7, 'E'), (7, 'F'), (7, 'G'), (7, 'H'), + ; (7, 'A'), (7, 'B'), (7, 'C'), (7, 'D'), (7, 'E'), (7, 'F'), (7, 'G'), (7, 'H')] Python has support for various fancy argument and keyword arguments. From 21709f0199424df5b400c24ef5612219dec3ba13 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Fri, 17 Jan 2014 12:47:05 +0100 Subject: [PATCH 163/202] hy/core/macros.hy: Add defn-alias / defun-alias In the same vein as defmacro-alias, this implements defn-alias / defun-alias, which does essentially the same thing as defmacro-alias, but for functions. Signed-off-by: Gergely Nagy --- docs/language/api.rst | 25 +++++++++++++++++++++++++ hy/core/macros.hy | 11 +++++++++++ tests/native_tests/native_macros.hy | 8 ++++++++ 3 files changed, 44 insertions(+) diff --git a/docs/language/api.rst b/docs/language/api.rst index 7a3639c..423c50b 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -352,6 +352,8 @@ below: Meow +.. _defn: + defn / defun ------------ @@ -422,6 +424,29 @@ Parameters may have following keywords in front of them: => (zig-zag-sum 1 2 3 4 5 6) -3 +.. _defn-alias / defun-alias: + +defn-alias / defun-alias +------------------------ + +.. versionadded:: 0.9.13 + +The `defn-alias` and `defun-alias` macros are much like `defn`_ above, +with the difference that instead of defining a function with a single +name, these can also define aliases. Other than taking a list of +symbols for function names as the first parameter, `defn-alias` and +`defun-alias` have no other differences compared to `defn` and +`defun`. + +.. code-block:: clj + + => (defn-alias [main-name alias] [] + ... (print "Hello!")) + => (main-name) + "Hello!" + => (alias) + "Hello!" + .. _defmacro: defmacro diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 9219975..d00ccce 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -181,3 +181,14 @@ (setv -args (cdr (car -args)))) `(apply ~-fun [~@-args] (dict (sum ~-okwargs []))))) + + +(defmacro-alias [defn-alias defun-alias] [names lambda-list &rest body] + "define one function with several names" + (let [[main (first names)] + [aliases (rest names)]] + (setv ret `(do (defn ~main ~lambda-list ~@body))) + (for* [name aliases] + (.append ret + `(setv ~name ~main))) + ret)) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 0841b1e..cf25118 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -176,3 +176,11 @@ (assert (in ":res_" s1)) (assert (in ":res_" s2)) (assert (not (= s1 s2)))) + +(defn test-defn-alias [] + (defn-alias [tda-main tda-a1 tda-a2] [] :bazinga) + (defun-alias [tda-main tda-a1 tda-a2] [] :bazinga) + (assert (= (tda-main) :bazinga)) + (assert (= (tda-a1) :bazinga)) + (assert (= (tda-a2) :bazinga)) + (assert (= tda-main tda-a1 tda-a2))) From e90b24b73ad11a86ced252b38e0619b313cbe0d8 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sat, 18 Jan 2014 16:27:26 +0100 Subject: [PATCH 164/202] hy/core/macros.hy: Add an (if-not) macro Sometimes it is better to start with the false condition, sometimes that makes the code clearer. For that, the (if-not) macro, which simply reverses the order of the condition blocks, can be of great use. Signed-off-by: Gergely Nagy --- docs/language/api.rst | 13 ++++++++++--- hy/core/macros.hy | 7 +++++++ tests/native_tests/native_macros.hy | 9 +++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 7a3639c..349676a 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -679,13 +679,16 @@ would thrown a `NameError`. (set-a 5) (print-a) -if --- +if / if-not +----------- the `if` form is used to conditionally select code to be executed. It has to contain the condition block and the block to be executed if the condition evaluates `True`. Optionally it may contain a block that is executed in case -the evaluation of the condition is `False`. +the evaluation of the condition is `False`. The `if-not` form (*new in +0.9.13*) is similar, but the first block after the test will be +executed when the test fails, while the other, conditional one, when +the test succeeds - opposite of the order of the `if` form. Example usage: @@ -695,6 +698,10 @@ Example usage: (print "lets go shopping") (print "lets go and work")) + (if-not (money-left? account) + (print "lets go and work") + (print "lets go shopping")) + Truth values of Python objects are respected. Values `None`, `False`, zero of any numeric type, empty sequence and empty dictionary are considered `False`. Everything else is considered `True`. diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 9219975..fcaa59a 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -138,6 +138,13 @@ ret) +(defmacro if-not [test not-branch &optional [yes-branch nil]] + "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))) + + (defmacro when [test &rest body] "Execute `body` when `test` is true" `(if ~test (do ~@body))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 0841b1e..b74c89e 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -176,3 +176,12 @@ (assert (in ":res_" s1)) (assert (in ":res_" s2)) (assert (not (= s1 s2)))) + +(defn test-if-not [] + (assert (= (if-not True :yes :no) + :no)) + (assert (= (if-not False :yes :no) + :yes)) + (assert (nil? (if-not True :yes))) + (assert (= (if-not False :yes) + :yes))) From 3bad6c14ee06d398ab48e3c37f986d950bcbaed4 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sat, 18 Jan 2014 22:57:29 +0100 Subject: [PATCH 165/202] hy/core/macros.hy: Reimplement (unless) in terms of (if-not) Signed-off-by: Gergely Nagy --- hy/core/macros.hy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index fcaa59a..bdeb288 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -152,7 +152,7 @@ (defmacro unless [test &rest body] "Execute `body` when `test` is false" - `(if ~test None (do ~@body))) + `(if-not ~test (do ~@body))) (defmacro yield-from [iterable] From ca40113e37ee5ba16b6cdfe6dca8242b6b9e096f Mon Sep 17 00:00:00 2001 From: Jack Hooper Date: Mon, 20 Jan 2014 14:29:57 +1100 Subject: [PATCH 166/202] Corrected --spy cmdoption (I think) For the --spy commmand line option, it currently says '''Print equivalent Hy code...'''. Now, I haven't actually gotten around to installing Hy on my computer yet, so I haven't had a chance to test this out to make sure, but from the looks of the code example, it looks as though it is printing out the equivalent Python code of the executed Hy code, not the other way around. This would certainly make more sense. As such, I have changed the word 'Hy' to 'Python' so that the documentation more accurately reflects what (one assumes) is going on. --- docs/language/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 875efa5..317e233 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -20,7 +20,7 @@ Command line options .. cmdoption:: --spy - Print equivalent Hy code before executing. For example:: + Print equivalent Python code before executing. For example:: => (defn salutationsnm [name] (print (+ "Hy " name "!"))) def salutationsnm(name): From 4d95cc48a5f5353a772b999bc4eef149162230f0 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Mon, 20 Jan 2014 21:17:22 +0530 Subject: [PATCH 167/202] Add @jackhooper to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 775db60..e3a234e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -37,3 +37,4 @@ * Vasudev Kamath * Yuval Langer * Fatih Kadir Akın +* Jack Hooper \ No newline at end of file From e71631f48fc77453e85266eabf3b243c561a7f3f Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Mon, 20 Jan 2014 22:46:17 +0530 Subject: [PATCH 168/202] Wrong email id, I messed up that commit --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index e3a234e..5eeaf05 100644 --- a/AUTHORS +++ b/AUTHORS @@ -37,4 +37,4 @@ * Vasudev Kamath * Yuval Langer * Fatih Kadir Akın -* Jack Hooper \ No newline at end of file +* Jack Hooper \ No newline at end of file From 10f1f24a1817d0dd74d2f2677ef929feca921457 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Wed, 22 Jan 2014 00:49:49 +0530 Subject: [PATCH 169/202] Adding a simple `identity' function * hy/core/language.hy: Adding a simple `identity` function that returns the argument supplied to it * docs/language/core.rst: Updated docs with identity function --- docs/language/core.rst | 18 ++++++++++++++++++ hy/core/language.hy | 17 ++++++++++------- tests/native_tests/core.hy | 6 ++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index 1b31dc7..9007348 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -137,6 +137,24 @@ Raises ``TypeError`` if ``(not (numeric? x))``. True +.. _identity-fn: + +identity +-------- + +Usage: ``(identity x)`` + +Returns argument supplied to the function + +.. code-block:: clojure + + => (identity 4) + 4 + + => (list (map identity [1 2 3 4])) + [1 2 3 4] + + .. _inc-fn: inc diff --git a/hy/core/language.hy b/hy/core/language.hy index 65c8b17..2578924 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -157,6 +157,10 @@ "Return first item from `coll`" (get coll 0)) +(defn identity [x] + "Returns the argument unchanged" + x) + (defn inc [n] "Increment n by 1" (_numeric-check n) @@ -310,10 +314,9 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name coll? cycle dec distinct - disassemble drop drop-while empty? even? filter flatten - float? gensym inc instance? integer integer? iterable? - iterate iterator? macroexpand macroexpand-1 neg? nil? - none? nth numeric? odd? pos? remove repeat repeatedly - second string string? take take-nth take-while zero? - first rest]) +(def *exports* '[calling-module-name coll? cycle dec distinct disassemble + drop drop-while empty? even? first filter flatten float? + gensym identity inc instance? integer integer? iterable? + iterate iterator? macroexpand macroexpand-1 neg? nil? + none? nth numeric? odd? pos? remove repeat repeatedly + rest second string string? take take-nth take-while zero?]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index db2c9e5..1b2bbbc 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -181,6 +181,12 @@ (assert (not (= s2 s3))) (assert (not (= (str s2) (str s3))))) +(defn test-identity [] + "NATIVE: testing the identity function" + (assert (= 4 (identity 4))) + (assert (= "hy" (identity "hy"))) + (assert (= [1 2] (identity [1 2])))) + (defn test-inc [] "NATIVE: testing the inc function" (assert-equal 3 (inc 2)) From 54757b83165d95bd19ceda0a67e952374546e398 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Tue, 25 Jun 2013 20:08:12 +0200 Subject: [PATCH 170/202] Slicing a HyList makes the same kind of object again --- hy/models/list.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hy/models/list.py b/hy/models/list.py index d2d4da7..e45e40b 100644 --- a/hy/models/list.py +++ b/hy/models/list.py @@ -36,5 +36,16 @@ class HyList(HyObject, list): def __add__(self, other): return self.__class__(super(HyList, self).__add__(other)) + def __getslice__(self, start, end): + return self.__class__(super(HyList, self).__getslice__(start, end)) + + def __getitem__(self, item): + ret = super(HyList, self).__getitem__(item) + + if isinstance(item, slice): + return self.__class__(ret) + + return ret + def __repr__(self): return "[%s]" % (" ".join([repr(x) for x in self])) From bb2b868aaf19857e85eddb1b1500e417a1a4ccbf Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 23 Jan 2014 20:03:01 +0100 Subject: [PATCH 171/202] Make empty macroexpansions do the right thing --- hy/compiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 93e6526..28fb57c 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1645,15 +1645,15 @@ class HyASTCompiler(object): @builds(HyExpression) def compile_expression(self, expression): - if expression == []: - return self.compile_list(expression) - # Perform macro expansions expression = macroexpand(expression, self.module_name) if not isinstance(expression, HyExpression): # Go through compile again if the type changed. return self.compile(expression) + if expression == []: + return self.compile_list(expression) + fn = expression[0] func = None if isinstance(fn, HyKeyword): From 52144820cadc47c8fe94f8de2530260fa8b70b49 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Thu, 16 May 2013 18:59:20 +0200 Subject: [PATCH 172/202] Add a cons object and related mechanisms Closes: #183 --- docs/language/core.rst | 66 +++++++++++++++++++++- docs/language/internals.rst | 32 +++++++++++ hy/__init__.py | 1 + hy/compiler.py | 23 ++++++++ hy/core/language.hy | 29 ++++++++-- hy/lex/parser.py | 32 +++++++++++ hy/models/cons.py | 108 ++++++++++++++++++++++++++++++++++++ tests/__init__.py | 1 + tests/compilers/test_ast.py | 5 ++ tests/lex/test_lex.py | 32 +++++++++++ tests/models/test_cons.py | 56 +++++++++++++++++++ tests/models/test_list.py | 13 +++++ tests/native_tests/cons.hy | 63 +++++++++++++++++++++ 13 files changed, 453 insertions(+), 8 deletions(-) create mode 100644 hy/models/cons.py create mode 100644 tests/models/test_cons.py create mode 100644 tests/native_tests/cons.hy diff --git a/docs/language/core.rst b/docs/language/core.rst index 9007348..57f00b2 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -29,6 +29,48 @@ Returns true if argument is iterable and not a string. False +cons +---- + +.. versionadded:: 0.9.13 + +Usage: ``(cons a b)`` + +Returns a fresh :ref:`cons cell ` with car `a` and cdr `b`. + +.. code-block:: clojure + + => (setv a (cons 'hd 'tl)) + + => (= 'hd (car a)) + True + + => (= 'tl (cdr a)) + True + + +cons? +----- + +.. versionadded:: 0.9.13 + +Usage: ``(cons? foo)`` + +Checks whether ``foo`` is a :ref:`cons cell `. + +.. code-block:: clojure + + => (setv a (cons 'hd 'tl)) + + => (cons? a) + True + + => (cons? nil) + False + + => (cons? [1 2 3]) + False + .. _dec-fn: dec @@ -284,6 +326,28 @@ Contrast with :ref:`iterable?-fn`. => (iterator? (iter {:a 1 :b 2 :c 3})) True +list* +----- + +Usage: ``(list* head &rest tail)`` + +Generate a chain of nested cons cells (a dotted list) containing the +arguments. If the argument list only has one element, return it. + +.. code-block:: clojure + + => (list* 1 2 3 4) + (1 2 3 . 4) + + => (list* 1 2 3 [4]) + [1, 2, 3, 4] + + => (list* 1) + 1 + + => (cons? (list* 1 2 3 4)) + True + .. _macroexpand-fn: macroexpand @@ -863,5 +927,3 @@ Return an iterator from ``coll`` as long as predicate, ``pred`` returns True. => (list (take-while neg? [ 1 2 3 -4 5])) [] - - diff --git a/docs/language/internals.rst b/docs/language/internals.rst index a8de44e..f294ca6 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -172,6 +172,38 @@ keywords, that is keywords used by the language definition inside function signatures. Lambda-list keywords are symbols starting with a ``&``. The class inherits :ref:`HyString` +.. _hycons: + +Cons Cells +========== + +``hy.models.cons.HyCons`` is a representation of Python-friendly `cons +cells`_. Cons cells are especially useful to mimic features of "usual" +LISP variants such as Scheme or Common Lisp. + +.. _cons cells: http://en.wikipedia.org/wiki/Cons + +A cons cell is a 2-item object, containing a ``car`` (head) and a +``cdr`` (tail). In some Lisp variants, the cons cell is the fundamental +building block, and S-expressions are actually represented as linked +lists of cons cells. This is not the case in Hy, as the usual +expressions are made of Python lists wrapped in a +``HyExpression``. However, the ``HyCons`` mimicks the behavior of +"usual" Lisp variants thusly: + + - ``(cons something nil)`` is ``(HyExpression [something])`` + - ``(cons something some-list)`` is ``((type some-list) (+ [something] + some-list))`` (if ``some-list`` inherits from ``list``). + - ``(get (cons a b) 0)`` is ``a`` + - ``(slice (cons a b) 1)`` is ``b`` + +Hy supports a dotted-list syntax, where ``'(a . b)`` means ``(cons 'a +'b)`` and ``'(a b . c)`` means ``(cons 'a (cons 'b 'c))``. If the +compiler encounters a cons cell at the top level, it raises a +compilation error. + +``HyCons`` wraps the passed arguments (car and cdr) in Hy types, to ease +the manipulation of cons cells in a macro context. Hy Internal Theory ================== diff --git a/hy/__init__.py b/hy/__init__.py index fd95f90..f3a26a1 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -32,6 +32,7 @@ from hy.models.symbol import HySymbol # NOQA from hy.models.float import HyFloat # NOQA from hy.models.dict import HyDict # NOQA from hy.models.list import HyList # NOQA +from hy.models.cons import HyCons # NOQA import hy.importer # NOQA diff --git a/hy/compiler.py b/hy/compiler.py index 28fb57c..622b182 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -34,6 +34,7 @@ from hy.models.symbol import HySymbol from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict +from hy.models.cons import HyCons from hy.errors import HyCompileError, HyTypeError @@ -597,6 +598,24 @@ class HyASTCompiler(object): return imports, HyExpression([HySymbol(name), contents]).replace(form), False + elif isinstance(form, HyCons): + ret = HyExpression([HySymbol(name)]) + nimport, contents, splice = self._render_quoted_form(form.car, + level) + if splice: + raise HyTypeError(form, "Can't splice dotted lists yet") + imports.update(nimport) + ret.append(contents) + + nimport, contents, splice = self._render_quoted_form(form.cdr, + level) + if splice: + raise HyTypeError(form, "Can't splice the cdr of a cons") + imports.update(nimport) + ret.append(contents) + + return imports, ret.replace(form), False + elif isinstance(form, (HySymbol, HyLambdaListKeyword)): return imports, HyExpression([HySymbol(name), HyString(form)]).replace(form), False @@ -2036,6 +2055,10 @@ class HyASTCompiler(object): self.module_name) return Result() + @builds(HyCons) + def compile_cons(self, cons): + raise HyTypeError(cons, "Can't compile a top-level cons cell") + @builds(HyInteger) def compile_integer(self, number): return ast.Num(n=long_type(number), diff --git a/hy/core/language.hy b/hy/core/language.hy index 2578924..026acb5 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -25,6 +25,8 @@ (import [hy._compat [long-type]]) ; long for python2, int for python3 +(import [hy.models.cons [HyCons]]) + (defn _numeric-check [x] (if (not (numeric? x)) @@ -34,6 +36,14 @@ "Checks whether item is a collection" (and (iterable? coll) (not (string? coll)))) +(defn cons [a b] + "Return a fresh cons cell with car = a and cdr = b" + (HyCons a b)) + +(defn cons? [c] + "Check whether c can be used as a cons object" + (instance? HyCons c)) + (defn cycle [coll] "Yield an infinite repetition of the items in coll" (setv seen []) @@ -193,6 +203,12 @@ (try (= x (iter x)) (catch [TypeError] false))) +(defn list* [hd &rest tl] + "Return a dotted list construed from the elements of the argument" + (if (not tl) + hd + (cons hd (apply list* tl)))) + (defn macroexpand [form] "Return the full macro expansion of form" (import hy.macros) @@ -314,9 +330,10 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name coll? cycle dec distinct disassemble - drop drop-while empty? even? first filter flatten float? - gensym identity inc instance? integer integer? iterable? - iterate iterator? macroexpand macroexpand-1 neg? nil? - none? nth numeric? odd? pos? remove repeat repeatedly - rest second string string? take take-nth take-while zero?]) +(def *exports* '[calling-module-name coll? cons cons? cycle dec distinct + disassemble drop drop-while empty? even? first filter + flatten float? gensym identity inc instance? integer + integer? iterable? iterate iterator? list* macroexpand + macroexpand-1 neg? nil? none? nth numeric? odd? pos? + remove repeat repeatedly rest second string string? + take take-nth take-while zero?]) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 2d47201..0f3a96a 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -24,6 +24,7 @@ from functools import wraps from rply import ParserGenerator from hy.models.complex import HyComplex +from hy.models.cons import HyCons from hy.models.dict import HyDict from hy.models.expression import HyExpression from hy.models.float import HyFloat @@ -95,9 +96,40 @@ def real_main_empty(p): return [] +def reject_spurious_dots(*items): + "Reject the spurious dots from items" + for list in items: + for tok in list: + if tok == "." and type(tok) == HySymbol: + raise LexException("Malformed dotted list", + tok.start_line, tok.start_column) + + @pg.production("paren : LPAREN list_contents RPAREN") @set_boundaries def paren(p): + cont = p[1] + + # Dotted lists are expressions of the form + # (a b c . d) + # that evaluate to nested cons cells of the form + # (a . (b . (c . d))) + if len(cont) >= 3 and isinstance(cont[-2], HySymbol) and cont[-2] == ".": + + reject_spurious_dots(cont[:-2], cont[-1:]) + + if len(cont) == 3: + # Two-item dotted list: return the cons cell directly + return HyCons(cont[0], cont[2]) + else: + # Return a nested cons cell + return HyCons(cont[0], paren([p[0], cont[1:], p[2]])) + + # Warn preemptively on a malformed dotted list. + # Only check for dots after the first item to allow for a potential + # attribute accessor shorthand + reject_spurious_dots(cont[1:]) + return HyExpression(p[1]) diff --git a/hy/models/cons.py b/hy/models/cons.py new file mode 100644 index 0000000..180c5cb --- /dev/null +++ b/hy/models/cons.py @@ -0,0 +1,108 @@ +# 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"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from hy.macros import _wrap_value +from hy.models import HyObject +from hy.models.expression import HyExpression +from hy.models.symbol import HySymbol + + +class HyCons(HyObject): + """ + HyCons: a cons object. + + Building a HyCons of something and a HyList really builds a HyList + """ + + __slots__ = ["car", "cdr"] + + def __new__(cls, car, cdr): + if isinstance(cdr, list): + + # Keep unquotes in the cdr of conses + if type(cdr) == HyExpression: + if len(cdr) > 0 and type(cdr[0]) == HySymbol: + if cdr[0] in ("unquote", "unquote_splice"): + return super(HyCons, cls).__new__(cls) + + return cdr.__class__([_wrap_value(car)] + cdr) + + elif cdr is None: + return HyExpression([_wrap_value(car)]) + + else: + return super(HyCons, cls).__new__(cls) + + def __init__(self, car, cdr): + self.car = _wrap_value(car) + self.cdr = _wrap_value(cdr) + + def __getitem__(self, n): + if n == 0: + return self.car + if n == slice(1, None): + return self.cdr + + raise IndexError( + "Can only get the car ([0]) or the cdr ([1:]) of a HyCons") + + def __setitem__(self, n, new): + if n == 0: + self.car = new + return + if n == slice(1, None): + self.cdr = new + return + + raise IndexError( + "Can only set the car ([0]) or the cdr ([1:]) of a HyCons") + + def __iter__(self): + yield self.car + try: + iterator = (i for i in self.cdr) + except TypeError: + if self.cdr is not None: + yield self.cdr + raise TypeError("Iteration on malformed cons") + else: + for i in iterator: + yield i + + def replace(self, other): + if self.car is not None: + self.car.replace(other) + if self.cdr is not None: + self.cdr.replace(other) + + HyObject.replace(self, other) + + def __repr__(self): + if isinstance(self.cdr, self.__class__): + return "(%s %s)" % (repr(self.car), repr(self.cdr)[1:-1]) + else: + return "(%s . %s)" % (repr(self.car), repr(self.cdr)) + + def __eq__(self, other): + return ( + isinstance(other, self.__class__) and + self.car == other.car and + self.cdr == other.cdr + ) diff --git a/tests/__init__.py b/tests/__init__.py index 2df7cf0..adcb583 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -2,6 +2,7 @@ import hy # noqa +from .native_tests.cons import * # noqa from .native_tests.defclass import * # noqa from .native_tests.math import * # noqa from .native_tests.native_macros import * # noqa diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index e13cf53..f4fdf41 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -475,3 +475,8 @@ def test_attribute_access(): cant_compile("(. foo bar :baz [0] quux [frob])") cant_compile("(. foo bar baz (0) quux [frob])") cant_compile("(. foo bar baz [0] quux {frob})") + + +def test_cons_correct(): + """Ensure cons gets compiled correctly""" + can_compile("(cons a b)") diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index b937aa7..7ab30e5 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2014 Nicolas Dandrimont # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -26,6 +27,8 @@ from hy.models.complex import HyComplex from hy.models.symbol import HySymbol from hy.models.string import HyString from hy.models.dict import HyDict +from hy.models.list import HyList +from hy.models.cons import HyCons from hy.lex import LexException, PrematureEndOfInput, tokenize @@ -302,3 +305,32 @@ def test_lex_mangling_qmark(): assert entry == [HySymbol("is_foo.bar")] entry = tokenize(".foo?.bar.baz?") assert entry == [HySymbol(".is_foo.bar.is_baz")] + + +def test_simple_cons(): + """Check that cons gets tokenized correctly""" + entry = tokenize("(a . b)")[0] + assert entry == HyCons(HySymbol("a"), HySymbol("b")) + + +def test_dotted_list(): + """Check that dotted lists get tokenized correctly""" + entry = tokenize("(a b c . (d . e))")[0] + assert entry == HyCons(HySymbol("a"), + HyCons(HySymbol("b"), + HyCons(HySymbol("c"), + HyCons(HySymbol("d"), + HySymbol("e"))))) + + +def test_cons_list(): + """Check that cons of something and a list gets tokenized as a list""" + entry = tokenize("(a . [])")[0] + assert entry == HyList([HySymbol("a")]) + assert type(entry) == HyList + entry = tokenize("(a . ())")[0] + assert entry == HyExpression([HySymbol("a")]) + assert type(entry) == HyExpression + entry = tokenize("(a b . {})")[0] + assert entry == HyDict([HySymbol("a"), HySymbol("b")]) + assert type(entry) == HyDict diff --git a/tests/models/test_cons.py b/tests/models/test_cons.py new file mode 100644 index 0000000..51b3b10 --- /dev/null +++ b/tests/models/test_cons.py @@ -0,0 +1,56 @@ +# 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"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from hy.models.cons import HyCons + + +def test_cons_slicing(): + """Check that cons slicing works as expected""" + cons = HyCons("car", "cdr") + assert cons[0] == "car" + assert cons[1:] == "cdr" + try: + cons[:] + assert True is False + except IndexError: + pass + + try: + cons[1] + assert True is False + except IndexError: + pass + + +def test_cons_replacing(): + """Check that assigning to a cons works as expected""" + cons = HyCons("foo", "bar") + cons[0] = "car" + + assert cons == HyCons("car", "bar") + + cons[1:] = "cdr" + assert cons == HyCons("car", "cdr") + + try: + cons[:] = "foo" + assert True is False + except IndexError: + pass diff --git a/tests/models/test_list.py b/tests/models/test_list.py index f403802..cc49c70 100644 --- a/tests/models/test_list.py +++ b/tests/models/test_list.py @@ -2,8 +2,21 @@ from hy.models.list import HyList def test_list_add(): + """Check that adding two HyLists generates a HyList""" a = HyList([1, 2, 3]) b = HyList([3, 4, 5]) c = a + b assert c == [1, 2, 3, 3, 4, 5] assert c.__class__ == HyList + + +def test_list_slice(): + """Check that slicing a HyList produces a HyList""" + a = HyList([1, 2, 3, 4]) + sl1 = a[1:] + sl5 = a[5:] + + assert type(sl1) == HyList + assert sl1 == HyList([2, 3, 4]) + assert type(sl5) == HyList + assert sl5 == HyList([]) diff --git a/tests/native_tests/cons.hy b/tests/native_tests/cons.hy new file mode 100644 index 0000000..5bd8fbd --- /dev/null +++ b/tests/native_tests/cons.hy @@ -0,0 +1,63 @@ +(defn test-cons-mutability [] + "Test the mutability of conses" + (setv tree (cons (cons 1 2) (cons 2 3))) + (setv (car tree) "foo") + (assert (= tree (cons "foo" (cons 2 3)))) + (setv (cdr tree) "bar") + (assert (= tree (cons "foo" "bar")))) + + +(defn test-cons-quoting [] + "Test quoting of conses" + (assert (= (cons 1 2) (quote (1 . 2)))) + (assert (= (quote foo) (car (quote (foo . bar))))) + (assert (= (quote bar) (cdr (quote (foo . bar)))))) + + +(defn test-cons-behavior [] + "NATIVE: test the behavior of cons is consistent" + (defn t= [a b] + (and (= a b) (= (type a) (type b)))) + (assert (t= (cons 1 2) '(1 . 2))) + (assert (t= (cons 1 nil) '(1))) + (assert (t= (cons nil 2) '(nil . 2))) + (assert (t= (cons 1 []) [1])) + (setv tree (cons (cons 1 2) (cons 2 3))) + (assert (t= (car tree) (cons 1 2))) + (assert (t= (cdr tree) (cons 2 3)))) + + +(defn test-cons-iteration [] + "NATIVE: test the iteration behavior of cons" + (setv x '(0 1 2 3 4 . 5)) + (setv it (iter x)) + (for* [i (range 6)] + (assert (= i (next it)))) + (assert + (= 'success + (try + (do + (next it) + 'failurenext) + (except [e TypeError] (if (= e.args (, "Iteration on malformed cons")) + 'success + 'failureexc)) + (except [e Exception] 'failureexc2))))) + + +(defn test-cons? [] + "NATIVE: test behavior of cons?" + (assert (cons? (cons 1 2))) + (assert (cons? '(1 . 2))) + (assert (cons? '(1 2 3 . 4))) + (assert (cons? (list* 1 2 3))) + (assert (not (cons? (cons 1 [2])))) + (assert (not (cons? (list* 1 nil))))) + + +(defn test-list* [] + "NATIVE: test behavior of list*" + (assert (= 1 (list* 1))) + (assert (= (cons 1 2) (list* 1 2))) + (assert (= (cons 1 (cons 2 3)) (list* 1 2 3))) + (assert (= '(1 2 3 4 . 5) (list* 1 2 3 4 5)))) From d84242630889a0ba4e66655ffce6516c8a10a24d Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 25 Jan 2014 21:01:16 +0200 Subject: [PATCH 173/202] Reorganize documentation. --- docs/_static/hy-logo-small.png | Bin 0 -> 19221 bytes docs/index.rst | 49 +++++++++++---------------------- docs/language/index.rst | 2 +- 3 files changed, 17 insertions(+), 34 deletions(-) create mode 100644 docs/_static/hy-logo-small.png diff --git a/docs/_static/hy-logo-small.png b/docs/_static/hy-logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a3c9cfe66b9db3fd7c227745dcdc36a5ef9a38 GIT binary patch literal 19221 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%0A#X0rFy| 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 literal 0 HcmV?d00001 diff --git a/docs/index.rst b/docs/index.rst index 6080c8f..1fc685e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,43 +1,26 @@ Welcome to Hy's documentation! ============================== - - - -.. image:: _static/hy-logo-full.png - :alt: Hy logo +.. image:: _static/hy-logo-small.png + :alt: Hy :align: left -Welcome to `Hy `_! +:Try Hy: https://try-hy.appspot.com +:PyPI: https://pypi.python.org/pypi/hy +:Source: https://github.com/hylang/hy +:List: `hylang-discuss `_ +:IRC: ``#hy`` on Freenode +:Build status: + .. image:: https://secure.travis-ci.org/hylang/hy.png + :alt: Travis CI + :target: http://travis-ci.org/hylang/hy + Hy is a wonderful dialect of Lisp that's embedded in Python. -Since Hy transforms its lisp code into the python Abstract Syntax -Tree, you have the whole beautiful world of python at your fingertips, -in lisp form! -Meet our mascot, "Cuddles": +Since Hy transforms its Lisp code into the Python Abstract Syntax +Tree, you have the whole beautiful world of Python at your fingertips, +in Lisp form! -.. image:: http://fc07.deviantart.net/fs70/i/2013/138/f/0/cuddles_the_hacker_by_doctormo-d65l7lq.png - :alt: Paul riding cuddles into the distance - -.. - Our old ascii art mascot version - Retained as an easter egg for those who read the docs via .rst! - - LET'S CUDDLEFISH - ______ - _.----'#' # ' - ,' #' ,# ; - (' (w) _,-'_/ - /// / /'.____.' - \|\||/ - - -You can try Hy `in your browser `_. - -Read more about Hy in these docs! - -We're also on IRC! Join -`#hy on irc.freenode.net `_! Documentation Index =================== @@ -49,6 +32,6 @@ Contents: quickstart tutorial - hacking language/index contrib/index + hacking diff --git a/docs/language/index.rst b/docs/language/index.rst index 4721eb3..d55032d 100644 --- a/docs/language/index.rst +++ b/docs/language/index.rst @@ -10,5 +10,5 @@ Contents: cli api core - internals readermacros + internals From 65f8bd2b91539f72ea0805a76183ad2c56b94eeb Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 25 Jan 2014 21:32:56 +0200 Subject: [PATCH 174/202] Document hyc command line options. --- docs/language/cli.rst | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 317e233..5b9dc73 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -2,8 +2,11 @@ Command Line Interface ====================== +hy +-- + Command line options --------------------- +^^^^^^^^^^^^^^^^^^^^ .. cmdoption:: -c @@ -41,3 +44,30 @@ Command line options .. cmdoption:: -v Print the Hy version number and exit. + + +hyc +--- + +Command line options +^^^^^^^^^^^^^^^^^^^^ + +.. cmdoption:: file[, fileN] + + Compile Hy code to Python bytecode. For example, save the + following code as ``hyname.hy``: + + .. code-block:: clojure + + (defn hy-hy [name] + (print (+ "Hy " name "!"))) + + (hy-hy "Afroman") + + Then run: + + .. code-block:: bash + + $ hyc hyname.hy + $ python hyname.pyc + Hy Afroman! From 0177541d9bda3087c24e163b4d47bc15116ea9a0 Mon Sep 17 00:00:00 2001 From: Brian McKenna Date: Sat, 25 Jan 2014 15:30:03 -0700 Subject: [PATCH 175/202] Add `items`, `values` and `keys` methods to HyDict Useful for macros, for example: (defmacro defn-checked [n x b] `(defn ~n [(unquote-splice (.keys x))] (print "Expecting:" (unquote-splice (.values x))) ~b)) (defn-checked hello {name str age int} (print "Hello" name "you are" age)) (hello "Godzilla" 60) ;; Expecting: ;; Hello Godzilla you are 60 --- hy/models/dict.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hy/models/dict.py b/hy/models/dict.py index 1bf020c..d9fb626 100644 --- a/hy/models/dict.py +++ b/hy/models/dict.py @@ -28,3 +28,12 @@ class HyDict(HyList): def __repr__(self): return "{%s}" % (" ".join([repr(x) for x in self])) + + def keys(self): + return self[0::2] + + def values(self): + return self[1::2] + + def items(self): + return zip(self.keys(), self.values()) From cef709170896e515606b084674a818e5ca90134d Mon Sep 17 00:00:00 2001 From: Brian McKenna Date: Sat, 25 Jan 2014 18:15:17 -0700 Subject: [PATCH 176/202] Add tests for HyDict methods --- tests/models/test_dict.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/models/test_dict.py diff --git a/tests/models/test_dict.py b/tests/models/test_dict.py new file mode 100644 index 0000000..c5da994 --- /dev/null +++ b/tests/models/test_dict.py @@ -0,0 +1,13 @@ +from hy.models.dict import HyDict + + +hydict = HyDict(["a", 1, "b", 2, "c", 3]) + +def test_dict_items(): + assert hydict.items() == [("a", 1), ("b", 2), ("c", 3)] + +def test_dict_keys(): + assert hydict.keys() == ["a", "b", "c"] + +def test_dict_values(): + assert hydict.values() == [1, 2, 3] From 13a88b358e0dd782472a75436392d43d204348bc Mon Sep 17 00:00:00 2001 From: Brian McKenna Date: Sat, 25 Jan 2014 18:15:49 -0700 Subject: [PATCH 177/202] Add Brian McKenna to AUTHORS --- AUTHORS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 5eeaf05..e54d53f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -37,4 +37,5 @@ * Vasudev Kamath * Yuval Langer * Fatih Kadir Akın -* Jack Hooper \ No newline at end of file +* Jack Hooper +* Brian McKenna From 990d901d65dea9549ea7cb5604af7669fe21882e Mon Sep 17 00:00:00 2001 From: Brian McKenna Date: Sat, 25 Jan 2014 20:29:09 -0700 Subject: [PATCH 178/202] Fix single spaces between methods in test_dict.py --- tests/models/test_dict.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/models/test_dict.py b/tests/models/test_dict.py index c5da994..cf3b2fd 100644 --- a/tests/models/test_dict.py +++ b/tests/models/test_dict.py @@ -3,11 +3,14 @@ from hy.models.dict import HyDict hydict = HyDict(["a", 1, "b", 2, "c", 3]) + def test_dict_items(): assert hydict.items() == [("a", 1), ("b", 2), ("c", 3)] + def test_dict_keys(): assert hydict.keys() == ["a", "b", "c"] + def test_dict_values(): assert hydict.values() == [1, 2, 3] From 6bfc4a8471d88f5db7ca0aa3c084105c0aa1d70b Mon Sep 17 00:00:00 2001 From: Brian McKenna Date: Sun, 26 Jan 2014 10:55:31 -0700 Subject: [PATCH 179/202] Attempt to fix HyDict#items for Python 3 Looks like Python 3 changed `zip` to returning an iterable and not a list. We should be good to just wrap the call in `list`. --- hy/models/dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/models/dict.py b/hy/models/dict.py index d9fb626..e0e99bd 100644 --- a/hy/models/dict.py +++ b/hy/models/dict.py @@ -36,4 +36,4 @@ class HyDict(HyList): return self[1::2] def items(self): - return zip(self.keys(), self.values()) + return list(zip(self.keys(), self.values())) From cbb8cc1d3730ab8cc104b2f3a96fed4ec75dfc7f Mon Sep 17 00:00:00 2001 From: James King Date: Tue, 28 Jan 2014 17:28:41 -0500 Subject: [PATCH 180/202] Add integer-char? to core --- hy/core/language.hy | 21 +++++++++++++++------ tests/native_tests/core.hy | 8 ++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 2578924..133d6b7 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -177,6 +177,13 @@ "Return True if x in an integer" (isinstance x (, int long-type))) +(defn integer-char? [x] + "Return True if char `x` parses as an integer" + (try + (integer? (int x)) + (catch [e ValueError] False) + (catch [e TypeError] False))) + (defn iterable? [x] "Return true if x is iterable" (try (do (iter x) true) @@ -314,9 +321,11 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name coll? cycle dec distinct disassemble - drop drop-while empty? even? first filter flatten float? - gensym identity inc instance? integer integer? iterable? - iterate iterator? macroexpand macroexpand-1 neg? nil? - none? nth numeric? odd? pos? remove repeat repeatedly - rest second string string? take take-nth take-while zero?]) +(def *exports* '[calling-module-name coll? cycle dec distinct + disassemble drop drop-while empty? even? first filter + flatten float? gensym identity inc instance? integer + integer? integer-char? iterable? iterate iterator? + macroexpand macroexpand-1 neg? nil? none? nth + numeric? odd? pos? remove repeat repeatedly rest + second string string? take take-nth take-while + zero?]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 1b2bbbc..d9e27a3 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -223,6 +223,14 @@ (assert-false (integer? None)) (assert-false (integer? "foo"))) +(defn test-integer-char? [] + "NATIVE: testing the integer-char? function" + (assert-true (integer-char? "1")) + (assert-true (integer-char? "-1")) + (assert-true (integer-char? (str (integer 300)))) + (assert-false (integer-char? "foo")) + (assert-false (integer-char? None))) + (defn test-iterable [] "NATIVE: testing iterable? function" ;; should work for a string From f17e52f1e52f579fdcc51bf7fbbedd815300cff5 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Wed, 29 Jan 2014 10:09:21 +0100 Subject: [PATCH 181/202] README.md: Add Adderall to Hylarious Hacks Signed-off-by: Gergely Nagy --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81d4f14..784ece5 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Hylarious Hacks [Hy IRC bot](https://github.com/hylang/hygdrop) +[miniKanren in Hy](https://github.com/algernon/adderall) OK, so, why? ------------ From 75748eb05dc5e38a88da2fa3789e9aa629ac65fc Mon Sep 17 00:00:00 2001 From: Halit Alptekin Date: Wed, 29 Jan 2014 15:58:49 +0200 Subject: [PATCH 182/202] Fix unicode error #483 --- hy/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/errors.py b/hy/errors.py index 0e786e8..d0240c5 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -137,7 +137,7 @@ class HyTypeError(TypeError): (self.__class__.__name__, self.message)) - return result + return result.encode('utf-8') class HyMacroExpansionError(HyTypeError): From b8ef4ccc3c4b873cf37b1fc49db08ca8e2fdb241 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 30 Jan 2014 06:01:32 +0200 Subject: [PATCH 183/202] Update tutorial. - Fix a couple of typos - Use open() built-in instead of file() - Update (for) and (with) examples --- docs/tutorial.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index ceae5a8..0dbbfd2 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -276,7 +276,7 @@ You might notice above that if you have code like: (body-if-true) (body-if-false)) -But wait! What if you want to execute more than one statment in the +But wait! What if you want to execute more than one statement in the body of one of these? You can do the following: @@ -289,7 +289,7 @@ You can do the following: (print "and why not, let's keep talking about how true it is!)) (print "this one's still simply just false")) -You can see that we used "do" to wrap multiple statments. If you're +You can see that we used "do" to wrap multiple statements. If you're familiar with other lisps, this is the equivalent of "progn" elsewhere. @@ -311,8 +311,8 @@ The equivalent in hy would be: .. code-block:: clj - (for (i (range 10)) - (print (+ "'i' is now at " (str i)))) + (for [i (range 10)] + (print (+ "'i' is now at " (str i)))) You can also import and make use of various python libraries. For @@ -330,13 +330,13 @@ Python's context managers ('with' statements) are used like this: .. code-block:: clj - (with [f (file "/tmp/data.in")] - (print (.read f))) + (with [[f (open "/tmp/data.in")]] + (print (.read f))) which is equivalent to:: - with file("/tmp/data.in") as f: - print f.read() + with open("/tmp/data.in") as f: + print f.read() And yes, we do have lisp comprehensions! In Python you might do:: @@ -435,7 +435,7 @@ The Hy equivalent: Finally, of course we need classes! In python we might have a class like:: - class FooBar (object): + class FooBar(object): def __init__(self, x): self.x = x From f0dd5ba74cb51ac49c2b79da37cfdca511bb3f14 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 30 Jan 2014 05:50:15 +0200 Subject: [PATCH 184/202] Move all badges to top of README. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 784ece5..fe48757 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ Hy == -![](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png) - -Lisp and Python should love each other. Let's make it happen. [Try it](http://try-hy.appspot.com/). - [![Build Status](https://travis-ci.org/hylang/hy.png?branch=master)](https://travis-ci.org/hylang/hy) [![Downloads](https://pypip.in/d/hy/badge.png)](https://crate.io/packages/hy) [![version](https://pypip.in/v/hy/badge.png)](https://crate.io/packages/hy) [![Coverage Status](https://coveralls.io/repos/hylang/hy/badge.png)](https://coveralls.io/r/hylang/hy) +![](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png) + +Lisp and Python should love each other. Let's make it happen. [Try it](http://try-hy.appspot.com/). + Hylarious Hacks --------------- From afff5fd93bc06ca08a0afe5960453e633c22248b Mon Sep 17 00:00:00 2001 From: halit Date: Thu, 30 Jan 2014 15:55:30 +0200 Subject: [PATCH 185/202] Add new author --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index e54d53f..7388b5b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,3 +39,4 @@ * Fatih Kadir Akın * Jack Hooper * Brian McKenna +* Halit Alptekin From a318afea3a4894fa32ecd89b1c7d5688708ae8af Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 30 Jan 2014 05:47:14 +0200 Subject: [PATCH 186/202] Port update_coreteam.py to Hy. --- scripts/update-coreteam.hy | 37 ++++++++++++++++++++++++++++++++ scripts/update_coreteam.py | 44 -------------------------------------- 2 files changed, 37 insertions(+), 44 deletions(-) create mode 100644 scripts/update-coreteam.hy delete mode 100644 scripts/update_coreteam.py diff --git a/scripts/update-coreteam.hy b/scripts/update-coreteam.hy new file mode 100644 index 0000000..394bbd8 --- /dev/null +++ b/scripts/update-coreteam.hy @@ -0,0 +1,37 @@ +;; You need to install the requests package first + +(import os.path) +(import requests) + + +(setv *api-url* "https://api.github.com/{}") +(setv *rst-format* "* `{} <{}>`_") +(setv *missing-names* {"khinsen" "Konrad Hinsen"}) +;; We have three concealed members on the hylang organization +;; and GitHub only shows public members if the requester is not +;; an owner of the organization. +(setv *concealed-members* [(, "aldeka" "Karen Rustad") + (, "tuturto" "Tuukka Turto") + (, "cndreisbach" "Clinton N. Dreisbach")]) + +(defn get-dev-name [login] + (setv name (get (.json (requests.get (.format *api-url* (+ "users/" login)))) "name")) + (if-not name + (.get *missing-names* login) + name)) + +(setv coredevs (requests.get (.format *api-url* "orgs/hylang/members"))) + +(setv result (set)) +(for [dev (.json coredevs)] + (result.add (.format *rst-format* (get-dev-name (get dev "login")) + (get dev "html_url")))) + +(for [(, login name) *concealed-members*] + (result.add (.format *rst-format* name (+ "https://github.com/" login)))) + +(setv filename (os.path.abspath (os.path.join os.path.pardir + "docs" "coreteam.rst"))) + +(with [[fobj (open filename "w+")]] + (fobj.write (+ (.join "\n" result) "\n"))) diff --git a/scripts/update_coreteam.py b/scripts/update_coreteam.py deleted file mode 100644 index 90f2d86..0000000 --- a/scripts/update_coreteam.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -You need to install the requests package first:: - - $ pip install requests - -""" - -import os.path -import requests - -API_URL = 'https://api.github.com/%s' - -RST_FORMAT = '* `%s <%s>`_' -MISSING_NAMES = { - 'khinsen': 'Konrad Hinsen', -} -# We have three concealed members on the hylang organization -# and GitHub only shows public members if the requester is not -# an owner of the organization. -CONCEALED_MEMBERS = [ - ('aldeka', 'Karen Rustad'), - ('tuturto', 'Tuukka Turto'), -] - - -def get_dev_name(login): - name = requests.get(API_URL % 'users/' + login).json()['name'] - if not name: - return MISSING_NAMES.get(login) - return name - -coredevs = requests.get(API_URL % 'orgs/hylang/members') - -result = set() -for dev in coredevs.json(): - result.add(RST_FORMAT % (get_dev_name(dev['login']), dev['html_url'])) - -for login, name in CONCEALED_MEMBERS: - result.add(RST_FORMAT % (name, 'https://github.com/' + login)) - -filename = os.path.abspath(os.path.join(os.path.pardir, - 'docs', 'coreteam.rst')) -with open(filename, 'w+') as fobj: - fobj.write('\n'.join(result) + '\n') From 817b4688d89b260b5a7b77c00a1e85cb0499ef24 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sun, 26 Jan 2014 03:53:44 +0100 Subject: [PATCH 187/202] hy.contrib.walk: New contrib module for walking the Hy AST The hy.contrib.walk module provides a few functions to walk the Hy AST, and potentially transform it along the way. The main entry point is (walk), which takes two functions and a form as arguments, and applies the first (inner) function to each element of the form, building up a data structure of the same type as the original. Then applies outer (the second function) to the result. Two convenience functions are provided: (postwalk) and (prewalk), which do a depth-first, post/pre-order traversal of the form. Signed-off-by: Gergely Nagy --- hy/contrib/walk.hy | 47 ++++++++++++++++++++++++++++++ tests/__init__.py | 1 + tests/native_tests/contrib/walk.hy | 24 +++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 hy/contrib/walk.hy create mode 100644 tests/native_tests/contrib/walk.hy diff --git a/hy/contrib/walk.hy b/hy/contrib/walk.hy new file mode 100644 index 0000000..9fc3771 --- /dev/null +++ b/hy/contrib/walk.hy @@ -0,0 +1,47 @@ +;;; Hy AST walker +;; +;; Copyright (c) 2014 Gergely Nagy +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +(import [hy [HyExpression HyDict]] + [functools [partial]]) + +(defn walk [inner outer form] + "Traverses form, an arbitrary data structure. Applies inner to each + element of form, building up a data structure of the same type. + Applies outer to the result." + (cond + [(instance? HyExpression form) + (outer (HyExpression (map inner form)))] + [(instance? HyDict form) + (HyDict (outer (HyExpression (map inner form))))] + [(instance? list form) + ((type form) (outer (HyExpression (map inner form))))] + [true (outer form)])) + +(defn postwalk [f form] + "Performs depth-first, post-order traversal of form. Calls f on each + sub-form, uses f's return value in place of the original." + (walk (partial postwalk f) f form)) + +(defn prewalk [f form] + "Performs depth-first, pre-order traversal of form. Calls f on each + sub-form, uses f's return value in place of the original." + (walk (partial prewalk f) identity (f form))) diff --git a/tests/__init__.py b/tests/__init__.py index adcb583..e36ce11 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,3 +17,4 @@ from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa from .native_tests.contrib.meth import * # noqa +from .native_tests.contrib.walk import * # noqa diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy new file mode 100644 index 0000000..854dfc7 --- /dev/null +++ b/tests/native_tests/contrib/walk.hy @@ -0,0 +1,24 @@ +(import [hy.contrib.walk [*]]) + +(def walk-form '(print {"foo" "bar" + "array" [1 2 3 [4]] + "something" (+ 1 2 3 4) + "quoted?" '(foo)})) + +(defn collector [acc x] + (.append acc x) + nil) + +(defn test-walk-identity [] + (assert (= (walk identity identity walk-form) + walk-form))) + +(defn test-walk [] + (let [[acc '()]] + (assert (= (walk (partial collector acc) identity walk-form) + [nil nil])) + (assert (= acc walk-form))) + (let [[acc []]] + (assert (= (walk identity (partial collector acc) walk-form) + nil)) + (assert (= acc [walk-form])))) From fa24042cb00613e1b448b95443b0bd5ba1d1c0a2 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sun, 26 Jan 2014 03:59:47 +0100 Subject: [PATCH 188/202] hy.contrib.walk: Add (macroexpand-all) This function will recursively perform all possible macroexpansions in the supplied form. Unfortunately, it also traverses into quasiquoted parts, where it shouldn't, but it is a useful estimation of macro expansion anyway. Signed-off-by: Gergely Nagy --- hy/contrib/walk.hy | 8 ++++++++ tests/native_tests/contrib/walk.hy | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/hy/contrib/walk.hy b/hy/contrib/walk.hy index 9fc3771..a69078d 100644 --- a/hy/contrib/walk.hy +++ b/hy/contrib/walk.hy @@ -45,3 +45,11 @@ "Performs depth-first, pre-order traversal of form. Calls f on each sub-form, uses f's return value in place of the original." (walk (partial prewalk f) identity (f form))) + +(defn macroexpand-all [form] + "Recursively performs all possible macroexpansions in form." + (prewalk (fn [x] + (if (instance? HyExpression x) + (macroexpand x) + x)) + form)) diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy index 854dfc7..24feb76 100644 --- a/tests/native_tests/contrib/walk.hy +++ b/tests/native_tests/contrib/walk.hy @@ -22,3 +22,7 @@ (assert (= (walk identity (partial collector acc) walk-form) nil)) (assert (= acc [walk-form])))) + +(defn test-macroexpand-all [] + (assert (= (macroexpand-all '(with [a b c] (for [d c] foo))) + '(with* [a] (with* [b] (with* [c] (do (for* [d c] foo)))))))) From e8dfe5bfb20ecd1d19c4913bed2449793ab6204e Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sat, 1 Feb 2014 18:36:57 +0100 Subject: [PATCH 189/202] hy.contrib.walk: Add support for walking cons cells Signed-off-by: Gergely Nagy --- hy/contrib/walk.hy | 3 +++ tests/native_tests/contrib/walk.hy | 1 + 2 files changed, 4 insertions(+) diff --git a/hy/contrib/walk.hy b/hy/contrib/walk.hy index a69078d..a846ed3 100644 --- a/hy/contrib/walk.hy +++ b/hy/contrib/walk.hy @@ -32,6 +32,9 @@ (outer (HyExpression (map inner form)))] [(instance? HyDict form) (HyDict (outer (HyExpression (map inner form))))] + [(cons? form) + (outer (cons (inner (first form)) + (inner (rest form))))] [(instance? list form) ((type form) (outer (HyExpression (map inner form))))] [true (outer form)])) diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy index 24feb76..d6ecbf8 100644 --- a/tests/native_tests/contrib/walk.hy +++ b/tests/native_tests/contrib/walk.hy @@ -3,6 +3,7 @@ (def walk-form '(print {"foo" "bar" "array" [1 2 3 [4]] "something" (+ 1 2 3 4) + "cons!" (cons 1 2) "quoted?" '(foo)})) (defn collector [acc x] From a41a3c7edc647d7d9bcb64913c03fae22ef71345 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Tue, 4 Feb 2014 01:35:41 +0530 Subject: [PATCH 190/202] faster distinct: maintain seen items in a set * hy/core/language.hy: maintain the seen items in a set instead of a list in `distinct`. This is much faster for lookups. --- hy/core/language.hy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hy/core/language.hy b/hy/core/language.hy index 520d3f1..7fd5b2a 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -75,12 +75,12 @@ (defn distinct [coll] "Return a generator from the original collection with duplicates removed" - (let [[seen []] [citer (iter coll)]] + (let [[seen (set)] [citer (iter coll)]] (for* [val citer] (if (not_in val seen) (do (yield val) - (.append seen val)))))) + (.add seen val)))))) (defn drop [count coll] "Drop `count` elements from `coll` and yield back the rest" From 66366b5bc9653ad0b3b50c9a912742d80c5c64f4 Mon Sep 17 00:00:00 2001 From: Foxboron Date: Mon, 30 Dec 2013 14:32:36 +0100 Subject: [PATCH 191/202] Added defmulti --- docs/contrib/index.rst | 1 + docs/contrib/multi.rst | 23 ++++++++++++ hy/contrib/dispatch/__init__.py | 50 +++++++++++++++++++++++++ hy/contrib/multi.hy | 41 +++++++++++++++++++++ tests/__init__.py | 1 + tests/native_tests/contrib/multi.hy | 57 +++++++++++++++++++++++++++++ 6 files changed, 173 insertions(+) create mode 100644 docs/contrib/multi.rst create mode 100644 hy/contrib/dispatch/__init__.py create mode 100644 hy/contrib/multi.hy create mode 100644 tests/native_tests/contrib/multi.hy diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst index ba0e3a4..53dcf23 100644 --- a/docs/contrib/index.rst +++ b/docs/contrib/index.rst @@ -9,3 +9,4 @@ Contents: anaphoric loop + multi diff --git a/docs/contrib/multi.rst b/docs/contrib/multi.rst new file mode 100644 index 0000000..b8f3619 --- /dev/null +++ b/docs/contrib/multi.rst @@ -0,0 +1,23 @@ +======== +defmulti +======== + +.. versionadded:: 0.9.13 + +`defmulti` lets you arity-overload a function by the given number of +args and/or kwargs. Inspired by clojures take on `defn`. + +.. code-block:: clj + + => (require hy.contrib.multi) + => (defmulti fun + ... ([a] a) + ... ([a b] "a b") + ... ([a b c] "a b c")) + => (fun 1 2 3) + 'a b c' + => (fun a b) + "a b" + => (fun 1) + 1 + diff --git a/hy/contrib/dispatch/__init__.py b/hy/contrib/dispatch/__init__.py new file mode 100644 index 0000000..a14091b --- /dev/null +++ b/hy/contrib/dispatch/__init__.py @@ -0,0 +1,50 @@ +# -*- encoding: utf-8 -*- +# +# Decorator for defmulti +# +# Copyright (c) 2014 Morten Linderud +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from collections import defaultdict + + +class MultiDispatch(object): + _fns = defaultdict(dict) + + def __init__(self, fn): + self.fn = fn + self.__doc__ = fn.__doc__ + if fn.__name__ not in self._fns[fn.__module__].keys(): + self._fns[fn.__module__][fn.__name__] = {} + values = fn.__code__.co_varnames + self._fns[fn.__module__][fn.__name__][values] = fn + + def is_fn(self, v, args, kwargs): + """Compare the given (checked fn) too the called fn""" + com = list(args) + list(kwargs.keys()) + if len(com) == len(v): + return all([kw in com for kw in kwargs.keys()]) + return False + + def __call__(self, *args, **kwargs): + for i, fn in self._fns[self.fn.__module__][self.fn.__name__].items(): + if self.is_fn(i, args, kwargs): + return fn(*args, **kwargs) + raise TypeError("No matching functions with this signature!") diff --git a/hy/contrib/multi.hy b/hy/contrib/multi.hy new file mode 100644 index 0000000..19246ee --- /dev/null +++ b/hy/contrib/multi.hy @@ -0,0 +1,41 @@ +;; Hy Arity-overloading +;; Copyright (c) 2014 Morten Linderud + +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: + +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. + +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +(import [collections [defaultdict]]) +(import [hy.models.string [HyString]]) + + +(defmacro defmulti [name &rest bodies] + (def comment (HyString)) + (if (= (type (first bodies)) HyString) + (do (def comment (car bodies)) + (def bodies (cdr bodies)))) + + (def ret `(do)) + + (.append ret '(import [hy.contrib.dispatch [MultiDispatch]])) + + (for [body bodies] + (def let-binds (car body)) + (def body (cdr body)) + (.append ret + `(with-decorator MultiDispatch (defn ~name ~let-binds ~comment ~@body)))) + ret) diff --git a/tests/__init__.py b/tests/__init__.py index e36ce11..f336ad0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -18,3 +18,4 @@ from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.loop import * # noqa from .native_tests.contrib.meth import * # noqa from .native_tests.contrib.walk import * # noqa +from .native_tests.contrib.multi import * # noqa diff --git a/tests/native_tests/contrib/multi.hy b/tests/native_tests/contrib/multi.hy new file mode 100644 index 0000000..5ce9932 --- /dev/null +++ b/tests/native_tests/contrib/multi.hy @@ -0,0 +1,57 @@ +;; Copyright (c) 2014 Morten Linderud + +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: + +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. + +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +(require hy.contrib.multi) + + +(defn test-basic-multi [] + "NATIVE: Test a basic defmulti" + (defmulti fun + ([] "Hello!") + ([a] a) + ([a b] "a b") + ([a b c] "a b c")) + + (assert (= (fun) "Hello!")) + (assert (= (fun "a") "a")) + (assert (= (fun "a" "b") "a b")) + (assert (= (fun "a" "b" "c") "a b c"))) + + +(defn test-kw-args [] + "NATIVE: Test if kwargs are handled correctly" + (defmulti fun + ([a] a) + ([&optional [a "nop"] [b "p"]] (+ a b))) + + (assert (= (fun 1) 1)) + (assert (= (apply fun [] {"a" "t"}) "t")) + (assert (= (apply fun ["hello "] {"b" "world"}) "hello world")) + (assert (= (apply fun [] {"a" "hello " "b" "world"}) "hello world"))) + + +(defn test-docs [] + "NATIVE: Test if docs are properly handled" + (defmulti fun + "docs" + ([a] (print a)) + ([a b] (print b))) + + (assert (= fun.--doc-- "docs"))) From 1af8e47a2000969592fb2160dd603824812eb4e1 Mon Sep 17 00:00:00 2001 From: Steven Degutis Date: Mon, 10 Feb 2014 17:29:35 -0600 Subject: [PATCH 192/202] Making readme more friendly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe48757..bd66b81 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Why? Well, I wrote Hy to help people realize one thing about Python: -It's really goddamn awesome. +It's really awesome. Oh, and lisps are neat. From 24a1567b007c8cf152bc587b485771543ccee5c2 Mon Sep 17 00:00:00 2001 From: han semaj Date: Tue, 11 Feb 2014 21:42:56 +1300 Subject: [PATCH 193/202] Implement every? and some --- docs/language/core.rst | 52 ++++++++++++++++++++++++++++++++++++++ hy/core/language.hy | 12 +++++++-- tests/native_tests/core.hy | 15 ++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/docs/language/core.rst b/docs/language/core.rst index 57f00b2..7781d21 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -138,6 +138,32 @@ Return True if ``coll`` is empty, i.e. ``(= 0 (len coll))``. False +.. _every?-fn: + +every? +------ + +.. versionadded:: 0.9.13 + +Usage: ``(every? pred coll)`` + +Return True if ``(pred x)`` is logical true for every ``x`` in ``coll``, otherwise False. Return True if ``coll`` is empty. + +.. code-block:: clojure + + => (every? even? [2 4 6]) + True + + => (every? even? [1 3 5]) + False + + => (every? even? [2 4 5]) + False + + => (every? even? []) + True + + .. _float?-fn: float? @@ -570,6 +596,32 @@ Return the second member of ``coll``. Equivalent to 1 +.. _some-fn: + +some +---- + +.. versionadded:: 0.9.13 + +Usage: ``(some pred coll)`` + +Return True if ``(pred x)`` is logical true for any ``x`` in ``coll``, otherwise False. Return False if ``coll`` is empty. + +.. code-block:: clojure + + => (some even? [2 4 6]) + True + + => (some even? [1 3 5]) + False + + => (some even? [1 3 6]) + True + + => (some even? []) + False + + .. _string?-fn: string? diff --git a/hy/core/language.hy b/hy/core/language.hy index 7fd5b2a..d8b0df8 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -108,6 +108,10 @@ (_numeric-check n) (= (% n 2) 0)) +(defn every? [pred coll] + "Return true if (pred x) is logical true for every x in coll, else false" + (all (map pred coll))) + (defn fake-source-positions [tree] "Fake the source positions for a given tree" (if (and (iterable? tree) (not (string? tree))) @@ -294,6 +298,10 @@ "Return second item from `coll`" (get coll 1)) +(defn some [pred coll] + "Return true if (pred x) is logical true for any x in coll, else false" + (any (map pred coll))) + (defn string [x] "Cast x as current string implementation" (if-python2 @@ -338,9 +346,9 @@ (= n 0)) (def *exports* '[calling-module-name coll? cons cons? cycle dec distinct - disassemble drop drop-while empty? even? first filter + disassemble drop drop-while empty? even? every? first filter flatten float? gensym identity inc instance? integer integer? integer-char? iterable? iterate iterator? list* macroexpand macroexpand-1 neg? nil? none? nth numeric? odd? pos? remove repeat repeatedly rest second - string string? take take-nth take-while zero?]) + some string string? take take-nth take-while zero?]) diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index d9e27a3..c973948 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -123,6 +123,13 @@ (try (even? None) (catch [e [TypeError]] (assert (in "not a number" (str e)))))) +(defn test-every? [] + "NATIVE: testing the every? function" + (assert-true (every? even? [2 4 6])) + (assert-false (every? even? [1 3 5])) + (assert-false (every? even? [2 4 5])) + (assert-true (every? even? []))) + (defn test-filter [] "NATIVE: testing the filter function" (setv res (list (filter pos? [ 1 2 3 -4 5]))) @@ -399,6 +406,13 @@ (assert-equal 2 (second [1 2])) (assert-equal 3 (second [2 3 4]))) +(defn test-some [] + "NATIVE: testing the some function" + (assert-true (some even? [2 4 6])) + (assert-false (some even? [1 3 5])) + (assert-true (some even? [1 3 6])) + (assert-false (some even? []))) + (defn test-string? [] "NATIVE: testing string?" (assert-true (string? "foo")) @@ -456,4 +470,3 @@ (assert-equal res [None None]) (setv res (list (take-while (fn [x] (not (none? x))) [1 2 3 4 None 5 6 None 7]))) (assert-equal res [1 2 3 4])) - From 6b4e3940aaef04d63942913ba0dd4d9e668d5f70 Mon Sep 17 00:00:00 2001 From: Richard Parsons Date: Tue, 11 Feb 2014 15:25:17 +0000 Subject: [PATCH 194/202] updated import documentation --- AUTHORS | 1 + docs/language/api.rst | 3 +++ 2 files changed, 4 insertions(+) diff --git a/AUTHORS b/AUTHORS index e54d53f..6fd2f4a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,3 +39,4 @@ * Fatih Kadir Akın * Jack Hooper * Brian McKenna +* Richard Parsons diff --git a/docs/language/api.rst b/docs/language/api.rst index 7e991b3..2313921 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -762,6 +762,9 @@ of import you can use. [os.path [exists isdir isfile]] [sys :as systest]) + ;; Import all module functions into current namespace + (import [sys [*]]) + kwapply ------- From 6c846a24b16cb3e1e7d07a2d042a6a9456e85982 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Tue, 11 Feb 2014 16:57:31 +0100 Subject: [PATCH 195/202] hy/compiler.py: import only handles HySymbol and HyList, bail on others When (import) encounters anything but a HySymbol or HyList, raise an exception, as that is not valid in Hy. Previously, anything other than a HySymbol or HyList was simply ignored, turning that particular import into a no-op, which was both wrong and confusing. Reported-by: Richard Parsons Signed-off-by: Gergely Nagy --- hy/compiler.py | 4 ++++ tests/importer/test_importer.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/hy/compiler.py b/hy/compiler.py index 622b182..dbb4782 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1034,6 +1034,10 @@ class HyASTCompiler(object): while len(expr) > 0: iexpr = expr.pop(0) + if not isinstance(iexpr, (HySymbol, HyList)): + raise HyTypeError(iexpr, "(import) requires a Symbol " + "or a List.") + if isinstance(iexpr, HySymbol): rimports += _compile_import(expr, iexpr) continue diff --git a/tests/importer/test_importer.py b/tests/importer/test_importer.py index 5f4dbf1..edfbb5a 100644 --- a/tests/importer/test_importer.py +++ b/tests/importer/test_importer.py @@ -1,4 +1,5 @@ from hy.importer import import_file_to_module, import_buffer_to_ast, MetaLoader +from hy.errors import HyTypeError import os import ast @@ -27,3 +28,16 @@ def test_imports(): assert _import_test() == "Error" assert _import_test() is not None + + +def test_import_error_reporting(): + "Make sure that (import) reports errors correctly." + + def _import_error_test(): + try: + import_buffer_to_ast("(import \"sys\")", '') + except HyTypeError: + return "Error reported" + + assert _import_error_test() == "Error reported" + assert _import_error_test() is not None From 3c9947a3f6b93e94ddc204455eebd17c9239489d Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 12 Feb 2014 23:59:36 -0500 Subject: [PATCH 196/202] add @microamp to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 6fd2f4a..af95f5a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,3 +40,4 @@ * Jack Hooper * Brian McKenna * Richard Parsons +* han semaj From 6e0486ba78bfafcaa74d455f9455218a29cc08da Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 15 Feb 2014 19:22:53 +0200 Subject: [PATCH 197/202] Update copyright year in docs. --- docs/conf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1d5b516..4744800 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys +import time sys.path.append(os.path.abspath("..")) import hy @@ -44,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'hy' -copyright = u'2013, Paul Tagliamonte' +copyright = u'2013-%s, Paul Tagliamonte' % time.strftime('%Y') # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From e2199738537758ab8110fcbefe189648b5ddc51f Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 17 Feb 2014 17:57:27 -0500 Subject: [PATCH 198/202] Fix up quoting keywords. Fixes this: => :foo '\ufdd0:foo' => `:foo '\ufdd0\ufdd0:foo' --- hy/models/keyword.py | 5 ++++- tests/native_tests/language.hy | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hy/models/keyword.py b/hy/models/keyword.py index f2633a9..6d25633 100644 --- a/hy/models/keyword.py +++ b/hy/models/keyword.py @@ -29,5 +29,8 @@ class HyKeyword(HyObject, str_type): """ def __new__(cls, value): - obj = str_type.__new__(cls, "\uFDD0" + value) + if not value.startswith("\uFDD0"): + value = "\uFDD0" + value + + obj = str_type.__new__(cls, value) return obj diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index cb75e66..5e6f6c7 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -983,3 +983,8 @@ (assert (is bar (get foo 1))) (setv (. foo [1] test) "hello") (assert (= (getattr (. foo [1]) "test") "hello"))) + +(defn test-keyword-quoting [] + "NATIVE: test keyword quoting magic" + (assert (= :foo "\ufdd0:foo")) + (assert (= `:foo "\ufdd0:foo"))) From b97e0258e16a003bd7e081612987ecdc8bef19c8 Mon Sep 17 00:00:00 2001 From: James King Date: Wed, 19 Feb 2014 00:09:37 -0500 Subject: [PATCH 199/202] Fix for #497 One would expect the form: > (defmacro a (&rest b) b) > (a 1 2) To return a tuple object but we have no Hy model so it returns a HyList. Not sure if this is the right thing to do. --- hy/macros.py | 1 + tests/native_tests/native_macros.hy | 3 +++ 2 files changed, 4 insertions(+) diff --git a/hy/macros.py b/hy/macros.py index b155799..cf994a2 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -115,6 +115,7 @@ _wrappers = { str_type: HyString, dict: lambda d: HyDict(_wrap_value(x) for x in sum(d.items(), ())), list: lambda l: HyList(_wrap_value(x) for x in l), + tuple: lambda t: HyList(_wrap_value(x) for x in t), type(None): lambda foo: HySymbol("None"), } diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index dc5715d..5bdf821 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -31,6 +31,9 @@ (defmacro a-list [] [1 2]) (assert (= (a-list) [1 2])) +(defmacro a-tuple [&rest b] b) +(assert (= (a-tuple 1 2) [1 2])) + (defmacro a-dict [] {1 2}) (assert (= (a-dict) {1 2})) From d282d65cdeb7bab1de9b3785a7fd345491417767 Mon Sep 17 00:00:00 2001 From: kirbyfan64 Date: Wed, 19 Feb 2014 20:27:49 -0600 Subject: [PATCH 200/202] Add zipwith --- docs/language/api.rst | 17 +++++++++++++++++ hy/core/language.hy | 8 +++++++- hy/core/macros.hy | 1 + tests/native_tests/core.hy | 9 +++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 2313921..ff301ae 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1250,3 +1250,20 @@ infinite series without consuming infinite amount of memory. ... (while True (yield (.randint random low high)))) => (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] + +zipwith +------- + +`zipwith` zips multiple lists and maps the given function over the result. It is equilavent to calling `zip`, followed by calling `map` on the result. + +In the following example, `zipwith` is used to add the contents of two lists together. The equilavent `map` and `zip` calls follow. + +.. code-block:: clj + + => (import operator.add) + + => (zipwith operator.add [1 2 3] [4 5 6]) ; using zipwith + [5, 7, 9] + + => (map operator.add (zip [1 2 3] [4 5 6])) ; using map+zip + [5, 7, 9] diff --git a/hy/core/language.hy b/hy/core/language.hy index d8b0df8..19e8486 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -345,10 +345,16 @@ (_numeric_check n) (= n 0)) +(defn zipwith [func &rest lists] + "Zip the contents of several lists and map a function to the result" + (do + (import functools) + (map (functools.partial (fn [f args] (apply f args)) func) (apply zip lists)))) + (def *exports* '[calling-module-name coll? cons cons? cycle dec distinct disassemble drop drop-while empty? even? every? first filter flatten float? gensym identity inc instance? integer integer? integer-char? iterable? iterate iterator? list* macroexpand macroexpand-1 neg? nil? none? nth numeric? odd? pos? remove repeat repeatedly rest second - some string string? take take-nth take-while zero?]) + some string string? take take-nth take-while zero? zipwith]) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index ff497b4..8e3c21a 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -199,3 +199,4 @@ (.append ret `(setv ~name ~main))) ret)) + diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index c973948..1a6ea61 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -470,3 +470,12 @@ (assert-equal res [None None]) (setv res (list (take-while (fn [x] (not (none? x))) [1 2 3 4 None 5 6 None 7]))) (assert-equal res [1 2 3 4])) + +(defn test-zipwith [] + "NATIVE: testing the zipwith function" + (import operator) + (setv res (zipwith operator.add [1 2 3] [3 2 1])) + (assert-equal (list res) [4 4 4]) + (setv res (zipwith operator.sub [3 7 9] [1 2 4])) + (assert-equal (list res) [2 5 5])) + From 6829d6fb3a417c6d184dcbdcb3b3e3bf7a06ba59 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 22 Feb 2014 02:21:34 +0200 Subject: [PATCH 201/202] Tweak the zipwith doc a bit and add @kirbyfan64 to AUTHORS. --- AUTHORS | 1 + docs/language/api.rst | 12 ++++++++---- hy/core/macros.hy | 1 - tests/native_tests/core.hy | 1 - 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index fc94b27..fe17b8c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,3 +42,4 @@ * Halit Alptekin * Richard Parsons * han semaj +* kirbyfan64 diff --git a/docs/language/api.rst b/docs/language/api.rst index ff301ae..2e30ab4 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -1251,19 +1251,23 @@ infinite series without consuming infinite amount of memory. => (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] +.. _zipwith: + zipwith ------- -`zipwith` zips multiple lists and maps the given function over the result. It is equilavent to calling `zip`, followed by calling `map` on the result. +.. versionadded:: 0.9.13 -In the following example, `zipwith` is used to add the contents of two lists together. The equilavent `map` and `zip` calls follow. +`zipwith` zips multiple lists and maps the given function over the result. It is +equilavent to calling ``zip``, followed by calling ``map`` on the result. + +In the following example, `zipwith` is used to add the contents of two lists +together. The equilavent ``map`` and ``zip`` calls follow. .. code-block:: clj => (import operator.add) - => (zipwith operator.add [1 2 3] [4 5 6]) ; using zipwith [5, 7, 9] - => (map operator.add (zip [1 2 3] [4 5 6])) ; using map+zip [5, 7, 9] diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 8e3c21a..ff497b4 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -199,4 +199,3 @@ (.append ret `(setv ~name ~main))) ret)) - diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 1a6ea61..15debf4 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -478,4 +478,3 @@ (assert-equal (list res) [4 4 4]) (setv res (zipwith operator.sub [3 7 9] [1 2 4])) (assert-equal (list res) [2 5 5])) - From ecfd737fb906d863f93c0425abb07603a6b3883d Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sun, 23 Feb 2014 15:20:43 -0600 Subject: [PATCH 202/202] Document how to supply docstrings to classes and class methods / lambdas We want to encourage good practice, documentation-wise, amongst Hy users! --- docs/language/api.rst | 16 ++++++++++++++++ docs/tutorial.rst | 16 ++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 2e30ab4..02823a1 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -814,6 +814,22 @@ function is defined and passed to another function for filtering output. Alice Dave +Just as in normal function definitions, if the first element of the +body is a string, it serves as docstring. This is useful for giving +class methods docstrings. + + => (setv times-three + ... (fn [x] + ... "Multiplies input by three and returns the result." + ... (* x 3))) + + => (help times-three) + Help on function times_three: + + times_three(x) + Multiplies input by three and returns result + (END) + let --- diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 0dbbfd2..4acb72b 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -436,11 +436,17 @@ Finally, of course we need classes! In python we might have a class like:: class FooBar(object): - def __init__(self, x): - self.x = x + """ + Yet Another Example Class + """ + def __init__(self, x): + self.x = x - def get_x(self): - return self.x + def get_x(self): + """ + Return our copy of x + """ + return self.x In Hy: @@ -448,6 +454,7 @@ In Hy: .. code-block:: clj (defclass FooBar [object] + "Yet Another Example Class" [[--init-- (fn [self x] (setv self.x x) @@ -457,6 +464,7 @@ In Hy: [get-x (fn [self] + "Return our copy of x" self.x)]])