Skip to content

Commit 379fe18

Browse files
committed
Add str_decrement() function
1 parent 030c547 commit 379fe18

File tree

6 files changed

+200
-0
lines changed

6 files changed

+200
-0
lines changed

ext/standard/basic_functions.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,6 +2344,8 @@ function strtolower(string $string): string {}
23442344

23452345
function str_increment(string $string): string {}
23462346

2347+
function str_decrement(string $string): string {}
2348+
23472349
/** @refcount 1 */
23482350
function basename(string $path, string $suffix = ""): string {}
23492351

ext/standard/basic_functions_arginfo.h

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/string.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,51 @@ PHP_FUNCTION(str_increment)
12461246
RETURN_STR(incremented);
12471247
}
12481248

1249+
1250+
PHP_FUNCTION(str_decrement)
1251+
{
1252+
zend_string *str;
1253+
1254+
ZEND_PARSE_PARAMETERS_START(1, 1)
1255+
Z_PARAM_STR(str)
1256+
ZEND_PARSE_PARAMETERS_END();
1257+
1258+
if (ZSTR_LEN(str) == 0) {
1259+
zend_argument_value_error(1, "cannot be empty");
1260+
RETURN_THROWS();
1261+
}
1262+
if (!zend_string_only_has_ascii_alphanumeric(str)) {
1263+
zend_argument_value_error(1, "must be composed only of alphanumeric ASCII characters");
1264+
RETURN_THROWS();
1265+
}
1266+
1267+
zend_string *decremented = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), /* persistant */ false);
1268+
size_t position = ZSTR_LEN(str)-1;
1269+
bool carry = false;
1270+
1271+
do {
1272+
char c = ZSTR_VAL(decremented)[position];
1273+
if (EXPECTED( (c > 'a' && c <= 'z') || (c > 'A' && c <= 'Z') || (c > '0' && c <= '9') )) {
1274+
carry = false;
1275+
ZSTR_VAL(decremented)[position]--;
1276+
} else { /* if 'a', 'A', or '0' */
1277+
carry = true;
1278+
if (c == '0') {
1279+
ZSTR_VAL(decremented)[position] = '9';
1280+
} else {
1281+
ZSTR_VAL(decremented)[position] += 25;
1282+
}
1283+
}
1284+
} while (carry && position-- > 0);
1285+
1286+
if (UNEXPECTED(carry)) {
1287+
zend_string_release_ex(decremented, /* persistant */ false);
1288+
zend_argument_value_error(1, "\"%s\" is out of decrement range", ZSTR_VAL(str));
1289+
RETURN_THROWS();
1290+
}
1291+
RETURN_STR(decremented);
1292+
}
1293+
12491294
#if defined(PHP_WIN32)
12501295
static bool _is_basename_start(const char *start, const char *pos)
12511296
{
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
str_decrement(): Decrementing 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 (no underflow)
12+
"Za",
13+
"zA",
14+
"Z0",
15+
"z0",
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_decrement($s));
26+
var_dump($s);
27+
}
28+
29+
?>
30+
--EXPECT--
31+
string(2) "Ay"
32+
string(2) "Az"
33+
string(2) "aY"
34+
string(2) "aZ"
35+
string(2) "A8"
36+
string(2) "A9"
37+
string(2) "a8"
38+
string(2) "a9"
39+
string(2) "Yz"
40+
string(2) "Za"
41+
string(2) "yZ"
42+
string(2) "zA"
43+
string(2) "Y9"
44+
string(2) "Z0"
45+
string(2) "y9"
46+
string(2) "z0"
47+
string(3) "5e5"
48+
string(3) "5e6"
49+
string(1) "c"
50+
string(1) "d"
51+
string(1) "C"
52+
string(1) "D"
53+
string(1) "3"
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_decrement(): Invalid strings to decrement 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_decrement($s));
33+
} catch (ValueError $e) {
34+
echo $e->getMessage(), PHP_EOL;
35+
}
36+
}
37+
38+
?>
39+
--EXPECT--
40+
str_decrement(): Argument #1 ($string) cannot be empty
41+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
42+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
43+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
44+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
45+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
46+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
47+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
48+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
49+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
50+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
51+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
52+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
53+
str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
str_decrement(): Out of Range ValueErrors for strings that cannot be decremented
3+
--FILE--
4+
<?php
5+
6+
$strings = [
7+
"0",
8+
"a",
9+
"A",
10+
"00",
11+
"Aa",
12+
"aA",
13+
"A0",
14+
"a0",
15+
"0a",
16+
"0A",
17+
];
18+
19+
foreach ($strings as $s) {
20+
try {
21+
var_dump(str_decrement($s));
22+
} catch (ValueError $e) {
23+
echo $e->getMessage(), PHP_EOL;
24+
}
25+
}
26+
27+
?>
28+
--EXPECT--
29+
str_decrement(): Argument #1 ($string) "0" is out of decrement range
30+
str_decrement(): Argument #1 ($string) "a" is out of decrement range
31+
str_decrement(): Argument #1 ($string) "A" is out of decrement range
32+
str_decrement(): Argument #1 ($string) "00" is out of decrement range
33+
str_decrement(): Argument #1 ($string) "Aa" is out of decrement range
34+
str_decrement(): Argument #1 ($string) "aA" is out of decrement range
35+
str_decrement(): Argument #1 ($string) "A0" is out of decrement range
36+
str_decrement(): Argument #1 ($string) "a0" is out of decrement range
37+
str_decrement(): Argument #1 ($string) "0a" is out of decrement range
38+
str_decrement(): Argument #1 ($string) "0A" is out of decrement range

0 commit comments

Comments
 (0)