Merge branch 'master' into pr/455

This commit is contained in:
Nicolas Dandrimont 2014-01-17 20:35:44 +01:00
commit 8447a9dfdb
17 changed files with 220 additions and 104 deletions

View File

@ -1,24 +1,39 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import print_function 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 argparse
import_file_to_hst) import sys
import astor.codegen import astor.codegen
import sys
module_name = "<STDIN>" module_name = "<STDIN>"
hst = import_file_to_hst(sys.argv[1]) parser = argparse.ArgumentParser(
print(str(hst).encode("utf-8")) prog="hy2py",
print("") usage="%(prog)s [options] FILE",
print("") formatter_class=argparse.RawDescriptionHelpFormatter)
_ast = import_file_to_ast(sys.argv[1], module_name) parser.add_argument("--with-source", "-s", action="store_true",
print("") help="Show the parsed source structure")
print("") parser.add_argument("--with-ast", "-a", action="store_true",
print(astor.dump(_ast).encode("utf-8")) help="Show the generated AST")
print("") parser.add_argument("--without-python", "-np", action="store_true",
print("") help="Do not show the python code generated from the AST")
print(astor.codegen.to_source(_ast).encode("utf-8")) 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"))

View File

@ -574,8 +574,10 @@ for
------- -------
`for` 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 The results of each call are discarded and the for expression returns
collection and calls side-effect to each element in the collection: None instead. The example code iterates over `collection` and
for each `element` in `collection` calls the `side-effect`
function with `element` as its argument:
.. code-block:: clj .. code-block:: clj

View File

@ -24,19 +24,28 @@ Syntax
=> #^1+2+3+4+3+2 => #^1+2+3+4+3+2
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 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, ``defreader`` takes a single character as symbol name for the reader macro,
anything longer will return an error. Implementation wise, ``defreader`` anything longer will return an error. Implementation wise, ``defreader``
expands into a lambda covered with a decorator, this decorater saves the 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)) => (defreader ^ [expr] (print expr))
;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr))) ;=> (with_decorator (hy.macros.reader ^) (fn [expr] (print expr)))
``#`` expands into ``(dispatch_reader_macro ...)`` where the symbol
Anything passed along is quoted, thus given to the function defined. and expression is passed to the correct function.
:: ::
=> #^()
;=> (dispatch_reader_macro ^ ())
=> #^"Hello" => #^"Hello"
"Hello" "Hello"
.. warning:: .. warning::
Because of a limitation in Hy's lexer and parser, reader macros can't Because of a limitation in Hy's lexer and parser, reader macros can't
redefine defined syntax such as ``()[]{}``. This will most likely be redefine defined syntax such as ``()[]{}``. This will most likely be

27
eg/flask/meth_example.hy Normal file
View File

@ -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})

View File

@ -214,8 +214,8 @@ def run_repl(hr=None, spy=False):
return 0 return 0
def run_icommand(source): def run_icommand(source, spy=False):
hr = HyREPL() hr = HyREPL(spy)
hr.runsource(source, filename='<input>', symbol='single') hr.runsource(source, filename='<input>', symbol='single')
return run_repl(hr) return run_repl(hr)
@ -270,7 +270,7 @@ def cmdline_handler(scriptname, argv):
if options.icommand: if options.icommand:
# User did "hy -i ..." # User did "hy -i ..."
return run_icommand(options.icommand) return run_icommand(options.icommand, spy=options.spy)
if options.args: if options.args:
if options.args[0] == "-": if options.args[0] == "-":

View File

@ -38,8 +38,8 @@ from hy.models.dict import HyDict
from hy.errors import HyCompileError, HyTypeError from hy.errors import HyCompileError, HyTypeError
import hy.macros import hy.macros
from hy.macros import require, macroexpand
from hy._compat import str_type, long_type, PY27, 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 hy.importer
import traceback import traceback
@ -1980,7 +1980,7 @@ class HyASTCompiler(object):
return ret return ret
@builds("defreader") @builds("defreader")
@checkargs(min=2, max=3) @checkargs(min=2)
def compile_reader(self, expression): def compile_reader(self, expression):
expression.pop(0) expression.pop(0)
name = expression.pop(0) name = expression.pop(0)
@ -2002,6 +2002,23 @@ class HyASTCompiler(object):
return ret 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") @builds("eval_and_compile")
def compile_eval_and_compile(self, expression): def compile_eval_and_compile(self, expression):
expression[0] = HySymbol("progn") expression[0] = HySymbol("progn")

View File

@ -1,6 +1,7 @@
;;; Hy tail-call optimization ;;; Hy tail-call optimization
;; ;;
;; Copyright (c) 2014 Clinton Dreisbach <clinton@dreisbach.us> ;; Copyright (c) 2014 Clinton Dreisbach <clinton@dreisbach.us>
;; Copyright (c) 2014 Paul R. Tagliamonte <tag@pault.ag>
;; ;;
;; Permission is hereby granted, free of charge, to any person obtaining a ;; Permission is hereby granted, free of charge, to any person obtaining a
;; copy of this software and associated documentation files (the "Software"), ;; copy of this software and associated documentation files (the "Software"),
@ -55,7 +56,24 @@
(recursive-replace old-term new-term term)] (recursive-replace old-term new-term term)]
[True term]) [term body]))) [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: ;; Use inside functions like so:
;; (defun factorial [n] ;; (defun factorial [n]
;; (loop [[i n] ;; (loop [[i n]
@ -67,13 +85,7 @@
;; If recur is used in a non-tail-call position, None is returned, which ;; 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 ;; causes chaos. Fixing this to detect if recur is in a tail-call position
;; and erroring if not is a giant TODO. ;; and erroring if not is a giant TODO.
(with-gensyms [recur-fn] (let [[fnargs (map (fn [x] (first x)) bindings)]
(let [[fnargs (map (fn [x] (first x)) bindings)] [initargs (map second bindings)]]
[initargs (map second bindings)] `(do (defnr ~g!recur-fn [~@fnargs] ~@body)
[new-body (recursive-replace 'recur recur-fn body)]] (~g!recur-fn ~@initargs))))
`(do
(import [hy.contrib.loop [--trampoline--]])
(def ~recur-fn
(--trampoline-- (fn [~@fnargs]
~@new-body)))
(~recur-fn ~@initargs)))))

View File

@ -1,5 +1,5 @@
;;; Meth ;;; Hy on Meth
;; based on paultag's meth library to access a Flask based application ;;; based on paultag's meth library to access a Flask based application
(defmacro route-with-methods [name path methods params &rest code] (defmacro route-with-methods [name path methods params &rest code]
"Same as route but with an extra methods array to specify HTTP methods" "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] (defmacro delete-route [name path params &rest code]
"Delete request" "Delete request"
`(route-with-methods ~name ~path ["DELETE"] ~params ~@code)) `(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

View File

@ -181,13 +181,3 @@
(setv -args (cdr (car -args)))) (setv -args (cdr (car -args))))
`(apply ~-fun [~@-args] (dict (sum ~-okwargs []))))) `(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))))

View File

@ -154,8 +154,8 @@ def term_unquote_splice(p):
@set_quote_boundaries @set_quote_boundaries
def hash_reader(p): def hash_reader(p):
st = p[0].getstr()[1] st = p[0].getstr()[1]
str_object = HyExpression([HySymbol("quote"), HyString(st)]) str_object = HyString(st)
expr = HyExpression([HySymbol("quote"), p[1]]) expr = p[1]
return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr]) return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr])
@ -242,14 +242,19 @@ def t_identifier(p):
if obj.startswith("&"): if obj.startswith("&"):
return HyLambdaListKeyword(obj) return HyLambdaListKeyword(obj)
if obj.startswith("*") and obj.endswith("*") and obj not in ("*", "**"): def mangle(p):
obj = obj[1:-1].upper() if p.startswith("*") and p.endswith("*") and p not in ("*", "**"):
p = p[1:-1].upper()
if "-" in obj and obj != "-": if "-" in p and p != "-":
obj = obj.replace("-", "_") p = p.replace("-", "_")
if obj.endswith("?") and obj != "?": if p.endswith("?") and p != "?":
obj = "is_%s" % (obj[:-1]) p = "is_%s" % (p[:-1])
return p
obj = ".".join([mangle(part) for part in obj.split(".")])
return HySymbol(obj) return HySymbol(obj)

View File

@ -43,7 +43,6 @@ EXTRA_MACROS = [
_hy_macros = defaultdict(dict) _hy_macros = defaultdict(dict)
_hy_reader = defaultdict(dict) _hy_reader = defaultdict(dict)
_hy_reader_chars = set()
def macro(name): def macro(name):
@ -85,8 +84,6 @@ def reader(name):
module_name = None module_name = None
_hy_reader[module_name][name] = fn _hy_reader[module_name][name] = fn
# Ugly hack to get some error handling
_hy_reader_chars.add(name)
return fn return fn
return _ return _
@ -209,3 +206,20 @@ def macroexpand_1(tree, module_name):
return ntree return ntree
return tree 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)

View File

@ -15,4 +15,4 @@ from .native_tests.reader_macros import * # noqa
from .native_tests.with_test import * # noqa from .native_tests.with_test import * # noqa
from .native_tests.contrib.anaphoric import * # noqa from .native_tests.contrib.anaphoric import * # noqa
from .native_tests.contrib.loop import * # noqa from .native_tests.contrib.loop import * # noqa
from .contrib.test_meth import * # noqa from .native_tests.contrib.meth import * # noqa

View File

@ -258,7 +258,7 @@ def test_reader_macro():
"""Ensure reader macros are handles properly""" """Ensure reader macros are handles properly"""
entry = tokenize("#^()") entry = tokenize("#^()")
assert entry[0][0] == HySymbol("dispatch_reader_macro") 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 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""" """Ensure that we can tokenize sources with a comment at the end"""
entry = tokenize("foo ;bar\n;baz") entry = tokenize("foo ;bar\n;baz")
assert entry == [HySymbol("foo")] 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")]

View File

@ -3,7 +3,7 @@
(defclass FakeMeth [] (defclass FakeMeth []
"Mocking decorator class" "Mocking decorator class"
[[rules {}] [[rules {}]
[route (fn [self rule &kwargs options] [route (fn [self rule &kwargs options]
(fn [f] (fn [f]
(assoc self.rules rule (, f options)) (assoc self.rules rule (, f options))
f))]]) f))]])

View File

@ -23,10 +23,14 @@
(assert (= #+2 3))) (assert (= #+2 3)))
(defn test-reader-macro-compile-docstring [] (defn test-reader-macros-macros []
"Test if we can compile with a docstring" "Test if defreader is actually a macro"
(try (defreader t [expr]
(defreader d [] `(, ~@expr))
"Compiles with docstrings")
(except [Exception] (def a #t[1 2 3])
(assert False))))
(assert (= (type a) tuple))
(assert (= (, 1 2 3) a)))

View File

@ -76,6 +76,14 @@ def test_bin_hy_icmd():
assert "figlet" in output 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(): def test_bin_hy_missing_file():
ret = run_cmd("hy foobarbaz") ret = run_cmd("hy foobarbaz")
assert ret[0] == 2 assert ret[0] == 2
@ -126,7 +134,7 @@ def test_hy2py():
for f in filenames: for f in filenames:
if f.endswith(".hy"): if f.endswith(".hy"):
i += 1 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 ret[0] == 0, f
assert len(ret[1]) > 1, f assert len(ret[1]) > 1, f
assert len(ret[2]) == 0, f assert len(ret[2]) == 0, f