Skip to content

Commit

Permalink
Merge pull request #1426 from gilch/letmacro
Browse files Browse the repository at this point in the history
implement `let`
  • Loading branch information
kirbyfan64 authored Nov 1, 2017
2 parents 687be1e + e0e664c commit 49d2523
Show file tree
Hide file tree
Showing 15 changed files with 800 additions and 97 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,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 @@ -840,6 +840,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 @@ -1134,6 +1135,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 @@ -2014,14 +2016,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 @@ -2063,62 +2066,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

0 comments on commit 49d2523

Please sign in to comment.