Merge "Added missing @since tags to all methods"
[mediawiki.git] / resources / mediawiki.libs / CLDRPluralRuleParser.js
blobbb1491d304985c68d62add94bb5f3982ad005f76
1 /* This is cldrpluralparser 1.0, ported to MediaWiki ResourceLoader */
3 /**
4 * cldrpluralparser.js
5 * A parser engine for CLDR plural rules.
7 * Copyright 2012 GPLV3+, Santhosh Thottingal
9 * @version 0.1.0-alpha
10 * @source https://github.com/santhoshtr/CLDRPluralRuleParser
11 * @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
12 * @author Timo Tijhof
13 * @author Amir Aharoni
16 /**
17  * Evaluates a plural rule in CLDR syntax for a number
18  * @param rule
19  * @param number
20  * @return true|false|null
21  */
22 ( function( mw ) {
24 function pluralRuleParser(rule, number) {
25         /*
26         Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
27         -----------------------------------------------------------------
29         condition     = and_condition ('or' and_condition)*
30         and_condition = relation ('and' relation)*
31         relation      = is_relation | in_relation | within_relation | 'n' <EOL>
32         is_relation   = expr 'is' ('not')? value
33         in_relation   = expr ('not')? 'in' range_list
34         within_relation = expr ('not')? 'within' range_list
35         expr          = 'n' ('mod' value)?
36         range_list    = (range | value) (',' range_list)*
37         value         = digit+
38         digit         = 0|1|2|3|4|5|6|7|8|9
39         range         = value'..'value
41         */
42         // Indicates current position in the rule as we parse through it.
43         // Shared among all parsing functions below.
44         var pos = 0;
46         var whitespace = makeRegexParser(/^\s+/);
47         var digits = makeRegexParser(/^\d+/);
49         var _n_ = makeStringParser('n');
50         var _is_ = makeStringParser('is');
51         var _mod_ = makeStringParser('mod');
52         var _not_ = makeStringParser('not');
53         var _in_ = makeStringParser('in');
54         var _within_ = makeStringParser('within');
55         var _range_ = makeStringParser('..');
56         var _comma_ = makeStringParser(',');
57         var _or_ = makeStringParser('or');
58         var _and_ = makeStringParser('and');
60         function debug() {
61                 /* console.log.apply(console, arguments);*/
62         }
64         debug('pluralRuleParser', rule, number);
66         // Try parsers until one works, if none work return null
67         function choice(parserSyntax) {
68                 return function () {
69                         for (var i = 0; i < parserSyntax.length; i++) {
70                                 var result = parserSyntax[i]();
71                                 if (result !== null) {
72                                         return result;
73                                 }
74                         }
75                         return null;
76                 };
77         }
79         // Try several parserSyntax-es in a row.
80         // All must succeed; otherwise, return null.
81         // This is the only eager one.
82         function sequence(parserSyntax) {
83                 var originalPos = pos;
84                 var result = [];
85                 for (var i = 0; i < parserSyntax.length; i++) {
86                         var res = parserSyntax[i]();
87                         if (res === null) {
88                                 pos = originalPos;
89                                 return null;
90                         }
91                         result.push(res);
92                 }
93                 return result;
94         }
96         // Run the same parser over and over until it fails.
97         // Must succeed a minimum of n times; otherwise, return null.
98         function nOrMore(n, p) {
99                 return function () {
100                         var originalPos = pos;
101                         var result = [];
102                         var parsed = p();
103                         while (parsed !== null) {
104                                 result.push(parsed);
105                                 parsed = p();
106                         }
107                         if (result.length < n) {
108                                 pos = originalPos;
109                                 return null;
110                         }
111                         return result;
112                 };
113         }
115         // Helpers -- just make parserSyntax out of simpler JS builtin types
117         function makeStringParser(s) {
118                 var len = s.length;
119                 return function () {
120                         var result = null;
121                         if (rule.substr(pos, len) === s) {
122                                 result = s;
123                                 pos += len;
124                         }
125                         return result;
126                 };
127         }
129         function makeRegexParser(regex) {
130                 return function () {
131                         var matches = rule.substr(pos).match(regex);
132                         if (matches === null) {
133                                 return null;
134                         }
135                         pos += matches[0].length;
136                         return matches[0];
137                 };
138         }
140         function n() {
141                 var result = _n_();
142                 if (result === null) {
143                         debug(" -- failed n");
144                         return result;
145                 }
146                 result = parseInt(number, 10);
147                 debug(" -- passed n ", result);
148                 return result;
149         }
151         var expression = choice([mod, n]);
153         function mod() {
154                 var result = sequence([n, whitespace, _mod_, whitespace, digits]);
155                 if (result === null) {
156                         debug(" -- failed mod");
157                         return null;
158                 }
159                 debug(" -- passed mod");
160                 return parseInt(result[0], 10) % parseInt(result[4], 10);
161         }
163         function not() {
164                 var result = sequence([whitespace, _not_]);
165                 if (result === null) {
166                         debug(" -- failed not");
167                         return null;
168                 } else {
169                         return result[1];
170                 }
171         }
173         function is() {
174                 var result = sequence([expression, whitespace, _is_, nOrMore(0, not), whitespace, digits]);
175                 if (result !== null) {
176                         debug(" -- passed is");
177                         if (result[3][0] === 'not') {
178                                 return result[0] !== parseInt(result[5], 10);
179                         } else {
180                                 return result[0] === parseInt(result[5], 10);
181                         }
182                 }
183                 debug(" -- failed is");
184                 return null;
185         }
187         function rangeList() {
188                 // range_list    = (range | value) (',' range_list)*
189                 var result = sequence([choice([range, digits]), nOrMore(0, rangeTail)]);
190                 var resultList = [];
191                 if (result !== null) {
192                         resultList = resultList.concat(result[0], result[1][0]);
193                         return resultList;
194                 }
195                 debug(" -- failed rangeList");
196                 return null;
197         }
199         function rangeTail() {
200                 // ',' range_list
201                 var result = sequence([_comma_, rangeList]);
202                 if (result !== null) {
203                         return result[1];
204                 }
205                 debug(" -- failed rangeTail");
206                 return null;
207         }
209         function range() {
210                 var result = sequence([digits, _range_, digits]);
211                 if (result !== null) {
212                         debug(" -- passed range");
213                         var array = [];
214                         var left = parseInt(result[0], 10);
215                         var right = parseInt(result[2], 10);
216                         for ( i = left; i <= right; i++) {
217                                 array.push(i);
218                         }
219                         return array;
220                 }
221                 debug(" -- failed range");
222                 return null;
223         }
225         function _in() {
226                 // in_relation   = expr ('not')? 'in' range_list
227                 var result = sequence([expression, nOrMore(0, not), whitespace, _in_, whitespace, rangeList]);
228                 if (result !== null) {
229                         debug(" -- passed _in");
230                         var range_list = result[5];
231                         for (var i = 0; i < range_list.length; i++) {
232                                 if (parseInt(range_list[i], 10) === result[0]) {
233                                         return (result[1][0] !== 'not');
234                                 }
235                         }
236                         return (result[1][0] === 'not');
237                 }
238                 debug(" -- failed _in ");
239                 return null;
240         }
242         function within() {
243                 var result = sequence([expression, whitespace, _within_, whitespace, rangeList]);
244                 if (result !== null) {
245                         debug(" -- passed within ");
246                         var range_list = result[4];
247                         return (parseInt( range_list[0],10 )<= result[0] && result[0] <= parseInt( range_list[1], 10));
248                 }
249                 debug(" -- failed within ");
250                 return null;
251         }
254         var relation = choice([is, _in, within]);
256         function and() {
257                 var result = sequence([relation, whitespace, _and_, whitespace, condition]);
258                 if (result) {
259                         debug(" -- passed and");
260                         return result[0] && result[4];
261                 }
262                 debug(" -- failed and");
263                 return null;
264         }
266         function or() {
267                 var result = sequence([relation, whitespace, _or_, whitespace, condition]);
268                 if (result) {
269                         debug(" -- passed or");
270                         return result[0] || result[4];
271                 }
272                 debug(" -- failed or");
273                 return null;
274         }
276         var condition = choice([and, or, relation]);
278         function start() {
279                 var result = condition();
280                 return result;
281         }
284         var result = start();
286         /*
287          * For success, the pos must have gotten to the end of the rule
288          * and returned a non-null.
289          * n.b. This is part of language infrastructure, so we do not throw an internationalizable message.
290          */
291         if (result === null || pos !== rule.length) {
292                 // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result);
293         }
295         return result;
298 /* For module loaders, e.g. NodeJS, NPM */
299 if (typeof module !== 'undefined' && module.exports) {
300         module.exports = pluralRuleParser;
303 /* pluralRuleParser ends here */
304 mw.libs.pluralRuleParser = pluralRuleParser;
306 } )( mediaWiki );