MediaWiki
REL1_23
|
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 }