Merge pull request #1294 from Kodiologist/nan-and-inf

Require capitalizing NaN and Inf like so
This commit is contained in:
Kodi Arfer 2017-07-10 08:54:38 -07:00 committed by GitHub
commit 5610d7dedf
6 changed files with 83 additions and 21 deletions

1
NEWS
View File

@ -8,6 +8,7 @@ Changes from 0.13.0
* The compiler now automatically promotes values to Hy model objects * The compiler now automatically promotes values to Hy model objects
as necessary, so you can write ``(eval `(+ 1 ~n))`` instead of as necessary, so you can write ``(eval `(+ 1 ~n))`` instead of
``(eval `(+ 1 ~(HyInteger n)))`` ``(eval `(+ 1 ~(HyInteger n)))``
* Literal `Inf`s and `NaN`s must now be capitalized like that
[ Bug Fixes ] [ Bug Fixes ]
* Numeric literals are no longer parsed as symbols when followed by a dot * Numeric literals are no longer parsed as symbols when followed by a dot

View File

@ -52,6 +52,9 @@ digits.
(print 10,000,000,000 10_000_000_000) (print 10,000,000,000 10_000_000_000)
Unlike Python, Hy provides literal forms for NaN and infinity: `NaN`, `Inf`,
and `-Inf`.
string literals string literals
--------------- ---------------

View File

@ -2,8 +2,10 @@
;; This file is part of Hy, which is free software licensed under the Expat ;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE. ;; license. See the LICENSE.
(import [hy._compat [PY3 str-type bytes-type long-type]]) (import
(import [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyList HyDict HySet HyString HyBytes]]) [math [isnan]]
[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] (defn hy-repr [obj]
(setv seen (set)) (setv seen (set))
@ -72,8 +74,14 @@
(.format "(int {})" (repr x)) (.format "(int {})" (repr x))
(and (not PY3) (in t [long_type HyInteger])) (and (not PY3) (in t [long_type HyInteger]))
(.rstrip (repr x) "L") (.rstrip (repr x) "L")
(is t complex) (and (in t [float HyFloat]) (isnan x))
(.strip (repr x) "()") "NaN"
(= x Inf)
"Inf"
(= x -Inf)
"-Inf"
(in t [complex HyComplex])
(.replace (.replace (.strip (repr x) "()") "inf" "Inf") "nan" "NaN")
(is t fraction) (is t fraction)
(.format "{}/{}" (f x.numerator q) (f x.denominator q)) (.format "{}/{}" (f x.numerator q) (f x.denominator q))
; else ; else

View File

@ -3,6 +3,7 @@
# license. See the LICENSE. # license. See the LICENSE.
from __future__ import unicode_literals from __future__ import unicode_literals
from math import isnan, isinf
from hy._compat import PY3, str_type, bytes_type, long_type, string_types from hy._compat import PY3, str_type, bytes_type, long_type, string_types
from fractions import Fraction from fractions import Fraction
@ -142,15 +143,24 @@ if not PY3: # do not add long on python3
_wrappers[long_type] = HyInteger _wrappers[long_type] = HyInteger
def check_inf_nan_cap(arg, value):
if isinstance(arg, string_types):
if isinf(value) and "Inf" not in arg:
raise ValueError('Inf must be capitalized as "Inf"')
if isnan(value) and "NaN" not in arg:
raise ValueError('NaN must be capitalized as "NaN"')
class HyFloat(HyObject, float): class HyFloat(HyObject, float):
""" """
Internal representation of a Hy Float. May raise a ValueError as if Internal representation of a Hy Float. May raise a ValueError as if
float(foo) was called, given HyFloat(foo). float(foo) was called, given HyFloat(foo).
""" """
def __new__(cls, number, *args, **kwargs): def __new__(cls, num, *args, **kwargs):
number = float(strip_digit_separators(number)) value = super(HyFloat, cls).__new__(cls, strip_digit_separators(num))
return super(HyFloat, cls).__new__(cls, number) check_inf_nan_cap(num, value)
return value
_wrappers[float] = HyFloat _wrappers[float] = HyFloat
@ -161,9 +171,18 @@ class HyComplex(HyObject, complex):
complex(foo) was called, given HyComplex(foo). complex(foo) was called, given HyComplex(foo).
""" """
def __new__(cls, number, *args, **kwargs): def __new__(cls, num, *args, **kwargs):
number = complex(strip_digit_separators(number)) value = super(HyComplex, cls).__new__(cls, strip_digit_separators(num))
return super(HyComplex, cls).__new__(cls, number) if isinstance(num, string_types):
p1, _, p2 = num.lstrip("+-").replace("-", "+").partition("+")
if p2:
check_inf_nan_cap(p1, value.real)
check_inf_nan_cap(p2, value.imag)
elif "j" in p1:
check_inf_nan_cap(p1, value.imag)
else:
check_inf_nan_cap(p1, value.real)
return value
_wrappers[complex] = HyComplex _wrappers[complex] = HyComplex

View File

@ -3,16 +3,17 @@
;; license. See the LICENSE. ;; license. See the LICENSE.
(import (import
[math [isnan]]
[hy.contrib.hy-repr [hy-repr]]) [hy.contrib.hy-repr [hy-repr]])
(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.
(setv values [ (setv values [
None False True None False True
5 5.1 '5 '5.1 5 5.1 '5 '5.1 Inf -Inf
(int 5) (int 5)
1/2 1/2
5j 5.1j 2+1j 1.2+3.4j 5j 5.1j 2+1j 1.2+3.4j Inf-Infj
"" b"" "" b""
'"" 'b"" '"" 'b""
"apple bloom" b"apple bloom" "⚘" "apple bloom" b"apple bloom" "⚘"
@ -32,10 +33,17 @@
(for [original-val values] (for [original-val values]
(setv evaled (eval (read-str (hy-repr original-val)))) (setv evaled (eval (read-str (hy-repr original-val))))
(assert (= evaled original-val)) (assert (= evaled original-val))
(assert (is (type evaled) (type original-val))))) (assert (is (type evaled) (type original-val))))
(assert (isnan (eval (read-str (hy-repr NaN))))))
(defn test-hy-repr-roundtrip-from-str [] (defn test-hy-repr-roundtrip-from-str []
(setv strs [ (setv strs [
"'Inf"
"'-Inf"
"'NaN"
"1+2j"
"NaN+NaNj"
"'NaN+NaNj"
"[1 2 3]" "[1 2 3]"
"'[1 2 3]" "'[1 2 3]"
"[1 'a 3]" "[1 'a 3]"

View File

@ -2,6 +2,7 @@
# This file is part of Hy, which is free software licensed under the Expat # This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE. # license. See the LICENSE.
from math import isnan
from hy.models import (HyExpression, HyInteger, HyFloat, HyComplex, HySymbol, from hy.models import (HyExpression, HyInteger, HyFloat, HyComplex, HySymbol,
HyString, HyDict, HyList, HySet, HyCons) HyString, HyDict, HyList, HySet, HyCons)
from hy.lex import LexException, PrematureEndOfInput, tokenize from hy.lex import LexException, PrematureEndOfInput, tokenize
@ -91,16 +92,38 @@ def test_lex_expression_float():
assert objs == [HyExpression([HySymbol("foo"), HyFloat(1.e7)])] assert objs == [HyExpression([HySymbol("foo"), HyFloat(1.e7)])]
def test_lex_nan_and_inf():
assert isnan(tokenize("NaN")[0])
assert tokenize("Nan") == [HySymbol("Nan")]
assert tokenize("nan") == [HySymbol("nan")]
assert tokenize("NAN") == [HySymbol("NAN")]
assert tokenize("Inf") == [HyFloat(float("inf"))]
assert tokenize("inf") == [HySymbol("inf")]
assert tokenize("INF") == [HySymbol("INF")]
assert tokenize("-Inf") == [HyFloat(float("-inf"))]
assert tokenize("-inf") == [HySymbol("_inf")]
assert tokenize("-INF") == [HySymbol("_INF")]
def test_lex_expression_complex(): def test_lex_expression_complex():
""" Make sure expressions can produce complex """ """ Make sure expressions can produce complex """
objs = tokenize("(foo 2.j)")
assert objs == [HyExpression([HySymbol("foo"), HyComplex(2.j)])] def t(x): return tokenize("(foo {})".format(x))
objs = tokenize("(foo -0.5j)")
assert objs == [HyExpression([HySymbol("foo"), HyComplex(-0.5j)])] def f(x): return [HyExpression([HySymbol("foo"), x])]
objs = tokenize("(foo 1.e7j)")
assert objs == [HyExpression([HySymbol("foo"), HyComplex(1.e7j)])] assert t("2.j") == f(HyComplex(2.j))
objs = tokenize("(foo j)") assert t("-0.5j") == f(HyComplex(-0.5j))
assert objs == [HyExpression([HySymbol("foo"), HySymbol("j")])] assert t("1.e7j") == f(HyComplex(1e7j))
assert t("j") == f(HySymbol("j"))
assert isnan(t("NaNj")[0][1].imag)
assert t("nanj") == f(HySymbol("nanj"))
assert t("Inf+Infj") == f(HyComplex(complex(float("inf"), float("inf"))))
assert t("Inf-Infj") == f(HyComplex(complex(float("inf"), float("-inf"))))
assert t("Inf-INFj") == f(HySymbol("Inf_INFj"))
def test_lex_digit_separators(): def test_lex_digit_separators():