Merge pull request #761 from larme/fix-replace-hyobject
Add 'replace_hy_obj' to safely replace Hy objects
This commit is contained in:
commit
4ee308c5f2
@ -20,7 +20,7 @@
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
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.errors import HyIOError
|
||||
|
||||
@ -107,7 +107,7 @@ def hy_eval(hytree, namespace, module_name):
|
||||
foo.end_line = 0
|
||||
foo.start_column = 0
|
||||
foo.end_column = 0
|
||||
hytree.replace(foo)
|
||||
replace_hy_obj(hytree, foo)
|
||||
_ast, expr = hy_compile(hytree, module_name, get_expr=True)
|
||||
|
||||
# Spoof the positions in the generated ast...
|
||||
|
47
hy/macros.py
47
hy/macros.py
@ -18,20 +18,13 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import replace_hy_obj, wrap_value
|
||||
from hy.models.expression import HyExpression
|
||||
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 collections import defaultdict
|
||||
import sys
|
||||
|
||||
CORE_MACROS = [
|
||||
"hy.core.bootstrap",
|
||||
@ -106,37 +99,6 @@ def require(source_module, target_module):
|
||||
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):
|
||||
"""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)
|
||||
if m is not None:
|
||||
try:
|
||||
obj = _wrap_value(m(*ntree[1:]))
|
||||
obj = wrap_value(m(*ntree[1:]))
|
||||
|
||||
except HyTypeError as e:
|
||||
if e.expression is None:
|
||||
e.expression = tree
|
||||
@ -202,7 +165,7 @@ def macroexpand_1(tree, module_name):
|
||||
except Exception as e:
|
||||
msg = "expanding `" + str(tree[0]) + "': " + repr(e)
|
||||
raise HyMacroExpansionError(tree, msg)
|
||||
obj.replace(tree)
|
||||
replace_hy_obj(obj, tree)
|
||||
return obj
|
||||
|
||||
return ntree
|
||||
@ -223,4 +186,4 @@ def reader_macroexpand(char, tree, module_name):
|
||||
)
|
||||
|
||||
expr = _hy_reader[module_name][char](tree)
|
||||
return _wrap_value(expr).replace(tree)
|
||||
return replace_hy_obj(wrap_value(expr), tree)
|
||||
|
@ -35,3 +35,35 @@ class HyObject(object):
|
||||
raise TypeError("Can't replace a non Hy object with a Hy object")
|
||||
|
||||
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))
|
||||
|
@ -18,7 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import HyObject
|
||||
from hy.models import HyObject, _wrappers
|
||||
|
||||
|
||||
class HyComplex(HyObject, complex):
|
||||
@ -30,3 +30,5 @@ class HyComplex(HyObject, complex):
|
||||
def __new__(cls, number, *args, **kwargs):
|
||||
number = complex(number)
|
||||
return super(HyComplex, cls).__new__(cls, number)
|
||||
|
||||
_wrappers[complex] = HyComplex
|
||||
|
@ -18,8 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.macros import _wrap_value
|
||||
from hy.models import HyObject
|
||||
from hy.models import HyObject, replace_hy_obj, wrap_value
|
||||
from hy.models.expression import HyExpression
|
||||
from hy.models.symbol import HySymbol
|
||||
|
||||
@ -42,17 +41,17 @@ class HyCons(HyObject):
|
||||
if cdr[0] in ("unquote", "unquote_splice"):
|
||||
return super(HyCons, cls).__new__(cls)
|
||||
|
||||
return cdr.__class__([_wrap_value(car)] + cdr)
|
||||
return cdr.__class__([wrap_value(car)] + cdr)
|
||||
|
||||
elif cdr is None:
|
||||
return HyExpression([_wrap_value(car)])
|
||||
return HyExpression([wrap_value(car)])
|
||||
|
||||
else:
|
||||
return super(HyCons, cls).__new__(cls)
|
||||
|
||||
def __init__(self, car, cdr):
|
||||
self.car = _wrap_value(car)
|
||||
self.cdr = _wrap_value(cdr)
|
||||
self.car = wrap_value(car)
|
||||
self.cdr = wrap_value(cdr)
|
||||
|
||||
def __getitem__(self, n):
|
||||
if n == 0:
|
||||
@ -88,9 +87,9 @@ class HyCons(HyObject):
|
||||
|
||||
def replace(self, other):
|
||||
if self.car is not None:
|
||||
self.car.replace(other)
|
||||
replace_hy_obj(self.car, other)
|
||||
if self.cdr is not None:
|
||||
self.cdr.replace(other)
|
||||
replace_hy_obj(self.cdr, other)
|
||||
|
||||
HyObject.replace(self, other)
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import _wrappers, wrap_value
|
||||
from hy.models.list import HyList
|
||||
|
||||
|
||||
@ -37,3 +38,5 @@ class HyDict(HyList):
|
||||
|
||||
def items(self):
|
||||
return list(zip(self.keys(), self.values()))
|
||||
|
||||
_wrappers[dict] = lambda d: HyDict(wrap_value(x) for x in sum(d.items(), ()))
|
||||
|
@ -18,6 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import _wrappers, wrap_value
|
||||
from hy.models.list import HyList
|
||||
|
||||
|
||||
@ -28,3 +29,5 @@ class HyExpression(HyList):
|
||||
|
||||
def __repr__(self):
|
||||
return "(%s)" % (" ".join([repr(x) for x in self]))
|
||||
|
||||
_wrappers[HyExpression] = lambda e: HyExpression(wrap_value(x) for x in e)
|
||||
|
@ -18,7 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import HyObject
|
||||
from hy.models import HyObject, _wrappers
|
||||
|
||||
|
||||
class HyFloat(HyObject, float):
|
||||
@ -30,3 +30,5 @@ class HyFloat(HyObject, float):
|
||||
def __new__(cls, number, *args, **kwargs):
|
||||
number = float(number)
|
||||
return super(HyFloat, cls).__new__(cls, number)
|
||||
|
||||
_wrappers[float] = HyFloat
|
||||
|
@ -18,9 +18,11 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import HyObject
|
||||
from hy.models import HyObject, _wrappers
|
||||
from hy._compat import long_type
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class HyInteger(HyObject, long_type):
|
||||
"""
|
||||
@ -32,3 +34,9 @@ class HyInteger(HyObject, long_type):
|
||||
def __new__(cls, number, *args, **kwargs):
|
||||
number = long_type(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
|
||||
|
@ -18,7 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import HyObject
|
||||
from hy.models import HyObject, replace_hy_obj, _wrappers, wrap_value
|
||||
|
||||
|
||||
class HyList(HyObject, list):
|
||||
@ -28,7 +28,7 @@ class HyList(HyObject, list):
|
||||
|
||||
def replace(self, other):
|
||||
for x in self:
|
||||
x.replace(other)
|
||||
replace_hy_obj(x, other)
|
||||
|
||||
HyObject.replace(self, other)
|
||||
return self
|
||||
@ -49,3 +49,6 @@ class HyList(HyObject, list):
|
||||
|
||||
def __repr__(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)
|
||||
|
@ -18,7 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import HyObject
|
||||
from hy.models import HyObject, _wrappers
|
||||
from hy._compat import str_type
|
||||
|
||||
|
||||
@ -29,3 +29,5 @@ class HyString(HyObject, str_type):
|
||||
Python version.
|
||||
"""
|
||||
pass
|
||||
|
||||
_wrappers[str_type] = HyString
|
||||
|
@ -18,6 +18,7 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from hy.models import _wrappers
|
||||
from hy.models.string import HyString
|
||||
|
||||
|
||||
@ -28,3 +29,6 @@ class HySymbol(HyString):
|
||||
|
||||
def __init__(self, string):
|
||||
self += string
|
||||
|
||||
_wrappers[bool] = lambda x: HySymbol("True") if x else HySymbol("False")
|
||||
_wrappers[type(None)] = lambda foo: HySymbol("None")
|
||||
|
26
tests/models/test_replace_hy_obj.py
Normal file
26
tests/models/test_replace_hy_obj.py
Normal 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)])
|
@ -3,18 +3,18 @@ from hy.models.integer import HyInteger
|
||||
from hy.models.list import HyList
|
||||
from hy.models.expression import HyExpression
|
||||
|
||||
from hy.macros import _wrap_value
|
||||
from hy.models import wrap_value
|
||||
|
||||
|
||||
def test_wrap_long_type():
|
||||
""" Test conversion of integers."""
|
||||
wrapped = _wrap_value(long_type(0))
|
||||
wrapped = wrap_value(long_type(0))
|
||||
assert type(wrapped) == HyInteger
|
||||
|
||||
|
||||
def test_wrap_tuple():
|
||||
""" Test conversion of tuples."""
|
||||
wrapped = _wrap_value((HyInteger(0),))
|
||||
wrapped = wrap_value((HyInteger(0),))
|
||||
assert type(wrapped) == HyList
|
||||
assert type(wrapped[0]) == HyInteger
|
||||
assert wrapped == HyList([HyInteger(0)])
|
||||
@ -22,7 +22,7 @@ def test_wrap_tuple():
|
||||
|
||||
def test_wrap_nested_expr():
|
||||
""" 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[0]) == HyInteger
|
||||
assert wrapped == HyExpression([HyInteger(0)])
|
Loading…
Reference in New Issue
Block a user