441bc91f68027132af764baa68026c6489092d64
[lhc/web/wiklou.git] / resources / mediawiki.libs / CLDRPluralRuleParser.js
1 /* This is cldrpluralparser 1.0, ported to MediaWiki ResourceLoader */
2
3 /**
4 * cldrpluralparser.js
5 * A parser engine for CLDR plural rules.
6 *
7 * Copyright 2012 GPLV3+, Santhosh Thottingal
8 *
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
14 */
15
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 ) {
23
24 function pluralRuleParser(rule, number) {
25 /*
26 Syntax: see http://unicode.org/reports/tr35/#Language_Plural_Rules
27 -----------------------------------------------------------------
28
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
40
41 */
42 // Indicates current position in the rule as we parse through it.
43 // Shared among all parsing functions below.
44 var pos = 0;
45
46 var whitespace = makeRegexParser(/^\s+/);
47 var digits = makeRegexParser(/^\d+/);
48
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');
59
60 function debug() {
61 /* console.log.apply(console, arguments);*/
62 }
63
64 debug('pluralRuleParser', rule, number);
65
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 }
78
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 }
95
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 }
114
115 // Helpers -- just make parserSyntax out of simpler JS builtin types
116
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 }
128
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 }
139
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 }
150
151 var expression = choice([mod, n]);
152
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 }
162
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 }
172
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 }
186
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 }
198
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 }
208
209 function range() {
210 var i;
211 var result = sequence([digits, _range_, digits]);
212 if (result !== null) {
213 debug(" -- passed range");
214 var array = [];
215 var left = parseInt(result[0], 10);
216 var right = parseInt(result[2], 10);
217 for ( i = left; i <= right; i++) {
218 array.push(i);
219 }
220 return array;
221 }
222 debug(" -- failed range");
223 return null;
224 }
225
226 function _in() {
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');
235 }
236 }
237 return (result[1][0] === 'not');
238 }
239 debug(" -- failed _in ");
240 return null;
241 }
242
243 function within() {
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));
249 }
250 debug(" -- failed within ");
251 return null;
252 }
253
254
255 var relation = choice([is, _in, within]);
256
257 function and() {
258 var result = sequence([relation, whitespace, _and_, whitespace, condition]);
259 if (result) {
260 debug(" -- passed and");
261 return result[0] && result[4];
262 }
263 debug(" -- failed and");
264 return null;
265 }
266
267 function or() {
268 var result = sequence([relation, whitespace, _or_, whitespace, condition]);
269 if (result) {
270 debug(" -- passed or");
271 return result[0] || result[4];
272 }
273 debug(" -- failed or");
274 return null;
275 }
276
277 var condition = choice([and, or, relation]);
278
279 function isInt(n) {
280 return parseFloat(n) % 1 === 0;
281 }
282
283
284 function start() {
285 if (!isInt(number)) {
286 return false;
287 }
288 var result = condition();
289 return result;
290 }
291
292
293 var result = start();
294
295 /*
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.
299 */
300 if (result === null || pos !== rule.length) {
301 // throw new Error("Parse error at position " + pos.toString() + " in input: " + rule + " result is " + result);
302 }
303
304 return result;
305 }
306
307 /* For module loaders, e.g. NodeJS, NPM */
308 if (typeof module !== 'undefined' && module.exports) {
309 module.exports = pluralRuleParser;
310 }
311
312 /* pluralRuleParser ends here */
313 mw.libs.pluralRuleParser = pluralRuleParser;
314
315 } )( mediaWiki );