Merge pull request #761 from larme/fix-replace-hyobject

Add 'replace_hy_obj' to safely replace Hy objects
This commit is contained in:
Morten Linderud 2015-07-23 14:09:56 +02:00
commit 4ee308c5f2
14 changed files with 109 additions and 62 deletions

View File

@ -20,7 +20,7 @@
# DEALINGS IN THE SOFTWARE. # DEALINGS IN THE SOFTWARE.
from hy.compiler import hy_compile, HyTypeError from hy.compiler import hy_compile, HyTypeError
from hy.models import HyObject from hy.models import HyObject, replace_hy_obj
from hy.lex import tokenize, LexException from hy.lex import tokenize, LexException
from hy.errors import HyIOError from hy.errors import HyIOError
@ -107,7 +107,7 @@ def hy_eval(hytree, namespace, module_name):
foo.end_line = 0 foo.end_line = 0
foo.start_column = 0 foo.start_column = 0
foo.end_column = 0 foo.end_column = 0
hytree.replace(foo) replace_hy_obj(hytree, foo)
_ast, expr = hy_compile(hytree, module_name, get_expr=True) _ast, expr = hy_compile(hytree, module_name, get_expr=True)
# Spoof the positions in the generated ast... # Spoof the positions in the generated ast...

View File

@ -18,20 +18,13 @@
# 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.
from hy.models import replace_hy_obj, wrap_value
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
from hy.models.string import HyString from hy.models.string import HyString
from hy.models.symbol import HySymbol
from hy.models.list import HyList
from hy.models.integer import HyInteger
from hy.models.float import HyFloat
from hy.models.complex import HyComplex
from hy.models.dict import HyDict
from hy._compat import str_type, long_type
from hy.errors import HyTypeError, HyMacroExpansionError from hy.errors import HyTypeError, HyMacroExpansionError
from collections import defaultdict from collections import defaultdict
import sys
CORE_MACROS = [ CORE_MACROS = [
"hy.core.bootstrap", "hy.core.bootstrap",
@ -106,37 +99,6 @@ def require(source_module, target_module):
reader_refs[name] = reader reader_refs[name] = reader
# type -> wrapping function mapping for _wrap_value
_wrappers = {
int: HyInteger,
bool: lambda x: HySymbol("True") if x else HySymbol("False"),
float: HyFloat,
complex: HyComplex,
str_type: HyString,
dict: lambda d: HyDict(_wrap_value(x) for x in sum(d.items(), ())),
list: lambda l: HyList(_wrap_value(x) for x in l),
tuple: lambda t: HyList(_wrap_value(x) for x in t),
type(None): lambda foo: HySymbol("None"),
HyExpression: lambda e: HyExpression(_wrap_value(x) for x in e),
}
if sys.version_info[0] < 3: # do not add long on python3
_wrappers[long_type] = HyInteger
def _wrap_value(x):
"""Wrap `x` into the corresponding Hy type.
This allows a macro to return an unquoted expression transparently.
"""
wrapper = _wrappers.get(type(x))
if wrapper is None:
return x
else:
return wrapper(x)
def load_macros(module_name): def load_macros(module_name):
"""Load the hy builtin macros for module `module_name`. """Load the hy builtin macros for module `module_name`.
@ -194,7 +156,8 @@ def macroexpand_1(tree, module_name):
m = _hy_macros[None].get(fn) m = _hy_macros[None].get(fn)
if m is not None: if m is not None:
try: try:
obj = _wrap_value(m(*ntree[1:])) obj = wrap_value(m(*ntree[1:]))
except HyTypeError as e: except HyTypeError as e:
if e.expression is None: if e.expression is None:
e.expression = tree e.expression = tree
@ -202,7 +165,7 @@ def macroexpand_1(tree, module_name):
except Exception as e: except Exception as e:
msg = "expanding `" + str(tree[0]) + "': " + repr(e) msg = "expanding `" + str(tree[0]) + "': " + repr(e)
raise HyMacroExpansionError(tree, msg) raise HyMacroExpansionError(tree, msg)
obj.replace(tree) replace_hy_obj(obj, tree)
return obj return obj
return ntree return ntree
@ -223,4 +186,4 @@ def reader_macroexpand(char, tree, module_name):
) )
expr = _hy_reader[module_name][char](tree) expr = _hy_reader[module_name][char](tree)
return _wrap_value(expr).replace(tree) return replace_hy_obj(wrap_value(expr), tree)

View File

@ -35,3 +35,35 @@ class HyObject(object):
raise TypeError("Can't replace a non Hy object with a Hy object") raise TypeError("Can't replace a non Hy object with a Hy object")
return self return self
_wrappers = {}
def wrap_value(x):
"""Wrap `x` into the corresponding Hy type.
This allows replace_hy_obj to convert a non Hy object to a Hy object.
This also allows a macro to return an unquoted expression transparently.
"""
wrapper = _wrappers.get(type(x))
if wrapper is None:
return x
else:
return wrapper(x)
def replace_hy_obj(obj, other):
if isinstance(obj, HyObject):
return obj.replace(other)
wrapped_obj = wrap_value(obj)
if isinstance(wrapped_obj, HyObject):
return wrapped_obj.replace(other)
else:
raise TypeError("Don't know how to wrap a %s object to a HyObject"
% type(obj))

View File

@ -18,7 +18,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.
from hy.models import HyObject from hy.models import HyObject, _wrappers
class HyComplex(HyObject, complex): class HyComplex(HyObject, complex):
@ -30,3 +30,5 @@ class HyComplex(HyObject, complex):
def __new__(cls, number, *args, **kwargs): def __new__(cls, number, *args, **kwargs):
number = complex(number) number = complex(number)
return super(HyComplex, cls).__new__(cls, number) return super(HyComplex, cls).__new__(cls, number)
_wrappers[complex] = HyComplex

View File

@ -18,8 +18,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.
from hy.macros import _wrap_value from hy.models import HyObject, replace_hy_obj, wrap_value
from hy.models import HyObject
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
from hy.models.symbol import HySymbol from hy.models.symbol import HySymbol
@ -42,17 +41,17 @@ class HyCons(HyObject):
if cdr[0] in ("unquote", "unquote_splice"): if cdr[0] in ("unquote", "unquote_splice"):
return super(HyCons, cls).__new__(cls) return super(HyCons, cls).__new__(cls)
return cdr.__class__([_wrap_value(car)] + cdr) return cdr.__class__([wrap_value(car)] + cdr)
elif cdr is None: elif cdr is None:
return HyExpression([_wrap_value(car)]) return HyExpression([wrap_value(car)])
else: else:
return super(HyCons, cls).__new__(cls) return super(HyCons, cls).__new__(cls)
def __init__(self, car, cdr): def __init__(self, car, cdr):
self.car = _wrap_value(car) self.car = wrap_value(car)
self.cdr = _wrap_value(cdr) self.cdr = wrap_value(cdr)
def __getitem__(self, n): def __getitem__(self, n):
if n == 0: if n == 0:
@ -88,9 +87,9 @@ class HyCons(HyObject):
def replace(self, other): def replace(self, other):
if self.car is not None: if self.car is not None:
self.car.replace(other) replace_hy_obj(self.car, other)
if self.cdr is not None: if self.cdr is not None:
self.cdr.replace(other) replace_hy_obj(self.cdr, other)
HyObject.replace(self, other) HyObject.replace(self, other)

View File

@ -18,6 +18,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.
from hy.models import _wrappers, wrap_value
from hy.models.list import HyList from hy.models.list import HyList
@ -37,3 +38,5 @@ class HyDict(HyList):
def items(self): def items(self):
return list(zip(self.keys(), self.values())) return list(zip(self.keys(), self.values()))
_wrappers[dict] = lambda d: HyDict(wrap_value(x) for x in sum(d.items(), ()))

View File

@ -18,6 +18,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.
from hy.models import _wrappers, wrap_value
from hy.models.list import HyList from hy.models.list import HyList
@ -28,3 +29,5 @@ class HyExpression(HyList):
def __repr__(self): def __repr__(self):
return "(%s)" % (" ".join([repr(x) for x in self])) return "(%s)" % (" ".join([repr(x) for x in self]))
_wrappers[HyExpression] = lambda e: HyExpression(wrap_value(x) for x in e)

View File

@ -18,7 +18,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.
from hy.models import HyObject from hy.models import HyObject, _wrappers
class HyFloat(HyObject, float): class HyFloat(HyObject, float):
@ -30,3 +30,5 @@ class HyFloat(HyObject, float):
def __new__(cls, number, *args, **kwargs): def __new__(cls, number, *args, **kwargs):
number = float(number) number = float(number)
return super(HyFloat, cls).__new__(cls, number) return super(HyFloat, cls).__new__(cls, number)
_wrappers[float] = HyFloat

View File

@ -18,9 +18,11 @@
# 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.
from hy.models import HyObject from hy.models import HyObject, _wrappers
from hy._compat import long_type from hy._compat import long_type
import sys
class HyInteger(HyObject, long_type): class HyInteger(HyObject, long_type):
""" """
@ -32,3 +34,9 @@ class HyInteger(HyObject, long_type):
def __new__(cls, number, *args, **kwargs): def __new__(cls, number, *args, **kwargs):
number = long_type(number) number = long_type(number)
return super(HyInteger, cls).__new__(cls, number) return super(HyInteger, cls).__new__(cls, number)
_wrappers[int] = HyInteger
if sys.version_info[0] < 3: # do not add long on python3
_wrappers[long_type] = HyInteger

View File

@ -18,7 +18,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.
from hy.models import HyObject from hy.models import HyObject, replace_hy_obj, _wrappers, wrap_value
class HyList(HyObject, list): class HyList(HyObject, list):
@ -28,7 +28,7 @@ class HyList(HyObject, list):
def replace(self, other): def replace(self, other):
for x in self: for x in self:
x.replace(other) replace_hy_obj(x, other)
HyObject.replace(self, other) HyObject.replace(self, other)
return self return self
@ -49,3 +49,6 @@ class HyList(HyObject, list):
def __repr__(self): def __repr__(self):
return "[%s]" % (" ".join([repr(x) for x in self])) return "[%s]" % (" ".join([repr(x) for x in self]))
_wrappers[list] = lambda l: HyList(wrap_value(x) for x in l)
_wrappers[tuple] = lambda t: HyList(wrap_value(x) for x in t)

View File

@ -18,7 +18,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.
from hy.models import HyObject from hy.models import HyObject, _wrappers
from hy._compat import str_type from hy._compat import str_type
@ -29,3 +29,5 @@ class HyString(HyObject, str_type):
Python version. Python version.
""" """
pass pass
_wrappers[str_type] = HyString

View File

@ -18,6 +18,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.
from hy.models import _wrappers
from hy.models.string import HyString from hy.models.string import HyString
@ -28,3 +29,6 @@ class HySymbol(HyString):
def __init__(self, string): def __init__(self, string):
self += string self += string
_wrappers[bool] = lambda x: HySymbol("True") if x else HySymbol("False")
_wrappers[type(None)] = lambda foo: HySymbol("None")

View File

@ -0,0 +1,26 @@
from hy._compat import long_type, str_type
from hy.models.string import HyString
from hy.models.integer import HyInteger
from hy.models.list import HyList
from hy.models import replace_hy_obj
def test_replace_long_type():
""" Test replacing integers."""
replaced = replace_hy_obj(long_type(0), HyInteger(13))
assert replaced == HyInteger(0)
def test_replace_string_type():
"""Test replacing python string"""
replaced = replace_hy_obj(str_type("foo"), HyString("bar"))
assert replaced == HyString("foo")
def test_replace_tuple():
""" Test replacing tuples."""
replaced = replace_hy_obj((long_type(0), ), HyInteger(13))
assert type(replaced) == HyList
assert type(replaced[0]) == HyInteger
assert replaced == HyList([HyInteger(0)])

View File

@ -3,18 +3,18 @@ from hy.models.integer import HyInteger
from hy.models.list import HyList from hy.models.list import HyList
from hy.models.expression import HyExpression from hy.models.expression import HyExpression
from hy.macros import _wrap_value from hy.models import wrap_value
def test_wrap_long_type(): def test_wrap_long_type():
""" Test conversion of integers.""" """ Test conversion of integers."""
wrapped = _wrap_value(long_type(0)) wrapped = wrap_value(long_type(0))
assert type(wrapped) == HyInteger assert type(wrapped) == HyInteger
def test_wrap_tuple(): def test_wrap_tuple():
""" Test conversion of tuples.""" """ Test conversion of tuples."""
wrapped = _wrap_value((HyInteger(0),)) wrapped = wrap_value((HyInteger(0),))
assert type(wrapped) == HyList assert type(wrapped) == HyList
assert type(wrapped[0]) == HyInteger assert type(wrapped[0]) == HyInteger
assert wrapped == HyList([HyInteger(0)]) assert wrapped == HyList([HyInteger(0)])
@ -22,7 +22,7 @@ def test_wrap_tuple():
def test_wrap_nested_expr(): def test_wrap_nested_expr():
""" Test conversion of HyExpressions with embedded non-HyObjects.""" """ Test conversion of HyExpressions with embedded non-HyObjects."""
wrapped = _wrap_value(HyExpression([long_type(0)])) wrapped = wrap_value(HyExpression([long_type(0)]))
assert type(wrapped) == HyExpression assert type(wrapped) == HyExpression
assert type(wrapped[0]) == HyInteger assert type(wrapped[0]) == HyInteger
assert wrapped == HyExpression([HyInteger(0)]) assert wrapped == HyExpression([HyInteger(0)])