Skip to content

Commit 3bbd5d5

Browse files
committed
Add str.isdigit primitive
1 parent 653610f commit 3bbd5d5

File tree

7 files changed

+94
-1
lines changed

7 files changed

+94
-1
lines changed

mypyc/doc/str_operations.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ Methods
3838
* ``s1.find(s2: str)``
3939
* ``s1.find(s2: str, start: int)``
4040
* ``s1.find(s2: str, start: int, end: int)``
41-
* ``s.isspace()``
4241
* ``s.isalnum()``
42+
* ``s.isdigit()``
43+
* ``s.isspace()``
4344
* ``s.join(x: Iterable)``
4445
* ``s.lstrip()``
4546
* ``s.lstrip(chars: str)``

mypyc/lib-rt/CPy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ CPyTagged CPyStr_Ord(PyObject *obj);
782782
PyObject *CPyStr_Multiply(PyObject *str, CPyTagged count);
783783
bool CPyStr_IsSpace(PyObject *str);
784784
bool CPyStr_IsAlnum(PyObject *str);
785+
bool CPyStr_IsDigit(PyObject *str);
785786

786787
// Bytes operations
787788

mypyc/lib-rt/str_ops.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,3 +677,40 @@ bool CPyStr_IsAlnum(PyObject *str) {
677677
}
678678
return true;
679679
}
680+
681+
bool CPyStr_IsDigit(PyObject *str) {
682+
Py_ssize_t len = PyUnicode_GET_LENGTH(str);
683+
if (len == 0) return false;
684+
685+
#define CHECK_ISDIGIT(TYPE, DATA, CHECK) \
686+
{ \
687+
const TYPE *data = (const TYPE *)(DATA); \
688+
for (Py_ssize_t i = 0; i < len; i++) { \
689+
if (!CHECK(data[i])) \
690+
return false; \
691+
} \
692+
}
693+
694+
// ASCII fast path
695+
if (PyUnicode_IS_ASCII(str)) {
696+
CHECK_ISDIGIT(Py_UCS1, PyUnicode_1BYTE_DATA(str), Py_ISDIGIT);
697+
return true;
698+
}
699+
700+
switch (PyUnicode_KIND(str)) {
701+
case PyUnicode_1BYTE_KIND:
702+
CHECK_ISDIGIT(Py_UCS1, PyUnicode_1BYTE_DATA(str), Py_UNICODE_ISDIGIT);
703+
break;
704+
case PyUnicode_2BYTE_KIND:
705+
CHECK_ISDIGIT(Py_UCS2, PyUnicode_2BYTE_DATA(str), Py_UNICODE_ISDIGIT);
706+
break;
707+
case PyUnicode_4BYTE_KIND:
708+
CHECK_ISDIGIT(Py_UCS4, PyUnicode_4BYTE_DATA(str), Py_UNICODE_ISDIGIT);
709+
break;
710+
default:
711+
Py_UNREACHABLE();
712+
}
713+
return true;
714+
715+
#undef CHECK_ISDIGIT
716+
}

mypyc/primitives/str_ops.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,14 @@
413413
error_kind=ERR_NEVER,
414414
)
415415

416+
method_op(
417+
name="isdigit",
418+
arg_types=[str_rprimitive],
419+
return_type=bool_rprimitive,
420+
c_function_name="CPyStr_IsDigit",
421+
error_kind=ERR_NEVER,
422+
)
423+
416424

417425
# obj.decode()
418426
method_op(

mypyc/test-data/fixtures/ir.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def islower(self) -> bool: ...
133133
def count(self, substr: str, start: Optional[int] = None, end: Optional[int] = None) -> int: pass
134134
def isspace(self) -> bool: ...
135135
def isalnum(self) -> bool: ...
136+
def isdigit(self) -> bool: ...
136137

137138
class float:
138139
def __init__(self, x: object) -> None: pass

mypyc/test-data/irbuild-str.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,3 +994,14 @@ def is_alnum(x):
994994
L0:
995995
r0 = CPyStr_IsAlnum(x)
996996
return r0
997+
998+
[case testStrIsDigit]
999+
def is_digit(x: str) -> bool:
1000+
return x.isdigit()
1001+
[out]
1002+
def is_digit(x):
1003+
x :: str
1004+
r0 :: bool
1005+
L0:
1006+
r0 = CPyStr_IsDigit(x)
1007+
return r0

mypyc/test-data/run-strings.test

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,3 +1299,37 @@ def test_isalnum_unicode() -> None:
12991299
# Unicode letter/digit mixed with punctuation — not alnum
13001300
assert not "\u00E9!".isalnum()
13011301
assert not "\u4E2D\u2000".isalnum() # CJK + whitespace
1302+
1303+
[case testIsDigit]
1304+
from typing import Any
1305+
1306+
def test_isdigit() -> None:
1307+
for i in range(0x110000):
1308+
c = chr(i)
1309+
a: Any = c
1310+
assert c.isdigit() == a.isdigit()
1311+
1312+
def test_isdigit_strings() -> None:
1313+
# ASCII digits
1314+
assert "0123456789".isdigit()
1315+
assert not "".isdigit()
1316+
assert not " ".isdigit()
1317+
assert not "a".isdigit()
1318+
assert not "abc".isdigit()
1319+
assert not "!@#".isdigit()
1320+
1321+
# Mixed ASCII
1322+
assert not "123abc".isdigit()
1323+
assert not "abc123".isdigit()
1324+
assert not "12 34".isdigit()
1325+
assert not "123!".isdigit()
1326+
1327+
# Unicode digits
1328+
assert "\u0660\u0661\u0662".isdigit()
1329+
assert "\u00b2\u00b3".isdigit()
1330+
assert "123\U0001d7ce\U0001d7cf\U0001d7d0".isdigit()
1331+
1332+
# Mixed digits and Unicode non-digits
1333+
assert not "\u00e9\u00e8".isdigit()
1334+
assert not "123\u00e9".isdigit()
1335+
assert not "\U0001d7ce!".isdigit()

0 commit comments

Comments
 (0)