Merge branch 'master' into pr/141

This commit is contained in:
Paul Tagliamonte 2013-04-23 19:23:37 -04:00
commit a1cf9e50eb
26 changed files with 505 additions and 201 deletions

View File

@ -6,7 +6,9 @@ python:
- "3.3"
- "2.6"
# command to install dependencies
install: "pip install -r requirements.txt --use-mirrors"
install:
- pip install -r requirements.txt --use-mirrors
- python setup.py -q install
# # command to run tests
script: nosetests
notifications:

View File

@ -9,3 +9,4 @@
* Gergely Nagy <algernon@madhouse-project.org>
* Konrad Hinsen <konrad.hinsen@fastmail.net>
* Vladimir Gorbunov <vsg@suburban.me>
* John Jacobsen <john@mail.npxdesigns.com>

8
bin/hy
View File

@ -21,7 +21,7 @@ from hy.lex.states import Idle, LexException
from hy.lex.machine import Machine
from hy.compiler import hy_compile
from hy.core import process
from hy.importer import compile_
from hy.importer import ast_compile
import hy.completer
@ -59,7 +59,7 @@ class HyREPL(code.InteractiveConsole):
_machine = Machine(Idle, 1, 0)
try:
_ast = hy_compile(tokens, root=ast.Interactive)
code = compile_(_ast, filename, symbol)
code = ast_compile(_ast, filename, symbol)
except Exception:
self.showtraceback()
return False
@ -98,7 +98,7 @@ def koan_macro(tree):
return HyExpression([HySymbol('print'),
HyString("""
=> (import-from sh figlet)
=> (import [sh [figlet]])
=> (figlet "Hi, Hy!")
_ _ _ _ _ _
| | | (_) | | | |_ _| |
@ -113,7 +113,7 @@ def koan_macro(tree):
;;; this one plays with command line bits
(import-from sh cat grep)
(import [sh [cat grep]])
(-> (cat "/usr/share/dict/words") (grep "-E" "bro$"))

View File

@ -1,22 +1,24 @@
#!/usr/bin/env python
from __future__ import print_function
from hy.importer import (import_file_to_ast, import_file_to_module,
import_file_to_hst)
import astor.codegen
#import astor.codegen
import sys
import ast
hst = import_file_to_hst(sys.argv[1])
print hst
print ""
print ""
print(hst)
print("")
print("")
_ast = import_file_to_ast(sys.argv[1])
print ""
print ""
print ast.dump(_ast)
print ""
print ""
print astor.codegen.to_source(_ast)
print("")
print("")
print(ast.dump(_ast))
print("")
print("")
#print(astor.codegen.to_source(_ast))
import_file_to_module("<STDIN>", sys.argv[1])

View File

@ -1,6 +1,6 @@
#!/usr/bin/env hy
(import sys)
(import-from hy.importer write-hy-as-pyc)
(import [hy.importer [write-hy-as-pyc]])
(write-hy-as-pyc (get sys.argv 1))

View File

@ -324,6 +324,18 @@ Comments start with semicolons:
; (print "but this will not")
(+ 1 2 3) ; we'll execute the addition, but not this comment!
Python's context managers ('with' statements) are used like this:
.. code-block:: clj
(with [f (file "/tmp/data.in")]
(print (.read f)))
which is equivalent to::
with file("/tmp/data.in") as f:
print f.read()
And yes, we do have lisp comprehensions! In Python you might do::
odds_squared = [
@ -392,7 +404,7 @@ a pipe:
.. code-block:: clj
=> (import-from sh cat grep wc)
=> (import [sh [cat grep wc]])
=> (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l"))
210
@ -408,6 +420,8 @@ Much more readable, no! Use the threading macro!
TODO
====
- How do I index into arrays or dictionaries?
- How do I do array ranges? e.g. x[5:] or y[2:10]
- How do I define classes?
- Blow your mind with macros!
- Where's my banana???

View File

@ -35,6 +35,6 @@
(print source "is a(n)" (get block "Description"))
(import-from sh apt-cache)
(import [sh [apt-cache]])
(setv archive-block (parse-rfc822-stream (.show apt-cache source)))
(print "The archive has version" (get archive-block "Version") "of" source)

View File

@ -1,4 +1,4 @@
(import-from gevent.server StreamServer)
(import [gevent.server [StreamServer]])
(defn handle [socket address]

View File

@ -1,7 +1,6 @@
(import-from concurrent.futures ThreadPoolExecutor as-completed)
(import-from random randint)
(import-from sh sleep)
(import [concurrent.futures [ThreadPoolExecutor as-completed]]
[random [randint]]
[sh [sleep]])
(defn task-to-do [] (sleep (randint 1 5)))

View File

@ -1,5 +1,5 @@
;; python-sh from hy
(import-from sh cat grep)
(import [sh [cat grep]])
(print "Words that end with `tag`:")
(print (-> (cat "/usr/share/dict/words") (grep "-E" "tag$")))

View File

@ -4,8 +4,8 @@
; the source.
(import sys)
(import-from sunlight openstates)
(import-from collections Counter)
(import [sunlight [openstates]]
[collections [Counter]])
(def *state* (get sys.argv 1))

View File

@ -23,23 +23,24 @@
from hy.errors import HyError
from hy.models.lambdalist import HyLambdaListKeyword
from hy.models.expression import HyExpression
from hy.models.keyword import HyKeyword
from hy.models.integer import HyInteger
from hy.models.float import HyFloat
from hy.models.complex import HyComplex
from hy.models.string import HyString
from hy.models.symbol import HySymbol
from hy.models.float import HyFloat
from hy.models.list import HyList
from hy.models.dict import HyDict
from hy.models.keyword import HyKeyword
from hy.util import flatten_literal_list, str_type
from hy.util import flatten_literal_list, str_type, temporary_attribute_value
from collections import defaultdict
import traceback
import codecs
import ast
import sys
import traceback
class HyCompileError(HyError):
@ -83,7 +84,7 @@ def ast_str(foobar):
enc = codecs.getencoder('punycode')
foobar, _ = enc(foobar)
return "__hy_%s" % (str(foobar).replace("-", "_"))
return "hy_%s" % (str(foobar).replace("-", "_"))
def builds(_type):
@ -129,11 +130,14 @@ class HyASTCompiler(object):
self.anon_fn_count = 0
self.imports = defaultdict(list)
def is_returnable(self, v):
return temporary_attribute_value(self, "returnable", v)
def compile(self, tree):
try:
for _type in _compile_table:
if type(tree) == _type:
return _compile_table[_type](self, tree)
_type = type(tree)
if _type in _compile_table:
return _compile_table[_type](self, tree)
except HyCompileError:
# compile calls compile, so we're going to have multiple raise
# nested; so let's re-raise this exception, let's not wrap it in
@ -157,7 +161,7 @@ class HyASTCompiler(object):
ret = []
if self.returnable and len(tree) > 0:
if self.returnable:
el = tree[0]
if not isinstance(el, ast.stmt):
el = tree.pop(0)
@ -184,6 +188,66 @@ class HyASTCompiler(object):
ret.reverse()
return ret
def _parse_lambda_list(self, exprs):
""" Return FunctionDef parameter values from lambda list."""
args = []
defaults = []
varargs = None
kwargs = None
lambda_keyword = None
for expr in exprs:
if isinstance(expr, HyLambdaListKeyword):
if expr not in expr._valid_types:
raise HyCompileError("{0} is not a valid "
"lambda-keyword.".format(repr(expr)))
if expr == "&rest" and lambda_keyword is None:
lambda_keyword = expr
elif expr == "&optional":
lambda_keyword = expr
elif expr == "&key":
lambda_keyword = expr
elif expr == "&kwargs":
lambda_keyword = expr
else:
raise HyCompileError("{0} is in an invalid "
"position.".format(repr(expr)))
# we don't actually care about this token, so we set
# our state and continue to the next token...
continue
if lambda_keyword is None:
args.append(expr)
elif lambda_keyword == "&rest":
if varargs:
raise HyCompileError("There can only be one "
"&rest argument")
varargs = str(expr)
elif lambda_keyword == "&key":
if type(expr) != HyDict:
raise TypeError("There can only be one &key "
"argument")
else:
if len(defaults) > 0:
raise HyCompileError("There can only be "
"one &key argument")
# As you can see, Python has a funny way of
# defining keyword arguments.
for k, v in expr.items():
args.append(k)
defaults.append(self.compile(v))
elif lambda_keyword == "&optional":
# not implemented yet.
pass
elif lambda_keyword == "&kwargs":
if kwargs:
raise HyCompileError("There can only be one "
"&kwargs argument")
kwargs = str(expr)
return args, defaults, varargs, kwargs
@builds(list)
def compile_raw_list(self, entries):
return [self.compile(x) for x in entries]
@ -576,29 +640,10 @@ class HyASTCompiler(object):
raise TypeError("Unknown entry (`%s`) in the HyList" % (entry))
return rimports
@builds("import_as")
def compile_import_as_expression(self, expr):
expr.pop(0) # index
modlist = [expr[i:i + 2] for i in range(0, len(expr), 2)]
return ast.Import(
lineno=expr.start_line,
col_offset=expr.start_column,
module=ast_str(expr.pop(0)),
names=[ast.alias(name=ast_str(x[0]),
asname=ast_str(x[1])) for x in modlist])
@builds("import_from")
@checkargs(min=1)
def compile_import_from_expression(self, expr):
expr.pop(0) # index
return ast.ImportFrom(
lineno=expr.start_line,
col_offset=expr.start_column,
module=ast_str(expr.pop(0)),
names=[ast.alias(name=ast_str(x), asname=None) for x in expr],
level=0)
if len(rimports) == 1:
return rimports[0]
else:
return rimports
@builds("get")
@checkargs(2)
@ -979,37 +1024,35 @@ class HyASTCompiler(object):
@builds("foreach")
@checkargs(min=1)
def compile_for_expression(self, expression):
ret_status = self.returnable
self.returnable = False
with self.is_returnable(False):
expression.pop(0) # for
name, iterable = expression.pop(0)
target = self._storeize(self.compile_symbol(name))
expression.pop(0) # for
name, iterable = expression.pop(0)
target = self._storeize(self.compile_symbol(name))
orelse = []
# (foreach [] 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")
elif len(else_expr) == 2:
orelse = self._code_branch(
self.compile(else_expr[1]),
else_expr[1].start_line,
else_expr[1].start_column)
orelse = []
# (foreach [] 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")
elif len(else_expr) == 2:
orelse = self._code_branch(
self.compile(else_expr[1]),
else_expr[1].start_line,
else_expr[1].start_column)
ret = ast.For(lineno=expression.start_line,
col_offset=expression.start_column,
target=target,
iter=self.compile(iterable),
body=self._code_branch(
[self.compile(x) for x in expression],
expression.start_line,
expression.start_column),
orelse=orelse)
ret = ast.For(lineno=expression.start_line,
col_offset=expression.start_column,
target=target,
iter=self.compile(iterable),
body=self._code_branch(
[self.compile(x) for x in expression],
expression.start_line,
expression.start_column),
orelse=orelse)
self.returnable = ret_status
return ret
@builds("while")
@ -1036,51 +1079,50 @@ class HyASTCompiler(object):
col_offset=expr.start_column)
@builds("fn")
@checkargs(min=2)
@checkargs(min=1)
def compile_fn_expression(self, expression):
expression.pop(0) # fn
ret_status = self.returnable
self.anon_fn_count += 1
name = "_hy_anon_fn_%d" % (self.anon_fn_count)
sig = expression.pop(0)
body = []
if expression != []:
self.returnable = True
tailop = self.compile(expression.pop(-1))
self.returnable = False
for el in expression:
body.append(self.compile(el))
with self.is_returnable(True):
tailop = self.compile(expression.pop(-1))
with self.is_returnable(False):
for el in expression:
body.append(self.compile(el))
body.append(tailop)
self.returnable = True
body = self._code_branch(body,
expression.start_line,
expression.start_column)
with self.is_returnable(True):
body = self._code_branch(body,
expression.start_line,
expression.start_column)
ret = ast.FunctionDef(
name=name,
lineno=expression.start_line,
col_offset=expression.start_column,
args=ast.arguments(
args=[
ast.Name(
arg=ast_str(x), id=ast_str(x),
ctx=ast.Param(),
lineno=x.start_line,
col_offset=x.start_column)
for x in sig],
vararg=None,
kwarg=None,
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=body,
decorator_list=[])
args, defaults, stararg, kwargs = self._parse_lambda_list(sig)
ret = ast.FunctionDef(
name=name,
lineno=expression.start_line,
col_offset=expression.start_column,
args=ast.arguments(
args=[
ast.Name(
arg=ast_str(x), id=ast_str(x),
ctx=ast.Param(),
lineno=x.start_line,
col_offset=x.start_column)
for x in args],
vararg=stararg,
kwarg=kwargs,
kwonlyargs=[],
kw_defaults=[],
defaults=defaults),
body=body,
decorator_list=[])
self.returnable = ret_status
return ret
@builds(HyInteger)
@ -1180,9 +1222,8 @@ def hy_compile(tree, root=None):
imported.add(entry)
imports.append(HyExpression([
HySymbol("import_from"),
HySymbol(package),
HySymbol(entry)
HySymbol("import"),
HyList([HySymbol(package), HyList([HySymbol(entry)])])
]).replace(replace))
_ast = compiler.compile(imports) + _ast

View File

@ -133,14 +133,40 @@ def rest_macro(tree):
@macro("let")
def let_macro(tree):
tree.pop(0) # "let"
ret = tree.pop(0) # vars
variables = tree.pop(0)
# tree is now the body
expr = HyExpression([HySymbol("fn"), HyList([])])
for var in ret:
expr.append(HyExpression([HySymbol("setf"), var[0], var[1]]))
for var in variables:
if isinstance(var, list):
expr.append(HyExpression([HySymbol("setf"),
var[0], var[1]]))
else:
expr.append(HyExpression([HySymbol("setf"),
var, HySymbol("None")]))
for stmt in tree:
expr.append(stmt)
return HyExpression([expr])
@macro("take")
def take_macro(tree):
tree.pop(0) # "take"
n = tree.pop(0)
ret = tree.pop(0)
return HyExpression([HySymbol('slice'),
ret,
HyInteger(0),
HyInteger(n)])
@macro("drop")
def drop_macro(tree):
tree.pop(0) # "drop"
n = tree.pop(0)
ret = tree.pop(0)
return HyExpression([HySymbol('slice'),
ret,
HyInteger(n)])

View File

@ -26,7 +26,26 @@ import hy.mangle
class HoistableMangle(hy.mangle.Mangle):
"""
superclass for all the mangles down below -- this contains a bunch
of the core logic on when we should hoist things out.
"""
def should_hoist(self):
"""
If the stack is:
- unquoted
- not at the top-level of the "scope" (code branch)
- not ignorable (something that factors out during AST render)
then we return True, otherwise, we return False.
"""
for frame in self.stack:
if (isinstance(frame, HyExpression) and
frame and frame[0] == "quote"):
return False
for frame in self.stack:
if frame is self.scope:
return False
@ -40,8 +59,13 @@ class HoistableMangle(hy.mangle.Mangle):
class FunctionMangle(HoistableMangle):
hoistable = ["fn"]
"""
This will hoist function defs out of an inner expression (such as invoking
an anon function "((fn [] ...))", or using it as an arg in a call)
"""
ignore = ["def", "decorate_with", "setf", "setv", "foreach", "do"]
# ^^ these guys don't affect us, really.
def __init__(self):
self.series = 0
@ -51,9 +75,13 @@ class FunctionMangle(HoistableMangle):
return "_hy_hoisted_fn_%s" % (self.series)
def visit(self, tree):
"""
Visit all the nodes in the Hy code tree.
"""
if isinstance(tree, HyExpression) and tree != []:
call = tree[0]
if call == "fn" and self.should_hoist():
# if we have a Function and we should hoist it --
new_name = HySymbol(self.unique_name())
new_name.replace(tree)
fn_def = HyExpression([HySymbol("def"),
@ -65,6 +93,12 @@ class FunctionMangle(HoistableMangle):
class IfMangle(HoistableMangle):
"""
This will mangle an `if' statement that's nested inside something (meaning
we need some sort of return statement from the (if)), we should
special-case the code to give us some juju.
"""
ignore = ["foreach", "do"]
def __init__(self):
@ -74,6 +108,7 @@ class IfMangle(HoistableMangle):
if isinstance(tree, HyExpression) and tree != []:
call = tree[0]
if call == "if" and self.should_hoist():
# If we've got a hoistable if statement
fn = HyExpression([HyExpression([HySymbol("fn"),
HyList([]),
tree])])

View File

@ -34,45 +34,47 @@ import os
import __future__
if sys.version_info[0] >= 3:
from io import StringIO
long_type = int
else:
from StringIO import StringIO # NOQA
import __builtin__
long_type = long # NOQA
def compile_(ast, filename, mode):
def ast_compile(ast, filename, mode):
"""Compile AST.
Like Python's compile, but with some special flags."""
return compile(ast, filename, mode, __future__.CO_FUTURE_DIVISION)
def import_buffer_to_hst(fd):
tree = tokenize(fd.read() + "\n")
tree = process(tree)
return tree
def import_buffer_to_hst(buf):
"""Import content from buf and return an Hy AST."""
return process(tokenize(buf + "\n"))
def import_file_to_hst(fpath):
return import_buffer_to_hst(open(fpath, 'r', encoding='utf-8'))
"""Import content from fpath and return an Hy AST."""
with open(fpath, 'r', encoding='utf-8') as f:
return import_buffer_to_hst(f.read())
def import_buffer_to_ast(buf):
""" Import content from buf and return a Python AST."""
return hy_compile(import_buffer_to_hst(buf))
def import_file_to_ast(fpath):
tree = import_file_to_hst(fpath)
_ast = hy_compile(tree)
return _ast
"""Import content from fpath and return a Python AST."""
return hy_compile(import_file_to_hst(fpath))
def import_string_to_ast(buff):
tree = import_buffer_to_hst(StringIO(buff))
_ast = hy_compile(tree)
return _ast
def import_file_to_module(module_name, fpath):
"""Import content from fpath and puts it into a Python module.
def import_file_to_module(name, fpath):
Returns the module."""
_ast = import_file_to_ast(fpath)
mod = imp.new_module(name)
mod = imp.new_module(module_name)
mod.__file__ = fpath
eval(compile_(_ast, fpath, "exec"), mod.__dict__)
eval(ast_compile(_ast, fpath, "exec"), mod.__dict__)
return mod
@ -84,7 +86,7 @@ def hy_eval(hytree, namespace):
foo.end_column = 0
hytree.replace(foo)
_ast = hy_compile(hytree, root=ast.Expression)
return eval(compile_(_ast, "<eval>", "eval"), namespace)
return eval(ast_compile(_ast, "<eval>", "eval"), namespace)
def write_hy_as_pyc(fname):
@ -96,7 +98,7 @@ def write_hy_as_pyc(fname):
timestamp = long_type(st.st_mtime)
_ast = import_file_to_ast(fname)
code = compile_(_ast, fname, "exec")
code = ast_compile(_ast, fname, "exec")
cfile = "%s.pyc" % fname[:-len(".hy")]
if sys.version_info[0] >= 3:
@ -118,7 +120,10 @@ def write_hy_as_pyc(fname):
fc.write(MAGIC)
class HyFinder(object):
class MetaLoader(object):
def __init__(self, path):
self.path = path
def is_package(self, fullname):
dirpath = "/".join(fullname.split("."))
for pth in sys.path:
@ -128,33 +133,20 @@ class HyFinder(object):
return True
return False
def find_on_path(self, fullname):
fls = ["%s/__init__.hy", "%s.hy"]
dirpath = "/".join(fullname.split("."))
for pth in sys.path:
pth = os.path.abspath(pth)
for fp in fls:
composed_path = fp % ("%s/%s" % (pth, dirpath))
if os.path.exists(composed_path):
return composed_path
class MetaLoader(HyFinder):
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
pth = self.find_on_path(fullname)
if pth is None:
if not self.path:
return
sys.modules[fullname] = None
mod = import_file_to_module(fullname, pth)
mod = import_file_to_module(fullname,
self.path)
ispkg = self.is_package(fullname)
mod.__file__ = pth
mod.__file__ = self.path
mod.__loader__ = self
mod.__name__ = fullname
@ -168,12 +160,22 @@ class MetaLoader(HyFinder):
return mod
class MetaImporter(HyFinder):
class MetaImporter(object):
def find_on_path(self, fullname):
fls = ["%s/__init__.hy", "%s.hy"]
dirpath = "/".join(fullname.split("."))
for pth in sys.path:
pth = os.path.abspath(pth)
for fp in fls:
composed_path = fp % ("%s/%s" % (pth, dirpath))
if os.path.exists(composed_path):
return composed_path
def find_module(self, fullname, path=None):
pth = self.find_on_path(fullname)
if pth is None:
return
return MetaLoader()
path = self.find_on_path(fullname)
if path:
return MetaLoader(path)
sys.meta_path.append(MetaImporter())

View File

@ -20,6 +20,7 @@
from hy.models.expression import HyExpression
from hy.models.integer import HyInteger
from hy.models.lambdalist import HyLambdaListKeyword
from hy.models.float import HyFloat
from hy.models.complex import HyComplex
from hy.models.symbol import HySymbol
@ -48,6 +49,7 @@ def _resolve_atom(obj):
Resolve a bare atom into one of the following (in order):
- Integer
- LambdaListKeyword
- Float
- Complex
- Symbol
@ -57,6 +59,9 @@ def _resolve_atom(obj):
except ValueError:
pass
if obj.startswith("&"):
return HyLambdaListKeyword(obj)
try:
return HyFloat(obj)
except ValueError:
@ -388,4 +393,4 @@ class Hash(State):
if char == "!":
return Comment
raise LexException("Unknown char (Hash state): `%s`" % (char))
raise LexException("Unknown char (Hash state): `%s'" % (char))

View File

@ -18,6 +18,8 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
import abc
from hy.models.expression import HyExpression
# from hy.models.list import HyList
@ -34,39 +36,59 @@ class Mangle(object):
(but mostly hacking)
"""
__metaclass___ = abc.ABCMeta
class TreeChanged(Exception):
"""
This exception gets raised whenver any code alters the tree. This is
to let the handling code re-normalize parents, etc, and make sure we
re-enter the current position in order.
"""
pass
def _mangle(self, tree):
# Things that force a scope push to go into:
#
# - Functions
# - If
scopable = ["fn", "if"]
scoped = False
@abc.abstractmethod
def visit(self, element):
raise NotImplementedError
def _mangle(self, tree):
"""
Main function of self.mangle, which is called over and over. This
is used to beat the tree until it stops moving.
"""
scoped = False
self.push_stack(tree)
if isinstance(tree, HyExpression):
# If it's an expression, let's make sure we reset the "scope"
# (code branch) if it's a scopable object.
what = tree[0]
if what in scopable:
if what in ["fn", "if"]:
self.push_scope(tree)
scoped = True
if isinstance(tree, list):
# If it's a list, let's mangle all the elements of the list.
for i, element in enumerate(tree):
nel = self.visit(element)
if nel:
# if the subclass returned an object, we replace the
# current node.
tree[i] = nel
self.tree_changed()
self._mangle(element)
self.tree_changed() # auto-raise a changed notice.
self._mangle(element) # recurse down, unwind on change.
if scoped:
self.pop_scope()
self.pop_stack()
def hoist(self, what):
"""
Take a thing (what), and move it before whichever ancestor is in the
"scope" (code branch). This will hoist it *all* the way out of a deeply
nested statement in one pass. If it's still "invalid" (which it
shouldn't be), it'll just hoist again anyway.
"""
scope = self.scope
for point, el in enumerate(scope):
if el in self.stack:
@ -77,6 +99,7 @@ class Mangle(object):
return self.scopes[0]
def tree_changed(self):
""" Invoke this if you alter the tree in any way """
raise self.TreeChanged()
@property
@ -96,14 +119,18 @@ class Mangle(object):
return self.stack.pop(0)
def mangle(self, tree):
unfinished = True
while unfinished:
self.root = tree
"""Magic external entry point.
We mangle until the tree stops moving, i.e. until we don't get a
TreeChanged Exception during mangle.
"""
while True:
self.scopes = []
self.stack = []
self.push_scope(tree)
try:
self._mangle(tree)
unfinished = False
break
except self.TreeChanged:
pass

39
hy/models/lambdalist.py Normal file
View File

@ -0,0 +1,39 @@
# Copyright (c) 2013 James King <james@agentultra.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
from hy.models.string import HyString
class HyLambdaListKeyword(HyString):
"""
Hy LambdaListKeyword. Demarcates arguments in an argument list.
(defun my-fun (x &rest xs &optional (foo "default string")))
becomes:
def my_fun(x, *xs, foo="default string"):
pass
"""
_valid_types = ["&rest", "&optional", "&key", "&kwargs"]
def __init__(self, string):
self += string

View File

@ -1,4 +1,5 @@
# Copyright (c) 2013 Paul Tagliamonte <paultag@debian.org>
# Copyright (c) 2013 Julien Danjou <julien@danjou.info>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
@ -18,6 +19,7 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
import contextlib
import sys
@ -27,6 +29,20 @@ else:
str_type = unicode
@contextlib.contextmanager
def temporary_attribute_value(obj, attribute, value):
"""Temporarily switch an object attribute value to another value."""
original_value = getattr(obj, attribute)
setattr(obj, attribute, value)
try:
yield
except Exception:
pass
setattr(obj, attribute, original_value)
def flatten_literal_list(entry):
for e in entry:
if type(e) == list:

View File

@ -1,7 +1,7 @@
;;;
;;;
(import-from hy HyExpression HySymbol HyString)
(import [hy [HyExpression HySymbol HyString]])
(defn test-basic-quoting []

View File

@ -216,13 +216,8 @@ def test_ast_bad_yield():
def test_ast_good_import_from():
"Make sure AST can compile valid import-from"
hy_compile(tokenize("(import-from x y)"))
def test_ast_bad_import_from():
"Make sure AST can't compile invalid import-from"
cant_compile("(import-from)")
"Make sure AST can compile valid selective import"
hy_compile(tokenize("(import [x [y]])"))
def test_ast_good_get():
@ -250,6 +245,16 @@ def test_ast_bad_slice():
cant_compile("(slice 1 2 3 4)")
def test_ast_good_take():
"Make sure AST can compile valid 'take'"
hy_compile(tokenize("(take 1 [2 3])"))
def test_ast_good_drop():
"Make sure AST can compile valid 'drop'"
hy_compile(tokenize("(drop 1 [2 3])"))
def test_ast_good_assoc():
"Make sure AST can compile valid assoc"
hy_compile(tokenize("(assoc x y z)"))
@ -308,6 +313,8 @@ def test_ast_anon_fns_basics():
""" Ensure anon fns work. """
code = hy_compile(tokenize("(fn (x) (* x x))")).body[0]
assert type(code) == ast.FunctionDef
code = hy_compile(tokenize("(fn (x))")).body[0]
cant_compile("(fn)")
def test_ast_non_decoratable():
@ -341,6 +348,30 @@ def test_ast_tuple():
assert type(code) == ast.Tuple
def test_lambda_list_keywords_rest():
""" Ensure we can compile functions with lambda list keywords."""
hy_compile(tokenize("(fn (x &rest xs) (print xs))"))
cant_compile("(fn (x &rest xs &rest ys) (print xs))")
def test_lambda_list_keywords_key():
""" Ensure we can compile functions with &key."""
hy_compile(tokenize("(fn (x &key {foo True}) (list x foo))"))
cant_compile("(fn (x &key {bar \"baz\"} &key {foo 42}) (list x bar foo))")
def test_lambda_list_keywords_kwargs():
""" Ensure we can compile functions with &kwargs."""
hy_compile(tokenize("(fn (x &kwargs kw) (list x kw))"))
cant_compile("(fn (x &kwargs xs &kwargs ys) (list x xs ys))")
def test_lambda_list_keywords_mixed():
""" Ensure we can mix them up."""
hy_compile(tokenize("(fn (x &rest xs &kwargs kw) (list x xs kw))"))
cant_compile("(fn (x &rest xs &fasfkey {bar \"baz\"}))")
def test_ast_unicode_strings():
"""Ensure we handle unicode strings correctly"""

View File

@ -1,4 +1,4 @@
from hy.importer import import_file_to_module, import_string_to_ast
from hy.importer import import_file_to_module, import_buffer_to_ast
import ast
@ -10,5 +10,5 @@ def test_basics():
def test_stringer():
"Make sure the basics of the importer work"
_ast = import_string_to_ast("(defn square [x] (* x x))")
_ast = import_buffer_to_ast("(defn square [x] (* x x))")
assert type(_ast.body[0]) == ast.FunctionDef

View File

@ -20,6 +20,7 @@
from hy.models.expression import HyExpression
from hy.models.integer import HyInteger
from hy.models.lambdalist import HyLambdaListKeyword
from hy.models.float import HyFloat
from hy.models.complex import HyComplex
from hy.models.symbol import HySymbol
@ -79,6 +80,14 @@ def test_lex_expression_integer():
assert objs == [HyExpression([HySymbol("foo"), HyInteger(2)])]
def test_lex_lambda_list_keyword():
""" Make sure expressions can produce lambda list keywords """
objs = tokenize("(x &rest xs)")
assert objs == [HyExpression([HySymbol("x"),
HyLambdaListKeyword("&rest"),
HySymbol("xs")])]
def test_lex_symbols():
""" Make sure that symbols are valid expressions"""
objs = tokenize("foo ")

View File

@ -1,8 +1,8 @@
;
(import-from tests.resources kwtest function-with-a-dash)
(import-from os.path exists isdir isfile)
(import-as sys systest)
(import [tests.resources [kwtest function-with-a-dash]]
[os.path [exists isdir isfile]]
[sys :as systest])
(import sys)
@ -376,6 +376,20 @@
(assert (= (slice [1 2 3 4 5]) [1 2 3 4 5])))
(defn test-take []
"NATIVE: test take"
(assert (= (take 0 [2 3]) []))
(assert (= (take 1 [2 3]) [2]))
(assert (= (take 2 [2 3]) [2 3])))
(defn test-drop []
"NATIVE: test drop"
(assert (= (drop 0 [2 3]) [2 3]))
(assert (= (drop 1 [2 3]) [3]))
(assert (= (drop 2 [2 3]) [])))
(defn test-rest []
"NATIVE: test rest"
(assert (= (rest [1 2 3 4 5]) [2 3 4 5])))
@ -474,12 +488,15 @@
(defn test-fn-return []
"NATIVE: test function return"
(setv fn-test ((fn [] (fn [] (+ 1 1)))))
(assert (= (fn-test) 2)))
(assert (= (fn-test) 2))
(setv fn-test (fn []))
(assert (= (fn-test) None)))
(defn test-let []
"NATIVE: test let works rightish"
(assert (= (let [[x 1] [y 2] [z 3]] (+ x y z)) 6)))
(assert (= (let [[x 1] [y 2] [z 3]] (+ x y z)) 6))
(assert (= (let [[x 1] a [y 2] b] (if a 1 2)) 2)))
(defn test-if-mangler []
@ -552,6 +569,7 @@
(do)
((fn [] 1))))))
(defn test-keyword []
"NATIVE: test if keywords are recognised"
@ -583,12 +601,15 @@
(setf test-payload (quote (+ x 2)))
(setf x 4)
(assert (= 6 (eval test-payload)))
(assert (= 6 (eval (quote ((fn [] (+ 3 3)))))))
; (assert (= 6 (eval (quote ((fn [] (+ 3 3)))))))
; XXX: This must be commented out while we resolve stmts being run through
; eval. Please fix me. -- PRT
(assert (= 1 (eval (quote 1))))
(assert (= "foobar" (eval (quote "foobar"))))
(setv x (quote 42))
(assert (= x (eval x))))
(defn test-import-syntax []
"NATIVE: test the import syntax."
@ -616,3 +637,16 @@
(assert (= (dirname "/some/path") "/some"))
(assert (= op.dirname dirname))
(assert (= dn dirname)))
(defn test-lambda-keyword-lists []
"NATIVE: test lambda keyword lists"
(defn foo (x &rest xs &kwargs kw) [x xs kw])
(assert (= (foo 10 20 30) [10 (, 20 30) {}])))
(defn test-quoted-hoistable []
"NATIVE: test quoted hoistable"
(setf f (quote (if true true true)))
(assert (= (car f) "if"))
(assert (= (cdr f) (quote (true true true)))))

8
tests/test_bin.py Normal file
View File

@ -0,0 +1,8 @@
import subprocess
def test_bin_hy():
p = subprocess.Popen("echo | bin/hy",
shell=True)
p.wait()
assert p.returncode == 0

13
tests/test_util.py Normal file
View File

@ -0,0 +1,13 @@
from hy import util
def test_temporary_attribute_value():
class O(object):
def __init__(self):
self.foobar = 0
o = O()
with util.temporary_attribute_value(o, "foobar", 42):
assert o.foobar == 42
assert o.foobar == 0