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