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.
|
# 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...
|
||||||
|
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
|
# 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)
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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(), ()))
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
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.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)])
|
Loading…
Reference in New Issue
Block a user