Merge branch 'master' of github.com:hylang/hy

This commit is contained in:
John Jacobsen 2013-04-21 09:33:16 -05:00
commit 5ce87d53b1
9 changed files with 142 additions and 60 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:

4
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

View File

@ -157,7 +157,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)

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,15 @@ class HoistableMangle(hy.mangle.Mangle):
class FunctionMangle(HoistableMangle): class FunctionMangle(HoistableMangle):
"""
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)
"""
hoistable = ["fn"] hoistable = ["fn"]
# ^^ we're just looking for functions
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 +77,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 +95,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 +110,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

@ -35,38 +35,55 @@ class Mangle(object):
""" """
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): def _mangle(self, tree):
# Things that force a scope push to go into: """
# Main function of self.mangle, which is called over and over. This
# - Functions is used to beat the tree until it stops moving.
# - If """
scopable = ["fn", "if"]
scoped = False
scopable = ["fn", "if"]
# Not actually scope, more like code branch.
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 scopable:
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 +94,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,6 +114,12 @@ class Mangle(object):
return self.stack.pop(0) return self.stack.pop(0)
def mangle(self, tree): def mangle(self, tree):
"""
Magic external entry point.
We mangle until the tree stops moving (we don't get a TreeChanged
Exception during mangle)
"""
unfinished = True unfinished = True
while unfinished: while unfinished:
self.root = tree self.root = tree

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

@ -583,7 +583,9 @@
(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))
@ -616,3 +618,10 @@
(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-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