commit
49d2523e17
1
NEWS
1
NEWS
@ -55,6 +55,7 @@ Changes from 0.13.0
|
|||||||
[ Misc. Improvements ]
|
[ Misc. Improvements ]
|
||||||
* `read`, `read_str`, and `eval` are exposed and documented as top-level
|
* `read`, `read_str`, and `eval` are exposed and documented as top-level
|
||||||
functions in the `hy` module
|
functions in the `hy` module
|
||||||
|
* Experimental `let` macro in `hy.contrib.walk`
|
||||||
|
|
||||||
Changes from 0.12.1
|
Changes from 0.12.1
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Functions
|
|||||||
.. _walk:
|
.. _walk:
|
||||||
|
|
||||||
walk
|
walk
|
||||||
-----
|
----
|
||||||
|
|
||||||
Usage: `(walk inner outer form)`
|
Usage: `(walk inner outer form)`
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ Example:
|
|||||||
97
|
97
|
||||||
|
|
||||||
postwalk
|
postwalk
|
||||||
---------
|
--------
|
||||||
|
|
||||||
.. _postwalk:
|
.. _postwalk:
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ each sub-form, uses ``f`` 's return value in place of the original.
|
|||||||
HyInteger(7)])])])])
|
HyInteger(7)])])])])
|
||||||
|
|
||||||
prewalk
|
prewalk
|
||||||
--------
|
-------
|
||||||
|
|
||||||
.. _prewalk:
|
.. _prewalk:
|
||||||
|
|
||||||
@ -194,3 +194,57 @@ each sub-form, uses ``f`` 's return value in place of the original.
|
|||||||
HyInteger(6),
|
HyInteger(6),
|
||||||
HyList([
|
HyList([
|
||||||
HyInteger(7)])])])])
|
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.
|
||||||
|
@ -125,7 +125,7 @@ class HyREPL(code.InteractiveConsole):
|
|||||||
|
|
||||||
|
|
||||||
@macro("koan")
|
@macro("koan")
|
||||||
def koan_macro():
|
def koan_macro(ETname):
|
||||||
return HyExpression([HySymbol('print'),
|
return HyExpression([HySymbol('print'),
|
||||||
HyString("""
|
HyString("""
|
||||||
Ummon asked the head monk, "What sutra are you lecturing on?"
|
Ummon asked the head monk, "What sutra are you lecturing on?"
|
||||||
@ -143,7 +143,7 @@ def koan_macro():
|
|||||||
|
|
||||||
|
|
||||||
@macro("ideas")
|
@macro("ideas")
|
||||||
def ideas_macro():
|
def ideas_macro(ETname):
|
||||||
return HyExpression([HySymbol('print'),
|
return HyExpression([HySymbol('print'),
|
||||||
HyString(r"""
|
HyString(r"""
|
||||||
|
|
||||||
|
@ -840,6 +840,7 @@ class HyASTCompiler(object):
|
|||||||
@builds("try")
|
@builds("try")
|
||||||
@checkargs(min=2)
|
@checkargs(min=2)
|
||||||
def compile_try_expression(self, expr):
|
def compile_try_expression(self, expr):
|
||||||
|
expr = copy.deepcopy(expr)
|
||||||
expr.pop(0) # try
|
expr.pop(0) # try
|
||||||
|
|
||||||
# (try something…)
|
# (try something…)
|
||||||
@ -1134,6 +1135,7 @@ class HyASTCompiler(object):
|
|||||||
|
|
||||||
@builds("import")
|
@builds("import")
|
||||||
def compile_import_expression(self, expr):
|
def compile_import_expression(self, expr):
|
||||||
|
expr = copy.deepcopy(expr)
|
||||||
def _compile_import(expr, module, names=None, importer=asty.Import):
|
def _compile_import(expr, module, names=None, importer=asty.Import):
|
||||||
if not names:
|
if not names:
|
||||||
names = [ast.alias(name=ast_str(module), asname=None)]
|
names = [ast.alias(name=ast_str(module), asname=None)]
|
||||||
@ -2014,14 +2016,15 @@ class HyASTCompiler(object):
|
|||||||
expressions.pop(0) # class
|
expressions.pop(0) # class
|
||||||
|
|
||||||
class_name = expressions.pop(0)
|
class_name = expressions.pop(0)
|
||||||
|
if not isinstance(class_name, HySymbol):
|
||||||
|
raise HyTypeError(class_name, "Class name must be a symbol.")
|
||||||
|
|
||||||
bases_expr = []
|
bases_expr = []
|
||||||
bases = Result()
|
bases = Result()
|
||||||
if expressions:
|
if expressions:
|
||||||
base_list = expressions.pop(0)
|
base_list = expressions.pop(0)
|
||||||
if not isinstance(base_list, HyList):
|
if not isinstance(base_list, HyList):
|
||||||
raise HyTypeError(expressions,
|
raise HyTypeError(base_list, "Base classes must be a list.")
|
||||||
"Bases class must be a list")
|
|
||||||
bases_expr, bases, _ = self._compile_collect(base_list)
|
bases_expr, bases, _ = self._compile_collect(base_list)
|
||||||
|
|
||||||
body = Result()
|
body = Result()
|
||||||
@ -2063,62 +2066,6 @@ class HyASTCompiler(object):
|
|||||||
bases=bases_expr,
|
bases=bases_expr,
|
||||||
body=body.stmts)
|
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")
|
@builds("dispatch_tag_macro")
|
||||||
@checkargs(exact=2)
|
@checkargs(exact=2)
|
||||||
def compile_dispatch_tag_macro(self, expression):
|
def compile_dispatch_tag_macro(self, expression):
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
;; license. See the LICENSE.
|
;; license. See the LICENSE.
|
||||||
|
|
||||||
(import [hy [HyExpression HyDict]]
|
(import [hy [HyExpression HyDict]]
|
||||||
[functools [partial]])
|
[functools [partial]]
|
||||||
|
[collections [OrderedDict]]
|
||||||
|
[hy.macros [macroexpand :as mexpand]]
|
||||||
|
[hy.compiler [HyASTCompiler]])
|
||||||
|
|
||||||
(defn walk [inner outer form]
|
(defn walk [inner outer form]
|
||||||
"Traverses form, an arbitrary data structure. Applies inner to each
|
"Traverses form, an arbitrary data structure. Applies inner to each
|
||||||
@ -34,10 +37,349 @@
|
|||||||
sub-form, uses f's return value in place of the original."
|
sub-form, uses f's return value in place of the original."
|
||||||
(walk (partial prewalk f) identity (f form)))
|
(walk (partial prewalk f) identity (f form)))
|
||||||
|
|
||||||
(defn macroexpand-all [form]
|
;; TODO: move to hy.core?
|
||||||
|
(defn call? [form]
|
||||||
|
"Checks whether form is a non-empty HyExpression"
|
||||||
|
(and (instance? HyExpression form)
|
||||||
|
form))
|
||||||
|
|
||||||
|
(defn macroexpand-all [form &optional module-name]
|
||||||
"Recursively performs all possible macroexpansions in form."
|
"Recursively performs all possible macroexpansions in form."
|
||||||
(prewalk (fn [x]
|
(setv module-name (or module-name (calling-module-name))
|
||||||
(if (instance? HyExpression x)
|
quote-level [0]) ; TODO: make nonlocal after dropping Python2
|
||||||
(macroexpand x)
|
(defn traverse [form]
|
||||||
x))
|
(walk expand identity form))
|
||||||
form))
|
(defn expand [form]
|
||||||
|
;; manages quote levels
|
||||||
|
(defn +quote [&optional [x 1]]
|
||||||
|
(setv head (first form))
|
||||||
|
(+= (get quote-level 0) x)
|
||||||
|
(when (neg? (get quote-level 0))
|
||||||
|
(raise (TypeError "unquote outside of quasiquote")))
|
||||||
|
(setv res (traverse (cut form 1)))
|
||||||
|
(-= (get quote-level 0) x)
|
||||||
|
`(~head ~@res))
|
||||||
|
(if (call? form)
|
||||||
|
(cond [(get quote-level 0)
|
||||||
|
(cond [(in (first form) '[unquote unquote-splice])
|
||||||
|
(+quote -1)]
|
||||||
|
[(= (first form) 'quasiquote) (+quote)]
|
||||||
|
[True (traverse form)])]
|
||||||
|
[(= (first form) 'quote) form]
|
||||||
|
[(= (first form) 'quasiquote) (+quote)]
|
||||||
|
[True (traverse (mexpand form (HyASTCompiler module-name)))])
|
||||||
|
(if (coll? form)
|
||||||
|
(traverse form)
|
||||||
|
form)))
|
||||||
|
(expand form))
|
||||||
|
|
||||||
|
;; TODO: move to hy.extra.reserved?
|
||||||
|
(import hy)
|
||||||
|
(setv special-forms (list-comp k
|
||||||
|
[k (.keys hy.compiler._compile-table)]
|
||||||
|
(isinstance k hy._compat.string-types)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn lambda-list [form]
|
||||||
|
"
|
||||||
|
splits a fn argument list into sections based on &-headers.
|
||||||
|
|
||||||
|
returns an OrderedDict mapping headers to sublists.
|
||||||
|
Arguments without a header are under None.
|
||||||
|
"
|
||||||
|
(setv headers '[&optional &key &rest &kwonly &kwargs]
|
||||||
|
sections (OrderedDict [(, None [])])
|
||||||
|
header None)
|
||||||
|
(for [arg form]
|
||||||
|
(if (in arg headers)
|
||||||
|
(do (setv header arg)
|
||||||
|
(assoc sections header [])
|
||||||
|
;; Don't use a header more than once. It's the compiler's problem.
|
||||||
|
(.remove headers header))
|
||||||
|
(.append (get sections header) arg)))
|
||||||
|
sections)
|
||||||
|
|
||||||
|
|
||||||
|
(defn symbolexpand [form expander
|
||||||
|
&optional
|
||||||
|
[protected (frozenset)]
|
||||||
|
[quote-level 0]]
|
||||||
|
(.expand (SymbolExpander form expander protected quote-level)))
|
||||||
|
|
||||||
|
(defclass SymbolExpander[]
|
||||||
|
|
||||||
|
(defn __init__ [self form expander protected quote-level]
|
||||||
|
(setv self.form form
|
||||||
|
self.expander expander
|
||||||
|
self.protected protected
|
||||||
|
self.quote-level quote-level))
|
||||||
|
|
||||||
|
(defn expand-symbols [self form &optional protected quote-level]
|
||||||
|
(if (none? protected)
|
||||||
|
(setv protected self.protected))
|
||||||
|
(if (none? quote-level)
|
||||||
|
(setv quote-level self.quote-level))
|
||||||
|
(symbolexpand form self.expander protected quote-level))
|
||||||
|
|
||||||
|
(defn traverse [self form &optional protected quote-level]
|
||||||
|
(if (none? protected)
|
||||||
|
(setv protected self.protected))
|
||||||
|
(if (none? quote-level)
|
||||||
|
(setv quote-level self.quote-level))
|
||||||
|
(walk (partial symbolexpand
|
||||||
|
:expander self.expander
|
||||||
|
:protected protected
|
||||||
|
:quote-level quote-level)
|
||||||
|
identity
|
||||||
|
form))
|
||||||
|
|
||||||
|
;; manages quote levels
|
||||||
|
(defn +quote [self &optional [x 1]]
|
||||||
|
`(~(self.head) ~@(self.traverse (self.tail)
|
||||||
|
:quote-level (+ self.quote-level x))))
|
||||||
|
|
||||||
|
(defn handle-dot [self]
|
||||||
|
`(. ~@(walk (fn [form]
|
||||||
|
(if (symbol? form)
|
||||||
|
form ; don't expand attrs
|
||||||
|
(self.expand-symbols form)))
|
||||||
|
identity
|
||||||
|
(self.tail))))
|
||||||
|
|
||||||
|
(defn head [self]
|
||||||
|
(first self.form))
|
||||||
|
|
||||||
|
(defn tail [self]
|
||||||
|
(cut self.form 1))
|
||||||
|
|
||||||
|
(defn handle-except [self]
|
||||||
|
(setv tail (self.tail))
|
||||||
|
;; protect the "as" name binding the exception
|
||||||
|
`(~(self.head) ~@(self.traverse tail (| self.protected
|
||||||
|
(if (and tail
|
||||||
|
(-> tail
|
||||||
|
first
|
||||||
|
len
|
||||||
|
(= 2)))
|
||||||
|
#{(first (first tail))}
|
||||||
|
#{})))))
|
||||||
|
(defn handle-args-list [self]
|
||||||
|
(setv protected #{}
|
||||||
|
argslist `[])
|
||||||
|
(for [[header section] (-> self (.tail) first lambda-list .items)]
|
||||||
|
(if header (.append argslist header))
|
||||||
|
(cond [(in header [None '&rest '&kwargs])
|
||||||
|
(.update protected (-> section flatten set))
|
||||||
|
(.extend argslist section)]
|
||||||
|
[(in header '[&optional &kwonly])
|
||||||
|
(for [pair section]
|
||||||
|
(cond [(coll? pair)
|
||||||
|
(.add protected (first pair))
|
||||||
|
(.append argslist
|
||||||
|
`[~(first pair)
|
||||||
|
~(self.expand-symbols (second pair))])]
|
||||||
|
[True
|
||||||
|
(.add protected pair)
|
||||||
|
(.append argslist pair)]))]
|
||||||
|
[(= header '&key)
|
||||||
|
(setv &key-dict '{})
|
||||||
|
(for [[k v] (-> section first partition)]
|
||||||
|
(.add protected k)
|
||||||
|
(.append &key-dict k)
|
||||||
|
(.append &key-dict (self.expand-symbols v)))
|
||||||
|
(.append argslist &key-dict)]))
|
||||||
|
(, protected argslist))
|
||||||
|
|
||||||
|
(defn handle-fn [self]
|
||||||
|
(setv [protected argslist] (self.handle-args-list))
|
||||||
|
`(~(self.head) ~argslist
|
||||||
|
~@(self.traverse (cut (self.tail) 1)(| protected self.protected))))
|
||||||
|
|
||||||
|
;; don't expand symbols in quotations
|
||||||
|
(defn handle-quoted [self]
|
||||||
|
(if (call? self.form)
|
||||||
|
(if (in (self.head) '[unquote unquote-splice]) (self.+quote -1)
|
||||||
|
(= (self.head) 'quasiquote) (self.+quote)
|
||||||
|
(self.handle-coll))
|
||||||
|
(if (coll? self.form)
|
||||||
|
(self.handle-coll)
|
||||||
|
(self.handle-base))))
|
||||||
|
|
||||||
|
;; convert dotted names to the standard special form
|
||||||
|
(defn convert-dotted-symbol [self]
|
||||||
|
(self.expand-symbols `(. ~@(map HySymbol (.split self.form '.)))))
|
||||||
|
|
||||||
|
(defn expand-symbol [self]
|
||||||
|
(if (not-in self.form self.protected)
|
||||||
|
(self.expander self.form)
|
||||||
|
(self.handle-base)))
|
||||||
|
|
||||||
|
(defn handle-symbol [self]
|
||||||
|
(if (and self.form
|
||||||
|
(not (.startswith self.form '.))
|
||||||
|
(in '. self.form))
|
||||||
|
(self.convert-dotted-symbol)
|
||||||
|
(self.expand-symbol)))
|
||||||
|
|
||||||
|
(defn handle-global [self]
|
||||||
|
(.update self.protected (set (self.tail)))
|
||||||
|
(self.handle-base))
|
||||||
|
|
||||||
|
(defn handle-defclass [self]
|
||||||
|
;; don't expand the name of the class
|
||||||
|
`(~(self.head) ~(first (self.tail))
|
||||||
|
~@(self.traverse (cut (self.tail) 1))))
|
||||||
|
|
||||||
|
(defn handle-special-form [self]
|
||||||
|
;; don't expand other special form symbols in head position
|
||||||
|
`(~(self.head) ~@(self.traverse (self.tail))))
|
||||||
|
|
||||||
|
(defn handle-base [self]
|
||||||
|
self.form)
|
||||||
|
|
||||||
|
(defn handle-coll [self]
|
||||||
|
;; recursion
|
||||||
|
(self.traverse self.form))
|
||||||
|
|
||||||
|
;; We have to treat special forms differently.
|
||||||
|
;; Quotation should suppress symbol expansion,
|
||||||
|
;; and local bindings should shadow those made by let.
|
||||||
|
(defn handle-call [self]
|
||||||
|
(setv head (first self.form))
|
||||||
|
(if (in head '[fn fn*]) (self.handle-fn)
|
||||||
|
(in head '[import
|
||||||
|
require
|
||||||
|
quote
|
||||||
|
eval-and-compile
|
||||||
|
eval-when-compile]) (self.handle-base)
|
||||||
|
(= head 'except) (self.handle-except)
|
||||||
|
(= head ".") (self.handle-dot)
|
||||||
|
(= head 'defclass) (self.handle-defclass)
|
||||||
|
(= head 'quasiquote) (self.+quote)
|
||||||
|
;; must be checked last!
|
||||||
|
(in head special-forms) (self.handle-special-form)
|
||||||
|
;; Not a special form. Traverse it like a coll
|
||||||
|
(self.handle-coll)))
|
||||||
|
|
||||||
|
(defn expand [self]
|
||||||
|
"the main entry point. Call this to do the expansion"
|
||||||
|
(setv form self.form)
|
||||||
|
(if self.quote-level (self.handle-quoted)
|
||||||
|
(symbol? form) (self.handle-symbol)
|
||||||
|
(call? form) (self.handle-call)
|
||||||
|
(coll? form) (self.handle-coll)
|
||||||
|
;; recursive base case--it's an atom. Put it back.
|
||||||
|
(self.handle-base))))
|
||||||
|
|
||||||
|
(defmacro smacrolet [bindings &optional module-name &rest body]
|
||||||
|
"
|
||||||
|
symbol macro let.
|
||||||
|
|
||||||
|
Replaces symbols in body, but only where it would be a valid let binding.
|
||||||
|
The bindings pairs the target symbol and the expansion form for that symbol.
|
||||||
|
"
|
||||||
|
(if (odd? (len bindings))
|
||||||
|
(macro-error bindings "bindings must be paired"))
|
||||||
|
(for [k (cut bindings None None 2)]
|
||||||
|
(if-not (symbol? k)
|
||||||
|
(macro-error k "bind targets must be symbols")
|
||||||
|
(if (in '. k)
|
||||||
|
(macro-error k "binding target may not contain a dot"))))
|
||||||
|
(setv bindings (dict (partition bindings))
|
||||||
|
body (macroexpand-all body (or module-name (calling-module-name))))
|
||||||
|
(symbolexpand `(do ~@body)
|
||||||
|
(fn [symbol]
|
||||||
|
(.get bindings symbol symbol))))
|
||||||
|
|
||||||
|
(defmacro let [bindings &rest body]
|
||||||
|
"
|
||||||
|
sets up lexical bindings in its body
|
||||||
|
|
||||||
|
Bindings are processed sequentially,
|
||||||
|
so you can use the result of a earlier binding in a later one.
|
||||||
|
|
||||||
|
Basic assignments (e.g. setv, +=) will update the let binding,
|
||||||
|
if they use the name of a let binding.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Function arguments can shadow let bindings in their body,
|
||||||
|
as can nested let forms.
|
||||||
|
"
|
||||||
|
(if (odd? (len bindings))
|
||||||
|
(macro-error bindings "let bindings must be paired"))
|
||||||
|
(setv g!let (gensym 'let)
|
||||||
|
replacements (OrderedDict)
|
||||||
|
values [])
|
||||||
|
(defn expander [symbol]
|
||||||
|
(.get replacements symbol symbol))
|
||||||
|
(for [[k v] (partition bindings)]
|
||||||
|
(if-not (symbol? k)
|
||||||
|
(macro-error k "bind targets must be symbols")
|
||||||
|
(if (in '. k)
|
||||||
|
(macro-error k "binding target may not contain a dot")))
|
||||||
|
(.append values (symbolexpand (macroexpand-all v &name) expander))
|
||||||
|
(assoc replacements k `(get ~g!let ~(name k))))
|
||||||
|
`(do
|
||||||
|
(setv ~g!let {}
|
||||||
|
~@(interleave (.values replacements) values))
|
||||||
|
~@(symbolexpand (macroexpand-all body &name) expander)))
|
||||||
|
|
||||||
|
;; (defmacro macrolet [])
|
||||||
|
|
||||||
|
#_[special cases for let
|
||||||
|
;; Symbols containing a dot should be converted to this form.
|
||||||
|
;; attrs should not get expanded,
|
||||||
|
;; but [] lookups should.
|
||||||
|
'.',
|
||||||
|
|
||||||
|
;;; can shadow let bindings with Python locals
|
||||||
|
;; protect its bindings for the lexical scope of its body.
|
||||||
|
'fn',
|
||||||
|
'fn*',
|
||||||
|
;; protect as bindings for the lexical scope of its body
|
||||||
|
'except',
|
||||||
|
|
||||||
|
;;; changes scope of named variables
|
||||||
|
;; protect the variables they name for the lexical scope of their container
|
||||||
|
'global',
|
||||||
|
'nonlocal',
|
||||||
|
;; should we provide a protect form?
|
||||||
|
;; it's an anaphor only valid in a `let` body.
|
||||||
|
;; this would make the named variables python-scoped in its body
|
||||||
|
;; expands to a do
|
||||||
|
'protect',
|
||||||
|
|
||||||
|
;;; quoted variables must not be expanded.
|
||||||
|
;; but unprotected, unquoted variables must be.
|
||||||
|
'quasiquote',
|
||||||
|
'quote',
|
||||||
|
'unquote',
|
||||||
|
'unquote-splice',
|
||||||
|
|
||||||
|
;;;; deferred
|
||||||
|
|
||||||
|
;; should really only exist at toplevel. Ignore until someone complains?
|
||||||
|
;; raise an error? treat like fn?
|
||||||
|
;; should probably be implemented as macros in terms of fn/setv anyway.
|
||||||
|
'defmacro',
|
||||||
|
'deftag',
|
||||||
|
|
||||||
|
;;; create Python-scoped variables. It's probably hard to avoid this.
|
||||||
|
;; Best just doc this behavior for now.
|
||||||
|
;; we can't avoid clobbering enclosing python scope, unless we use a gensym,
|
||||||
|
;; but that corrupts '__name__'.
|
||||||
|
;; It could be set later, but that could mess up metaclasses!
|
||||||
|
;; Should the class name update let variables too?
|
||||||
|
'defclass',
|
||||||
|
;; should this update let variables?
|
||||||
|
;; it could be done with gensym/setv.
|
||||||
|
'import',
|
||||||
|
|
||||||
|
;; I don't understand these. Ignore until someone complains?
|
||||||
|
'eval_and_compile', 'eval_when_compile', 'require',]
|
||||||
|
@ -6,6 +6,30 @@
|
|||||||
;;; These macros are the essential hy macros.
|
;;; These macros are the essential hy macros.
|
||||||
;;; They are automatically required everywhere, even inside hy.core modules.
|
;;; They are automatically required everywhere, even inside hy.core modules.
|
||||||
|
|
||||||
|
(eval-and-compile
|
||||||
|
(import hy)
|
||||||
|
((hy.macros.macro "defmacro")
|
||||||
|
(fn [&name macro-name lambda-list &rest body]
|
||||||
|
"the defmacro macro"
|
||||||
|
(if* (not (isinstance macro-name hy.models.HySymbol))
|
||||||
|
(raise
|
||||||
|
(hy.errors.HyTypeError
|
||||||
|
macro-name
|
||||||
|
(% "received a `%s' instead of a symbol for macro name"
|
||||||
|
(. (type name)
|
||||||
|
__name__)))))
|
||||||
|
(for* [kw '[&kwonly &kwargs &key]]
|
||||||
|
(if* (in kw lambda-list)
|
||||||
|
(raise (hy.errors.HyTypeError macro-name
|
||||||
|
(% "macros cannot use %s"
|
||||||
|
kw)))))
|
||||||
|
;; this looks familiar...
|
||||||
|
`(eval-and-compile
|
||||||
|
(import hy)
|
||||||
|
((hy.macros.macro ~(str macro-name))
|
||||||
|
(fn ~(+ `[&name] lambda-list)
|
||||||
|
~@body))))))
|
||||||
|
|
||||||
(defmacro if [&rest args]
|
(defmacro if [&rest args]
|
||||||
"Conditionally evaluate alternating test and then expressions."
|
"Conditionally evaluate alternating test and then expressions."
|
||||||
(setv n (len args))
|
(setv n (len args))
|
||||||
@ -16,6 +40,23 @@
|
|||||||
~(get args 1)
|
~(get args 1)
|
||||||
(if ~@(cut args 2))))))
|
(if ~@(cut args 2))))))
|
||||||
|
|
||||||
|
(defmacro deftag [tag-name lambda-list &rest body]
|
||||||
|
(if (and (not (isinstance tag-name hy.models.HySymbol))
|
||||||
|
(not (isinstance tag-name hy.models.HyString)))
|
||||||
|
(raise (hy.errors.HyTypeError
|
||||||
|
tag-name
|
||||||
|
(% "received a `%s' instead of a symbol for tag macro name"
|
||||||
|
(. (type tag-name) __name__)))))
|
||||||
|
(if (or (= tag-name ":")
|
||||||
|
(= tag-name "&"))
|
||||||
|
(raise (NameError (% "%s can't be used as a tag macro name" tag-name))))
|
||||||
|
(setv tag-name (.replace (hy.models.HyString tag-name)
|
||||||
|
tag-name))
|
||||||
|
`(eval-and-compile
|
||||||
|
(import hy)
|
||||||
|
((hy.macros.tag ~tag-name)
|
||||||
|
(fn ~lambda-list ~@body))))
|
||||||
|
|
||||||
(defmacro macro-error [location reason]
|
(defmacro macro-error [location reason]
|
||||||
"Error out properly within a macro at `location` giving `reason`."
|
"Error out properly within a macro at `location` giving `reason`."
|
||||||
`(raise (hy.errors.HyMacroExpansionError ~location ~reason)))
|
`(raise (hy.errors.HyMacroExpansionError ~location ~reason)))
|
||||||
|
@ -224,7 +224,7 @@ Return series of accumulated sums (or other binary function results)."
|
|||||||
(global _gensym_lock)
|
(global _gensym_lock)
|
||||||
(.acquire _gensym_lock)
|
(.acquire _gensym_lock)
|
||||||
(try (do (setv _gensym_counter (inc _gensym_counter))
|
(try (do (setv _gensym_counter (inc _gensym_counter))
|
||||||
(setv new_symbol (HySymbol (.format ":{0}_{1}" g _gensym_counter))))
|
(setv new_symbol (HySymbol (.format "_;{0}|{1}" g _gensym_counter))))
|
||||||
(finally (.release _gensym_lock)))
|
(finally (.release _gensym_lock)))
|
||||||
new_symbol)
|
new_symbol)
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class HyTypeError(TypeError):
|
|||||||
|
|
||||||
result += colored.yellow("%s: %s\n\n" %
|
result += colored.yellow("%s: %s\n\n" %
|
||||||
(self.__class__.__name__,
|
(self.__class__.__name__,
|
||||||
self.message.encode('utf-8')))
|
self.message))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ def macro(name):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
def _(fn):
|
def _(fn):
|
||||||
fn.__name__ = "({})".format(name)
|
fn.__name__ = '({})'.format(name)
|
||||||
try:
|
try:
|
||||||
argspec = getargspec(fn)
|
argspec = getargspec(fn)
|
||||||
fn._hy_macro_pass_compiler = argspec.keywords is not None
|
fn._hy_macro_pass_compiler = argspec.keywords is not None
|
||||||
@ -191,14 +191,14 @@ def macroexpand_1(tree, compiler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
m_copy = make_empty_fn_copy(m)
|
m_copy = make_empty_fn_copy(m)
|
||||||
m_copy(*ntree[1:], **opts)
|
m_copy(compiler.module_name, *ntree[1:], **opts)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
msg = "expanding `" + str(tree[0]) + "': "
|
msg = "expanding `" + str(tree[0]) + "': "
|
||||||
msg += str(e).replace("<lambda>()", "", 1).strip()
|
msg += str(e).replace("<lambda>()", "", 1).strip()
|
||||||
raise HyMacroExpansionError(tree, msg)
|
raise HyMacroExpansionError(tree, msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = m(*ntree[1:], **opts)
|
obj = m(compiler.module_name, *ntree[1:], **opts)
|
||||||
except HyTypeError as e:
|
except HyTypeError as e:
|
||||||
if e.expression is None:
|
if e.expression is None:
|
||||||
e.expression = tree
|
e.expression = tree
|
||||||
|
@ -10,9 +10,11 @@ from hy.errors import HyMacroExpansionError
|
|||||||
|
|
||||||
from hy.compiler import HyASTCompiler
|
from hy.compiler import HyASTCompiler
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@macro("test")
|
@macro("test")
|
||||||
def tmac(*tree):
|
def tmac(ETname, *tree):
|
||||||
""" Turn an expression into a list """
|
""" Turn an expression into a list """
|
||||||
return HyList(tree)
|
return HyList(tree)
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ def test_preprocessor_expression():
|
|||||||
assert obj == macroexpand(obj, HyASTCompiler(""))
|
assert obj == macroexpand(obj, HyASTCompiler(""))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail
|
||||||
def test_preprocessor_exceptions():
|
def test_preprocessor_exceptions():
|
||||||
""" Test that macro expansion raises appropriate exceptions"""
|
""" Test that macro expansion raises appropriate exceptions"""
|
||||||
try:
|
try:
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
;; license. See the LICENSE.
|
;; license. See the LICENSE.
|
||||||
|
|
||||||
(import [hy.contrib.walk [*]])
|
(import [hy.contrib.walk [*]])
|
||||||
|
(require [hy.contrib.walk [*]])
|
||||||
|
|
||||||
|
(import pytest)
|
||||||
|
|
||||||
(def walk-form '(print {"foo" "bar"
|
(def walk-form '(print {"foo" "bar"
|
||||||
"array" [1 2 3 [4]]
|
"array" [1 2 3 [4]]
|
||||||
@ -34,6 +37,304 @@
|
|||||||
(drop 1 [1 [2 [3 [4]]]]))
|
(drop 1 [1 [2 [3 [4]]]]))
|
||||||
[[2 [3 [4]] 2 [3 [4]]]])))
|
[[2 [3 [4]] 2 [3 [4]]]])))
|
||||||
|
|
||||||
|
(defmacro foo-walk []
|
||||||
|
42)
|
||||||
|
|
||||||
(defn test-macroexpand-all []
|
(defn test-macroexpand-all []
|
||||||
|
;; make sure a macro from the current module works
|
||||||
|
(assert (= (macroexpand-all '(foo-walk))
|
||||||
|
42))
|
||||||
|
(assert (= (macroexpand-all '(with [a 1]))
|
||||||
|
'(with* [a 1] (do))))
|
||||||
(assert (= (macroexpand-all '(with [a 1 b 2 c 3] (for [d c] foo)))
|
(assert (= (macroexpand-all '(with [a 1 b 2 c 3] (for [d c] foo)))
|
||||||
'(with* [a 1] (with* [b 2] (with* [c 3] (do (for* [d c] (do foo)))))))))
|
'(with* [a 1] (with* [b 2] (with* [c 3] (do (for* [d c] (do foo))))))))
|
||||||
|
(assert (= (macroexpand-all '(with [a 1]
|
||||||
|
'(with [b 2])
|
||||||
|
`(with [c 3]
|
||||||
|
~(with [d 4])
|
||||||
|
~@[(with [e 5])])))
|
||||||
|
'(with* [a 1]
|
||||||
|
(do '(with [b 2])
|
||||||
|
`(with [c 3]
|
||||||
|
~(with* [d 4] (do))
|
||||||
|
~@[(with* [e 5] (do))]))))))
|
||||||
|
|
||||||
|
(defn test-let-basic []
|
||||||
|
(assert (zero? (let [a 0] a)))
|
||||||
|
(setv a "a"
|
||||||
|
b "b")
|
||||||
|
(let [a "x"
|
||||||
|
b "y"]
|
||||||
|
(assert (= (+ a b)
|
||||||
|
"xy"))
|
||||||
|
(let [a "z"]
|
||||||
|
(assert (= (+ a b)
|
||||||
|
"zy")))
|
||||||
|
;; let-shadowed variable doesn't get clobbered.
|
||||||
|
(assert (= (+ a b)
|
||||||
|
"xy")))
|
||||||
|
(let [q "q"]
|
||||||
|
(assert (= q "q")))
|
||||||
|
(assert (= a "a"))
|
||||||
|
(assert (= b "b"))
|
||||||
|
(assert (in "a" (.keys (vars))))
|
||||||
|
;; scope of q is limited to let body
|
||||||
|
(assert (not-in "q" (.keys (vars)))))
|
||||||
|
|
||||||
|
(defn test-let-sequence []
|
||||||
|
;; assignments happen in sequence, not parallel.
|
||||||
|
(let [a "a"
|
||||||
|
b "b"
|
||||||
|
ab (+ a b)]
|
||||||
|
(assert (= ab "ab"))
|
||||||
|
(let [c "c"
|
||||||
|
abc (+ ab c)]
|
||||||
|
(assert (= abc "abc")))))
|
||||||
|
|
||||||
|
(defn test-let-early []
|
||||||
|
(setv a "a")
|
||||||
|
(let [q (+ a "x")
|
||||||
|
a 2 ; should not affect q
|
||||||
|
b 3]
|
||||||
|
(assert (= q "ax"))
|
||||||
|
(let [q (* a b)
|
||||||
|
a (+ a b)
|
||||||
|
b (* a b)]
|
||||||
|
(assert (= q 6))
|
||||||
|
(assert (= a 5))
|
||||||
|
(assert (= b 15))))
|
||||||
|
(assert (= a "a")))
|
||||||
|
|
||||||
|
(defn test-let-special []
|
||||||
|
;; special forms in function position still work as normal
|
||||||
|
(let [, 1]
|
||||||
|
(assert (= (, , ,)
|
||||||
|
(, 1 1)))))
|
||||||
|
|
||||||
|
(defn test-let-quasiquote []
|
||||||
|
(setv a-symbol 'a)
|
||||||
|
(let [a "x"]
|
||||||
|
(assert (= a "x"))
|
||||||
|
(assert (= 'a a-symbol))
|
||||||
|
(assert (= `a a-symbol))
|
||||||
|
(assert (= `(foo ~a)
|
||||||
|
'(foo "x")))
|
||||||
|
(assert (= `(foo `(bar a ~a ~~a))
|
||||||
|
'(foo `(bar a ~a ~"x"))))
|
||||||
|
(assert (= `(foo ~@[a])
|
||||||
|
'(foo "x")))
|
||||||
|
(assert (= `(foo `(bar [a] ~@[a] ~@~[a 'a `a] ~~@[a]))
|
||||||
|
'(foo `(bar [a] ~@[a] ~@["x" a a] ~"x"))))))
|
||||||
|
|
||||||
|
(defn test-let-except []
|
||||||
|
(let [foo 42
|
||||||
|
bar 33]
|
||||||
|
(assert (= foo 42))
|
||||||
|
(try
|
||||||
|
(do
|
||||||
|
1/0
|
||||||
|
(assert False))
|
||||||
|
(except [foo Exception]
|
||||||
|
;; let bindings should work in except block
|
||||||
|
(assert (= bar 33))
|
||||||
|
;; but exception bindings can shadow let bindings
|
||||||
|
(assert (instance? Exception foo))))
|
||||||
|
;; let binding did not get clobbered.
|
||||||
|
(assert (= foo 42))))
|
||||||
|
|
||||||
|
(defn test-let-mutation []
|
||||||
|
(setv foo 42)
|
||||||
|
(setv error False)
|
||||||
|
(let [foo 12
|
||||||
|
bar 13]
|
||||||
|
(assert (= foo 12))
|
||||||
|
(setv foo 14)
|
||||||
|
(assert (= foo 14))
|
||||||
|
(del foo)
|
||||||
|
;; deleting a let binding should not affect others
|
||||||
|
(assert (= bar 13))
|
||||||
|
(try
|
||||||
|
;; foo=42 is still shadowed, but the let binding was deleted.
|
||||||
|
(do
|
||||||
|
foo
|
||||||
|
(assert False))
|
||||||
|
(except [le LookupError]
|
||||||
|
(setv error le)))
|
||||||
|
(setv foo 16)
|
||||||
|
(assert (= foo 16))
|
||||||
|
(setv [foo bar baz] [1 2 3])
|
||||||
|
(assert (= foo 1))
|
||||||
|
(assert (= bar 2))
|
||||||
|
(assert (= baz 3)))
|
||||||
|
(assert error)
|
||||||
|
(assert (= foo 42))
|
||||||
|
(assert (= baz 3)))
|
||||||
|
|
||||||
|
(defn test-let-break []
|
||||||
|
(for [x (range 3)]
|
||||||
|
(let [done (odd? x)]
|
||||||
|
(if done (break))))
|
||||||
|
(assert (= x 1)))
|
||||||
|
|
||||||
|
(defn test-let-continue []
|
||||||
|
(let [foo []]
|
||||||
|
(for [x (range 10)]
|
||||||
|
(let [odd (odd? x)]
|
||||||
|
(if odd (continue))
|
||||||
|
(.append foo x)))
|
||||||
|
(assert (= foo [0 2 4 6 8]))))
|
||||||
|
|
||||||
|
(defn test-let-yield []
|
||||||
|
(defn grind []
|
||||||
|
(yield 0)
|
||||||
|
(let [a 1
|
||||||
|
b 2]
|
||||||
|
(yield a)
|
||||||
|
(yield b)))
|
||||||
|
(assert (= (tuple (grind))
|
||||||
|
(, 0 1 2))))
|
||||||
|
|
||||||
|
(defn test-let-return []
|
||||||
|
(defn get-answer []
|
||||||
|
(let [answer 42]
|
||||||
|
(return answer)))
|
||||||
|
(assert (= (get-answer)
|
||||||
|
42)))
|
||||||
|
|
||||||
|
(defn test-let-import []
|
||||||
|
(let [types 6]
|
||||||
|
;; imports don't fail, even if using a let-bound name
|
||||||
|
(import types)
|
||||||
|
;; let-bound name is not affected
|
||||||
|
(assert (= types 6)))
|
||||||
|
;; import happened in Python scope.
|
||||||
|
(assert (in "types" (vars)))
|
||||||
|
(assert (instance? types.ModuleType types)))
|
||||||
|
|
||||||
|
(defn test-let-defclass []
|
||||||
|
(let [Foo 42
|
||||||
|
quux object]
|
||||||
|
;; the name of the class is just a symbol, even if it's a let binding
|
||||||
|
(defclass Foo [quux] ; let bindings apply in inheritance list
|
||||||
|
;; let bindings apply inside class body
|
||||||
|
(setv x Foo)
|
||||||
|
;; quux is not local
|
||||||
|
(setv quux "quux"))
|
||||||
|
(assert (= quux "quux")))
|
||||||
|
;; defclass always creates a python-scoped variable, even if it's a let binding name
|
||||||
|
(assert (= Foo.x 42)))
|
||||||
|
|
||||||
|
(defn test-let-dot []
|
||||||
|
(setv foo (fn [])
|
||||||
|
foo.a 42)
|
||||||
|
(let [a 1
|
||||||
|
b []]
|
||||||
|
(assert (= a 1))
|
||||||
|
(assert (= b []))
|
||||||
|
;; method syntax not affected
|
||||||
|
(.append b 2)
|
||||||
|
(assert (= b [2]))
|
||||||
|
;; attrs access is not affected
|
||||||
|
(assert (= foo.a 42))
|
||||||
|
(assert (= (. foo a)
|
||||||
|
42))
|
||||||
|
;; but indexing is
|
||||||
|
(assert (= (. [1 2 3]
|
||||||
|
[a])
|
||||||
|
2))))
|
||||||
|
|
||||||
|
(defn test-let-positional []
|
||||||
|
(let [a 0
|
||||||
|
b 1
|
||||||
|
c 2]
|
||||||
|
(defn foo [a b]
|
||||||
|
(, a b c))
|
||||||
|
(assert (= (foo 100 200)
|
||||||
|
(, 100 200 2)))
|
||||||
|
(setv c 300)
|
||||||
|
(assert (= (foo 1000 2000)
|
||||||
|
(, 1000 2000 300)))
|
||||||
|
(assert (= a 0))
|
||||||
|
(assert (= b 1))
|
||||||
|
(assert (= c 300))))
|
||||||
|
|
||||||
|
(defn test-let-rest []
|
||||||
|
(let [xs 6
|
||||||
|
a 88
|
||||||
|
c 64
|
||||||
|
&rest 12]
|
||||||
|
(defn foo [a b &rest xs]
|
||||||
|
(-= a 1)
|
||||||
|
(setv xs (list xs))
|
||||||
|
(.append xs 42)
|
||||||
|
(, &rest a b c xs))
|
||||||
|
(assert (= xs 6))
|
||||||
|
(assert (= a 88))
|
||||||
|
(assert (= (foo 1 2 3 4)
|
||||||
|
(, 12 0 2 64 [3 4 42])))
|
||||||
|
(assert (= xs 6))
|
||||||
|
(assert (= c 64))
|
||||||
|
(assert (= a 88))))
|
||||||
|
|
||||||
|
(defn test-let-kwargs []
|
||||||
|
(let [kws 6
|
||||||
|
&kwargs 13]
|
||||||
|
(defn foo [&kwargs kws]
|
||||||
|
(, &kwargs kws))
|
||||||
|
(assert (= kws 6))
|
||||||
|
(assert (= (foo :a 1)
|
||||||
|
(, 13 {"a" 1})))))
|
||||||
|
|
||||||
|
(defn test-let-optional []
|
||||||
|
(let [a 1
|
||||||
|
b 6
|
||||||
|
d 2]
|
||||||
|
(defn foo [&optional [a a] b [c d]]
|
||||||
|
(, a b c))
|
||||||
|
(assert (= (foo)
|
||||||
|
(, 1 None 2)))
|
||||||
|
(assert (= (foo 10 20 30)
|
||||||
|
(, 10 20 30)))))
|
||||||
|
|
||||||
|
(defn test-let-key []
|
||||||
|
(let [a 1
|
||||||
|
b 6
|
||||||
|
d 2]
|
||||||
|
(defn foo [&key {a a b None c d}]
|
||||||
|
(, a b c))
|
||||||
|
(assert (= (foo)
|
||||||
|
(, 1 None 2)))
|
||||||
|
(assert (= (foo 10 20 30)
|
||||||
|
(, 10 20 30)))
|
||||||
|
(assert (= (, a b d)
|
||||||
|
(, 1 6 2)))))
|
||||||
|
|
||||||
|
(defn test-let-closure []
|
||||||
|
(let [count 0]
|
||||||
|
(defn +count [&optional [x 1]]
|
||||||
|
(+= count x)
|
||||||
|
count))
|
||||||
|
;; let bindings can still exist outside of a let body
|
||||||
|
(assert (= 1 (+count)))
|
||||||
|
(assert (= 2 (+count)))
|
||||||
|
(assert (= 42 (+count 40))))
|
||||||
|
|
||||||
|
(defmacro triple [a]
|
||||||
|
(setv g!a (gensym a))
|
||||||
|
`(do
|
||||||
|
(setv ~g!a ~a)
|
||||||
|
(+ ~g!a ~g!a ~g!a)))
|
||||||
|
|
||||||
|
(defmacro ap-triple []
|
||||||
|
'(+ a a a))
|
||||||
|
|
||||||
|
(defn test-let-macros []
|
||||||
|
(let [a 1
|
||||||
|
b (triple a)
|
||||||
|
c (ap-triple)]
|
||||||
|
(assert (= (triple a)
|
||||||
|
3))
|
||||||
|
(assert (= (ap-triple)
|
||||||
|
3))
|
||||||
|
(assert (= b 3))
|
||||||
|
(assert (= c 3))))
|
||||||
|
@ -272,10 +272,10 @@ result['y in globals'] = 'y' in globals()")
|
|||||||
(import [hy.models [HySymbol]])
|
(import [hy.models [HySymbol]])
|
||||||
(setv s1 (gensym))
|
(setv s1 (gensym))
|
||||||
(assert (isinstance s1 HySymbol))
|
(assert (isinstance s1 HySymbol))
|
||||||
(assert (= 0 (.find s1 ":G_")))
|
(assert (= 0 (.find s1 "_;G|")))
|
||||||
(setv s2 (gensym "xx"))
|
(setv s2 (gensym "xx"))
|
||||||
(setv s3 (gensym "xx"))
|
(setv s3 (gensym "xx"))
|
||||||
(assert (= 0 (.find s2 ":xx_")))
|
(assert (= 0 (.find s2 "_;xx|")))
|
||||||
(assert (not (= s2 s3)))
|
(assert (not (= s2 s3)))
|
||||||
(assert (not (= (str s2) (str s3)))))
|
(assert (not (= (str s2) (str s3)))))
|
||||||
|
|
||||||
|
@ -163,8 +163,8 @@
|
|||||||
(setv s1 (to_source _ast1))
|
(setv s1 (to_source _ast1))
|
||||||
(setv s2 (to_source _ast2))
|
(setv s2 (to_source _ast2))
|
||||||
;; and make sure there is something new that starts with :G_
|
;; and make sure there is something new that starts with :G_
|
||||||
(assert (in ":G_" s1))
|
(assert (in "_;G|" s1))
|
||||||
(assert (in ":G_" s2))
|
(assert (in "_;G|" s2))
|
||||||
;; but make sure the two don't match each other
|
;; but make sure the two don't match each other
|
||||||
(assert (not (= s1 s2))))
|
(assert (not (= s1 s2))))
|
||||||
|
|
||||||
@ -188,8 +188,8 @@
|
|||||||
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
|
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
|
||||||
(setv s1 (to_source _ast1))
|
(setv s1 (to_source _ast1))
|
||||||
(setv s2 (to_source _ast2))
|
(setv s2 (to_source _ast2))
|
||||||
(assert (in ":a_" s1))
|
(assert (in "_;a|" s1))
|
||||||
(assert (in ":a_" s2))
|
(assert (in "_;a|" s2))
|
||||||
(assert (not (= s1 s2))))
|
(assert (not (= s1 s2))))
|
||||||
|
|
||||||
(defn test-defmacro-g! []
|
(defn test-defmacro-g! []
|
||||||
@ -211,8 +211,8 @@
|
|||||||
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
|
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
|
||||||
(setv s1 (to_source _ast1))
|
(setv s1 (to_source _ast1))
|
||||||
(setv s2 (to_source _ast2))
|
(setv s2 (to_source _ast2))
|
||||||
(assert (in ":res_" s1))
|
(assert (in "_;res|" s1))
|
||||||
(assert (in ":res_" s2))
|
(assert (in "_;res|" s2))
|
||||||
(assert (not (= s1 s2)))
|
(assert (not (= s1 s2)))
|
||||||
|
|
||||||
;; defmacro/g! didn't like numbers initially because they
|
;; defmacro/g! didn't like numbers initially because they
|
||||||
@ -240,8 +240,8 @@
|
|||||||
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
|
(setv _ast2 (import_buffer_to_ast macro1 "foo"))
|
||||||
(setv s1 (to_source _ast1))
|
(setv s1 (to_source _ast1))
|
||||||
(setv s2 (to_source _ast2))
|
(setv s2 (to_source _ast2))
|
||||||
(assert (in ":res_" s1))
|
(assert (in "_;res|" s1))
|
||||||
(assert (in ":res_" s2))
|
(assert (in "_;res|" s2))
|
||||||
(assert (not (= s1 s2)))
|
(assert (not (= s1 s2)))
|
||||||
|
|
||||||
;; defmacro/g! didn't like numbers initially because they
|
;; defmacro/g! didn't like numbers initially because they
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
(defn test-exception-cause []
|
(defn test-exception-cause []
|
||||||
(try (raise ValueError :from NameError)
|
(try (raise ValueError :from NameError)
|
||||||
(except [e [ValueError]]
|
(except [e [ValueError]]
|
||||||
(assert (= (type (. e __cause__)) NameError)))))
|
(assert (= (type (. e __cause__)) NameError)))))
|
||||||
|
|
||||||
|
|
||||||
(defn test-kwonly []
|
(defn test-kwonly []
|
||||||
@ -21,8 +21,8 @@
|
|||||||
;; keyword-only without default ...
|
;; keyword-only without default ...
|
||||||
(defn kwonly-foo-no-default [&kwonly foo] foo)
|
(defn kwonly-foo-no-default [&kwonly foo] foo)
|
||||||
(setv attempt-to-omit-default (try
|
(setv attempt-to-omit-default (try
|
||||||
(kwonly-foo-no-default)
|
(kwonly-foo-no-default)
|
||||||
(except [e [Exception]] e)))
|
(except [e [Exception]] e)))
|
||||||
;; works
|
;; works
|
||||||
(assert (= (kwonly-foo-no-default :foo "quux") "quux"))
|
(assert (= (kwonly-foo-no-default :foo "quux") "quux"))
|
||||||
;; raises TypeError with appropriate message if not supplied
|
;; raises TypeError with appropriate message if not supplied
|
||||||
@ -64,9 +64,23 @@
|
|||||||
(assert 0))
|
(assert 0))
|
||||||
(defn yield-from-test []
|
(defn yield-from-test []
|
||||||
(for* [i (range 3)]
|
(for* [i (range 3)]
|
||||||
(yield i))
|
(yield i))
|
||||||
(try
|
(try
|
||||||
(yield-from (yield-from-subgenerator-test))
|
(yield-from (yield-from-subgenerator-test))
|
||||||
(except [e AssertionError]
|
(except [e AssertionError]
|
||||||
(yield 4))))
|
(yield 4))))
|
||||||
(assert (= (list (yield-from-test)) [0 1 2 1 2 3 4])))
|
(assert (= (list (yield-from-test)) [0 1 2 1 2 3 4])))
|
||||||
|
|
||||||
|
(require [hy.contrib.walk [let]])
|
||||||
|
|
||||||
|
(defn test-let-optional []
|
||||||
|
(let [a 1
|
||||||
|
b 6
|
||||||
|
d 2]
|
||||||
|
(defn foo [&kwonly [a a] b [c d]]
|
||||||
|
(, a b c))
|
||||||
|
(assert (= (foo :b "b")
|
||||||
|
(, 1 "b" 2)))
|
||||||
|
(assert (= (foo :b 20 :a 10 :c 30)
|
||||||
|
(, 10 20 30)))))
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ from hy import HyList, HyInteger
|
|||||||
|
|
||||||
|
|
||||||
@macro("qplah")
|
@macro("qplah")
|
||||||
def tmac(*tree):
|
def tmac(ETname, *tree):
|
||||||
return HyList((HyInteger(8), ) + tree)
|
return HyList((HyInteger(8), ) + tree)
|
||||||
|
|
||||||
|
|
||||||
@macro("parald")
|
@macro("parald")
|
||||||
def tmac2(*tree):
|
def tmac2(ETname, *tree):
|
||||||
return HyList((HyInteger(9), ) + tree)
|
return HyList((HyInteger(9), ) + tree)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user