MediaWiki  REL1_24
CLDRPluralRuleEvaluator.php
Go to the documentation of this file.
00001 <?php
00002 
00034 class CLDRPluralRuleEvaluator {
00043     public static function evaluate( $number, array $rules ) {
00044         $rules = self::compile( $rules );
00045 
00046         return self::evaluateCompiled( $number, $rules );
00047     }
00048 
00056     public static function compile( array $rules ) {
00057         // We can't use array_map() for this because it generates a warning if
00058         // there is an exception.
00059         foreach ( $rules as &$rule ) {
00060             $rule = CLDRPluralRuleConverter::convert( $rule );
00061         }
00062 
00063         return $rules;
00064     }
00065 
00075     public static function evaluateCompiled( $number, array $rules ) {
00076         // Calculate the values of the operand symbols
00077         $number = strval( $number );
00078         if ( !preg_match( '/^ -? ( ([0-9]+) (?: \. ([0-9]+) )? )$/x', $number, $m ) ) {
00079             wfDebug( __METHOD__ . ": invalid number input, returning 'other'\n" );
00080 
00081             return count( $rules );
00082         }
00083         if ( !isset( $m[3] ) ) {
00084             $operandSymbols = array(
00085                 'n' => intval( $m[1] ),
00086                 'i' => intval( $m[1] ),
00087                 'v' => 0,
00088                 'w' => 0,
00089                 'f' => 0,
00090                 't' => 0
00091             );
00092         } else {
00093             $absValStr = $m[1];
00094             $intStr = $m[2];
00095             $fracStr = $m[3];
00096             $operandSymbols = array(
00097                 'n' => floatval( $absValStr ),
00098                 'i' => intval( $intStr ),
00099                 'v' => strlen( $fracStr ),
00100                 'w' => strlen( rtrim( $fracStr, '0' ) ),
00101                 'f' => intval( $fracStr ),
00102                 't' => intval( rtrim( $fracStr, '0' ) ),
00103             );
00104         }
00105 
00106         // The compiled form is RPN, with tokens strictly delimited by
00107         // spaces, so this is a simple RPN evaluator.
00108         foreach ( $rules as $i => $rule ) {
00109             $stack = array();
00110             $zero = ord( '0' );
00111             $nine = ord( '9' );
00112             foreach ( StringUtils::explode( ' ', $rule ) as $token ) {
00113                 $ord = ord( $token );
00114                 if ( isset( $operandSymbols[$token] ) ) {
00115                     $stack[] = $operandSymbols[$token];
00116                 } elseif ( $ord >= $zero && $ord <= $nine ) {
00117                     $stack[] = intval( $token );
00118                 } else {
00119                     $right = array_pop( $stack );
00120                     $left = array_pop( $stack );
00121                     $result = self::doOperation( $token, $left, $right );
00122                     $stack[] = $result;
00123                 }
00124             }
00125             if ( $stack[0] ) {
00126                 return $i;
00127             }
00128         }
00129         // None of the provided rules match. The number belongs to category
00130         // 'other', which comes last.
00131         return count( $rules );
00132     }
00133 
00143     private static function doOperation( $token, $left, $right ) {
00144         if ( in_array( $token, array( 'in', 'not-in', 'within', 'not-within' ) ) ) {
00145             if ( !( $right instanceof CLDRPluralRuleEvaluatorRange ) ) {
00146                 $right = new CLDRPluralRuleEvaluatorRange( $right );
00147             }
00148         }
00149         switch ( $token ) {
00150             case 'or':
00151                 return $left || $right;
00152             case 'and':
00153                 return $left && $right;
00154             case 'is':
00155                 return $left == $right;
00156             case 'is-not':
00157                 return $left != $right;
00158             case 'in':
00159                 return $right->isNumberIn( $left );
00160             case 'not-in':
00161                 return !$right->isNumberIn( $left );
00162             case 'within':
00163                 return $right->isNumberWithin( $left );
00164             case 'not-within':
00165                 return !$right->isNumberWithin( $left );
00166             case 'mod':
00167                 if ( is_int( $left ) ) {
00168                     return (int)fmod( $left, $right );
00169                 }
00170 
00171                 return fmod( $left, $right );
00172             case ',':
00173                 if ( $left instanceof CLDRPluralRuleEvaluatorRange ) {
00174                     $range = $left;
00175                 } else {
00176                     $range = new CLDRPluralRuleEvaluatorRange( $left );
00177                 }
00178                 $range->add( $right );
00179 
00180                 return $range;
00181             case '..':
00182                 return new CLDRPluralRuleEvaluatorRange( $left, $right );
00183             default:
00184                 throw new CLDRPluralRuleError( "Invalid RPN token" );
00185         }
00186     }
00187 }