diff --git a/docs/index.rst b/docs/index.rst
index 5c14f4e..47f1f11 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -32,8 +32,7 @@ Meet our mascot, "Cuddles":
.. \|\||/
-Read more about Hy in these docs! Or, if you'd like, try the
-`interactive hy->python demo `_!
+Read more about Hy in these docs!
We're also on IRC! Join
`#hy on irc.freenode.net `_!
diff --git a/docs/language/api.rst b/docs/language/api.rst
index 7b97ae9..e1c0fe7 100644
--- a/docs/language/api.rst
+++ b/docs/language/api.rst
@@ -38,6 +38,34 @@ Hy features a number special forms that are used to help generate
correct Python AST. The following are "special" forms, which may have
behavior that's slightly unexpected in some situations.
+->
+--
+
+`->` or `threading macro` is used to avoid nesting of expressions. The threading
+macro inserts each expression into the next expression’s first argument place.
+The following code demonstrates this:
+
+.. code-block:: clj
+
+ => (defn output [a b] (print a b))
+ => (-> (+ 5 5) (output 5))
+ 10 5
+
+
+->>
+---
+
+`->>` or `threading tail macro` is similar to `threading macro` but instead of
+inserting each expression into the next expression’s first argument place it
+appends it as the last argument. The following code demonstrates this:
+
+.. code-block:: clj
+
+ => (defn output [a b] (print a b))
+ => (->> (+ 5 5) (output 5))
+ 5 10
+
+
and
---
@@ -75,6 +103,7 @@ case the first false value will be returned. Examples of usage:
I can has False
False
+
assert
------
@@ -88,22 +117,29 @@ condition is not met, an `AssertionError` is raised. The example usage:
Assert takes a single parameter, a conditional that evaluates to either `True`
or `False`.
+
assoc
-----
`assoc` form is used to associate a key with a value in a dictionary or to set
-an index of a list to a value. It takes three parameters: `datastructure` to be
-modified, `key` or `index` and `value`.
+an index of a list to a value. It takes at least three parameters: `datastructure`
+to be modified, `key` or `index` and `value`. If more than three parameters are
+used it will associate in pairs.
Examples of usage:
.. code-block:: clj
- =>(let [[collection ({})]]
+ =>(let [[collection {}]]
... (assoc collection "Dog" "Bark")
... (print collection))
{u'Dog': u'Bark'}
+ =>(let [[collection {}]]
+ ... (assoc collection "Dog" "Bark" "Cat" "Meow")
+ ... (print collection))
+ {u'Cat': u'Meow', u'Dog': u'Bark'}
+
=>(let [[collection [1 2 3 4]]]
... (assoc collection 2 None)
... (print collection))
@@ -111,6 +147,7 @@ Examples of usage:
.. note:: `assoc` modifies the datastructure in place and returns `None`.
+
break
-----
@@ -126,6 +163,36 @@ the user enters `k`.
(print "Try again")))
+cond
+----
+
+`cond` macro can be used to build nested if-statements.
+
+The following example shows the relationship between the macro and the expanded
+code:
+
+.. code-block:: clj
+
+ (cond (condition-1 result-1)
+ (condition-2 result-2))
+
+ (if condition-1 result-1
+ (if condition-2 result-2))
+
+As shown below only the first matching result block is executed.
+
+.. code-block:: clj
+
+ => (defn check-value [value]
+ ... (cond ((< value 5) (print "value is smaller than 5"))
+ ... ((= value 5) (print "value is equal to 5"))
+ ... ((> value 5) (print "value is greater than 5"))
+ ... (True (print "value is something that it should not be"))))
+
+ => (check-value 6)
+ value is greater than 5
+
+
continue
--------
@@ -144,6 +211,7 @@ however is called only for every other value in the list.
(continue))
(side-effect2 x)))
+
do / progn
----------
@@ -176,14 +244,73 @@ Some example usage:
def / setv
-----------------
+`def` and `setv` are used to bind value, object or a function to a symbol. For
+example:
+
+.. code-block:: clj
+
+ => (def names ["Alice" "Bob" "Charlie"]
+ => (print names)
+ [u'Alice', u'Bob', u'Charlie']
+
+ => (setv counter (fn [collection item] (.count collection item)))
+ => (counter [1 2 3 4 5 2 3] 2)
+ 2
+
defclass
--------
+new classes are declared with `defclass`. It can takes two optional parameters:
+a vector defining a possible super classes and another vector containing
+attributes of the new class as two item vectors.
+
+.. code-block:: clj
+
+ (defclass class-name [super-class-1 super-class-2]
+ [[attribute value]])
+
+Both values and functions can be bound on the new class as shown by the example
+below:
+
+.. code-block:: clj
+
+ => (defclass Cat []
+ ... [[age None]
+ ... [colour "white"]
+ ... [speak (fn [self] (print "Meow"))]])
+
+ => (def spot (Cat))
+ => (setv spot.colour "Black")
+ 'Black'
+ => (.speak spot)
+ Meow
+
+
+defn / defun
+------------
+
defmacro
--------
+`defmacro` is used to define macros. The general format is
+`(defmacro [parameters] expr)`.
+
+Following example defines a macro that can be used to swap order of elements in
+code, allowing the user to write code in infix notation, where operator is in
+between the operands.
+
+.. code-block:: clj
+
+ => (defmacro infix [code]
+ ... (quasiquote (
+ ... (unquote (get code 1))
+ ... (unquote (get code 0))
+ ... (unquote (get code 2)))))
+
+ => (infix (1 + 1))
+ 2
eval
----
@@ -197,9 +324,70 @@ eval-when-compile
-----------------
+first / car
+-----------
+
+`first` and `car` are macros for accessing the first element of a collection:
+
+.. code-block:: clj
+
+ => (first (range 10))
+ 0
+
+
+for
+---
+
+`for` macro is used to build nested `foreach` loops. The macro takes two
+parameters, first being a vector specifying collections to iterate over and
+variables to bind. The second parameter is a statement which is executed during
+each loop:
+
+.. code-block:: clj
+
+ (for [x iter y iter] stmt)
+
+ (foreach [x iter]
+ (foreach [y iter] stmt))
+
+
foreach
-------
+`foreach` is used to call a function for each element in a list or vector.
+Results are discarded and None is returned instead. Example code iterates over
+collection and calls side-effect to each element in the collection:
+
+.. code-block:: clj
+
+ ;; assuming that (side-effect) is a function that takes a single parameter
+ (foreach [element collection] (side-effect element))
+
+ ;; foreach can have an optional else block
+ (foreach [element collection] (side-effect element)
+ (else (side-effect-2)))
+
+The optional `else` block is executed only if the `foreach` loop terminates
+normally. If the execution is halted with `break`, the `else` does not execute.
+
+.. code-block:: clj
+
+ => (foreach [element [1 2 3]] (if (< element 3)
+ ... (print element)
+ ... (break))
+ ... (else (print "loop finished")))
+ 1
+ 2
+
+ => (foreach [element [1 2 3]] (if (< element 4)
+ ... (print element)
+ ... (break))
+ ... (else (print "loop finished")))
+ 1
+ 2
+ 3
+ loop finished
+
get
---
@@ -224,9 +412,29 @@ Example usages:
.. note:: `get` raises an IndexError if a list is queried for an index that is
out of bounds.
+
global
------
+`global` can be used to mark a symbol as global. This allows the programmer to
+assign a value to a global symbol. Reading a global symbol does not require the
+`global` keyword, just the assigning does.
+
+Following example shows how global `a` is assigned a value in a function and later
+on printed on another function. Without the `global` keyword, the second function
+would thrown a `NameError`.
+
+.. code-block:: clj
+
+ (defn set-a [value]
+ (global a)
+ (setv a value))
+
+ (defn print-a []
+ (print a))
+
+ (set-a 5)
+ (print-a)
if
--
@@ -283,10 +491,55 @@ of import you can use.
kwapply
-------
+`kwapply` can be used to supply keyword arguments to a function.
+
+For example:
+
+.. code-block:: clj
+
+ => (defn rent-car [&kwargs kwargs]
+ ... (cond ((in :brand kwargs) (print "brand:" (:brand kwargs)))
+ ... ((in :model kwargs) (print "model:" (:model kwargs)))))
+
+ => (kwapply (rent-car) {:model "T-Model"})
+ model: T-Model
+
+ => (defn total-purchase [price amount &optional [fees 1.05] [vat 1.1]]
+ ... (* price amount fees vat))
+
+ => (total-purchase 10 15)
+ 173.25
+
+ => (kwapply (total-purchase 10 15) {"vat" 1.05})
+ 165.375
+
lambda / fn
-----------
+`lambda` and `fn` can be used to define an anonymous function. The parameters are
+similar to `defn`: first parameter is vector of parameters and the rest is the
+body of the function. lambda returns a new function. In the example an anonymous
+function is defined and passed to another function for filtering output.
+
+.. code-block:: clj
+
+ => (def people [{:name "Alice" :age 20}
+ ... {:name "Bob" :age 25}
+ ... {:name "Charlie" :age 50}
+ ... {:name "Dave" :age 5}])
+
+ => (defn display-people [people filter]
+ ... (foreach [person people] (if (filter person) (print (:name person)))))
+
+ => (display-people people (fn [person] (< (:age person) 25)))
+ Alice
+ Dave
+
+
+let
+---
+
list-comp
---------
@@ -309,6 +562,7 @@ conditional expression. Some examples:
=> (list-comp (* x 2) [x collection] (< x 5))
[0, 2, 4, 6, 8]
+
not
---
@@ -374,9 +628,32 @@ the `print` form is used to output on screen. Example usage:
.. note:: `print` always returns None
+
require
-------
+`require` is used to import macros from a given module. It takes at least one
+parameter specifying the module which macros should be imported. Multiple
+modules can be imported with a single `require`.
+
+The following example will import macros from `module-1` and `module-2`:
+
+.. code-block:: clj
+
+ (require module-1 module-2)
+
+
+rest / cdr
+----------
+
+`rest` and `cdr` return the collection passed as an argument without the first
+element:
+
+.. code-block:: clj
+
+ => (rest (range 10))
+ [1, 2, 3, 4, 5, 6, 7, 8, 9]
+
slice
-----
@@ -457,6 +734,33 @@ be executed. If no errors are raised the `else` block is executed. Regardless
if an error was raised or not, the `finally` block is executed as last.
+unless
+------
+
+`unless` macro is a shorthand for writing a if-statement that checks if the
+given conditional is False. The following shows how the macro expands into code.
+
+.. code-block:: clj
+
+ (unless conditional statement)
+
+ (if conditional
+ None
+ (do statement))
+
+when
+----
+
+`when` is similar to `unless`, except it tests when the given conditional is
+True. It is not possible to have an `else` block in `when` macro. The following
+shows how the macro is expanded into code.
+
+.. code-block:: clj
+
+ (when conditional statement)
+
+ (if conditional (do statement))
+
while
-----
@@ -469,16 +773,75 @@ The following example will output "hello world!" on screen indefinetely:
(while True (print "hello world!"))
+
with
----
+`with` is used to wrap execution of a block with a context manager. The context
+manager can then set up the local system and tear it down in a controlled
+manner. Typical example of using `with` is processing files. `with` can bind
+context to an argument or ignore it completely, as shown below:
+
+.. code-block:: clj
+
+ (with [arg (expr)] block)
+
+ (with [(expr)] block)
+
+The following example will open file `NEWS` and print its content on screen. The
+file is automatically closed after it has been processed.
+
+.. code-block:: clj
+
+ (with [f (open "NEWS")] (print (.read f)))
+
with-decorator
--------------
+`with-decorator` is used to wrap a function with another. The function performing
+decoration should accept a single value, the function being decorated and return
+a new function. `with-decorator` takes two parameters, the function performing
+decoration and the function being decorated.
+
+In the following example, `inc-decorator` is used to decorate function `addition`
+with a function that takes two parameters and calls the decorated function with
+values that are incremented by 1. When decorated `addition` is called with values
+1 and 1, the end result will be 4 (1+1 + 1+1).
+
+.. code-block:: clj
+
+ => (defn inc-decorator [func]
+ ... (fn [value-1 value-2] (func (+ value-1 1) (+ value-2 1))))
+ => (with-decorator inc-decorator (defn addition [a b] (+ a b)))
+ => (addition 1 1)
+ 4
+
yield
-----
+`yield` is used to create a generator object, that returns 1 or more values.
+The generator is iterable and therefore can be used in loops, list
+comprehensions and other similar constructs.
+Especially the second example shows how generators can be used to generate
+infinite series without consuming infinite amount of memory.
+.. code-block:: clj
+
+ => (defn multiply [bases coefficients]
+ ... (foreach [(, base coefficient) (zip bases coefficients)]
+ ... (yield (* base coefficient))))
+
+ => (multiply (range 5) (range 5))
+
+
+ => (list-comp value [value (multiply (range 10) (range 10))])
+ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
+
+ => (import random)
+ => (defn random-numbers [low high]
+ ... (while True (yield (.randint random low high))))
+ => (list-comp x [x (take 15 (random-numbers 1 50))])])
+ [7, 41, 6, 22, 32, 17, 5, 38, 18, 38, 17, 14, 23, 23, 19]
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 0c3838b..5ce99f0 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -392,7 +392,7 @@ In python we might see::
The same thing in Hy::
- => (defn optional_arg [pos1 pos2 &optional keyword1 [keyword2 88]]
+ => (defn optional_arg [pos1 pos2 &optional keyword1 [keyword2 42]]
... [pos1 pos2 keyword1 keyword2])
=> (optional_arg 1 2)
[1 2 None 42]
diff --git a/hy/compiler.py b/hy/compiler.py
index cbe8472..36af599 100644
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -154,7 +154,8 @@ class Result(object):
The Result object is interoperable with python AST objects: when an AST
object gets added to a Result object, it gets converted on-the-fly.
"""
- __slots__ = ("imports", "stmts", "temp_variables", "_expr", "__used_expr")
+ __slots__ = ("imports", "stmts", "temp_variables",
+ "_expr", "__used_expr", "contains_yield")
def __init__(self, *args, **kwargs):
if args:
@@ -165,12 +166,14 @@ class Result(object):
self.stmts = []
self.temp_variables = []
self._expr = None
+ self.contains_yield = False
self.__used_expr = False
# XXX: Make sure we only have AST where we should.
for kwarg in kwargs:
- if kwarg not in ["imports", "stmts", "expr", "temp_variables"]:
+ if kwarg not in ["imports", "contains_yield", "stmts", "expr",
+ "temp_variables"]:
raise TypeError(
"%s() got an unexpected keyword argument '%s'" % (
self.__class__.__name__, kwarg))
@@ -282,13 +285,21 @@ class Result(object):
result.stmts = self.stmts + other.stmts
result.expr = other.expr
result.temp_variables = other.temp_variables
+ result.contains_yield = False
+ if self.contains_yield or other.contains_yield:
+ result.contains_yield = True
+
return result
def __str__(self):
- return "Result(imports=[%s], stmts=[%s], expr=%s)" % (
+ return (
+ "Result(imports=[%s], stmts=[%s], "
+ "expr=%s, contains_yield=%s)"
+ ) % (
", ".join(ast.dump(x) for x in self.imports),
", ".join(ast.dump(x) for x in self.stmts),
ast.dump(self.expr) if self.expr else None,
+ self.contains_yield
)
@@ -318,7 +329,7 @@ def _raise_wrong_args_number(expression, error):
len(expression)))
-def checkargs(exact=None, min=None, max=None):
+def checkargs(exact=None, min=None, max=None, even=None):
def _dec(fn):
def checker(self, expression):
if exact is not None and (len(expression) - 1) != exact:
@@ -335,6 +346,14 @@ def checkargs(exact=None, min=None, max=None):
expression,
"`%%s' needs at most %d arguments, got %%d" % (max))
+ is_even = not((len(expression) - 1) % 2)
+ if even is not None and is_even != even:
+ even_str = "even" if even else "odd"
+ _raise_wrong_args_number(
+ expression,
+ "`%%s' needs an %s number of arguments, got %%d"
+ % (even_str))
+
return fn(self, expression)
return checker
@@ -1011,7 +1030,7 @@ class HyASTCompiler(object):
@checkargs(max=1)
def compile_yield_expression(self, expr):
expr.pop(0)
- ret = Result()
+ ret = Result(contains_yield=True)
value = None
if expr != []:
@@ -1130,25 +1149,28 @@ class HyASTCompiler(object):
ctx=ast.Load())
@builds("assoc")
- @checkargs(3)
+ @checkargs(min=3, even=False)
def compile_assoc_expression(self, expr):
expr.pop(0) # assoc
# (assoc foo bar baz) => foo[bar] = baz
target = self.compile(expr.pop(0))
- key = self.compile(expr.pop(0))
- val = self.compile(expr.pop(0))
+ ret = target
+ i = iter(expr)
+ for (key, val) in ((self.compile(x), self.compile(y))
+ for (x, y) in zip(i, i)):
- return target + key + val + ast.Assign(
- lineno=expr.start_line,
- col_offset=expr.start_column,
- targets=[
- ast.Subscript(
- lineno=expr.start_line,
- col_offset=expr.start_column,
- value=target.force_expr,
- slice=ast.Index(value=key.force_expr),
- ctx=ast.Store())],
- value=val.force_expr)
+ ret += key + val + ast.Assign(
+ lineno=expr.start_line,
+ col_offset=expr.start_column,
+ targets=[
+ ast.Subscript(
+ lineno=expr.start_line,
+ col_offset=expr.start_column,
+ value=target.force_expr,
+ slice=ast.Index(value=key.force_expr),
+ ctx=ast.Store())],
+ value=val.force_expr)
+ return ret
@builds("with_decorator")
@checkargs(min=1)
@@ -1178,11 +1200,18 @@ class HyASTCompiler(object):
thing = self._storeize(self.compile(args.pop(0)))
body = self._compile_branch(expr)
- body += body.expr_as_stmt()
- if not body.stmts:
- body += ast.Pass(lineno=expr.start_line,
- col_offset=expr.start_column)
+ var = self.get_anon_var()
+ name = ast.Name(id=ast_str(var), arg=ast_str(var),
+ ctx=ast.Store(),
+ lineno=expr.start_line,
+ col_offset=expr.start_column)
+
+ # Store the result of the body in a tempvar
+ body += ast.Assign(targets=[name],
+ value=body.force_expr,
+ lineno=expr.start_line,
+ col_offset=expr.start_column)
the_with = ast.With(context_expr=ctx.force_expr,
lineno=expr.start_line,
@@ -1194,7 +1223,16 @@ class HyASTCompiler(object):
the_with.items = [ast.withitem(context_expr=ctx.force_expr,
optional_vars=thing)]
- return ctx + the_with
+ ret = ctx + the_with
+ # And make our expression context our temp variable
+ expr_name = ast.Name(id=ast_str(var), arg=ast_str(var),
+ ctx=ast.Load(),
+ lineno=expr.start_line,
+ col_offset=expr.start_column)
+
+ ret += Result(expr=expr_name, temp_variables=[expr_name, name])
+
+ return ret
@builds(",")
def compile_tuple(self, expr):
@@ -1537,6 +1575,8 @@ class HyASTCompiler(object):
body=body.stmts,
orelse=orel.stmts)
+ ret.contains_yield = body.contains_yield
+
return ret
@builds("while")
@@ -1554,6 +1594,8 @@ class HyASTCompiler(object):
lineno=expr.start_line,
col_offset=expr.start_column)
+ ret.contains_yield = body.contains_yield
+
return ret
@builds(HyList)
@@ -1597,9 +1639,12 @@ class HyASTCompiler(object):
return ret
if body.expr:
- body += ast.Return(value=body.expr,
- lineno=body.expr.lineno,
- col_offset=body.expr.col_offset)
+ if body.contains_yield:
+ body += body.expr_as_stmt()
+ else:
+ body += ast.Return(value=body.expr,
+ lineno=body.expr.lineno,
+ col_offset=body.expr.col_offset)
if not body.stmts:
body += ast.Pass(lineno=expression.start_line,
@@ -1643,6 +1688,17 @@ class HyASTCompiler(object):
body = Result()
+ # grab the doc string, if there is one
+ if expression and isinstance(expression[0], HyString):
+ docstring = expression.pop(0)
+ symb = HySymbol("__doc__")
+ symb.start_line = docstring.start_line
+ symb.start_column = docstring.start_column
+ body += self._compile_assign(symb, docstring,
+ docstring.start_line,
+ docstring.start_column)
+ body += body.expr_as_stmt()
+
if expression:
try:
body_expression = iter(expression.pop(0))
diff --git a/tests/native_tests/defclass.hy b/tests/native_tests/defclass.hy
index 4860d39..c1ebe5b 100644
--- a/tests/native_tests/defclass.hy
+++ b/tests/native_tests/defclass.hy
@@ -60,3 +60,26 @@
(x)
(assert false))
(except [NameError])))
+
+(defn test-defclass-docstring []
+ "NATIVE: test defclass docstring"
+ (defclass A []
+ [[--doc-- "doc string"]
+ [x 1]])
+ (setv a (A))
+ (assert (= a.__doc__ "doc string"))
+ (defclass B []
+ "doc string"
+ [[x 1]])
+ (setv b (B))
+ (assert (= b.x 1))
+ (assert (= b.__doc__ "doc string"))
+ (defclass MultiLine []
+ "begin a very long multi-line string to make
+ sure that it comes out the way we hope
+ and can span 3 lines end."
+ [[x 1]])
+ (setv mL (MultiLine))
+ (assert (= mL.x 1))
+ (assert (in "begin" mL.__doc__))
+ (assert (in "end" mL.__doc__)))
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
index fa55e27..6dd2986 100644
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -348,6 +348,11 @@
(assoc vals "two" "three")
(assert (= (get vals "two") "three")))
+(defn test-multiassoc []
+ "NATIVE: test assoc multiple values"
+ (setv vals {"one" "two"})
+ (assoc vals "two" "three" "four" "five")
+ (assert (and (= (get vals "two") "three") (= (get vals "four") "five") (= (get vals "one") "two"))))
(defn test-pass []
"NATIVE: Test pass worksish"
@@ -406,6 +411,13 @@
(with [(open "README.md" "r")] (do)))
+(defn test-with-return []
+ "NATIVE: test that with returns stuff"
+ (defn read-file [filename]
+ (with [fd (open filename "r")] (.read fd)))
+ (assert (!= 0 (len (read-file "README.md")))))
+
+
(defn test-for-doodle []
"NATIVE: test for-do"
(do (do (do (do (do (do (do (do (do (setv (, x y) (, 0 0)))))))))))
diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy
index 2fb23d3..439ee03 100644
--- a/tests/native_tests/native_macros.hy
+++ b/tests/native_tests/native_macros.hy
@@ -46,6 +46,36 @@
"NATIVE: test macro calling a plain function"
(assert (= 3 (bar 1 2))))
+(defn test-midtree-yield []
+ "NATIVE: test yielding with a returnable"
+ (defn kruft [] (yield) (+ 1 1)))
+
+(defn test-midtree-yield-in-for []
+ "NATIVE: test yielding in a for with a return"
+ (defn kruft-in-for []
+ (for [i (range 5)]
+ (yield i))
+ (+ 1 2)))
+
+(defn test-midtree-yield-in-while []
+ "NATIVE: test yielding in a while with a return"
+ (defn kruft-in-while []
+ (setv i 0)
+ (while (< i 5)
+ (yield i)
+ (setv i (+ i 1)))
+ (+ 2 3)))
+
+(defn test-multi-yield []
+ "NATIVE: testing multiple yields"
+ (defn multi-yield []
+ (for [i (range 3)]
+ (yield i))
+ (yield "a")
+ (yield "end"))
+ (assert (= (list (multi-yield)) [0 1 2 "a" "end"])))
+
+
; Macro that checks a variable defined at compile or load time
(setv phase "load")
(eval-when-compile