diff --git a/.travis.yml b/.travis.yml index 2b44aa0..9f8dea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,21 @@ sudo: false language: python -python: - - "pypy" - - "2.7" - - "3.3" - - "3.4" -cache: pip -# command to run tests -script: make travis +matrix: + include: + - python: 3.5 + env: TOXENV=py35 +env: + - TOXENV=py27 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=pypy + - TOXENV=flake8 +install: pip install tox +script: tox +cache: + directories: + - .tox + - $HOME/.cache/pip after_success: make coveralls notifications: email: diff --git a/AUTHORS b/AUTHORS index 631d741..e4a45bc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -65,3 +65,7 @@ * Antony Woods * Matthew Egan Odendahl * Tim Martin +* Johnathon Mlady +* Andrew Savchyn +* Lev Kravinsky +* Luna Lunapiena diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7d451d4..01ba87a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -6,11 +6,20 @@ helps in making Hy more awesome. Pull requests are great! We love them; here is a quick guide: -- Fork the repo and create a topic branch for a feature/fix. Avoid +- `Fork the repo`_ and create a topic branch for a feature/fix. Avoid making changes directly on the master branch. + (If you're new to Git: `Start Here`_) - All incoming features should be accompanied with tests. +- If you are contributing a major change to the Hy language (e.g. changing + the behavior of or removing functions or macros), or you're unsure of + the proposed change, please open an issue in the `issue tracker`_ before + submitting the PR. This will allow others to give feedback on your idea, + and it will avoid constant changes or wasted work. For other PRs (such as + documentation fixes or code cleanup), you can directly open the PR without + first opening a corresponding issue. + - Before you submit a PR, please run the tests and check your code against the style guide. You can do both of these things at once:: @@ -82,3 +91,6 @@ version 1.1.0, available at http://contributor-covenant.org/version/1/1/0/. .. _Contributor Covenant: http://contributor-covenant.org +.. _issue tracker: https://github.com/hylang/hy/issues +.. _Fork the Repo: https://help.github.com/articles/fork-a-repo/ +.. _Start Here: http://rogerdudler.github.io/git-guide/) \ No newline at end of file diff --git a/Makefile b/Makefile index 884648c..ccf8456 100644 --- a/Makefile +++ b/Makefile @@ -67,14 +67,6 @@ endif $(pip) install coveralls $(pip) install --allow-all-external -e . -travis: python - $(nose) -s --with-coverage --cover-package hy -ifeq (PyPy,$(findstring PyPy,$(shell python -V 2>&1 | tail -1))) - @echo "skipping flake8 on pypy" -else - flake8 hy bin tests -endif - coveralls: $(coveralls) diff --git a/NEWS b/NEWS index be48b1f..ece2092 100644 --- a/NEWS +++ b/NEWS @@ -60,7 +60,7 @@ Changes from 0.10.0 * nth returns default value when out of bounds * merge-with added * doto macro added - * keyword? to findout keywords + * keyword? to find out keywords * setv no longer allows "." in names [Internals ] @@ -107,7 +107,7 @@ Changes from 0.9.12 Many thanks to algernon for working on adderall, which helped push Hy further this cycle. Adderall is an implementation of miniKanren in Hy. If you're interested in using Adderall, check out hydiomatic, - which prettfies Hy source using Adderall rules. + which prettifies Hy source using Adderall rules. This release saw an increase of about 11 contributors for a point release, you guys rock! diff --git a/README.md b/README.md index 54ab956..3c1eb38 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Hy [![Version](https://img.shields.io/pypi/v/hy.svg)](https://pypi.python.org/pypi/hy) [![Coverage Status](https://img.shields.io/coveralls/hylang/hy/master.svg)](https://coveralls.io/r/hylang/hy) -![XKCD #224](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png) +[![XKCD #224](https://raw.github.com/hylang/shyte/18f6925e08684b0e1f52b2cc2c803989cd62cd91/imgs/xkcd.png)](https://xkcd.com/224/) Lisp and Python should love each other. Let's make it happen. [Try it](http://try-hy.appspot.com/). @@ -32,7 +32,7 @@ It's really awesome. Oh, and lisps are neat. -![Cuddles the Hacker](http://i.imgur.com/QbPMXTN.png) +![Cuddles the Hacker](https://i.imgur.com/QbPMXTN.png) (fan art from the one and only [doctormo](http://doctormo.deviantart.com/art/Cuddles-the-Hacker-372184766)) @@ -46,3 +46,4 @@ Project * Bug reports: We have no bugs! Your bugs are your own! (https://github.com/hylang/hy/issues) * License: MIT (Expat) * [Contributor Guidelines & Code of Conduct](https://github.com/hylang/hy/blob/master/CONTRIBUTING.rst) +* IRC: Join #hy on [freenode](https://webchat.freenode.net/) diff --git a/docs/contrib/anaphoric.rst b/docs/contrib/anaphoric.rst index db36ffc..cce39e0 100644 --- a/docs/contrib/anaphoric.rst +++ b/docs/contrib/anaphoric.rst @@ -13,6 +13,9 @@ concise and easy to read. -- Wikipedia (http://en.wikipedia.org/wiki/Anaphoric_macro) +To use these macros you need to require the hy.contrib.anaphoric module like so: + +``(require hy.contrib.anaphoric)`` .. _ap-if: @@ -233,7 +236,7 @@ xi Usage ``(xi body ...)`` -Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for lambda. The xi forms cannot be nested. +Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for lambda. The xi forms cannot be nested. This is similar to Clojure's anonymous function literals (``#()``). @@ -244,5 +247,3 @@ This is similar to Clojure's anonymous function literals (``#()``). => (def add-10 (xi + 10 x1)) => (add-10 6) 16 - - diff --git a/docs/language/api.rst b/docs/language/api.rst index 3ee2374..c96e0b6 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -400,7 +400,7 @@ below: .. _defn: defn ------------- +---- ``defn`` macro is used to define functions. It takes three parameters: the *name* of the function to define, a vector of *parameters*, @@ -430,7 +430,26 @@ Parameters may have the following keywords in front of them: 101.0 &key + Parameter is a dict of keyword arguments. The keys of the dict + specify the parameter names and the values give the default values + of the parameters. + .. code-block:: clj + + => (defn key-parameters [&key {"a" 1 "b" 2}] + ... (print "a is" a "and b is" b)) + => (key-parameters :a 1 :b 2) + a is 1 and b is 2 + => (key-parameters :b 1 :a 2) + a is 2 and b is 1 + + The following declarations are equivalent: + + .. code-block:: clj + + (defn key-parameters [&key {"a" 1 "b" 2}]) + + (defn key-parameters [&optional [a 1] [b 2]]) &kwargs Parameter will contain 0 or more keyword arguments. @@ -1489,6 +1508,27 @@ expands to: Section :ref:`using-gensym` +xor +--- + +.. versionadded:: 0.12.0 + +``xor`` is used in logical expressions to perform exclusive or. It takes two +parameters. It returns ``True`` if only of the parameters is ``True``. In all +other cases ``False`` is returned. Example usage: + +.. code-block:: clj + + => (xor True False) + True + + => (xor True True) + False + + => (xor [] [0]) + True + + yield ----- @@ -1502,7 +1542,7 @@ infinite series without consuming infinite amount of memory. .. code-block:: clj => (defn multiply [bases coefficients] - ... (for [[(, base coefficient) (zip bases coefficients)]] + ... (for [(, base coefficient) (zip bases coefficients)] ... (yield (* base coefficient)))) => (multiply (range 5) (range 5)) @@ -1514,7 +1554,7 @@ infinite series without consuming infinite amount of memory. => (import random) => (defn random-numbers [low high] ... (while True (yield (.randint random low high)))) - => (list-comp x [x (take 15 (random-numbers 1 50))])]) + => (list-comp x [x (take 15 (random-numbers 1 50))]) [7, 41, 6, 22, 32, 17, 5, 38, 18, 38, 17, 14, 23, 23, 19] diff --git a/docs/language/internals.rst b/docs/language/internals.rst index 9fb1d53..d543847 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -177,7 +177,7 @@ A cons cell is a 2-item object, containing a ``car`` (head) and a 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 +``HyExpression``. However, the ``HyCons`` mimics the behavior of "usual" Lisp variants thusly: - ``(cons something nil)`` is ``(HyExpression [something])`` @@ -386,7 +386,7 @@ A first pass might be something like: [(zero? obscure-name) ~zero-form] [(neg? obscure-name) ~neg-form]))) -where ``obsure-name`` is an attempt to pick some variable name as not to +where ``obscure-name`` is an attempt to pick some variable name as not to conflict with other code. But of course, while well-intentioned, this is no guarantee. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1809a1f..24bca21 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -31,13 +31,18 @@ Quickstart 7. Open up an elite programming editor and type:: + #! /usr/bin/env hy (print "I was going to code in Python syntax, but then I got Hy.") 8. Save as ``awesome.hy``. -9. And run your first Hy program:: +9. Make it executable:: - hy awesome.hy + chmod +x awesome.hy -10. Take a deep breath so as to not hyperventilate. -11. Smile villainously and sneak off to your hydeaway and do +10. And run your first Hy program:: + + ./awesome.hy + +11. Take a deep breath so as to not hyperventilate. +12. Smile villainously and sneak off to your hydeaway and do unspeakable things. diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 2e3bd12..b577d79 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -9,7 +9,7 @@ Hy Style Guide The Hy style guide intends to be a set of ground rules for the Hyve (yes, the Hy community prides itself in appending Hy to everything) to write idiomatic Hy code. Hy derives a lot from Clojure & Common -Lisp, while always maintaining Python interopability. +Lisp, while always maintaining Python interoperability. Prelude diff --git a/docs/tutorial.rst b/docs/tutorial.rst index a16ac51..f455bd3 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -119,7 +119,7 @@ processing"; this means that the structure of the program is actually lists of lists. (If you're familiar with Python lists, imagine the entire same structure as above but with square brackets instead, any you'll be able to see the structure above as both a -program and a datastructure.) This is easier to understand with more +program and a data structure.) This is easier to understand with more examples, so let's write a simple Python program, test it, and then show the equivalent Hy program:: @@ -168,7 +168,7 @@ There are some advantages to having a code structure that's actually a very simple data structure as the core of Lisp is based on. For one thing, it means that your programs are easy to parse and that the entire actual structure of the program is very clearly exposed to you. -(There's an extra step in hy where the structure you see is converted +(There's an extra step in Hy where the structure you see is converted to Python's own representations ... in "purer" Lisps such as Common Lisp or Emacs Lisp, the data structure you see in the code and the data structure that is executed is much more literally close.) @@ -258,6 +258,7 @@ In Hy, you would do: .. code-block:: clj + (setv somevar 33) (cond [(> somevar 50) (print "That variable is too big!")] @@ -306,6 +307,13 @@ Comments start with semicolons: ; (print "but this will not") (+ 1 2 3) ; we'll execute the addition, but not this comment! +Hashbang (``#!``) syntax is supported: + +.. code-block:: clj + + #! /usr/bin/env hy + (print "Make me executable, and run me!") + Looping is not hard but has a kind of special structure. In Python, we might do:: @@ -505,22 +513,22 @@ In Hy: Macros ====== -One really powerful feature of Hy are macros. They are small functios that are +One really powerful feature of Hy are macros. They are small functions that are used to generate code (or data). When program written in Hy is started, the -macros are executed and their output is placed in program source. After this, +macros are executed and their output is placed in the program source. After this, the program starts executing normally. Very simple example: .. code-block:: clj => (defmacro hello [person] ... `(print "Hello there," ~person)) - => (Hello "Tuukka") + => (hello "Tuukka") Hello there, Tuukka The thing to notice here is that hello macro doesn't output anything on screen. Instead it creates piece of code that is then executed and prints on -screen. Macro writes a piece of program that looks like this (provided that -we used "Tuukka" as parameter: +screen. This macro writes a piece of program that looks like this (provided that +we used "Tuukka" as parameter): .. code-block:: clj @@ -536,14 +544,14 @@ We can also manipulate code with macros: => (rev (1 2 3 +)) 6 -The code that was generated with this macro just switched around some the -elements, so by the time program started executing, it actually red: +The code that was generated with this macro just switched around some of the +elements, so by the time program started executing, it actually reads: .. code-block:: clj (+ 1 2 3) -Sometimes it's nice to have a very short name for macro that doesn't take much +Sometimes it's nice to have a very short name for a macro that doesn't take much space or use extra parentheses. Reader macros can be pretty useful in these situations (and since Hy operates well with unicode, we aren't running out of characters that soon): @@ -556,14 +564,14 @@ characters that soon): => #↻(1 2 3 +) 6 -Macros are useful when one wishes to extend the Hy or write their own +Macros are useful when one wishes to extend Hy or write their own language on top of that. Many features of Hy are macros, like ``when``, ``cond`` and ``->``. To use macros defined in a different module, it is not enough to ``import`` the module, because importing happens at run-time, while we would need macros at compile-time. Instead of importing the module -with macros, it must be ``require``d: +with macros, ``require`` must be used: .. code-block:: clj diff --git a/hy/cmdline.py b/hy/cmdline.py index 142eb00..195a5bc 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -97,7 +97,7 @@ class HyREPL(code.InteractiveConsole): if e.source is None: e.source = source e.filename = filename - sys.stderr.write(str(e)) + print(e, file=sys.stderr) return False try: @@ -110,7 +110,7 @@ class HyREPL(code.InteractiveConsole): e.source = source e.filename = filename if SIMPLE_TRACEBACKS: - sys.stderr.write(str(e)) + print(e, file=sys.stderr) else: self.showtraceback() return False @@ -179,16 +179,18 @@ require("hy.cmdline", "__main__") SIMPLE_TRACEBACKS = True -def run_command(source): +def pretty_error(func, *args, **kw): try: - import_buffer_to_module("__main__", source) + return func(*args, **kw) except (HyTypeError, LexException) as e: if SIMPLE_TRACEBACKS: - sys.stderr.write(str(e)) - return 1 - raise - except Exception: + print(e, file=sys.stderr) + sys.exit(1) raise + + +def run_command(source): + pretty_error(import_buffer_to_module, "__main__", source) return 0 @@ -199,22 +201,14 @@ def run_module(mod_name): sys.argv = [pth] + sys.argv return run_file(pth) - sys.stderr.write("{0}: module '{1}' not found.\n".format(hy.__appname__, - mod_name)) + print("{0}: module '{1}' not found.\n".format(hy.__appname__, mod_name), + file=sys.stderr) return 1 def run_file(filename): from hy.importer import import_file_to_module - try: - import_file_to_module("__main__", filename) - except (HyTypeError, LexException) as e: - if SIMPLE_TRACEBACKS: - sys.stderr.write(str(e)) - return 1 - raise - except Exception: - raise + pretty_error(import_file_to_module, "__main__", filename) return 0 @@ -334,8 +328,8 @@ def cmdline_handler(scriptname, argv): try: return run_file(options.args[0]) except HyIOError as e: - sys.stderr.write("hy: Can't open file '%s': [Errno %d] %s\n" % - (e.filename, e.errno, e.strerror)) + print("hy: Can't open file '{0}': [Errno {1}] {2}\n".format( + e.filename, e.errno, e.strerror), file=sys.stderr) sys.exit(e.errno) # User did NOTHING! @@ -359,11 +353,11 @@ def hyc_main(): for file in options.files: try: - write_hy_as_pyc(file) print("Compiling %s" % file) + pretty_error(write_hy_as_pyc, file) except IOError as x: - sys.stderr.write("hyc: Can't open file '%s': [Errno %d] %s\n" % - (x.filename, x.errno, x.strerror)) + print("hyc: Can't open file '{0}': [Errno {1}] {2}\n".format( + x.filename, x.errno, x.strerror), file=sys.stderr) sys.exit(x.errno) @@ -391,7 +385,7 @@ def hy2py_main(): parser.exit(1, parser.format_help()) if options.with_source: - hst = import_file_to_hst(options.args[0]) + hst = pretty_error(import_file_to_hst, options.args[0]) # need special printing on Windows in case the # codepage doesn't support utf-8 characters if PY3 and platform.system() == "Windows": @@ -405,7 +399,7 @@ def hy2py_main(): print() print() - _ast = import_file_to_ast(options.args[0], module_name) + _ast = pretty_error(import_file_to_ast, options.args[0], module_name) if options.with_ast: if PY3 and platform.system() == "Windows": _print_for_windows(astor.dump(_ast)) diff --git a/hy/compiler.py b/hy/compiler.py index 8032153..c2e4dc3 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -532,7 +532,7 @@ class HyASTCompiler(object): raise HyTypeError(expr, "There can only be one " "&rest argument") - varargs = str(expr) + varargs = expr elif lambda_keyword == "&key": if type(expr) != HyDict: raise HyTypeError(expr, @@ -547,6 +547,10 @@ class HyASTCompiler(object): # defining keyword arguments. it = iter(expr) for k, v in zip(it, it): + if not isinstance(k, HyString): + raise HyTypeError(expr, + "Only strings can be used " + "as parameter names") args.append(k) ret += self.compile(v) defaults.append(ret.force_expr) @@ -560,6 +564,10 @@ class HyASTCompiler(object): else: k = expr v = HySymbol("None").replace(k) + if not isinstance(k, HyString): + raise HyTypeError(expr, + "Only strings can be used as " + "parameter names") args.append(k) ret += self.compile(v) defaults.append(ret.force_expr) @@ -586,25 +594,26 @@ class HyASTCompiler(object): raise HyTypeError(expr, "There can only be one " "&kwargs argument") - kwargs = str(expr) + kwargs = expr return ret, args, defaults, varargs, kwonlyargs, kwonlydefaults, kwargs - def _storeize(self, name, func=None): + def _storeize(self, expr, name, func=None): """Return a new `name` object with an ast.Store() context""" if not func: func = ast.Store if isinstance(name, Result): if not name.is_expr(): - raise TypeError("Can't assign / delete a non-expression") + raise HyTypeError(expr, + "Can't assign or delete a non-expression") name = name.expr if isinstance(name, (ast.Tuple, ast.List)): typ = type(name) new_elts = [] for x in name.elts: - new_elts.append(self._storeize(x, func)) + new_elts.append(self._storeize(expr, x, func)) new_name = typ(elts=new_elts) elif isinstance(name, ast.Name): new_name = ast.Name(id=name.id, arg=name.arg) @@ -613,7 +622,9 @@ class HyASTCompiler(object): elif isinstance(name, ast.Attribute): new_name = ast.Attribute(value=name.value, attr=name.attr) else: - raise TypeError("Can't assign / delete a %s object" % type(name)) + raise HyTypeError(expr, + "Can't assign or delete a %s" % + type(expr).__name__) new_name.ctx = func() ast.copy_location(new_name, name) @@ -953,7 +964,7 @@ class HyASTCompiler(object): name = ast_str(name) else: # Python2 requires an ast.Name, set to ctx Store. - name = self._storeize(self.compile(name)) + name = self._storeize(name, self.compile(name)) else: name = None @@ -971,7 +982,7 @@ class HyASTCompiler(object): col_offset=expr.start_column, ctx=ast.Load()) else: - # [] → all exceptions catched + # [] → all exceptions caught _type = Result() elif isinstance(exceptions_list, HySymbol): _type = self.compile(exceptions_list) @@ -1343,11 +1354,13 @@ class HyASTCompiler(object): col_offset=root.start_column) return result - ld_targets, ret, _ = self._compile_collect(expr) - del_targets = [] - for target in ld_targets: - del_targets.append(self._storeize(target, ast.Del)) + ret = Result() + for target in expr: + compiled_target = self.compile(target) + ret += compiled_target + del_targets.append(self._storeize(target, compiled_target, + ast.Del)) return ret + ast.Delete( lineno=expr.start_line, @@ -1436,7 +1449,7 @@ class HyASTCompiler(object): thing = None if args != []: - thing = self._storeize(self.compile(args.pop(0))) + thing = self._storeize(args[0], self.compile(args.pop(0))) body = self._compile_branch(expr) @@ -1498,7 +1511,7 @@ class HyASTCompiler(object): gen = [] for target, iterable in paired_gens: comp_target = self.compile(target) - target = self._storeize(comp_target) + target = self._storeize(target, comp_target) gen_res += self.compile(iterable) gen.append(ast.comprehension( target=target, @@ -1963,7 +1976,7 @@ class HyASTCompiler(object): op = ops[expression[0]] - target = self._storeize(self.compile(expression[1])) + target = self._storeize(expression[1], self.compile(expression[1])) ret = self.compile(expression[2]) ret += ast.AugAssign( @@ -1984,7 +1997,7 @@ class HyASTCompiler(object): @builds(HyExpression) def compile_expression(self, expression): # Perform macro expansions - expression = macroexpand(expression, self.module_name) + expression = macroexpand(expression, self) if not isinstance(expression, HyExpression): # Go through compile again if the type changed. return self.compile(expression) @@ -2099,7 +2112,7 @@ class HyASTCompiler(object): and '.' not in name: result.rename(name) else: - st_name = self._storeize(ld_name) + st_name = self._storeize(name, ld_name) result += ast.Assign( lineno=start_line, col_offset=start_column, @@ -2127,7 +2140,7 @@ class HyASTCompiler(object): raise HyTypeError(expression, "for requires two forms in the list") - target = self._storeize(self.compile(target_name)) + target = self._storeize(target_name, self.compile(target_name)) ret = Result() @@ -2257,10 +2270,14 @@ class HyASTCompiler(object): # list because it's really just an internal parsing thing. if kwargs: - kwargs = ast.arg(arg=kwargs, annotation=None) + kwargs = ast.arg(arg=ast_str(kwargs), annotation=None, + lineno=kwargs.start_line, + col_offset=kwargs.start_column) if stararg: - stararg = ast.arg(arg=stararg, annotation=None) + stararg = ast.arg(arg=ast_str(stararg), annotation=None, + lineno=stararg.start_line, + col_offset=stararg.start_column) # Let's find a better home for these guys. else: @@ -2275,6 +2292,12 @@ class HyASTCompiler(object): col_offset=x.start_column) for x in kwonlyargs] + if kwargs: + kwargs = ast_str(kwargs) + + if stararg: + stararg = ast_str(stararg) + args = ast.arguments( args=args, vararg=stararg, @@ -2294,7 +2317,10 @@ class HyASTCompiler(object): return ret if body.expr: - if body.contains_yield: + if body.contains_yield and not PY33: + # Prior to PEP 380 (introduced in Python 3.3) + # generators may not have a value in a return + # statement. body += body.expr_as_stmt() else: body += ast.Return(value=body.expr, @@ -2381,7 +2407,7 @@ class HyASTCompiler(object): body += self.compile(rewire_init(expr)) for expression in expressions: - expr = rewire_init(macroexpand(expression, self.module_name)) + expr = rewire_init(macroexpand(expression, self)) body += self.compile(expr) self.allow_builtins = allow_builtins @@ -2441,7 +2467,7 @@ class HyASTCompiler(object): NOT_READERS = [":", "&"] if name in NOT_READERS or len(name) > 1: raise NameError("%s can't be used as a macro reader symbol" % name) - if not isinstance(name, HySymbol): + if not isinstance(name, HySymbol) and not isinstance(name, HyString): raise HyTypeError(name, ("received a `%s' instead of a symbol " "for reader macro name" % type(name).__name__)) @@ -2467,10 +2493,7 @@ class HyASTCompiler(object): "Trying to expand a reader macro using `{0}' instead " "of string".format(type(str_char).__name__), ) - - module = self.module_name - expr = reader_macroexpand(str_char, expression.pop(0), module) - + expr = reader_macroexpand(str_char, expression.pop(0), self) return self.compile(expr) @builds("eval_and_compile") @@ -2568,11 +2591,6 @@ def hy_compile(tree, module_name, root=ast.Module, get_expr=False): `last_expression` is the. """ - if hasattr(sys, "subversion"): - implementation = sys.subversion[0].lower() - elif hasattr(sys, "implementation"): - implementation = sys.implementation.name.lower() - body = [] expr = None @@ -2592,12 +2610,6 @@ def hy_compile(tree, module_name, root=ast.Module, get_expr=False): ret = root(body=body) - # PyPy _really_ doesn't like the ast going backwards... - if implementation != "cpython": - for node in ast.walk(ret): - node.lineno = 1 - node.col_offset = 1 - if get_expr: expr = ast.Expression(body=expr) ret = (ret, expr) diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index 019d6db..aaee36b 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -25,8 +25,9 @@ ;;; These macros make writing functional programs more concise -(defmacro ap-if (test-form &rest args) - `(let [it ~test-form] (if it ~@args))) +(defmacro ap-if [test-form then-form &optional else-form] + `(let [it ~test-form] + (if it ~then-form ~else-form))) (defmacro ap-each [lst &rest body] @@ -35,7 +36,7 @@ (defmacro ap-each-while [lst form &rest body] - "Evalutate the body form for each element in the list while the + "Evaluate the body form for each element in the list while the predicate form evaluates to True." `(let [p (lambda [it] ~form)] (for [it ~lst] @@ -140,7 +141,7 @@ (.startswith a 'x) (.isdigit (cdr a)))) [0]))))]) - ;; generate the &rest paremeter only if 'xi is present in body + ;; generate the &rest parameter only if 'xi is present in body ~@(if (in 'xi flatbody) '(&rest xi) '())] diff --git a/hy/core/language.hy b/hy/core/language.hy index ad81b3f..07bc202 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -37,6 +37,7 @@ [hy.models.symbol [HySymbol]] [hy.models.keyword [HyKeyword *keyword-prefix*]]) (import [hy.lex [LexException PrematureEndOfInput tokenize]]) +(import [hy.compiler [HyASTCompiler]]) (defn _numeric-check [x] (if (not (numeric? x)) @@ -296,14 +297,14 @@ (import hy.macros) (setv name (calling-module-name)) - (hy.macros.macroexpand form name)) + (hy.macros.macroexpand form (HyASTCompiler name))) (defn macroexpand-1 [form] "Return the single step macro expansion of form" (import hy.macros) (setv name (calling-module-name)) - (hy.macros.macroexpand-1 form name)) + (hy.macros.macroexpand-1 form (HyASTCompiler name))) (defn merge-with [f &rest maps] "Returns a map that consists of the rest of the maps joined onto @@ -463,6 +464,11 @@ (hyify (. value __name__)) (except [] (string value)))))) +(defn xor [a b] + "Perform exclusive or between two parameters" + (or (and a (not b)) + (and b (not a)))) + (def *exports* '[*map accumulate butlast calling-module-name chain coll? combinations compress cons cons? count cycle dec distinct disassemble drop drop-last @@ -472,4 +478,4 @@ last list* macroexpand macroexpand-1 map merge-with multicombinations name neg? nil? none? nth numeric? odd? partition permutations pos? product range read read-str remove repeat repeatedly rest reduce second some string - string? symbol? take take-nth take-while tee zero? zip zip-longest]) + string? symbol? take take-nth take-while xor tee zero? zip zip-longest]) diff --git a/hy/errors.py b/hy/errors.py index 931656f..34ef31c 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -25,8 +25,6 @@ import traceback from clint.textui import colored -from hy._compat import PY3 - class HyError(Exception): """ @@ -101,12 +99,9 @@ class HyTypeError(TypeError): result += colored.yellow("%s: %s\n\n" % (self.__class__.__name__, - self.message)) + self.message.encode('utf-8'))) - if not PY3: - return result.encode('utf-8') - else: - return result + return result class HyMacroExpansionError(HyTypeError): diff --git a/hy/macros.py b/hy/macros.py index d5821b9..154e258 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -52,6 +52,8 @@ def macro(name): """ def _(fn): + argspec = getargspec(fn) + fn._hy_macro_pass_compiler = argspec.keywords is not None module_name = fn.__module__ if module_name.startswith("hy.core"): module_name = None @@ -123,7 +125,7 @@ def load_macros(module_name): _import(module) -def make_emtpy_fn_copy(fn): +def make_empty_fn_copy(fn): argspec = getargspec(fn) formatted_args = formatargspec(*argspec) fn_str = 'lambda {}: None'.format( @@ -133,22 +135,22 @@ def make_emtpy_fn_copy(fn): return empty_fn -def macroexpand(tree, module_name): +def macroexpand(tree, compiler): """Expand the toplevel macros for the `tree`. Load the macros from the given `module_name`, then expand the (top-level) macros in `tree` until it stops changing. """ - load_macros(module_name) + load_macros(compiler.module_name) old = None while old != tree: old = tree - tree = macroexpand_1(tree, module_name) + tree = macroexpand_1(tree, compiler) return tree -def macroexpand_1(tree, module_name): +def macroexpand_1(tree, compiler): """Expand the toplevel macro from `tree` once, in the context of `module_name`.""" if isinstance(tree, HyExpression): @@ -161,22 +163,25 @@ def macroexpand_1(tree, module_name): ntree = HyExpression(tree[:]) ntree.replace(tree) + opts = {} + if isinstance(fn, HyString): - m = _hy_macros[module_name].get(fn) + m = _hy_macros[compiler.module_name].get(fn) if m is None: m = _hy_macros[None].get(fn) if m is not None: + if m._hy_macro_pass_compiler: + opts['compiler'] = compiler + try: - m_copy = make_emtpy_fn_copy(m) - m_copy(*ntree[1:]) + m_copy = make_empty_fn_copy(m) + m_copy(*ntree[1:], **opts) except TypeError as e: msg = "expanding `" + str(tree[0]) + "': " msg += str(e).replace("()", "", 1).strip() raise HyMacroExpansionError(tree, msg) - try: - obj = wrap_value(m(*ntree[1:])) - + obj = wrap_value(m(*ntree[1:], **opts)) except HyTypeError as e: if e.expression is None: e.expression = tree @@ -186,16 +191,15 @@ def macroexpand_1(tree, module_name): raise HyMacroExpansionError(tree, msg) replace_hy_obj(obj, tree) return obj - return ntree return tree -def reader_macroexpand(char, tree, module_name): +def reader_macroexpand(char, tree, compiler): """Expand the reader macro "char" with argument `tree`.""" - load_macros(module_name) + load_macros(compiler.module_name) - reader_macro = _hy_reader[module_name].get(char) + reader_macro = _hy_reader[compiler.module_name].get(char) if reader_macro is None: try: reader_macro = _hy_reader[None][char] diff --git a/setup.py b/setup.py index 6791e52..d2d8efd 100755 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ if sys.version_info[:2] < (2, 7): install_requires.append('argparse>=1.2.1') install_requires.append('importlib>=1.0.2') if os.name == 'nt': - install_requires.append('pyreadline==2.0') + install_requires.append('pyreadline>=2.1') setup( name=PKG, @@ -89,6 +89,7 @@ setup( "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Compilers", "Topic :: Software Development :: Libraries", diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 41156a3..4898c40 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -372,6 +372,7 @@ def test_ast_lambda_lists(): cant_compile('(fn [&key {"a" b} &key {"foo" bar}] [a foo])') cant_compile('(fn [&optional a &key {"foo" bar}] [a foo])') cant_compile('(fn [&optional [a b c]] a)') + cant_compile('(fn [&optional [1 2]] (list 1 2))') def test_ast_print(): @@ -402,6 +403,7 @@ def test_lambda_list_keywords_key(): """ Ensure we can compile functions with &key.""" can_compile("(fn (x &key {foo True}) (list x foo))") cant_compile("(fn (x &key {bar \"baz\"} &key {foo 42}) (list x bar foo))") + cant_compile("(fn (x &key {1 2 3 4}) (list x))") def test_lambda_list_keywords_kwargs(): diff --git a/tests/compilers/test_compiler.py b/tests/compilers/test_compiler.py index 59801cd..063bdda 100644 --- a/tests/compilers/test_compiler.py +++ b/tests/compilers/test_compiler.py @@ -26,6 +26,8 @@ from hy import compiler from hy.models.expression import HyExpression from hy.models.list import HyList from hy.models.symbol import HySymbol +from hy.models.integer import HyInteger +from hy._compat import PY33 if sys.version_info[0] <= 2 and sys.version_info[1] <= 6: import unittest2 as unittest @@ -98,3 +100,37 @@ class HyASTCompilerTest(unittest.TestCase): expr = ret.expr self.assertIsInstance(expr, ast.Name) self.assertEqual(expr.id, "c") + + def test_compiler_yield_return(self): + """ + Check that the compiler correctly generates return statements for + a generator function. In Python versions prior to 3.3, the return + statement in a generator can't take a value, so the final expression + should not generate a return statement. From 3.3 onwards a return + value should be generated. + """ + ret = self.c.compile_function_def( + self._make_expression(HySymbol("fn"), + HyList(), + HyExpression([HySymbol("yield"), + HyInteger(2)]), + HyExpression([HySymbol("+"), + HyInteger(1), + HyInteger(1)]))) + + self.assertEqual(len(ret.stmts), 1) + stmt = ret.stmts[0] + self.assertIsInstance(stmt, ast.FunctionDef) + body = stmt.body + self.assertEquals(len(body), 2) + self.assertIsInstance(body[0], ast.Expr) + self.assertIsInstance(body[0].value, ast.Yield) + + if PY33: + # From 3.3+, the final statement becomes a return value + self.assertIsInstance(body[1], ast.Return) + self.assertIsInstance(body[1].value, ast.BinOp) + else: + # In earlier versions, the expression is not returned + self.assertIsInstance(body[1], ast.Expr) + self.assertIsInstance(body[1].value, ast.BinOp) diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index 03ff61b..9cb17c2 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -8,6 +8,8 @@ from hy.models.symbol import HySymbol from hy.models.expression import HyExpression from hy.errors import HyMacroExpansionError +from hy.compiler import HyASTCompiler + @macro("test") def tmac(*tree): @@ -17,14 +19,16 @@ def tmac(*tree): def test_preprocessor_simple(): """ Test basic macro expansion """ - obj = macroexpand(tokenize('(test "one" "two")')[0], __name__) + obj = macroexpand(tokenize('(test "one" "two")')[0], + HyASTCompiler(__name__)) assert obj == HyList(["one", "two"]) assert type(obj) == HyList def test_preprocessor_expression(): """ Test that macro expansion doesn't recurse""" - obj = macroexpand(tokenize('(test (test "one" "two"))')[0], __name__) + obj = macroexpand(tokenize('(test (test "one" "two"))')[0], + HyASTCompiler(__name__)) assert type(obj) == HyList assert type(obj[0]) == HyExpression @@ -35,13 +39,13 @@ def test_preprocessor_expression(): obj = HyList([HyString("one"), HyString("two")]) obj = tokenize('(shill ["one" "two"])')[0][1] - assert obj == macroexpand(obj, '') + assert obj == macroexpand(obj, HyASTCompiler("")) def test_preprocessor_exceptions(): """ Test that macro expansion raises appropriate exceptions""" try: - macroexpand(tokenize('(defn)')[0], __name__) + macroexpand(tokenize('(defn)')[0], HyASTCompiler(__name__)) assert False except HyMacroExpansionError as e: assert "_hy_anon_fn_" not in str(e) diff --git a/tests/macros/test_reader_macros.py b/tests/macros/test_reader_macros.py index 791a046..0104d87 100644 --- a/tests/macros/test_reader_macros.py +++ b/tests/macros/test_reader_macros.py @@ -1,11 +1,12 @@ from hy.macros import macroexpand -from hy.compiler import HyTypeError +from hy.compiler import HyTypeError, HyASTCompiler from hy.lex import tokenize def test_reader_macro_error(): - """Check if we get correct error with wrong disptach character""" + """Check if we get correct error with wrong dispatch character""" try: - macroexpand(tokenize("(dispatch_reader_macro '- '())")[0], __name__) + macroexpand(tokenize("(dispatch_reader_macro '- '())")[0], + HyASTCompiler(__name__)) except HyTypeError as e: assert "with the character `-`" in str(e) diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy index 9e0ddd9..69caac2 100644 --- a/tests/native_tests/contrib/anaphoric.hy +++ b/tests/native_tests/contrib/anaphoric.hy @@ -18,10 +18,11 @@ ;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;; DEALINGS IN THE SOFTWARE. -;;;; some simple helpers - +(import [hy.errors [HyMacroExpansionError]]) (require hy.contrib.anaphoric) +;;;; some simple helpers + (defn assert-true [x] (assert (= True x))) @@ -35,7 +36,10 @@ (defn test-ap-if [] "NATIVE: testing anaphoric if" (ap-if true (assert-true it)) - (ap-if false true (assert-false it))) + (ap-if false true (assert-false it)) + (try (macroexpand '(ap-if true)) + (except [HyMacroExpansionError] true) + (else (assert false)))) (defn test-ap-each [] "NATIVE: testing anaphoric each" diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 8c7533a..0300ffb 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -1,7 +1,8 @@ (import [tests.resources [kwtest function-with-a-dash]] [os.path [exists isdir isfile]] [sys :as systest] - [operator [or_]]) + [operator [or_]] + [hy.errors [HyTypeError]]) (import sys) (import [hy._compat [PY33 PY34 PY35]]) @@ -60,6 +61,7 @@ (setv (get foo 0) 12) (assert (= (get foo 0) 12))) + (defn test-setv-builtin [] "NATIVE: test that setv doesn't work on builtins" (try (eval '(setv False 1)) @@ -93,6 +95,37 @@ (except [e [TypeError]] (assert (in "`setv' needs an even number of arguments" (str e)))))) +(defn test-store-errors [] + "NATIVE: test that setv raises the correct errors when given wrong argument types" + (try + (do + (eval '(setv (do 1 2) 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a non-expression")))) + + (try + (do + (eval '(setv 1 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a HyInteger")))) + + (try + (do + (eval '(setv {1 2} 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a HyDict")))) + + (try + (do + (eval '(del 1 1)) + (assert false)) + (except [e HyTypeError] + (assert (= e.message "Can't assign or delete a HyInteger"))))) + + (defn test-fn-corner-cases [] "NATIVE: tests that fn/defn handles corner cases gracefully" (try (eval '(fn "foo")) @@ -894,6 +927,15 @@ (assert (= a 1))) +(defn test-xor [] + "NATIVE: test the xor macro" + (let [xor-both-true (xor true true) + xor-both-false (xor false false) + xor-true-false (xor true false)] + (assert (= xor-both-true false)) + (assert (= xor-both-false false)) + (assert (= xor-true-false true)))) + (defn test-if-return-branching [] "NATIVE: test the if return branching" ; thanks, algernon @@ -965,7 +1007,6 @@ (defn test-eval-failure [] "NATIVE: test eval failure modes" - (import [hy.errors [HyTypeError]]) ; yo dawg (try (eval '(eval)) (except [e HyTypeError]) (else (assert False))) (try (eval '(eval "snafu")) (except [e HyTypeError]) (else (assert False))) diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy index be61c5c..8ba770e 100644 --- a/tests/native_tests/reader_macros.hy +++ b/tests/native_tests/reader_macros.hy @@ -2,7 +2,7 @@ (defn test-reader-macro [] - "Test a basic redaer macro" + "Test a basic reader macro" (defreader ^ [expr] expr) @@ -37,6 +37,15 @@ (assert (= (, 1 2 3) a))) +(defn test-reader-macro-string-name [] + "Test if defreader accepts a string as a macro name." + + (defreader "." [expr] + expr) + + (assert (= #."works" "works"))) + + (defn test-builtin-decorator-reader [] (defn increment-arguments [func] "Increments each argument passed to the decorated function." @@ -55,7 +64,7 @@ (assert (= (, (, 2 3 4) {"quux" 5 "baz" 6}) (foo 1 2 3 :quux 4 :baz 5))) - ;; @wraps preserved the doctstring and __name__ + ;; @wraps preserved the docstring and __name__ (assert (= "foo" (. foo --name--))) (assert (= "Bar." (. foo --doc--))) diff --git a/tox.ini b/tox.ini index 9789610..2bea02d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,13 @@ [tox] -envlist = py27,pypy,py33,flake8 +envlist = py27,pypy,py33,py34,py35,flake8 skipsdist = True [testenv] commands = pip install --allow-all-external -e . nosetests +passenv = + TERM deps = -rrequirements-dev.txt