CLDR Plural rules based plural form calculation
[lhc/web/wiklou.git] / languages / utils / CLDRPluralRuleEvaluator.php
1 <?php
2 /**
3 * Parse and evaluate a plural rule
4 *
5 * @author Niklas Laxstrom
6 *
7 * @copyright Copyright © 2010-2012, Niklas Laxström
8 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
9 * @file
10 */
11
12 class CLDRPluralRuleEvaluator {
13 /**
14 * Evaluate a number against a set of plural rules. If a rule passes,
15 * return the index of plural rule.
16 *
17 * @param int The number to be evaluated against the rules
18 * @param array The associative array of plural rules in pluralform => rule format.
19 * @return int The index of the plural form which passed the evaluation
20 */
21 public static function evaluate( $number, $rules ) {
22 $formIndex = 0;
23 if ( !$rules ) {
24 return 0;
25 }
26 foreach ( $rules as $form => $rule ) {
27 $parsedRule = self::parseCLDRRule( $rule, $number );
28 // FIXME eval is bad.
29 if ( eval( "return $parsedRule;" ) ) {
30 return $formIndex;
31 }
32 $formIndex++;
33 }
34 return $formIndex;
35 }
36 private static function parseCLDRRule( $rule ) {
37 $rule = preg_replace( '/\bn\b/', '$number', $rule );
38 $rule = preg_replace( '/([^ ]+) mod (\d+)/', 'self::mod(\1,\2)', $rule );
39 $rule = preg_replace( '/([^ ]+) is not (\d+)/' , '\1!=\2', $rule );
40 $rule = preg_replace( '/([^ ]+) is (\d+)/', '\1==\2', $rule );
41 $rule = preg_replace( '/([^ ]+) not in (\d+)\.\.(\d+)/', '!self::in(\1,\2,\3)', $rule );
42 $rule = preg_replace( '/([^ ]+) not within (\d+)\.\.(\d+)/', '!self::within(\1,\2,\3)', $rule );
43 $rule = preg_replace( '/([^ ]+) in (\d+)\.\.(\d+)/', 'self::in(\1,\2,\3)', $rule );
44 $rule = preg_replace( '/([^ ]+) within (\d+)\.\.(\d+)/', 'self::within(\1,\2,\3)', $rule );
45 // AND takes precedence over OR
46 $andrule = '/([^ ]+) and ([^ ]+)/i';
47 while ( preg_match( $andrule, $rule ) ) {
48 $rule = preg_replace( $andrule, '(\1&&\2)', $rule );
49 }
50 $orrule = '/([^ ]+) or ([^ ]+)/i';
51 while ( preg_match( $orrule, $rule ) ) {
52 $rule = preg_replace( $orrule, '(\1||\2)', $rule );
53 }
54
55 return $rule;
56 }
57
58 private static function in( $num, $low, $high ) {
59 return is_int( $num ) && $num >= $low && $num <= $high;
60 }
61
62 private static function within( $num, $low, $high ) {
63 return $num >= $low && $num <= $high;
64 }
65
66 private static function mod( $num, $mod ) {
67 if ( is_int( $num ) ) {
68 return (int) fmod( $num, $mod );
69 }
70 return fmod( $num, $mod );
71 }
72 }