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.
This commit is contained in:
Kodi Arfer 2017-10-31 16:48:59 -07:00
parent a688355777
commit 83c4f63bc2
2 changed files with 108 additions and 90 deletions

View File

@ -7,92 +7,96 @@
[hy._compat [PY3 str-type bytes-type long-type]] [hy._compat [PY3 str-type bytes-type long-type]]
[hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]]) [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyFloat HyComplex HyList HyDict HySet HyString HyBytes]])
(setv -registry {})
(defn hy-repr-register [types f &optional placeholder]
(for [typ (if (instance? list types) types [types])]
(setv (get -registry typ) (, f placeholder))))
(setv -quoting False)
(setv -seen (set))
(defn hy-repr [obj] (defn hy-repr [obj]
(setv seen (set)) (setv [f placeholder] (next
; We keep track of objects we've already seen, and avoid (genexpr (get -registry t)
; redisplaying their contents, so a self-referential object [t (. (type obj) __mro__)]
; doesn't send us into an infinite loop. (in t -registry))
(defn f [x q] [-base-repr None]))
; `x` is the current object being stringified.
; `q` is True if we're inside a single quote, False otherwise. (global -quoting)
(setv old? (in (id x) seen)) (setv started-quoting False)
(.add seen (id x)) (when (and (not -quoting) (instance? HyObject obj))
(setv t (type x)) (setv -quoting True)
(defn catted [] (setv started-quoting True))
(if old? "..." (.join " " (list-comp (f it q) [it x]))))
(setv prefix "") (setv oid (id obj))
(if (and (not q) (instance? HyObject x)) (when (in oid -seen)
(setv prefix "'" q True)) (return (if (none? placeholder) "..." placeholder)))
(+ prefix (if (.add -seen oid)
(hasattr x "__hy_repr__")
(.__hy-repr__ x) (try
(is t HyExpression) (+ (if started-quoting "'" "") (f obj))
(if (and x (symbol? (first x))) (finally
(if (.discard -seen oid)
(= (first x) 'quote) (when started-quoting
(+ "'" (f (second x) True)) (setv -quoting False)))))
(= (first x) 'quasiquote)
(+ "`" (f (second x) q)) (hy-repr-register list :placeholder "[...]" (fn [x]
(= (first x) 'unquote) (+ "[" (-cat x) "]")))
(+ "~" (f (second x) q)) (hy-repr-register tuple (fn [x]
(= (first x) 'unquote_splice) (+ "(," (if x " " "") (-cat x) ")")))
(+ "~@" (f (second x) q)) (hy-repr-register dict :placeholder "{...}" (fn [x]
(= (first x) 'unpack_iterable) (+ "{" (-cat (reduce + (.items x))) "}")))
(+ "#* " (f (second x) q)) (hy-repr-register HyDict :placeholder "{...}" (fn [x]
(= (first x) 'unpack_mapping) (+ "{" (-cat x) "}")))
(+ "#** " (f (second x) q)) (hy-repr-register [set HySet] (fn [x]
; else (+ "#{" (-cat x) "}")))
(+ "(" (catted) ")")) (hy-repr-register frozenset (fn [x]
(+ "(" (catted) ")")) (+ "(frozenset #{" (-cat x) "})")))
(is t tuple) (hy-repr-register HyExpression (fn [x]
(+ "(," (if x " " "") (catted) ")") (setv syntax {
(in t [list HyList]) 'quote "'"
(+ "[" (catted) "]") 'quasiquote "`"
(is t HyDict) 'unquote "~"
(+ "{" (catted) "}") 'unquote_splice "~@"
(is t dict) '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 old? "..." (.join " " (list-comp (if (.startswith "\"" r)
(+ (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 ; If Python's built-in repr produced a double-quoted string, use
; that. ; that.
r r
; Otherwise, we have a single-quoted string, which isn't valid Hy, so ; Otherwise, we have a single-quoted string, which isn't valid Hy, so
; convert it. ; convert it.
(+ "\"" (.replace (cut r 1 -1) "\"" "\\\"") "\"")))) (+ "\"" (.replace (cut r 1 -1) "\"" "\\\"") "\"")))))
(and (not PY3) (is t int)) (hy-repr-register bool str)
(.format "(int {})" (base-repr x)) (if (not PY3) (hy-repr-register int (fn [x]
(and (not PY3) (in t [long_type HyInteger])) (.format "(int {})" (-base-repr x)))))
(.rstrip (base-repr x) "L") (if (not PY3) (hy-repr-register long_type (fn [x]
(and (in t [float HyFloat]) (isnan x)) (.rstrip (-base-repr x) "L"))))
"NaN" (hy-repr-register float (fn [x]
(and (in t [float HyFloat]) (= x Inf)) (if
"Inf" (isnan x) "NaN"
(and (in t [float HyFloat]) (= x -Inf)) (= x Inf) "Inf"
"-Inf" (= x -Inf) "-Inf"
(in t [complex HyComplex]) (-base-repr x))))
(.replace (.replace (.strip (base-repr x) "()") "inf" "Inf") "nan" "NaN") (hy-repr-register complex (fn [x]
(is t fraction) (.replace (.replace (.strip (-base-repr x) "()") "inf" "Inf") "nan" "NaN")))
(.format "{}/{}" (f x.numerator q) (f x.denominator q)) (hy-repr-register fraction (fn [x]
; else (.format "{}/{}" (hy-repr x.numerator) (hy-repr x.denominator))))
(base-repr x))))
(f obj False))
(defn base-repr [x] (defn -cat [obj]
(.join " " (map hy-repr obj)))
(defn -base-repr [x]
(unless (instance? HyObject x) (unless (instance? HyObject x)
(return (repr x))) (return (repr x)))
; Call (.repr x) using the first class of x that doesn't inherit from ; Call (.repr x) using the first class of x that doesn't inherit from

View File

@ -4,7 +4,7 @@
(import (import
[math [isnan]] [math [isnan]]
[hy.contrib.hy-repr [hy-repr]]) [hy.contrib.hy-repr [hy-repr hy-repr-register]])
(defn test-hy-repr-roundtrip-from-value [] (defn test-hy-repr-roundtrip-from-value []
; Test that a variety of values round-trip properly. ; Test that a variety of values round-trip properly.
@ -86,10 +86,24 @@
(+ "{" (.join " " p) "}") (+ "{" (.join " " p) "}")
[p (permutations ["1 2" "3 [4 {...}]" "6 7"])])))) [p (permutations ["1 2" "3 [4 {...}]" "6 7"])]))))
(defn test-hy-repr-dunder-method [] (defn test-hy-repr-custom []
(defclass C [list] [__hy-repr__ (fn [self] "cuddles")])
(assert (= (hy-repr (C)) "cuddles"))) (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 [] (defn test-hy-repr-fallback []
(defclass D [list] [__repr__ (fn [self] "cuddles")]) (defclass D [object]
[__repr__ (fn [self] "cuddles")])
(assert (= (hy-repr (D)) "cuddles"))) (assert (= (hy-repr (D)) "cuddles")))