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