Skip to content

Commit 6cb6729

Browse files
authored
basilisp.core/str delegates to Python str in most cases (#1238)
Fixes #1237
1 parent 0d5a5b1 commit 6cb6729

File tree

7 files changed

+97
-31
lines changed

7 files changed

+97
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Changed
1212
* Removed implicit support for single-use iterables in sequences, and introduced `iterator-seq` to expliciltly handle them (#1192)
13+
* `basilisp.core/str` now delegates to the builtin Python `str` in all cases except for customizing the string output for builtin Python types (#1237)
1314

1415
### Fixed
1516
* Fix a bug where protocols with methods with leading hyphens in the could not be defined (#1230)

src/basilisp/contrib/nrepl_server.lpy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
(read reader))}
193193
(catch python/Exception e
194194
(debug :symbol-identify-reader-error :input symbol-str :exception e)
195-
{:error (str e)}))]
195+
{:error (repr e)}))]
196196

197197
(cond
198198
error
@@ -210,7 +210,7 @@
210210
:else
211211
(let [{:keys [var error]} (try {:var (ns-resolve resolve-ns form)}
212212
(catch python/Exception e
213-
{:error (str e)}))]
213+
{:error (repr e)}))]
214214
(cond
215215
var [:var var]
216216
error [:error error]

src/basilisp/core.lpy

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4394,8 +4394,9 @@
43944394

43954395
(defn pr
43964396
"Print the arguments to the stream bound to :lpy:var:`*out*` in a format which is
4397-
readable by the Basilisp reader. Multiple arguments will be separated by the string
4398-
value bound to :lpy:var:`*print-sep*` (default is an ASCII space).
4397+
readable by the Basilisp reader (as by :lpy:fn:`repr`). Multiple arguments will be
4398+
separated by the string value bound to :lpy:var:`*print-sep*` (default is an ASCII
4399+
space).
43994400

44004401
Note that some dynamically created Basilisp forms (such keywords and symbols) and
44014402
Python objects may not be readable again.
@@ -4461,13 +4462,13 @@
44614462
:lpy:var:`*print-sep*` (default is an ASCII space)."
44624463
([] (print ""))
44634464
([x]
4464-
(.write *out* (basilisp.lang.runtime/lstr x))
4465+
(.write *out* (basilisp.lang.runtime/lrepr x ** :human-readable true))
44654466
nil)
44664467
([x & args]
44674468
(let [stdout *out*
44684469
sep *print-sep*
4469-
repr-args (interpose sep (map basilisp.lang.runtime/lstr args))]
4470-
(.write stdout (basilisp.lang.runtime/lstr x))
4470+
repr-args (interpose sep (map #(basilisp.lang.runtime/lrepr % ** :human-readable true) args))]
4471+
(.write stdout (basilisp.lang.runtime/lrepr x ** :human-readable true))
44714472
(.write stdout sep)
44724473
(.write stdout (apply str repr-args))
44734474
nil)))

src/basilisp/lang/obj.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import fractions
23
import math
34
import re
45
import uuid
@@ -123,6 +124,49 @@ def seq_lrepr(
123124
return f"{start}{seq_lrepr}{end}"
124125

125126

127+
@singledispatch
128+
def lstr(o: Any) -> str:
129+
return str(o)
130+
131+
132+
@lstr.register(type(re.compile("")))
133+
def _lstr_pattern(o: Pattern) -> str:
134+
return o.pattern
135+
136+
137+
@lstr.register(bool)
138+
@lstr.register(bytes)
139+
@lstr.register(type(None))
140+
@lstr.register(str)
141+
@lstr.register(list)
142+
@lstr.register(dict)
143+
@lstr.register(set)
144+
@lstr.register(tuple)
145+
@lstr.register(complex)
146+
@lstr.register(float)
147+
@lstr.register(datetime.datetime)
148+
@lstr.register(Decimal)
149+
@lstr.register(fractions.Fraction)
150+
@lstr.register(Path)
151+
def _lstr_lrepr(o) -> str:
152+
"""For built-in types, we want the `str()` representation to match the `lrepr()`.
153+
154+
User types can be customized to their liking.
155+
156+
This function intentionally does not capture the runtime values of the lrepr
157+
keyword arguments."""
158+
return lrepr(
159+
o,
160+
human_readable=True,
161+
print_dup=PRINT_DUP,
162+
print_length=PRINT_LENGTH,
163+
print_level=PRINT_LEVEL,
164+
print_meta=PRINT_META,
165+
print_namespace_maps=PRINT_NAMESPACE_MAPS,
166+
print_readably=PRINT_READABLY,
167+
)
168+
169+
126170
# pylint: disable=unused-argument
127171
@singledispatch
128172
def lrepr( # pylint: disable=too-many-arguments

src/basilisp/lang/runtime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,8 +2011,8 @@ def lrepr(o, human_readable: bool = False) -> str:
20112011

20122012

20132013
def lstr(o) -> str:
2014-
"""Produce a human readable string representation of an object."""
2015-
return lrepr(o, human_readable=True)
2014+
"""Produce a human-readable string representation of an object."""
2015+
return lobj.lstr(o)
20162016

20172017

20182018
__NOT_COMPLETEABLE = re.compile(r"^[0-9].*")

tests/basilisp/lrepr_test.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ def test_print_readably(lcompile: CompileFn):
168168
("##Inf", "(pr-str ##Inf)"),
169169
("##-Inf", "(pr-str ##-Inf)"),
170170
('"hi"', '(pr-str "hi")'),
171+
("sym", "(pr-str 'sym)"),
172+
("some.ns/sym", "(pr-str 'some.ns/sym)"),
173+
(":kw", "(pr-str :kw)"),
174+
(":some.ns/kw", "(pr-str :some.ns/kw)"),
171175
('"Hello\\nworld!"', '(pr-str "Hello\nworld!")'),
172176
(r'"\"Hello world!\""', r'(pr-str "\"Hello world!\"")'),
173177
(
@@ -196,6 +200,8 @@ def test_print_readably(lcompile: CompileFn):
196200
("#py #{:a}", "(pr-str #py #{:a})"),
197201
("#py ()", "(pr-str #py ())"),
198202
('#py (:a 1 "s")', '(pr-str #py (:a 1 "s"))'),
203+
("#'basilisp.core/pr-str", "(pr-str #'pr-str)"),
204+
("basilisp.lrepr-test", "(pr-str *ns*)"),
199205
],
200206
)
201207
def test_lrepr(lcompile: CompileFn, repr: str, code: str):
@@ -267,6 +273,10 @@ def test_lrepr_round_trip_special_cases(lcompile: CompileFn):
267273
("##NaN", "(print-str ##NaN)"),
268274
("##Inf", "(print-str ##Inf)"),
269275
("##-Inf", "(print-str ##-Inf)"),
276+
("sym", "(print-str 'sym)"),
277+
("some.ns/sym", "(print-str 'some.ns/sym)"),
278+
(":kw", "(print-str :kw)"),
279+
(":some.ns/kw", "(print-str :some.ns/kw)"),
270280
("hi", '(print-str "hi")'),
271281
("Hello\nworld!", '(print-str "Hello\nworld!")'),
272282
('"Hello world!"', r'(print-str "\"Hello world!\"")'),
@@ -277,14 +287,8 @@ def test_lrepr_round_trip_special_cases(lcompile: CompileFn):
277287
),
278288
# In Clojure, (print-str #uuid "...") produces '#uuid "..."' but in Basilisp
279289
# print-str is tied directly to str (which in Clojure simply returns the string
280-
# part of the UUID).
281-
#
282-
# I'm not sure which one is right, but it feels a little inconsistent on the
283-
# Clojure side. For now, I'm erring on the side of preferring for str to return
284-
# only the string portion of the UUID and have print-str be slightly wrong.
285-
#
286-
# Maybe at some point, we can deep dive on whether Clojure is in error or if
287-
# Basilisp should just try harder to match the Clojure side regardless.
290+
# part of the UUID). I believe the more correct approach is Basilisp's, so
291+
# I am leaving this as it is.
288292
(
289293
"81f35603-0408-4b3d-bbc0-462e3702747f",
290294
'(print-str #uuid "81f35603-0408-4b3d-bbc0-462e3702747f")',
@@ -298,6 +302,8 @@ def test_lrepr_round_trip_special_cases(lcompile: CompileFn):
298302
("#py ()", "(print-str #py ())"),
299303
("#py {}", "(print-str #py {})"),
300304
("#py #{}", "(print-str #py #{})"),
305+
("#'basilisp.core/print-str", "(print-str #'print-str)"),
306+
("basilisp.lrepr-test", "(print-str *ns*)"),
301307
],
302308
)
303309
def test_lstr(lcompile: CompileFn, s: str, code: str):
@@ -320,6 +326,10 @@ def test_lstr(lcompile: CompileFn, s: str, code: str):
320326
("##NaN", "(str ##NaN)"),
321327
("##Inf", "(str ##Inf)"),
322328
("##-Inf", "(str ##-Inf)"),
329+
("sym", "(str 'sym)"),
330+
("some.ns/sym", "(str 'some.ns/sym)"),
331+
(":kw", "(str :kw)"),
332+
(":some.ns/kw", "(str :some.ns/kw)"),
323333
("hi", '(str "hi")'),
324334
("Hello\nworld!", '(str "Hello\nworld!")'),
325335
('"Hello world!"', r'(str "\"Hello world!\"")'),
@@ -332,7 +342,7 @@ def test_lstr(lcompile: CompileFn, s: str, code: str):
332342
"81f35603-0408-4b3d-bbc0-462e3702747f",
333343
'(str #uuid "81f35603-0408-4b3d-bbc0-462e3702747f")',
334344
),
335-
('#"\\s"', '(str #"\\s")'),
345+
("\\s", '(str #"\\s")'),
336346
(
337347
'#inst "2018-11-28T12:43:25.477000+00:00"',
338348
'(str #inst "2018-11-28T12:43:25.477-00:00")',
@@ -341,6 +351,8 @@ def test_lstr(lcompile: CompileFn, s: str, code: str):
341351
('#py ("a" 1 false)', '(str #py ("a" 1 false))'),
342352
('#py {"a" "b"}', '(str #py {"a" "b"})'),
343353
("#py #{}", "(str #py #{})"),
354+
("#'basilisp.core/str", "(str #'str)"),
355+
("basilisp.lrepr-test", "(str *ns*)"),
344356
("abc", '(str "abc")'),
345357
("false", "(str false)"),
346358
("123", "(str 123)"),

tests/basilisp/test_defrecord.lpy

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -199,23 +199,26 @@
199199
"#tests.basilisp.test-defrecord.Point{:y 2 :x 1 :z 3}"
200200
"#tests.basilisp.test-defrecord.Point{:x 1 :z 3 :y 2}"
201201
"#tests.basilisp.test-defrecord.Point{:z 3 :y 2 :x 1}"}
202-
(str p)))
202+
(repr p)))
203203
(is (contains? #{"^{:interesting :yes} #tests.basilisp.test-defrecord.Point{:x 1 :y 2 :z 3}"
204204
"^{:interesting :yes} #tests.basilisp.test-defrecord.Point{:y 2 :z 3 :x 1}"
205205
"^{:interesting :yes} #tests.basilisp.test-defrecord.Point{:z 3 :x 1 :y 2}"
206206
"^{:interesting :yes} #tests.basilisp.test-defrecord.Point{:y 2 :x 1 :z 3}"
207207
"^{:interesting :yes} #tests.basilisp.test-defrecord.Point{:x 1 :z 3 :y 2}"
208208
"^{:interesting :yes} #tests.basilisp.test-defrecord.Point{:z 3 :y 2 :x 1}"}
209209
(binding [*print-meta* true]
210-
(str (with-meta p {:interesting :yes}))))))))
210+
(repr (with-meta p {:interesting :yes}))))))))
211211

212212
(definterface Shape
213213
(area []))
214214

215215
(defrecord Circle [radius]
216216
Shape
217217
(area [self]
218-
(* 3.14 radius radius)))
218+
(* 3.14 radius radius))
219+
220+
(__str__ [self]
221+
(str "radius: " radius)))
219222

220223
(defrecord InvokeConstructorsRecord []
221224
(__call__ [self ctor]
@@ -251,13 +254,18 @@
251254

252255
(testing "repr"
253256
(is (= "#tests.basilisp.test-defrecord.Circle{:radius 1}"
254-
(str c)))
257+
(repr c)))
255258
(is (= "^{:interesting :yes} #tests.basilisp.test-defrecord.Circle{:radius 1}"
256259
(binding [*print-meta* true]
257-
(str (with-meta c {:interesting :yes})))))
260+
(pr-str (with-meta c {:interesting :yes})))))
258261
(is (contains? #{"#tests.basilisp.test-defrecord.Circle{:radius 1 :name \"Kurt\"}"
259262
"#tests.basilisp.test-defrecord.Circle{:name \"Kurt\" :radius 1}"}
260-
(str c1))))))
263+
(repr c1))))
264+
265+
(testing "str"
266+
(is (= "radius: 1" (str c)))
267+
(is (= "radius: 1" (str c1)))
268+
(is (= "radius: 2" (str (assoc c :radius 2)))))))
261269

262270
(defrecord ?Name [? x? ?x ?? ?x? ??x ?x- x?x])
263271

@@ -279,19 +287,19 @@
279287
p1 (assoc p :w 0)
280288
p2 (assoc p1 :new-key "some-value")
281289
p3 (dissoc p :y)]
282-
(is (= p (eval (read-string (str p)))))
283-
(is (= p1 (eval (read-string (str p1)))))
284-
(is (= p2 (eval (read-string (str p2)))))
285-
(is (= p3 (eval (read-string (str p3)))))
290+
(is (= p (eval (read-string (repr p)))))
291+
(is (= p1 (eval (read-string (repr p1)))))
292+
(is (= p2 (eval (read-string (repr p2)))))
293+
(is (= p3 (eval (read-string (repr p3)))))
286294
(is (= p #tests.basilisp.test-defrecord.Point[1 2 3]))))
287295

288296
(testing "record with methods"
289297
(let [c (->Circle 1)
290298
c1 (assoc c :name "Kurt")
291299
c2 (assoc c1 :radius 3)]
292-
(is (= c (eval (read-string (str c)))))
293-
(is (= c1 (eval (read-string (str c1)))))
294-
(is (= c2 (eval (read-string (str c2)))))
300+
(is (= c (eval (read-string (repr c)))))
301+
(is (= c1 (eval (read-string (repr c1)))))
302+
(is (= c2 (eval (read-string (repr c2)))))
295303
(is (= c #tests.basilisp.test-defrecord.Circle[1]))))
296304

297305
(testing "illegal other forms"

0 commit comments

Comments
 (0)