Merge branch 'master' of github.com:hylang/hy
This commit is contained in:
commit
5ce87d53b1
@ -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:
|
||||
|
4
bin/hy
4
bin/hy
@ -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
|
||||
|
@ -157,7 +157,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)
|
||||
|
@ -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,15 @@ class HoistableMangle(hy.mangle.Mangle):
|
||||
|
||||
|
||||
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"]
|
||||
# ^^ we're just looking for functions
|
||||
ignore = ["def", "decorate_with", "setf", "setv", "foreach", "do"]
|
||||
# ^^ these guys don't affect us, really.
|
||||
|
||||
def __init__(self):
|
||||
self.series = 0
|
||||
@ -51,9 +77,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 +95,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 +110,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])])
|
||||
|
@ -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())
|
||||
|
42
hy/mangle.py
42
hy/mangle.py
@ -35,38 +35,55 @@ class Mangle(object):
|
||||
"""
|
||||
|
||||
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
|
||||
"""
|
||||
Main function of self.mangle, which is called over and over. This
|
||||
is used to beat the tree until it stops moving.
|
||||
"""
|
||||
|
||||
scopable = ["fn", "if"]
|
||||
# Not actually scope, more like code branch.
|
||||
|
||||
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:
|
||||
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 +94,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,6 +114,12 @@ class Mangle(object):
|
||||
return self.stack.pop(0)
|
||||
|
||||
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
|
||||
while unfinished:
|
||||
self.root = tree
|
||||
|
@ -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
|
||||
|
@ -583,7 +583,9 @@
|
||||
(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))
|
||||
@ -616,3 +618,10 @@
|
||||
(assert (= (dirname "/some/path") "/some"))
|
||||
(assert (= op.dirname 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
8
tests/test_bin.py
Normal 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
|
Loading…
Reference in New Issue
Block a user