diff --git a/AUTHORS b/AUTHORS index 09c2e2e..775db60 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,3 +35,5 @@ * Thom Neale * Tuukka Turto * Vasudev Kamath +* Yuval Langer +* Fatih Kadir Akın diff --git a/bin/hy2py b/bin/hy2py index 3af8a62..3bcade4 100755 --- a/bin/hy2py +++ b/bin/hy2py @@ -1,24 +1,39 @@ #!/usr/bin/env python from __future__ import print_function +from hy.importer import (import_file_to_ast, import_file_to_hst) -from hy.importer import (import_file_to_ast, import_file_to_module, - import_file_to_hst) +import argparse +import sys import astor.codegen -import sys module_name = "" -hst = import_file_to_hst(sys.argv[1]) -print(str(hst).encode("utf-8")) -print("") -print("") -_ast = import_file_to_ast(sys.argv[1], module_name) -print("") -print("") -print(astor.dump(_ast).encode("utf-8")) -print("") -print("") -print(astor.codegen.to_source(_ast).encode("utf-8")) +parser = argparse.ArgumentParser( + prog="hy2py", + usage="%(prog)s [options] FILE", + formatter_class=argparse.RawDescriptionHelpFormatter) +parser.add_argument("--with-source", "-s", action="store_true", + help="Show the parsed source structure") +parser.add_argument("--with-ast", "-a", action="store_true", + help="Show the generated AST") +parser.add_argument("--without-python", "-np", action="store_true", + help="Do not show the python code generated from the AST") +parser.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) -import_file_to_module(module_name, sys.argv[1]) +options = parser.parse_args(sys.argv[1:]) + +if options.with_source: + hst = import_file_to_hst(options.args[0]) + print(str(hst).encode("utf-8")) + print() + print() + +_ast = import_file_to_ast(options.args[0], module_name) +if options.with_ast: + print(astor.dump(_ast).encode("utf-8")) + print() + print() + +if not options.without_python: + print(astor.codegen.to_source(_ast).encode("utf-8")) diff --git a/docs/_static/cuddles-transparent-small.png b/docs/_static/cuddles-transparent-small.png new file mode 100644 index 0000000..1570db6 Binary files /dev/null and b/docs/_static/cuddles-transparent-small.png differ diff --git a/docs/_static/cuddles-transparent.png b/docs/_static/cuddles-transparent.png new file mode 100644 index 0000000..8f16ebd Binary files /dev/null and b/docs/_static/cuddles-transparent.png differ diff --git a/docs/hacking.rst b/docs/hacking.rst index 0a35898..4329c68 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -97,7 +97,7 @@ core team. Additional review is clearly welcome, but we need a minimum of 2 signoffs for any change. 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 +include the 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 diff --git a/docs/language/api.rst b/docs/language/api.rst index 7e06579..a7b35a9 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -36,7 +36,7 @@ languages. Builtins ======== -Hy features a number special forms that are used to help generate +Hy features a number of special forms that are used to help generate correct Python AST. The following are "special" forms, which may have behavior that's slightly unexpected in some situations. @@ -89,7 +89,7 @@ The following code demonstrates this: --- `->>` or `threading tail macro` is similar to `threading macro` but instead of -inserting each expression into the next expression’s first argument place it +inserting each expression into the next expression’s first argument place, it appends it as the last argument. The following code demonstrates this: .. code-block:: clj @@ -283,7 +283,7 @@ do / progn the `do` and `progn` forms are used to evaluate each of their arguments and return the last one. Return values from every other than the last argument are discarded. It can be used in `lambda` or `list-comp` to perform more complex -logic as show by one of the examples. +logic as shown by one of the examples. Some example usage: @@ -430,7 +430,7 @@ defmacro `defmacro` is used to define macros. The general format is `(defmacro [parameters] expr)`. -Following example defines a macro that can be used to swap order of elements in +The following example defines a macro that can be used to swap order of elements in code, allowing the user to write code in infix notation, where operator is in between the operands. @@ -1169,7 +1169,7 @@ yield The generator is iterable and therefore can be used in loops, list comprehensions and other similar constructs. -Especially the second example shows how generators can be used to generate +The function random-numbers shows how generators can be used to generate infinite series without consuming infinite amount of memory. .. code-block:: clj diff --git a/docs/language/core.rst b/docs/language/core.rst index deaf16b..1b31dc7 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -9,7 +9,7 @@ Core Functions .. _is-coll-fn: coll? ----- +----- .. versionadded:: 0.9.13 diff --git a/docs/language/readermacros.rst b/docs/language/readermacros.rst index 0c05d9c..392feb6 100644 --- a/docs/language/readermacros.rst +++ b/docs/language/readermacros.rst @@ -24,19 +24,28 @@ Syntax => #^1+2+3+4+3+2 1+2+3+4+3+2 +Hy has no literal for tuples. Lets say you dislike `(, ...)` and want something +else. This is a problem reader macros are able to solve in a neat way. + +:: + + => (defreader t [expr] `(, ~@expr)) + => #t(1 2 3) + (1, 2, 3) + +You could even do like clojure, and have a literal for regular expressions! + +:: + + => (import re) + => (defreader r [expr] `(re.compile ~expr)) + => #r".*" + <_sre.SRE_Pattern object at 0xcv7713ph15#> + 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 @@ -47,14 +56,17 @@ 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. +``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol +and expression is passed to the correct function. :: + => #^() + ;=> (dispatch_reader_macro ^ ()) => #^"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 diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5bdb130..79a9255 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -2,20 +2,20 @@ Quickstart ========== -.. image:: _static/cuddles.png +.. image:: _static/cuddles-transparent-small.png :alt: Karen Rustard's Cuddles -(thanks to Karen Rustad for Cuddles!) +(Thanks to Karen Rustad for Cuddles!) -HOW TO GET HY REAL FAST: +**HOW TO GET HY REAL FAST**: -1. create a `Python virtual environment +1. Create a `Virtual Python Environment `_ -2. activate your Python virtual environment -3. ``pip install hy`` -4. start a REPL with ``hy`` -5. type stuff in the REPL:: +2. Activate your Virtual Python Environment +3. Install `hy from PyPI `_ with ``pip install hy`` +4. Start a REPL with ``hy`` +5. Type stuff in the REPL:: => (print "Hy!") Hy! @@ -25,20 +25,19 @@ HOW TO GET HY REAL FAST: etc -6. hit CTRL-D when you're done +6. Hit CTRL-D when you're done OMG! That's amazing! I want to write a hy program. -7. open up an elite programming editor -8. type:: +7. Open up an elite programming editor and type:: - (print "i was going to code in python syntax, but then i got hy") + (print "I was going to code in python syntax, but then I got hy.") -9. save as ``test_program_of_awesome.hy`` -10. run:: +8. Save as ``awesome.hy`` +9. And run your first Hy program:: - hy test_program_of_awesome.hy + hy awesome.hy -11. take a deep breath so as to not hyperventilate -12. smile villainously and sneak off to your hydeaway and do +10. Take a deep breath so as to not hyperventilate +11. Smile villainously and sneak off to your hydeaway and do unspeakable things diff --git a/docs/tutorial.rst b/docs/tutorial.rst index c856cf7..819b7cd 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -406,7 +406,7 @@ The same thing in Hy:: ... [3, 2, 1, 4] -See how we use kwapply to handle the fancy pssing? :) +See how we use kwapply to handle the fancy passing? :) There's also a dictionary-style keyword arguments construction that looks like: diff --git a/eg/flask/meth_example.hy b/eg/flask/meth_example.hy new file mode 100644 index 0000000..4df70d7 --- /dev/null +++ b/eg/flask/meth_example.hy @@ -0,0 +1,27 @@ +;;; Simple Flask application +;;; +;;; Requires to have Flask installed +;;; +;;; You can test it via: +;;; +;;; $ curl 127.0.0.1:5151 +;;; $ curl -X POST 127.0.0.1:5151/post +;;; $ curl -X POST 127.0.0.1:5151/both +;;; $ curl 127.0.0.1:5151/both + +(import [flask [Flask]]) + +(require hy.contrib.meth) + +(setv app (Flask "__main__")) + +(route get-index "/" [] + (str "Hy world!")) + +(post-route post-index "/post" [] + (str "Hy post world!")) + +(route-with-methods both-index "/both" ["GET" "POST"] [] + (str "Hy to both worlds!")) + +(apply app.run [] {"port" 5151}) diff --git a/hy/_compat.py b/hy/_compat.py index 37bb023..096282a 100644 --- a/hy/_compat.py +++ b/hy/_compat.py @@ -38,6 +38,7 @@ except ImportError: (x >> 24) & 0xff])) import sys +PY27 = sys.version_info >= (2, 7) PY3 = sys.version_info[0] >= 3 PY33 = sys.version_info >= (3, 3) PY34 = sys.version_info >= (3, 4) diff --git a/hy/cmdline.py b/hy/cmdline.py index b09595f..98f10cd 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -214,8 +214,8 @@ def run_repl(hr=None, spy=False): return 0 -def run_icommand(source): - hr = HyREPL() +def run_icommand(source, spy=False): + hr = HyREPL(spy) hr.runsource(source, filename='', symbol='single') return run_repl(hr) @@ -270,7 +270,7 @@ def cmdline_handler(scriptname, argv): if options.icommand: # User did "hy -i ..." - return run_icommand(options.icommand) + return run_icommand(options.icommand, spy=options.spy) if options.args: if options.args[0] == "-": diff --git a/hy/compiler.py b/hy/compiler.py index 6e65b30..93e6526 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -38,8 +38,8 @@ from hy.models.dict import HyDict from hy.errors import HyCompileError, HyTypeError import hy.macros -from hy.macros import require, macroexpand -from hy._compat import str_type, long_type, PY33, PY3, PY34 +from hy._compat import str_type, long_type, PY27, PY33, PY3, PY34 +from hy.macros import require, macroexpand, reader_macroexpand import hy.importer import traceback @@ -1265,41 +1265,114 @@ class HyASTCompiler(object): ctx=ast.Load()) return ret + def _compile_generator_iterables(self, trailers): + """Helper to compile the "trailing" parts of comprehensions: + generators and conditions""" + + generators = trailers.pop(0) + + cond = self.compile(trailers.pop(0)) if trailers != [] else Result() + + gen_it = iter(generators) + paired_gens = zip(gen_it, gen_it) + + gen_res = Result() + gen = [] + for target, iterable in paired_gens: + comp_target = self.compile(target) + target = self._storeize(comp_target) + gen_res += self.compile(iterable) + gen.append(ast.comprehension( + target=target, + iter=gen_res.force_expr, + ifs=[])) + + if cond.expr: + gen[-1].ifs.append(cond.expr) + + return gen_res + cond, gen + @builds("list_comp") @checkargs(min=2, max=3) def compile_list_comprehension(self, expr): # (list-comp expr (target iter) cond?) expr.pop(0) expression = expr.pop(0) - tar_it = iter(expr.pop(0)) - targets = zip(tar_it, tar_it) - cond = self.compile(expr.pop(0)) if expr != [] else Result() - - generator_res = Result() - generators = [] - for target, iterable in targets: - comp_target = self.compile(target) - target = self._storeize(comp_target) - generator_res += self.compile(iterable) - generators.append(ast.comprehension( - target=target, - iter=generator_res.force_expr, - ifs=[])) - - if cond.expr: - generators[-1].ifs.append(cond.expr) + gen_res, gen = self._compile_generator_iterables(expr) compiled_expression = self.compile(expression) - ret = compiled_expression + generator_res + cond + ret = compiled_expression + gen_res ret += ast.ListComp( lineno=expr.start_line, col_offset=expr.start_column, elt=compiled_expression.force_expr, - generators=generators) + generators=gen) return ret + @builds("set_comp") + @checkargs(min=2, max=3) + def compile_set_comprehension(self, expr): + if PY27: + ret = self.compile_list_comprehension(expr) + expr = ret.expr + ret.expr = ast.SetComp( + lineno=expr.lineno, + col_offset=expr.col_offset, + elt=expr.elt, + generators=expr.generators) + + return ret + + expr[0] = HySymbol("list_comp").replace(expr[0]) + expr = HyExpression([HySymbol("set"), expr]).replace(expr) + return self.compile(expr) + + @builds("dict_comp") + @checkargs(min=3, max=4) + def compile_dict_comprehension(self, expr): + if PY27: + expr.pop(0) # dict-comp + key = expr.pop(0) + value = expr.pop(0) + + gen_res, gen = self._compile_generator_iterables(expr) + + compiled_key = self.compile(key) + compiled_value = self.compile(value) + ret = compiled_key + compiled_value + gen_res + ret += ast.DictComp( + lineno=expr.start_line, + col_offset=expr.start_column, + key=compiled_key.force_expr, + value=compiled_value.force_expr, + generators=gen) + + return ret + + # In Python 2.6, turn (dict-comp key value [foo]) into + # (dict (list-comp (, key value) [foo])) + + expr[0] = HySymbol("list_comp").replace(expr[0]) + expr[1:3] = [HyExpression( + [HySymbol(",")] + + expr[1:3] + ).replace(expr[1])] + expr = HyExpression([HySymbol("dict"), expr]).replace(expr) + return self.compile(expr) + + @builds("genexpr") + def compile_genexpr(self, expr): + ret = self.compile_list_comprehension(expr) + expr = ret.expr + ret.expr = ast.GeneratorExp( + lineno=expr.lineno, + col_offset=expr.col_offset, + elt=expr.elt, + generators=expr.generators) + return ret + @builds("apply") @checkargs(min=1, max=3) def compile_apply_expression(self, expr): @@ -1907,7 +1980,7 @@ class HyASTCompiler(object): return ret @builds("defreader") - @checkargs(min=2, max=3) + @checkargs(min=2) def compile_reader(self, expression): expression.pop(0) name = expression.pop(0) @@ -1929,6 +2002,23 @@ class HyASTCompiler(object): return ret + @builds("dispatch_reader_macro") + @checkargs(exact=2) + def compile_dispatch_reader_macro(self, expression): + expression.pop(0) # dispatch-reader-macro + str_char = expression.pop(0) + if not type(str_char) == HyString: + raise HyTypeError( + str_char, + "Trying to expand a reader macro using `{0}' instead " + "of string".format(type(str_char).__name__), + ) + + module = self.module_name + expr = reader_macroexpand(str_char, expression.pop(0), module) + + return self.compile(expr) + @builds("eval_and_compile") def compile_eval_and_compile(self, expression): expression[0] = HySymbol("progn") diff --git a/hy/contrib/loop.hy b/hy/contrib/loop.hy index 73526fa..2c2690a 100644 --- a/hy/contrib/loop.hy +++ b/hy/contrib/loop.hy @@ -1,6 +1,7 @@ ;;; Hy tail-call optimization ;; ;; Copyright (c) 2014 Clinton Dreisbach +;; Copyright (c) 2014 Paul R. Tagliamonte ;; ;; Permission is hereby granted, free of charge, to any person obtaining a ;; copy of this software and associated documentation files (the "Software"), @@ -55,7 +56,24 @@ (recursive-replace old-term new-term term)] [True term]) [term body]))) -(defmacro loop [bindings &rest body] + +(defmacro/g! fnr [signature &rest body] + (let [[new-body (recursive-replace 'recur g!recur-fn body)]] + `(do + (import [hy.contrib.loop [--trampoline--]]) + (with-decorator + --trampoline-- + (def ~g!recur-fn (fn [~@signature] ~@new-body))) + ~g!recur-fn))) + + +(defmacro defnr [name lambda-list &rest body] + (if (not (= (type name) HySymbol)) + (macro-error name "defnr takes a name as first argument")) + `(setv ~name (fnr ~lambda-list ~@body))) + + +(defmacro/g! loop [bindings &rest body] ;; Use inside functions like so: ;; (defun factorial [n] ;; (loop [[i n] @@ -67,13 +85,7 @@ ;; If recur is used in a non-tail-call position, None is returned, which ;; causes chaos. Fixing this to detect if recur is in a tail-call position ;; and erroring if not is a giant TODO. - (with-gensyms [recur-fn] - (let [[fnargs (map (fn [x] (first x)) bindings)] - [initargs (map second bindings)] - [new-body (recursive-replace 'recur recur-fn body)]] - `(do - (import [hy.contrib.loop [--trampoline--]]) - (def ~recur-fn - (--trampoline-- (fn [~@fnargs] - ~@new-body))) - (~recur-fn ~@initargs))))) + (let [[fnargs (map (fn [x] (first x)) bindings)] + [initargs (map second bindings)]] + `(do (defnr ~g!recur-fn [~@fnargs] ~@body) + (~g!recur-fn ~@initargs)))) diff --git a/hy/contrib/meth.hy b/hy/contrib/meth.hy index 18203ea..11e637c 100644 --- a/hy/contrib/meth.hy +++ b/hy/contrib/meth.hy @@ -1,5 +1,5 @@ -;;; Meth -;; based on paultag's meth library to access a Flask based application +;;; Hy on Meth +;;; based on paultag's meth library to access a Flask based application (defmacro route-with-methods [name path methods params &rest code] "Same as route but with an extra methods array to specify HTTP methods" @@ -25,29 +25,3 @@ (defmacro delete-route [name path params &rest code] "Delete request" `(route-with-methods ~name ~path ["DELETE"] ~params ~@code)) - - -;;; Simple example application -;;; Requires to have Flask installed - -;; (import [flask [Flask]]) -;; (setv app (Flask "__main__")) - -;; (require hy.contrib.meth) - -;; (print "setup / with GET") -;; (route get-index "/" [] (str "Hy world!")) - -;; (print "setup /post with POST") -;; (post-route post-index "/post" [] (str "Hy post world!")) - -;; (route-with-methods both-index "/both" [] -;; (str "Hy to both worlds!") ["GET" "POST"]) - -;; (.run app) - -;;; Now you can do: -;;; curl 127.0.0.1:5000 -;;; curl -X POST 127.0.0.1:5000/post -;;; curl -X POST 127.0.0.1:5000/both -;;; curl 127.0.0.1:5000/both diff --git a/hy/core/macros.hy b/hy/core/macros.hy index 79d7c12..9219975 100644 --- a/hy/core/macros.hy +++ b/hy/core/macros.hy @@ -181,13 +181,3 @@ (setv -args (cdr (car -args)))) `(apply ~-fun [~@-args] (dict (sum ~-okwargs []))))) - - -(defmacro dispatch-reader-macro [char &rest body] - "Dispatch a reader macro based on the character" - (import [hy.macros]) - (setv str_char (get char 1)) - (if (not (in str_char hy.macros._hy_reader_chars)) - (raise (hy.compiler.HyTypeError char (.format "There is no reader macro with the character `{0}`" str_char)))) - `(do (import [hy.macros [_hy_reader]]) - ((get (get _hy_reader --name--) ~char) ~(get body 0)))) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 72f0d8f..2d47201 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -154,8 +154,8 @@ def term_unquote_splice(p): @set_quote_boundaries def hash_reader(p): st = p[0].getstr()[1] - str_object = HyExpression([HySymbol("quote"), HyString(st)]) - expr = HyExpression([HySymbol("quote"), p[1]]) + str_object = HyString(st) + expr = p[1] return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr]) @@ -242,14 +242,19 @@ def t_identifier(p): if obj.startswith("&"): return HyLambdaListKeyword(obj) - if obj.startswith("*") and obj.endswith("*") and obj not in ("*", "**"): - obj = obj[1:-1].upper() + def mangle(p): + if p.startswith("*") and p.endswith("*") and p not in ("*", "**"): + p = p[1:-1].upper() - if "-" in obj and obj != "-": - obj = obj.replace("-", "_") + if "-" in p and p != "-": + p = p.replace("-", "_") - if obj.endswith("?") and obj != "?": - obj = "is_%s" % (obj[:-1]) + if p.endswith("?") and p != "?": + p = "is_%s" % (p[:-1]) + + return p + + obj = ".".join([mangle(part) for part in obj.split(".")]) return HySymbol(obj) diff --git a/hy/macros.py b/hy/macros.py index d98b328..b155799 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -43,7 +43,6 @@ EXTRA_MACROS = [ _hy_macros = defaultdict(dict) _hy_reader = defaultdict(dict) -_hy_reader_chars = set() def macro(name): @@ -85,8 +84,6 @@ def reader(name): module_name = None _hy_reader[module_name][name] = fn - # Ugly hack to get some error handling - _hy_reader_chars.add(name) return fn return _ @@ -209,3 +206,20 @@ def macroexpand_1(tree, module_name): return ntree return tree + + +def reader_macroexpand(char, tree, module_name): + """Expand the reader macro "char" with argument `tree`.""" + load_macros(module_name) + + if not char in _hy_reader[module_name]: + raise HyTypeError( + char, + "`{0}' is not a reader macro in module '{1}'".format( + char, + module_name, + ), + ) + + expr = _hy_reader[module_name][char](tree) + return _wrap_value(expr).replace(tree) diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index af9f286..b937aa7 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -258,7 +258,7 @@ def test_reader_macro(): """Ensure reader macros are handles properly""" entry = tokenize("#^()") assert entry[0][0] == HySymbol("dispatch_reader_macro") - assert entry[0][1] == HyExpression([HySymbol("quote"), HyString("^")]) + assert entry[0][1] == HyString("^") assert len(entry[0]) == 3 @@ -266,3 +266,39 @@ def test_lex_comment_382(): """Ensure that we can tokenize sources with a comment at the end""" entry = tokenize("foo ;bar\n;baz") assert entry == [HySymbol("foo")] + + +def test_lex_mangling_star(): + """Ensure that mangling starred identifiers works according to plan""" + entry = tokenize("*foo*") + assert entry == [HySymbol("FOO")] + entry = tokenize("*") + assert entry == [HySymbol("*")] + entry = tokenize("*foo") + assert entry == [HySymbol("*foo")] + + +def test_lex_mangling_hyphen(): + """Ensure that hyphens get translated to underscores during mangling""" + entry = tokenize("foo-bar") + assert entry == [HySymbol("foo_bar")] + entry = tokenize("-") + assert entry == [HySymbol("-")] + + +def test_lex_mangling_qmark(): + """Ensure that identifiers ending with a question mark get mangled ok""" + entry = tokenize("foo?") + assert entry == [HySymbol("is_foo")] + entry = tokenize("?") + assert entry == [HySymbol("?")] + entry = tokenize("im?foo") + assert entry == [HySymbol("im?foo")] + entry = tokenize(".foo?") + assert entry == [HySymbol(".is_foo")] + entry = tokenize("foo.bar?") + assert entry == [HySymbol("foo.is_bar")] + entry = tokenize("foo?.bar") + assert entry == [HySymbol("is_foo.bar")] + entry = tokenize(".foo?.bar.baz?") + assert entry == [HySymbol(".is_foo.bar.is_baz")] diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy index 11bf01a..cb75e66 100644 --- a/tests/native_tests/language.hy +++ b/tests/native_tests/language.hy @@ -525,7 +525,7 @@ (assert (= x 3)))) -(defn test-comprehensions [] +(defn test-list-comprehensions [] "NATIVE: test list comprehensions" (assert (= (list-comp (* x 2) (x (range 2))) [0 2])) (assert (= (list-comp (* x 2) (x (range 4)) (% x 2)) [2 6])) @@ -536,6 +536,41 @@ (assert (= (list-comp j (j [1 2])) [1 2]))) +(defn test-set-comprehensions [] + "NATIVE: test set comprehensions" + (assert (instance? set (set-comp x [x (range 2)]))) + (assert (= (set-comp (* x 2) (x (range 2))) (set [0 2]))) + (assert (= (set-comp (* x 2) (x (range 4)) (% x 2)) (set [2 6]))) + (assert (= (set-comp (* y 2) ((, x y) (.items {"1" 1 "2" 2}))) + (set [2 4]))) + (assert (= (set-comp (, x y) (x (range 2) y (range 2))) + (set [(, 0 0) (, 0 1) (, 1 0) (, 1 1)]))) + (assert (= (set-comp j (j [1 2])) (set [1 2])))) + + +(defn test-dict-comprehensions [] + "NATIVE: test dict comprehensions" + (assert (instance? dict (dict-comp x x [x (range 2)]))) + (assert (= (dict-comp x (* x 2) (x (range 2))) {1 2 0 0})) + (assert (= (dict-comp x (* x 2) (x (range 4)) (% x 2)) {3 6 1 2})) + (assert (= (dict-comp x (* y 2) ((, x y) (.items {"1" 1 "2" 2}))) + {"2" 4 "1" 2})) + (assert (= (dict-comp (, x y) (+ x y) (x (range 2) y (range 2))) + {(, 0 0) 0 (, 1 0) 1 (, 0 1) 1 (, 1 1) 2}))) + + +(defn test-generator-expressions [] + "NATIVE: test generator expressions" + (assert (not (instance? list (genexpr x [x (range 2)])))) + (assert (= (list (genexpr (* x 2) (x (range 2)))) [0 2])) + (assert (= (list (genexpr (* x 2) (x (range 4)) (% x 2))) [2 6])) + (assert (= (list (sorted (genexpr (* y 2) ((, x y) (.items {"1" 1 "2" 2}))))) + [2 4])) + (assert (= (list (genexpr (, x y) (x (range 2) y (range 2)))) + [(, 0 0) (, 0 1) (, 1 0) (, 1 1)])) + (assert (= (list (genexpr j (j [1 2]))) [1 2]))) + + (defn test-defn-order [] "NATIVE: test defn evaluation order" (setv acc []) diff --git a/tests/native_tests/reader_macros.hy b/tests/native_tests/reader_macros.hy index e43220b..84a48a5 100644 --- a/tests/native_tests/reader_macros.hy +++ b/tests/native_tests/reader_macros.hy @@ -23,10 +23,14 @@ (assert (= #+2 3))) -(defn test-reader-macro-compile-docstring [] - "Test if we can compile with a docstring" - (try - (defreader d [] - "Compiles with docstrings") - (except [Exception] - (assert False)))) +(defn test-reader-macros-macros [] + "Test if defreader is actually a macro" + (defreader t [expr] + `(, ~@expr)) + + (def a #t[1 2 3]) + + (assert (= (type a) tuple)) + (assert (= (, 1 2 3) a))) + + diff --git a/tests/test_bin.py b/tests/test_bin.py index d18dd31..a6087bd 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -76,6 +76,14 @@ def test_bin_hy_icmd(): assert "figlet" in output +def test_bin_hy_icmd_and_spy(): + ret = run_cmd("hy -i \"(+ [] [])\" --spy", "(+ 1 1)") + assert ret[0] == 0 + output = ret[1] + + assert "([] + [])" in output + + def test_bin_hy_missing_file(): ret = run_cmd("hy foobarbaz") assert ret[0] == 2 @@ -126,7 +134,7 @@ def test_hy2py(): for f in filenames: if f.endswith(".hy"): i += 1 - ret = run_cmd("bin/hy2py " + os.path.join(dirpath, f)) + ret = run_cmd("bin/hy2py -s -a " + os.path.join(dirpath, f)) assert ret[0] == 0, f assert len(ret[1]) > 1, f assert len(ret[2]) == 0, f