Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-promote values to HyObjects in the compiler #1314

Merged
merged 6 commits into from
Jul 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changes from 0.13.0
* Single-character "sharp macros" changed to "tag macros", which can have
longer names
* Periods are no longer allowed in keywords
* `eval` is now a function instead of a special form
* The compiler now automatically promotes values to Hy model objects
as necessary, so you can write ``(eval `(+ 1 ~n))`` instead of
``(eval `(+ 1 ~(HyInteger n)))``

[ Bug Fixes ]
* Numeric literals are no longer parsed as symbols when followed by a dot
Expand Down
21 changes: 0 additions & 21 deletions docs/language/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -863,27 +863,6 @@ doto
=> collection
[2 1]

eval
----

``eval`` evaluates a quoted expression and returns the value. The optional
second and third arguments specify the dictionary of globals to use and the
module name. The globals dictionary defaults to ``(local)`` and the module name
defaults to the name of the current module.

.. code-block:: clj

=> (eval '(print "Hello World"))
"Hello World"

If you want to evaluate a string, use ``read-str`` to convert it to a
form first:

.. code-block:: clj

=> (eval (read-str "(+ 1 1)"))
2


eval-and-compile
----------------
Expand Down
24 changes: 24 additions & 0 deletions docs/language/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,30 @@ Returns ``True`` if *coll* is empty. Equivalent to ``(= 0 (len coll))``.
False


.. _eval-fn:

eval
----

``eval`` evaluates a quoted expression and returns the value. The optional
second and third arguments specify the dictionary of globals to use and the
module name. The globals dictionary defaults to ``(local)`` and the module name
defaults to the name of the current module.

.. code-block:: clj

=> (eval '(print "Hello World"))
"Hello World"

If you want to evaluate a string, use ``read-str`` to convert it to a
form first:

.. code-block:: clj

=> (eval (read-str "(+ 1 1)"))
2


.. _every?-fn:

every?
Expand Down
7 changes: 2 additions & 5 deletions hy/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import hy

from hy.lex import LexException, PrematureEndOfInput, tokenize
from hy.lex import LexException, PrematureEndOfInput
from hy.lex.parser import hy_symbol_mangle
from hy.compiler import HyTypeError
from hy.importer import (hy_eval, import_buffer_to_module,
Expand Down Expand Up @@ -77,12 +77,9 @@ def runsource(self, source, filename='<input>', symbol='single'):
global SIMPLE_TRACEBACKS
try:
try:
tokens = tokenize(source)
do = import_buffer_to_hst(source)
except PrematureEndOfInput:
return True
do = HyExpression([HySymbol('do')] + tokens)
do.start_line = do.end_line = do.start_column = do.end_column = 1
do.replace(do)
except LexException as e:
if e.source is None:
e.source = source
Expand Down
96 changes: 43 additions & 53 deletions hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

from hy.models import (HyObject, HyExpression, HyKeyword, HyInteger, HyComplex,
HyString, HyBytes, HySymbol, HyFloat, HyList, HySet,
HyDict, HyCons)
HyDict, HyCons, wrap_value)
from hy.errors import HyCompileError, HyTypeError

from hy.lex.parser import hy_symbol_mangle

import hy.macros
from hy._compat import (
str_type, bytes_type, long_type, PY3, PY34, PY35, raise_empty)
str_type, string_types, bytes_type, long_type, PY3, PY34, PY35,
raise_empty)
from hy.macros import require, macroexpand, tag_macroexpand
import hy.importer

Expand Down Expand Up @@ -110,6 +111,19 @@ def builds_if(_type, condition):
return lambda fn: fn


def spoof_positions(obj):
if not isinstance(obj, HyObject) or isinstance(obj, HyCons):
return
if not hasattr(obj, "start_column"):
obj.start_column = 0
if not hasattr(obj, "start_line"):
obj.start_line = 0
if (hasattr(obj, "__iter__") and
not isinstance(obj, (string_types, bytes_type))):
for x in obj:
spoof_positions(x)


class Result(object):
"""
Smart representation of the result of a hy->AST compilation
Expand Down Expand Up @@ -378,23 +392,23 @@ def imports_as_stmts(self, expr):
ret = Result()
for module, names in self.imports.items():
if None in names:
ret += self.compile([
HyExpression([
e = HyExpression([
HySymbol("import"),
HySymbol(module),
]).replace(expr)
])
spoof_positions(e)
ret += self.compile(e)
names = sorted(name for name in names if name)
if names:
ret += self.compile([
HyExpression([
e = HyExpression([
HySymbol("import"),
HyList([
HySymbol(module),
HyList([HySymbol(name) for name in names])
])
]).replace(expr)
])
spoof_positions(e)
ret += self.compile(e)
self.imports = defaultdict(set)
return ret.stmts

Expand All @@ -404,6 +418,11 @@ def compile_atom(self, atom_type, atom):
if not isinstance(ret, Result):
ret = Result() + ret
return ret
if not isinstance(atom, HyObject):
atom = wrap_value(atom)
if isinstance(atom, HyObject):
spoof_positions(atom)
return self.compile_atom(type(atom), atom)

def compile(self, tree):
try:
Expand Down Expand Up @@ -602,12 +621,6 @@ def _storeize(self, expr, name, func=None):
ast.copy_location(new_name, name)
return new_name

@builds(list)
def compile_raw_list(self, entries):
ret = self._compile_branch(entries)
ret += ret.expr_as_stmt()
return ret

def _render_quoted_form(self, form, level):
"""
Render a quoted form as a new HyExpression.
Expand Down Expand Up @@ -706,31 +719,6 @@ def compile_unquote(self, expr):
raise HyTypeError(expr,
"`%s' can't be used at the top-level" % expr[0])

@builds("eval")
@checkargs(min=1, max=3)
def compile_eval(self, expr):
expr.pop(0)

if not isinstance(expr[0], (HyExpression, HySymbol)):
raise HyTypeError(expr, "expression expected as first argument")

elist = [HySymbol("hy_eval")] + [expr[0]]
if len(expr) >= 2:
elist.append(expr[1])
else:
elist.append(HyExpression([HySymbol("locals")]))

if len(expr) == 3:
elist.append(expr[2])
else:
elist.append(HyString(self.module_name))

ret = self.compile(HyExpression(elist).replace(expr))

ret.add_imports("hy.importer", ["hy_eval"])

return ret

@builds("do")
def compile_do(self, expression):
expression.pop(0)
Expand Down Expand Up @@ -766,6 +754,7 @@ def compile_raise_expression(self, expr):
return ret

@builds("try")
@checkargs(min=2)
def compile_try_expression(self, expr):
expr.pop(0) # try

Expand Down Expand Up @@ -1858,6 +1847,7 @@ def _compile_maths_expression(self, expression):
op = ops[expression.pop(0)]
right_associative = op == ast.Pow

lineno, col_offset = expression.start_line, expression.start_column
if right_associative:
expression = expression[::-1]
ret = self.compile(expression.pop(0))
Expand All @@ -1870,8 +1860,8 @@ def _compile_maths_expression(self, expression):
ret += ast.BinOp(left=left_expr,
op=op(),
right=right_expr,
lineno=child.start_line,
col_offset=child.start_column)
lineno=lineno,
col_offset=col_offset)
return ret

@builds("**")
Expand Down Expand Up @@ -2619,21 +2609,21 @@ def hy_compile(tree, module_name, root=ast.Module, get_expr=False):
body = []
expr = None

if not (isinstance(tree, HyObject) or type(tree) is list):
raise HyCompileError("tree must be a HyObject or a list")
if not isinstance(tree, HyObject):
tree = wrap_value(tree)
if not isinstance(tree, HyObject):
raise HyCompileError("`tree` must be a HyObject or capable of "
"being promoted to one")
spoof_positions(tree)

if isinstance(tree, HyObject) or tree:
compiler = HyASTCompiler(module_name)
result = compiler.compile(tree)
expr = result.force_expr
compiler = HyASTCompiler(module_name)
result = compiler.compile(tree)
expr = result.force_expr

if not get_expr:
result += result.expr_as_stmt()
if not get_expr:
result += result.expr_as_stmt()

# We need to test that the type is *exactly* `list` because we don't
# want to do `tree[0]` on HyList or such.
spoof_tree = tree[0] if type(tree) is list else tree
body = compiler.imports_as_stmts(spoof_tree) + result.stmts
body = compiler.imports_as_stmts(tree) + result.stmts

ret = root(body=body)

Expand Down
18 changes: 5 additions & 13 deletions hy/core/language.hy
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
(import [hy._compat [long-type]]) ; long for python2, int for python3
(import [hy.models [HyCons HySymbol HyKeyword]])
(import [hy.lex [LexException PrematureEndOfInput tokenize]])
(import [hy.compiler [HyASTCompiler]])
(import [hy.compiler [HyASTCompiler spoof-positions]])
(import [hy.importer [hy-eval :as eval]])

(defn butlast [coll]
"Returns coll except of last element."
Expand Down Expand Up @@ -74,8 +75,8 @@
(import astor)
(import hy.compiler)

(fake-source-positions tree)
(setv compiled (hy.compiler.hy_compile tree (calling-module-name)))
(spoof-positions tree)
(setv compiled (hy.compiler.hy-compile tree (calling-module-name)))
((if codegen
astor.codegen.to_source
astor.dump)
Expand Down Expand Up @@ -174,15 +175,6 @@
"Return true if (pred x) is logical true for every x in coll, else false"
(all (map pred coll)))

(defn fake-source-positions [tree]
"Fake the source positions for a given tree"
(if (coll? tree)
(for* [subtree tree]
(fake-source-positions subtree)))
(for* [attr '[start-line end-line start-column end-column]]
(if (not (hasattr tree attr))
(setattr tree attr 1))))

(defn flatten [coll]
"Return a single flat list expanding all members of coll"
(if (coll? coll)
Expand Down Expand Up @@ -469,7 +461,7 @@
(def *exports*
'[*map accumulate butlast calling-module-name chain coll? combinations
comp complement compress cons cons? constantly count cycle dec distinct
disassemble drop drop-last drop-while empty? even? every? first filter
disassemble drop drop-last drop-while empty? eval even? every? first filter
flatten float? fraction gensym group-by identity inc input instance?
integer integer? integer-char? interleave interpose islice iterable?
iterate iterator? juxt keyword keyword? last list* macroexpand
Expand Down
27 changes: 24 additions & 3 deletions hy/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# license. See the LICENSE.

from hy.compiler import hy_compile, HyTypeError
from hy.models import HyObject, replace_hy_obj
from hy.models import HyObject, HyExpression, HySymbol, replace_hy_obj
from hy.lex import tokenize, LexException
from hy.errors import HyIOError

Expand All @@ -14,6 +14,7 @@
import imp
import sys
import ast
import inspect
import os
import __future__

Expand All @@ -31,7 +32,7 @@ def ast_compile(ast, filename, mode):

def import_buffer_to_hst(buf):
"""Import content from buf and return a Hy AST."""
return tokenize(buf + "\n")
return HyExpression([HySymbol("do")] + tokenize(buf + "\n"))


def import_file_to_hst(fpath):
Expand Down Expand Up @@ -142,7 +143,27 @@ def import_buffer_to_module(module_name, buf):
return mod


def hy_eval(hytree, namespace, module_name, ast_callback=None):
def hy_eval(hytree, namespace=None, module_name=None, ast_callback=None):
"""``eval`` evaluates a quoted expression and returns the value. The optional
second and third arguments specify the dictionary of globals to use and the
module name. The globals dictionary defaults to ``(local)`` and the module
name defaults to the name of the current module.

=> (eval '(print "Hello World"))
"Hello World"

If you want to evaluate a string, use ``read-str`` to convert it to a
form first:

=> (eval (read-str "(+ 1 1)"))
2"""
if namespace is None:
frame = inspect.stack()[1][0]
namespace = inspect.getargvalues(frame).locals
if module_name is None:
m = inspect.getmodule(inspect.stack()[1][0])
module_name = '__eval__' if m is None else m.__name__

foo = HyObject()
foo.start_line = 0
foo.end_line = 0
Expand Down
Loading