Skip to content

Commit 7bacf2c

Browse files
committed
Add str_increment() function
1 parent f109caa commit 7bacf2c

File tree

7 files changed

+187
-13
lines changed

7 files changed

+187
-13
lines changed

Zend/zend_operators.c

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,6 +2397,19 @@ ZEND_API bool ZEND_FASTCALL instanceof_function_slow(const zend_class_entry *ins
23972397
#define UPPER_CASE 2
23982398
#define NUMERIC 3
23992399

2400+
ZEND_API bool zend_string_only_has_ascii_alphanumeric(const zend_string *str)
2401+
{
2402+
const char *p = ZSTR_VAL(str);
2403+
const char *e = ZSTR_VAL(str) + ZSTR_LEN(str);
2404+
while (p < e) {
2405+
char c = *p++;
2406+
if (UNEXPECTED( c < '0' || c > 'z' || (c < 'a' && c > 'Z') || (c < 'A' && c > '9') ) ) {
2407+
return false;
2408+
}
2409+
}
2410+
return true;
2411+
}
2412+
24002413
static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */
24012414
{
24022415
int carry=0;
@@ -2416,18 +2429,10 @@ static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */
24162429
return;
24172430
}
24182431

2419-
{
2420-
const char *p = Z_STRVAL_P(str);
2421-
const char *e = Z_STRVAL_P(str) + Z_STRLEN_P(str);
2422-
while (p < e) {
2423-
char c = *p++;
2424-
if (UNEXPECTED( c < '0' || c > 'z' || (c < 'a' && c > 'Z') || (c < 'A' && c > '9') ) ) {
2425-
zend_error(E_DEPRECATED, "Increment on non-alphanumeric string is deprecated");
2426-
if (EG(exception)) {
2427-
return;
2428-
}
2429-
break;
2430-
}
2432+
if (UNEXPECTED(!zend_string_only_has_ascii_alphanumeric(Z_STR_P(str)))) {
2433+
zend_error(E_DEPRECATED, "Increment on non-alphanumeric string is deprecated");
2434+
if (EG(exception)) {
2435+
return;
24312436
}
24322437
}
24332438

Zend/zend_operators.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ static zend_always_inline bool instanceof_function(
7171
return instance_ce == ce || instanceof_function_slow(instance_ce, ce);
7272
}
7373

74+
ZEND_API bool zend_string_only_has_ascii_alphanumeric(const zend_string *str);
75+
7476
/**
7577
* Checks whether the string "str" with length "length" is numeric. The value
7678
* of allow_errors determines whether it's required to be entirely numeric, or

ext/standard/basic_functions.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,6 +2299,8 @@ function strtoupper(string $string): string {}
22992299
/** @compile-time-eval */
23002300
function strtolower(string $string): string {}
23012301

2302+
function str_increment(string $string): string {}
2303+
23022304
/** @refcount 1 */
23032305
function basename(string $path, string $suffix = ""): string {}
23042306

ext/standard/basic_functions_arginfo.h

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/string.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,60 @@ PHP_FUNCTION(strtolower)
11891189
}
11901190
/* }}} */
11911191

1192+
PHP_FUNCTION(str_increment)
1193+
{
1194+
zend_string *str;
1195+
1196+
ZEND_PARSE_PARAMETERS_START(1, 1)
1197+
Z_PARAM_STR(str)
1198+
ZEND_PARSE_PARAMETERS_END();
1199+
1200+
if (ZSTR_LEN(str) == 0) {
1201+
zend_argument_value_error(1, "cannot be empty");
1202+
RETURN_THROWS();
1203+
}
1204+
if (!zend_string_only_has_ascii_alphanumeric(str)) {
1205+
zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters");
1206+
RETURN_THROWS();
1207+
}
1208+
1209+
zend_string *incremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistant */ false);
1210+
size_t position = ZSTR_LEN(str)-1;
1211+
bool carry = false;
1212+
1213+
do {
1214+
char c = ZSTR_VAL(incremented)[position];
1215+
if (EXPECTED( (c >= 'a' && c < 'z') || (c >= 'A' && c < 'Z') || (c >= '0' && c < '9') )) {
1216+
carry = false;
1217+
ZSTR_VAL(incremented)[position]++;
1218+
} else { /* if 'z', 'Z', or '9' */
1219+
carry = true;
1220+
if (c == '9') {
1221+
ZSTR_VAL(incremented)[position] = '0';
1222+
} else {
1223+
ZSTR_VAL(incremented)[position] -= 25;
1224+
}
1225+
}
1226+
} while (carry && position-- > 0);
1227+
1228+
if (UNEXPECTED(carry)) {
1229+
zend_string *tmp = zend_string_alloc(ZSTR_LEN(incremented)+1, 0);
1230+
memcpy(ZSTR_VAL(tmp) + 1, ZSTR_VAL(incremented), ZSTR_LEN(incremented));
1231+
ZSTR_VAL(tmp)[ZSTR_LEN(incremented)+1] = '\0';
1232+
switch (ZSTR_VAL(incremented)[0]) {
1233+
case '0':
1234+
ZSTR_VAL(tmp)[0] = '1';
1235+
break;
1236+
default:
1237+
ZSTR_VAL(tmp)[0] = ZSTR_VAL(incremented)[0];
1238+
break;
1239+
}
1240+
zend_string_release_ex(incremented, /* persistant */ false);
1241+
RETURN_STR(tmp);
1242+
}
1243+
RETURN_STR(incremented);
1244+
}
1245+
11921246
#if defined(PHP_WIN32)
11931247
static bool _is_basename_start(const char *start, const char *pos)
11941248
{
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
str_increment(): Incrementing various strings
3+
--FILE--
4+
<?php
5+
6+
$strictlyAlphaNumeric = [
7+
"Az",
8+
"aZ",
9+
"A9",
10+
"a9",
11+
// Carrying values until the beginning of the string
12+
"Zz",
13+
"zZ",
14+
"9z",
15+
"9Z",
16+
// string interpretable as a number in scientific notation
17+
"5e6",
18+
// Interned strings
19+
"d",
20+
"D",
21+
"4",
22+
];
23+
24+
foreach ($strictlyAlphaNumeric as $s) {
25+
var_dump(str_increment($s));
26+
var_dump($s);
27+
}
28+
29+
?>
30+
--EXPECT--
31+
string(2) "Ba"
32+
string(2) "Az"
33+
string(2) "bA"
34+
string(2) "aZ"
35+
string(2) "B0"
36+
string(2) "A9"
37+
string(2) "b0"
38+
string(2) "a9"
39+
string(3) "AAa"
40+
string(2) "Zz"
41+
string(3) "aaA"
42+
string(2) "zZ"
43+
string(3) "10a"
44+
string(2) "9z"
45+
string(3) "10A"
46+
string(2) "9Z"
47+
string(3) "5e7"
48+
string(3) "5e6"
49+
string(1) "e"
50+
string(1) "d"
51+
string(1) "E"
52+
string(1) "D"
53+
string(1) "5"
54+
string(1) "4"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
str_increment(): Invalid strings to increment should throw a ValueError
3+
--FILE--
4+
<?php
5+
6+
$strings = [
7+
// Empty string
8+
"",
9+
// String increments are unaware of being "negative"
10+
"-cc",
11+
// Trailing whitespace
12+
"Z ",
13+
// Leading whitespace
14+
" Z",
15+
// Non-ASCII characters
16+
"é",
17+
"あいうえお",
18+
"α",
19+
"ω",
20+
"Α", // Capital alpha
21+
"Ω",
22+
// With period
23+
"foo1.txt",
24+
"1f.5",
25+
// With multiple period
26+
"foo.1.txt",
27+
"1.f.5",
28+
];
29+
30+
foreach ($strings as $s) {
31+
try {
32+
var_dump(str_increment($s));
33+
} catch (ValueError $e) {
34+
echo $e->getMessage(), PHP_EOL;
35+
}
36+
}
37+
38+
?>
39+
--EXPECT--
40+
str_increment(): Argument #1 ($string) cannot be empty
41+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
42+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
43+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
44+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
45+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
46+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
47+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
48+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
49+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
50+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
51+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
52+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
53+
str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters

0 commit comments

Comments
 (0)