Merge pull request #1360 from gilch/model-repr

proper reprs for Hy models
This commit is contained in:
Ryan Gonzalez 2017-09-18 08:45:28 -05:00 committed by GitHub
commit db210929d0
7 changed files with 509 additions and 144 deletions

View File

@ -22,12 +22,18 @@ Example:
.. code-block:: hy
=> (import [hy.contrib.walk [walk]])
=> (setv a '(a b c d e f))
=> (walk ord identity a)
(97 98 99 100 101 102)
=> (walk ord first a)
97
=> (import [hy.contrib.walk [walk]])
=> (setv a '(a b c d e f))
=> (walk ord identity a)
HyExpression([
97,
98,
99,
100,
101,
102])
=> (walk ord first a)
97
postwalk
---------
@ -41,25 +47,73 @@ each sub-form, uses ``f`` 's return value in place of the original.
.. code-block:: hy
=> (import [hy.contrib.walk [postwalk]])
=> (def trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
(print "Walking:" x)
x )
=> (postwalk walking trail)
Walking: 1
Walking: 2
Walking: 3
Walking: (1 2 3)
Walking: 4
Walking: 5
Walking: 6
Walking: 7
Walking: (7)
Walking: (5 6 [7])
Walking: (4 [5 6 [7]])
Walking: ([1 2 3] [4 [5 6 [7]]])
([1 2 3] [4 [5 6 [7]]])
=> (import [hy.contrib.walk [postwalk]])
=> (def trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
... (print "Walking:" x :sep "\n")
... x)
=> (postwalk walking trail)
Walking:
1
Walking:
2
Walking:
3
Walking:
HyExpression([
HyInteger(1),
HyInteger(2),
HyInteger(3)])
Walking:
4
Walking:
5
Walking:
6
Walking:
7
Walking:
HyExpression([
HyInteger(7)])
Walking:
HyExpression([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])
Walking:
HyExpression([
HyInteger(4),
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])])
Walking:
HyExpression([
HyList([
HyInteger(1),
HyInteger(2),
HyInteger(3)]),
HyList([
HyInteger(4),
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])])])
HyExpression([
HyList([
HyInteger(1),
HyInteger(2),
HyInteger(3)]),
HyList([
HyInteger(4),
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])])])
prewalk
--------
@ -73,22 +127,70 @@ each sub-form, uses ``f`` 's return value in place of the original.
.. code-block:: hy
=> (import [hy.contrib.walk [prewalk]])
=> (def trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
(print "Walking:" x)
x )
=> (prewalk walking trail)
Walking: ([1 2 3] [4 [5 6 [7]]])
Walking: [1 2 3]
Walking: 1
Walking: 2
Walking: 3
Walking: [4 [5 6 [7]]]
Walking: 4
Walking: [5 6 [7]]
Walking: 5
Walking: 6
Walking: [7]
Walking: 7
([1 2 3] [4 [5 6 [7]]])
=> (import [hy.contrib.walk [prewalk]])
=> (def trail '([1 2 3] [4 [5 6 [7]]]))
=> (defn walking [x]
... (print "Walking:" x :sep "\n")
... x)
=> (prewalk walking trail)
Walking:
HyExpression([
HyList([
HyInteger(1),
HyInteger(2),
HyInteger(3)]),
HyList([
HyInteger(4),
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])])])
Walking:
HyList([
HyInteger(1),
HyInteger(2),
HyInteger(3)])
Walking:
1
Walking:
2
Walking:
3
Walking:
HyList([
HyInteger(4),
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])])
Walking:
4
Walking:
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])
Walking:
5
Walking:
6
Walking:
HyList([
HyInteger(7)])
Walking:
7
HyExpression([
HyList([
HyInteger(1),
HyInteger(2),
HyInteger(3)]),
HyList([
HyInteger(4),
HyList([
HyInteger(5),
HyInteger(6),
HyList([
HyInteger(7)])])])])

View File

@ -890,7 +890,7 @@ doto
.. code-block:: clj
=> (doto [] (.append 1) (.append 2) .reverse)
[2 1]
[2, 1]
.. code-block:: clj
@ -899,7 +899,7 @@ doto
=> (.append collection 2)
=> (.reverse collection)
=> collection
[2 1]
[2, 1]
eval-and-compile
@ -1383,9 +1383,10 @@ alternatively be written using the apostrophe (``'``) symbol.
.. code-block:: clj
=> (setv x '(print "Hello World"))
; variable x is set to expression & not evaluated
=> x
(u'print' u'Hello World')
=> x ; varible x is set to unevaluated expression
HyExpression([
HySymbol('print'),
HyString('Hello World')])
=> (eval x)
Hello World
@ -1694,12 +1695,17 @@ is aliased to the tilde (``~``) symbol.
.. code-block:: clj
(def name "Cuddles")
(quasiquote (= name (unquote name)))
;=> (u'=' u'name' u'Cuddles')
`(= name ~name)
;=> (u'=' u'name' u'Cuddles')
=> (setv nickname "Cuddles")
=> (quasiquote (= nickname (unquote nickname)))
HyExpression([
HySymbol('='),
HySymbol('nickname'),
'Cuddles'])
=> `(= nickname ~nickname)
HyExpression([
HySymbol('='),
HySymbol('nickname'),
'Cuddles'])
unquote-splice
@ -1715,15 +1721,25 @@ into the form. ``unquote-splice`` is aliased to the ``~@`` syntax.
.. code-block:: clj
(def nums [1 2 3 4])
(quasiquote (+ (unquote-splice nums)))
;=> ('+' 1 2 3 4)
`(+ ~@nums)
;=> ('+' 1 2 3 4)
`[1 2 ~@(if (< (nth nums 0) 0) nums)]
;=> ('+' 1 2)
=> (setv nums [1 2 3 4])
=> (quasiquote (+ (unquote-splice nums)))
HyExpression([
HySymbol('+'),
1,
2,
3,
4])
=> `(+ ~@nums)
HyExpression([
HySymbol('+'),
1,
2,
3,
4])
=> `[1 2 ~@(if (neg? (first nums)) nums)]
HyList([
HyInteger(1),
HyInteger(2)])
Here, the last example evaluates to ``('+' 1 2)``, since the condition
``(< (nth nums 0) 0)`` is ``False``, which makes this ``if`` expression

View File

@ -618,17 +618,29 @@ arguments. If the argument list only has one element, return it.
.. code-block:: hy
=> (list* 1 2 3 4)
(1 2 3 . 4)
=> (list* 1 2 3 [4])
[1, 2, 3, 4]
=> (list* 1)
1
=> (cons? (list* 1 2 3 4))
True
=> (list* 1 2 3 4)
<HyCons (
HyInteger(1)
HyInteger(2)
HyInteger(3)
. HyInteger(4))>
=> (list* 1 2 3 [4])
[HyInteger(1), HyInteger(2), HyInteger(3), 4]
=> (list* 1)
1
=> (cons? (list* 1 2 3 4))
True
=> (list* 1 10 2 20 '{})
HyDict([
HyInteger(1), HyInteger(10),
HyInteger(2), HyInteger(20)])
=> (list* 1 10 2 20 {})
<HyCons (
HyInteger(1)
HyInteger(10)
HyInteger(2)
HyInteger(20)
. HyDict())>
.. _macroexpand-fn:
@ -643,11 +655,23 @@ Returns the full macro expansion of *form*.
.. code-block:: hy
=> (macroexpand '(-> (a b) (x y)))
(u'x' (u'a' u'b') u'y')
=> (macroexpand '(-> (a b) (-> (c d) (e f))))
(u'e' (u'c' (u'a' u'b') u'd') u'f')
=> (macroexpand '(-> (a b) (x y)))
HyExpression([
HySymbol('x'),
HyExpression([
HySymbol('a'),
HySymbol('b')]),
HySymbol('y')])
=> (macroexpand '(-> (a b) (-> (c d) (e f))))
HyExpression([
HySymbol('e'),
HyExpression([
HySymbol('c'),
HyExpression([
HySymbol('a'),
HySymbol('b')]),
HySymbol('d')]),
HySymbol('f')])
.. _macroexpand-1-fn:
@ -662,8 +686,18 @@ Returns the single step macro expansion of *form*.
.. code-block:: hy
=> (macroexpand-1 '(-> (a b) (-> (c d) (e f))))
(u'_>' (u'a' u'b') (u'c' u'd') (u'e' u'f'))
=> (macroexpand-1 '(-> (a b) (-> (c d) (e f))))
HyExpression([
HySymbol('_>'),
HyExpression([
HySymbol('a'),
HySymbol('b')]),
HyExpression([
HySymbol('c'),
HySymbol('d')]),
HyExpression([
HySymbol('e'),
HySymbol('f')])])
.. _merge-with-fn:
@ -839,26 +873,26 @@ Chunks *coll* into *n*-tuples (pairs by default).
.. code-block:: hy
=> (list (partition (range 10))) ; n=2
[(, 0 1) (, 2 3) (, 4 5) (, 6 7) (, 8 9)]
=> (list (partition (range 10))) ; n=2
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
The *step* defaults to *n*, but can be more to skip elements, or less for a sliding window with overlap.
.. code-block:: hy
=> (list (partition (range 10) 2 3))
[(, 0 1) (, 3 4) (, 6 7)]
=> (list (partition (range 5) 2 1))
[(, 0 1) (, 1 2) (, 2 3) (, 3 4)])
=> (list (partition (range 10) 2 3))
[(0, 1), (3, 4), (6, 7)]
=> (list (partition (range 5) 2 1))
[(0, 1), (1, 2), (2, 3), (3, 4)]
The remainder, if any, is not included unless a *fillvalue* is specified.
.. code-block:: hy
=> (list (partition (range 10) 3))
[(, 0 1 2) (, 3 4 5) (, 6 7 8)]
=> (list (partition (range 10) 3 :fillvalue "x"))
[(, 0 1 2) (, 3 4 5) (, 6 7 8) (, 9 "x" "x")]
=> (list (partition (range 10) 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8)]
=> (list (partition (range 10) 3 :fillvalue "x"))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 'x', 'x')]
.. _pos?-fn:
@ -1220,36 +1254,43 @@ if *from-file* ends before a complete expression can be parsed.
.. code-block:: hy
=> (read)
(+ 2 2)
('+' 2 2)
=> (eval (read))
(+ 2 2)
4
=> (read)
(+ 2 2)
HyExpression([
HySymbol('+'),
HyInteger(2),
HyInteger(2)])
=> (eval (read))
(+ 2 2)
4
=> (import io)
=> (setv buffer (io.StringIO "(+ 2 2)\n(- 2 1)"))
=> (eval (read :from_file buffer))
4
=> (eval (read :from_file buffer))
1
=> (import io)
=> (def buffer (io.StringIO "(+ 2 2)\n(- 2 1)"))
=> (eval (read :from_file buffer))
4
=> (eval (read :from_file buffer))
1
=> ; assuming "example.hy" contains:
=> ; (print "hello")
=> ; (print "hyfriends!")
=> (with [f (open "example.hy")]
... (try
... (while True
... (setv exp (read f))
... (print "OHY" exp)
... (eval exp))
... (except [e EOFError]
... (print "EOF!"))))
OHY ('print' 'hello')
hello
OHY ('print' 'hyfriends!')
hyfriends!
EOF!
=> (with [f (open "example.hy" "w")]
... (.write f "(print 'hello)\n(print \"hyfriends!\")"))
35
=> (with [f (open "example.hy")]
... (try (while True
... (setv exp (read f))
... (print "OHY" exp)
... (eval exp))
... (except [e EOFError]
... (print "EOF!"))))
OHY HyExpression([
HySymbol('print'),
HyExpression([
HySymbol('quote'),
HySymbol('hello')])])
hello
OHY HyExpression([
HySymbol('print'),
HyString('hyfriends!')])
hyfriends!
EOF!
read-str
--------
@ -1261,11 +1302,12 @@ string:
.. code-block:: hy
=> (read-str "(print 1)")
(u'print' 1L)
=> (eval (read-str "(print 1)"))
1
=>
=> (read-str "(print 1)")
HyExpression([
HySymbol('print'),
HyInteger(1)])
=> (eval (read-str "(print 1)"))
1
.. _remove-fn:
@ -1409,3 +1451,4 @@ are available. Some of their names have been changed:
- ``dropwhile`` has been changed to ``drop-while``
- ``filterfalse`` has been changed to ``remove``

View File

@ -39,6 +39,15 @@ Compound Models
Parenthesized and bracketed lists are parsed as compound models by the
Hy parser.
Hy uses pretty-printing reprs for its compound models by default.
If this is causing issues,
it can be turned off globally by setting ``hy.models.PRETTY`` to ``False``,
or temporarily by using the ``hy.models.pretty`` context manager.
Hy also attempts to color pretty reprs using ``clint.textui.colored``.
This module has a flag to disable coloring,
and a method ``clean`` to strip colored strings of their color tags.
.. _hylist:
HyList

View File

@ -66,7 +66,7 @@
(or (is t HyKeyword) (and (is t str-type) (.startswith x HyKeyword.PREFIX)))
(cut x 1)
(in t [str-type HyString bytes-type HyBytes]) (do
(setv r (.lstrip (repr x) "ub"))
(setv r (.lstrip (base-repr x) "ub"))
(+ (if (in t [bytes-type HyBytes]) "b" "") (if (.startswith "\"" r)
; If Python's built-in repr produced a double-quoted string, use
; that.
@ -75,9 +75,9 @@
; convert it.
(+ "\"" (.replace (cut r 1 -1) "\"" "\\\"") "\""))))
(and (not PY3) (is t int))
(.format "(int {})" (repr x))
(.format "(int {})" (base-repr x))
(and (not PY3) (in t [long_type HyInteger]))
(.rstrip (repr x) "L")
(.rstrip (base-repr x) "L")
(and (in t [float HyFloat]) (isnan x))
"NaN"
(and (in t [float HyFloat]) (= x Inf))
@ -85,9 +85,18 @@
(and (in t [float HyFloat]) (= x -Inf))
"-Inf"
(in t [complex HyComplex])
(.replace (.replace (.strip (repr x) "()") "inf" "Inf") "nan" "NaN")
(.replace (.replace (.strip (base-repr x) "()") "inf" "Inf") "nan" "NaN")
(is t fraction)
(.format "{}/{}" (f x.numerator q) (f x.denominator q))
; else
(repr x))))
(base-repr x))))
(f obj False))
(defn base-repr [x]
(unless (instance? HyObject x)
(return (repr x)))
; Call (.repr x) using the first class of x that doesn't inherit from
; HyObject.
(.__repr__
(next (genexpr t [t (. (type x) __mro__)] (not (issubclass t HyObject))))
x))

View File

@ -3,9 +3,28 @@
# license. See the LICENSE.
from __future__ import unicode_literals
from contextlib import contextmanager
from math import isnan, isinf
from hy._compat import PY3, str_type, bytes_type, long_type, string_types
from fractions import Fraction
from clint.textui import colored
PRETTY = True
@contextmanager
def pretty(pretty=True):
"""
Context manager to temporarily enable
or disable pretty-printing of Hy model reprs.
"""
global PRETTY
old, PRETTY = PRETTY, pretty
try:
yield
finally:
PRETTY = old
class HyObject(object):
@ -25,6 +44,9 @@ class HyObject(object):
return self
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, super(HyObject, self).__repr__())
_wrappers = {}
@ -59,6 +81,10 @@ def replace_hy_obj(obj, other):
% type(obj))
def repr_indent(obj):
return repr(obj).replace("\n", "\n ")
class HyString(HyObject, str_type):
"""
Generic Hy String object. Helpful to store string literals from Hy
@ -109,6 +135,9 @@ class HyKeyword(HyObject, str_type):
obj = str_type.__new__(cls, value)
return obj
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self[1:]))
def strip_digit_separators(number):
# Don't strip a _ or , if it's the first character, as _42 and
@ -217,8 +246,22 @@ class HyList(HyObject, list):
return ret
color = staticmethod(colored.cyan)
def __repr__(self):
return "[%s]" % (" ".join([repr(x) for x in self]))
return str(self) if PRETTY else super(HyList, self).__repr__()
def __str__(self):
with pretty():
c = self.color
if self:
return ("{}{}\n {}{}").format(
c(self.__class__.__name__),
c("(["),
(c(",") + "\n ").join([repr_indent(e) for e in self]),
c("])"))
else:
return '' + c(self.__class__.__name__ + "()")
_wrappers[list] = lambda l: HyList(wrap_value(x) for x in l)
_wrappers[tuple] = lambda t: HyList(wrap_value(x) for x in t)
@ -229,8 +272,24 @@ class HyDict(HyList):
HyDict (just a representation of a dict)
"""
def __repr__(self):
return "{%s}" % (" ".join([repr(x) for x in self]))
def __str__(self):
with pretty():
g = colored.green
if self:
pairs = []
for k, v in zip(self[::2],self[1::2]):
k, v = repr_indent(k), repr_indent(v)
pairs.append(
("{0}{c}\n {1}\n "
if '\n' in k+v
else "{0}{c} {1}").format(k, v, c=g(',')))
if len(self) % 2 == 1:
pairs.append("{} {}\n".format(
repr_indent(self[-1]), g("# odd")))
return "{}\n {}{}".format(
g("HyDict(["), ("{c}\n ".format(c=g(',')).join(pairs)), g("])"))
else:
return '' + g("HyDict()")
def keys(self):
return self[0::2]
@ -248,9 +307,7 @@ class HyExpression(HyList):
"""
Hy S-Expression. Basically just a list.
"""
def __repr__(self):
return "(%s)" % (" ".join([repr(x) for x in self]))
color = staticmethod(colored.yellow)
_wrappers[HyExpression] = lambda e: HyExpression(wrap_value(x) for x in e)
_wrappers[Fraction] = lambda e: HyExpression(
@ -261,9 +318,7 @@ class HySet(HyList):
"""
Hy set (just a representation of a set)
"""
def __repr__(self):
return "#{%s}" % (" ".join([repr(x) for x in self]))
color = staticmethod(colored.red)
_wrappers[set] = lambda s: HySet(wrap_value(x) for x in s)
@ -339,10 +394,24 @@ class HyCons(HyObject):
HyObject.replace(self, other)
def __repr__(self):
if isinstance(self.cdr, self.__class__):
return "(%s %s)" % (repr(self.car), repr(self.cdr)[1:-1])
if PRETTY:
return str(self)
else:
return "(%s . %s)" % (repr(self.car), repr(self.cdr))
return "HyCons({}, {})".format(
repr(self.car), repr(self.cdr))
def __str__(self):
with pretty():
c = colored.yellow
lines = ['' + c("<HyCons (")]
while True:
lines.append(" " + repr_indent(self.car))
if not isinstance(self.cdr, HyCons):
break
self = self.cdr
lines.append("{} {}{}".format(
c("."), repr_indent(self.cdr), c(")>")))
return '\n'.join(lines)
def __eq__(self, other):
return (

View File

@ -3,9 +3,12 @@
# license. See the LICENSE.
import copy
import hy
from clint.textui.colored import clean
from hy._compat import long_type, str_type
from hy.models import (wrap_value, replace_hy_obj, HyString, HyInteger, HyList,
HyDict, HySet, HyExpression, HyCons, HyComplex, HyFloat)
HyDict, HySet, HyExpression, HyCons, HyComplex, HyFloat,
pretty)
def test_wrap_long_type():
@ -140,3 +143,117 @@ def test_number_model_copy():
c = HyComplex(42j)
assert (c == copy.copy(c))
assert (c == copy.deepcopy(c))
PRETTY_STRINGS = {
k % ('[1.0] {1.0} (1.0) #{1.0} (0.0 1.0 . 2.0)',):
v.format("""
HyList([
HyFloat(1.0)]),
HyDict([
HyFloat(1.0) # odd
]),
HyExpression([
HyFloat(1.0)]),
HySet([
HyFloat(1.0)]),
<HyCons (
HyFloat(0.0)
HyFloat(1.0)
. HyFloat(2.0))>""")
for k, v in {'[%s]': 'HyList([{}])',
'#{%s}': 'HySet([{}])'}.items()}
PRETTY_STRINGS.update({
'{[1.0] {1.0} (1.0) #{1.0} (0.0 1.0 . 2.0)}':
"""HyDict([
HyList([
HyFloat(1.0)]),
HyDict([
HyFloat(1.0) # odd
])
,
HyExpression([
HyFloat(1.0)]),
HySet([
HyFloat(1.0)])
,
<HyCons (
HyFloat(0.0)
HyFloat(1.0)
. HyFloat(2.0))> # odd
])"""
,
'([1.0] {1.0} (1.0) #{1.0} (0.0 1.0 . 2.0) . 3.0)':
"""<HyCons (
HyList([
HyFloat(1.0)])
HyDict([
HyFloat(1.0) # odd
])
HyExpression([
HyFloat(1.0)])
HySet([
HyFloat(1.0)])
<HyCons (
HyFloat(0.0)
HyFloat(1.0)
. HyFloat(2.0))>
. HyFloat(3.0))>"""
,
'[1.0 1j [] {} () #{}]':
"""HyList([
HyFloat(1.0),
HyComplex(1j),
HyList(),
HyDict(),
HyExpression(),
HySet()])"""
,
'{{1j 2j} {1j 2j [][1j]} {[1j][] 1j 2j} {[1j][1j]}}':
"""HyDict([
HyDict([
HyComplex(1j), HyComplex(2j)]),
HyDict([
HyComplex(1j), HyComplex(2j),
HyList(),
HyList([
HyComplex(1j)])
])
,
HyDict([
HyList([
HyComplex(1j)]),
HyList()
,
HyComplex(1j), HyComplex(2j)]),
HyDict([
HyList([
HyComplex(1j)]),
HyList([
HyComplex(1j)])
])
])"""})
def test_compound_model_repr():
HY_LIST_MODELS = (HyExpression, HyDict, HySet, HyList)
with pretty(False):
assert eval(repr(HyCons(1, 2))).__class__ is HyCons
assert eval(repr(HyCons(1, 2))) == HyCons(1, 2)
for model in HY_LIST_MODELS:
assert eval(repr(model())).__class__ is model
assert eval(repr(model([1, 2]))) == model([1, 2])
assert eval(repr(model([1, 2, 3]))) == model([1, 2, 3])
for k, v in PRETTY_STRINGS.items():
# `str` should be pretty, even under `pretty(False)`.
assert clean(str(hy.read_str(k))) == v
for k in PRETTY_STRINGS.keys():
assert eval(repr(hy.read_str(k))) == hy.read_str(k)
with pretty(True):
for model in HY_LIST_MODELS:
assert eval(clean(repr(model()))).__class__ is model
assert eval(clean(repr(model([1, 2])))) == model([1, 2])
assert eval(clean(repr(model([1, 2, 3])))) == model([1, 2, 3])
for k, v in PRETTY_STRINGS.items():
assert clean(repr(hy.read_str(k))) == v