%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/languages/utils/ |
| Current File : //www/varak.net/wiki.varak.net/languages/utils/CLDRPluralRuleEvaluator.php |
<?php
/**
* Parse and evaluate a plural rule.
*
* UTS #35 Revision 33
* http://www.unicode.org/reports/tr35/tr35-33/tr35-numbers.html#Language_Plural_Rules
*
* @author Niklas Laxström, Tim Starling
*
* @copyright Copyright © 2010-2012, Niklas Laxström
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0
* or later
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*
* @file
* @since 1.20
*/
class CLDRPluralRuleEvaluator {
/**
* Evaluate a number against a set of plural rules. If a rule passes,
* return the index of plural rule.
*
* @param int $number The number to be evaluated against the rules
* @param array $rules The associative array of plural rules in pluralform => rule format.
* @return int The index of the plural form which passed the evaluation
*/
public static function evaluate( $number, array $rules ) {
$rules = self::compile( $rules );
return self::evaluateCompiled( $number, $rules );
}
/**
* Convert a set of rules to a compiled form which is optimised for
* fast evaluation. The result will be an array of strings, and may be cached.
*
* @param array $rules The rules to compile
* @return array An array of compile rules.
*/
public static function compile( array $rules ) {
// We can't use array_map() for this because it generates a warning if
// there is an exception.
foreach ( $rules as &$rule ) {
$rule = CLDRPluralRuleConverter::convert( $rule );
}
return $rules;
}
/**
* Evaluate a compiled set of rules returned by compile(). Do not allow
* the user to edit the compiled form, or else PHP errors may result.
*
* @param string $number The number to be evaluated against the rules, in English, or it
* may be a type convertible to string.
* @param array $rules The associative array of plural rules in pluralform => rule format.
* @return int The index of the plural form which passed the evaluation
*/
public static function evaluateCompiled( $number, array $rules ) {
// Calculate the values of the operand symbols
$number = strval( $number );
if ( !preg_match( '/^ -? ( ([0-9]+) (?: \. ([0-9]+) )? )$/x', $number, $m ) ) {
wfDebug( __METHOD__ . ": invalid number input, returning 'other'\n" );
return count( $rules );
}
if ( !isset( $m[3] ) ) {
$operandSymbols = array(
'n' => intval( $m[1] ),
'i' => intval( $m[1] ),
'v' => 0,
'w' => 0,
'f' => 0,
't' => 0
);
} else {
$absValStr = $m[1];
$intStr = $m[2];
$fracStr = $m[3];
$operandSymbols = array(
'n' => floatval( $absValStr ),
'i' => intval( $intStr ),
'v' => strlen( $fracStr ),
'w' => strlen( rtrim( $fracStr, '0' ) ),
'f' => intval( $fracStr ),
't' => intval( rtrim( $fracStr, '0' ) ),
);
}
// The compiled form is RPN, with tokens strictly delimited by
// spaces, so this is a simple RPN evaluator.
foreach ( $rules as $i => $rule ) {
$stack = array();
$zero = ord( '0' );
$nine = ord( '9' );
foreach ( StringUtils::explode( ' ', $rule ) as $token ) {
$ord = ord( $token );
if ( isset( $operandSymbols[$token] ) ) {
$stack[] = $operandSymbols[$token];
} elseif ( $ord >= $zero && $ord <= $nine ) {
$stack[] = intval( $token );
} else {
$right = array_pop( $stack );
$left = array_pop( $stack );
$result = self::doOperation( $token, $left, $right );
$stack[] = $result;
}
}
if ( $stack[0] ) {
return $i;
}
}
// None of the provided rules match. The number belongs to category
// 'other', which comes last.
return count( $rules );
}
/**
* Do a single operation
*
* @param string $token The token string
* @param mixed $left The left operand. If it is an object, its state may be destroyed.
* @param mixed $right The right operand
* @throws CLDRPluralRuleError
* @return mixed The operation result
*/
private static function doOperation( $token, $left, $right ) {
if ( in_array( $token, array( 'in', 'not-in', 'within', 'not-within' ) ) ) {
if ( !( $right instanceof CLDRPluralRuleEvaluatorRange ) ) {
$right = new CLDRPluralRuleEvaluatorRange( $right );
}
}
switch ( $token ) {
case 'or':
return $left || $right;
case 'and':
return $left && $right;
case 'is':
return $left == $right;
case 'is-not':
return $left != $right;
case 'in':
return $right->isNumberIn( $left );
case 'not-in':
return !$right->isNumberIn( $left );
case 'within':
return $right->isNumberWithin( $left );
case 'not-within':
return !$right->isNumberWithin( $left );
case 'mod':
if ( is_int( $left ) ) {
return (int)fmod( $left, $right );
}
return fmod( $left, $right );
case ',':
if ( $left instanceof CLDRPluralRuleEvaluatorRange ) {
$range = $left;
} else {
$range = new CLDRPluralRuleEvaluatorRange( $left );
}
$range->add( $right );
return $range;
case '..':
return new CLDRPluralRuleEvaluatorRange( $left, $right );
default:
throw new CLDRPluralRuleError( "Invalid RPN token" );
}
}
}