Add hy.contrib.hy-repr

This commit is contained in:
Kodi Arfer 2017-03-20 12:04:00 -07:00
parent 32e76caafe
commit bf2f90a0d9
6 changed files with 207 additions and 0 deletions

4
NEWS
View File

@ -22,6 +22,10 @@ Changes from 0.12.1
returns.
* `setv` no longer unnecessarily tries to get attributes
[ Misc. Improvements ]
* New contrib module `hy-repr`
* Added a command-line option --hy-repr
Changes from 0.12.0
[ Bug Fixes ]

42
docs/contrib/hy_repr.rst Normal file
View File

@ -0,0 +1,42 @@
==================
Hy representations
==================
.. versionadded:: 0.13.0
``hy.contrib.hy-repr`` is a module containing a single function.
To import it, say ``(import [hy.contrib.hy-repr [hy-repr]])``.
.. _hy-repr-fn:
hy-repr
-------
Usage: ``(hy-repr x)``
This function is Hy's equivalent of Python's built-in ``repr``.
It returns a string representing the input object in Hy syntax.
.. code-block:: hy
=> (hy-repr [1 2 3])
'[1 2 3]'
=> (repr [1 2 3])
'[1, 2, 3]'
If the input object has a method ``__hy-repr__``, it will be called
instead of doing anything else.
.. code-block:: hy
=> (defclass C [list] [__hy-repr__ (fn [self] "cuddles")])
=> (hy-repr (C))
'cuddles'
When ``hy-repr`` doesn't know how to handle its input, it falls back
on ``repr``.
Like ``repr`` in Python, ``hy-repr`` can round-trip many kinds of
values. Round-tripping implies that given an object ``x``,
``(eval (read-str (hy-repr x)))`` returns ``x``, or at least a value
that's equal to ``x``.

View File

@ -16,3 +16,4 @@ Contents:
profile
sequences
walk
hy_repr

77
hy/contrib/hy_repr.hy Normal file
View File

@ -0,0 +1,77 @@
(import [hy._compat [PY3 str-type bytes-type long-type]])
(import [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyList HyDict HySet HyString HyBytes]])
(defn hy-repr [obj]
(setv seen (set))
; We keep track of objects we've already seen, and avoid
; redisplaying their contents, so a self-referential object
; doesn't send us into an infinite loop.
(defn f [x q]
; `x` is the current object being stringified.
; `q` is True if we're inside a single quote, False otherwise.
(setv old? (in (id x) seen))
(.add seen (id x))
(setv t (type x))
(defn catted []
(if old? "..." (.join " " (list-comp (f it q) [it x]))))
(setv prefix "")
(if (and (not q) (instance? HyObject x))
(setv prefix "'" q True))
(+ prefix (if
(hasattr x "__hy_repr__")
(.__hy-repr__ x)
(is t HyExpression)
(if (and x (symbol? (first x)))
(if
(= (first x) 'quote)
(+ "'" (f (second x) True))
(= (first x) 'quasiquote)
(+ "`" (f (second x) q))
(= (first x) 'unquote)
(+ "~" (f (second x) q))
(= (first x) 'unquote_splice)
(+ "~@" (f (second x) q))
; else
(+ "(" (catted) ")"))
(+ "(" (catted) ")"))
(is t tuple)
(+ "(," (if x " " "") (catted) ")")
(in t [list HyList])
(+ "[" (catted) "]")
(is t HyDict)
(+ "{" (catted) "}")
(is t dict)
(+
"{"
(if old? "..." (.join " " (list-comp
(+ (f k q) " " (f v q))
[[k v] (.items x)])))
"}")
(in t [set HySet])
(+ "#{" (catted) "}")
(is t frozenset)
(+ "(frozenset #{" (catted) "})")
(is t HySymbol)
x
(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"))
(+ (if (in t [bytes-type HyBytes]) "b" "") (if (.startswith "\"" r)
; If Python's built-in repr produced a double-quoted string, use
; that.
r
; Otherwise, we have a single-quoted string, which isn't valid Hy, so
; convert it.
(+ "\"" (.replace (cut r 1 -1) "\"" "\\\"") "\""))))
(and (not PY3) (is t int))
(.format "(int {})" (repr x))
(and (not PY3) (in t [long_type HyInteger]))
(.rstrip (repr x) "L")
(is t complex)
(.strip (repr x) "()")
(is t fraction)
(.format "{}/{}" (f x.numerator q) (f x.denominator q))
; else
(repr x))))
(f obj False))

View File

@ -19,6 +19,7 @@ from .native_tests.contrib.loop import * # noqa
from .native_tests.contrib.walk import * # noqa
from .native_tests.contrib.multi import * # noqa
from .native_tests.contrib.sequences import * # noqa
from .native_tests.contrib.hy_repr import * # noqa
if PY3:
from .native_tests.py3_only_tests import * # noqa

View File

@ -0,0 +1,82 @@
(import
[hy.contrib.hy-repr [hy-repr]])
(defn test-hy-repr-roundtrip-from-value []
; Test that a variety of values round-trip properly.
(setv values [
None False True
5 5.1 '5 '5.1
(int 5)
1/2
5j 5.1j 2+1j 1.2+3.4j
"" b""
'"" 'b""
"apple bloom" b"apple bloom" "⚘"
'"apple bloom" 'b"apple bloom" '"⚘"
"single ' quotes" b"single ' quotes"
"\"double \" quotes\"" b"\"double \" quotes\""
'mysymbol :mykeyword
[] (,) #{} (frozenset #{})
'[] '(,) '#{} '(frozenset #{})
'['[]]
'(+ 1 2)
[1 2 3] (, 1 2 3) #{1 2 3} (frozenset #{1 2 3})
'[1 2 3] '(, 1 2 3) '#{1 2 3} '(frozenset #{1 2 3})
{"a" 1 "b" 2 "a" 3} '{"a" 1 "b" 2 "a" 3}
[1 [2 3] (, 4 (, 'mysymbol :mykeyword)) {"a" b"hello"}]
'[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"}]])
(for [original-val values]
(setv evaled (eval (read-str (hy-repr original-val))))
(assert (= evaled original-val))
(assert (is (type evaled) (type original-val)))))
(defn test-hy-repr-roundtrip-from-str []
(setv strs [
"[1 2 3]"
"'[1 2 3]"
"[1 'a 3]"
"'[1 a 3]"
"'[1 'a 3]"
"[1 '[2 3] 4]"
"'[1 [2 3] 4]"
"'[1 '[2 3] 4]"
"'[1 `[2 3] 4]"
"'[1 `[~foo ~@bar] 4]"
"'[1 `[~(+ 1 2) ~@(+ [1] [2])] 4]"
"'[1 `[~(do (print x 'y) 1)] 4]"
"{1 20}"
"'{1 10 1 20}"
"'asymbol"
":akeyword"])
(for [original-str strs]
(setv rep (hy-repr (eval (read-str original-str))))
(assert (= rep original-str))))
(defn test-hy-model-constructors []
(import hy)
(assert (= (hy-repr (hy.HyInteger 7)) "'7"))
(assert (= (hy-repr (hy.HyString "hello")) "'\"hello\""))
(assert (= (hy-repr (hy.HyList [1 2 3])) "'[1 2 3]"))
(assert (= (hy-repr (hy.HyDict [1 2 3])) "'{1 2 3}")))
(defn test-hy-repr-self-reference []
(setv x [1 2 3])
(setv (get x 1) x)
(assert (= (hy-repr x) "[1 [...] 3]"))
(setv x {1 2 3 [4 5] 6 7})
(setv (get x 3 1) x)
(assert (in (hy-repr x) (list-comp
; The ordering of a dictionary isn't guaranteed, so we need
; to check for all possible orderings.
(+ "{" (.join " " p) "}")
[p (permutations ["1 2" "3 [4 {...}]" "6 7"])]))))
(defn test-hy-repr-dunder-method []
(defclass C [list] [__hy-repr__ (fn [self] "cuddles")])
(assert (= (hy-repr (C)) "cuddles")))
(defn test-hy-repr-fallback []
(defclass D [list] [__repr__ (fn [self] "cuddles")])
(assert (= (hy-repr (D)) "cuddles")))