diff --git a/docs/language/api.rst b/docs/language/api.rst index 28c13ba..9097910 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -305,7 +305,7 @@ Some example usage: Yeah, really! ;; assuming that (side-effect) is a function that we want to call for each - ;; and every value in the list, but which return values we do not care + ;; and every value in the list, but whose return value we do not care about => (list-comp (do (side-effect x) ... (if (< x 5) (* 2 x) ... (* 4 x))) @@ -416,7 +416,7 @@ Parameters may have following keywords in front of them: arguments may be specified after this one. The following code example defines a function that can be given 0 to n - numerical parameters. It then sums every odd number and substracts + numerical parameters. It then sums every odd number and subtracts every even number. .. code-block:: clj @@ -1293,23 +1293,48 @@ file is automatically closed after it has been processed. 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). +`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 a minimum +of two parameters, the function performing decoration and the function +being decorated. More than one decorator function can be applied, they +will be applied in order from outermost to innermost, ie. the first +decorator will be the outermost one & so on. Decorators with arguments +are called just like a function call. .. code-block:: clj - => (defn inc-decorator [func] + (with-decorator decorator-fun + (defn some-function [] ...) + + (with-decorator decorator1 decorator2 ... + (defn some-function [] ...) + + (with-decorator (decorator arg) .. + (defn some-function [] ...) + + + +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)))) + => (defn inc2-decorator [func] + ... (fn [value-1 value-2] (func (+ value-1 2) (+ value-2 2)))) + => (with-decorator inc-decorator (defn addition [a b] (+ a b))) => (addition 1 1) 4 + => (with-decorator inc2-decorator inc-decorator + ... (defn addition [a b] (+ a b))) + => (addition 1 1) + 8 .. _with-gensyms: diff --git a/hy/__init__.py b/hy/__init__.py index f3a26a1..120a9d9 100644 --- a/hy/__init__.py +++ b/hy/__init__.py @@ -23,7 +23,6 @@ from hy.version import __version__, __appname__ # NOQA from hy.models.expression import HyExpression # NOQA -from hy.models.lambdalist import HyLambdaListKeyword # NOQA from hy.models.integer import HyInteger # NOQA from hy.models.keyword import HyKeyword # NOQA from hy.models.complex import HyComplex # NOQA diff --git a/hy/cmdline.py b/hy/cmdline.py index a29d901..51417b2 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -47,7 +47,7 @@ from hy.models.expression import HyExpression from hy.models.string import HyString from hy.models.symbol import HySymbol -from hy._compat import builtins +from hy._compat import builtins, PY3 class HyQuitter(object): @@ -327,6 +327,7 @@ def hyc_main(): # entry point for cmd line script "hy2py" def hy2py_main(): + import platform module_name = "" options = dict(prog="hy2py", usage="%(prog)s [options] FILE", @@ -349,17 +350,42 @@ def hy2py_main(): if options.with_source: hst = import_file_to_hst(options.args[0]) - print(hst) + # need special printing on Windows in case the + # codepage doesn't support utf-8 characters + if PY3 and platform.system() == "Windows": + for h in hst: + try: + print(h) + except: + print(str(h).encode('utf-8')) + else: + print(hst) print() print() _ast = import_file_to_ast(options.args[0], module_name) if options.with_ast: - print(astor.dump(_ast)) + if PY3 and platform.system() == "Windows": + _print_for_windows(astor.dump(_ast)) + else: + print(astor.dump(_ast)) print() print() if not options.without_python: - print(astor.codegen.to_source(_ast)) + if PY3 and platform.system() == "Windows": + _print_for_windows(astor.codegen.to_source(_ast)) + else: + print(astor.codegen.to_source(_ast)) parser.exit(0) + + +# need special printing on Windows in case the +# codepage doesn't support utf-8 characters +def _print_for_windows(src): + for line in src.split("\n"): + try: + print(line) + except: + print(line.encode('utf-8')) diff --git a/hy/compiler.py b/hy/compiler.py index fa44676..a82d89e 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1,10 +1,10 @@ # -*- encoding: utf-8 -*- # -# Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013, 2014 Paul Tagliamonte # Copyright (c) 2013 Julien Danjou # Copyright (c) 2013 Nicolas Dandrimont # Copyright (c) 2013 James King -# Copyright (c) 2013 Bob Tolbert +# Copyright (c) 2013, 2014 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -24,7 +24,6 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from hy.models.lambdalist import HyLambdaListKeyword from hy.models.expression import HyExpression from hy.models.keyword import HyKeyword from hy.models.integer import HyInteger @@ -430,6 +429,7 @@ class HyASTCompiler(object): def _parse_lambda_list(self, exprs): """ Return FunctionDef parameter values from lambda list.""" + ll_keywords = ("&rest", "&optional", "&key", "&kwargs") ret = Result() args = [] defaults = [] @@ -439,10 +439,7 @@ class HyASTCompiler(object): for expr in exprs: - if isinstance(expr, HyLambdaListKeyword): - if expr not in expr._valid_types: - raise HyTypeError(expr, "{0} is not a valid " - "lambda-keyword.".format(repr(expr))) + if expr in ll_keywords: if expr == "&rest" and lambda_keyword is None: lambda_keyword = expr elif expr == "&optional": @@ -616,7 +613,7 @@ class HyASTCompiler(object): return imports, ret.replace(form), False - elif isinstance(form, (HySymbol, HyLambdaListKeyword)): + elif isinstance(form, HySymbol): return imports, HyExpression([HySymbol(name), HyString(form)]).replace(form), False @@ -707,7 +704,8 @@ class HyASTCompiler(object): lineno=expr.start_line, col_offset=expr.start_column) - returnable = Result(expr=expr_name, temp_variables=[expr_name, name]) + returnable = Result(expr=expr_name, temp_variables=[expr_name, name], + contains_yield=body.contains_yield) body += ast.Assign(targets=[name], value=body.force_expr, @@ -999,7 +997,10 @@ class HyASTCompiler(object): @checkargs(max=1) def compile_yield_expression(self, expr): expr.pop(0) - ret = Result(contains_yield=True) + if PY33: + ret = Result(contains_yield=False) + else: + ret = Result(contains_yield=True) value = None if expr != []: diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index 5a6b236..5000cbf 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -84,18 +84,20 @@ (defmacro ap-first [predfn lst] "Yield the first element that passes `predfn`" - `(let [[n (gensym)]] - (ap-each ~lst (when ~predfn (setv n it) (break))) - n)) + (with-gensyms [n] + `(let [[~n None]] + (ap-each ~lst (when ~predfn (setv ~n it) (break))) + ~n))) (defmacro ap-last [predfn lst] "Yield the last element that passes `predfn`" - `(let [[n (gensym)]] - (ap-each ~lst (none? n) - (when ~predfn - (setv n it))) - n)) + (with-gensyms [n] + `(let [[~n None]] + (ap-each ~lst (none? ~n) + (when ~predfn + (setv ~n it))) + ~n))) (defmacro ap-reduce [form lst &optional [initial-value None]] diff --git a/hy/core/__init__.py b/hy/core/__init__.py index d5349f3..be885ce 100644 --- a/hy/core/__init__.py +++ b/hy/core/__init__.py @@ -1,3 +1,4 @@ STDLIB = [ - "hy.core.language" + "hy.core.language", + "hy.core.shadow" ] diff --git a/hy/core/language.hy b/hy/core/language.hy index 262a45a..fe921b7 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -92,7 +92,8 @@ (setv map itertools.imap) (setv zip itertools.izip) (setv range xrange) - (setv input raw_input)) + (setv input raw_input) + (setv reduce reduce)) (do (setv reduce functools.reduce) (setv filterfalse itertools.filterfalse) @@ -331,10 +332,12 @@ (_numeric_check n) (= n 0)) -(def *exports* '[butlast calling-module-name coll? cons cons? cycle dec distinct - disassemble drop drop-while empty? even? every? first filter - flatten float? gensym identity inc instance? integer - integer? integer-char? iterable? iterate iterator? keyword? - list* macroexpand macroexpand-1 map neg? nil? none? nth - numeric? odd? pos? range remove repeat repeatedly rest second - some string string? take take-nth take-while zero? zip zipwith]) +(def *exports* '[butlast calling-module-name coll? cons cons? cycle + dec distinct disassemble drop drop-while empty? even? + every? first filter flatten float? gensym identity + inc instance? integer integer? integer-char? + iterable? iterate iterator? keyword? list* + macroexpand macroexpand-1 map neg? nil? none? nth + numeric? odd? pos? range remove repeat repeatedly + rest reduce second some string string? take take-nth + take-while zero? zip zipwith]) diff --git a/hy/core/shadow.hy b/hy/core/shadow.hy new file mode 100644 index 0000000..f59f0ca --- /dev/null +++ b/hy/core/shadow.hy @@ -0,0 +1,61 @@ +;; Copyright (c) 2014 Paul Tagliamonte +;; Copyright (c) 2014 James King + +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: + +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. + +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +;;;; Hy shadow functions + +(import operator) + + +(defn + [&rest args] + "Shadow + operator for when we need to import / map it against something" + (if (= (len args) 0) + 0 + (sum args))) ; shortcut here. + + +(defn - [&rest args] + "Shadow - operator for when we need to import / map it against something" + (let [[count (len args)]] + (if (= count 0) + (raise (TypeError "Need at least 1 argument to subtract")) + (if (= count 1) + (- (get args 0)) + (reduce operator.sub args))))) + + +(defn * [&rest args] + "Shadow * operator for when we need to import / map it against something" + (if (= (len args) 0) + 1 ; identity + (reduce operator.mul args))) + + +(defn / [&rest args] + "Shadow / operator for when we need to import / map it against something" + (let [[count (len args)]] + (if (= count 0) + (raise (TypeError "Need at least 1 argument to divide")) + (if (= count 1) + (operator.truediv 1 (get args 0)) + (reduce operator.truediv args))))) + + +(setv *exports* ['+ '- '* '/]) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 0f3a96a..30793f2 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -30,7 +30,6 @@ from hy.models.expression import HyExpression from hy.models.float import HyFloat from hy.models.integer import HyInteger from hy.models.keyword import HyKeyword -from hy.models.lambdalist import HyLambdaListKeyword from hy.models.list import HyList from hy.models.string import HyString from hy.models.symbol import HySymbol @@ -271,9 +270,6 @@ def t_identifier(p): if obj.startswith(":"): return HyKeyword(obj) - if obj.startswith("&"): - return HyLambdaListKeyword(obj) - def mangle(p): if p.startswith("*") and p.endswith("*") and p not in ("*", "**"): p = p[1:-1].upper() diff --git a/hy/models/lambdalist.py b/hy/models/lambdalist.py deleted file mode 100644 index 1a38066..0000000 --- a/hy/models/lambdalist.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2013 James King -# -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - -from hy.models.string import HyString - - -class HyLambdaListKeyword(HyString): - """ - Hy LambdaListKeyword. Demarcates arguments in an argument list. - - (defun my-fun (x &rest xs &optional (foo "default string"))) - - becomes: - - def my_fun(x, *xs, foo="default string"): - pass - """ - - _valid_types = ["&rest", "&optional", "&key", "&kwargs"] - - def __init__(self, string): - self += string diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index 7ab30e5..ebf0404 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -21,7 +21,6 @@ from hy.models.expression import HyExpression from hy.models.integer import HyInteger -from hy.models.lambdalist import HyLambdaListKeyword from hy.models.float import HyFloat from hy.models.complex import HyComplex from hy.models.symbol import HySymbol @@ -85,14 +84,6 @@ def test_lex_expression_integer(): assert objs == [HyExpression([HySymbol("foo"), HyInteger(2)])] -def test_lex_lambda_list_keyword(): - """ Make sure expressions can produce lambda list keywords """ - objs = tokenize("(x &rest xs)") - assert objs == [HyExpression([HySymbol("x"), - HyLambdaListKeyword("&rest"), - HySymbol("xs")])] - - def test_lex_symbols(): """ Make sure that symbols are valid expressions""" objs = tokenize("foo ") diff --git a/tests/native_tests/contrib/anaphoric.hy b/tests/native_tests/contrib/anaphoric.hy index 3d67e82..50281fa 100644 --- a/tests/native_tests/contrib/anaphoric.hy +++ b/tests/native_tests/contrib/anaphoric.hy @@ -87,12 +87,14 @@ (defn test-ap-first [] "NATIVE: testing anaphoric first" (assert-equal (ap-first (> it 5) (range 10)) 6) - (assert-equal (ap-first (even? it) [1 2 3 4]) 2)) + (assert-equal (ap-first (even? it) [1 2 3 4]) 2) + (assert-equal (ap-first (> it 10) (range 10)) None)) (defn test-ap-last [] "NATIVE: testing anaphoric last" (assert-equal (ap-last (> it 5) (range 10)) 9) - (assert-equal (ap-last (even? it) [1 2 3 4]) 4)) + (assert-equal (ap-last (even? it) [1 2 3 4]) 4) + (assert-equal (ap-last (> it 10) (range 10)) None)) (defn test-ap-reduce [] "NATIVE: testing anaphoric reduce" diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 8bff65c..7db5320 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -3,6 +3,7 @@ [sys :as systest]) (import sys) +(import [hy._compat [PY33 PY34]]) (defn test-sys-argv [] "NATIVE: test sys.argv" @@ -448,6 +449,29 @@ (for [y (gen)] (setv ret (+ ret y))) (assert (= ret 10))) +(defn test-yield-with-return [] + "NATIVE: test yield with return" + (defn gen [] (yield 3) "goodbye") + (if PY33 + (do (setv gg (gen)) + (assert (= 3 (next gg))) + (try (next gg) + (except [e StopIteration] (assert (hasattr e "value")) + (assert (= (getattr e "value") "goodbye"))))) + (do (setv gg (gen)) + (assert (= 3 (next gg))) + (try (next gg) + (except [e StopIteration] (assert (not (hasattr e "value")))))))) + + +(defn test-yield-in-try [] + "NATIVE: test yield in try" + (defn gen [] + (let [[x 1]] + (try (yield x) + (finally (print x))))) + (setv output (list (gen))) + (assert (= [1] output))) (defn test-first [] "NATIVE: test firsty things" @@ -975,3 +999,10 @@ "NATIVE: test keyword quoting magic" (assert (= :foo "\ufdd0:foo")) (assert (= `:foo "\ufdd0:foo"))) + +(defn test-only-parse-lambda-list-in-defn [] + "NATIVE: test lambda lists are only parsed in defn" + (try + (foo [&rest spam] 1) + (catch [NameError] True) + (else (raise AssertionError)))) diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy index 8900578..c1c0859 100644 --- a/tests/native_tests/quote.hy +++ b/tests/native_tests/quote.hy @@ -77,12 +77,6 @@ (assert (= q qq))) -(defn test-quote-lambdalistkeyword [] - "NATIVE: test quoting lambda list keywords" - (setv opt (quote &optional)) - (assert (isinstance opt hy.HyLambdaListKeyword)) - (assert (= (str opt) "&optional"))) - (defmacro doodle [&rest body] `(do ~@body)) diff --git a/tests/native_tests/shadow.hy b/tests/native_tests/shadow.hy new file mode 100644 index 0000000..362ac50 --- /dev/null +++ b/tests/native_tests/shadow.hy @@ -0,0 +1,41 @@ + + +(defn test-shadow-addition [] + "NATIVE: test shadow addition" + (let [[x +]] + (assert (= (x) 0)) + (assert (= (x 1 2 3 4) 10)) + (assert (= (x 1 2 3 4 5) 15)))) + + +(defn test-shadow-subtraction [] + "NATIVE: test shadow subtraction" + (let [[x -]] + (assert (try + (x) + (catch [TypeError] True) + (else (throw AssertionError)))) + (assert (= (x 1) -1)) + (assert (= (x 2 1) 1)) + (assert (= (x 2 1 1) 0)))) + + +(defn test-shadow-multiplication [] + "NATIVE: test shadow multiplication" + (let [[x *]] + (assert (= (x) 1)) + (assert (= (x 3) 3)) + (assert (= (x 3 3) 9)))) + + +(defn test-shadow-division [] + "NATIVE: test shadow division" + (let [[x /]] + (assert (try + (x) + (catch [TypeError] True) + (else (throw AssertionError)))) + (assert (= (x 1) 1)) + (assert (= (x 8 2) 4)) + (assert (= (x 8 2 2) 2)) + (assert (= (x 8 2 2 2) 1))))