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" - "3.3"
- "2.6" - "2.6"
# command to install dependencies # 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 # # command to run tests
script: nosetests script: nosetests
notifications: notifications:

View File

@ -9,3 +9,4 @@
* Gergely Nagy <algernon@madhouse-project.org> * Gergely Nagy <algernon@madhouse-project.org>
* Konrad Hinsen <konrad.hinsen@fastmail.net> * Konrad Hinsen <konrad.hinsen@fastmail.net>
* Vladimir Gorbunov <vsg@suburban.me> * 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.lex.machine import Machine
from hy.compiler import hy_compile from hy.compiler import hy_compile
from hy.core import process from hy.core import process
from hy.importer import compile_ from hy.importer import ast_compile
import hy.completer import hy.completer
@ -59,7 +59,7 @@ class HyREPL(code.InteractiveConsole):
_machine = Machine(Idle, 1, 0) _machine = Machine(Idle, 1, 0)
try: try:
_ast = hy_compile(tokens, root=ast.Interactive) _ast = hy_compile(tokens, root=ast.Interactive)
code = compile_(_ast, filename, symbol) code = ast_compile(_ast, filename, symbol)
except Exception: except Exception:
self.showtraceback() self.showtraceback()
return False return False
@ -98,7 +98,7 @@ def koan_macro(tree):
return HyExpression([HySymbol('print'), return HyExpression([HySymbol('print'),
HyString(""" HyString("""
=> (import-from sh figlet) => (import [sh [figlet]])
=> (figlet "Hi, Hy!") => (figlet "Hi, Hy!")
_ _ _ _ _ _ _ _ _ _ _ _
| | | (_) | | | |_ _| | | | | (_) | | | |_ _| |
@ -113,7 +113,7 @@ def koan_macro(tree):
;;; this one plays with command line bits ;;; this one plays with command line bits
(import-from sh cat grep) (import [sh [cat grep]])
(-> (cat "/usr/share/dict/words") (grep "-E" "bro$")) (-> (cat "/usr/share/dict/words") (grep "-E" "bro$"))

View File

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

View File

@ -1,6 +1,6 @@
#!/usr/bin/env hy #!/usr/bin/env hy
(import sys) (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)) (write-hy-as-pyc (get sys.argv 1))

View File

@ -324,6 +324,18 @@ Comments start with semicolons:
; (print "but this will not") ; (print "but this will not")
(+ 1 2 3) ; we'll execute the addition, but not this comment! (+ 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:: And yes, we do have lisp comprehensions! In Python you might do::
odds_squared = [ odds_squared = [
@ -392,7 +404,7 @@ a pipe:
.. code-block:: clj .. code-block:: clj
=> (import-from sh cat grep wc) => (import [sh [cat grep wc]])
=> (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l")) => (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l"))
210 210
@ -408,6 +420,8 @@ Much more readable, no! Use the threading macro!
TODO 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? - How do I define classes?
- Blow your mind with macros! - Blow your mind with macros!
- Where's my banana??? - Where's my banana???

View File

@ -35,6 +35,6 @@
(print source "is a(n)" (get block "Description")) (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))) (setv archive-block (parse-rfc822-stream (.show apt-cache source)))
(print "The archive has version" (get archive-block "Version") "of" 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] (defn handle [socket address]

View File

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

View File

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

View File

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

View File

@ -23,23 +23,24 @@
from hy.errors import HyError from hy.errors import HyError
from hy.models.lambdalist import HyLambdaListKeyword
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
from hy.models.keyword import HyKeyword
from hy.models.integer import HyInteger from hy.models.integer import HyInteger
from hy.models.float import HyFloat
from hy.models.complex import HyComplex from hy.models.complex import HyComplex
from hy.models.string import HyString from hy.models.string import HyString
from hy.models.symbol import HySymbol from hy.models.symbol import HySymbol
from hy.models.float import HyFloat
from hy.models.list import HyList from hy.models.list import HyList
from hy.models.dict import HyDict 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 from collections import defaultdict
import traceback
import codecs import codecs
import ast import ast
import sys import sys
import traceback
class HyCompileError(HyError): class HyCompileError(HyError):
@ -83,7 +84,7 @@ def ast_str(foobar):
enc = codecs.getencoder('punycode') enc = codecs.getencoder('punycode')
foobar, _ = enc(foobar) foobar, _ = enc(foobar)
return "__hy_%s" % (str(foobar).replace("-", "_")) return "hy_%s" % (str(foobar).replace("-", "_"))
def builds(_type): def builds(_type):
@ -129,11 +130,14 @@ class HyASTCompiler(object):
self.anon_fn_count = 0 self.anon_fn_count = 0
self.imports = defaultdict(list) self.imports = defaultdict(list)
def is_returnable(self, v):
return temporary_attribute_value(self, "returnable", v)
def compile(self, tree): def compile(self, tree):
try: try:
for _type in _compile_table: _type = type(tree)
if type(tree) == _type: if _type in _compile_table:
return _compile_table[_type](self, tree) return _compile_table[_type](self, tree)
except HyCompileError: except HyCompileError:
# compile calls compile, so we're going to have multiple raise # 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 # nested; so let's re-raise this exception, let's not wrap it in
@ -157,7 +161,7 @@ class HyASTCompiler(object):
ret = [] ret = []
if self.returnable and len(tree) > 0: if self.returnable:
el = tree[0] el = tree[0]
if not isinstance(el, ast.stmt): if not isinstance(el, ast.stmt):
el = tree.pop(0) el = tree.pop(0)
@ -184,6 +188,66 @@ class HyASTCompiler(object):
ret.reverse() ret.reverse()
return ret 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) @builds(list)
def compile_raw_list(self, entries): def compile_raw_list(self, entries):
return [self.compile(x) for x in 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)) raise TypeError("Unknown entry (`%s`) in the HyList" % (entry))
return rimports if len(rimports) == 1:
return rimports[0]
@builds("import_as") else:
def compile_import_as_expression(self, expr): return rimports
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)
@builds("get") @builds("get")
@checkargs(2) @checkargs(2)
@ -979,37 +1024,35 @@ class HyASTCompiler(object):
@builds("foreach") @builds("foreach")
@checkargs(min=1) @checkargs(min=1)
def compile_for_expression(self, expression): def compile_for_expression(self, expression):
ret_status = self.returnable with self.is_returnable(False):
self.returnable = False expression.pop(0) # for
name, iterable = expression.pop(0)
target = self._storeize(self.compile_symbol(name))
expression.pop(0) # for orelse = []
name, iterable = expression.pop(0) # (foreach [] body (else …))
target = self._storeize(self.compile_symbol(name)) 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 = [] ret = ast.For(lineno=expression.start_line,
# (foreach [] body (else …)) col_offset=expression.start_column,
if expression and expression[-1][0] == HySymbol("else"): target=target,
else_expr = expression.pop() iter=self.compile(iterable),
if len(else_expr) > 2: body=self._code_branch(
raise HyTypeError(else_expr, [self.compile(x) for x in expression],
"`else' statement in `foreach' is too long") expression.start_line,
elif len(else_expr) == 2: expression.start_column),
orelse = self._code_branch( orelse=orelse)
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)
self.returnable = ret_status
return ret return ret
@builds("while") @builds("while")
@ -1036,51 +1079,50 @@ class HyASTCompiler(object):
col_offset=expr.start_column) col_offset=expr.start_column)
@builds("fn") @builds("fn")
@checkargs(min=2) @checkargs(min=1)
def compile_fn_expression(self, expression): def compile_fn_expression(self, expression):
expression.pop(0) # fn expression.pop(0) # fn
ret_status = self.returnable
self.anon_fn_count += 1 self.anon_fn_count += 1
name = "_hy_anon_fn_%d" % (self.anon_fn_count) name = "_hy_anon_fn_%d" % (self.anon_fn_count)
sig = expression.pop(0) sig = expression.pop(0)
body = [] body = []
if expression != []: if expression != []:
self.returnable = True with self.is_returnable(True):
tailop = self.compile(expression.pop(-1)) tailop = self.compile(expression.pop(-1))
self.returnable = False with self.is_returnable(False):
for el in expression: for el in expression:
body.append(self.compile(el)) body.append(self.compile(el))
body.append(tailop) body.append(tailop)
self.returnable = True with self.is_returnable(True):
body = self._code_branch(body, body = self._code_branch(body,
expression.start_line, expression.start_line,
expression.start_column) expression.start_column)
ret = ast.FunctionDef( args, defaults, stararg, kwargs = self._parse_lambda_list(sig)
name=name,
lineno=expression.start_line, ret = ast.FunctionDef(
col_offset=expression.start_column, name=name,
args=ast.arguments( lineno=expression.start_line,
args=[ col_offset=expression.start_column,
ast.Name( args=ast.arguments(
arg=ast_str(x), id=ast_str(x), args=[
ctx=ast.Param(), ast.Name(
lineno=x.start_line, arg=ast_str(x), id=ast_str(x),
col_offset=x.start_column) ctx=ast.Param(),
for x in sig], lineno=x.start_line,
vararg=None, col_offset=x.start_column)
kwarg=None, for x in args],
kwonlyargs=[], vararg=stararg,
kw_defaults=[], kwarg=kwargs,
defaults=[]), kwonlyargs=[],
body=body, kw_defaults=[],
decorator_list=[]) defaults=defaults),
body=body,
decorator_list=[])
self.returnable = ret_status
return ret return ret
@builds(HyInteger) @builds(HyInteger)
@ -1180,9 +1222,8 @@ def hy_compile(tree, root=None):
imported.add(entry) imported.add(entry)
imports.append(HyExpression([ imports.append(HyExpression([
HySymbol("import_from"), HySymbol("import"),
HySymbol(package), HyList([HySymbol(package), HyList([HySymbol(entry)])])
HySymbol(entry)
]).replace(replace)) ]).replace(replace))
_ast = compiler.compile(imports) + _ast _ast = compiler.compile(imports) + _ast

View File

@ -133,14 +133,40 @@ def rest_macro(tree):
@macro("let") @macro("let")
def let_macro(tree): def let_macro(tree):
tree.pop(0) # "let" tree.pop(0) # "let"
ret = tree.pop(0) # vars variables = tree.pop(0)
# tree is now the body # tree is now the body
expr = HyExpression([HySymbol("fn"), HyList([])]) expr = HyExpression([HySymbol("fn"), HyList([])])
for var in ret: for var in variables:
expr.append(HyExpression([HySymbol("setf"), var[0], var[1]])) 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: for stmt in tree:
expr.append(stmt) expr.append(stmt)
return HyExpression([expr]) 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): 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): 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: for frame in self.stack:
if frame is self.scope: if frame is self.scope:
return False return False
@ -40,8 +59,13 @@ class HoistableMangle(hy.mangle.Mangle):
class FunctionMangle(HoistableMangle): 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"] ignore = ["def", "decorate_with", "setf", "setv", "foreach", "do"]
# ^^ these guys don't affect us, really.
def __init__(self): def __init__(self):
self.series = 0 self.series = 0
@ -51,9 +75,13 @@ class FunctionMangle(HoistableMangle):
return "_hy_hoisted_fn_%s" % (self.series) return "_hy_hoisted_fn_%s" % (self.series)
def visit(self, tree): def visit(self, tree):
"""
Visit all the nodes in the Hy code tree.
"""
if isinstance(tree, HyExpression) and tree != []: if isinstance(tree, HyExpression) and tree != []:
call = tree[0] call = tree[0]
if call == "fn" and self.should_hoist(): 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 = HySymbol(self.unique_name())
new_name.replace(tree) new_name.replace(tree)
fn_def = HyExpression([HySymbol("def"), fn_def = HyExpression([HySymbol("def"),
@ -65,6 +93,12 @@ class FunctionMangle(HoistableMangle):
class IfMangle(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"] ignore = ["foreach", "do"]
def __init__(self): def __init__(self):
@ -74,6 +108,7 @@ class IfMangle(HoistableMangle):
if isinstance(tree, HyExpression) and tree != []: if isinstance(tree, HyExpression) and tree != []:
call = tree[0] call = tree[0]
if call == "if" and self.should_hoist(): if call == "if" and self.should_hoist():
# If we've got a hoistable if statement
fn = HyExpression([HyExpression([HySymbol("fn"), fn = HyExpression([HyExpression([HySymbol("fn"),
HyList([]), HyList([]),
tree])]) tree])])

View File

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

View File

@ -20,6 +20,7 @@
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
from hy.models.integer import HyInteger from hy.models.integer import HyInteger
from hy.models.lambdalist import HyLambdaListKeyword
from hy.models.float import HyFloat from hy.models.float import HyFloat
from hy.models.complex import HyComplex from hy.models.complex import HyComplex
from hy.models.symbol import HySymbol 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): Resolve a bare atom into one of the following (in order):
- Integer - Integer
- LambdaListKeyword
- Float - Float
- Complex - Complex
- Symbol - Symbol
@ -57,6 +59,9 @@ def _resolve_atom(obj):
except ValueError: except ValueError:
pass pass
if obj.startswith("&"):
return HyLambdaListKeyword(obj)
try: try:
return HyFloat(obj) return HyFloat(obj)
except ValueError: except ValueError:
@ -388,4 +393,4 @@ class Hash(State):
if char == "!": if char == "!":
return Comment 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 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE. # DEALINGS IN THE SOFTWARE.
import abc
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
# from hy.models.list import HyList # from hy.models.list import HyList
@ -34,39 +36,59 @@ class Mangle(object):
(but mostly hacking) (but mostly hacking)
""" """
__metaclass___ = abc.ABCMeta
class TreeChanged(Exception): 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 pass
def _mangle(self, tree): @abc.abstractmethod
# Things that force a scope push to go into: def visit(self, element):
# raise NotImplementedError
# - Functions
# - If
scopable = ["fn", "if"]
scoped = False
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) self.push_stack(tree)
if isinstance(tree, HyExpression): 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] what = tree[0]
if what in scopable: if what in ["fn", "if"]:
self.push_scope(tree) self.push_scope(tree)
scoped = True scoped = True
if isinstance(tree, list): if isinstance(tree, list):
# If it's a list, let's mangle all the elements of the list.
for i, element in enumerate(tree): for i, element in enumerate(tree):
nel = self.visit(element) nel = self.visit(element)
if nel: if nel:
# if the subclass returned an object, we replace the
# current node.
tree[i] = nel tree[i] = nel
self.tree_changed() self.tree_changed() # auto-raise a changed notice.
self._mangle(element) # recurse down, unwind on change.
self._mangle(element)
if scoped: if scoped:
self.pop_scope() self.pop_scope()
self.pop_stack() self.pop_stack()
def hoist(self, what): 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 scope = self.scope
for point, el in enumerate(scope): for point, el in enumerate(scope):
if el in self.stack: if el in self.stack:
@ -77,6 +99,7 @@ class Mangle(object):
return self.scopes[0] return self.scopes[0]
def tree_changed(self): def tree_changed(self):
""" Invoke this if you alter the tree in any way """
raise self.TreeChanged() raise self.TreeChanged()
@property @property
@ -96,14 +119,18 @@ class Mangle(object):
return self.stack.pop(0) return self.stack.pop(0)
def mangle(self, tree): def mangle(self, tree):
unfinished = True """Magic external entry point.
while unfinished:
self.root = tree We mangle until the tree stops moving, i.e. until we don't get a
TreeChanged Exception during mangle.
"""
while True:
self.scopes = [] self.scopes = []
self.stack = [] self.stack = []
self.push_scope(tree) self.push_scope(tree)
try: try:
self._mangle(tree) self._mangle(tree)
unfinished = False break
except self.TreeChanged: except self.TreeChanged:
pass 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 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 # 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"),
@ -18,6 +19,7 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE. # DEALINGS IN THE SOFTWARE.
import contextlib
import sys import sys
@ -27,6 +29,20 @@ else:
str_type = unicode 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): def flatten_literal_list(entry):
for e in entry: for e in entry:
if type(e) == list: 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 [] (defn test-basic-quoting []

View File

@ -216,13 +216,8 @@ def test_ast_bad_yield():
def test_ast_good_import_from(): def test_ast_good_import_from():
"Make sure AST can compile valid import-from" "Make sure AST can compile valid selective import"
hy_compile(tokenize("(import-from x y)")) hy_compile(tokenize("(import [x [y]])"))
def test_ast_bad_import_from():
"Make sure AST can't compile invalid import-from"
cant_compile("(import-from)")
def test_ast_good_get(): def test_ast_good_get():
@ -250,6 +245,16 @@ def test_ast_bad_slice():
cant_compile("(slice 1 2 3 4)") 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(): def test_ast_good_assoc():
"Make sure AST can compile valid assoc" "Make sure AST can compile valid assoc"
hy_compile(tokenize("(assoc x y z)")) hy_compile(tokenize("(assoc x y z)"))
@ -308,6 +313,8 @@ def test_ast_anon_fns_basics():
""" Ensure anon fns work. """ """ Ensure anon fns work. """
code = hy_compile(tokenize("(fn (x) (* x x))")).body[0] code = hy_compile(tokenize("(fn (x) (* x x))")).body[0]
assert type(code) == ast.FunctionDef assert type(code) == ast.FunctionDef
code = hy_compile(tokenize("(fn (x))")).body[0]
cant_compile("(fn)")
def test_ast_non_decoratable(): def test_ast_non_decoratable():
@ -341,6 +348,30 @@ def test_ast_tuple():
assert type(code) == 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(): def test_ast_unicode_strings():
"""Ensure we handle unicode strings correctly""" """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 import ast
@ -10,5 +10,5 @@ def test_basics():
def test_stringer(): def test_stringer():
"Make sure the basics of the importer work" "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 assert type(_ast.body[0]) == ast.FunctionDef

View File

@ -20,6 +20,7 @@
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
from hy.models.integer import HyInteger from hy.models.integer import HyInteger
from hy.models.lambdalist import HyLambdaListKeyword
from hy.models.float import HyFloat from hy.models.float import HyFloat
from hy.models.complex import HyComplex from hy.models.complex import HyComplex
from hy.models.symbol import HySymbol from hy.models.symbol import HySymbol
@ -79,6 +80,14 @@ def test_lex_expression_integer():
assert objs == [HyExpression([HySymbol("foo"), HyInteger(2)])] 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(): def test_lex_symbols():
""" Make sure that symbols are valid expressions""" """ Make sure that symbols are valid expressions"""
objs = tokenize("foo ") objs = tokenize("foo ")

View File

@ -1,8 +1,8 @@
; ;
(import-from tests.resources kwtest function-with-a-dash) (import [tests.resources [kwtest function-with-a-dash]]
(import-from os.path exists isdir isfile) [os.path [exists isdir isfile]]
(import-as sys systest) [sys :as systest])
(import sys) (import sys)
@ -376,6 +376,20 @@
(assert (= (slice [1 2 3 4 5]) [1 2 3 4 5]))) (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 [] (defn test-rest []
"NATIVE: test rest" "NATIVE: test rest"
(assert (= (rest [1 2 3 4 5]) [2 3 4 5]))) (assert (= (rest [1 2 3 4 5]) [2 3 4 5])))
@ -474,12 +488,15 @@
(defn test-fn-return [] (defn test-fn-return []
"NATIVE: test function return" "NATIVE: test function return"
(setv fn-test ((fn [] (fn [] (+ 1 1))))) (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 [] (defn test-let []
"NATIVE: test let works rightish" "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 [] (defn test-if-mangler []
@ -552,6 +569,7 @@
(do) (do)
((fn [] 1)))))) ((fn [] 1))))))
(defn test-keyword [] (defn test-keyword []
"NATIVE: test if keywords are recognised" "NATIVE: test if keywords are recognised"
@ -583,12 +601,15 @@
(setf test-payload (quote (+ x 2))) (setf test-payload (quote (+ x 2)))
(setf x 4) (setf x 4)
(assert (= 6 (eval test-payload))) (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 (= 1 (eval (quote 1))))
(assert (= "foobar" (eval (quote "foobar")))) (assert (= "foobar" (eval (quote "foobar"))))
(setv x (quote 42)) (setv x (quote 42))
(assert (= x (eval x)))) (assert (= x (eval x))))
(defn test-import-syntax [] (defn test-import-syntax []
"NATIVE: test the import syntax." "NATIVE: test the import syntax."
@ -616,3 +637,16 @@
(assert (= (dirname "/some/path") "/some")) (assert (= (dirname "/some/path") "/some"))
(assert (= op.dirname dirname)) (assert (= op.dirname dirname))
(assert (= dn 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