Samples::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace ICanBoogie\CLDR\Supplemental\Plurals;
4
5
use ICanBoogie\CLDR\Numbers\Number;
6
use IteratorAggregate;
7
use Traversable;
8
9
use function array_merge;
10
use function array_slice;
11
use function array_values;
12
use function explode;
13
use function is_array;
14
use function str_repeat;
15
use function trim;
16
17
/**
18
 * Representation of plural samples.
19
 *
20
 * @internal
21
 *
22
 * @implements IteratorAggregate<string>
23
 *
24
 * @link https://unicode.org/reports/tr35/tr35-numbers.html#Samples
25
 */
26
final class Samples implements IteratorAggregate
27
{
28
    /**
29
     * @private
30
     */
31
    public const INFINITY = '…';
32
33
    /**
34
     * @private
35
     */
36
    public const SAMPLE_RANGE_SEPARATOR = '~';
37
38
    public static function from(string $samples): Samples
39
    {
40
        return SamplesCache::get(
41
            $samples,
42
            static fn(): Samples => new self(self::parse_rules($samples))
43
        );
44
    }
45
46
    /**
47
     * @return array<string|string[]>
48
     */
49
    private static function parse_rules(string $sample_string): array
50
    {
51
        $samples = [];
52
        $type_and_samples_string_list = array_slice(explode('@', $sample_string), 1);
53
54
        foreach ($type_and_samples_string_list as $type_and_samples_string) {
55
            [ $type, $samples_string ] = explode(' ', trim($type_and_samples_string), 2);
56
57
            $samples[$type] = self::parse_samples($samples_string);
58
        }
59
60
        return array_merge(...array_values($samples));
61
    }
62
63
    /**
64
     * Parse a samples string.
65
     *
66
     *
67
     * @return array<string|string[]>
68
     */
69
    private static function parse_samples(string $samples_string): array
70
    {
71
        $samples = [];
72
73
        foreach (explode(', ', $samples_string) as $sample) {
74
            if ($sample === self::INFINITY) {
75
                continue;
76
            }
77
78
            if (!str_contains($sample, self::SAMPLE_RANGE_SEPARATOR)) {
79
                $samples[] = $sample;
80
81
                continue;
82
            }
83
84
            [ $start, $end ] = explode(self::SAMPLE_RANGE_SEPARATOR, $sample);
85
86
            $samples[] = [ $start, $end ];
87
        }
88
89
        return $samples;
90
    }
91
92
    /**
93
     * @param float|int|numeric-string $number
0 ignored issues
show
Documentation Bug introduced by
The doc comment float|int|numeric-string at position 4 could not be parsed: Unknown type name 'numeric-string' at position 4 in float|int|numeric-string.
Loading history...
94
     */
95
    private static function precision_from(float|int|string $number): int
96
    {
97
        return Number::precision_from($number);
98
    }
99
100
    /**
101
     * @param array<string|string[]> $samples
102
     */
103
    private function __construct(
104
        private readonly array $samples
105
    ) {
106
    }
107
108
    /**
109
     * Note: The iterator yields numeric strings to avoid '0.30000000000000004' when '0.3' is correct,
110
     * and to avoid removing trailing zeros e.g. '1.0' or '1.00'.
111
     */
112
    public function getIterator(): Traversable
113
    {
114
        foreach ($this->samples as $sample) {
115
            if (!is_array($sample)) {
116
                yield $sample;
117
118
                continue;
119
            }
120
121
            /**
122
             * @var numeric-string $start
123
             * @var numeric-string $end
124
             */
125
126
            [ $start, $end ] = $sample;
127
128
            $precision = self::precision_from($start) ?: self::precision_from($end);
129
            $step = 1 / (int)('1' . str_repeat('0', $precision));
130
            $start += 0;
131
            $end += 0;
132
133
            // we use a for/times, so we don't lose quantities, compared to a $start += $step
134
            $times = ($end - $start) / $step;
135
136
            for ($i = 0; $i < $times + 1; $i++) {
137
                yield (string)($start + $step * $i);
138
            }
139
        }
140
    }
141
}
142