diff --git a/docs/contrib/walk.rst b/docs/contrib/walk.rst index 01e2bc0..f3c4efe 100644 --- a/docs/contrib/walk.rst +++ b/docs/contrib/walk.rst @@ -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)])])])]) diff --git a/docs/language/api.rst b/docs/language/api.rst index e201ec9..2f5511e 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -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 diff --git a/docs/language/core.rst b/docs/language/core.rst index d5b1cef..3e9611b 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -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) + + => (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 {}) + .. _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`` + diff --git a/docs/language/internals.rst b/docs/language/internals.rst index ffd3844..48a4985 100644 --- a/docs/language/internals.rst +++ b/docs/language/internals.rst @@ -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 diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index 195e271..c715839 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -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)) diff --git a/hy/models.py b/hy/models.py index 2b32142..3a769e8 100644 --- a/hy/models.py +++ b/hy/models.py @@ -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(""))) + return '\n'.join(lines) def __eq__(self, other): return ( diff --git a/tests/test_models.py b/tests/test_models.py index 291caa7..89c5e16 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -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)]), + """) + 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)]) + , + # odd +])""" + , + '([1.0] {1.0} (1.0) #{1.0} (0.0 1.0 . 2.0) . 3.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