1 /* This is cldrpluralparser 1.0, ported to MediaWiki ResourceLoader */
5 * A parser engine for CLDR plural rules.
7 * Copyright 2012 GPLV3+, Santhosh Thottingal
10 * @source https://github.com/santhoshtr/CLDRPluralRuleParser
11 * @author Santhosh Thottingal <santhosh.thottingal@gmail.com>
13 * @author Amir Aharoni
17 * Evaluates a plural rule in CLDR syntax for a number
20 * @return true|false|null
24 function pluralRuleParser(rule, number) {
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)*
38 digit = 0|1|2|3|4|5|6|7|8|9
39 range = value'..'value
42 // Indicates current position in the rule as we parse through it.
43 // Shared among all parsing functions below.
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');
61 /* console.log.apply(console, arguments);*/
64 debug('pluralRuleParser', rule, number);
66 // Try parsers until one works, if none work return null
67 function choice(parserSyntax) {
69 for (var i = 0; i < parserSyntax.length; i++) {
70 var result = parserSyntax[i]();
71 if (result !== null) {
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;
85 for (var i = 0; i < parserSyntax.length; i++) {
86 var res = parserSyntax[i]();
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) {
100 var originalPos = pos;
103 while (parsed !== null) {
107 if (result.length < n) {
115 // Helpers -- just make parserSyntax out of simpler JS builtin types
117 function makeStringParser(s) {
121 if (rule.substr(pos, len) === s) {
129 function makeRegexParser(regex) {
131 var matches = rule.substr(pos).match(regex);
132 if (matches === null) {
135 pos += matches[0].length;
142 if (result === null) {
143 debug(" -- failed n");
146 result = parseInt(number, 10);
147 debug(" -- passed n ", result);
151 var expression = choice([mod, n]);
154 var result = sequence([n, whitespace, _mod_, whitespace, digits]);
155 if (result === null) {
156 debug(" -- failed mod");
159 debug(" -- passed mod");
160 return parseInt(result[0], 10) % parseInt(result[4], 10);
164 var result = sequence([whitespace, _not_]);
165 if (result === null) {
166 debug(" -- failed not");
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);
180 return result[0] === parseInt(result[5], 10);
183 debug(" -- failed is");
187 function rangeList() {
188 // range_list = (range | value) (',' range_list)*
189 var result = sequence([choice([range, digits]), nOrMore(0, rangeTail)]);
191 if (result !== null) {
192 resultList = resultList.concat(result[0], result[1][0]);
195 debug(" -- failed rangeList");
199 function rangeTail() {
201 var result = sequence([_comma_, rangeList]);
202 if (result !== null) {
205 debug(" -- failed rangeTail");
211 var result = sequence([digits, _range_, digits]);
212 if (result !== null) {
213 debug(" -- passed range");
215 var left = parseInt(result[0], 10);
216 var right = parseInt(result[2], 10);
217 for ( i = left; i <= right; i++) {
222 debug(" -- failed range");
227 // in_relation = expr ('not')? 'in' range_list
228 var result = sequence([expression, nOrMore(0, not), whitespace, _in_, whitespace, rangeList]);
229 if (result !== null) {
230 debug(" -- passed _in");
231 var range_list = result[5];
232 for (var i = 0; i < range_list.length; i++) {
233 if (parseInt(range_list[i], 10) === result[0]) {
234 return (result[1][0] !== 'not');
237 return (result[1][0] === 'not');
239 debug(" -- failed _in ");
244 var result = sequence([expression, whitespace, _within_, whitespace, rangeList]);
245 if (result !== null) {
246 debug(" -- passed within ");
247 var range_list = result[4];
248 return (parseInt( range_list[0],10 )<= result[0] && result[0] <= parseInt( range_list[1], 10));
250 debug(" -- failed within ");
255 var relation = choice([is, _in, within]);
258 var result = sequence([relation, whitespace, _and_, whitespace, condition]);
260 debug(" -- passed and");
261 return result[0] && result[4];
263 debug(" -- failed and");
268 var result = sequence([relation, whitespace, _or_, whitespace, condition]);
270 debug(" -- passed or");
271 return result[0] || result[4];
273 debug(" -- failed or");
277 var condition = choice([and, or, relation]);
280 return parseFloat(n) % 1 === 0;
285 if (!isInt(number)) {
288 var result = condition();
293 var result = start();
296 * For success, the pos must have gotten to the end of the rule
297 * and returned a non-null.
298 * n.b. This is part of language infrastructure, so we do not throw an internationalizable message.
300 if (result === null || pos !== rule.length) {
301 // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result);
307 /* For module loaders, e.g. NodeJS, NPM */
308 if (typeof module !== 'undefined' && module.exports) {
309 module.exports = pluralRuleParser;
312 /* pluralRuleParser ends here */
313 mw.libs.pluralRuleParser = pluralRuleParser;