From 48dd9684612170636d89f2c40a1e72248ae93432 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 22 Sep 2013 15:08:43 +0200 Subject: [PATCH 01/16] Coerce the contents of unquote-splice'd things to a list This fixes the conversion issue in the following macro: (defmacro doodle [&rest body] `(do ~@body)) --- hy/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/compiler.py b/hy/compiler.py index 90ad043..bfe48a4 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -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]) From 6a7d97c286595a4e71e6b74a30222e02423785ed Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 22 Sep 2013 15:12:59 +0200 Subject: [PATCH 02/16] Add test for unquote-splice behavior --- tests/native_tests/quote.hy | 11 +++++++++++ 1 file changed, 11 insertions(+) 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]))) From d495473c541b6152542544a017ea4fe3b572f9a8 Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Sat, 21 Sep 2013 02:08:10 +0200 Subject: [PATCH 03/16] Translation of meth from Python to Hy --- hy/contrib/meth.hy | 54 ++++++++++++++++++++++++++++++++++++++++++ hy/contrib/meth.py | 58 ---------------------------------------------- 2 files changed, 54 insertions(+), 58 deletions(-) create mode 100644 hy/contrib/meth.hy delete mode 100644 hy/contrib/meth.py diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy new file mode 100644 index 0000000..ca3f739 --- /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" + (quasiquote (let [[deco ((getattr app "route") (unquote path))]] + (with-decorator deco + (defn (unquote name) (unquote params) (unquote-splice code)))))) + +(defmacro route-with-methods [name path params code methods] + "Same as route but with an extra methods array to specify HTTP methods" + (quasiquote (let [[deco (kwapply ((getattr app "route") (unquote path)) + {"methods" (unquote methods)})]] + (with-decorator deco + (defn (unquote name) (unquote params) (unquote-splice 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")])) From 3f1243f88cf94afa76758ad4d30f5def60db80ea Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Sun, 22 Sep 2013 13:10:37 +0200 Subject: [PATCH 04/16] changed to use short version of macros --- hy/contrib/meth.hy | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index ca3f739..d4eef65 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -3,16 +3,16 @@ (defmacro route [name path params code] "Default get request" - (quasiquote (let [[deco ((getattr app "route") (unquote path))]] - (with-decorator deco - (defn (unquote name) (unquote params) (unquote-splice code)))))) + `(let [[deco ((getattr app "route") ~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" - (quasiquote (let [[deco (kwapply ((getattr app "route") (unquote path)) - {"methods" (unquote methods)})]] - (with-decorator deco - (defn (unquote name) (unquote params) (unquote-splice code)))))) + `(let [[deco (kwapply ((getattr app "route") ~path) + {"methods" ~methods})]] + (with-decorator deco + (defn ~name ~params ~@code)))) ;; Some macro examples (defmacro post-route [name path params code] From ff7c71b9c934962d992b0c944f39705ffe56f793 Mon Sep 17 00:00:00 2001 From: Guillermo Vaya Date: Mon, 23 Sep 2013 01:23:44 +0200 Subject: [PATCH 05/16] comply with hy's tao rules for getattr --- hy/contrib/meth.hy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index d4eef65..1f4ae70 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -3,13 +3,13 @@ (defmacro route [name path params code] "Default get request" - `(let [[deco ((getattr app "route") ~path)]] + `(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 ((getattr app "route") ~path) + `(let [[deco (kwapply (.route app ~path) {"methods" ~methods})]] (with-decorator deco (defn ~name ~params ~@code)))) From f21ddeededc3ce216791725598a0bb97120a2497 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 24 Sep 2013 10:27:30 +0300 Subject: [PATCH 06/16] Add hy._compat module. There was a couple of duplicate imports and type checkings in the codebase. So I added a new module to unify all Python 2 and 3 compatibility codes. Also, this is a somewhat common pattern in Python. See Jinja2 for example: https://github.com/mitsuhiko/jinja2/blob/master/jinja2/_compat.py --- hy/{util.py => _compat.py} | 12 +++++++++++- hy/cmdline.py | 6 +----- hy/compiler.py | 2 +- hy/completer.py | 8 ++------ hy/importer.py | 11 ++--------- hy/macros.py | 2 +- hy/models/keyword.py | 2 +- hy/models/string.py | 2 +- 8 files changed, 20 insertions(+), 25 deletions(-) rename hy/{util.py => _compat.py} (86%) 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..3b203f8 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 d80bbf0..8830aa3 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -37,7 +37,7 @@ from hy.models.list import HyList from hy.models.dict import HyDict from hy.macros import require, process -from hy.util import str_type +from hy._compat import str_type import hy.importer import traceback 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/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..b08f1a4 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 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): From 295e1240ad0273e2328a196effdbf07a9192ca7d Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 29 Sep 2013 14:53:44 +0200 Subject: [PATCH 07/16] PY3 should really check for Python >= 3 --- hy/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/_compat.py b/hy/_compat.py index 3b203f8..e30d0dc 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -25,7 +25,7 @@ except ImportError: import builtins # NOQA import sys -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 if PY3: str_type = str From 9ea153fd7ee753ab748c6744c6e2b7928e257bd1 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 29 Sep 2013 15:10:33 +0200 Subject: [PATCH 08/16] Add @berkerpeksag to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) 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 From d5bf328aa7b6105f151bd286b5a897f202801d22 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 29 Sep 2013 17:55:15 +0200 Subject: [PATCH 09/16] Cleanup the hy.macros module Add comments to the functions, reorder, make the file clearer --- hy/compiler.py | 4 +- hy/macros.py | 82 +++++++++++++++++++++------- tests/macros/test_macro_processor.py | 8 +-- 3 files changed, 69 insertions(+), 25 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 8830aa3..f14c79f 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -36,7 +36,7 @@ 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.macros import require, macroexpand from hy._compat import str_type import hy.importer @@ -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: diff --git a/hy/macros.py b/hy/macros.py index b08f1a4..20d0cac 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -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/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, '') From 875d5f2ff597bfb267f4ba74ebc10a84b6fa11f8 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 29 Sep 2013 18:00:24 +0200 Subject: [PATCH 10/16] Rewrite the bootstrap macros in hy This gets rid of the dichotomy between bootstrap.py and macros.hy, by making both files hy modules. I added some error checking to make the macros more resilient. The biggest (user-visible) change is the change in cond, which now only accepts lists as arguments. Tests updated accordingly. Closes: #176 (whoops, no more bootstrap) --- hy/core/bootstrap.hy | 74 ++++++++++++++++ hy/core/bootstrap.py | 156 --------------------------------- hy/core/macros.hy | 126 +++++++++++++++++++++++--- tests/native_tests/language.hy | 4 +- tests/resources/argparse_ex.hy | 8 +- 5 files changed, 196 insertions(+), 172 deletions(-) create mode 100644 hy/core/bootstrap.hy delete mode 100644 hy/core/bootstrap.py 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/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/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/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"))]) From a90c8663270bd636afdf06ca55bf2e8afb9093cd Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Sun, 29 Sep 2013 18:01:20 +0200 Subject: [PATCH 11/16] Rewrite language.hy not to require hy.core.macros As was intended when the bootstrap and core macros were separated. --- hy/core/language.hy | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) 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))))) From a038d3c592f86fda040151f2db55316bfc421b74 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sun, 29 Sep 2013 18:33:27 +0300 Subject: [PATCH 12/16] Remove the redundant pass statement. --- hy/models/__init__.py | 1 - 1 file changed, 1 deletion(-) 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): From 0248e426337e5927a5ac1fa45d3243000142969f Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 8 Oct 2013 05:16:31 +0300 Subject: [PATCH 13/16] Update hacking guide. --- docs/hacking.rst | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/hacking.rst b/docs/hacking.rst index 404f4f3..0d7faba 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -2,6 +2,8 @@ Hacking on hy =============== +.. highlight:: bash + Join our hyve! ============== @@ -21,24 +23,32 @@ 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 +2. get the source code:: -5. install other develop-y requirements:: + $ 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 +70,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 From 221a9b4918cdb37d740ae3a02e12bf2836919e28 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Wed, 9 Oct 2013 12:36:35 +0300 Subject: [PATCH 14/16] Mention virtualenvwrapper in the hacking guide. --- docs/hacking.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/hacking.rst b/docs/hacking.rst index 0d7faba..b6a0963 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -31,6 +31,13 @@ Do this: and activate it:: $ . venv/bin/activate + + or use `virtualenvwrapper `_ + to create and manage your virtual environment:: + + $ mkvirtualenv venv + $ workon venv + 2. get the source code:: $ git clone https://github.com/hylang/hy.git From 6364296a7bebe3ec9ba54a1fbf0973738f83c3c9 Mon Sep 17 00:00:00 2001 From: "Sean B. Palmer" Date: Thu, 10 Oct 2013 15:59:19 +0100 Subject: [PATCH 15/16] Documented punycode conversion more thoroughly using a compound case --- docs/language/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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`. From 656d6461988430de7a9e003ac0d7ef5ba0beff99 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Thu, 10 Oct 2013 17:17:43 -0400 Subject: [PATCH 16/16] change virtualenv name to be `hy' --- docs/hacking.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hacking.rst b/docs/hacking.rst index b6a0963..77e0036 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -35,8 +35,8 @@ Do this: or use `virtualenvwrapper `_ to create and manage your virtual environment:: - $ mkvirtualenv venv - $ workon venv + $ mkvirtualenv hy + $ workon hy 2. get the source code::