ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
ILIAS\Language\ComponentTranslation\PluralForms Class Reference

The plural rule of a language, as declared by the "Plural-Forms" header of a gettext catalog (nplurals=<count>; plural=<C expression of n>;), and the one place deciding which plural form a message serves. More...

+ Collaboration diagram for ILIAS\Language\ComponentTranslation\PluralForms:

Public Member Functions

 getCount ()
 
 getHeader ()
 
 formIndexFor (int $n)
 The index of the form for the quantity $n. More...
 
 defaultFormIndex ()
 The form serving a plural message where only one value can be used: the last one, msgstr[nplurals - 1]. More...
 
 defaultValueOf (array $forms)
 The value of the default form (see defaultFormIndex()) of a message with $forms (msgstr[0], ...); if that is missing or empty the closest non-empty form before it, '' if all are empty. More...
 
 selectsSameFormsAs (PluralForms $other)
 Whether $other has as many forms and selects the same form for every sampled n, e.g. More...
 
 previousFormsOf (PluralForms $previous)
 For every form of this rule, the forms of $previous serving the same quantities: $previous->formIndexFor(n) for every sampled n this rule serves with that form. More...
 
 isOneSingularOtherPlural ()
 Whether the rule is "n == 1 -> form 0, every other n -> form 1" (English, German) for every sampled n. More...
 

Static Public Member Functions

static fromHeader (string $header)
 
static fromHeaderOrGermanic (?string $header, ?callable $on_invalid=null)
 fromHeader(), or the Germanic rule (GERMANIC_HEADER) if $header is missing or invalid - reported through $on_invalid then. More...
 
static germanic ()
 
static defaultFormIndexForCount (int $count)
 
static defaultValueForCount (array $forms, int $count)
 

Data Fields

const string GERMANIC_HEADER = 'nplurals=2; plural=(n != 1);'
 The fallback for a missing or invalid header: the rule of the Germanic languages. More...
 

Private Member Functions

 __construct (private readonly int $count, private readonly string $formula, private readonly array $tree)
 
 evaluate (int $n)
 

Static Private Member Functions

static evaluateNode (array $node, int $n)
 
static parse (string $formula)
 
static tokenize (string $formula)
 
static parseTernary (array $tokens, int &$position, int $depth)
 ternary := or ( "?" ternary ":" ternary )? More...
 
static parseBinary (array $tokens, int &$position, int $depth, int $level)
 
static parseUnary (array $tokens, int &$position, int $depth)
 unary := "!" unary | "n" | integer | "(" ternary ")" More...
 
static expect (array $tokens, int &$position, string $token)
 
static assertDepth (int $depth)
 

Private Attributes

const int MAX_COUNT = 10
 
const int MAX_FORMULA_LENGTH = 512
 
const int MAX_DEPTH = 64
 
const int MAX_INTEGER_DIGITS = 9
 
const array EXTRA_SAMPLES = [1000, 1001, 1011, 100000, 1000000, 1000001, 2000000]
 
const int SAMPLED_UP_TO = 200
 
const array BINARY_LEVELS
 The binary operators by precedence, lowest first (C rules; all left-associative). More...
 

Static Private Attributes

static array $parsed = []
 

Detailed Description

The plural rule of a language, as declared by the "Plural-Forms" header of a gettext catalog (nplurals=<count>; plural=<C expression of n>;), and the one place deciding which plural form a message serves.

The expression is parsed by a small recursive descent parser for the subset gettext/CLDR formulas use (n, non-negative integers, ?:, ||, &&, !, ==, !=, <, <=, >, >=, %, parentheses) into a tree evaluated without eval(): a header comes from a file (shipped .po, overlay, plugin) and is untrusted. Anything else (other operators, very long or deeply nested formulas, an index outside 0..count-1 for a sampled n) makes the header invalid.

For callers that need one value of a plural message (txt(), the database fallback, a plugin's database write) the default form is the last one, msgstr[nplurals - 1] - CLDR's "other", the most general category (msgstr[1] is e.g. the 2-4 form in cs/pl/ru, the dual in sl). If it is empty, the closest non-empty form before it is used, see defaultFormIndex() and defaultValueOf().

Definition at line 41 of file PluralForms.php.

Constructor & Destructor Documentation

◆ __construct()

ILIAS\Language\ComponentTranslation\PluralForms::__construct ( private readonly int  $count,
private readonly string  $formula,
private readonly array  $tree 
)
private
Parameters
array<int,mixed>$tree

Definition at line 79 of file PluralForms.php.

83 {
84 }

Member Function Documentation

◆ assertDepth()

static ILIAS\Language\ComponentTranslation\PluralForms::assertDepth ( int  $depth)
staticprivate

Definition at line 450 of file PluralForms.php.

450 : void
451 {
452 if ($depth > self::MAX_DEPTH) {
453 throw new InvalidArgumentException('The plural expression is nested too deeply.');
454 }
455 }

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\parseTernary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseUnary().

+ Here is the caller graph for this function:

◆ defaultFormIndex()

ILIAS\Language\ComponentTranslation\PluralForms::defaultFormIndex ( )

The form serving a plural message where only one value can be used: the last one, msgstr[nplurals - 1].

Definition at line 184 of file PluralForms.php.

184 : int
185 {
186 return self::defaultFormIndexForCount($this->count);
187 }

References ILIAS\Language\ComponentTranslation\PluralForms\defaultFormIndexForCount().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\formIndexFor(), and ILIAS\Language\ComponentTranslation\MigratedLanguageFileSync\mapPluralForms().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ defaultFormIndexForCount()

static ILIAS\Language\ComponentTranslation\PluralForms::defaultFormIndexForCount ( int  $count)
static

Definition at line 189 of file PluralForms.php.

189 : int
190 {
191 return max(0, $count - 1);
192 }

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\defaultFormIndex(), and ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultFormIndexForCountIsTheLastForm().

+ Here is the caller graph for this function:

◆ defaultValueForCount()

static ILIAS\Language\ComponentTranslation\PluralForms::defaultValueForCount ( array  $forms,
int  $count 
)
static
Parameters
array<int,string>$forms

Definition at line 206 of file PluralForms.php.

206 : string
207 {
208 for ($form = self::defaultFormIndexForCount($count); $form >= 0; $form--) {
209 $value = (string) ($forms[$form] ?? '');
210 if ($value !== '') {
211 return $value;
212 }
213 }
214
215 return '';
216 }

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\defaultValueOf().

+ Here is the caller graph for this function:

◆ defaultValueOf()

ILIAS\Language\ComponentTranslation\PluralForms::defaultValueOf ( array  $forms)

The value of the default form (see defaultFormIndex()) of a message with $forms (msgstr[0], ...); if that is missing or empty the closest non-empty form before it, '' if all are empty.

Parameters
array<int,string>$forms

Definition at line 200 of file PluralForms.php.

200 : string
201 {
202 return self::defaultValueForCount($forms, $this->count);
203 }
static defaultValueForCount(array $forms, int $count)

References ILIAS\Language\ComponentTranslation\PluralForms\defaultValueForCount().

+ Here is the call graph for this function:

◆ evaluate()

ILIAS\Language\ComponentTranslation\PluralForms::evaluate ( int  $n)
private

Definition at line 274 of file PluralForms.php.

274 : ?int
275 {
276 try {
277 return self::evaluateNode($this->tree, $n);
278 } catch (\ArithmeticError) {
279 return null;
280 }
281 }

References ILIAS\Language\ComponentTranslation\PluralForms\evaluateNode().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\formIndexFor().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ evaluateNode()

static ILIAS\Language\ComponentTranslation\PluralForms::evaluateNode ( array  $node,
int  $n 
)
staticprivate
Parameters
array<int,mixed>$node

Definition at line 286 of file PluralForms.php.

286 : int
287 {
288 switch ($node[0]) {
289 case 'n':
290 return $n;
291 case 'int':
292 return $node[1];
293 case '!':
294 return self::evaluateNode($node[1], $n) === 0 ? 1 : 0;
295 case '?':
296 return self::evaluateNode($node[1], $n) !== 0
297 ? self::evaluateNode($node[2], $n)
298 : self::evaluateNode($node[3], $n);
299 case '||':
300 return self::evaluateNode($node[1], $n) !== 0 || self::evaluateNode($node[2], $n) !== 0 ? 1 : 0;
301 case '&&':
302 return self::evaluateNode($node[1], $n) !== 0 && self::evaluateNode($node[2], $n) !== 0 ? 1 : 0;
303 }
304
305 $left = self::evaluateNode($node[1], $n);
306 $right = self::evaluateNode($node[2], $n);
307
308 return match ($node[0]) {
309 '==' => (int) ($left === $right),
310 '!=' => (int) ($left !== $right),
311 '<' => (int) ($left < $right),
312 '<=' => (int) ($left <= $right),
313 '>' => (int) ($left > $right),
314 '>=' => (int) ($left >= $right),
315 // intdiv()'s sibling: throws DivisionByZeroError (an ArithmeticError) for 0
316 '%' => $left % $right,
317 // parse() creates no other node
318 default => throw new \LogicException(sprintf('Unknown node "%s" of a plural expression.', (string) $node[0])),
319 };
320 }

References ILIAS\Language\ComponentTranslation\PluralForms\evaluateNode(), and ILIAS\Repository\int().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\evaluate(), and ILIAS\Language\ComponentTranslation\PluralForms\evaluateNode().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ expect()

static ILIAS\Language\ComponentTranslation\PluralForms::expect ( array  $tokens,
int &  $position,
string  $token 
)
staticprivate
Parameters
list<string>$tokens

Definition at line 442 of file PluralForms.php.

442 : void
443 {
444 if (($tokens[$position] ?? null) !== $token) {
445 throw new InvalidArgumentException(sprintf('"%s" expected in the plural expression.', $token));
446 }
447 $position++;
448 }
$token
Definition: xapitoken.php:69

References $token.

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\parseTernary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseUnary().

+ Here is the caller graph for this function:

◆ formIndexFor()

ILIAS\Language\ComponentTranslation\PluralForms::formIndexFor ( int  $n)

The index of the form for the quantity $n.

A negative $n counts as its absolute value (gettext takes an unsigned number). If the expression fails for $n (e.g. a modulo by zero), the default form.

Definition at line 172 of file PluralForms.php.

172 : int
173 {
174 $n = $n === PHP_INT_MIN ? PHP_INT_MAX : abs($n);
175 $index = $this->evaluate($n);
176
177 return $index !== null && $index >= 0 && $index < $this->count ? $index : $this->defaultFormIndex();
178 }
defaultFormIndex()
The form serving a plural message where only one value can be used: the last one, msgstr[nplurals - 1...

References ILIAS\Language\ComponentTranslation\PluralForms\defaultFormIndex(), and ILIAS\Language\ComponentTranslation\PluralForms\evaluate().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\isOneSingularOtherPlural(), ILIAS\Language\ComponentTranslation\PluralForms\previousFormsOf(), and ILIAS\Language\ComponentTranslation\PluralForms\selectsSameFormsAs().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fromHeader()

array< string, self > parsed headers of this request see ILIAS\Language\ComponentTranslation\PluralForms::fromHeader ( string  $header)
static
Exceptions
InvalidArgumentExceptionif $header is no valid "Plural-Forms" value, see the class docblock

Definition at line 90 of file PluralForms.php.

90 : self
91 {
92 if (isset(self::$parsed[$header])) {
93 return self::$parsed[$header];
94 }
95 // limited before any regular expression runs on it; possessive quantifiers, no backtracking
96 if (strlen($header) > self::MAX_FORMULA_LENGTH + 64) {
97 throw new InvalidArgumentException('The Plural-Forms header is too long.');
98 }
99 if (preg_match('/\A\s*+nplurals\s*+=\s*+(\d{1,2}+)\s*+;\s*+plural\s*+=([^;]++);?\s*+\z/', $header, $matches) !== 1) {
100 throw new InvalidArgumentException('The Plural-Forms header does not have the form "nplurals=<count>; plural=<expression>;".');
101 }
102 $count = (int) $matches[1];
103 $formula = trim($matches[2]);
104 if ($count < 1 || $count > self::MAX_COUNT) {
105 throw new InvalidArgumentException(sprintf('nplurals must be between 1 and %d.', self::MAX_COUNT));
106 }
107 if (strlen($formula) > self::MAX_FORMULA_LENGTH) {
108 throw new InvalidArgumentException('The plural expression is too long.');
109 }
110
111 $forms = new self($count, $formula, self::parse($formula));
112 foreach ([...range(0, self::SAMPLED_UP_TO), ...self::EXTRA_SAMPLES] as $n) {
113 $index = $forms->evaluate($n);
114 if ($index === null || $index < 0 || $index >= $count) {
115 throw new InvalidArgumentException(sprintf(
116 'The plural expression yields no valid form for n = %d (nplurals=%d).',
117 $n,
118 $count
119 ));
120 }
121 }
122 if (count(self::$parsed) >= 64) {
123 self::$parsed = [];
124 }
125
126 return self::$parsed[$header] = $forms;
127 }

References ILIAS\Repository\int(), and ILIAS\Language\ComponentTranslation\PluralForms\parse().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\fromHeaderOrGermanic(), ILIAS\Language\ComponentTranslation\PluralForms\germanic(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testAnOverlongHeaderIsRejectedBeforeTheRegularExpressionRunsOnIt(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testBinaryOperatorsAreLeftAssociative(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfFallsBackDownwardsWhenTheLastFormIsEmpty(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfFallsBackDownwardsWhenTheLastFormIsMissingEntirely(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfIsEmptyWhenEveryFormIsEmpty(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfPrefersTheLastFormWhenItIsNotEmpty(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfPrefersTheLastOfThreeForms(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfSkipsAnEmptyFormInTheMiddleWhileSearchingDownwards(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfUsesFormZeroForASingleFormLanguage(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testDefaultValueOfWithNoFormsAtAllIsEmpty(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFormIndexForFallsBackToTheDefaultFormWhenTheFormulaDividesByZeroForThisN(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFormIndexForFallsBackToTheLastFormOnEvaluationFailureForAMultiFormLanguage(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFormIndexForHandlesPhpIntMinWithoutOverflowing(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFormIndexForMatchesTheKnownRuleOfTheLanguage(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFormIndexForTreatsANegativeNAsItsAbsoluteValueLikeGettext(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderAcceptsMaximumNplurals(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderThrowsForAnInvalidHeader(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testGetHeaderRoundTripsTheParsedHeader(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testIsOneSingularOtherPluralIsFalseForAnyNonTwoFormCount(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testIsOneSingularOtherPluralIsFalseForFrenchWhichTreatsZeroAsSingular(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testModuloBindsTighterThanComparisonsAndComparisonsTighterThanLogicalAnd(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testPreviousFormsOfListsThePreviousFormsServingTheSameQuantities(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testSelectsSameFormsAsComparesTheSelectedFormsNotTheSpelling(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsShippedRulesTest\testShippedHeaderSelectsTheCldrCategoryForEveryInteger(), and ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testTernaryIsRightAssociativeAndBindsLooserThanComparisons().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fromHeaderOrGermanic()

static ILIAS\Language\ComponentTranslation\PluralForms::fromHeaderOrGermanic ( ?string  $header,
?callable  $on_invalid = null 
)
static

fromHeader(), or the Germanic rule (GERMANIC_HEADER) if $header is missing or invalid - reported through $on_invalid then.

Parameters
(callable(string$message): void)|null $on_invalid

Definition at line 135 of file PluralForms.php.

135 : self
136 {
137 if ($header !== null && trim($header) !== '') {
138 try {
139 return self::fromHeader($header);
140 } catch (InvalidArgumentException $e) {
141 $message = sprintf('Invalid Plural-Forms header "%s" (%s) - using "%s".', $header, $e->getMessage(), self::GERMANIC_HEADER);
142 }
143 } else {
144 $message = sprintf('Missing Plural-Forms header - using "%s".', self::GERMANIC_HEADER);
145 }
146 if ($on_invalid !== null) {
147 $on_invalid(PlainLogText::of($message));
148 }
149
150 return self::germanic();
151 }

References Vendor\Package\$e, $message, ILIAS\Language\ComponentTranslation\PluralForms\fromHeader(), ILIAS\Language\ComponentTranslation\PluralForms\germanic(), and ILIAS\Language\ComponentTranslation\PlainLogText\of().

Referenced by ILIAS\Language\ComponentTranslation\Catalog\TranslationCatalog\prepareMoPluralForms(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderOrGermanicFallsBackAndReportsForABlankHeader(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderOrGermanicFallsBackAndReportsForAnInvalidHeader(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderOrGermanicFallsBackAndReportsForANullHeader(), ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderOrGermanicReturnsTheParsedRuleForAValidHeader(), and ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testFromHeaderOrGermanicWorksWithoutACallback().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ germanic()

static ILIAS\Language\ComponentTranslation\PluralForms::germanic ( )
static

◆ getCount()

ILIAS\Language\ComponentTranslation\PluralForms::getCount ( )

Definition at line 158 of file PluralForms.php.

158 : int
159 {
160 return $this->count;
161 }

Referenced by ILIAS\Language\ComponentTranslation\MigratedLanguageFileSync\formsOf().

+ Here is the caller graph for this function:

◆ getHeader()

ILIAS\Language\ComponentTranslation\PluralForms::getHeader ( )

Definition at line 163 of file PluralForms.php.

163 : string
164 {
165 return sprintf('nplurals=%d; plural=%s;', $this->count, $this->formula);
166 }

◆ isOneSingularOtherPlural()

ILIAS\Language\ComponentTranslation\PluralForms::isOneSingularOtherPlural ( )

Whether the rule is "n == 1 -> form 0, every other n -> form 1" (English, German) for every sampled n.

Definition at line 260 of file PluralForms.php.

260 : bool
261 {
262 if ($this->count !== 2) {
263 return false;
264 }
265 foreach ([...range(0, self::SAMPLED_UP_TO), ...self::EXTRA_SAMPLES] as $n) {
266 if ($this->formIndexFor($n) !== ($n === 1 ? 0 : 1)) {
267 return false;
268 }
269 }
270
271 return true;
272 }
formIndexFor(int $n)
The index of the form for the quantity $n.

References ILIAS\Language\ComponentTranslation\PluralForms\formIndexFor().

+ Here is the call graph for this function:

◆ parse()

static ILIAS\Language\ComponentTranslation\PluralForms::parse ( string  $formula)
staticprivate
Returns
array<int, mixed> the tree of $formula
Exceptions
InvalidArgumentException

Definition at line 326 of file PluralForms.php.

326 : array
327 {
328 $tokens = self::tokenize($formula);
329 $position = 0;
330 $tree = self::parseTernary($tokens, $position, 0);
331 if ($position !== count($tokens)) {
332 throw new InvalidArgumentException(sprintf('Unexpected "%s" in the plural expression.', $tokens[$position]));
333 }
334
335 return $tree;
336 }
static parseTernary(array $tokens, int &$position, int $depth)
ternary := or ( "?" ternary ":" ternary )?

References ILIAS\Language\ComponentTranslation\PluralForms\parseTernary(), and ILIAS\Language\ComponentTranslation\PluralForms\tokenize().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\fromHeader().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseBinary()

static ILIAS\Language\ComponentTranslation\PluralForms::parseBinary ( array  $tokens,
int &  $position,
int  $depth,
int  $level 
)
staticprivate
Parameters
list<string>$tokens
Returns
array<int, mixed>

Definition at line 392 of file PluralForms.php.

392 : array
393 {
394 // the levels are a fixed, small number - only parentheses, "!" and "?:" nest (see assertDepth())
395 if ($level === count(self::BINARY_LEVELS)) {
396 return self::parseUnary($tokens, $position, $depth);
397 }
398 $left = self::parseBinary($tokens, $position, $depth, $level + 1);
399 while (in_array($tokens[$position] ?? null, self::BINARY_LEVELS[$level], true)) {
400 $operator = $tokens[$position++];
401 $left = [$operator, $left, self::parseBinary($tokens, $position, $depth, $level + 1)];
402 }
403
404 return $left;
405 }
static parseBinary(array $tokens, int &$position, int $depth, int $level)
static parseUnary(array $tokens, int &$position, int $depth)
unary := "!" unary | "n" | integer | "(" ternary ")"

References ILIAS\Language\ComponentTranslation\PluralForms\parseBinary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseUnary().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\parseBinary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseTernary().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseTernary()

static ILIAS\Language\ComponentTranslation\PluralForms::parseTernary ( array  $tokens,
int &  $position,
int  $depth 
)
staticprivate

ternary := or ( "?" ternary ":" ternary )?

Parameters
list<string>$tokens
Returns
array<int, mixed>

Definition at line 373 of file PluralForms.php.

373 : array
374 {
375 self::assertDepth($depth);
376 $condition = self::parseBinary($tokens, $position, $depth, 0);
377 if (($tokens[$position] ?? null) !== '?') {
378 return $condition;
379 }
380 $position++;
381 $then = self::parseTernary($tokens, $position, $depth + 1);
382 self::expect($tokens, $position, ':');
383 $else = self::parseTernary($tokens, $position, $depth + 1);
384
385 return ['?', $condition, $then, $else];
386 }
static expect(array $tokens, int &$position, string $token)

References ILIAS\Language\ComponentTranslation\PluralForms\assertDepth(), ILIAS\Language\ComponentTranslation\PluralForms\expect(), ILIAS\Language\ComponentTranslation\PluralForms\parseBinary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseTernary().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\parse(), ILIAS\Language\ComponentTranslation\PluralForms\parseTernary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseUnary().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseUnary()

static ILIAS\Language\ComponentTranslation\PluralForms::parseUnary ( array  $tokens,
int &  $position,
int  $depth 
)
staticprivate

unary := "!" unary | "n" | integer | "(" ternary ")"

Parameters
list<string>$tokens
Returns
array<int, mixed>

Definition at line 413 of file PluralForms.php.

413 : array
414 {
415 self::assertDepth($depth);
416 $token = $tokens[$position] ?? null;
417 if ($token === null) {
418 throw new InvalidArgumentException('The plural expression ends unexpectedly.');
419 }
420 $position++;
421 if ($token === '!') {
422 return ['!', self::parseUnary($tokens, $position, $depth + 1)];
423 }
424 if ($token === 'n') {
425 return ['n'];
426 }
427 if (ctype_digit($token)) {
428 return ['int', (int) $token];
429 }
430 if ($token === '(') {
431 $inner = self::parseTernary($tokens, $position, $depth + 1);
432 self::expect($tokens, $position, ')');
433 return $inner;
434 }
435
436 throw new InvalidArgumentException(sprintf('Unexpected "%s" in the plural expression.', $token));
437 }

References $token, ILIAS\Language\ComponentTranslation\PluralForms\assertDepth(), ILIAS\Language\ComponentTranslation\PluralForms\expect(), ILIAS\Repository\int(), ILIAS\Language\ComponentTranslation\PluralForms\parseTernary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseUnary().

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\parseBinary(), and ILIAS\Language\ComponentTranslation\PluralForms\parseUnary().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ previousFormsOf()

ILIAS\Language\ComponentTranslation\PluralForms::previousFormsOf ( PluralForms  $previous)

For every form of this rule, the forms of $previous serving the same quantities: $previous->formIndexFor(n) for every sampled n this rule serves with that form.

One: same meaning under both rules; several: it covers quantities of several former forms; none: no sampled n selects it.

Returns
array<int, list<int>> form => forms of $previous (ascending)

Definition at line 243 of file PluralForms.php.

243 : array
244 {
245 $previous_forms = array_fill(0, $this->count, []);
246 foreach ([...range(0, self::SAMPLED_UP_TO), ...self::EXTRA_SAMPLES] as $n) {
247 $previous_forms[$this->formIndexFor($n)][$previous->formIndexFor($n)] = true;
248 }
249
250 return array_map(static function (array $forms): array {
251 $forms = array_keys($forms);
252 sort($forms);
253 return $forms;
254 }, $previous_forms);
255 }

References ILIAS\Language\ComponentTranslation\PluralForms\formIndexFor(), and ILIAS\UI\examples\Symbol\Glyph\Sort\sort().

Referenced by ILIAS\Language\ComponentTranslation\MigratedLanguageFileSync\mapPluralForms().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ selectsSameFormsAs()

ILIAS\Language\ComponentTranslation\PluralForms::selectsSameFormsAs ( PluralForms  $other)

Whether $other has as many forms and selects the same form for every sampled n, e.g.

a header differing only in white space or parentheses.

Definition at line 222 of file PluralForms.php.

222 : bool
223 {
224 if ($other->count !== $this->count) {
225 return false;
226 }
227 foreach ([...range(0, self::SAMPLED_UP_TO), ...self::EXTRA_SAMPLES] as $n) {
228 if ($other->formIndexFor($n) !== $this->formIndexFor($n)) {
229 return false;
230 }
231 }
232
233 return true;
234 }

References ILIAS\Language\ComponentTranslation\PluralForms\formIndexFor().

+ Here is the call graph for this function:

◆ tokenize()

static ILIAS\Language\ComponentTranslation\PluralForms::tokenize ( string  $formula)
staticprivate
Returns
list<string>
Exceptions
InvalidArgumentException

Definition at line 342 of file PluralForms.php.

342 : array
343 {
344 $matched = preg_match_all(
345 '/\s*(?:(\d+)|(n)|(\|\||&&|==|!=|<=|>=|[<>!?:%()]))\s*/A',
346 $formula,
347 $matches,
348 PREG_SET_ORDER
349 );
350 $consumed = 0;
351 $tokens = [];
352 foreach ($matches ?: [] as $match) {
353 $consumed += strlen($match[0]);
354 $token = trim($match[0]);
355 if (ctype_digit($token) && strlen(ltrim($token, '0')) > self::MAX_INTEGER_DIGITS) {
356 throw new InvalidArgumentException('An integer of the plural expression is too large.');
357 }
358 $tokens[] = $token;
359 }
360 if ($matched === false || $consumed !== strlen($formula) || $tokens === []) {
361 throw new InvalidArgumentException('The plural expression contains something other than n, integers, ?:, ||, &&, !, comparisons, % and parentheses.');
362 }
363
364 return $tokens;
365 }

References $token.

Referenced by ILIAS\Language\ComponentTranslation\PluralForms\parse().

+ Here is the caller graph for this function:

Field Documentation

◆ $parsed

array ILIAS\Language\ComponentTranslation\PluralForms::$parsed = []
staticprivate

Definition at line 74 of file PluralForms.php.

◆ BINARY_LEVELS

const array ILIAS\Language\ComponentTranslation\PluralForms::BINARY_LEVELS
private
Initial value:
= [
['||'],
['&&'],
['==', '!='],
['<', '<=', '>', '>='],
['%'],
]

The binary operators by precedence, lowest first (C rules; all left-associative).

Definition at line 65 of file PluralForms.php.

◆ EXTRA_SAMPLES

const array ILIAS\Language\ComponentTranslation\PluralForms::EXTRA_SAMPLES = [1000, 1001, 1011, 100000, 1000000, 1000001, 2000000]
private

Definition at line 59 of file PluralForms.php.

◆ GERMANIC_HEADER

const string ILIAS\Language\ComponentTranslation\PluralForms::GERMANIC_HEADER = 'nplurals=2; plural=(n != 1);'

The fallback for a missing or invalid header: the rule of the Germanic languages.

Definition at line 46 of file PluralForms.php.

Referenced by ILIAS\Language\Tests\ComponentTranslation\PluralFormsTest\testGermanicIsTheDocumentedFallbackHeader().

◆ MAX_COUNT

const int ILIAS\Language\ComponentTranslation\PluralForms::MAX_COUNT = 10
private

Definition at line 48 of file PluralForms.php.

◆ MAX_DEPTH

const int ILIAS\Language\ComponentTranslation\PluralForms::MAX_DEPTH = 64
private

Definition at line 50 of file PluralForms.php.

◆ MAX_FORMULA_LENGTH

const int ILIAS\Language\ComponentTranslation\PluralForms::MAX_FORMULA_LENGTH = 512
private

Definition at line 49 of file PluralForms.php.

◆ MAX_INTEGER_DIGITS

const int ILIAS\Language\ComponentTranslation\PluralForms::MAX_INTEGER_DIGITS = 9
private

Definition at line 51 of file PluralForms.php.

◆ SAMPLED_UP_TO

const int ILIAS\Language\ComponentTranslation\PluralForms::SAMPLED_UP_TO = 200
private

Definition at line 60 of file PluralForms.php.


The documentation for this class was generated from the following file: