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