Add 'replace_hy_obj' to safely replace Hy objects

Currently '.replace' method is used to replace hy objects. This is not
safe when we are not sure if the 'obj' in 'obj.replace(other)' is an
instance of HyObject.

In these cases, we can use function 'replace_hy_obj(obj, other)'
instead. This function will try to wrap 'obj' if it's not an instance of
HyObject.

This also means that we need a wrapping function in hy.models'. Hence I
moved the '_wrap_value' function from hy.macros into hy.models. To avoid
circular importing, the wrapper functions are provided individually by
each model type's own file.
This commit is contained in:
Zhao Shenyang 2015-01-24 08:16:38 +08:00
parent 8d6f9c3d84
commit 2375392962
12 changed files with 78 additions and 50 deletions

View File

@ -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 io import open
@ -103,7 +103,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...

View File

@ -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 replace_hy_obj, wrap_value
from hy.models.expression import HyExpression
from hy.models.string import HyString
from hy.models.symbol import HySymbol
@ -106,37 +107,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 +164,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 +173,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 +194,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)

View File

@ -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("Can't replace a Hy object with a non wrappable non Hy object")

View File

@ -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

View File

@ -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)

View File

@ -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(), ()))

View File

@ -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)

View File

@ -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

View File

@ -18,9 +18,10 @@
# 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 +33,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

View File

@ -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)

View File

@ -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

View File

@ -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")