diff --git a/AUTHORS b/AUTHORS index 0f5bc7f..311903b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,3 +17,4 @@ * Bob Tolbert * Ralph Möritz * Josh McLaughlin +* Berker Peksag diff --git a/docs/hacking.rst b/docs/hacking.rst index 404f4f3..77e0036 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -2,6 +2,8 @@ Hacking on hy =============== +.. highlight:: bash + Join our hyve! ============== @@ -21,24 +23,39 @@ Hack! Do this: -1. create a `Python virtual environment - `_ -2. (optional) go to https://github.com/paultag/hy and fork it -3. get the source code:: +1. create a `virtual environment + `_:: - $ git clone git://github.com/paultag/hy.git + $ virtualenv venv - (or use your fork) -4. install for hacking:: + and activate it:: - $ python setup.py develop + $ . venv/bin/activate -5. install other develop-y requirements:: + or use `virtualenvwrapper `_ + to create and manage your virtual environment:: + + $ mkvirtualenv hy + $ workon hy + +2. get the source code:: + + $ git clone https://github.com/hylang/hy.git + + or use your fork:: + + $ git clone git@github.com:/hy.git +3. install for hacking:: + + $ cd hy/ + $ pip install -e . + +4. install other develop-y requirements:: $ pip install -r requirements-dev.txt -6. do awesome things; make someone shriek in delight/disgust at what - you have wrought +5. do awesome things; make someone shriek in delight/disgust at what + you have wrought. Test! @@ -60,7 +77,7 @@ Document! Documentation is located in ``docs/``. We use `Sphinx `_. -To build the docs in html:: +To build the docs in HTML:: $ cd docs $ make html diff --git a/docs/language/api.rst b/docs/language/api.rst index 9226bc5..3fe8beb 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -24,8 +24,8 @@ languages. * UTF-8 entities will be encoded using `punycode `_ and prefixed with - `hy_`. For instance, `⚘` will become `hy_w7h`, and `♥` will become - `hy_g6h`. + `hy_`. For instance, `⚘` will become `hy_w7h`, `♥` will become `hy_g6h`, + and `i♥u` will become `hy_iu_t0x`. * Symbols that contain dashes will have them replaced with underscores. For example, `render-template` will become `render_template`. diff --git a/hy/util.py b/hy/_compat.py similarity index 86% rename from hy/util.py rename to hy/_compat.py index c0fc193..e30d0dc 100644 --- a/hy/util.py +++ b/hy/_compat.py @@ -19,10 +19,20 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +try: + import __builtin__ as builtins +except ImportError: + import builtins # NOQA import sys +PY3 = sys.version_info[0] >= 3 -if sys.version_info[0] >= 3: +if PY3: str_type = str else: str_type = unicode # NOQA + +if PY3: + long_type = int +else: + long_type = long # NOQA diff --git a/hy/cmdline.py b/hy/cmdline.py index 32afd05..031eb33 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -41,11 +41,7 @@ from hy.models.expression import HyExpression from hy.models.string import HyString from hy.models.symbol import HySymbol - -try: - import __builtin__ as builtins -except ImportError: - import builtins +from hy._compat import builtins class HyQuitter(object): diff --git a/hy/compiler.py b/hy/compiler.py index 040e924..f14c79f 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -36,8 +36,8 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict -from hy.macros import require, process -from hy.util import str_type +from hy.macros import require, macroexpand +from hy._compat import str_type import hy.importer import traceback @@ -419,7 +419,7 @@ class HyASTCompiler(object): def compile(self, tree): try: - tree = process(tree, self.module_name) + tree = macroexpand(tree, self.module_name) _type = type(tree) ret = self.compile_atom(_type, tree) if ret: @@ -610,7 +610,7 @@ class HyASTCompiler(object): level) imports.update(f_imports) if splice: - to_add = f_contents + to_add = HyExpression([HySymbol("list"), f_contents]) else: to_add = HyList([f_contents]) diff --git a/hy/completer.py b/hy/completer.py index 4a6df63..96c46af 100644 --- a/hy/completer.py +++ b/hy/completer.py @@ -43,15 +43,11 @@ except ImportError: import hy.macros import hy.compiler -try: - import __builtin__ -except ImportError: - import builtins as __builtin__ # NOQA - +from hy._compat import builtins PATH = [hy.compiler._compile_table, hy.macros._hy_macros, - __builtin__.__dict__] + builtins.__dict__] class Completer(object): diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy new file mode 100644 index 0000000..1f4ae70 --- /dev/null +++ b/hy/contrib/meth.hy @@ -0,0 +1,54 @@ +;;; Meth +;; based on paultag's meth library to access a Flask based application + +(defmacro route [name path params code] + "Default get request" + `(let [[deco (.route app ~path)]] + (with-decorator deco + (defn ~name ~params ~@code)))) + +(defmacro route-with-methods [name path params code methods] + "Same as route but with an extra methods array to specify HTTP methods" + `(let [[deco (kwapply (.route app ~path) + {"methods" ~methods})]] + (with-decorator deco + (defn ~name ~params ~@code)))) + +;; Some macro examples +(defmacro post-route [name path params code] + "Post request" + `(route-with-methods ~name ~path ~params ~code ["POST"])) + +(defmacro put-route [name path params code] + "Put request" + `(route-with-methods ~name ~path ~params ~code ["PUT"])) + +(defmacro delete-route [name path params code] + "Delete request" + `(route-with-methods ~name ~path ~params ~code ["DELETE"])) + + +;;; Simple example application +;;; Requires to have Flask installed + +;; (import [flask [Flask]]) +;; (setv app (Flask "__main__")) + +;; (require methy) + +;; (print "setup / with GET") +;; (route get-index "/" [] (str "Hy world!")) + +;; (print "setup /post with POST") +;; (post-route post-index "/post" [] (str "Hy post world!")) + +;; (route-with-methods both-index "/both" [] +;; (str "Hy to both worlds!") ["GET" "POST"]) + +;; (.run app) + +;;; Now you can do: +;;; curl 127.0.0.1:5000 +;;; curl -X POST 127.0.0.1:5000/post +;;; curl -X POST 127.0.0.1:5000/both +;;; curl 127.0.0.1:5000/both diff --git a/hy/contrib/meth.py b/hy/contrib/meth.py deleted file mode 100644 index eacd443..0000000 --- a/hy/contrib/meth.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2013 Paul Tagliamonte -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -from hy.models.expression import HyExpression -from hy.models.symbol import HySymbol -from hy.models.string import HyString -from hy.models.list import HyList -from hy.models.dict import HyDict - -from hy.macros import macro - - -def router(tree, rkwargs=None): - tree = HyExpression(tree) - name = tree.pop(0) - path = tree.pop(0) - tree.insert(0, HySymbol("fn")) - tree = HyExpression([HySymbol("def"), name, tree]) - - route = HyExpression([HySymbol(".route"), HySymbol("app"), path]) - - if rkwargs: - route = HyExpression([HySymbol("kwapply"), route, - HyDict({HyString("methods"): rkwargs})]) - - return HyExpression([HySymbol("with_decorator"), route, tree]) - - -@macro("route") -def route_macro(*tree): - return router(tree) - - -@macro("post_route") -def post_route_macro(*tree): - return router(tree, rkwargs=HyList([HyString("POST")])) - - -@macro("get_route") -def get_route_macro(*tree): - return router(tree, rkwargs=HyList([HyString("GET")])) diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy new file mode 100644 index 0000000..69fbfbb --- /dev/null +++ b/hy/core/bootstrap.hy @@ -0,0 +1,74 @@ +;;; Hy bootstrap macros +;; +;; Copyright (c) 2013 Nicolas Dandrimont +;; Copyright (c) 2013 Paul Tagliamonte +;; Copyright (c) 2013 Konrad Hinsen +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. +;; +;;; These macros are the essential hy macros. +;;; They are automatically required everywhere, even inside hy.core modules. + +(import [hy.compiler [HyTypeError]]) + + +(defmacro macro-error [location reason] + "error out properly within a macro" + `(raise (HyTypeError ~location ~reason))) + + +(defmacro defmacro-alias [names lambda-list &rest body] + "define one macro with several names" + (setv ret `(do)) + (foreach [name names] + (.append ret + `(defmacro ~name ~lambda-list ~@body))) + ret) + + +(defmacro-alias [defn defun] [name lambda-list &rest body] + "define a function `name` with signature `lambda-list` and body `body`" + (if (not (= (type name) HySymbol)) + (macro-error name "defn/defun takes a name as first argument")) + `(setv ~name (fn ~lambda-list ~@body))) + + +(defmacro let [variables &rest body] + "Execute `body` in the lexical context of `variables`" + (setv macroed_variables []) + (if (not (isinstance variables HyList)) + (macro-error variables "let lexical context must be a list")) + (foreach [variable variables] + (if (isinstance variable HyList) + (do + (if (!= (len variable) 2) + (macro-error variable "let variable assignments must contain two items")) + (.append macroed-variables `(setv ~(get variable 0) ~(get variable 1)))) + (.append macroed-variables `(setv ~variable None)))) + `((fn [] + ~@macroed-variables + ~@body))) + + +(defmacro if-python2 [python2-form python3-form] + "If running on python2, execute python2-form, else, execute python3-form" + (import sys) + (if (< (get sys.version_info 0) 3) + python2-form + python3-form)) diff --git a/hy/core/bootstrap.py b/hy/core/bootstrap.py deleted file mode 100644 index eaedb28..0000000 --- a/hy/core/bootstrap.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (c) 2013 Paul Tagliamonte -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - - -from hy.macros import macro -from hy.models.expression import HyExpression -from hy.models.integer import HyInteger -from hy.models.symbol import HySymbol -from hy.models.list import HyList - - -@macro("defn") -@macro("defun") -def defn_macro(name, *body): - return HyExpression([HySymbol("def"), - name, HyExpression([HySymbol("fn")] + list(body))]) - - -@macro("cond") -def cond_macro(*tree): - it = iter(tree) - test, branch = next(it) - - root = HyExpression([HySymbol("if"), test, branch]) - ret = root - for (test, branch) in it: - n = HyExpression([HySymbol("if"), test, branch]) - ret.append(n) - ret = n - - return root - - -@macro("for") -def for_macro(*tree): - ret = None - # for [x iter y iter] ... - # -> - # foreach x iter - # foreach y iter - # ... - - tree = HyExpression(tree).replace(tree[0]) - - it = iter(tree.pop(0)) - blocks = list(zip(it, it)) # List for Python 3.x degenerating. - - key, val = blocks.pop(0) - ret = HyExpression([HySymbol("foreach"), - HyList([key, val])]) - root = ret - ret.replace(tree) - - for key, val in blocks: - # x, [1, 2, 3, 4] - nret = HyExpression([HySymbol("foreach"), - HyList([key, val])]) - nret.replace(key) - ret.append(nret) - ret = nret - - [ret.append(x) for x in tree] # we really need ~@ - return root - - -@macro("_>") -def threading_macro(head, *rest): - ret = head - for node in rest: - if not isinstance(node, HyExpression): - nnode = HyExpression([node]) - nnode.replace(node) - node = nnode - node.insert(1, ret) - ret = node - return ret - - -@macro("_>>") -def threading_tail_macro(head, *rest): - ret = head - for node in rest: - if not isinstance(node, HyExpression): - nnode = HyExpression([node]) - nnode.replace(node) - node = nnode - node.append(ret) - ret = node - return ret - - -@macro("car") -@macro("first") -def first_macro(lst): - return HyExpression([HySymbol('get'), - lst, - HyInteger(0)]) - - -@macro("cdr") -@macro("rest") -def rest_macro(lst): - return HyExpression([HySymbol('slice'), - lst, - HyInteger(1)]) - - -@macro("let") -def let_macro(variables, *body): - expr = HyExpression([HySymbol("fn"), HyList([])]) - - for var in variables: - if isinstance(var, list): - expr.append(HyExpression([HySymbol("setv"), - var[0], var[1]])) - else: - expr.append(HyExpression([HySymbol("setv"), - var, HySymbol("None")])) - - return HyExpression([expr + list(body)]) - - -@macro("when") -def when_macro(test, *body): - return HyExpression([ - HySymbol('if'), - test, - HyExpression([HySymbol("do")] + list(body)), - ]) - - -@macro("unless") -def unless_macro(test, *body): - return HyExpression([ - HySymbol('if'), - test, - HySymbol('None'), - HyExpression([HySymbol("do")] + list(body)), - ]) diff --git a/hy/core/language.hy b/hy/core/language.hy index 10f81e9..a96b25d 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -23,8 +23,6 @@ ;;;; to make functional programming slightly easier. ;;;; -(require hy.core.macros) - (defn _numeric-check [x] (if (not (numeric? x)) (raise (TypeError (.format "{0!r} is not a number" x))))) @@ -32,11 +30,11 @@ (defn cycle [coll] "Yield an infinite repetition of the items in coll" (setv seen []) - (for [x coll] + (foreach [x coll] (yield x) (.append seen x)) (while seen - (for [x seen] + (foreach [x seen] (yield x)))) (defn dec [n] @@ -48,7 +46,7 @@ "Return a generator from the original collection with duplicates removed" (let [[seen []] [citer (iter coll)]] - (for [val citer] + (foreach [val citer] (if (not_in val seen) (do (yield val) @@ -57,7 +55,7 @@ (defn drop [count coll] "Drop `count` elements from `coll` and yield back the rest" (let [[citer (iter coll)]] - (try (for [i (range count)] + (try (foreach [i (range count)] (next citer)) (catch [StopIteration])) citer)) @@ -65,10 +63,10 @@ (defn drop-while [pred coll] "Drop all elements of `coll` until `pred` is False" (let [[citer (iter coll)]] - (for [val citer] + (foreach [val citer] (if (not (pred val)) (do (yield val) (break)))) - (for [val citer] + (foreach [val citer] (yield val)))) (defn empty? [coll] @@ -83,7 +81,7 @@ (defn filter [pred coll] "Return all elements from `coll` that pass `pred`" (let [[citer (iter coll)]] - (for [val citer] + (foreach [val citer] (if (pred val) (yield val))))) @@ -138,7 +136,7 @@ "Return nth item in collection or sequence, counting from 0" (if (not (neg? index)) (if (iterable? coll) - (try (first (list (take 1 (drop index coll)))) + (try (get (list (take 1 (drop index coll))) 0) (catch [IndexError] None)) (try (get coll index) (catch [IndexError] None))) @@ -157,7 +155,7 @@ (defn remove [pred coll] "Return coll with elements removed that pass `pred`" (let [[citer (iter coll)]] - (for [val citer] + (foreach [val citer] (if (not (pred val)) (yield val))))) @@ -165,7 +163,7 @@ "Yield x forever or optionally n times" (if (none? n) (setv dispatch (fn [] (while true (yield x)))) - (setv dispatch (fn [] (for [_ (range n)] (yield x))))) + (setv dispatch (fn [] (foreach [_ (range n)] (yield x))))) (dispatch)) (defn repeatedly [func] @@ -187,7 +185,7 @@ "Take `count` elements from `coll`, or the whole set if the total number of entries in `coll` is less than `count`." (let [[citer (iter coll)]] - (for [_ (range count)] + (foreach [_ (range count)] (yield (next citer))))) (defn take-nth [n coll] @@ -195,16 +193,16 @@ raises ValueError for (not (pos? n))" (if (pos? n) (let [[citer (iter coll)] [skip (dec n)]] - (for [val citer] + (foreach [val citer] (yield val) - (for [_ (range skip)] + (foreach [_ (range skip)] (next citer)))) (raise (ValueError "n must be positive")))) (defn take-while [pred coll] "Take all elements while `pred` is true" (let [[citer (iter coll)]] - (for [val citer] + (foreach [val citer] (if (pred val) (yield val) (break))))) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index d25dd25..17792f9 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -1,12 +1,118 @@ -;;; hy core macros +;;; Hy core macros +;; +;; Copyright (c) 2013 Nicolas Dandrimont +;; Copyright (c) 2013 Paul Tagliamonte +;; Copyright (c) 2013 Konrad Hinsen +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. +;; +;;; These macros form the hy language +;;; They are automatically required in every module, except inside hy.core -(defmacro if-python2 [python2-form python3-form] - (import sys) - (if (< (get sys.version_info 0) 3) - python2-form - python3-form)) -(defmacro yield-from [_hy_yield_from_els] - (quasiquote - (for [_hy_yield_from_x (unquote _hy_yield_from_els)] - (yield _hy_yield_from_x)))) +(defmacro for [args &rest body] + "shorthand for nested foreach loops: + (for [x foo y bar] baz) -> + (foreach [x foo] + (foreach [y bar] + baz))" + ;; TODO: that signature sucks. + ;; (for [[x foo] [y bar]] baz) would be more consistent + (if (% (len args) 2) + (macro-error args "for needs an even number of elements in its first argument")) + (if args + `(foreach [~(.pop args 0) ~(.pop args 0)] (for ~args ~@body)) + `(do ~@body))) + + +(defmacro-alias [car first] [thing] + "Get the first element of a list/cons" + `(get ~thing 0)) + + +(defmacro-alias [cdr rest] [thing] + "Get all the elements of a thing, except the first" + `(slice ~thing 1)) + + +(defmacro cond [&rest branches] + "shorthand for nested ifs: + (cond [foo bar] [baz quux]) -> + (if foo + bar + (if baz + quux))" + (setv branches (iter branches)) + (setv branch (next branches)) + (defn check-branch [branch] + "check `cond` branch for validity, return the corresponding `if` expr" + (if (not (= (type branch) HyList)) + (macro-error branch "cond branches need to be a list")) + (if (!= (len branch) 2) + (macro-error branch "cond branches need two items: a test and a code branch")) + (setv (, test thebranch) branch) + `(if ~test ~thebranch)) + + (setv root (check-branch branch)) + (setv latest-branch root) + + (foreach [branch branches] + (setv cur-branch (check-branch branch)) + (.append latest-branch cur-branch) + (setv latest-branch cur-branch)) + root) + + +(defmacro -> [head &rest rest] + ;; TODO: fix the docstring by someone who understands this + (setv ret head) + (foreach [node rest] + (if (not (isinstance node HyExpression)) + (setv node `(~node))) + (.insert node 1 ret) + (setv ret node)) + ret) + + +(defmacro ->> [head &rest rest] + ;; TODO: fix the docstring by someone who understands this + (setv ret head) + (foreach [node rest] + (if (not (isinstance node HyExpression)) + (setv node `(~node))) + (.append node ret) + (setv ret node)) + ret) + + +(defmacro when [test &rest body] + "Execute `body` when `test` is true" + `(if ~test (do ~@body))) + + +(defmacro unless [test &rest body] + "Execute `body` when `test` is false" + `(if ~test None (do ~@body))) + + +(defmacro yield-from [iterable] + "Yield all the items from iterable" + ;; TODO: this needs some gensym love + `(foreach [_hy_yield_from_x ~iterable] + (yield _hy_yield_from_x))) diff --git a/hy/importer.py b/hy/importer.py index c3ef1a5..b32b71a 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -32,11 +32,7 @@ import ast import os import __future__ -if sys.version_info[0] >= 3: - long_type = int -else: - import __builtin__ - long_type = long # NOQA +from hy._compat import builtins, long_type def ast_compile(ast, filename, mode): @@ -129,10 +125,7 @@ def write_hy_as_pyc(fname): code = ast_compile(_ast, fname, "exec") cfile = "%s.pyc" % fname[:-len(".hy")] - if sys.version_info[0] >= 3: - open_ = open - else: - open_ = __builtin__.open + open_ = builtins.open with open_(cfile, 'wb') as fc: if sys.version_info[0] >= 3: diff --git a/hy/macros.py b/hy/macros.py index baa5e51..20d0cac 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -26,7 +26,7 @@ from hy.models.integer import HyInteger from hy.models.float import HyFloat from hy.models.complex import HyComplex from hy.models.dict import HyDict -from hy.util import str_type +from hy._compat import str_type from collections import defaultdict @@ -43,6 +43,17 @@ _hy_macros = defaultdict(dict) def macro(name): + """Decorator to define a macro called `name`. + + This stores the macro `name` in the namespace for the module where it is + defined. + + If the module where it is defined is in `hy.core`, then the macro is stored + in the default `None` namespace. + + This function is called from the `defmacro` special form in the compiler. + + """ def _(fn): module_name = fn.__module__ if module_name.startswith("hy.core"): @@ -52,20 +63,20 @@ def macro(name): return _ -def require(source_module_name, target_module_name): - macros = _hy_macros[source_module_name] - refs = _hy_macros[target_module_name] +def require(source_module, target_module): + """Load the macros from `source_module` in the namespace of + `target_module`. + + This function is called from the `require` special form in the compiler. + + """ + macros = _hy_macros[source_module] + refs = _hy_macros[target_module] for name, macro in macros.items(): refs[name] = macro -def _wrap_value(x): - wrapper = _wrappers.get(type(x)) - if wrapper is None: - return x - else: - return wrapper(x) - +# type -> wrapping function mapping for _wrap_value _wrappers = { int: HyInteger, bool: lambda x: HySymbol("True") if x else HySymbol("False"), @@ -77,27 +88,60 @@ _wrappers = { } -def process(tree, module_name): - load_macros(module_name) - old = None - while old != tree: - old = tree - tree = macroexpand(tree, module_name) - return tree +def _wrap_value(x): + """Wrap `x` into the corresponding Hy type. + + This allows a macro to return an unquoted expression transparently. + + """ + wrapper = _wrappers.get(type(x)) + if wrapper is None: + return x + else: + return wrapper(x) def load_macros(module_name): + """Load the hy builtin macros for module `module_name`. + + Modules from `hy.core` can only use the macros from CORE_MACROS. + Other modules get the macros from CORE_MACROS and EXTRA_MACROS. + + """ + + def _import(module, module_name=module_name): + "__import__ a module, avoiding recursions" + if module != module_name: + __import__(module) + for module in CORE_MACROS: - __import__(module) + _import(module) if module_name.startswith("hy.core"): return for module in EXTRA_MACROS: - __import__(module) + _import(module) def macroexpand(tree, module_name): + """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) + old = None + while old != tree: + old = tree + tree = macroexpand_1(tree, module_name) + return tree + + +def macroexpand_1(tree, module_name): + """Expand the toplevel macro from `tree` once, in the context of + `module_name`.""" if isinstance(tree, HyExpression): if tree == []: return tree diff --git a/hy/models/__init__.py b/hy/models/__init__.py index 775862a..42032ca 100644 --- a/hy/models/__init__.py +++ b/hy/models/__init__.py @@ -24,7 +24,6 @@ class HyObject(object): Generic Hy Object model. This is helpful to inject things into all the Hy lexing Objects at once. """ - pass def replace(self, other): if isinstance(other, HyObject): diff --git a/hy/models/keyword.py b/hy/models/keyword.py index 2d1d48b..f2633a9 100644 --- a/hy/models/keyword.py +++ b/hy/models/keyword.py @@ -20,7 +20,7 @@ from __future__ import unicode_literals from hy.models import HyObject -from hy.util import str_type +from hy._compat import str_type class HyKeyword(HyObject, str_type): diff --git a/hy/models/string.py b/hy/models/string.py index ce0a238..ab7d792 100644 --- a/hy/models/string.py +++ b/hy/models/string.py @@ -19,7 +19,7 @@ # DEALINGS IN THE SOFTWARE. from hy.models import HyObject -from hy.util import str_type +from hy._compat import str_type class HyString(HyObject, str_type): diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index 46e33a1..373b967 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -1,5 +1,5 @@ -from hy.macros import macro, process +from hy.macros import macro, macroexpand from hy.lex import tokenize from hy.models.string import HyString @@ -16,14 +16,14 @@ def tmac(*tree): def test_preprocessor_simple(): """ Test basic macro expansion """ - obj = process(tokenize('(test "one" "two")')[0], __name__) + obj = macroexpand(tokenize('(test "one" "two")')[0], __name__) assert obj == HyList(["one", "two"]) assert type(obj) == HyList def test_preprocessor_expression(): """ Test that macro expansion doesn't recurse""" - obj = process(tokenize('(test (test "one" "two"))')[0], __name__) + obj = macroexpand(tokenize('(test (test "one" "two"))')[0], __name__) assert type(obj) == HyList assert type(obj[0]) == HyExpression @@ -34,4 +34,4 @@ def test_preprocessor_expression(): obj = HyList([HyString("one"), HyString("two")]) obj = tokenize('(shill ["one" "two"])')[0][1] - assert obj == process(obj, '') + assert obj == macroexpand(obj, '') diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 6dd2986..d19ca17 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -122,8 +122,8 @@ (defn test-cond [] "NATIVE: test if cond sorta works." (cond - ((= 1 2) (assert (is true false))) - ((is null null) (assert (is true true))))) + [(= 1 2) (assert (is true false))] + [(is null null) (assert (is true true))])) (defn test-index [] diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy index 45c7648..8900578 100644 --- a/tests/native_tests/quote.hy +++ b/tests/native_tests/quote.hy @@ -82,3 +82,14 @@ (setv opt (quote &optional)) (assert (isinstance opt hy.HyLambdaListKeyword)) (assert (= (str opt) "&optional"))) + +(defmacro doodle [&rest body] + `(do ~@body)) + +(defn test-unquote-splice [] + "NATIVE: test unquote-splice does what's intended" + (assert (= + (doodle + [1 2 3] + [4 5 6]) + [4 5 6]))) diff --git a/tests/resources/argparse_ex.hy b/tests/resources/argparse_ex.hy index 9b2500f..82df479 100755 --- a/tests/resources/argparse_ex.hy +++ b/tests/resources/argparse_ex.hy @@ -12,7 +12,7 @@ ;; using (cond) allows -i to take precedence over -c -(cond (args.i - (print (str args.i))) - (args.c - (print (str "got c")))) +(cond [args.i + (print (str args.i))] + [args.c + (print (str "got c"))])