From f3edeb99ae0f313a36be6e976e89545e580da91c Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Tue, 14 Feb 2017 13:18:51 -0800 Subject: [PATCH] Allow commas and underscores in numeric literals You can use them as thousands separators. This change differs from PEP 515 in that not only does it allow commas in addition to underscores, but it's much more liberal about placement. Any number of underscores or commas can be placed anywhere, even at the start. --- NEWS | 1 + docs/language/api.rst | 15 ++++++++++----- hy/models/complex.py | 3 +++ hy/models/float.py | 3 +++ hy/models/integer.py | 5 +++-- tests/lex/test_lex.py | 26 ++++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 2414f89..86f8588 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ Changes from 0.12.1 [ Language Changes ] * `let` has been removed. Python's scoping rules do not make a proper implementation of it possible. Use `setv` instead. + * Commas and underscores are allowed in numeric literals * xor: If exactly one argument is true, return it [ Bug Fixes ] diff --git a/docs/language/api.rst b/docs/language/api.rst index 0b68f90..ba1fe5f 100644 --- a/docs/language/api.rst +++ b/docs/language/api.rst @@ -18,7 +18,7 @@ These rules help ensure that Hy code is idiomatic and interfaceable in both languages. - * Symbols in earmufs will be translated to the upper-cased version of that + * Symbols in earmuffs will be translated to the upper-cased version of that string. For example, ``foo`` will become ``FOO``. * UTF-8 entities will be encoded using @@ -34,10 +34,8 @@ languages. Notes on Syntax =============== -integers --------- - -.. versionadded:: 0.11.1 +numeric literals +---------------- In addition to regular numbers, standard notation from Python 3 for non-base 10 integers is used. ``0x`` for Hex, ``0o`` for Octal, ``0b`` for Binary. @@ -46,6 +44,13 @@ integers is used. ``0x`` for Hex, ``0o`` for Octal, ``0b`` for Binary. (print 0x80 0b11101 0o102 30) +Underscores and commas can appear anywhere in a numeric literal. They have no +effect on the value of the literal, but they're useful for visually separating +digits. + +.. code-block:: clj + + (print 10,000,000,000 10_000_000_000) Built-Ins ========= diff --git a/hy/models/complex.py b/hy/models/complex.py index aead797..d45ce6f 100644 --- a/hy/models/complex.py +++ b/hy/models/complex.py @@ -19,6 +19,7 @@ # DEALINGS IN THE SOFTWARE. from hy.models import HyObject, _wrappers +from hy._compat import string_types class HyComplex(HyObject, complex): @@ -28,6 +29,8 @@ class HyComplex(HyObject, complex): """ def __new__(cls, number, *args, **kwargs): + if isinstance(number, string_types): + number = number.replace("_", "").replace(",", "") number = complex(number) return super(HyComplex, cls).__new__(cls, number) diff --git a/hy/models/float.py b/hy/models/float.py index 1632963..4669ff2 100644 --- a/hy/models/float.py +++ b/hy/models/float.py @@ -19,6 +19,7 @@ # DEALINGS IN THE SOFTWARE. from hy.models import HyObject, _wrappers +from hy._compat import string_types class HyFloat(HyObject, float): @@ -28,6 +29,8 @@ class HyFloat(HyObject, float): """ def __new__(cls, number, *args, **kwargs): + if isinstance(number, string_types): + number = number.replace("_", "").replace(",", "") number = float(number) return super(HyFloat, cls).__new__(cls, number) diff --git a/hy/models/integer.py b/hy/models/integer.py index f54727b..e780b26 100644 --- a/hy/models/integer.py +++ b/hy/models/integer.py @@ -19,7 +19,7 @@ # DEALINGS IN THE SOFTWARE. from hy.models import HyObject, _wrappers -from hy._compat import long_type, str_type +from hy._compat import long_type, string_types import sys @@ -32,7 +32,8 @@ class HyInteger(HyObject, long_type): """ def __new__(cls, number, *args, **kwargs): - if isinstance(number, str_type): + if isinstance(number, string_types): + number = number.replace("_", "").replace(",", "") bases = {"0x": 16, "0o": 8, "0b": 2} for leader, base in bases.items(): if number.startswith(leader): diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index cc9e7cc..881eef2 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -143,6 +143,32 @@ def test_lex_expression_complex(): assert objs == [HyExpression([HySymbol("foo"), HySymbol("j")])] +def test_lex_digit_separators(): + + assert tokenize("1_000_000") == [HyInteger(1000000)] + assert tokenize("1,000,000") == [HyInteger(1000000)] + assert tokenize("1,000_000") == [HyInteger(1000000)] + assert tokenize("1_000,000") == [HyInteger(1000000)] + + assert tokenize("0x_af") == [HyInteger(0xaf)] + assert tokenize("0x,af") == [HyInteger(0xaf)] + assert tokenize("0b_010") == [HyInteger(0b010)] + assert tokenize("0b,010") == [HyInteger(0b010)] + assert tokenize("0o_373") == [HyInteger(0o373)] + assert tokenize("0o,373") == [HyInteger(0o373)] + + assert tokenize('1_2.3,4') == [HyFloat(12.34)] + assert tokenize('1_2e3,4') == [HyFloat(12e34)] + assert (tokenize("1,2/3_4") == + [HyExpression([HySymbol("fraction"), + HyInteger(12), HyInteger(34)])]) + assert tokenize("1,0_00j") == [HyComplex(1000j)] + + assert tokenize(",,,,___,__1__,,__,,2__,,,__") == [HyInteger(12)] + assert (tokenize(",,,,___,__1__,,__,,2__,q,__") == + [HySymbol(",,,,___,__1__,,__,,2__,q,__")]) + + def test_lex_line_counting(): """ Make sure we can count lines / columns """ entry = tokenize("(foo (one two))")[0]