From 54757b83165d95bd19ceda0a67e952374546e398 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Tue, 25 Jun 2013 20:08:12 +0200 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 f17e52f1e52f579fdcc51bf7fbbedd815300cff5 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Wed, 29 Jan 2014 10:09:21 +0100 Subject: [PATCH 05/17] 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 b8ef4ccc3c4b873cf37b1fc49db08ca8e2fdb241 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 30 Jan 2014 06:01:32 +0200 Subject: [PATCH 06/17] 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 07/17] 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 a318afea3a4894fa32ecd89b1c7d5688708ae8af Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Thu, 30 Jan 2014 05:47:14 +0200 Subject: [PATCH 08/17] 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 09/17] 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 10/17] 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 11/17] 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 12/17] 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 13/17] 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 14/17] 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 15/17] 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 16/17] 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 3c9947a3f6b93e94ddc204455eebd17c9239489d Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 12 Feb 2014 23:59:36 -0500 Subject: [PATCH 17/17] 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