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
;