Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement let #1426

Merged
merged 15 commits into from
Nov 1, 2017
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Changes from 0.13.0
[ Misc. Improvements ]
* `read`, `read_str`, and `eval` are exposed and documented as top-level
functions in the `hy` module
* Experimental `let` macro in `hy.contrib.walk`

Changes from 0.12.1

Expand Down
60 changes: 57 additions & 3 deletions docs/contrib/walk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Functions
.. _walk:

walk
-----
----

Usage: `(walk inner outer form)`

Expand All @@ -36,7 +36,7 @@ Example:
97

postwalk
---------
--------

.. _postwalk:

Expand Down Expand Up @@ -116,7 +116,7 @@ each sub-form, uses ``f`` 's return value in place of the original.
HyInteger(7)])])])])

prewalk
--------
-------

.. _prewalk:

Expand Down Expand Up @@ -194,3 +194,57 @@ each sub-form, uses ``f`` 's return value in place of the original.
HyInteger(6),
HyList([
HyInteger(7)])])])])

macroexpand-all
---------------

Usage: `(macroexpand-all form &optional module-name)`

Recursively performs all possible macroexpansions in form, using the ``require`` context of ``module-name``.
`macroexpand-all` assumes the calling module's context if unspecified.

Macros
======

let
---

``let`` creates lexically-scoped names for local variables.
A let-bound name ceases to refer to that local outside the ``let`` form.
Arguments in nested functions and bindings in nested ``let`` forms can shadow these names.

.. code-block:: hy

=> (let [x 5] ; creates a new local bound to name 'x
... (print x)
... (let [x 6] ; new local and name binding that shadows 'x
... (print x))
... (print x)) ; 'x refers to the first local again
5
6
5

Basic assignments (e.g. ``setv``, ``+=``) will update the local variable named by a let binding,
when they assign to a let-bound name.

But assignments via ``import`` are always hoisted to normal Python scope, and
likewise, ``defclass`` will assign the class to the Python scope,
even if it shares the name of a let binding.

Use ``__import__`` and ``type`` (or whatever metaclass) instead,
if you must avoid this hoisting.

The ``let`` macro takes two parameters: a list defining *variables*
and the *body* which gets executed. *variables* is a vector of
variable and value pairs.

``let`` executes the variable assignments one-by-one, in the order written.

.. code-block:: hy

=> (let [x 5
... y (+ x 1)]
... (print x y))
5 6

It is an error to use a let-bound name in a ``global`` or ``nonlocal`` form.
4 changes: 2 additions & 2 deletions hy/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def ast_callback(main_ast, expr_ast):


@macro("koan")
def koan_macro():
def koan_macro(ETname):
return HyExpression([HySymbol('print'),
HyString("""
Ummon asked the head monk, "What sutra are you lecturing on?"
Expand All @@ -143,7 +143,7 @@ def koan_macro():


@macro("ideas")
def ideas_macro():
def ideas_macro(ETname):
return HyExpression([HySymbol('print'),
HyString(r"""

Expand Down
63 changes: 5 additions & 58 deletions hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ def compile_raise_expression(self, expr):
@builds("try")
@checkargs(min=2)
def compile_try_expression(self, expr):
expr = copy.deepcopy(expr)
expr.pop(0) # try

# (try something…)
Expand Down Expand Up @@ -1125,6 +1126,7 @@ def compile_yield_expression(self, expr):

@builds("import")
def compile_import_expression(self, expr):
expr = copy.deepcopy(expr)
def _compile_import(expr, module, names=None, importer=asty.Import):
if not names:
names = [ast.alias(name=ast_str(module), asname=None)]
Expand Down Expand Up @@ -2005,14 +2007,15 @@ def rewire_init(expr):
expressions.pop(0) # class

class_name = expressions.pop(0)
if not isinstance(class_name, HySymbol):
raise HyTypeError(class_name, "Class name must be a symbol.")

bases_expr = []
bases = Result()
if expressions:
base_list = expressions.pop(0)
if not isinstance(base_list, HyList):
raise HyTypeError(expressions,
"Bases class must be a list")
raise HyTypeError(base_list, "Base classes must be a list.")
bases_expr, bases, _ = self._compile_collect(base_list)

body = Result()
Expand Down Expand Up @@ -2054,62 +2057,6 @@ def rewire_init(expr):
bases=bases_expr,
body=body.stmts)

def _compile_time_hack(self, expression):
"""Compile-time hack: we want to get our new macro now
We must provide __name__ in the namespace to make the Python
compiler set the __module__ attribute of the macro function."""

hy.importer.hy_eval(copy.deepcopy(expression),
compile_time_ns(self.module_name),
self.module_name)

# We really want to have a `hy` import to get hy.macro in
ret = self.compile(expression)
ret.add_imports('hy', [None])
return ret

@builds("defmacro")
@checkargs(min=1)
def compile_macro(self, expression):
expression.pop(0)
name = expression.pop(0)
if not isinstance(name, HySymbol):
raise HyTypeError(name, ("received a `%s' instead of a symbol "
"for macro name" % type(name).__name__))
name = HyString(name).replace(name)
for kw in ("&kwonly", "&kwargs", "&key"):
if kw in expression[0]:
raise HyTypeError(name, "macros cannot use %s" % kw)
new_expression = HyExpression([
HyExpression([HySymbol("hy.macros.macro"), name]),
HyExpression([HySymbol("fn")] + expression),
]).replace(expression)

ret = self._compile_time_hack(new_expression)

return ret

@builds("deftag")
@checkargs(min=2)
def compile_tag_macro(self, expression):
expression.pop(0)
name = expression.pop(0)
if name == ":" or name == "&":
raise NameError("%s can't be used as a tag macro name" % name)
if not isinstance(name, HySymbol) and not isinstance(name, HyString):
raise HyTypeError(name,
("received a `%s' instead of a symbol "
"for tag macro name" % type(name).__name__))
name = HyString(name).replace(name)
new_expression = HyExpression([
HyExpression([HySymbol("hy.macros.tag"), name]),
HyExpression([HySymbol("fn")] + expression),
]).replace(expression)

ret = self._compile_time_hack(new_expression)

return ret

@builds("dispatch_tag_macro")
@checkargs(exact=2)
def compile_dispatch_tag_macro(self, expression):
Expand Down
Loading