From 83c4f63bc285c50ca918f5e00d40b319b39f886f Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 31 Oct 2017 16:48:59 -0700 Subject: [PATCH 1/9] Reimplement hy-repr with registered functions This removes a lot of hy-reprs that were hard-coded into the hy-repr function itself. It also allows you to add a hy-repr for an existing class without monkey-patching the class. --- hy/contrib/hy_repr.hy | 174 +++++++++++++------------- tests/native_tests/contrib/hy_repr.hy | 24 +++- 2 files changed, 108 insertions(+), 90 deletions(-) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index e47adcf..8c8da65 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -7,92 +7,96 @@ [hy._compat [PY3 str-type bytes-type long-type]] [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex 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)) - (= (first x) 'unpack_iterable) - (+ "#* " (f (second x) q)) - (= (first x) 'unpack_mapping) - (+ "#** " (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 (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. - 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 {})" (base-repr x)) - (and (not PY3) (in t [long_type HyInteger])) - (.rstrip (base-repr x) "L") - (and (in t [float HyFloat]) (isnan x)) - "NaN" - (and (in t [float HyFloat]) (= x Inf)) - "Inf" - (and (in t [float HyFloat]) (= x -Inf)) - "-Inf" - (in t [complex HyComplex]) - (.replace (.replace (.strip (base-repr x) "()") "inf" "Inf") "nan" "NaN") - (is t fraction) - (.format "{}/{}" (f x.numerator q) (f x.denominator q)) - ; else - (base-repr x)))) - (f obj False)) +(setv -registry {}) +(defn hy-repr-register [types f &optional placeholder] + (for [typ (if (instance? list types) types [types])] + (setv (get -registry typ) (, f placeholder)))) -(defn base-repr [x] +(setv -quoting False) +(setv -seen (set)) +(defn hy-repr [obj] + (setv [f placeholder] (next + (genexpr (get -registry t) + [t (. (type obj) __mro__)] + (in t -registry)) + [-base-repr None])) + + (global -quoting) + (setv started-quoting False) + (when (and (not -quoting) (instance? HyObject obj)) + (setv -quoting True) + (setv started-quoting True)) + + (setv oid (id obj)) + (when (in oid -seen) + (return (if (none? placeholder) "..." placeholder))) + (.add -seen oid) + + (try + (+ (if started-quoting "'" "") (f obj)) + (finally + (.discard -seen oid) + (when started-quoting + (setv -quoting False))))) + +(hy-repr-register list :placeholder "[...]" (fn [x] + (+ "[" (-cat x) "]"))) +(hy-repr-register tuple (fn [x] + (+ "(," (if x " " "") (-cat x) ")"))) +(hy-repr-register dict :placeholder "{...}" (fn [x] + (+ "{" (-cat (reduce + (.items x))) "}"))) +(hy-repr-register HyDict :placeholder "{...}" (fn [x] + (+ "{" (-cat x) "}"))) +(hy-repr-register [set HySet] (fn [x] + (+ "#{" (-cat x) "}"))) +(hy-repr-register frozenset (fn [x] + (+ "(frozenset #{" (-cat x) "})"))) +(hy-repr-register HyExpression (fn [x] + (setv syntax { + 'quote "'" + 'quasiquote "`" + 'unquote "~" + 'unquote_splice "~@" + 'unpack_iterable "#* " + 'unpack_mapping "#** "}) + (if (and x (symbol? (first x)) (in (first x) syntax)) + (+ (get syntax (first x)) (hy-repr (second x))) + (+ "(" (-cat x) ")")))) + +(hy-repr-register HySymbol str) +(hy-repr-register [str-type bytes-type HyKeyword] (fn [x] + (if (and (instance? str-type x) (.startswith x HyKeyword.PREFIX)) + (return (cut x 1))) + (setv r (.lstrip (-base-repr x) "ub")) + (+ + (if (instance? bytes-type x) "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) "\"" "\\\"") "\""))))) +(hy-repr-register bool str) +(if (not PY3) (hy-repr-register int (fn [x] + (.format "(int {})" (-base-repr x))))) +(if (not PY3) (hy-repr-register long_type (fn [x] + (.rstrip (-base-repr x) "L")))) +(hy-repr-register float (fn [x] + (if + (isnan x) "NaN" + (= x Inf) "Inf" + (= x -Inf) "-Inf" + (-base-repr x)))) +(hy-repr-register complex (fn [x] + (.replace (.replace (.strip (-base-repr x) "()") "inf" "Inf") "nan" "NaN"))) +(hy-repr-register fraction (fn [x] + (.format "{}/{}" (hy-repr x.numerator) (hy-repr x.denominator)))) + +(defn -cat [obj] + (.join " " (map hy-repr obj))) + +(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 diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 3f40fcc..45129c9 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -4,7 +4,7 @@ (import [math [isnan]] - [hy.contrib.hy-repr [hy-repr]]) + [hy.contrib.hy-repr [hy-repr hy-repr-register]]) (defn test-hy-repr-roundtrip-from-value [] ; Test that a variety of values round-trip properly. @@ -86,10 +86,24 @@ (+ "{" (.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-custom [] + + (defclass C [object]) + (hy-repr-register C (fn [x] "cuddles")) + (assert (= (hy-repr (C)) "cuddles")) + + (defclass Container [object] + [__init__ (fn [self value] + (setv self.value value))]) + (hy-repr-register Container :placeholder "(Container ...)" (fn [x] + (+ "(Container " (hy-repr x.value) ")"))) + (setv container (Container 5)) + (setv container.value container) + (assert (= (hy-repr container) "(Container (Container ...))")) + (setv container.value [1 container 3]) + (assert (= (hy-repr container) "(Container [1 (Container ...) 3])"))) (defn test-hy-repr-fallback [] - (defclass D [list] [__repr__ (fn [self] "cuddles")]) + (defclass D [object] + [__repr__ (fn [self] "cuddles")]) (assert (= (hy-repr (D)) "cuddles"))) From 4f2706e18e5148a4cb6049b5c0197e93a4c0afaf Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 31 Oct 2017 11:35:46 -0700 Subject: [PATCH 2/9] Test a case where hy-repr shouldn't round-trip --- tests/native_tests/contrib/hy_repr.hy | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 45129c9..5085cc3 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -65,6 +65,19 @@ (setv rep (hy-repr (eval (read-str original-str)))) (assert (= rep original-str)))) +(defn test-hy-repr-no-roundtrip [] + ; Test one of the corner cases in which hy-repr doesn't + ; round-trip: when a HyObject contains a non-HyObject, we + ; promote the constituent to a HyObject. + + (setv orig `[a ~5.0]) + (setv reprd (hy-repr orig)) + (assert (= reprd "'[a 5.0]")) + (setv result (eval (read-str reprd))) + + (assert (is (type (get orig 1)) float)) + (assert (is (type (get result 1)) HyFloat))) + (defn test-hy-model-constructors [] (import hy) (assert (= (hy-repr (hy.HyInteger 7)) "'7")) From 199bb70150c59187db4cec7e45adc41e8ed56321 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 31 Oct 2017 16:49:29 -0700 Subject: [PATCH 3/9] Add a hy-repr test for keyword-like bytes objects --- tests/native_tests/contrib/hy_repr.hy | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 5085cc3..8787435 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -3,6 +3,7 @@ ;; license. See the LICENSE. (import + [hy._compat [PY3]] [math [isnan]] [hy.contrib.hy-repr [hy-repr hy-repr-register]]) @@ -78,6 +79,14 @@ (assert (is (type (get orig 1)) float)) (assert (is (type (get result 1)) HyFloat))) +(when PY3 (defn test-bytes-keywords [] + ; Make sure that keyword-like bytes objects aren't hy-repred as if + ; they were real keywords. + (setv kw :mykeyword) + (assert (= (hy-repr kw) ":mykeyword")) + (assert (= (hy-repr (str ':mykeyword)) ":mykeyword")) + (assert (= (hy-repr (.encode kw "UTF-8") #[[b"\xef\xb7\x90:hello"]]))))) + (defn test-hy-model-constructors [] (import hy) (assert (= (hy-repr (hy.HyInteger 7)) "'7")) From 90a09b5b4418fd1144c8a14480f8da4ba77f0079 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 31 Oct 2017 11:58:25 -0700 Subject: [PATCH 4/9] Make hy-repr use double spaces for dictionaries --- hy/contrib/hy_repr.hy | 12 ++++++++++-- tests/native_tests/contrib/hy_repr.hy | 8 ++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index 8c8da65..669c486 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -44,9 +44,17 @@ (hy-repr-register tuple (fn [x] (+ "(," (if x " " "") (-cat x) ")"))) (hy-repr-register dict :placeholder "{...}" (fn [x] - (+ "{" (-cat (reduce + (.items x))) "}"))) + (setv text (.join " " (genexpr + (+ (hy-repr k) " " (hy-repr v)) + [[k v] (.items x)]))) + (+ "{" text "}"))) (hy-repr-register HyDict :placeholder "{...}" (fn [x] - (+ "{" (-cat x) "}"))) + (setv text (.join " " (genexpr + (+ (hy-repr k) " " (hy-repr v)) + [[k v] (partition x)]))) + (if (% (len x) 2) + (+= text (+ " " (hy-repr (get x -1))))) + (+ "{" text "}"))) (hy-repr-register [set HySet] (fn [x] (+ "#{" (-cat x) "}"))) (hy-repr-register frozenset (fn [x] diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 8787435..9821c90 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -28,7 +28,7 @@ '(+ 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} + {"a" 1 "b" 2 "a" 3} '{"a" 1 "b" 2 "a" 3} [1 [2 3] (, 4 (, 'mysymbol :mykeyword)) {"a" b"hello"} '(f #* a #** b)] '[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"} (f #* a #** b)]]) (for [original-val values] @@ -58,7 +58,7 @@ "'[1 `[~(+ 1 2) ~@(+ [1] [2])] 4]" "'[1 `[~(do (print x 'y) 1)] 4]" "{1 20}" - "'{1 10 1 20}" + "'{1 10 1 20}" "'asymbol" ":akeyword" "'(f #* args #** kwargs)"]) @@ -92,7 +92,7 @@ (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}"))) + (assert (= (hy-repr (hy.HyDict [1 2 3])) "'{1 2 3}"))) (defn test-hy-repr-self-reference [] @@ -105,7 +105,7 @@ (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) "}") + (+ "{" (.join " " p) "}") [p (permutations ["1 2" "3 [4 {...}]" "6 7"])])))) (defn test-hy-repr-custom [] From f7ab9a6e7c8d91a8179627d84478ee5a9888e8ed Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Thu, 2 Nov 2017 13:35:52 -0700 Subject: [PATCH 5/9] Make hy-repr support dictionary views --- hy/contrib/hy_repr.hy | 23 +++++++++++++++++------ tests/native_tests/contrib/hy_repr.hy | 5 +++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index 669c486..8d67522 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -7,6 +7,12 @@ [hy._compat [PY3 str-type bytes-type long-type]] [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]]) +(try + (import [_collections_abc [dict-keys dict-values dict-items]]) + (except [ImportError] + (defclass C) + (setv [dict-keys dict-values dict-items] [C C C]))) + (setv -registry {}) (defn hy-repr-register [types f &optional placeholder] (for [typ (if (instance? list types) types [types])] @@ -39,8 +45,6 @@ (when started-quoting (setv -quoting False))))) -(hy-repr-register list :placeholder "[...]" (fn [x] - (+ "[" (-cat x) "]"))) (hy-repr-register tuple (fn [x] (+ "(," (if x " " "") (-cat x) ")"))) (hy-repr-register dict :placeholder "{...}" (fn [x] @@ -55,10 +59,6 @@ (if (% (len x) 2) (+= text (+ " " (hy-repr (get x -1))))) (+ "{" text "}"))) -(hy-repr-register [set HySet] (fn [x] - (+ "#{" (-cat x) "}"))) -(hy-repr-register frozenset (fn [x] - (+ "(frozenset #{" (-cat x) "})"))) (hy-repr-register HyExpression (fn [x] (setv syntax { 'quote "'" @@ -101,6 +101,17 @@ (hy-repr-register fraction (fn [x] (.format "{}/{}" (hy-repr x.numerator) (hy-repr x.denominator)))) +(for [[types fmt] (partition [ + list "[...]" + [set HySet] "#{...}" + frozenset "(frozenset #{...})" + dict-keys "(dict-keys [...])" + dict-values "(dict-values [...])" + dict-items "(dict-items [...])"])] + (defn mkrepr [fmt] + (fn [x] (.replace fmt "..." (-cat x) 1))) + (hy-repr-register types :placeholder fmt (mkrepr fmt))) + (defn -cat [obj] (.join " " (map hy-repr obj))) diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 9821c90..076c16b 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -87,6 +87,11 @@ (assert (= (hy-repr (str ':mykeyword)) ":mykeyword")) (assert (= (hy-repr (.encode kw "UTF-8") #[[b"\xef\xb7\x90:hello"]]))))) +(when PY3 (defn test-dict-views [] + (assert (= (hy-repr (.keys {1 2})) "(dict-keys [1])")) + (assert (= (hy-repr (.values {1 2})) "(dict-values [2])")) + (assert (= (hy-repr (.items {1 2})) "(dict-items [(, 1 2)])")))) + (defn test-hy-model-constructors [] (import hy) (assert (= (hy-repr (hy.HyInteger 7)) "'7")) From 3dbe05302ec29cc03a363117fd96733eb84ba904 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Sun, 5 Nov 2017 11:12:38 -0800 Subject: [PATCH 6/9] Make hy-repr support regex match objects --- hy/contrib/hy_repr.hy | 9 +++++++++ tests/native_tests/contrib/hy_repr.hy | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index 8d67522..c414ffe 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -4,6 +4,7 @@ (import [math [isnan]] + re [hy._compat [PY3 str-type bytes-type long-type]] [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]]) @@ -101,6 +102,14 @@ (hy-repr-register fraction (fn [x] (.format "{}/{}" (hy-repr x.numerator) (hy-repr x.denominator)))) +(setv matchobject-type (type (re.match "" ""))) +(hy-repr-register matchobject-type (fn [x] + (.format "<{}.{} object; :span {} :match {}>" + matchobject-type.__module__ + matchobject-type.__name__ + (hy-repr (.span x)) + (hy-repr (.group x 0))))) + (for [[types fmt] (partition [ list "[...]" [set HySet] "#{...}" diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 076c16b..a295cec 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -113,6 +113,15 @@ (+ "{" (.join " " p) "}") [p (permutations ["1 2" "3 [4 {...}]" "6 7"])])))) +(defn test-matchobject [] + (import re) + (setv mo (re.search "b+" "aaaabbbccc")) + (assert (= (hy-repr mo) + (.format + #[[<{}.SRE_Match object; :span {} :match "bbb">]] + (. (type mo) __module__) + (if PY3 "(, 4 7)" "(, (int 4) (int 7))"))))) + (defn test-hy-repr-custom [] (defclass C [object]) From 38f461890d34b01f7bf032c6c6565100afec7ef9 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 3 Nov 2017 10:51:41 -0700 Subject: [PATCH 7/9] Make hy-repr support DateTime objects --- hy/contrib/hy_repr.hy | 27 ++++++++++++++++++++----- tests/native_tests/contrib/hy_repr.hy | 29 ++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index c414ffe..929a7b7 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -5,7 +5,8 @@ (import [math [isnan]] re - [hy._compat [PY3 str-type bytes-type long-type]] + datetime + [hy._compat [PY3 PY36 str-type bytes-type long-type]] [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]]) (try @@ -102,14 +103,30 @@ (hy-repr-register fraction (fn [x] (.format "{}/{}" (hy-repr x.numerator) (hy-repr x.denominator)))) -(setv matchobject-type (type (re.match "" ""))) -(hy-repr-register matchobject-type (fn [x] +(setv -matchobject-type (type (re.match "" ""))) +(hy-repr-register -matchobject-type (fn [x] (.format "<{}.{} object; :span {} :match {}>" - matchobject-type.__module__ - matchobject-type.__name__ + -matchobject-type.__module__ + -matchobject-type.__name__ (hy-repr (.span x)) (hy-repr (.group x 0))))) +(hy-repr-register datetime.datetime (fn [x] + (.format "(datetime.datetime {}{})" + (.strftime x "%Y %-m %-d %-H %-M %-S") + (-repr-time-innards x)))) +(hy-repr-register datetime.date (fn [x] + (.strftime x "(datetime.date %Y %-m %-d)"))) +(hy-repr-register datetime.time (fn [x] + (.format "(datetime.time {}{})" + (.strftime x "%-H %-M %-S") + (-repr-time-innards x)))) +(defn -repr-time-innards [x] + (.rstrip (+ " " (.join " " (filter identity [ + (if x.microsecond (str-type x.microsecond)) + (if (not (none? x.tzinfo)) (+ ":tzinfo " (hy-repr x.tzinfo))) + (if (and PY36 (!= x.fold 0)) (+ ":fold " (hy-repr x.fold)))]))))) + (for [[types fmt] (partition [ list "[...]" [set HySet] "#{...}" diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index a295cec..411d5a4 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -3,7 +3,7 @@ ;; license. See the LICENSE. (import - [hy._compat [PY3]] + [hy._compat [PY3 PY36]] [math [isnan]] [hy.contrib.hy-repr [hy-repr hy-repr-register]]) @@ -92,6 +92,33 @@ (assert (= (hy-repr (.values {1 2})) "(dict-values [2])")) (assert (= (hy-repr (.items {1 2})) "(dict-items [(, 1 2)])")))) +(defn test-datetime [] + (import [datetime :as D]) + + (assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 0)) + "(datetime.datetime 2009 1 15 15 27 5)")) + (assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 123)) + "(datetime.datetime 2009 1 15 15 27 5 123)")) + (when PY3 + (assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 123 :tzinfo D.timezone.utc)) + "(datetime.datetime 2009 1 15 15 27 5 123 :tzinfo datetime.timezone.utc)"))) + (when PY36 + (assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 :fold 1)) + "(datetime.datetime 2009 1 15 15 27 5 :fold 1)")) + (assert (= (hy-repr (D.datetime 2009 1 15 15 27 5 :fold 1 :tzinfo D.timezone.utc)) + "(datetime.datetime 2009 1 15 15 27 5 :tzinfo datetime.timezone.utc :fold 1)"))) + + (assert (= (hy-repr (D.date 2015 11 3)) + "(datetime.date 2015 11 3)")) + + (assert (= (hy-repr (D.time 1 2 3)) + "(datetime.time 1 2 3)")) + (assert (= (hy-repr (D.time 1 2 3 4567)) + "(datetime.time 1 2 3 4567)")) + (when PY36 + (assert (= (hy-repr (D.time 1 2 3 4567 :fold 1 :tzinfo D.timezone.utc)) + "(datetime.time 1 2 3 4567 :tzinfo datetime.timezone.utc :fold 1)")))) + (defn test-hy-model-constructors [] (import hy) (assert (= (hy-repr (hy.HyInteger 7)) "'7")) From 0574e275b5174579dc1adc449f2fc499bae4b18a Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 3 Nov 2017 11:56:46 -0700 Subject: [PATCH 8/9] Make hy-repr support some `collections` classes --- hy/contrib/hy_repr.hy | 19 ++++++++++++++++++- tests/native_tests/contrib/hy_repr.hy | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy index 929a7b7..627649a 100644 --- a/hy/contrib/hy_repr.hy +++ b/hy/contrib/hy_repr.hy @@ -6,6 +6,7 @@ [math [isnan]] re datetime + collections [hy._compat [PY3 PY36 str-type bytes-type long-type]] [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]]) @@ -48,7 +49,15 @@ (setv -quoting False))))) (hy-repr-register tuple (fn [x] - (+ "(," (if x " " "") (-cat x) ")"))) + (if (hasattr x "_fields") + ; It's a named tuple. (We can't use `instance?` or so because + ; generated named-tuple classes don't actually inherit from + ; collections.namedtuple.) + (.format "({} {})" + (. (type x) __name__) + (.join " " (genexpr (+ ":" k " " (hy-repr v)) [[k v] (zip x._fields x)]))) + ; Otherwise, print it as a regular tuple. + (+ "(," (if x " " "") (-cat x) ")")))) (hy-repr-register dict :placeholder "{...}" (fn [x] (setv text (.join " " (genexpr (+ (hy-repr k) " " (hy-repr v)) @@ -127,6 +136,14 @@ (if (not (none? x.tzinfo)) (+ ":tzinfo " (hy-repr x.tzinfo))) (if (and PY36 (!= x.fold 0)) (+ ":fold " (hy-repr x.fold)))]))))) +(hy-repr-register collections.Counter (fn [x] + (.format "(Counter {})" + (hy-repr (dict x))))) +(hy-repr-register collections.defaultdict (fn [x] + (.format "(defaultdict {} {})" + (hy-repr x.default-factory) + (hy-repr (dict x))))) + (for [[types fmt] (partition [ list "[...]" [set HySet] "#{...}" diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy index 411d5a4..a896013 100644 --- a/tests/native_tests/contrib/hy_repr.hy +++ b/tests/native_tests/contrib/hy_repr.hy @@ -119,6 +119,24 @@ (assert (= (hy-repr (D.time 1 2 3 4567 :fold 1 :tzinfo D.timezone.utc)) "(datetime.time 1 2 3 4567 :tzinfo datetime.timezone.utc :fold 1)")))) +(defn test-collections [] + (import collections) + (assert (= (hy-repr (collections.defaultdict :a 8)) + (if PY3 + "(defaultdict None {\"a\" 8})" + "(defaultdict None {b\"a\" 8})"))) + (assert (= (hy-repr (collections.defaultdict int :a 8)) + (if PY3 + "(defaultdict {\"a\" 8})" + "(defaultdict {b\"a\" 8})"))) + (assert (= (hy-repr (collections.Counter [15 15 15 15])) + (if PY3 + "(Counter {15 4})" + "(Counter {15 (int 4)})"))) + (setv C (collections.namedtuple "Fooey" ["cd" "a_b"])) + (assert (= (hy-repr (C 11 12)) + "(Fooey :cd 11 :a_b 12)"))) + (defn test-hy-model-constructors [] (import hy) (assert (= (hy-repr (hy.HyInteger 7)) "'7")) From 8c00ab66f99bc187ec69fc2138ba01f0c37fa6e9 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 3 Nov 2017 14:03:02 -0700 Subject: [PATCH 9/9] NEWS and docs for hy-repr --- NEWS.rst | 8 ++++++ docs/contrib/hy_repr.rst | 58 +++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 689dae5..beb22ed 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -7,6 +7,14 @@ Bug Fixes ------------------------------ * Fix `(return)` so it works correctly to exit a Python 2 generator +Other Breaking Changes +----------------------------- +* `hy-repr` uses registered functions instead of methods + +Misc. Improvements +---------------------------- +* `hy-repr` supports more standard types + 0.14.0 ============================== diff --git a/docs/contrib/hy_repr.rst b/docs/contrib/hy_repr.rst index 51d8842..fae2625 100644 --- a/docs/contrib/hy_repr.rst +++ b/docs/contrib/hy_repr.rst @@ -4,10 +4,10 @@ Hy representations .. versionadded:: 0.13.0 -``hy.contrib.hy-repr`` is a module containing a single function. -To import it, say:: +``hy.contrib.hy-repr`` is a module containing two functions. +To import them, say:: - (import [hy.contrib.hy-repr [hy-repr]]) + (import [hy.contrib.hy-repr [hy-repr hy-repr-register]]) To make the Hy REPL use it for output, invoke Hy like so:: @@ -30,19 +30,47 @@ It returns a string representing the input object in Hy syntax. => (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``. + +.. _hy-repr-register-fn: + +hy-repr-register +---------------- + +Usage: ``(hy-repr-register the-type fun)`` + +``hy-repr-register`` lets you set the function that ``hy-repr`` calls to +represent a type. + +.. code-block:: hy + + => (defclass C) + => (hy-repr-register C (fn [x] "cuddles")) + => (hy-repr [1 (C) 2]) + '[1 cuddles 2]' + +If the type of an object passed to ``hy-repr`` doesn't have a registered +function, ``hy-repr`` will search the type's method resolution order +(its ``__mro__`` attribute) for the first type that does. If ``hy-repr`` +doesn't find a candidate, it falls back on ``repr``. + +Registered functions often call ``hy-repr`` themselves. ``hy-repr`` will +automatically detect self-references, even deeply nested ones, and +output ``"..."`` for them instead of calling the usual registered +function. To use a placeholder other than ``"..."``, pass a string of +your choice to the keyword argument ``:placeholder`` of +``hy-repr-register``. + +.. code-block:: hy + + (defclass Container [object] + [__init__ (fn [self value] + (setv self.value value))]) + (hy-repr-register Container :placeholder "HY THERE" (fn [x] + (+ "(Container " (hy-repr x.value) ")"))) + (setv container (Container 5)) + (setv container.value container) + (print (hy-repr container)) ; Prints "(Container HY THERE)"