diff --git a/AUTHORS b/AUTHORS index 7388b5b..fc94b27 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,3 +40,5 @@ * Jack Hooper * Brian McKenna * Halit Alptekin +* Richard Parsons +* han semaj diff --git a/README.md b/README.md index 81d4f14..bd66b81 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 --------------- @@ -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? ------------ @@ -30,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. diff --git a/docs/_static/hy-logo-small.png b/docs/_static/hy-logo-small.png new file mode 100644 index 0000000..b6a3c9c Binary files /dev/null and b/docs/_static/hy-logo-small.png differ 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/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/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 ------- diff --git a/docs/language/core.rst b/docs/language/core.rst index 9007348..7781d21 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 @@ -96,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? @@ -284,6 +352,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 @@ -506,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? @@ -863,5 +979,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/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 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/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 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 93e6526..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 @@ -1645,15 +1664,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): @@ -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/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/hy/contrib/walk.hy b/hy/contrib/walk.hy new file mode 100644 index 0000000..a846ed3 --- /dev/null +++ b/hy/contrib/walk.hy @@ -0,0 +1,58 @@ +;;; 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))))] + [(cons? form) + (outer (cons (inner (first form)) + (inner (rest 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))) + +(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/hy/core/language.hy b/hy/core/language.hy index 133d6b7..d8b0df8 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 []) @@ -65,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" @@ -98,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))) @@ -200,6 +214,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) @@ -278,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 @@ -321,11 +345,10 @@ (_numeric_check n) (= n 0)) -(def *exports* '[calling-module-name coll? cycle dec distinct - disassemble drop drop-while empty? even? first filter +(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? - macroexpand macroexpand-1 neg? nil? none? nth - numeric? odd? pos? remove repeat repeatedly rest - second string string? take take-nth take-while - zero?]) + 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?]) 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/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])) 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') diff --git a/tests/__init__.py b/tests/__init__.py index 2df7cf0..f336ad0 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 @@ -16,3 +17,5 @@ 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 +from .native_tests.contrib.multi 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)))) 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"))) diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy new file mode 100644 index 0000000..d6ecbf8 --- /dev/null +++ b/tests/native_tests/contrib/walk.hy @@ -0,0 +1,29 @@ +(import [hy.contrib.walk [*]]) + +(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] + (.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])))) + +(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)))))))) 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])) -