Skip to content

Commit 030c547

Browse files
committed
Add str_increment() function
1 parent 273b887 commit 030c547

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
@@ -2493,6 +2493,19 @@ ZEND_API bool ZEND_FASTCALL instanceof_function_slow(const zend_class_entry *ins
24932493
#define UPPER_CASE 2
24942494
#define NUMERIC 3
24952495

2496+
ZEND_API bool zend_string_only_has_ascii_alphanumeric(const zend_string *str)
2497+
{
2498+
const char *p = ZSTR_VAL(str);
2499+
const char *e = ZSTR_VAL(str) + ZSTR_LEN(str);
2500+
while (p < e) {
2501+
char c = *p++;
2502+
if (UNEXPECTED( c < '0' || c > 'z' || (c < 'a' && c > 'Z') || (c < 'A' && c > '9') ) ) {
2503+
return false;
2504+
}
2505+
}
2506+
return true;
2507+
}
2508+
24962509
static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */
24972510
{
24982511
int carry=0;
@@ -2512,18 +2525,10 @@ static void ZEND_FASTCALL increment_string(zval *str) /* {{{ */
25122525
return;
25132526
}
25142527

2515-
{
2516-
const char *p = Z_STRVAL_P(str);
2517-
const char *e = Z_STRVAL_P(str) + Z_STRLEN_P(str);
2518-
while (p < e) {
2519-
char c = *p++;
2520-
if (UNEXPECTED( c < '0' || c > 'z' || (c < 'a' && c > 'Z') || (c < 'A' && c > '9') ) ) {
2521-
zend_error(E_DEPRECATED, "Increment on non-alphanumeric string is deprecated");
2522-
if (EG(exception)) {
2523-
return;
2524-
}
2525-
break;
2526-
}
2528+
if (UNEXPECTED(!zend_string_only_has_ascii_alphanumeric(Z_STR_P(str)))) {
2529+
zend_error(E_DEPRECATED, "Increment on non-alphanumeric string is deprecated");
2530+
if (EG(exception)) {
2531+
return;
25272532
}
25282533
}
25292534

Zend/zend_operators.h

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

75+
ZEND_API bool zend_string_only_has_ascii_alphanumeric(const zend_string *str);
76+
7577
/**
7678
* Checks whether the string "str" with length "length" is numeric. The value
7779
* 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
@@ -2342,6 +2342,8 @@ function strtoupper(string $string): string {}
23422342
/** @compile-time-eval */
23432343
function strtolower(string $string): string {}
23442344

2345+
function str_increment(string $string): string {}
2346+
23452347
/** @refcount 1 */
23462348
function basename(string $path, string $suffix = ""): string {}
23472349

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
@@ -1192,6 +1192,60 @@ PHP_FUNCTION(strtolower)
11921192
}
11931193
/* }}} */
11941194

1195+
PHP_FUNCTION(str_increment)
1196+
{
1197+
zend_string *str;
1198+
1199+
ZEND_PARSE_PARAMETERS_START(1, 1)
1200+
Z_PARAM_STR(str)
1201+
ZEND_PARSE_PARAMETERS_END();
1202+
1203+
if (ZSTR_LEN(str) == 0) {
1204+
zend_argument_value_error(1, "cannot be empty");
1205+
RETURN_THROWS();
1206+
}
1207+
if (!zend_string_only_has_ascii_alphanumeric(str)) {
1208+
zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters");
1209+
RETURN_THROWS();
1210+
}
1211+
1212+
zend_string *incremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistant */ false);
1213+
size_t position = ZSTR_LEN(str)-1;
1214+
bool carry = false;
1215+
1216+
do {
1217+
char c = ZSTR_VAL(incremented)[position];
1218+
if (EXPECTED( (c >= 'a' && c < 'z') || (c >= 'A' && c < 'Z') || (c >= '0' && c < '9') )) {
1219+
carry = false;
1220+
ZSTR_VAL(incremented)[position]++;
1221+
} else { /* if 'z', 'Z', or '9' */
1222+
carry = true;
1223+
if (c == '9') {
1224+
ZSTR_VAL(incremented)[position] = '0';
1225+
} else {
1226+
ZSTR_VAL(incremented)[position] -= 25;
1227+
}
1228+
}
1229+
} while (carry && position-- > 0);
1230+
1231+
if (UNEXPECTED(carry)) {
1232+
zend_string *tmp = zend_string_alloc(ZSTR_LEN(incremented)+1, 0);
1233+
memcpy(ZSTR_VAL(tmp) + 1, ZSTR_VAL(incremented), ZSTR_LEN(incremented));
1234+
ZSTR_VAL(tmp)[ZSTR_LEN(incremented)+1] = '\0';
1235+
switch (ZSTR_VAL(incremented)[0]) {
1236+
case '0':
1237+
ZSTR_VAL(tmp)[0] = '1';
1238+
break;
1239+
default:
1240+
ZSTR_VAL(tmp)[0] = ZSTR_VAL(incremented)[0];
1241+
break;
1242+
}
1243+
zend_string_release_ex(incremented, /* persistant */ false);
1244+
RETURN_STR(tmp);
1245+
}
1246+
RETURN_STR(incremented);
1247+
}
1248+
11951249
#if defined(PHP_WIN32)
11961250
static bool _is_basename_start(const char *start, const char *pos)
11971251
{
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)