Skip to content

Commit 4b3dddf

Browse files
committed
Add str.isdigit primitive
1 parent a62b691 commit 4b3dddf

File tree

7 files changed

+94
-0
lines changed

7 files changed

+94
-0
lines changed

mypyc/doc/str_operations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Methods
3838
* ``s1.find(s2: str)``
3939
* ``s1.find(s2: str, start: int)``
4040
* ``s1.find(s2: str, start: int, end: int)``
41+
* ``s.isdigit()``
4142
* ``s.join(x: Iterable)``
4243
* ``s.lstrip()``
4344
* ``s.lstrip(chars: str)``

mypyc/lib-rt/CPy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ Py_ssize_t CPyStr_CountFull(PyObject *unicode, PyObject *substring, CPyTagged st
781781
CPyTagged CPyStr_Ord(PyObject *obj);
782782
PyObject *CPyStr_Multiply(PyObject *str, CPyTagged count);
783783
bool CPyStr_IsSpace(PyObject *str);
784+
bool CPyStr_IsDigit(PyObject *str);
784785

785786
// Bytes operations
786787

mypyc/lib-rt/str_ops.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,3 +654,41 @@ bool CPyStr_IsSpace(PyObject *str) {
654654
}
655655
return true;
656656
}
657+
658+
659+
bool CPyStr_IsDigit(PyObject *str) {
660+
Py_ssize_t len = PyUnicode_GET_LENGTH(str);
661+
if (len == 0) return false;
662+
663+
#define CHECK_ISDIGIT(TYPE, DATA, CHECK) \
664+
{ \
665+
const TYPE *data = (const TYPE *)(DATA); \
666+
for (Py_ssize_t i = 0; i < len; i++) { \
667+
if (!CHECK(data[i])) \
668+
return false; \
669+
} \
670+
}
671+
672+
// ASCII fast path
673+
if (PyUnicode_IS_ASCII(str)) {
674+
CHECK_ISDIGIT(Py_UCS1, PyUnicode_1BYTE_DATA(str), Py_ISDIGIT);
675+
return true;
676+
}
677+
678+
switch (PyUnicode_KIND(str)) {
679+
case PyUnicode_1BYTE_KIND:
680+
CHECK_ISDIGIT(Py_UCS1, PyUnicode_1BYTE_DATA(str), Py_UNICODE_ISDIGIT);
681+
break;
682+
case PyUnicode_2BYTE_KIND:
683+
CHECK_ISDIGIT(Py_UCS2, PyUnicode_2BYTE_DATA(str), Py_UNICODE_ISDIGIT);
684+
break;
685+
case PyUnicode_4BYTE_KIND:
686+
CHECK_ISDIGIT(Py_UCS4, PyUnicode_4BYTE_DATA(str), Py_UNICODE_ISDIGIT);
687+
break;
688+
default:
689+
Py_UNREACHABLE();
690+
}
691+
return true;
692+
693+
#undef CHECK_ISDIGIT
694+
}

mypyc/primitives/str_ops.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,14 @@
405405
error_kind=ERR_NEVER,
406406
)
407407

408+
method_op(
409+
name="isdigit",
410+
arg_types=[str_rprimitive],
411+
return_type=bool_rprimitive,
412+
c_function_name="CPyStr_IsDigit",
413+
error_kind=ERR_NEVER,
414+
)
415+
408416
# obj.decode()
409417
method_op(
410418
name="decode",

mypyc/test-data/fixtures/ir.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def removesuffix(self, suffix: str, /) -> str: ...
132132
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: ...
135+
def isdigit(self) -> bool: ...
135136

136137
class float:
137138
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
@@ -983,3 +983,14 @@ def is_space(x):
983983
L0:
984984
r0 = CPyStr_IsSpace(x)
985985
return r0
986+
987+
[case testStrIsDigit]
988+
def is_digit(x: str) -> bool:
989+
return x.isdigit()
990+
[out]
991+
def is_digit(x):
992+
x :: str
993+
r0 :: bool
994+
L0:
995+
r0 = CPyStr_IsDigit(x)
996+
return r0

mypyc/test-data/run-strings.test

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,3 +1269,37 @@ def test_isspace() -> None:
12691269
c = chr(i)
12701270
a: Any = c
12711271
assert c.isspace() == a.isspace()
1272+
1273+
[case testIsDigit]
1274+
from typing import Any
1275+
1276+
def test_isdigit() -> None:
1277+
for i in range(0x110000):
1278+
c = chr(i)
1279+
a: Any = c
1280+
assert c.isdigit() == a.isdigit()
1281+
1282+
def test_isdigit_strings() -> None:
1283+
# ASCII digits
1284+
assert "0123456789".isdigit()
1285+
assert not "".isdigit()
1286+
assert not " ".isdigit()
1287+
assert not "a".isdigit()
1288+
assert not "abc".isdigit()
1289+
assert not "!@#".isdigit()
1290+
1291+
# Mixed ASCII
1292+
assert not "123abc".isdigit()
1293+
assert not "abc123".isdigit()
1294+
assert not "12 34".isdigit()
1295+
assert not "123!".isdigit()
1296+
1297+
# Unicode digits
1298+
assert "\u0660\u0661\u0662".isdigit()
1299+
assert "\u00b2\u00b3".isdigit()
1300+
assert "123\U0001d7ce\U0001d7cf\U0001d7d0".isdigit()
1301+
1302+
# Mixed digits and Unicode non-digits
1303+
assert not "\u00e9\u00e8".isdigit()
1304+
assert not "123\u00e9".isdigit()
1305+
assert not "\U0001d7ce!".isdigit()

0 commit comments

Comments
 (0)