From 55a7ab1667583d95a514e33a5df62c376078c2f5 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Fri, 27 Dec 2013 13:50:19 -0700 Subject: [PATCH 01/24] add nil as synonym for None --- hy/lex/parser.py | 1 + tests/native_tests/language.hy | 7 ++++--- tests/native_tests/unless.hy | 2 ++ tests/native_tests/when.hy | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 70d43f8..9e11069 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -229,6 +229,7 @@ def t_identifier(p): table = { "true": "True", "false": "False", + "nil": "None", "null": "None", } diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4efe768..ec110ae 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -86,9 +86,10 @@ (defn test-is [] "NATIVE: test is can deal with None" - (setv a null) - (assert (is a null)) - (assert (is-not a "b"))) + (setv a nil) + (assert (is a nil)) + (assert (is-not a "b")) + (assert (none? a))) (defn test-branching [] diff --git a/tests/native_tests/unless.hy b/tests/native_tests/unless.hy index f8c298a..2b957fe 100644 --- a/tests/native_tests/unless.hy +++ b/tests/native_tests/unless.hy @@ -4,5 +4,7 @@ (assert (= (unless false 1 2) 2)) (assert (= (unless false 1 3) 3)) (assert (= (unless true 2) null)) + (assert (= (unless true 2) nil)) (assert (= (unless (!= 1 2) 42) null)) + (assert (= (unless (!= 1 2) 42) nil)) (assert (= (unless (!= 2 2) 42) 42))) diff --git a/tests/native_tests/when.hy b/tests/native_tests/when.hy index 1409b3b..c281439 100644 --- a/tests/native_tests/when.hy +++ b/tests/native_tests/when.hy @@ -5,4 +5,6 @@ (assert (= (when true 1 3) 3)) (assert (= (when false 2) null)) (assert (= (when (= 1 2) 42) null)) + (assert (= (when false 2) nil)) + (assert (= (when (= 1 2) 42) nil)) (assert (= (when (= 2 2) 42) 42))) From d82636958b010d5cdf34af22576f04ecb4ffd098 Mon Sep 17 00:00:00 2001 From: Foxboron Date: Sun, 10 Nov 2013 19:00:01 +0100 Subject: [PATCH 02/24] added for and with macros Fixed up anaphoric.hy and added the tests too native_tests/__init__.py added __init__.hy --- docs/language/api.rst | 52 +++++++++++------------------ hy/compiler.py | 33 +++++++++++++----- hy/contrib/anaphoric.hy | 10 +++--- hy/core/bootstrap.hy | 4 +-- hy/core/language.hy | 28 ++++++++-------- hy/core/macros.hy | 33 ++++++++++-------- tests/__init__.py | 1 + tests/compilers/test_ast.py | 18 +++++----- tests/native_tests/language.hy | 36 ++++++++++---------- tests/native_tests/native_macros.hy | 6 ++-- tests/native_tests/with_test.hy | 44 ++++++++++++++++++++++++ 11 files changed, 160 insertions(+), 105 deletions(-) create mode 100644 tests/native_tests/with_test.hy diff --git a/docs/language/api.rst b/docs/language/api.rst index 9934b4d..60f4d89 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -441,52 +441,36 @@ first / car 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. +`for` 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)) + (for [[element collection]] (side-effect element)) - ;; foreach can have an optional else block - (foreach [element collection] (side-effect element) - (else (side-effect-2))) + ;; for can have an optional else block + (for [[element collection]] (side-effect element) + (else (side-effect-2))) -The optional `else` block is executed only if the `foreach` loop terminates +The optional `else` block is executed only if the `for` 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)) + => (for [[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)) + => (for [[element [1 2 3]]] (if (< element 4) + ... (print element) + ... (break)) ... (else (print "loop finished"))) 1 2 @@ -635,7 +619,7 @@ function is defined and passed to another function for filtering output. ... {:name "Dave" :age 5}]) => (defn display-people [people filter] - ... (foreach [person people] (if (filter person) (print (:name person))))) + ... (for [[person people]] (if (filter person) (print (:name person))))) => (display-people people (fn [person] (< (:age person) 25))) Alice @@ -949,16 +933,18 @@ context to an argument or ignore it completely, as shown below: .. code-block:: clj - (with [arg (expr)] block) + (with [[arg (expr)]] block) - (with [(expr)] block) + (with [[(expr)]] block) + + (with [[arg (expr)] [(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 [[f (open "NEWS")]] (print (.read f))) with-decorator @@ -996,7 +982,7 @@ infinite series without consuming infinite amount of memory. .. code-block:: clj => (defn multiply [bases coefficients] - ... (foreach [(, base coefficient) (zip bases coefficients)] + ... (for [[(, base coefficient) (zip bases coefficients)]] ... (yield (* base coefficient)))) => (multiply (range 5) (range 5)) diff --git a/hy/compiler.py b/hy/compiler.py index 9df4fb9..9a3b710 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1177,14 +1177,18 @@ class HyASTCompiler(object): fn.stmts[-1].decorator_list = decorators return ret + fn - @builds("with") + @builds("with*") @checkargs(min=2) def compile_with_expression(self, expr): - expr.pop(0) # with + expr.pop(0) # with* args = expr.pop(0) - if len(args) > 2 or len(args) < 1: - raise HyTypeError(expr, "with needs [arg (expr)] or [(expr)]") + if not isinstance(args, HyList): + raise HyTypeError(expr, + "with expects a list, received `{0}'".format( + type(args).__name__)) + if len(args) < 1: + raise HyTypeError(expr, "with needs [[arg (expr)]] or [[(expr)]]]") args.reverse() ctx = self.compile(args.pop(0)) @@ -1623,23 +1627,36 @@ class HyASTCompiler(object): result += ld_name return result - @builds("foreach") + @builds("for*") @checkargs(min=1) def compile_for_expression(self, expression): expression.pop(0) # for - target_name, iterable = expression.pop(0) + + args = expression.pop(0) + + if not isinstance(args, HyList): + raise HyTypeError(expression, + "for expects a list, received `{0}'".format( + type(args).__name__)) + + try: + target_name, iterable = args + except ValueError: + raise HyTypeError(expression, + "for requires two forms in the list") + target = self._storeize(self.compile(target_name)) ret = Result() orel = Result() - # (foreach [] body (else …)) + # (for* [] body (else …)) if expression and expression[-1][0] == HySymbol("else"): else_expr = expression.pop() if len(else_expr) > 2: raise HyTypeError( else_expr, - "`else' statement in `foreach' is too long") + "`else' statement in `for' is too long") elif len(else_expr) == 2: orel += self.compile(else_expr[1]) orel += orel.expr_as_stmt() diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index c451559..9a281d0 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -31,14 +31,14 @@ (defmacro ap-each [lst &rest body] "Evaluate the body form for each element in the list." - `(foreach [it ~lst] ~@body)) + `(for [[it ~lst]] ~@body)) (defmacro ap-each-while [lst form &rest body] "Evalutate the body form for each element in the list while the predicate form evaluates to True." `(let [[p (lambda [it] ~form)]] - (foreach [it ~lst] + (for [[it ~lst]] (if (p it) ~@body (break))))) @@ -47,7 +47,7 @@ (defmacro ap-map [form lst] "Yield elements evaluated in the form for each element in the list." `(let [[f (lambda [it] ~form)]] - (foreach [v ~lst] + (for [[v ~lst]] (yield (f v))))) @@ -55,7 +55,7 @@ "Yield elements evaluated for each element in the list when the predicate function returns True." `(let [[f (lambda [it] ~rep)]] - (foreach [it ~lst] + (for [[it ~lst]] (if (~predfn it) (yield (f it)) (yield it))))) @@ -64,7 +64,7 @@ (defmacro ap-filter [form lst] "Yield elements returned when the predicate form evaluates to True." `(let [[pred (lambda [it] ~form)]] - (foreach [val ~lst] + (for [[val ~lst]] (if (pred val) (yield val))))) diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy index 007d92b..5d3d367 100644 --- a/hy/core/bootstrap.hy +++ b/hy/core/bootstrap.hy @@ -34,7 +34,7 @@ (defmacro defmacro-alias [names lambda-list &rest body] "define one macro with several names" (setv ret `(do)) - (foreach [name names] + (for* [name names] (.append ret `(defmacro ~name ~lambda-list ~@body))) ret) @@ -52,7 +52,7 @@ (setv macroed_variables []) (if (not (isinstance variables HyList)) (macro-error variables "let lexical context must be a list")) - (foreach [variable variables] + (for* [variable variables] (if (isinstance variable HyList) (do (if (!= (len variable) 2) diff --git a/hy/core/language.hy b/hy/core/language.hy index d03db93..9ac2fee 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -33,11 +33,11 @@ (defn cycle [coll] "Yield an infinite repetition of the items in coll" (setv seen []) - (foreach [x coll] + (for* [x coll] (yield x) (.append seen x)) (while seen - (foreach [x seen] + (for* [x seen] (yield x)))) (defn dec [n] @@ -49,7 +49,7 @@ "Return a generator from the original collection with duplicates removed" (let [[seen []] [citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (not_in val seen) (do (yield val) @@ -58,7 +58,7 @@ (defn drop [count coll] "Drop `count` elements from `coll` and yield back the rest" (let [[citer (iter coll)]] - (try (foreach [i (range count)] + (try (for* [i (range count)] (next citer)) (catch [StopIteration])) citer)) @@ -66,10 +66,10 @@ (defn drop-while [pred coll] "Drop all elements of `coll` until `pred` is False" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (not (pred val)) (do (yield val) (break)))) - (foreach [val citer] + (for* [val citer] (yield val)))) (defn empty? [coll] @@ -84,7 +84,7 @@ (defn filter [pred coll] "Return all elements from `coll` that pass `pred`" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (pred val) (yield val))))) @@ -96,7 +96,7 @@ (defn _flatten [coll result] (if (and (iterable? coll) (not (string? coll))) - (do (foreach [b coll] + (do (for* [b coll] (_flatten b result))) (.append result coll)) result) @@ -187,7 +187,7 @@ (defn remove [pred coll] "Return coll with elements removed that pass `pred`" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (not (pred val)) (yield val))))) @@ -195,7 +195,7 @@ "Yield x forever or optionally n times" (if (none? n) (setv dispatch (fn [] (while true (yield x)))) - (setv dispatch (fn [] (foreach [_ (range n)] (yield x))))) + (setv dispatch (fn [] (for* [_ (range n)] (yield x))))) (dispatch)) (defn repeatedly [func] @@ -223,7 +223,7 @@ "Take `count` elements from `coll`, or the whole set if the total number of entries in `coll` is less than `count`." (let [[citer (iter coll)]] - (foreach [_ (range count)] + (for* [_ (range count)] (yield (next citer))))) (defn take-nth [n coll] @@ -231,16 +231,16 @@ raises ValueError for (not (pos? n))" (if (pos? n) (let [[citer (iter coll)] [skip (dec n)]] - (foreach [val citer] + (for* [val citer] (yield val) - (foreach [_ (range skip)] + (for* [_ (range skip)] (next citer)))) (raise (ValueError "n must be positive")))) (defn take-while [pred coll] "Take all elements while `pred` is true" (let [[citer (iter coll)]] - (foreach [val citer] + (for* [val citer] (if (pred val) (yield val) (break))))) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index d7c8b15..39338c1 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -26,17 +26,24 @@ ;;; They are automatically required in every module, except inside hy.core (defmacro for [args &rest body] - "shorthand for nested foreach loops: - (for [x foo y bar] baz) -> - (foreach [x foo] - (foreach [y bar] + "shorthand for nested for loops: + (for [[x foo] [y bar]] baz) -> + (for* [x foo] + (for* [y bar] baz))" - ;; TODO: that signature sucks. - ;; (for [[x foo] [y bar]] baz) would be more consistent - (if (% (len args) 2) - (macro-error args "for needs an even number of elements in its first argument")) (if args - `(foreach [~(.pop args 0) ~(.pop args 0)] (for ~args ~@body)) + `(for* ~(.pop args 0) (for ~args ~@body)) + `(do ~@body))) + + +(defmacro with [args &rest body] + "shorthand for nested for* loops: + (with [[x foo] [y bar]] baz) -> + (with* [x foo] + (with* [y bar] + baz))" + (if args + `(with* ~(.pop args 0) (with ~args ~@body)) `(do ~@body))) @@ -71,7 +78,7 @@ (setv root (check-branch branch)) (setv latest-branch root) - (foreach [branch branches] + (for* [branch branches] (setv cur-branch (check-branch branch)) (.append latest-branch cur-branch) (setv latest-branch cur-branch)) @@ -81,7 +88,7 @@ (defmacro -> [head &rest rest] ;; TODO: fix the docstring by someone who understands this (setv ret head) - (foreach [node rest] + (for* [node rest] (if (not (isinstance node HyExpression)) (setv node `(~node))) (.insert node 1 ret) @@ -92,7 +99,7 @@ (defmacro ->> [head &rest rest] ;; TODO: fix the docstring by someone who understands this (setv ret head) - (foreach [node rest] + (for* [node rest] (if (not (isinstance node HyExpression)) (setv node `(~node))) (.append node ret) @@ -113,7 +120,7 @@ (defmacro yield-from [iterable] "Yield all the items from iterable" (let [[x (gensym)]] - `(foreach [~x ~iterable] + `(for* [~x ~iterable] (yield ~x)))) (defmacro with-gensyms [args &rest body] diff --git a/tests/__init__.py b/tests/__init__.py index bbd24e2..5b9f090 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,4 +12,5 @@ from .native_tests.when import * # noqa from .native_tests.with_decorator import * # noqa from .native_tests.core import * # noqa from .native_tests.reader_macros import * # noqa +from .native_tests.with_test import * # noqa from .native_tests.contrib.anaphoric import * # noqa diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index a362d66..ebcfabd 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -295,9 +295,9 @@ def test_ast_bad_assoc(): def test_ast_bad_with(): "Make sure AST can't compile invalid with" - cant_compile("(with)") - cant_compile("(with [])") - cant_compile("(with [] (pass))") + cant_compile("(with*)") + cant_compile("(with* [])") + cant_compile("(with* [] (pass))") def test_ast_valid_while(): @@ -305,14 +305,14 @@ def test_ast_valid_while(): can_compile("(while foo bar)") -def test_ast_valid_foreach(): - "Make sure AST can compile valid foreach" - can_compile("(foreach [a 2])") +def test_ast_valid_for(): + "Make sure AST can compile valid for" + can_compile("(for [[a 2]])") -def test_ast_invalid_foreach(): - "Make sure AST can't compile invalid foreach" - cant_compile("(foreach [a 1] (else 1 2))") +def test_ast_invalid_for(): + "Make sure AST can't compile invalid for" + cant_compile("(for* [a 1] (else 1 2))") def test_ast_expression_basics(): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4efe768..25ecc33 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -31,12 +31,12 @@ (defn test-for-loop [] "NATIVE: test for loops?" (setv count 0) - (for [x [1 2 3 4 5]] + (for [[x [1 2 3 4 5]]] (setv count (+ count x))) (assert (= count 15)) (setv count 0) - (for [x [1 2 3 4 5] - y [1 2 3 4 5]] + (for [[x [1 2 3 4 5]] + [y [1 2 3 4 5]]] (setv count (+ count x y))) (assert (= count 150))) @@ -394,9 +394,9 @@ (defn test-yield [] "NATIVE: test yielding" - (defn gen [] (for [x [1 2 3 4]] (yield x))) + (defn gen [] (for [[x [1 2 3 4]]] (yield x))) (setv ret 0) - (for [y (gen)] (setv ret (+ ret y))) + (for [[y (gen)]] (setv ret (+ ret y))) (assert (= ret 10))) @@ -439,37 +439,37 @@ (defn test-context [] "NATIVE: test with" - (with [fd (open "README.md" "r")] (assert fd)) - (with [(open "README.md" "r")] (do))) + (with [[fd (open "README.md" "r")]] (assert fd)) + (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))) + (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))))))))))) - (foreach [- [1 2]] + (for [[- [1 2]]] (do (setv x (+ x 1)) (setv y (+ y 1)))) (assert (= y x 2))) -(defn test-foreach-else [] - "NATIVE: test foreach else" +(defn test-for-else [] + "NATIVE: test for else" (let [[x 0]] - (foreach [a [1 2]] + (for* [a [1 2]] (setv x (+ x a)) (else (setv x (+ x 50)))) (assert (= x 53))) (let [[x 0]] - (foreach [a [1 2]] + (for* [a [1 2]] (setv x (+ x a)) (else)) (assert (= x 3)))) @@ -636,7 +636,7 @@ (defn test-nested-if [] "NATIVE: test nested if" - (for [x (range 10)] + (for [[x (range 10)]] (if (in "foo" "foobar") (do (if true true true)) @@ -800,16 +800,16 @@ (defn test-break-breaking [] "NATIVE: test checking if break actually breaks" - (defn holy-grail [] (for [x (range 10)] (if (= x 5) (break))) x) + (defn holy-grail [] (for [[x (range 10)]] (if (= x 5) (break))) x) (assert (= (holy-grail) 5))) (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [x (range 10)] - (if (!= x 5) - (continue)) + (for [[x (range 10)]] + (if (!= x 5) + (continue)) (.append y x)) (assert (= y [5]))) diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy index 911a9c9..0841b1e 100644 --- a/tests/native_tests/native_macros.hy +++ b/tests/native_tests/native_macros.hy @@ -56,7 +56,7 @@ (defn test-midtree-yield-in-for [] "NATIVE: test yielding in a for with a return" (defn kruft-in-for [] - (for [i (range 5)] + (for* [i (range 5)] (yield i)) (+ 1 2))) @@ -72,7 +72,7 @@ (defn test-multi-yield [] "NATIVE: testing multiple yields" (defn multi-yield [] - (for [i (range 3)] + (for* [i (range 3)] (yield i)) (yield "a") (yield "end")) @@ -97,7 +97,7 @@ (defn test-yield-from [] "NATIVE: testing yield from" (defn yield-from-test [] - (for [i (range 3)] + (for* [i (range 3)] (yield i)) (yield-from [1 2 3])) (assert (= (list (yield-from-test)) [0 1 2 1 2 3]))) diff --git a/tests/native_tests/with_test.hy b/tests/native_tests/with_test.hy new file mode 100644 index 0000000..b094f35 --- /dev/null +++ b/tests/native_tests/with_test.hy @@ -0,0 +1,44 @@ +(defclass WithTest [object] + [(--init-- + (fn [self val] + (setv self.val val) + None)) + + (--enter-- + (fn [self] + self.val)) + + (--exit-- + (fn [self type value traceback] + (setv self.val None)))]) + +(defn test-single-with [] + "NATIVE: test a single with" + (with [[t (WithTest 1)]] + (assert (= t 1)))) + +(defn test-two-with [] + "NATIVE: test two withs" + (with [[t1 (WithTest 1)] + [t2 (WithTest 2)]] + (assert (= t1 1)) + (assert (= t2 2)))) + +(defn test-thrice-with [] + "NATIVE: test three withs" + (with [[t1 (WithTest 1)] + [t2 (WithTest 2)] + [t3 (WithTest 3)]] + (assert (= t1 1)) + (assert (= t2 2)) + (assert (= t3 3)))) + +(defn test-quince-with [] + "NATIVE: test four withs, one with no args" + (with [[t1 (WithTest 1)] + [t2 (WithTest 2)] + [t3 (WithTest 3)] + [(WithTest 4)]] + (assert (= t1 1)) + (assert (= t2 2)) + (assert (= t3 3)))) From 62522a5f8693dfb13f589f520739b1281776e379 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 30 Dec 2013 12:04:40 +0100 Subject: [PATCH 03/24] Allow get with multiple arguments When calling get with more than two arguments, treat the rest as indexes into the expression from the former. That is, (get foo "bar" "baz") would translate to foo["bar"]["baz"], and so on. This fixes #362. Requested-by: Sean B. Palmer Signed-off-by: Gergely Nagy --- hy/compiler.py | 25 ++++++++++++++++--------- tests/compilers/test_ast.py | 1 - tests/native_tests/language.hy | 11 ++++++++++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 9df4fb9..230cdda 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1086,18 +1086,25 @@ class HyASTCompiler(object): return rimports @builds("get") - @checkargs(2) + @checkargs(min=2) def compile_index_expression(self, expr): expr.pop(0) # index - val = self.compile(expr.pop(0)) # target - sli = self.compile(expr.pop(0)) # slice - return val + sli + ast.Subscript( - lineno=expr.start_line, - col_offset=expr.start_column, - value=val.force_expr, - slice=ast.Index(value=sli.force_expr), - ctx=ast.Load()) + val = self.compile(expr.pop(0)) + slices, ret = self._compile_collect(expr) + + if val.stmts: + ret += val + + for sli in slices: + val = Result() + ast.Subscript( + lineno=expr.start_line, + col_offset=expr.start_column, + value=val.force_expr, + slice=ast.Index(value=sli), + ctx=ast.Load()) + + return ret + val @builds("del") @checkargs(min=1) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index a362d66..98ac7ac 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -253,7 +253,6 @@ def test_ast_bad_get(): "Make sure AST can't compile invalid get" cant_compile("(get)") cant_compile("(get 1)") - cant_compile("(get 1 2 3)") def test_ast_good_slice(): diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 4efe768..65cca5c 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -129,7 +129,16 @@ (defn test-index [] "NATIVE: Test that dict access works" (assert (= (get {"one" "two"} "one") "two")) - (assert (= (get [1 2 3 4 5] 1) 2))) + (assert (= (get [1 2 3 4 5] 1) 2)) + (assert (= (get {"first" {"second" {"third" "level"}}} + "first" "second" "third") + "level")) + (assert (= (get ((fn [] {"first" {"second" {"third" "level"}}})) + "first" "second" "third") + "level")) + (assert (= (get {"first" {"second" {"third" "level"}}} + ((fn [] "first")) "second" "third") + "level"))) (defn test-lambda [] From 4e3b6fd4cf6d36302bbc7f80c11ca3aa5ac5961b Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Mon, 30 Dec 2013 14:42:55 -0700 Subject: [PATCH 04/24] Add some docs for gensym and siblings --- docs/language/api.rst | 71 +++++++++++++++++++++++++++ docs/language/internals.rst | 97 ++++++++++++++++++++++++++++++++++--- 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/docs/language/api.rst b/docs/language/api.rst index 9934b4d..12a7e06 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -357,6 +357,8 @@ Parameters may have following keywords in front of them: => (zig-zag-sum 1 2 3 4 5 6) -3 +.. _defmacro: + defmacro -------- @@ -378,6 +380,24 @@ between the operands. => (infix (1 + 1)) 2 +.. _defmacro/g!: + +defmacro/g! +------------ + +.. versionadded:: 0.9.12 + +`defmacro/g!` is a special version of `defmacro` that is used to +automatically generate :ref:`gensym` for any symbol that +starts with ``g!``. + +So ``g!a`` would become ``(gensym "a")``. + +.. seealso:: + + Section :ref:`using-gensym` + + del --- @@ -494,6 +514,28 @@ normally. If the execution is halted with `break`, the `else` does not execute. loop finished +.. _gensym: + +gensym +------ + +.. versionadded:: 0.9.12 + +`gensym` form is used to generate a unique symbol to allow writing macros +without accidental variable name clashes. + +.. code-block:: clj + + => (gensym) + u':G_1235' + + => (gensym "x") + u':x_1236' + +.. seealso:: + + Section :ref:`using-gensym` + get --- @@ -983,6 +1025,35 @@ values that are incremented by 1. When decorated `addition` is called with value 4 +.. _with-gensyms: + +with-gensyms +------------- + +.. versionadded:: 0.9.12 + +`with-gensym` form is used to generate a set of :ref:`gensym` for use +in a macro. + +.. code-block:: clojure + + (with-gensyms [a b c] + ...) + +expands to: + +.. code-block:: clojure + + (let [[a (gensym) + [b (gensym) + [c (gensym)]] + ...) + +.. seealso:: + + Section :ref:`using-gensym` + + yield ----- diff --git a/docs/language/internals.rst b/docs/language/internals.rst index ccf8263..cf4f99c 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -2,26 +2,111 @@ Internal Hy Documentation ========================= -.. info:: - These bits are for folks who hack on Hy it's self, mostly! +.. note:: + These bits are for folks who hack on Hy itself, mostly! Hy Models ========= -.. TODO:: +.. todo:: Write this. Hy Macros ========= -.. TODO:: - Write this. +.. _using-gensym: + +Using gensym for safer macros +------------------------------ + +When writing macros, one must be careful to avoid capturing external variables +or using variable names that might conflict with user code. + +We will use an example macro ``nif`` (see http://letoverlambda.com/index.cl/guest/chap3.html#sec_5 +for a more complete description.) ``nif`` is an example, something like a numeric ``if``, +where based on the expression, one of the 3 forms is called depending on if the +expression is positive, zero or negative. + +A first pass might be someting like: + +.. code-block:: clojure + + (defmacro nif [expr pos-form zero-form neg-form] + `(let [[obscure-name ~expr]] + (cond [(pos? obscure-name) ~pos-form] + [(zero? obscure-name) ~zero-form] + [(neg? obscure-name) ~neg-form]))) + +where ``obsure-name`` is an attempt to pick some variable name as not to +conflict with other code. But of course, while well-intentioned, +this is no guarantee. + +The method :ref:`gensym` is designed to generate a new, unique symbol for just +such an occasion. A much better version of ``nif`` would be: + +.. code-block:: clojure + + (defmacro nif [expr pos-form zero-form neg-form] + (let [[g (gensym)]] + `(let [[~g ~expr]] + (cond [(pos? ~g) ~pos-form] + [(zero? ~g) ~zero-form] + [(neg? ~g) ~neg-form])))) + +This is an easy case, since there is only one symbol. But if there is +a need for several gensym's there is a second macro :ref:`with-gensyms` that +basically expands to a series of ``let`` statements: + +.. code-block:: clojure + + (with-gensyms [a b c] + ...) + +expands to: + +.. code-block:: clojure + + (let [[a (gensym) + [b (gensym) + [c (gensym)]] + ...) + +so our re-written ``nif`` would look like: + +.. code-block:: clojure + + (defmacro nif [expr pos-form zero-form neg-form] + (with-gensyms [g] + `(let [[~g ~expr]] + (cond [(pos? ~g) ~pos-form] + [(zero? ~g) ~zero-form] + [(neg? ~g) ~neg-form])))) + +Finally, though we can make a new macro that does all this for us. :ref:`defmacro/g!` +will take all symbols that begin with ``g!`` and automatically call ``gensym`` with the +remainder of the symbol. So ``g!a`` would become ``(gensym "a")``. + +Our final version of ``nif``, built with ``defmacro/g!`` becomes: + +.. code-block:: clojure + + (defmacro/g! nif [expr pos-form zero-form neg-form] + `(let [[~g!res ~expr]] + (cond [(pos? ~g!res) ~pos-form] + [(zero? ~g!res) ~zero-form] + [(neg? ~g!res) ~neg-form])))) + + + +Checking macro arguments and raising exceptions +----------------------------------------------- + Hy Compiler Builtins ==================== -.. TODO:: +.. todo:: Write this. From d7956d03c35d23407172a69663576b5d95a4f892 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Mon, 30 Dec 2013 15:09:17 -0700 Subject: [PATCH 05/24] Adding documentation for flatten --- docs/language/core.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/language/core.rst b/docs/language/core.rst index acdc025..9b24747 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -575,6 +575,26 @@ See also :ref:`remove-fn`. => (list (filter even? [1 2 3 -4 5 -7])) [2, -4] +.. _flatten-fn: + +flatten +------- + +.. versionadded:: 0.9.12 + +Usage: ``(flatten coll)`` + +Return a single list of all the items in ``coll``, by flattening all +contained lists and/or tuples. + +.. code-block:: clojure + + => (flatten [1 2 [3 4] 5]) + [1, 2, 3, 4, 5] + + => (flatten ["foo" (, 1 2) [1 [2 3] 4] "bar"]) + ['foo', 1, 2, 1, 2, 3, 4, 'bar'] + .. _iterate-fn: From 67fd0ddbbeb7f6418cb151c1b4217dd29eafc62e Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 30 Dec 2013 17:32:57 -0500 Subject: [PATCH 06/24] Document the compiler a little. --- docs/language/internals.rst | 167 ++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index cf4f99c..df43061 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -13,6 +13,173 @@ Hy Models Write this. +Hy Internal Theory +================== + +.. _overview: + +Overview +-------- + +The Hy internals work by acting as a front-end to Python bytecode, so that +Hy it's self compiles down to Python Bytecode, allowing an unmodified Python +runtime to run Hy. + +The way we do this is by translating Hy into Python AST, and building that AST +down into Python bytecode using standard internals, so that we don't have +to duplicate all the work of the Python internals for every single Python +release. + +Hy works in four stages. The following sections will cover each step of Hy +from source to runtime. + +.. _lexing: + +Lexing / tokenizing +------------------- + +The first stage of compiling hy is to lex the source into tokens that we can +deal with. We use a project called rply, which is a really nice (and fast) +parser, written in a subset of Python called rply. + +The lexing code is all defined in ``hy.lex.lexer``. This code is mostly just +defining the Hy grammer, and all the actual hard parts are taken care of by +rply -- we just define "callbacks" for rply in ``hy.lex.parser``, which take +the tokens generated, and return the Hy models. + +You can think of the Hy models as the "AST" for Hy, it's what Macros operate +on (directly), and it's what the compiler uses when it compiles Hy down. + +Check the documentation for more information on the Hy models for more +information regarding the Hy models, and what they mean. + +.. TODO: Uh, we should, like, document models. + + +.. _compiling: + +Compiling +--------- + +This is where most of the magic in Hy happens. This is where we take Hy AST +(the models), and compile them into Python AST. A couple of funky things happen +here to work past a few problems in AST, and working in the compiler is some +of the most important work we do have. + +The compiler is a bit complex, so don't feel bad if you don't grok it on the +first shot, it may take a bit of time to get right. + +The main entry-point to the Compiler is ``HyASTCompiler.compile``. This method +is invoked, and the only real "public" method on the class (that is to say, +we don't really promise the API beyond that method). + +In fact, even internally, we don't recurse directly hardly ever, we almost +always force the Hy tree through ``compile``, and will often do this with +sub-elements of an expression that we have. It's up to the Type-based dispatcher +to properly dispatch sub-elements. + +All methods that preform a compilation are marked with the ``@builds()`` +decorator. You can either pass the class of the Hy model that it compiles, +or you can use a string for expressions. I'll clear this up in a second. + +First stage type-dispatch +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let's start in the ``compile`` method. The first thing we do is check the +Type of the thing we're building. We look up to see if we have a method that +can build the ``type()`` that we have, and dispatch to the method that can +handle it. If we don't have any methods that can build that type, we raise +an internal ``Exception``. + +For instance, if we have a ``HyString``, we have an almost 1-to-1 mapping of +Hy AST to Python AST. The ``compile_string`` method takes the ``HyString``, and +returns an ``ast.Str()`` that's populated with the correct line-numbers and +content. + +Macro-expand +~~~~~~~~~~~~ + +If we get a ``HyExpression``, we'll attempt to see if this is a known +Macro, and push to have it expanded by invoking ``hy.macros.macroexpand``, then +push the result back into ``HyASTCompiler.compile``. + +Second stage expression-dispatch +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The only special case is the ``HyExpression``, since we need to create different +AST depending on the special form in question. For instance, when we hit an +``(if true true false)``, we need to generate a ``ast.If``, and properly +compile the sub-nodes. This is where the ``@builds()`` with a String as an +argument comes in. + +For the ``compile_expression`` (which is defined with an +``@builds(HyExpression)``) will dispatch based on the string of the first +argument. If, for some reason, the first argument is not a string, it will +properly handle that case as well (most likely by raising an ``Exception``). + +If the String isn't known to Hy, it will default to create an ``ast.Call``, +which will try to do a runtime call (in Python, something like ``foo()``). + +Issues hit with Python AST +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Python AST is great; it's what's enabled us to write such a powerful project +on top of Python without having to fight Python too hard. Like anything, we've +had our fair share of issues, and here's a short list of the common ones you +might run into. + +*Python differentiates between Statements and Expressions*. + +This might not sound like a big deal -- in fact, to most Python programmers, +this will shortly become a "Well, yeah" moment. + +In Python, doing something like: + +``print for x in range(10): pass``, because ``print`` prints expressions, and +``for`` isn't an expression, it's a control flow statement. Things like +``1 + 1`` are Expressions, as is ``lambda x: 1 + x``, but other language +features, such as ``if``, ``for``, or ``while`` are statements. + +Since they have no "value" to Python, this makes working in Hy hard, since +doing something like ``(print (if true true false))`` is not just common, it's +expected. + +As a result, we auto-mangle things using a ``Result`` object, where we offer +up any ``ast.stmt`` that need to get run, and a single ``ast.expr`` that can +be used to get the value of whatever was just run. Hy does this by forcing +assignment to things while running. + +As example, the Hy:: + + (print (if true true false)) + +Will turn into:: + + if True: + _mangled_name_here = True + else: + _mangled_name_here = False + + print _mangled_name_here + + +OK, that was a bit of a lie, since we actually turn that statement +into:: + + print True if True else False + +By forcing things into an ``ast.expr`` if we can, but the general idea holds. + + +Runtime +------- + +After we have a Python AST tree that's complete, we can try and compile it to +Python bytecode by pushing it through ``eval``. From here on out, we're no +longer in control, and Python is taking care of everything. This is why things +like Python tracebacks, pdb and django apps work. + + Hy Macros ========= From b011048b419d32135bfe20cf67aa9347cca1e022 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Mon, 30 Dec 2013 18:02:03 -0500 Subject: [PATCH 07/24] allow one-shot merge --- docs/hacking.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/hacking.rst b/docs/hacking.rst index 6ee479c..0a35898 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -100,6 +100,9 @@ If a core member is sending in a PR, please find 2 core members that don't include them PR submitter. The idea here is that one can work with the PR author, and a second acks the entire change set. +If the change is adding documentation, feel free to just merge after one +ACK. We've got low coverage, so it'd be great to keep that barrier low. + Core Team ========= From 426d34288f37c61182c7c0ef054a127dfbd83c6d Mon Sep 17 00:00:00 2001 From: Foxboron Date: Tue, 31 Dec 2013 02:06:51 +0100 Subject: [PATCH 08/24] Added docs and one small bug fix in defreader --- docs/language/api.rst | 19 +++++++++++ docs/language/index.rst | 1 + docs/language/readermacros.rst | 61 ++++++++++++++++++++++++++++++++++ hy/compiler.py | 2 +- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docs/language/readermacros.rst diff --git a/docs/language/api.rst b/docs/language/api.rst index aff6745..4e70b7d 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -397,6 +397,25 @@ So ``g!a`` would become ``(gensym "a")``. Section :ref:`using-gensym` +defreader +--------- + +.. versionadded:: 0.9.12 + +`defreader` defines a reader macro, enabling you to restructure or +modify syntax. + +.. code-block:: clj + + => (defreader ^ [expr] (print expr)) + => #^(1 2 3 4) + (1 2 3 4) + => #^"Hello" + "Hello" + +.. seealso:: + + Section :ref:`Reader Macros ` del --- diff --git a/docs/language/index.rst b/docs/language/index.rst index 5132647..4721eb3 100644 --- a/docs/language/index.rst +++ b/docs/language/index.rst @@ -11,3 +11,4 @@ Contents: api core internals + readermacros diff --git a/docs/language/readermacros.rst b/docs/language/readermacros.rst new file mode 100644 index 0000000..0c05d9c --- /dev/null +++ b/docs/language/readermacros.rst @@ -0,0 +1,61 @@ +.. _reader-macros: + +.. highlight:: clj + +============= +Reader Macros +============= + +Reader macros gives LISP the power to modify and alter syntax on the fly. +You don't want polish notation? A reader macro can easily do just that. Want +Clojure's way of having a regex? Reader macros can also do this easily. + + +Syntax +====== + +:: + + => (defreader ^ [expr] (print expr)) + => #^(1 2 3 4) + (1 2 3 4) + => #^"Hello" + "Hello" + => #^1+2+3+4+3+2 + 1+2+3+4+3+2 + + +Implementation +============== + +Hy uses ``defreader`` to define the reader symbol, and ``#`` as the dispatch +character. ``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol +and expression is quoted, and then passed along to the correct function:: + + => (defreader ^ ...) + => #^() + ;=> (dispatch_reader_macro '^ '()) + + +``defreader`` takes a single character as symbol name for the reader macro, +anything longer will return an error. Implementation wise, ``defreader`` +expands into a lambda covered with a decorator, this decorater saves the +lambda in a dict with its module name and symbol. + +:: + + => (defreader ^ [expr] (print expr)) + ;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr))) + + +Anything passed along is quoted, thus given to the function defined. + +:: + + => #^"Hello" + "Hello" + +.. warning:: + Because of a limitation in Hy's lexer and parser, reader macros can't + redefine defined syntax such as ``()[]{}``. This will most likely be + adressed in the future. diff --git a/hy/compiler.py b/hy/compiler.py index 9a3b710..8b34a9d 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -1866,7 +1866,7 @@ class HyASTCompiler(object): expression.pop(0) name = expression.pop(0) NOT_READERS = [":", "&"] - if name in NOT_READERS: + if name in NOT_READERS or len(name) > 1: raise NameError("%s can't be used as a macro reader symbol" % name) if not isinstance(name, HySymbol): raise HyTypeError(name, From 9b1990901c619d6f8e728d3bed61607dd9d06e83 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 31 Dec 2013 15:51:58 +0200 Subject: [PATCH 09/24] Fix |Unknown directive type "todo"| error. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 40eb062..6b45309 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ import hy # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +extensions = ['sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] From 931ce889f9912f75b483d5c7b877976dbab05504 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 11:26:31 -0500 Subject: [PATCH 10/24] s/rply/rpython/ --- docs/language/internals.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language/internals.rst b/docs/language/internals.rst index df43061..b9513f6 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -40,7 +40,7 @@ Lexing / tokenizing The first stage of compiling hy is to lex the source into tokens that we can deal with. We use a project called rply, which is a really nice (and fast) -parser, written in a subset of Python called rply. +parser, written in a subset of Python called rpython. The lexing code is all defined in ``hy.lex.lexer``. This code is mostly just defining the Hy grammer, and all the actual hard parts are taken care of by From 48f916b34f8cda4fcb24290801b55b9d000c2253 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Tue, 31 Dec 2013 21:58:40 +0530 Subject: [PATCH 11/24] Add myself to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 643541b..63e699a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,3 +21,4 @@ * Henrique Carvalho Alves * Joe Hakim Rahme * Kenan Bölükbaşı +* Abhishek L \ No newline at end of file From f6160c755a59a8d8203fb58ad66b03b4f13e158e Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sun, 22 Dec 2013 12:56:03 -0700 Subject: [PATCH 12/24] Much better version of new error messages. This version is much simpler. At the point that the exception is raised, we don't have access to the actual source, just the current expression. but as the exception percolates up, we can intercept it, add the source and the re-raise it. Then at the final point, in the cmdline handler, we can choose to let the entire traceback print, or just the simpler, direct error message. And even with the full traceback, the last bit is nicely formatted just like the shorter, simpler message. The error message is colored if clint is installed, but to avoid yet another dependency, you get monochrome without clint. I'm sure there is a better way to do the markup, the current method is kludgy but works. I wish there was more shared code between HyTypeError and LexException but they are kind of different in some fundamental ways. This doesn't work (yet) with runtime errors generated from Python, like NameError, but I have a method that can catch NameError and turn it into a more pleasing output. Finally, there is no obvious way to raise HyTypeError from pure Hy code, so methods in core/language.hy throw ugly TypeError/ValueError. --- hy/cmdline.py | 49 ++++++++++++++++++++++++++------- hy/compiler.py | 55 ++++++++++++++++++++++++++++++++++--- hy/errors.py | 39 ++++++++++++++++++++++++++ hy/importer.py | 26 +++++++++++++----- hy/lex/__init__.py | 5 ++-- hy/lex/exceptions.py | 39 ++++++++++++++++++++++++-- hy/lex/parser.py | 7 ++--- tests/compilers/test_ast.py | 14 ++++++---- tests/test_bin.py | 2 +- 9 files changed, 200 insertions(+), 36 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index eb621b0..e40862d 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -5,6 +5,7 @@ # Copyright (c) 2013 Konrad Hinsen # Copyright (c) 2013 Thom Neale # Copyright (c) 2013 Will Kahn-Greene +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -32,7 +33,7 @@ import sys import hy from hy.lex import LexException, PrematureEndOfInput, tokenize -from hy.compiler import hy_compile +from hy.compiler import hy_compile, HyTypeError from hy.importer import ast_compile, import_buffer_to_module from hy.completer import completion @@ -83,8 +84,11 @@ class HyREPL(code.InteractiveConsole): tokens = tokenize(source) except PrematureEndOfInput: return True - except LexException: - self.showsyntaxerror(filename) + except LexException as e: + if e.source is None: + e.source = source + e.filename = filename + print(e) return False try: @@ -92,6 +96,12 @@ class HyREPL(code.InteractiveConsole): if self.spy: print_python_code(_ast) code = ast_compile(_ast, filename, symbol) + except HyTypeError as e: + if e.source is None: + e.source = source + e.filename = filename + print(e) + return False except Exception: self.showtraceback() return False @@ -154,22 +164,34 @@ def ideas_macro(): require("hy.cmdline", "__console__") require("hy.cmdline", "__main__") +SIMPLE_TRACEBACKS = True + def run_command(source): try: import_buffer_to_module("__main__", source) - except LexException as exc: - # TODO: This would be better if we had line, col info. - print(source) - print(repr(exc)) - return 1 + except (HyTypeError, LexException) as e: + if SIMPLE_TRACEBACKS: + print(e) + return 1 + raise + except Exception: + raise return 0 def run_file(filename): from hy.importer import import_file_to_module - import_file_to_module("__main__", filename) - return 0 # right? + try: + import_file_to_module("__main__", filename) + except (HyTypeError, LexException) as e: + if SIMPLE_TRACEBACKS: + print(e) + return 1 + raise + except Exception: + raise + return 0 def run_repl(hr=None, spy=False): @@ -218,6 +240,9 @@ def cmdline_handler(scriptname, argv): parser.add_argument("-v", action="version", version=VERSION) + parser.add_argument("--show_tracebacks", action="store_true", + help="show complete tracebacks for Hy exceptions") + # this will contain the script/program name and any arguments for it. parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) @@ -228,6 +253,10 @@ def cmdline_handler(scriptname, argv): options = parser.parse_args(argv[1:]) + if options.show_tracebacks: + global SIMPLE_TRACEBACKS + SIMPLE_TRACEBACKS = False + # reset sys.argv like Python sys.argv = options.args or [""] diff --git a/hy/compiler.py b/hy/compiler.py index 1c7c156..9fa4c69 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -4,6 +4,7 @@ # Copyright (c) 2013 Julien Danjou # Copyright (c) 2013 Nicolas Dandrimont # Copyright (c) 2013 James King +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -93,11 +94,52 @@ class HyTypeError(TypeError): def __init__(self, expression, message): super(HyTypeError, self).__init__(message) self.expression = expression + self.message = message + self.source = None + self.filename = None def __str__(self): - return (super(HyTypeError, self).__str__() + " (line %s, column %d)" - % (self.expression.start_line, - self.expression.start_column)) + from hy.errors import colored + + line = self.expression.start_line + start = self.expression.start_column + end = self.expression.end_column + + source = [] + if self.source is not None: + source = self.source.split("\n")[line-1:self.expression.end_line] + + if line == self.expression.end_line: + length = end - start + else: + length = len(source[0]) - start + + result = "" + + result += ' File "%s", line %d, column %d\n\n' % (self.filename, + line, + start) + + if len(source) == 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*(length-1) + '^')) + if len(source) > 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*length)) + if len(source) > 2: # write the middle lines + for line in source[1:-1]: + result += ' %s\n' % colored.red("".join(line)) + result += ' %s\n' % colored.green("-"*len(line)) + + # write the last line + result += ' %s\n' % colored.red("".join(source[-1])) + result += ' %s\n' % colored.green('-'*(end-1) + '^') + + result += colored.yellow("HyTypeError: %s\n\n" % self.message) + + return result _compile_table = {} @@ -341,7 +383,7 @@ def checkargs(exact=None, min=None, max=None, even=None): if min is not None and (len(expression) - 1) < min: _raise_wrong_args_number( expression, - "`%%s' needs at least %d arguments, got %%d" % (min)) + "`%%s' needs at least %d arguments, got %%d." % (min)) if max is not None and (len(expression) - 1) > max: _raise_wrong_args_number( @@ -430,6 +472,8 @@ class HyASTCompiler(object): # nested; so let's re-raise this exception, let's not wrap it in # another HyCompileError! raise + except HyTypeError as e: + raise except Exception as e: raise HyCompileError(e, sys.exc_info()[2]) @@ -1584,6 +1628,9 @@ class HyASTCompiler(object): fn.replace(ofn) # Get the object we want to take an attribute from + if len(expression) < 2: + raise HyTypeError(expression, + "attribute access requires object") func = self.compile(expression.pop(1)) # And get the attribute diff --git a/hy/errors.py b/hy/errors.py index 323d67e..be932eb 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -25,3 +26,41 @@ class HyError(Exception): Exception. """ pass + + +try: + from clint.textui import colored +except: + class colored: + + @staticmethod + def black(foo): + return foo + + @staticmethod + def red(foo): + return foo + + @staticmethod + def green(foo): + return foo + + @staticmethod + def yellow(foo): + return foo + + @staticmethod + def blue(foo): + return foo + + @staticmethod + def magenta(foo): + return foo + + @staticmethod + def cyan(foo): + return foo + + @staticmethod + def white(foo): + return foo diff --git a/hy/importer.py b/hy/importer.py index b32b71a..35cd685 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Paul Tagliamonte +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -19,10 +20,9 @@ # DEALINGS IN THE SOFTWARE. from py_compile import wr_long, MAGIC -from hy.compiler import hy_compile +from hy.compiler import hy_compile, HyTypeError from hy.models import HyObject -from hy.lex import tokenize - +from hy.lex import tokenize, LexException from io import open import marshal @@ -73,17 +73,29 @@ def import_file_to_module(module_name, fpath): mod = imp.new_module(module_name) mod.__file__ = fpath eval(ast_compile(_ast, fpath, "exec"), mod.__dict__) + except (HyTypeError, LexException) as e: + if e.source is None: + fp = open(fpath, 'rt') + e.source = fp.read() + fp.close() + e.filename = fpath + raise except Exception: sys.modules.pop(module_name, None) raise - return mod def import_buffer_to_module(module_name, buf): - _ast = import_buffer_to_ast(buf, module_name) - mod = imp.new_module(module_name) - eval(ast_compile(_ast, "", "exec"), mod.__dict__) + try: + _ast = import_buffer_to_ast(buf, module_name) + mod = imp.new_module(module_name) + eval(ast_compile(_ast, "", "exec"), mod.__dict__) + except (HyTypeError, LexException) as e: + if e.source is None: + e.source = buf + e.filename = '' + raise return mod diff --git a/hy/lex/__init__.py b/hy/lex/__init__.py index 651f89a..8153aa8 100644 --- a/hy/lex/__init__.py +++ b/hy/lex/__init__.py @@ -33,6 +33,5 @@ def tokenize(buf): return parser.parse(lexer.lex(buf)) except LexingError as e: pos = e.getsourcepos() - raise LexException( - "Could not identify the next token at line %s, column %s" % ( - pos.lineno, pos.colno)) + raise LexException("Could not identify the next token.", + pos.lineno, pos.colno) diff --git a/hy/lex/exceptions.py b/hy/lex/exceptions.py index 21b2700..4c4b760 100644 --- a/hy/lex/exceptions.py +++ b/hy/lex/exceptions.py @@ -1,4 +1,5 @@ # Copyright (c) 2013 Nicolas Dandrimont +# Copyright (c) 2013 Bob Tolbert # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -23,9 +24,43 @@ from hy.errors import HyError class LexException(HyError): """Error during the Lexing of a Hython expression.""" - pass + def __init__(self, message, lineno, colno): + super(LexException, self).__init__(message) + self.message = message + self.lineno = lineno + self.colno = colno + self.source = None + self.filename = '' + + def __str__(self): + from hy.errors import colored + + line = self.lineno + start = self.colno + + result = "" + + source = self.source.split("\n") + + if line > 0 and start > 0: + result += ' File "%s", line %d, column %d\n\n' % (self.filename, + line, + start) + + if len(self.source) > 0: + source_line = source[line-1] + else: + source_line = "" + + result += ' %s\n' % colored.red(source_line) + result += ' %s%s\n' % (' '*(start-1), colored.green('^')) + + result += colored.yellow("LexException: %s\n\n" % self.message) + + return result class PrematureEndOfInput(LexException): """We got a premature end of input""" - pass + def __init__(self, message): + super(PrematureEndOfInput, self).__init__(message, -1, -1) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 9e11069..72f0d8f 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -258,12 +258,11 @@ def t_identifier(p): def error_handler(token): tokentype = token.gettokentype() if tokentype == '$end': - raise PrematureEndOfInput + raise PrematureEndOfInput("Premature end of input") else: raise LexException( - "Ran into a %s where it wasn't expected at line %s, column %s" % - (tokentype, token.source_pos.lineno, token.source_pos.colno) - ) + "Ran into a %s where it wasn't expected." % tokentype, + token.source_pos.lineno, token.source_pos.colno) parser = pg.build() diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index c6590fa..43b33ea 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -22,6 +22,7 @@ from __future__ import unicode_literals from hy import HyString +from hy.models import HyObject from hy.compiler import hy_compile, HyCompileError, HyTypeError from hy.lex import tokenize @@ -42,10 +43,14 @@ def can_compile(expr): def cant_compile(expr): - expr = tokenize(expr) try: - hy_compile(expr, "__main__") + hy_compile(tokenize(expr), "__main__") assert False + except HyTypeError as e: + # Anything that can't be compiled should raise a user friendly + # error, otherwise it's a compiler bug. + assert isinstance(e.expression, HyObject) + assert e.message except HyCompileError as e: # Anything that can't be compiled should raise a user friendly # error, otherwise it's a compiler bug. @@ -422,8 +427,7 @@ def test_compile_error(): """Ensure we get compile error in tricky cases""" try: can_compile("(fn [] (= 1))") - except HyCompileError as e: - assert(str(e) - == "`=' needs at least 2 arguments, got 1 (line 1, column 8)") + except HyTypeError as e: + assert(e.message == "`=' needs at least 2 arguments, got 1.") else: assert(False) diff --git a/tests/test_bin.py b/tests/test_bin.py index 06cd455..8f73f56 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -64,7 +64,7 @@ def test_bin_hy_cmd(): ret = run_cmd("hy -c \"(koan\"") assert ret[0] == 1 - assert "PrematureEndOfInput" in ret[1] + assert "Premature end of input" in ret[1] def test_bin_hy_icmd(): From f064d3f1217c4bf0bdd122cec1c98b1240da55de Mon Sep 17 00:00:00 2001 From: Foxboron Date: Thu, 26 Dec 2013 04:02:20 +0100 Subject: [PATCH 13/24] Errors into errors.py, added HyMacroExpansionError, fixed macro arg fail --- hy/compiler.py | 74 ++---------------------------------------------- hy/errors.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ hy/macros.py | 8 +++++- 3 files changed, 85 insertions(+), 73 deletions(-) diff --git a/hy/compiler.py b/hy/compiler.py index 9fa4c69..01ddfce 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -24,8 +24,6 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from hy.errors import HyError - from hy.models.lambdalist import HyLambdaListKeyword from hy.models.expression import HyExpression from hy.models.keyword import HyKeyword @@ -37,12 +35,13 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict +from hy.errors import HyMacroExpansionError, HyCompileError, HyTypeError + import hy.macros from hy.macros import require, macroexpand from hy._compat import str_type, long_type import hy.importer -import traceback import importlib import codecs import ast @@ -73,75 +72,6 @@ def load_stdlib(): _stdlib[e] = module -class HyCompileError(HyError): - def __init__(self, exception, traceback=None): - self.exception = exception - self.traceback = traceback - - def __str__(self): - if isinstance(self.exception, HyTypeError): - return str(self.exception) - if self.traceback: - tb = "".join(traceback.format_tb(self.traceback)).strip() - else: - tb = "No traceback available. 😟" - return("Internal Compiler Bug 😱\n⤷ %s: %s\nCompilation traceback:\n%s" - % (self.exception.__class__.__name__, - self.exception, tb)) - - -class HyTypeError(TypeError): - def __init__(self, expression, message): - super(HyTypeError, self).__init__(message) - self.expression = expression - self.message = message - self.source = None - self.filename = None - - def __str__(self): - from hy.errors import colored - - line = self.expression.start_line - start = self.expression.start_column - end = self.expression.end_column - - source = [] - if self.source is not None: - source = self.source.split("\n")[line-1:self.expression.end_line] - - if line == self.expression.end_line: - length = end - start - else: - length = len(source[0]) - start - - result = "" - - result += ' File "%s", line %d, column %d\n\n' % (self.filename, - line, - start) - - if len(source) == 1: - result += ' %s\n' % colored.red(source[0]) - result += ' %s%s\n' % (' '*(start-1), - colored.green('^' + '-'*(length-1) + '^')) - if len(source) > 1: - result += ' %s\n' % colored.red(source[0]) - result += ' %s%s\n' % (' '*(start-1), - colored.green('^' + '-'*length)) - if len(source) > 2: # write the middle lines - for line in source[1:-1]: - result += ' %s\n' % colored.red("".join(line)) - result += ' %s\n' % colored.green("-"*len(line)) - - # write the last line - result += ' %s\n' % colored.red("".join(source[-1])) - result += ' %s\n' % colored.green('-'*(end-1) + '^') - - result += colored.yellow("HyTypeError: %s\n\n" % self.message) - - return result - - _compile_table = {} diff --git a/hy/errors.py b/hy/errors.py index be932eb..9f3f580 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -19,6 +19,8 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +import traceback + class HyError(Exception): """ @@ -64,3 +66,77 @@ except: @staticmethod def white(foo): return foo + + +class HyCompileError(HyError): + def __init__(self, exception, traceback=None): + self.exception = exception + self.traceback = traceback + + def __str__(self): + if isinstance(self.exception, HyTypeError): + return str(self.exception) + if self.traceback: + tb = "".join(traceback.format_tb(self.traceback)).strip() + else: + tb = "No traceback available. 😟" + return("Internal Compiler Bug 😱\n⤷ %s: %s\nCompilation traceback:\n%s" + % (self.exception.__class__.__name__, + self.exception, tb)) + + +class HyTypeError(TypeError): + def __init__(self, expression, message): + super(HyTypeError, self).__init__(message) + self.expression = expression + self.message = message + self.source = None + self.filename = None + + def __str__(self): + + line = self.expression.start_line + start = self.expression.start_column + end = self.expression.end_column + + source = [] + if self.source is not None: + source = self.source.split("\n")[line-1:self.expression.end_line] + + if line == self.expression.end_line: + length = end - start + else: + length = len(source[0]) - start + + result = "" + + result += ' File "%s", line %d, column %d\n\n' % (self.filename, + line, + start) + + if len(source) == 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*(length-1) + '^')) + if len(source) > 1: + result += ' %s\n' % colored.red(source[0]) + result += ' %s%s\n' % (' '*(start-1), + colored.green('^' + '-'*length)) + if len(source) > 2: # write the middle lines + for line in source[1:-1]: + result += ' %s\n' % colored.red("".join(line)) + result += ' %s\n' % colored.green("-"*len(line)) + + # write the last line + result += ' %s\n' % colored.red("".join(source[-1])) + result += ' %s\n' % colored.green('-'*(end-1) + '^') + + + result += colored.yellow("%s: %s\n\n" % (self.__class__.__name__, + self.message)) + + return result + + +class HyMacroExpansionError(HyTypeError): + pass diff --git a/hy/macros.py b/hy/macros.py index 4e2389c..dd31609 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -28,6 +28,8 @@ from hy.models.complex import HyComplex from hy.models.dict import HyDict from hy._compat import str_type, long_type +from hy.errors import HyMacroExpansionError + from collections import defaultdict import sys @@ -192,7 +194,11 @@ def macroexpand_1(tree, module_name): if m is None: m = _hy_macros[None].get(fn) if m is not None: - obj = _wrap_value(m(*ntree[1:])) + try: + obj = _wrap_value(m(*ntree[1:])) + except Exception as e: + msg = str(tree[0]) + " " + " ".join(str(e).split()[1:]) + raise HyMacroExpansionError(tree, msg) obj.replace(tree) return obj From 765dba3e56d40c7bed00273a7012f061908c3955 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Thu, 26 Dec 2013 09:36:45 -0700 Subject: [PATCH 14/24] More updates, including from Foxboron, for errors like (for) --- hy/cmdline.py | 12 ++++++++---- hy/compiler.py | 3 ++- hy/core/bootstrap.hy | 2 +- hy/core/macros.hy | 2 ++ hy/errors.py | 8 +++++--- hy/macros.py | 9 +++++++-- tests/compilers/test_ast.py | 35 ++++++++++++++++++++++++++++++++++- tests/test_bin.py | 2 +- 8 files changed, 60 insertions(+), 13 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index e40862d..09fe366 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -80,6 +80,7 @@ class HyREPL(code.InteractiveConsole): filename=filename) def runsource(self, source, filename='', symbol='single'): + global SIMPLE_TRACEBACKS try: tokens = tokenize(source) except PrematureEndOfInput: @@ -88,7 +89,7 @@ class HyREPL(code.InteractiveConsole): if e.source is None: e.source = source e.filename = filename - print(e) + sys.stderr.write(str(e)) return False try: @@ -100,7 +101,10 @@ class HyREPL(code.InteractiveConsole): if e.source is None: e.source = source e.filename = filename - print(e) + if SIMPLE_TRACEBACKS: + sys.stderr.write(str(e)) + else: + self.showtraceback() return False except Exception: self.showtraceback() @@ -172,7 +176,7 @@ def run_command(source): import_buffer_to_module("__main__", source) except (HyTypeError, LexException) as e: if SIMPLE_TRACEBACKS: - print(e) + sys.stderr.write(str(e)) return 1 raise except Exception: @@ -186,7 +190,7 @@ def run_file(filename): import_file_to_module("__main__", filename) except (HyTypeError, LexException) as e: if SIMPLE_TRACEBACKS: - print(e) + sys.stderr.write(str(e)) return 1 raise except Exception: diff --git a/hy/compiler.py b/hy/compiler.py index 01ddfce..c418dba 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -35,13 +35,14 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict -from hy.errors import HyMacroExpansionError, HyCompileError, HyTypeError +from hy.errors import HyCompileError, HyTypeError import hy.macros from hy.macros import require, macroexpand from hy._compat import str_type, long_type import hy.importer +import traceback import importlib import codecs import ast diff --git a/hy/core/bootstrap.hy b/hy/core/bootstrap.hy index 5d3d367..3afd2c5 100644 --- a/hy/core/bootstrap.hy +++ b/hy/core/bootstrap.hy @@ -28,7 +28,7 @@ (defmacro macro-error [location reason] "error out properly within a macro" - `(raise (hy.compiler.HyTypeError ~location ~reason))) + `(raise (hy.errors.HyMacroExpansionError ~location ~reason))) (defmacro defmacro-alias [names lambda-list &rest body] diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 39338c1..a2a240e 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -31,6 +31,8 @@ (for* [x foo] (for* [y bar] baz))" + (if (empty? body) + (macro-error None "`for' requires a body to evaluate")) (if args `(for* ~(.pop args 0) (for ~args ~@body)) `(do ~@body))) diff --git a/hy/errors.py b/hy/errors.py index 9f3f580..2f269d5 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -1,3 +1,5 @@ +# -*- encoding: utf-8 -*- +# # Copyright (c) 2013 Paul Tagliamonte # Copyright (c) 2013 Bob Tolbert # @@ -131,9 +133,9 @@ class HyTypeError(TypeError): result += ' %s\n' % colored.red("".join(source[-1])) result += ' %s\n' % colored.green('-'*(end-1) + '^') - - result += colored.yellow("%s: %s\n\n" % (self.__class__.__name__, - self.message)) + result += colored.yellow("%s: %s\n\n" % + (self.__class__.__name__, + self.message)) return result diff --git a/hy/macros.py b/hy/macros.py index dd31609..d98b328 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -28,7 +28,7 @@ from hy.models.complex import HyComplex from hy.models.dict import HyDict from hy._compat import str_type, long_type -from hy.errors import HyMacroExpansionError +from hy.errors import HyTypeError, HyMacroExpansionError from collections import defaultdict import sys @@ -196,8 +196,13 @@ def macroexpand_1(tree, module_name): if m is not None: try: obj = _wrap_value(m(*ntree[1:])) + except HyTypeError as e: + if e.expression is None: + e.expression = tree + raise except Exception as e: - msg = str(tree[0]) + " " + " ".join(str(e).split()[1:]) + msg = "`" + str(tree[0]) + "' " + \ + " ".join(str(e).split()[1:]) raise HyMacroExpansionError(tree, msg) obj.replace(tree) return obj diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 43b33ea..76a9e1d 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -23,7 +23,9 @@ from __future__ import unicode_literals from hy import HyString from hy.models import HyObject -from hy.compiler import hy_compile, HyCompileError, HyTypeError +from hy.compiler import hy_compile +from hy.errors import HyCompileError, HyTypeError +from hy.lex.exceptions import LexException from hy.lex import tokenize import ast @@ -431,3 +433,34 @@ def test_compile_error(): assert(e.message == "`=' needs at least 2 arguments, got 1.") else: assert(False) + + +def test_for_compile_error(): + """Ensure we get compile error in tricky 'for' cases""" + try: + can_compile("(fn [] (for)") + except LexException as e: + assert(e.message == "Premature end of input") + else: + assert(False) + + try: + can_compile("(fn [] (for)))") + except LexException as e: + assert(e.message == "Ran into a RPAREN where it wasn't expected.") + else: + assert(False) + + try: + can_compile("(fn [] (for [x]))") + except HyTypeError as e: + assert(e.message == "`for' requires an even number of elements in its first argument") # noqa + else: + assert(False) + + try: + can_compile("(fn [] (for [x xx]))") + except HyTypeError as e: + assert(e.message == "`for' requires a body to evaluate") + else: + assert(False) diff --git a/tests/test_bin.py b/tests/test_bin.py index 8f73f56..d18dd31 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -64,7 +64,7 @@ def test_bin_hy_cmd(): ret = run_cmd("hy -c \"(koan\"") assert ret[0] == 1 - assert "Premature end of input" in ret[1] + assert "Premature end of input" in ret[2] def test_bin_hy_icmd(): From 5040c2994632e60d891399f4a2e755319eb8aa2f Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Thu, 26 Dec 2013 20:46:48 -0700 Subject: [PATCH 15/24] Cleaning up some comments from berker --- docs/language/cli.rst | 4 ++++ hy/errors.py | 2 +- hy/importer.py | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 4bbca65..2cccd65 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -32,6 +32,10 @@ Command line options .. versionadded:: 0.9.11 +.. cmdoption:: --show_tracebacks + + Print extended tracebacks for Hy exceptions. + .. cmdoption:: -v Print the Hy version number and exit. diff --git a/hy/errors.py b/hy/errors.py index 2f269d5..0e786e8 100644 --- a/hy/errors.py +++ b/hy/errors.py @@ -34,7 +34,7 @@ class HyError(Exception): try: from clint.textui import colored -except: +except Exception: class colored: @staticmethod diff --git a/hy/importer.py b/hy/importer.py index 35cd685..1f8b7eb 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -75,9 +75,8 @@ def import_file_to_module(module_name, fpath): eval(ast_compile(_ast, fpath, "exec"), mod.__dict__) except (HyTypeError, LexException) as e: if e.source is None: - fp = open(fpath, 'rt') - e.source = fp.read() - fp.close() + with open(fpath, 'rt') as fp: + e.source = fp.read() e.filename = fpath raise except Exception: From 05899423df1b27a981f8e42f3cf480e93a0caa8d Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Sat, 28 Dec 2013 06:58:08 -0700 Subject: [PATCH 16/24] Changing --show_tracebacks to --show-tracebacks --- docs/language/cli.rst | 4 +++- hy/cmdline.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/language/cli.rst b/docs/language/cli.rst index 2cccd65..875efa5 100644 --- a/docs/language/cli.rst +++ b/docs/language/cli.rst @@ -32,10 +32,12 @@ Command line options .. versionadded:: 0.9.11 -.. cmdoption:: --show_tracebacks +.. cmdoption:: --show-tracebacks Print extended tracebacks for Hy exceptions. + .. versionadded:: 0.9.12 + .. cmdoption:: -v Print the Hy version number and exit. diff --git a/hy/cmdline.py b/hy/cmdline.py index 09fe366..b09595f 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -244,7 +244,7 @@ def cmdline_handler(scriptname, argv): parser.add_argument("-v", action="version", version=VERSION) - parser.add_argument("--show_tracebacks", action="store_true", + parser.add_argument("--show-tracebacks", action="store_true", help="show complete tracebacks for Hy exceptions") # this will contain the script/program name and any arguments for it. From faf782560cbfdfc064744c048e07479c480087cb Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Tue, 31 Dec 2013 09:53:43 -0700 Subject: [PATCH 17/24] Fixing tests for new 'for' syntax --- tests/compilers/test_ast.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index 76a9e1d..b191c17 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -313,7 +313,7 @@ def test_ast_valid_while(): def test_ast_valid_for(): "Make sure AST can compile valid for" - can_compile("(for [[a 2]])") + can_compile("(for [[a 2]] (print a))") def test_ast_invalid_for(): @@ -452,14 +452,14 @@ def test_for_compile_error(): assert(False) try: - can_compile("(fn [] (for [x]))") - except HyTypeError as e: - assert(e.message == "`for' requires an even number of elements in its first argument") # noqa - else: - assert(False) - - try: - can_compile("(fn [] (for [x xx]))") + can_compile("(fn [] (for [[x]]))") + except HyTypeError as e: + assert(e.message == "`for' requires a body to evaluate") + else: + assert(False) + + try: + can_compile("(fn [] (for [[x xx]]))") except HyTypeError as e: assert(e.message == "`for' requires a body to evaluate") else: From 8cabf22749478751911ebb69816b600f0165e5c7 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 12:04:43 -0500 Subject: [PATCH 18/24] add news for 0.9.12 --- NEWS | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 1a654ab..eea9ae5 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,76 @@ Changes from Hy 0.9.11 - [ Misc. Fixes ] - [ Syntax Fixes ] + tl;dr: + + 0.9.12 comes with some massive changes, + We finally took the time to implement gensym, as well as a few + other bits that help macro writing. Check the changelog for + what exactly was added. + + The biggest feature, Reader Macros, landed later + in the cycle, but were big enough to warrent a release on it's + own. A huge thanks goes to Foxboron for implementing them + and a massive hug goes out to olasd for providing ongoing + reviews during the development. + + Welcome to the new Hy contributors, Henrique Carvalho Alves + an Kenan Bölükbaşı. Thanks for your work so far, folks! + + Hope y'all enjoy the finest that 2013 has to offer, + - Hy Society + + + * Special thanks goes to Willyfrog, Foxboron and theanalyst for writing + 0.9.12's NEWS. Thanks, y'all! (PT) + + [ Language Changes ] - * Translate foo? -> is_foo, for better Python interop. (PT) + * Translate foo? -> is_foo, for better Python interop. (PT) + * Reader Macros! + * Operators + and * now can work without arguments + * Define kwapply as a macro + * Added apply as a function + * Instant symbol generation with gensym + * Allow macros to return None + * Add a method for casting into byte string or unicode depending on python version + * flatten function added to language + * Add a method for casting into byte string or unicode depending on python version + * Added type coercing to the right integer for the platform + + + [ Misc. Fixes ] + * Added information about core team members + * Documentation fixed and extended + * Add astor to install_requires to fix hy --spy failing on hy 0.9.11. + * Convert stdout and stderr to UTF-8 properly in the run_cmd helper. + * Update requirements.txt and setup.py to use rply upstream. + * tryhy link added in documentation and README + * Command line options documented + * Adding support for coverage tests at coveralls.io + * Added info about tox, so people can use it prior to a PR + * Added the start of hacking rules + * Halting Problem removed from example as it was nonfree + * Fixed PyPI is now behind a CDN. The --use-mirrors option is deprecated. + * Badges for pypi version and downloads. + + + [ Syntax Fixes ] + * for changed syntax from (for [] ...) -> (for [[]] ...) + * with changed syntax from (with [] ...) -> (with [[]] ...) + * get allows multiple arguments + + + [ Bug Fixes ] + * OSX: Fixes for readline Repl problem which caused HyREPL not allowing 'b' + * Fix REPL completions on OSX + * Make HyObject.replace more resilient to prevent compiler breakage. + + + [ Contrib changes ] + * Anaphoric macros added to contrib + * Modified eg/twisted to follow the newer hy syntax + * Added (experimental) profile module + Changes from Hy 0.9.10 From 3774a05d879062bd5f649009ae59aca15a7c48e6 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Tue, 31 Dec 2013 19:39:37 +0200 Subject: [PATCH 19/24] Add Kevin Zita to NEWS for 0.9.12. --- NEWS | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index eea9ae5..002ed0e 100644 --- a/NEWS +++ b/NEWS @@ -13,11 +13,12 @@ Changes from Hy 0.9.11 and a massive hug goes out to olasd for providing ongoing reviews during the development. - Welcome to the new Hy contributors, Henrique Carvalho Alves - an Kenan Bölükbaşı. Thanks for your work so far, folks! + Welcome to the new Hy contributors, Henrique Carvalho Alves, + Kevin Zita and Kenan Bölükbaşı. Thanks for your work so far, + folks! - Hope y'all enjoy the finest that 2013 has to offer, - - Hy Society + Hope y'all enjoy the finest that 2013 has to offer, + - Hy Society * Special thanks goes to Willyfrog, Foxboron and theanalyst for writing From eeef65b505cc0f8b7585db35772428e2b87a24c9 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 13:35:31 -0500 Subject: [PATCH 20/24] Change the signature of (for) and (with). (for) is restored to clojure-like flatness. (with) can be used without the nested list if you don't care about the name. Tests and examples updated. --- NEWS | 2 -- docs/language/api.rst | 21 +++++++++++---------- eg/python3/futures/hello-world.hy | 2 +- hy/contrib/anaphoric.hy | 10 +++++----- hy/core/macros.hy | 31 ++++++++++++++++++++++++++----- tests/compilers/test_ast.py | 8 ++++---- tests/native_tests/language.hy | 18 +++++++++--------- 7 files changed, 56 insertions(+), 36 deletions(-) diff --git a/NEWS b/NEWS index eea9ae5..cdfb630 100644 --- a/NEWS +++ b/NEWS @@ -55,8 +55,6 @@ Changes from Hy 0.9.11 [ Syntax Fixes ] - * for changed syntax from (for [] ...) -> (for [[]] ...) - * with changed syntax from (with [] ...) -> (with [[]] ...) * get allows multiple arguments diff --git a/docs/language/api.rst b/docs/language/api.rst index 4e70b7d..57c64e7 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -205,11 +205,12 @@ however is called only for every other value in the list. ;; assuming that (side-effect1) and (side-effect2) are functions and ;; collection is a list of numerical values - (for (x collection) (do - (side-effect1 x) - (if (% x 2) - (continue)) - (side-effect2 x))) + (for [x collection] + (do + (side-effect1 x) + (if (% x 2) + (continue)) + (side-effect2 x))) do / progn @@ -489,10 +490,10 @@ 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 - (for [[element collection]] (side-effect element)) + (for [element collection] (side-effect element)) ;; for can have an optional else block - (for [[element collection]] (side-effect element) + (for [element collection] (side-effect element) (else (side-effect-2))) The optional `else` block is executed only if the `for` loop terminates @@ -500,14 +501,14 @@ normally. If the execution is halted with `break`, the `else` does not execute. .. code-block:: clj - => (for [[element [1 2 3]]] (if (< element 3) + => (for [element [1 2 3]] (if (< element 3) ... (print element) ... (break)) ... (else (print "loop finished"))) 1 2 - => (for [[element [1 2 3]]] (if (< element 4) + => (for [element [1 2 3]] (if (< element 4) ... (print element) ... (break)) ... (else (print "loop finished"))) @@ -680,7 +681,7 @@ function is defined and passed to another function for filtering output. ... {:name "Dave" :age 5}]) => (defn display-people [people filter] - ... (for [[person people]] (if (filter person) (print (:name person))))) + ... (for [person people] (if (filter person) (print (:name person))))) => (display-people people (fn [person] (< (:age person) 25))) Alice diff --git a/eg/python3/futures/hello-world.hy b/eg/python3/futures/hello-world.hy index 19d99d4..99f691f 100644 --- a/eg/python3/futures/hello-world.hy +++ b/eg/python3/futures/hello-world.hy @@ -7,5 +7,5 @@ (with-as (ThreadPoolExecutor 10) executor (setv jobs (list-comp (.submit executor task-to-do) (x (range 0 10)))) - (for (future (as-completed jobs)) + (for [future (as-completed jobs)] (.result future))) diff --git a/hy/contrib/anaphoric.hy b/hy/contrib/anaphoric.hy index 9a281d0..2f37b47 100644 --- a/hy/contrib/anaphoric.hy +++ b/hy/contrib/anaphoric.hy @@ -31,14 +31,14 @@ (defmacro ap-each [lst &rest body] "Evaluate the body form for each element in the list." - `(for [[it ~lst]] ~@body)) + `(for [it ~lst] ~@body)) (defmacro ap-each-while [lst form &rest body] "Evalutate the body form for each element in the list while the predicate form evaluates to True." `(let [[p (lambda [it] ~form)]] - (for [[it ~lst]] + (for [it ~lst] (if (p it) ~@body (break))))) @@ -47,7 +47,7 @@ (defmacro ap-map [form lst] "Yield elements evaluated in the form for each element in the list." `(let [[f (lambda [it] ~form)]] - (for [[v ~lst]] + (for [v ~lst] (yield (f v))))) @@ -55,7 +55,7 @@ "Yield elements evaluated for each element in the list when the predicate function returns True." `(let [[f (lambda [it] ~rep)]] - (for [[it ~lst]] + (for [it ~lst] (if (~predfn it) (yield (f it)) (yield it))))) @@ -64,7 +64,7 @@ (defmacro ap-filter [form lst] "Yield elements returned when the predicate form evaluates to True." `(let [[pred (lambda [it] ~form)]] - (for [[val ~lst]] + (for [val ~lst] (if (pred val) (yield val))))) diff --git a/hy/core/macros.hy b/hy/core/macros.hy index a2a240e..cdc5d17 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -25,16 +25,29 @@ ;;; These macros form the hy language ;;; They are automatically required in every module, except inside hy.core + +(import [hy.models.list [HyList]] + [hy.models.symbol [HySymbol]]) + + + (defmacro for [args &rest body] "shorthand for nested for loops: - (for [[x foo] [y bar]] baz) -> + (for [x foo + y bar] + baz) -> (for* [x foo] (for* [y bar] baz))" + + (if (odd? (len args)) + (macro-error args "`for' requires an even number of args.")) + (if (empty? body) (macro-error None "`for' requires a body to evaluate")) (if args - `(for* ~(.pop args 0) (for ~args ~@body)) + `(for* [~(.pop args 0) ~(.pop args 0)] + (for ~args ~@body)) `(do ~@body))) @@ -44,9 +57,17 @@ (with* [x foo] (with* [y bar] baz))" - (if args - `(with* ~(.pop args 0) (with ~args ~@body)) - `(do ~@body))) + + (if (not (empty? args)) + (let [[primary (.pop args 0)]] + (if (isinstance primary HyList) + ;;; OK. if we have a list, we can go ahead and unpack that + ;;; as the argument to with. + `(with* [~@primary] (with ~args ~@body)) + ;;; OK, let's just give it away. This may not be something we + ;;; can do, but that's really the programmer's problem. + `(with* [~primary] (with ~args ~@body)))) + `(do ~@body))) (defmacro-alias [car first] [thing] diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py index b191c17..716f2bb 100644 --- a/tests/compilers/test_ast.py +++ b/tests/compilers/test_ast.py @@ -313,7 +313,7 @@ def test_ast_valid_while(): def test_ast_valid_for(): "Make sure AST can compile valid for" - can_compile("(for [[a 2]] (print a))") + can_compile("(for [a 2] (print a))") def test_ast_invalid_for(): @@ -452,14 +452,14 @@ def test_for_compile_error(): assert(False) try: - can_compile("(fn [] (for [[x]]))") + can_compile("(fn [] (for [x]))") except HyTypeError as e: - assert(e.message == "`for' requires a body to evaluate") + assert(e.message == "`for' requires an even number of args.") else: assert(False) try: - can_compile("(fn [] (for [[x xx]]))") + can_compile("(fn [] (for [x xx]))") except HyTypeError as e: assert(e.message == "`for' requires a body to evaluate") else: diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 8bd1776..30a63e4 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -31,12 +31,12 @@ (defn test-for-loop [] "NATIVE: test for loops?" (setv count 0) - (for [[x [1 2 3 4 5]]] + (for [x [1 2 3 4 5]] (setv count (+ count x))) (assert (= count 15)) (setv count 0) - (for [[x [1 2 3 4 5]] - [y [1 2 3 4 5]]] + (for [x [1 2 3 4 5] + y [1 2 3 4 5]] (setv count (+ count x y))) (assert (= count 150))) @@ -404,9 +404,9 @@ (defn test-yield [] "NATIVE: test yielding" - (defn gen [] (for [[x [1 2 3 4]]] (yield x))) + (defn gen [] (for [x [1 2 3 4]] (yield x))) (setv ret 0) - (for [[y (gen)]] (setv ret (+ ret y))) + (for [y (gen)] (setv ret (+ ret y))) (assert (= ret 10))) @@ -463,7 +463,7 @@ (defn test-for-doodle [] "NATIVE: test for-do" (do (do (do (do (do (do (do (do (do (setv (, x y) (, 0 0))))))))))) - (for [[- [1 2]]] + (for [- [1 2]] (do (setv x (+ x 1)) (setv y (+ y 1)))) @@ -646,7 +646,7 @@ (defn test-nested-if [] "NATIVE: test nested if" - (for [[x (range 10)]] + (for [x (range 10)] (if (in "foo" "foobar") (do (if true true true)) @@ -810,14 +810,14 @@ (defn test-break-breaking [] "NATIVE: test checking if break actually breaks" - (defn holy-grail [] (for [[x (range 10)]] (if (= x 5) (break))) x) + (defn holy-grail [] (for [x (range 10)] (if (= x 5) (break))) x) (assert (= (holy-grail) 5))) (defn test-continue-continuation [] "NATIVE: test checking if continue actually continues" (setv y []) - (for [[x (range 10)]] + (for [x (range 10)] (if (!= x 5) (continue)) (.append y x)) From dfbdbdfb73d43c63a6370a1afe2a3e834cbff783 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 31 Dec 2013 14:24:38 -0500 Subject: [PATCH 21/24] We're 0.9.12 --- hy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hy/version.py b/hy/version.py index e96cb4c..2c33563 100644 --- a/hy/version.py +++ b/hy/version.py @@ -20,4 +20,4 @@ __appname__ = "hy" -__version__ = "0.9.11" +__version__ = "0.9.12" From 032200bcb40a8d007bb1764516039f1ca4d06750 Mon Sep 17 00:00:00 2001 From: Bob Tolbert Date: Tue, 31 Dec 2013 16:14:05 -0700 Subject: [PATCH 22/24] Some small doc fixes This cleans up a number of doc warnings, including a bad underline for zero? While there, added a nil? function to match up with the new nil is None. Also un-hid myself from coreteam. --- docs/conf.py | 2 +- docs/index.rst | 20 ++++++++++---------- docs/language/core.rst | 32 +++++++++++++++++++++++++++++++- hy/core/language.hy | 6 +++++- scripts/update_coreteam.py | 1 - tests/native_tests/core.hy | 9 +++++++++ 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6b45309..1d5b516 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ release = hy.__version__ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ['_build', 'coreteam.rst'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None diff --git a/docs/index.rst b/docs/index.rst index a8ad82f..2f568c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,17 +19,17 @@ Meet our mascot, "Cuddles": .. image:: http://fc07.deviantart.net/fs70/i/2013/138/f/0/cuddles_the_hacker_by_doctormo-d65l7lq.png :alt: Paul riding cuddles into the distance - -.. Our old ascii art mascot version -.. Retained as an easter egg for those who read the docs via .rst! .. -.. LET'S CUDDLEFISH -.. ______ -.. _.----'#' # ' -.. ,' #' ,# ; -.. (' (w) _,-'_/ -.. /// / /'.____.' -.. \|\||/ + Our old ascii art mascot version + Retained as an easter egg for those who read the docs via .rst! + + LET'S CUDDLEFISH + ______ + _.----'#' # ' + ,' #' ,# ; + (' (w) _,-'_/ + /// / /'.____.' + \|\||/ You can try Hy `in your browser `_. diff --git a/docs/language/core.rst b/docs/language/core.rst index 9b24747..300da15 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -242,6 +242,36 @@ Raises ``TypeError`` if ``(not (numeric? x))``. => (neg? 0) False + +.. _nil?-fn: + +nil? +----- + +Usage: ``(nil? x)`` + +Return True if x is nil/None. + +.. code-block:: clojure + + => (nil? nil) + True + + => (nil? None) + True + + => (nil? 0) + False + + => (setf x nil) + => (nil? x) + True + + => ;; list.append always returns None + => (nil? (.append [1 2 3] 4)) + True + + .. _none?-fn: none? @@ -397,7 +427,7 @@ Return True if x is a string. .. _zero?-fn: zero? ----- +----- Usage: ``(zero? x)`` diff --git a/hy/core/language.hy b/hy/core/language.hy index 9ac2fee..5c69071 100644 --- a/hy/core/language.hy +++ b/hy/core/language.hy @@ -160,6 +160,10 @@ "Return true if x is None" (is x None)) +(defn nil? [x] + "Return true if x is nil (None)" + (is x None)) + (defn numeric? [x] (import numbers) (instance? numbers.Number x)) @@ -253,5 +257,5 @@ (def *exports* '[cycle dec distinct drop drop-while empty? even? filter flatten float? gensym inc instance? integer integer? iterable? iterate iterator? neg? - none? nth numeric? odd? pos? remove repeat repeatedly second + nil? none? nth numeric? odd? pos? remove repeat repeatedly second string string? take take-nth take-while zero?]) diff --git a/scripts/update_coreteam.py b/scripts/update_coreteam.py index dda9754..90f2d86 100644 --- a/scripts/update_coreteam.py +++ b/scripts/update_coreteam.py @@ -19,7 +19,6 @@ MISSING_NAMES = { # an owner of the organization. CONCEALED_MEMBERS = [ ('aldeka', 'Karen Rustad'), - ('rwtolbert', 'Bob Tolbert'), ('tuturto', 'Tuukka Turto'), ] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index aedf99c..1427187 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -293,6 +293,15 @@ (assert-false (none? 0)) (assert-false (none? ""))) +(defn test-nil? [] + "NATIVE: testing for `is nil`" + (assert-true (nil? nil)) + (assert-true (nil? None)) + (setv f nil) + (assert-true (nil? f)) + (assert-false (nil? 0)) + (assert-false (nil? ""))) + (defn test-nth [] "NATIVE: testing the nth function" (assert-equal 2 (nth [1 2 4 7] 1)) From 553337080a4268f2b75d6f03c20807c737a5e006 Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 12:38:44 -0500 Subject: [PATCH 23/24] Adding missing AUTHORs from the git log. Sorry, folks. --- AUTHORS | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 63e699a..3e28c80 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,4 +21,18 @@ * Henrique Carvalho Alves * Joe Hakim Rahme * Kenan Bölükbaşı -* Abhishek L \ No newline at end of file +* Abhishek L +* Christopher Browne +* Clinton N. Dreisbach +* D. Joe +* Duncan McGreggor +* E. Anders Lannerback +* Foxboron +* Jack +* Johan Euphrosine +* Kevin Zita +* Matt Fenwick +* Sean B. Palmer +* Thom Neale +* Tuukka Turto +* Vasudev Kamath From 8adae9a01f0acaf96df0da6d770eaec56cab8fcd Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Wed, 1 Jan 2014 12:41:22 -0500 Subject: [PATCH 24/24] No fox for you. --- AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 3e28c80..09c2e2e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -27,7 +27,6 @@ * D. Joe * Duncan McGreggor * E. Anders Lannerback -* Foxboron * Jack * Johan Euphrosine * Kevin Zita