Skip to content

Commit b61d381

Browse files
authored
Add support for NaN, positive infinity, and negative infinity literals (#582)
* Add support for NaN, positive infinity, and negative infinity literals * Changelog * Be less dumb
1 parent 13336f4 commit b61d381

File tree

5 files changed

+55
-0
lines changed

5 files changed

+55
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
* Added support for auto-resolving namespaces for keyword from the current namespace using the `::kw` syntax (#576)
1010
* Added support for namespaced map syntax (#577)
11+
* Added support for numeric constant literals for NaN, positive infinity, and negative infinity (#582)
1112

1213
### Fixed
1314
* Fixed a bug where `def` forms did not permit recursive references to the `def`'ed Vars (#578)

src/basilisp/lang/obj.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import math
23
import re
34
import uuid
45
from abc import ABC, abstractmethod
@@ -241,6 +242,15 @@ def _lrepr_complex(o: complex, **_) -> str:
241242
return repr(o).upper()
242243

243244

245+
@lrepr.register(float)
246+
def _lrepr_float(o: float, **_) -> str:
247+
if math.isinf(o):
248+
return "##Inf" if o > 0 else "##-Inf"
249+
if math.isnan(o):
250+
return "##NaN"
251+
return repr(o)
252+
253+
244254
@lrepr.register(datetime.datetime)
245255
def _lrepr_datetime(o: datetime.datetime, **_) -> str:
246256
return f'#inst "{o.isoformat()}"'

src/basilisp/lang/reader.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,25 @@ def _read_regex(ctx: ReaderContext) -> Pattern:
12291229
raise ctx.syntax_error(f"Unrecognized regex pattern syntax: {s}")
12301230

12311231

1232+
_NUMERIC_CONSTANTS = {
1233+
"NaN": float("nan"),
1234+
"Inf": float("inf"),
1235+
"-Inf": -float("inf"),
1236+
}
1237+
1238+
1239+
def _read_numeric_constant(ctx: ReaderContext) -> float:
1240+
start = ctx.reader.advance()
1241+
assert start == "#"
1242+
ns, name = _read_namespaced(ctx)
1243+
if ns is not None:
1244+
raise ctx.syntax_error(f"Unrecognized numeric constant: '##{ns}/{name}'")
1245+
c = _NUMERIC_CONSTANTS.get(name)
1246+
if c is None:
1247+
raise ctx.syntax_error(f"Unrecognized numeric constant: '##{name}'")
1248+
return c
1249+
1250+
12321251
def _should_splice_reader_conditional(ctx: ReaderContext, form: LispReaderForm) -> bool:
12331252
"""Return True if and only if form is a ReaderConditional which should be spliced
12341253
into a surrounding collection context."""
@@ -1357,6 +1376,8 @@ def _read_reader_macro(ctx: ReaderContext) -> LispReaderForm:
13571376
return _read_comment(ctx)
13581377
elif token == "?":
13591378
return _read_reader_conditional(ctx)
1379+
elif token == "#":
1380+
return _read_numeric_constant(ctx)
13601381
elif ns_name_chars.match(token):
13611382
s = _read_sym(ctx)
13621383
assert isinstance(s, symbol.Symbol)

tests/basilisp/lrepr_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
import re
23
import uuid
34
from fractions import Fraction
@@ -198,6 +199,9 @@ def test_lrepr(lcompile: CompileFn):
198199
assert "0.64" == lcompile("(pr-str 0.64)")
199200
assert "3.14" == lcompile("(pr-str 3.14M)")
200201
assert "22/7" == lcompile("(pr-str 22/7)")
202+
assert "##NaN" == lcompile("(pr-str ##NaN)")
203+
assert "##Inf" == lcompile("(pr-str ##Inf)")
204+
assert "##-Inf" == lcompile("(pr-str ##-Inf)")
201205
assert '"hi"' == lcompile('(pr-str "hi")')
202206
assert '"Hello\\nworld!"' == lcompile('(pr-str "Hello\nworld!")')
203207
assert '#uuid "81f35603-0408-4b3d-bbc0-462e3702747f"' == lcompile(
@@ -231,6 +235,9 @@ def test_lrepr_round_trip(lcompile: CompileFn):
231235
assert 0.64 == lcompile("(read-string (pr-str 0.64))")
232236
assert 3.14 == lcompile("(read-string (pr-str 3.14M))")
233237
assert Fraction(22, 7) == lcompile("(read-string (pr-str 22/7))")
238+
assert math.isnan(lcompile("(read-string (pr-str ##NaN))"))
239+
assert float("inf") == lcompile("(read-string (pr-str ##Inf))")
240+
assert -float("inf") == lcompile("(read-string (pr-str ##-Inf))")
234241
assert "hi" == lcompile('(read-string (pr-str "hi"))')
235242
assert "Hello\nworld!" == lcompile('(read-string (pr-str "Hello\nworld!"))')
236243
assert uuid.UUID("81f35603-0408-4b3d-bbc0-462e3702747f") == lcompile(
@@ -271,6 +278,9 @@ def test_lstr(lcompile: CompileFn):
271278
assert "0.64" == lcompile("(print-str 0.64)")
272279
assert "3.14" == lcompile("(print-str 3.14M)")
273280
assert "22/7" == lcompile("(print-str 22/7)")
281+
assert "##NaN" == lcompile("(print-str ##NaN)")
282+
assert "##Inf" == lcompile("(print-str ##Inf)")
283+
assert "##-Inf" == lcompile("(print-str ##-Inf)")
274284
assert "hi" == lcompile('(print-str "hi")')
275285
assert "Hello\nworld!" == lcompile('(print-str "Hello\nworld!")')
276286
assert '#uuid "81f35603-0408-4b3d-bbc0-462e3702747f"' == lcompile(

tests/basilisp/reader_test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io
2+
import math
23
from typing import Optional
34

45
import pytest
@@ -1265,6 +1266,18 @@ def test_regex_reader_literal():
12651266
read_str_first(r'#"\y"')
12661267

12671268

1269+
def test_numeric_constant_literal():
1270+
assert math.isnan(read_str_first("##NaN"))
1271+
assert read_str_first("##Inf") == float("inf")
1272+
assert read_str_first("##-Inf") == -float("inf")
1273+
1274+
with pytest.raises(reader.SyntaxError):
1275+
read_str_first("##float/NaN")
1276+
1277+
with pytest.raises(reader.SyntaxError):
1278+
read_str_first("##e")
1279+
1280+
12681281
def test_uuid_reader_literal():
12691282
assert read_str_first(
12701283
'#uuid "4ba98ef0-0620-4966-af61-f0f6c2dbf230"'

0 commit comments

Comments
 (0)